Browse Source

refactor: improving structure, moving things into a core module

Torkel Ödegaard 10 years ago
parent
commit
5545cdbf4d
39 changed files with 1193 additions and 1244 deletions
  1. 0 1
      public/app/app.js
  2. 17 1
      public/app/core/core.ts
  3. 1 1
      public/app/core/core_module.ts
  4. 4 6
      public/app/core/directives/annotation_tooltip.js
  5. 1 1
      public/app/core/directives/array_join.ts
  6. 48 0
      public/app/core/directives/body_class.js
  7. 46 0
      public/app/core/directives/config_modal.js
  8. 4 7
      public/app/core/directives/confirm_click.js
  9. 103 0
      public/app/core/directives/dash_edit_link.js
  10. 4 6
      public/app/core/directives/dash_upload.js
  11. 113 0
      public/app/core/directives/dropdown_typeahead.js
  12. 1 1
      public/app/core/directives/give_focus.ts
  13. 31 0
      public/app/core/directives/grafana_version_check.js
  14. 212 0
      public/app/core/directives/metric_segment.js
  15. 122 0
      public/app/core/directives/misc.js
  16. 54 0
      public/app/core/directives/ng_model_on_blur.js
  17. 45 0
      public/app/core/directives/password_strenght.js
  18. 41 0
      public/app/core/directives/spectrum_picker.js
  19. 5 8
      public/app/core/directives/tags.js
  20. 50 0
      public/app/core/directives/topnav.js
  21. 284 0
      public/app/core/directives/value_select_dropdown.js
  22. 1 1
      public/app/core/filters/filters.ts
  23. 0 18
      public/app/directives/all.js
  24. 0 50
      public/app/directives/bodyClass.js
  25. 0 48
      public/app/directives/configModal.js
  26. 0 107
      public/app/directives/dashEditLink.js
  27. 0 115
      public/app/directives/dropdown.typeahead.js
  28. 0 33
      public/app/directives/grafanaVersionCheck.js
  29. 0 216
      public/app/directives/metric.segment.js
  30. 0 132
      public/app/directives/misc.js
  31. 0 54
      public/app/directives/ngModelOnBlur.js
  32. 0 47
      public/app/directives/passwordStrenght.js
  33. 0 42
      public/app/directives/spectrumPicker.js
  34. 0 53
      public/app/directives/topnav.js
  35. 0 289
      public/app/directives/valueSelectDropdown.js
  36. 2 2
      public/app/partials/login.html
  37. 1 1
      public/app/partials/signup_step2.html
  38. 2 3
      public/test/specs/value_select_dropdown_specs.js
  39. 1 1
      public/test/test-main.js

+ 0 - 1
public/app/app.js

@@ -73,7 +73,6 @@ function (angular, $, _, appLevelRequire) {
     'services/all',
     'features/all',
     'controllers/all',
-    'directives/all',
     'components/partials',
     'routes/all',
   ];

+ 17 - 1
public/app/core/core.ts

@@ -1,6 +1,22 @@
+///<amd-dependency path="./directives/annotation_tooltip" />
+///<amd-dependency path="./directives/body_class" />
+///<amd-dependency path="./directives/config_modal" />
+///<amd-dependency path="./directives/confirm_click" />
+///<amd-dependency path="./directives/dash_edit_link" />
+///<amd-dependency path="./directives/dash_upload" />
+///<amd-dependency path="./directives/dropdown_typeahead" />
+///<amd-dependency path="./directives/grafana_version_check" />
+///<amd-dependency path="./directives/metric_segment" />
+///<amd-dependency path="./directives/misc" />
+///<amd-dependency path="./directives/ng_model_on_blur" />
+///<amd-dependency path="./directives/password_strenght" />
+///<amd-dependency path="./directives/spectrum_picker" />
+///<amd-dependency path="./directives/tags" />
+///<amd-dependency path="./directives/topnav" />
+///<amd-dependency path="./directives/value_select_dropdown" />
 
 export * from './directives/array_join'
-export * from './directives/giveFocus'
+export * from './directives/give_focus'
 
 export * from './routes/module_loader'
 export * from './filters/filters'

+ 1 - 1
public/app/core/core_module.ts

@@ -2,4 +2,4 @@
 
 import angular = require('angular');
 
-export default angular.module('grafana.core', []);
+export = angular.module('grafana.core', []);

+ 4 - 6
public/app/directives/annotationTooltip.js → public/app/core/directives/annotation_tooltip.js

@@ -1,14 +1,12 @@
 define([
-  'angular',
   'jquery',
-  'lodash'
+  'lodash',
+  '../core_module',
 ],
-function (angular, $, _) {
+function ($, _, coreModule) {
   'use strict';
 
-  angular
-  .module('grafana.directives')
-  .directive('annotationTooltip', function($sanitize, dashboardSrv, $compile) {
+  coreModule.directive('annotationTooltip', function($sanitize, dashboardSrv, $compile) {
 
     function sanitizeString(str) {
       try {

+ 1 - 1
public/app/core/directives/array_join.ts

@@ -2,7 +2,7 @@
 
 import angular = require('angular');
 import _ = require('lodash');
-import coreModule from '../core_module';
+import coreModule = require('../core_module');
 
 export function arrayJoin() {
   'use strict';

+ 48 - 0
public/app/core/directives/body_class.js

@@ -0,0 +1,48 @@
+define([
+  'lodash',
+  'jquery',
+  '../core_module',
+],
+function (_, $, coreModule) {
+  'use strict';
+
+  coreModule.directive('bodyClass', function() {
+    return {
+      link: function($scope, elem) {
+
+        var lastHideControlsVal;
+
+        // tooltip removal fix
+        $scope.$on("$routeChangeSuccess", function() {
+          $("#tooltip, .tooltip").remove();
+        });
+
+        $scope.$watch('submenuEnabled', function() {
+          if (!$scope.dashboard) {
+            return;
+          }
+
+          elem.toggleClass('submenu-controls-visible', $scope.submenuEnabled);
+        });
+
+        $scope.$watch('dashboard.hideControls', function() {
+          if (!$scope.dashboard) {
+            return;
+          }
+
+          var hideControls = $scope.dashboard.hideControls || $scope.playlist_active;
+
+          if (lastHideControlsVal !== hideControls) {
+            elem.toggleClass('hide-controls', hideControls);
+            lastHideControlsVal = hideControls;
+          }
+        });
+
+        $scope.$watch('playlistSrv', function(newValue) {
+          elem.toggleClass('playlist-active', _.isObject(newValue));
+        });
+      }
+    };
+  });
+
+});

+ 46 - 0
public/app/core/directives/config_modal.js

@@ -0,0 +1,46 @@
+define([
+  'lodash',
+  'jquery',
+  '../core_module',
+],
+function (_, $, coreModule) {
+  'use strict';
+
+  coreModule.directive('configModal', function($modal, $q, $timeout) {
+    return {
+      restrict: 'A',
+      link: function(scope, elem, attrs) {
+        var partial = attrs.configModal;
+        var id = '#' + partial.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id;
+
+        elem.bind('click',function() {
+          if ($(id).length) {
+            elem.attr('data-target', id).attr('data-toggle', 'modal');
+            scope.$apply(function() { scope.$broadcast('modal-opened'); });
+            return;
+          }
+
+          var panelModal = $modal({
+            template: partial,
+            persist: false,
+            show: false,
+            scope: scope.$new(),
+            keyboard: false
+          });
+
+          $q.when(panelModal).then(function(modalEl) {
+            elem.attr('data-target', id).attr('data-toggle', 'modal');
+
+            $timeout(function () {
+              if (!modalEl.data('modal').isShown) {
+                modalEl.modal('show');
+              }
+            }, 50);
+          });
+
+          scope.$apply();
+        });
+      }
+    };
+  });
+});

+ 4 - 7
public/app/directives/confirmClick.js → public/app/core/directives/confirm_click.js

@@ -1,13 +1,10 @@
 define([
-  'angular',
-  'kbn'
+  '../core_module',
 ],
-function (angular) {
+function (coreModule) {
   'use strict';
 
-  var module = angular.module('grafana.directives');
-
-  module.directive('confirmClick', function() {
+  coreModule.directive('confirmClick', function() {
     return {
       restrict: 'A',
       link: function(scope, elem, attrs) {
@@ -23,4 +20,4 @@ function (angular) {
       },
     };
   });
-});
+});

+ 103 - 0
public/app/core/directives/dash_edit_link.js

@@ -0,0 +1,103 @@
+define([
+  'jquery',
+  '../core_module',
+],
+function ($, coreModule) {
+  'use strict';
+
+  var editViewMap = {
+    'settings':    { src: 'app/features/dashboard/partials/settings.html', title: "Settings" },
+    'annotations': { src: 'app/features/annotations/partials/editor.html', title: "Annotations" },
+    'templating':  { src: 'app/features/templating/partials/editor.html', title: "Templating" }
+  };
+
+  coreModule.directive('dashEditorLink', function($timeout) {
+    return {
+      restrict: 'A',
+      link: function(scope, elem, attrs) {
+        var partial = attrs.dashEditorLink;
+
+        elem.bind('click',function() {
+          $timeout(function() {
+            var editorScope = attrs.editorScope === 'isolated' ? null : scope;
+            scope.appEvent('show-dash-editor', { src: partial, scope: editorScope });
+          });
+        });
+      }
+    };
+  });
+
+  coreModule.directive('dashEditorView', function($compile, $location) {
+    return {
+      restrict: 'A',
+      link: function(scope, elem) {
+        var editorScope;
+        var lastEditor;
+
+        function hideEditorPane() {
+          if (editorScope) { editorScope.dismiss(); }
+        }
+
+        function showEditorPane(evt, payload, editview) {
+          if (editview) {
+            scope.contextSrv.editview = editViewMap[editview];
+            payload.src = scope.contextSrv.editview.src;
+          }
+
+          if (lastEditor === payload.src) {
+            hideEditorPane();
+            return;
+          }
+
+          hideEditorPane();
+
+          scope.exitFullscreen();
+
+          lastEditor = payload.src;
+          editorScope = payload.scope ? payload.scope.$new() : scope.$new();
+
+          editorScope.dismiss = function() {
+            editorScope.$destroy();
+            elem.empty();
+            lastEditor = null;
+            editorScope = null;
+
+            if (editview) {
+              var urlParams = $location.search();
+              if (editview === urlParams.editview) {
+                delete urlParams.editview;
+                $location.search(urlParams);
+              }
+            }
+          };
+
+          var src = "'" + payload.src + "'";
+          var view = $('<div class="gf-box" ng-include="' + src + '"></div>');
+
+          if (payload.cssClass) {
+            view.addClass(payload.cssClass);
+          }
+
+          elem.append(view);
+          $compile(elem.contents())(editorScope);
+        }
+
+        scope.$watch("dashboardViewState.state.editview", function(newValue, oldValue) {
+          if (newValue) {
+            showEditorPane(null, {}, newValue);
+          } else if (oldValue) {
+            scope.contextSrv.editview = null;
+            if (lastEditor === editViewMap[oldValue]) {
+              hideEditorPane();
+            }
+          }
+        });
+
+        scope.contextSrv.editview = null;
+        scope.$on("$destroy", hideEditorPane);
+        scope.onAppEvent('hide-dash-editor', hideEditorPane);
+        scope.onAppEvent('show-dash-editor', showEditorPane);
+      }
+    };
+  });
+});

+ 4 - 6
public/app/directives/dashUpload.js → public/app/core/directives/dash_upload.js

@@ -1,13 +1,11 @@
 define([
-  'angular',
-  'kbn'
+  'kbn',
+  '../core_module',
 ],
-function (angular, kbn) {
+function (kbn, coreModule) {
   'use strict';
 
-  var module = angular.module('grafana.directives');
-
-  module.directive('dashUpload', function(timer, alertSrv, $location) {
+  coreModule.directive('dashUpload', function(timer, alertSrv, $location) {
     return {
       restrict: 'A',
       link: function(scope) {

+ 113 - 0
public/app/core/directives/dropdown_typeahead.js

@@ -0,0 +1,113 @@
+define([
+  'lodash',
+  'jquery',
+  '../core_module',
+],
+function (_, $, coreModule) {
+  'use strict';
+
+  coreModule.directive('dropdownTypeahead', function($compile) {
+
+    var inputTemplate = '<input type="text"'+
+      ' class="tight-form-input input-medium tight-form-input"' +
+      ' spellcheck="false" style="display:none"></input>';
+
+    var buttonTemplate = '<a  class="tight-form-item tight-form-func dropdown-toggle"' +
+      ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
+      ' data-placement="top"><i class="fa fa-plus"></i></a>';
+
+    return {
+      scope: {
+        menuItems: "=dropdownTypeahead",
+        dropdownTypeaheadOnSelect: "&dropdownTypeaheadOnSelect",
+        model: '=ngModel'
+      },
+      link: function($scope, elem, attrs) {
+        var $input = $(inputTemplate);
+        var $button = $(buttonTemplate);
+        $input.appendTo(elem);
+        $button.appendTo(elem);
+
+        if (attrs.linkText) {
+          $button.html(attrs.linkText);
+        }
+
+        if (attrs.ngModel) {
+          $scope.$watch('model', function(newValue) {
+            _.each($scope.menuItems, function(item) {
+              _.each(item.submenu, function(subItem) {
+                if (subItem.value === newValue) {
+                  $button.html(subItem.text);
+                }
+              });
+            });
+          });
+        }
+
+        var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) {
+          _.each(value.submenu, function(item, subIndex) {
+            item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
+            memo.push(value.text + ' ' + item.text);
+          });
+          return memo;
+        }, []);
+
+        $scope.menuItemSelected = function(index, subIndex) {
+          var item = $scope.menuItems[index];
+          $scope.dropdownTypeaheadOnSelect({$item: item, $subItem: item.submenu[subIndex]});
+        };
+
+        $input.attr('data-provide', 'typeahead');
+        $input.typeahead({
+          source: typeaheadValues,
+          minLength: 1,
+          items: 10,
+          updater: function (value) {
+            var result = {};
+            _.each($scope.menuItems, function(menuItem) {
+              _.each(menuItem.submenu, function(submenuItem) {
+                if (value === (menuItem.text + ' ' + submenuItem.text)) {
+                  result.$item = menuItem;
+                  result.$subItem = submenuItem;
+                }
+              });
+            });
+
+            if (result.$item) {
+              $scope.$apply(function() {
+                $scope.dropdownTypeaheadOnSelect(result);
+              });
+            }
+
+            $input.trigger('blur');
+            return '';
+          }
+        });
+
+        $button.click(function() {
+          $button.hide();
+          $input.show();
+          $input.focus();
+        });
+
+        $input.keyup(function() {
+          elem.toggleClass('open', $input.val() === '');
+        });
+
+        $input.blur(function() {
+          $input.hide();
+          $input.val('');
+          $button.show();
+          $button.focus();
+          // clicking the function dropdown menu wont
+          // work if you remove class at once
+          setTimeout(function() {
+            elem.removeClass('open');
+          }, 200);
+        });
+
+        $compile(elem.contents())($scope);
+      }
+    };
+  });
+});

+ 1 - 1
public/app/core/directives/giveFocus.ts → public/app/core/directives/give_focus.ts

@@ -1,7 +1,7 @@
 ///<reference path="../../headers/common.d.ts" />
 
 import angular = require('angular');
-import coreModule from '../core_module';
+import coreModule = require('../core_module');
 
 coreModule.directive('giveFocus', function() {
   return function(scope, element, attrs) {

+ 31 - 0
public/app/core/directives/grafana_version_check.js

@@ -0,0 +1,31 @@
+define([
+  '../core_module',
+],
+function (coreModule) {
+  'use strict';
+
+  coreModule.directive('grafanaVersionCheck', function($http, contextSrv) {
+    return {
+      restrict: 'A',
+      link: function(scope, elem) {
+        if (contextSrv.version === 'master') {
+          return;
+        }
+
+        $http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' })
+        .then(function(response) {
+          if (!response.data || !response.data.version) {
+            return;
+          }
+
+          if (contextSrv.version !== response.data.version) {
+            elem.append('<i class="icon-info-sign"></i> ' +
+                        '<a href="http://grafana.org/download" target="_blank"> ' +
+            'New version available: ' + response.data.version +
+              '</a>');
+          }
+        });
+      }
+    };
+  });
+});

+ 212 - 0
public/app/core/directives/metric_segment.js

@@ -0,0 +1,212 @@
+define([
+  'lodash',
+  'jquery',
+  '../core_module',
+],
+function (_, $, coreModule) {
+  'use strict';
+
+  coreModule.directive('metricSegment', function($compile, $sce) {
+    var inputTemplate = '<input type="text" data-provide="typeahead" ' +
+      ' class="tight-form-clear-input input-medium"' +
+      ' spellcheck="false" style="display:none"></input>';
+
+    var buttonTemplate = '<a class="tight-form-item" ng-class="segment.cssClass" ' +
+      'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
+
+    return {
+      scope: {
+        segment: "=",
+        getOptions: "&",
+        onChange: "&",
+      },
+
+      link: function($scope, elem) {
+        var $input = $(inputTemplate);
+        var $button = $(buttonTemplate);
+        var segment = $scope.segment;
+        var options = null;
+        var cancelBlur = null;
+
+        $input.appendTo(elem);
+        $button.appendTo(elem);
+
+        $scope.updateVariableValue = function(value) {
+          if (value === '' || segment.value === value) {
+            return;
+          }
+
+          $scope.$apply(function() {
+            var selected = _.findWhere($scope.altSegments, { value: value });
+            if (selected) {
+              segment.value = selected.value;
+              segment.html = selected.html;
+              segment.fake = false;
+              segment.expandable = selected.expandable;
+            }
+            else if (segment.custom !== 'false') {
+              segment.value = value;
+              segment.html = $sce.trustAsHtml(value);
+              segment.expandable = true;
+              segment.fake = false;
+            }
+
+            $scope.onChange();
+          });
+        };
+
+        $scope.switchToLink = function(now) {
+          if (now === true || cancelBlur) {
+            clearTimeout(cancelBlur);
+            cancelBlur = null;
+            $input.hide();
+            $button.show();
+            $scope.updateVariableValue($input.val());
+          }
+          else {
+            // need to have long delay because the blur
+            // happens long before the click event on the typeahead options
+            cancelBlur = setTimeout($scope.switchToLink, 100);
+          }
+        };
+
+        $scope.source = function(query, callback) {
+          if (options) { return options; }
+
+          $scope.$apply(function() {
+            $scope.getOptions().then(function(altSegments) {
+              $scope.altSegments = altSegments;
+              options = _.map($scope.altSegments, function(alt) { return alt.value; });
+
+              // add custom values
+              if (segment.custom !== 'false') {
+                if (!segment.fake && _.indexOf(options, segment.value) === -1) {
+                  options.unshift(segment.value);
+                }
+              }
+
+              callback(options);
+            });
+          });
+        };
+
+        $scope.updater = function(value) {
+          if (value === segment.value) {
+            clearTimeout(cancelBlur);
+            $input.focus();
+            return value;
+          }
+
+          $input.val(value);
+          $scope.switchToLink(true);
+
+          return value;
+        };
+
+        $scope.matcher = function(item) {
+          var str = this.query;
+          if (str[0] === '/') { str = str.substring(1); }
+          if (str[str.length - 1] === '/') { str = str.substring(0, str.length-1); }
+          try {
+            return item.toLowerCase().match(str);
+          } catch(e) {
+            return false;
+          }
+        };
+
+        $input.attr('data-provide', 'typeahead');
+        $input.typeahead({ source: $scope.source, minLength: 0, items: 10000, updater: $scope.updater, matcher: $scope.matcher });
+
+        var typeahead = $input.data('typeahead');
+        typeahead.lookup = function () {
+          this.query = this.$element.val() || '';
+          var items = this.source(this.query, $.proxy(this.process, this));
+          return items ? this.process(items) : items;
+        };
+
+        $button.keydown(function(evt) {
+          // trigger typeahead on down arrow or enter key
+          if (evt.keyCode === 40 || evt.keyCode === 13) {
+            $button.click();
+          }
+        });
+
+        $button.click(function() {
+          options = null;
+          $input.css('width', ($button.width() + 16) + 'px');
+
+          $button.hide();
+          $input.show();
+          $input.focus();
+
+          var typeahead = $input.data('typeahead');
+          if (typeahead) {
+            $input.val('');
+            typeahead.lookup();
+          }
+        });
+
+        $input.blur($scope.switchToLink);
+
+        $compile(elem.contents())($scope);
+      }
+    };
+  });
+
+  coreModule.directive('metricSegmentModel', function(uiSegmentSrv, $q) {
+    return {
+      template: '<metric-segment segment="segment" get-options="getOptionsInternal()" on-change="onSegmentChange()"></metric-segment>',
+      restrict: 'E',
+      scope: {
+        property: "=",
+        options: "=",
+        getOptions: "&",
+        onChange: "&",
+      },
+      link: {
+        pre: function postLink($scope, elem, attrs) {
+
+          $scope.valueToSegment = function(value) {
+            var option = _.findWhere($scope.options, {value: value});
+            var segment = {
+              cssClass: attrs.cssClass,
+              custom: attrs.custom,
+              value: option ? option.text : value,
+            };
+            return uiSegmentSrv.newSegment(segment);
+          };
+
+          $scope.getOptionsInternal = function() {
+            if ($scope.options) {
+              var optionSegments = _.map($scope.options, function(option) {
+                return uiSegmentSrv.newSegment({value: option.text});
+              });
+              return $q.when(optionSegments);
+            } else {
+              return $scope.getOptions();
+            }
+          };
+
+          $scope.onSegmentChange = function() {
+            if ($scope.options) {
+              var option = _.findWhere($scope.options, {text: $scope.segment.value});
+              if (option && option.value !== $scope.property) {
+                $scope.property = option.value;
+              }
+            } else {
+              $scope.property = $scope.segment.value;
+            }
+
+            // needs to call this after digest so
+            // property is synced with outerscope
+            $scope.$$postDigest(function() {
+              $scope.onChange();
+            });
+          };
+
+          $scope.segment = $scope.valueToSegment($scope.property);
+        }
+      }
+    };
+  });
+});

+ 122 - 0
public/app/core/directives/misc.js

@@ -0,0 +1,122 @@
+define([
+  'angular',
+  'kbn',
+  '../core_module',
+],
+function (angular, kbn, coreModule) {
+  'use strict';
+
+  coreModule.directive('tip', function($compile) {
+    return {
+      restrict: 'E',
+      link: function(scope, elem, attrs) {
+        var _t = '<i class="grafana-tip fa fa-'+(attrs.icon||'question-circle')+'" bs-tooltip="\''+
+          kbn.addslashes(elem.text())+'\'"></i>';
+        _t = _t.replace(/{/g, '\\{').replace(/}/g, '\\}');
+        elem.replaceWith($compile(angular.element(_t))(scope));
+      }
+    };
+  });
+
+  coreModule.directive('watchChange', function() {
+    return {
+      scope: { onchange: '&watchChange' },
+      link: function(scope, element) {
+        element.on('input', function() {
+          scope.$apply(function () {
+            scope.onchange({ inputValue: element.val() });
+          });
+        });
+      }
+    };
+  });
+
+  coreModule.directive('editorOptBool', function($compile) {
+    return {
+      restrict: 'E',
+      link: function(scope, elem, attrs) {
+        var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : '';
+        var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
+        var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : '';
+
+        var template = '<div class="editor-option text-center"' + showIf + '>' +
+          ' <label for="' + attrs.model + '" class="small">' +
+          attrs.text + tip + '</label>' +
+          '<input class="cr1" id="' + attrs.model + '" type="checkbox" ' +
+          '       ng-model="' + attrs.model + '"' + ngchange +
+          '       ng-checked="' + attrs.model + '"></input>' +
+          ' <label for="' + attrs.model + '" class="cr1"></label>';
+        elem.replaceWith($compile(angular.element(template))(scope));
+      }
+    };
+  });
+
+  coreModule.directive('editorCheckbox', function($compile, $interpolate) {
+    return {
+      restrict: 'E',
+      link: function(scope, elem, attrs) {
+        var text = $interpolate(attrs.text)(scope);
+        var model = $interpolate(attrs.model)(scope);
+        var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : '';
+        var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
+        var label = '<label for="' + scope.$id + model + '" class="checkbox-label">' +
+          text + tip + '</label>';
+
+        var template = '<input class="cr1" id="' + scope.$id + model + '" type="checkbox" ' +
+          '       ng-model="' + model + '"' + ngchange +
+          '       ng-checked="' + model + '"></input>' +
+          ' <label for="' + scope.$id + model + '" class="cr1"></label>';
+
+        template = label + template;
+        elem.replaceWith($compile(angular.element(template))(scope));
+      }
+    };
+  });
+
+  coreModule.directive('gfDropdown', function ($parse, $compile, $timeout) {
+    function buildTemplate(items, placement) {
+      var upclass = placement === 'top' ? 'dropup' : '';
+      var ul = [
+        '<ul class="dropdown-menu ' + upclass + '" 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 ? ' dash-editor-link="' + 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 placement = iElement.data('placement');
+          var dropdown = angular.element(buildTemplate(items, placement).join(''));
+          dropdown.insertAfter(iElement);
+          $compile(iElement.next('ul.dropdown-menu'))(scope);
+        });
+
+        iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
+      }
+    };
+  });
+
+});

+ 54 - 0
public/app/core/directives/ng_model_on_blur.js

@@ -0,0 +1,54 @@
+define([
+  'kbn',
+  '../core_module',
+],
+function (kbn, coreModule) {
+  'use strict';
+
+  coreModule.directive('ngModelOnblur', function() {
+    return {
+      restrict: 'A',
+      priority: 1,
+      require: 'ngModel',
+      link: function(scope, elm, attr, ngModelCtrl) {
+        if (attr.type === 'radio' || attr.type === 'checkbox') {
+          return;
+        }
+
+        elm.off('input keydown change');
+        elm.bind('blur', function() {
+          scope.$apply(function() {
+            ngModelCtrl.$setViewValue(elm.val());
+          });
+        });
+      }
+    };
+  });
+
+  coreModule.directive('emptyToNull', function () {
+    return {
+      restrict: 'A',
+      require: 'ngModel',
+      link: function (scope, elm, attrs, ctrl) {
+        ctrl.$parsers.push(function (viewValue) {
+          if(viewValue === "") { return null; }
+          return viewValue;
+        });
+      }
+    };
+  });
+
+  coreModule.directive('validTimeSpan', function() {
+    return {
+      require: 'ngModel',
+      link: function(scope, elm, attrs, ctrl) {
+        ctrl.$validators.integer = function(modelValue, viewValue) {
+          if (ctrl.$isEmpty(modelValue)) {
+            return true;
+          }
+          return kbn.isValidTimeSpan(viewValue);
+        };
+      }
+    };
+  });
+});

+ 45 - 0
public/app/core/directives/password_strenght.js

@@ -0,0 +1,45 @@
+define([
+  '../core_module',
+],
+function (coreModule) {
+  'use strict';
+
+  coreModule.directive('passwordStrength', function() {
+    var template = '<div class="password-strength small" ng-if="!loginMode" ng-class="strengthClass">' +
+      '<em>{{strengthText}}</em>' +
+      '</div>';
+    return {
+      template: template,
+      scope: {
+        password: "=",
+      },
+      link: function($scope) {
+
+        $scope.strengthClass = '';
+
+        function passwordChanged(newValue) {
+          if (!newValue) {
+            $scope.strengthText = "";
+            $scope.strengthClass = "hidden";
+            return;
+          }
+          if (newValue.length < 4) {
+            $scope.strengthText = "strength: weak sauce.";
+            $scope.strengthClass = "password-strength-bad";
+            return;
+          }
+          if (newValue.length <= 8) {
+            $scope.strengthText = "strength: you can do better.";
+            $scope.strengthClass = "password-strength-ok";
+            return;
+          }
+
+          $scope.strengthText = "strength: strong like a bull.";
+          $scope.strengthClass = "password-strength-good";
+        }
+
+        $scope.$watch("password", passwordChanged);
+      }
+    };
+  });
+});

+ 41 - 0
public/app/core/directives/spectrum_picker.js

@@ -0,0 +1,41 @@
+define([
+  'angular',
+  '../core_module',
+  'spectrum',
+],
+function (angular, coreModule) {
+  'use strict';
+
+  coreModule.directive('spectrumPicker', function() {
+    return {
+      restrict: 'E',
+      require: 'ngModel',
+      scope: false,
+      replace: true,
+      template: "<span><input class='input-small' /></span>",
+      link: function(scope, element, attrs, ngModel) {
+        var input = element.find('input');
+        var options = angular.extend({
+          showAlpha: true,
+          showButtons: false,
+          color: ngModel.$viewValue,
+          change: function(color) {
+            scope.$apply(function() {
+              ngModel.$setViewValue(color.toRgbString());
+            });
+          }
+        }, scope.$eval(attrs.options));
+
+        ngModel.$render = function() {
+          input.spectrum('set', ngModel.$viewValue || '');
+        };
+
+        input.spectrum(options);
+
+        scope.$on('$destroy', function() {
+          input.spectrum('destroy');
+        });
+      }
+    };
+  });
+});

+ 5 - 8
public/app/directives/tags.js → public/app/core/directives/tags.js

@@ -1,9 +1,10 @@
 define([
   'angular',
   'jquery',
-  'bootstrap-tagsinput'
+  '../core_module',
+  'bootstrap-tagsinput',
 ],
-function (angular, $) {
+function (angular, $, coreModule) {
   'use strict';
 
   function djb2(str) {
@@ -38,9 +39,7 @@ function (angular, $) {
     element.css("border-color", borderColor);
   }
 
-  angular
-  .module('grafana.directives')
-  .directive('tagColorFromName', function() {
+  coreModule.directive('tagColorFromName', function() {
     return {
       scope: { tagColorFromName: "=" },
       link: function (scope, element) {
@@ -49,9 +48,7 @@ function (angular, $) {
     };
   });
 
-  angular
-  .module('grafana.directives')
-  .directive('bootstrapTagsinput', function() {
+  coreModule.directive('bootstrapTagsinput', function() {
 
     function getItemProperty(scope, property) {
       if (!property) {

+ 50 - 0
public/app/core/directives/topnav.js

@@ -0,0 +1,50 @@
+define([
+  '../core_module',
+],
+function (coreModule) {
+  'use strict';
+
+  coreModule.directive('topnav', function($rootScope, contextSrv) {
+    return {
+      restrict: 'E',
+      transclude: true,
+      scope: {
+        title: "@",
+        section: "@",
+        titleAction: "&",
+        subnav: "=",
+      },
+      template:
+        '<div class="navbar navbar-static-top"><div class="navbar-inner"><div class="container-fluid">' +
+        '<div class="top-nav">' +
+        '<a class="top-nav-menu-btn pointer" ng-if="!contextSrv.sidemenu" ng-click="toggle()">' +
+        '<img class="logo-icon" src="img/fav32.png"></img> ' +
+        '<i class="fa fa-bars"></i>' +
+        '</a>' +
+
+        '<span class="icon-circle top-nav-icon">' +
+        '<i ng-class="icon"></i>' +
+        '</span>' +
+
+        '<span ng-show="section">' +
+        '<span class="top-nav-title">{{section}}</span>' +
+        '<i class="top-nav-breadcrumb-icon fa fa-angle-right"></i>' +
+        '</span>' +
+
+        '<a ng-click="titleAction()" class="top-nav-title">' +
+        '{{title}}' +
+        '</a>' +
+        '<i ng-show="subnav" class="top-nav-breadcrumb-icon fa fa-angle-right"></i>' +
+        '</div><div ng-transclude></div></div></div></div>',
+      link: function(scope, elem, attrs) {
+        scope.icon = attrs.icon;
+        scope.contextSrv = contextSrv;
+
+        scope.toggle = function() {
+          $rootScope.appEvent('toggle-sidemenu');
+        };
+      }
+    };
+  });
+
+});

+ 284 - 0
public/app/core/directives/value_select_dropdown.js

@@ -0,0 +1,284 @@
+define([
+  'angular',
+  'lodash',
+  '../core_module',
+],
+function (angular, _, coreModule) {
+  'use strict';
+
+  coreModule.controller('ValueSelectDropdownCtrl', function($q) {
+    var vm = this;
+
+    vm.show = function() {
+      vm.oldVariableText = vm.variable.current.text;
+      vm.highlightIndex = -1;
+
+      vm.options = vm.variable.options;
+      vm.selectedValues = _.filter(vm.options, {selected: true});
+
+      vm.tags = _.map(vm.variable.tags, function(value) {
+        var tag = { text: value, selected: false };
+        _.each(vm.variable.current.tags, function(tagObj) {
+          if (tagObj.text === value) {
+            tag = tagObj;
+          }
+        });
+        return tag;
+      });
+
+      vm.search = {
+        query: '',
+        options: vm.options.slice(0, Math.min(vm.options.length, 1000))
+      };
+
+      vm.dropdownVisible = true;
+    };
+
+    vm.updateLinkText = function() {
+      var current = vm.variable.current;
+
+      if (current.tags && current.tags.length) {
+        // filer out values that are in selected tags
+        var selectedAndNotInTag = _.filter(vm.variable.options, function(option) {
+          if (!option.selected) { return false; }
+          for (var i = 0; i < current.tags.length; i++)  {
+            var tag = current.tags[i];
+            if (_.indexOf(tag.values, option.value) !== -1) {
+              return false;
+            }
+          }
+          return true;
+        });
+
+        // convert values to text
+        var currentTexts = _.pluck(selectedAndNotInTag, 'text');
+
+        // join texts
+        vm.linkText = currentTexts.join(' + ');
+        if (vm.linkText.length > 0) {
+          vm.linkText += ' + ';
+        }
+      } else {
+        vm.linkText = vm.variable.current.text;
+      }
+    };
+
+    vm.clearSelections = function() {
+      _.each(vm.options, function(option) {
+        option.selected = false;
+      });
+
+      vm.selectionsChanged(false);
+    };
+
+    vm.selectTag = function(tag) {
+      tag.selected = !tag.selected;
+      var tagValuesPromise;
+      if (!tag.values) {
+        tagValuesPromise = vm.getValuesForTag({tagKey: tag.text});
+      } else {
+        tagValuesPromise = $q.when(tag.values);
+      }
+
+      tagValuesPromise.then(function(values) {
+        tag.values = values;
+        tag.valuesText = values.join(' + ');
+        _.each(vm.options, function(option) {
+          if (_.indexOf(tag.values, option.value) !== -1) {
+            option.selected = tag.selected;
+          }
+        });
+
+        vm.selectionsChanged(false);
+      });
+    };
+
+    vm.keyDown = function (evt) {
+      if (evt.keyCode === 27) {
+        vm.hide();
+      }
+      if (evt.keyCode === 40) {
+        vm.moveHighlight(1);
+      }
+      if (evt.keyCode === 38) {
+        vm.moveHighlight(-1);
+      }
+      if (evt.keyCode === 13) {
+        if (vm.search.options.length === 0) {
+          vm.commitChanges();
+        } else {
+          vm.selectValue(vm.search.options[vm.highlightIndex], {}, true, false);
+        }
+      }
+      if (evt.keyCode === 32) {
+        vm.selectValue(vm.search.options[vm.highlightIndex], {}, false, false);
+      }
+    };
+
+    vm.moveHighlight = function(direction) {
+      vm.highlightIndex = (vm.highlightIndex + direction) % vm.search.options.length;
+    };
+
+    vm.selectValue = function(option, event, commitChange, excludeOthers) {
+      if (!option) { return; }
+
+      option.selected = !option.selected;
+
+      commitChange = commitChange || false;
+      excludeOthers = excludeOthers || false;
+
+      var setAllExceptCurrentTo = function(newValue) {
+        _.each(vm.options, function(other) {
+          if (option !== other) { other.selected = newValue; }
+        });
+      };
+
+      // commit action (enter key), should not deselect it
+      if (commitChange) {
+        option.selected = true;
+      }
+
+      if (option.text === 'All' || excludeOthers) {
+        setAllExceptCurrentTo(false);
+        commitChange = true;
+      }
+      else if (!vm.variable.multi) {
+        setAllExceptCurrentTo(false);
+        commitChange = true;
+      } else if (event.ctrlKey || event.metaKey || event.shiftKey) {
+        commitChange = true;
+        setAllExceptCurrentTo(false);
+      }
+
+      vm.selectionsChanged(commitChange);
+    };
+
+    vm.selectionsChanged = function(commitChange) {
+      vm.selectedValues = _.filter(vm.options, {selected: true});
+
+      if (vm.selectedValues.length > 1 && vm.selectedValues.length !== vm.options.length) {
+        if (vm.selectedValues[0].text === 'All') {
+          vm.selectedValues[0].selected = false;
+          vm.selectedValues = vm.selectedValues.slice(1, vm.selectedValues.length);
+        }
+      }
+
+      // validate selected tags
+      _.each(vm.tags, function(tag) {
+        if (tag.selected)  {
+          _.each(tag.values, function(value) {
+            if (!_.findWhere(vm.selectedValues, {value: value})) {
+              tag.selected = false;
+            }
+          });
+        }
+      });
+
+      vm.selectedTags = _.filter(vm.tags, {selected: true});
+      vm.variable.current.value = _.pluck(vm.selectedValues, 'value');
+      vm.variable.current.text = _.pluck(vm.selectedValues, 'text').join(' + ');
+      vm.variable.current.tags = vm.selectedTags;
+
+      // only single value
+      if (vm.selectedValues.length === 1) {
+        vm.variable.current.value = vm.selectedValues[0].value;
+      }
+
+      if (commitChange) {
+        vm.commitChanges();
+      }
+    };
+
+    vm.commitChanges = function() {
+      // if we have a search query and no options use that
+      if (vm.search.options.length === 0 && vm.search.query.length > 0) {
+        vm.variable.current = {text: vm.search.query, value: vm.search.query};
+      }
+      else if (vm.selectedValues.length === 0) {
+        // make sure one option is selected
+        vm.options[0].selected = true;
+        vm.selectionsChanged(false);
+      }
+
+      vm.dropdownVisible = false;
+      vm.updateLinkText();
+
+      if (vm.variable.current.text !== vm.oldVariableText) {
+        vm.onUpdated();
+      }
+    };
+
+    vm.queryChanged = function() {
+      vm.highlightIndex = -1;
+      vm.search.options = _.filter(vm.options, function(option) {
+        return option.text.toLowerCase().indexOf(vm.search.query.toLowerCase()) !== -1;
+      });
+
+      vm.search.options = vm.search.options.slice(0, Math.min(vm.search.options.length, 1000));
+    };
+
+    vm.init = function() {
+      vm.selectedTags = vm.variable.current.tags || [];
+      vm.updateLinkText();
+    };
+
+  });
+
+  coreModule.directive('valueSelectDropdown', function($compile, $window, $timeout, $rootScope) {
+    return {
+      scope: { variable: "=", onUpdated: "&", getValuesForTag: "&" },
+      templateUrl: 'app/partials/valueSelectDropdown.html',
+      controller: 'ValueSelectDropdownCtrl',
+      controllerAs: 'vm',
+      bindToController: true,
+      link: function(scope, elem) {
+        var bodyEl = angular.element($window.document.body);
+        var linkEl = elem.find('.variable-value-link');
+        var inputEl = elem.find('input');
+
+        function openDropdown() {
+          inputEl.css('width', Math.max(linkEl.width(), 30) + 'px');
+
+          inputEl.show();
+          linkEl.hide();
+
+          inputEl.focus();
+          $timeout(function() { bodyEl.on('click', bodyOnClick); }, 0, false);
+        }
+
+        function switchToLink() {
+          inputEl.hide();
+          linkEl.show();
+          bodyEl.off('click', bodyOnClick);
+        }
+
+        function bodyOnClick (e) {
+          if (elem.has(e.target).length === 0) {
+            scope.$apply(function() {
+              scope.vm.commitChanges();
+            });
+          }
+        }
+
+        scope.$watch('vm.dropdownVisible', function(newValue) {
+          if (newValue) {
+            openDropdown();
+          } else {
+            switchToLink();
+          }
+        });
+
+        var cleanUp = $rootScope.$on('template-variable-value-updated', function() {
+          scope.vm.updateLinkText();
+        });
+
+        scope.$on("$destroy", function() {
+          cleanUp();
+        });
+
+        scope.vm.init();
+      },
+    };
+  });
+
+});

+ 1 - 1
public/app/core/filters/filters.ts

@@ -4,7 +4,7 @@ import angular = require('angular');
 import jquery = require('jquery');
 import moment = require('moment');
 import _ = require('lodash');
-import coreModule from '../core_module';
+import coreModule = require('../core_module');
 
 coreModule.filter('stringSort', function() {
   return function(input) {

+ 0 - 18
public/app/directives/all.js

@@ -1,18 +0,0 @@
-define([
-  './dashUpload',
-  './dashEditLink',
-  './ngModelOnBlur',
-  './misc',
-  './confirmClick',
-  './configModal',
-  './spectrumPicker',
-  './tags',
-  './bodyClass',
-  './valueSelectDropdown',
-  './metric.segment',
-  './grafanaVersionCheck',
-  './dropdown.typeahead',
-  './topnav',
-  './annotationTooltip',
-  './passwordStrenght',
-], function () {});

+ 0 - 50
public/app/directives/bodyClass.js

@@ -1,50 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  'jquery'
-],
-function (angular, _, $) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('bodyClass', function() {
-      return {
-        link: function($scope, elem) {
-
-          var lastHideControlsVal;
-
-          // tooltip removal fix
-          $scope.$on("$routeChangeSuccess", function() {
-            $("#tooltip, .tooltip").remove();
-          });
-
-          $scope.$watch('submenuEnabled', function() {
-            if (!$scope.dashboard) {
-              return;
-            }
-
-            elem.toggleClass('submenu-controls-visible', $scope.submenuEnabled);
-          });
-
-          $scope.$watch('dashboard.hideControls', function() {
-            if (!$scope.dashboard) {
-              return;
-            }
-
-            var hideControls = $scope.dashboard.hideControls || $scope.playlist_active;
-
-            if (lastHideControlsVal !== hideControls) {
-              elem.toggleClass('hide-controls', hideControls);
-              lastHideControlsVal = hideControls;
-            }
-          });
-
-          $scope.$watch('playlistSrv', function(newValue) {
-            elem.toggleClass('playlist-active', _.isObject(newValue));
-          });
-        }
-      };
-    });
-
-});

+ 0 - 48
public/app/directives/configModal.js

@@ -1,48 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  'jquery'
-],
-function (angular, _, $) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('configModal', function($modal, $q, $timeout) {
-      return {
-        restrict: 'A',
-        link: function(scope, elem, attrs) {
-          var partial = attrs.configModal;
-          var id = '#' + partial.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id;
-
-          elem.bind('click',function() {
-            if ($(id).length) {
-              elem.attr('data-target', id).attr('data-toggle', 'modal');
-              scope.$apply(function() { scope.$broadcast('modal-opened'); });
-              return;
-            }
-
-            var panelModal = $modal({
-              template: partial,
-              persist: false,
-              show: false,
-              scope: scope.$new(),
-              keyboard: false
-            });
-
-            $q.when(panelModal).then(function(modalEl) {
-              elem.attr('data-target', id).attr('data-toggle', 'modal');
-
-              $timeout(function () {
-                if (!modalEl.data('modal').isShown) {
-                  modalEl.modal('show');
-                }
-              }, 50);
-            });
-
-            scope.$apply();
-          });
-        }
-      };
-    });
-});

+ 0 - 107
public/app/directives/dashEditLink.js

@@ -1,107 +0,0 @@
-define([
-  'angular',
-  'jquery'
-],
-function (angular, $) {
-  'use strict';
-
-  var editViewMap = {
-    'settings':    { src: 'app/features/dashboard/partials/settings.html', title: "Settings" },
-    'annotations': { src: 'app/features/annotations/partials/editor.html', title: "Annotations" },
-    'templating':  { src: 'app/features/templating/partials/editor.html', title: "Templating" }
-  };
-
-  angular
-    .module('grafana.directives')
-    .directive('dashEditorLink', function($timeout) {
-      return {
-        restrict: 'A',
-        link: function(scope, elem, attrs) {
-          var partial = attrs.dashEditorLink;
-
-          elem.bind('click',function() {
-            $timeout(function() {
-              var editorScope = attrs.editorScope === 'isolated' ? null : scope;
-              scope.appEvent('show-dash-editor', { src: partial, scope: editorScope });
-            });
-          });
-        }
-      };
-    });
-
-  angular
-    .module('grafana.directives')
-    .directive('dashEditorView', function($compile, $location) {
-      return {
-        restrict: 'A',
-        link: function(scope, elem) {
-          var editorScope;
-          var lastEditor;
-
-          function hideEditorPane() {
-            if (editorScope) { editorScope.dismiss(); }
-          }
-
-          function showEditorPane(evt, payload, editview) {
-            if (editview) {
-              scope.contextSrv.editview = editViewMap[editview];
-              payload.src = scope.contextSrv.editview.src;
-            }
-
-            if (lastEditor === payload.src) {
-              hideEditorPane();
-              return;
-            }
-
-            hideEditorPane();
-
-            scope.exitFullscreen();
-
-            lastEditor = payload.src;
-            editorScope = payload.scope ? payload.scope.$new() : scope.$new();
-
-            editorScope.dismiss = function() {
-              editorScope.$destroy();
-              elem.empty();
-              lastEditor = null;
-              editorScope = null;
-
-              if (editview) {
-                var urlParams = $location.search();
-                if (editview === urlParams.editview) {
-                  delete urlParams.editview;
-                  $location.search(urlParams);
-                }
-              }
-            };
-
-            var src = "'" + payload.src + "'";
-            var view = $('<div class="gf-box" ng-include="' + src + '"></div>');
-
-            if (payload.cssClass) {
-              view.addClass(payload.cssClass);
-            }
-
-            elem.append(view);
-            $compile(elem.contents())(editorScope);
-          }
-
-          scope.$watch("dashboardViewState.state.editview", function(newValue, oldValue) {
-            if (newValue) {
-              showEditorPane(null, {}, newValue);
-            } else if (oldValue) {
-              scope.contextSrv.editview = null;
-              if (lastEditor === editViewMap[oldValue]) {
-                hideEditorPane();
-              }
-            }
-          });
-
-          scope.contextSrv.editview = null;
-          scope.$on("$destroy", hideEditorPane);
-          scope.onAppEvent('hide-dash-editor', hideEditorPane);
-          scope.onAppEvent('show-dash-editor', showEditorPane);
-        }
-      };
-    });
-});

+ 0 - 115
public/app/directives/dropdown.typeahead.js

@@ -1,115 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  'jquery',
-],
-function (angular, _, $) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('dropdownTypeahead', function($compile) {
-
-      var inputTemplate = '<input type="text"'+
-                            ' class="tight-form-input input-medium tight-form-input"' +
-                            ' spellcheck="false" style="display:none"></input>';
-
-      var buttonTemplate = '<a  class="tight-form-item tight-form-func dropdown-toggle"' +
-                              ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
-                              ' data-placement="top"><i class="fa fa-plus"></i></a>';
-
-      return {
-        scope: {
-          menuItems: "=dropdownTypeahead",
-          dropdownTypeaheadOnSelect: "&dropdownTypeaheadOnSelect",
-          model: '=ngModel'
-        },
-        link: function($scope, elem, attrs) {
-          var $input = $(inputTemplate);
-          var $button = $(buttonTemplate);
-          $input.appendTo(elem);
-          $button.appendTo(elem);
-
-          if (attrs.linkText) {
-            $button.html(attrs.linkText);
-          }
-
-          if (attrs.ngModel) {
-            $scope.$watch('model', function(newValue) {
-              _.each($scope.menuItems, function(item) {
-                _.each(item.submenu, function(subItem) {
-                  if (subItem.value === newValue) {
-                    $button.html(subItem.text);
-                  }
-                });
-              });
-            });
-          }
-
-          var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) {
-            _.each(value.submenu, function(item, subIndex) {
-              item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
-              memo.push(value.text + ' ' + item.text);
-            });
-            return memo;
-          }, []);
-
-          $scope.menuItemSelected = function(index, subIndex) {
-            var item = $scope.menuItems[index];
-            $scope.dropdownTypeaheadOnSelect({$item: item, $subItem: item.submenu[subIndex]});
-          };
-
-          $input.attr('data-provide', 'typeahead');
-          $input.typeahead({
-            source: typeaheadValues,
-            minLength: 1,
-            items: 10,
-            updater: function (value) {
-              var result = {};
-              _.each($scope.menuItems, function(menuItem) {
-                _.each(menuItem.submenu, function(submenuItem) {
-                  if (value === (menuItem.text + ' ' + submenuItem.text)) {
-                    result.$item = menuItem;
-                    result.$subItem = submenuItem;
-                  }
-                });
-              });
-
-              if (result.$item) {
-                $scope.$apply(function() {
-                  $scope.dropdownTypeaheadOnSelect(result);
-                });
-              }
-
-              $input.trigger('blur');
-              return '';
-            }
-          });
-
-          $button.click(function() {
-            $button.hide();
-            $input.show();
-            $input.focus();
-          });
-
-          $input.keyup(function() {
-            elem.toggleClass('open', $input.val() === '');
-          });
-
-          $input.blur(function() {
-            $input.hide();
-            $input.val('');
-            $button.show();
-            $button.focus();
-            // clicking the function dropdown menu wont
-            // work if you remove class at once
-            setTimeout(function() {
-              elem.removeClass('open');
-            }, 200);
-          });
-
-          $compile(elem.contents())($scope);
-        }
-      };
-    });
-});

+ 0 - 33
public/app/directives/grafanaVersionCheck.js

@@ -1,33 +0,0 @@
-define([
-  'angular'
-],
-function (angular) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('grafanaVersionCheck', function($http, contextSrv) {
-      return {
-        restrict: 'A',
-        link: function(scope, elem) {
-          if (contextSrv.version === 'master') {
-            return;
-          }
-
-          $http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' })
-            .then(function(response) {
-              if (!response.data || !response.data.version) {
-                return;
-              }
-
-              if (contextSrv.version !== response.data.version) {
-                elem.append('<i class="icon-info-sign"></i> ' +
-                            '<a href="http://grafana.org/download" target="_blank"> ' +
-                                'New version available: ' + response.data.version +
-                            '</a>');
-              }
-            });
-        }
-      };
-    });
-});

+ 0 - 216
public/app/directives/metric.segment.js

@@ -1,216 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  'jquery',
-],
-function (angular, _, $) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('metricSegment', function($compile, $sce) {
-      var inputTemplate = '<input type="text" data-provide="typeahead" ' +
-                            ' class="tight-form-clear-input input-medium"' +
-                            ' spellcheck="false" style="display:none"></input>';
-
-      var buttonTemplate = '<a class="tight-form-item" ng-class="segment.cssClass" ' +
-        'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
-
-      return {
-        scope: {
-          segment: "=",
-          getOptions: "&",
-          onChange: "&",
-        },
-
-        link: function($scope, elem) {
-          var $input = $(inputTemplate);
-          var $button = $(buttonTemplate);
-          var segment = $scope.segment;
-          var options = null;
-          var cancelBlur = null;
-
-          $input.appendTo(elem);
-          $button.appendTo(elem);
-
-          $scope.updateVariableValue = function(value) {
-            if (value === '' || segment.value === value) {
-              return;
-            }
-
-            $scope.$apply(function() {
-              var selected = _.findWhere($scope.altSegments, { value: value });
-              if (selected) {
-                segment.value = selected.value;
-                segment.html = selected.html;
-                segment.fake = false;
-                segment.expandable = selected.expandable;
-              }
-              else if (segment.custom !== 'false') {
-                segment.value = value;
-                segment.html = $sce.trustAsHtml(value);
-                segment.expandable = true;
-                segment.fake = false;
-              }
-
-              $scope.onChange();
-            });
-          };
-
-          $scope.switchToLink = function(now) {
-            if (now === true || cancelBlur) {
-              clearTimeout(cancelBlur);
-              cancelBlur = null;
-              $input.hide();
-              $button.show();
-              $scope.updateVariableValue($input.val());
-            }
-            else {
-              // need to have long delay because the blur
-              // happens long before the click event on the typeahead options
-              cancelBlur = setTimeout($scope.switchToLink, 100);
-            }
-          };
-
-          $scope.source = function(query, callback) {
-            if (options) { return options; }
-
-            $scope.$apply(function() {
-              $scope.getOptions().then(function(altSegments) {
-                $scope.altSegments = altSegments;
-                options = _.map($scope.altSegments, function(alt) { return alt.value; });
-
-                // add custom values
-                if (segment.custom !== 'false') {
-                  if (!segment.fake && _.indexOf(options, segment.value) === -1) {
-                    options.unshift(segment.value);
-                  }
-                }
-
-                callback(options);
-              });
-            });
-          };
-
-          $scope.updater = function(value) {
-            if (value === segment.value) {
-              clearTimeout(cancelBlur);
-              $input.focus();
-              return value;
-            }
-
-            $input.val(value);
-            $scope.switchToLink(true);
-
-            return value;
-          };
-
-          $scope.matcher = function(item) {
-            var str = this.query;
-            if (str[0] === '/') { str = str.substring(1); }
-            if (str[str.length - 1] === '/') { str = str.substring(0, str.length-1); }
-            try {
-              return item.toLowerCase().match(str);
-            } catch(e) {
-              return false;
-            }
-          };
-
-          $input.attr('data-provide', 'typeahead');
-          $input.typeahead({ source: $scope.source, minLength: 0, items: 10000, updater: $scope.updater, matcher: $scope.matcher });
-
-          var typeahead = $input.data('typeahead');
-          typeahead.lookup = function () {
-            this.query = this.$element.val() || '';
-            var items = this.source(this.query, $.proxy(this.process, this));
-            return items ? this.process(items) : items;
-          };
-
-          $button.keydown(function(evt) {
-            // trigger typeahead on down arrow or enter key
-            if (evt.keyCode === 40 || evt.keyCode === 13) {
-              $button.click();
-            }
-          });
-
-          $button.click(function() {
-            options = null;
-            $input.css('width', ($button.width() + 16) + 'px');
-
-            $button.hide();
-            $input.show();
-            $input.focus();
-
-            var typeahead = $input.data('typeahead');
-            if (typeahead) {
-              $input.val('');
-              typeahead.lookup();
-            }
-          });
-
-          $input.blur($scope.switchToLink);
-
-          $compile(elem.contents())($scope);
-        }
-      };
-    });
-
-  angular
-    .module('grafana.directives')
-    .directive('metricSegmentModel', function(uiSegmentSrv, $q) {
-      return {
-        template: '<metric-segment segment="segment" get-options="getOptionsInternal()" on-change="onSegmentChange()"></metric-segment>',
-        restrict: 'E',
-        scope: {
-          property: "=",
-          options: "=",
-          getOptions: "&",
-          onChange: "&",
-        },
-        link: {
-          pre: function postLink($scope, elem, attrs) {
-
-            $scope.valueToSegment = function(value) {
-              var option = _.findWhere($scope.options, {value: value});
-              var segment = {
-                cssClass: attrs.cssClass,
-                custom: attrs.custom,
-                value: option ? option.text : value,
-              };
-              return uiSegmentSrv.newSegment(segment);
-            };
-
-            $scope.getOptionsInternal = function() {
-              if ($scope.options) {
-                var optionSegments = _.map($scope.options, function(option) {
-                  return uiSegmentSrv.newSegment({value: option.text});
-                });
-                return $q.when(optionSegments);
-              } else {
-                return $scope.getOptions();
-              }
-            };
-
-            $scope.onSegmentChange = function() {
-              if ($scope.options) {
-                var option = _.findWhere($scope.options, {text: $scope.segment.value});
-                if (option && option.value !== $scope.property) {
-                  $scope.property = option.value;
-                }
-              } else {
-                $scope.property = $scope.segment.value;
-              }
-
-              // needs to call this after digest so
-              // property is synced with outerscope
-              $scope.$$postDigest(function() {
-                $scope.onChange();
-              });
-            };
-
-            $scope.segment = $scope.valueToSegment($scope.property);
-          }
-        }
-      };
-    });
-});

+ 0 - 132
public/app/directives/misc.js

@@ -1,132 +0,0 @@
-define([
-  'angular',
-  'kbn'
-],
-function (angular, kbn) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('tip', function($compile) {
-      return {
-        restrict: 'E',
-        link: function(scope, elem, attrs) {
-          var _t = '<i class="grafana-tip fa fa-'+(attrs.icon||'question-circle')+'" bs-tooltip="\''+
-            kbn.addslashes(elem.text())+'\'"></i>';
-          _t = _t.replace(/{/g, '\\{').replace(/}/g, '\\}');
-          elem.replaceWith($compile(angular.element(_t))(scope));
-        }
-      };
-    });
-
-  angular
-    .module('grafana.directives')
-    .directive('watchChange', function() {
-      return {
-        scope: { onchange: '&watchChange' },
-        link: function(scope, element) {
-          element.on('input', function() {
-            scope.$apply(function () {
-              scope.onchange({ inputValue: element.val() });
-            });
-          });
-        }
-      };
-    });
-
-  angular
-    .module('grafana.directives')
-    .directive('editorOptBool', function($compile) {
-      return {
-        restrict: 'E',
-        link: function(scope, elem, attrs) {
-          var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : '';
-          var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
-          var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : '';
-
-          var template = '<div class="editor-option text-center"' + showIf + '>' +
-                         ' <label for="' + attrs.model + '" class="small">' +
-                           attrs.text + tip + '</label>' +
-                          '<input class="cr1" id="' + attrs.model + '" type="checkbox" ' +
-                          '       ng-model="' + attrs.model + '"' + ngchange +
-                          '       ng-checked="' + attrs.model + '"></input>' +
-                          ' <label for="' + attrs.model + '" class="cr1"></label>';
-          elem.replaceWith($compile(angular.element(template))(scope));
-        }
-      };
-    });
-
-  angular
-    .module('grafana.directives')
-    .directive('editorCheckbox', function($compile, $interpolate) {
-      return {
-        restrict: 'E',
-        link: function(scope, elem, attrs) {
-          var text = $interpolate(attrs.text)(scope);
-          var model = $interpolate(attrs.model)(scope);
-          var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : '';
-          var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
-          var label = '<label for="' + scope.$id + model + '" class="checkbox-label">' +
-                           text + tip + '</label>';
-
-          var template = '<input class="cr1" id="' + scope.$id + model + '" type="checkbox" ' +
-                          '       ng-model="' + model + '"' + ngchange +
-                          '       ng-checked="' + model + '"></input>' +
-                          ' <label for="' + scope.$id + model + '" class="cr1"></label>';
-
-          template = label + template;
-          elem.replaceWith($compile(angular.element(template))(scope));
-        }
-      };
-    });
-
-  angular
-    .module('grafana.directives')
-    .directive('gfDropdown', function ($parse, $compile, $timeout) {
-
-      function buildTemplate(items, placement) {
-        var upclass = placement === 'top' ? 'dropup' : '';
-        var ul = [
-          '<ul class="dropdown-menu ' + upclass + '" 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 ? ' dash-editor-link="' + 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 placement = iElement.data('placement');
-            var dropdown = angular.element(buildTemplate(items, placement).join(''));
-            dropdown.insertAfter(iElement);
-            $compile(iElement.next('ul.dropdown-menu'))(scope);
-          });
-
-          iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
-        }
-      };
-    });
-
-});

+ 0 - 54
public/app/directives/ngModelOnBlur.js

@@ -1,54 +0,0 @@
-define([
-  'angular',
-  'kbn'
-],
-function (angular, kbn) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('ngModelOnblur', function() {
-      return {
-        restrict: 'A',
-        priority: 1,
-        require: 'ngModel',
-        link: function(scope, elm, attr, ngModelCtrl) {
-          if (attr.type === 'radio' || attr.type === 'checkbox') {
-            return;
-          }
-
-          elm.off('input keydown change');
-          elm.bind('blur', function() {
-            scope.$apply(function() {
-              ngModelCtrl.$setViewValue(elm.val());
-            });
-          });
-        }
-      };
-    })
-    .directive('emptyToNull', function () {
-      return {
-        restrict: 'A',
-        require: 'ngModel',
-        link: function (scope, elm, attrs, ctrl) {
-          ctrl.$parsers.push(function (viewValue) {
-            if(viewValue === "") { return null; }
-            return viewValue;
-          });
-        }
-      };
-    })
-    .directive('validTimeSpan', function() {
-      return {
-        require: 'ngModel',
-        link: function(scope, elm, attrs, ctrl) {
-          ctrl.$validators.integer = function(modelValue, viewValue) {
-            if (ctrl.$isEmpty(modelValue)) {
-              return true;
-            }
-            return kbn.isValidTimeSpan(viewValue);
-          };
-        }
-      };
-    });
-});

+ 0 - 47
public/app/directives/passwordStrenght.js

@@ -1,47 +0,0 @@
-define([
-  'angular',
-],
-function (angular) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('passwordStrength', function() {
-      var template = '<div class="password-strength small" ng-if="!loginMode" ng-class="strengthClass">' +
-                            '<em>{{strengthText}}</em>' +
-                          '</div>';
-      return {
-        template: template,
-        scope: {
-          password: "=",
-        },
-        link: function($scope) {
-
-          $scope.strengthClass = '';
-
-          function passwordChanged(newValue) {
-            if (!newValue) {
-              $scope.strengthText = "";
-              $scope.strengthClass = "hidden";
-              return;
-            }
-            if (newValue.length < 4) {
-              $scope.strengthText = "strength: weak sauce.";
-              $scope.strengthClass = "password-strength-bad";
-              return;
-            }
-            if (newValue.length <= 8) {
-              $scope.strengthText = "strength: you can do better.";
-              $scope.strengthClass = "password-strength-ok";
-              return;
-            }
-
-            $scope.strengthText = "strength: strong like a bull.";
-            $scope.strengthClass = "password-strength-good";
-          }
-
-          $scope.$watch("password", passwordChanged);
-        }
-      };
-    });
-});

+ 0 - 42
public/app/directives/spectrumPicker.js

@@ -1,42 +0,0 @@
-define([
-  'angular',
-  'spectrum'
-],
-function (angular) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('spectrumPicker', function() {
-      return {
-        restrict: 'E',
-        require: 'ngModel',
-        scope: false,
-        replace: true,
-        template: "<span><input class='input-small' /></span>",
-        link: function(scope, element, attrs, ngModel) {
-          var input = element.find('input');
-          var options = angular.extend({
-            showAlpha: true,
-            showButtons: false,
-            color: ngModel.$viewValue,
-            change: function(color) {
-              scope.$apply(function() {
-                ngModel.$setViewValue(color.toRgbString());
-              });
-            }
-          }, scope.$eval(attrs.options));
-
-          ngModel.$render = function() {
-            input.spectrum('set', ngModel.$viewValue || '');
-          };
-
-          input.spectrum(options);
-
-          scope.$on('$destroy', function() {
-            input.spectrum('destroy');
-          });
-        }
-      };
-    });
-});

+ 0 - 53
public/app/directives/topnav.js

@@ -1,53 +0,0 @@
-define([
-  'angular',
-  'kbn'
-],
-function (angular) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('topnav', function($rootScope, contextSrv) {
-      return {
-        restrict: 'E',
-        transclude: true,
-        scope: {
-          title: "@",
-          section: "@",
-          titleAction: "&",
-          subnav: "=",
-        },
-        template:
-          '<div class="navbar navbar-static-top"><div class="navbar-inner"><div class="container-fluid">' +
-          '<div class="top-nav">' +
-            '<a class="top-nav-menu-btn pointer" ng-if="!contextSrv.sidemenu" ng-click="toggle()">' +
-              '<img class="logo-icon" src="img/fav32.png"></img> ' +
-              '<i class="fa fa-bars"></i>' +
-            '</a>' +
-
-            '<span class="icon-circle top-nav-icon">' +
-              '<i ng-class="icon"></i>' +
-            '</span>' +
-
-            '<span ng-show="section">' +
-              '<span class="top-nav-title">{{section}}</span>' +
-              '<i class="top-nav-breadcrumb-icon fa fa-angle-right"></i>' +
-            '</span>' +
-
-            '<a ng-click="titleAction()" class="top-nav-title">' +
-              '{{title}}' +
-            '</a>' +
-            '<i ng-show="subnav" class="top-nav-breadcrumb-icon fa fa-angle-right"></i>' +
-          '</div><div ng-transclude></div></div></div></div>',
-        link: function(scope, elem, attrs) {
-          scope.icon = attrs.icon;
-          scope.contextSrv = contextSrv;
-
-          scope.toggle = function() {
-            $rootScope.appEvent('toggle-sidemenu');
-          };
-        }
-      };
-    });
-
-});

+ 0 - 289
public/app/directives/valueSelectDropdown.js

@@ -1,289 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  'jquery',
-],
-function (angular, _) {
-  'use strict';
-
-  angular
-    .module('grafana.controllers')
-    .controller('ValueSelectDropdownCtrl', function($q) {
-      var vm = this;
-
-      vm.show = function() {
-        vm.oldVariableText = vm.variable.current.text;
-        vm.highlightIndex = -1;
-
-        vm.options = vm.variable.options;
-        vm.selectedValues = _.filter(vm.options, {selected: true});
-
-        vm.tags = _.map(vm.variable.tags, function(value) {
-          var tag = { text: value, selected: false };
-          _.each(vm.variable.current.tags, function(tagObj) {
-            if (tagObj.text === value) {
-              tag = tagObj;
-            }
-          });
-          return tag;
-        });
-
-        vm.search = {
-          query: '',
-          options: vm.options.slice(0, Math.min(vm.options.length, 1000))
-        };
-
-        vm.dropdownVisible = true;
-      };
-
-      vm.updateLinkText = function() {
-        var current = vm.variable.current;
-
-        if (current.tags && current.tags.length) {
-          // filer out values that are in selected tags
-          var selectedAndNotInTag = _.filter(vm.variable.options, function(option) {
-            if (!option.selected) { return false; }
-            for (var i = 0; i < current.tags.length; i++)  {
-              var tag = current.tags[i];
-              if (_.indexOf(tag.values, option.value) !== -1) {
-                return false;
-              }
-            }
-            return true;
-          });
-
-          // convert values to text
-          var currentTexts = _.pluck(selectedAndNotInTag, 'text');
-
-          // join texts
-          vm.linkText = currentTexts.join(' + ');
-          if (vm.linkText.length > 0) {
-            vm.linkText += ' + ';
-          }
-        } else {
-          vm.linkText = vm.variable.current.text;
-        }
-      };
-
-      vm.clearSelections = function() {
-        _.each(vm.options, function(option) {
-          option.selected = false;
-        });
-
-        vm.selectionsChanged(false);
-      };
-
-      vm.selectTag = function(tag) {
-        tag.selected = !tag.selected;
-        var tagValuesPromise;
-        if (!tag.values) {
-          tagValuesPromise = vm.getValuesForTag({tagKey: tag.text});
-        } else {
-          tagValuesPromise = $q.when(tag.values);
-        }
-
-        tagValuesPromise.then(function(values) {
-          tag.values = values;
-          tag.valuesText = values.join(' + ');
-          _.each(vm.options, function(option) {
-            if (_.indexOf(tag.values, option.value) !== -1) {
-              option.selected = tag.selected;
-            }
-          });
-
-          vm.selectionsChanged(false);
-        });
-      };
-
-      vm.keyDown = function (evt) {
-        if (evt.keyCode === 27) {
-          vm.hide();
-        }
-        if (evt.keyCode === 40) {
-          vm.moveHighlight(1);
-        }
-        if (evt.keyCode === 38) {
-          vm.moveHighlight(-1);
-        }
-        if (evt.keyCode === 13) {
-          if (vm.search.options.length === 0) {
-            vm.commitChanges();
-          } else {
-            vm.selectValue(vm.search.options[vm.highlightIndex], {}, true, false);
-          }
-        }
-        if (evt.keyCode === 32) {
-          vm.selectValue(vm.search.options[vm.highlightIndex], {}, false, false);
-        }
-      };
-
-      vm.moveHighlight = function(direction) {
-        vm.highlightIndex = (vm.highlightIndex + direction) % vm.search.options.length;
-      };
-
-      vm.selectValue = function(option, event, commitChange, excludeOthers) {
-        if (!option) { return; }
-
-        option.selected = !option.selected;
-
-        commitChange = commitChange || false;
-        excludeOthers = excludeOthers || false;
-
-        var setAllExceptCurrentTo = function(newValue) {
-          _.each(vm.options, function(other) {
-            if (option !== other) { other.selected = newValue; }
-          });
-        };
-
-        // commit action (enter key), should not deselect it
-        if (commitChange) {
-          option.selected = true;
-        }
-
-        if (option.text === 'All' || excludeOthers) {
-          setAllExceptCurrentTo(false);
-          commitChange = true;
-        }
-        else if (!vm.variable.multi) {
-          setAllExceptCurrentTo(false);
-          commitChange = true;
-        } else if (event.ctrlKey || event.metaKey || event.shiftKey) {
-          commitChange = true;
-          setAllExceptCurrentTo(false);
-        }
-
-        vm.selectionsChanged(commitChange);
-      };
-
-      vm.selectionsChanged = function(commitChange) {
-        vm.selectedValues = _.filter(vm.options, {selected: true});
-
-        if (vm.selectedValues.length > 1 && vm.selectedValues.length !== vm.options.length) {
-          if (vm.selectedValues[0].text === 'All') {
-            vm.selectedValues[0].selected = false;
-            vm.selectedValues = vm.selectedValues.slice(1, vm.selectedValues.length);
-          }
-        }
-
-        // validate selected tags
-        _.each(vm.tags, function(tag) {
-          if (tag.selected)  {
-            _.each(tag.values, function(value) {
-              if (!_.findWhere(vm.selectedValues, {value: value})) {
-                tag.selected = false;
-              }
-            });
-          }
-        });
-
-        vm.selectedTags = _.filter(vm.tags, {selected: true});
-        vm.variable.current.value = _.pluck(vm.selectedValues, 'value');
-        vm.variable.current.text = _.pluck(vm.selectedValues, 'text').join(' + ');
-        vm.variable.current.tags = vm.selectedTags;
-
-        // only single value
-        if (vm.selectedValues.length === 1) {
-          vm.variable.current.value = vm.selectedValues[0].value;
-        }
-
-        if (commitChange) {
-          vm.commitChanges();
-        }
-      };
-
-      vm.commitChanges = function() {
-        // if we have a search query and no options use that
-        if (vm.search.options.length === 0 && vm.search.query.length > 0) {
-          vm.variable.current = {text: vm.search.query, value: vm.search.query};
-        }
-        else if (vm.selectedValues.length === 0) {
-          // make sure one option is selected
-          vm.options[0].selected = true;
-          vm.selectionsChanged(false);
-        }
-
-        vm.dropdownVisible = false;
-        vm.updateLinkText();
-
-        if (vm.variable.current.text !== vm.oldVariableText) {
-          vm.onUpdated();
-        }
-      };
-
-      vm.queryChanged = function() {
-        vm.highlightIndex = -1;
-        vm.search.options = _.filter(vm.options, function(option) {
-          return option.text.toLowerCase().indexOf(vm.search.query.toLowerCase()) !== -1;
-        });
-
-        vm.search.options = vm.search.options.slice(0, Math.min(vm.search.options.length, 1000));
-      };
-
-      vm.init = function() {
-        vm.selectedTags = vm.variable.current.tags || [];
-        vm.updateLinkText();
-      };
-
-    });
-
-  angular
-    .module('grafana.directives')
-    .directive('valueSelectDropdown', function($compile, $window, $timeout, $rootScope) {
-
-      return {
-        scope: { variable: "=", onUpdated: "&", getValuesForTag: "&" },
-        templateUrl: 'app/partials/valueSelectDropdown.html',
-        controller: 'ValueSelectDropdownCtrl',
-        controllerAs: 'vm',
-        bindToController: true,
-        link: function(scope, elem) {
-          var bodyEl = angular.element($window.document.body);
-          var linkEl = elem.find('.variable-value-link');
-          var inputEl = elem.find('input');
-
-          function openDropdown() {
-            inputEl.css('width', Math.max(linkEl.width(), 30) + 'px');
-
-            inputEl.show();
-            linkEl.hide();
-
-            inputEl.focus();
-            $timeout(function() { bodyEl.on('click', bodyOnClick); }, 0, false);
-          }
-
-          function switchToLink() {
-            inputEl.hide();
-            linkEl.show();
-            bodyEl.off('click', bodyOnClick);
-          }
-
-          function bodyOnClick (e) {
-            if (elem.has(e.target).length === 0) {
-              scope.$apply(function() {
-                scope.vm.commitChanges();
-              });
-            }
-          }
-
-          scope.$watch('vm.dropdownVisible', function(newValue) {
-            if (newValue) {
-              openDropdown();
-            } else {
-              switchToLink();
-            }
-          });
-
-          var cleanUp = $rootScope.$on('template-variable-value-updated', function() {
-            scope.vm.updateLinkText();
-          });
-
-          scope.$on("$destroy", function() {
-            cleanUp();
-          });
-
-          scope.vm.init();
-        },
-      };
-    });
-
-});

+ 2 - 2
public/app/partials/login.html

@@ -17,7 +17,7 @@
 			</div>
 
       <form name="loginForm" class="login-form" style="margin-top: 25px;">
-				<div class="tight-from-container">
+				<div class="tight-form-container">
 					<div class="tight-form" ng-if="loginMode">
 						<ul class="tight-form-list">
 							<li class="tight-form-item" style="width: 78px">
@@ -41,7 +41,7 @@
 						<div class="clearfix"></div>
 					</div>
 
-					<div class="tight-form" ng-if="!loginMode" style="margin: 20px 0 57px 0">
+					<div class="tight-form" ng-if="!loginMode">
 						<ul class="tight-form-list">
 							<li class="tight-form-item" style="width: 79px">
 								<strong>Email</strong>

+ 1 - 1
public/app/partials/signup_step2.html

@@ -36,7 +36,7 @@
 					</div>
 				</div>
 
-				<div class="tight-from-container">
+				<div class="tight-form-container">
 					<div class="tight-form" ng-if="!autoAssignOrg">
 						<ul class="tight-form-list">
 							<li class="tight-form-item" style="width: 128px">

+ 2 - 3
public/test/specs/valueSelectDropdown-specs.js → public/test/specs/value_select_dropdown_specs.js

@@ -1,17 +1,16 @@
 define([
-  'directives/valueSelectDropdown',
+  'core/directives/value_select_dropdown',
 ],
 function () {
   'use strict';
 
-
   describe("SelectDropdownCtrl", function() {
     var scope;
     var ctrl;
     var tagValuesMap = {};
     var rootScope;
 
-    beforeEach(module('grafana.controllers'));
+    beforeEach(module('grafana.core'));
     beforeEach(inject(function($controller, $rootScope, $q) {
       rootScope = $rootScope;
       scope = $rootScope.$new();

+ 1 - 1
public/test/test-main.js

@@ -149,7 +149,7 @@ require([
     'specs/singlestat-specs',
     'specs/dynamicDashboardSrv-specs',
     'specs/unsavedChangesSrv-specs',
-    'specs/valueSelectDropdown-specs',
+    'specs/value_select_dropdown_specs',
     'specs/opentsdbDatasource-specs',
     'specs/cloudwatch-datasource-specs',
     'specs/elasticsearch-specs',