diff --git a/public/app/app.js b/public/app/app.js index 5d4e38bab91..7bd8a086b74 100644 --- a/public/app/app.js +++ b/public/app/app.js @@ -73,7 +73,6 @@ function (angular, $, _, appLevelRequire) { 'services/all', 'features/all', 'controllers/all', - 'directives/all', 'components/partials', 'routes/all', ]; diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 4883464a428..609059fc8e9 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -1,6 +1,22 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// export * from './directives/array_join' -export * from './directives/giveFocus' +export * from './directives/give_focus' export * from './routes/module_loader' export * from './filters/filters' diff --git a/public/app/core/core_module.ts b/public/app/core/core_module.ts index 5de6eb1221f..1f6a02c79d9 100644 --- a/public/app/core/core_module.ts +++ b/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', []); diff --git a/public/app/directives/annotationTooltip.js b/public/app/core/directives/annotation_tooltip.js similarity index 88% rename from public/app/directives/annotationTooltip.js rename to public/app/core/directives/annotation_tooltip.js index f75f05a6715..c79ca97189c 100644 --- a/public/app/directives/annotationTooltip.js +++ b/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 { diff --git a/public/app/core/directives/array_join.ts b/public/app/core/directives/array_join.ts index ff27a0e9e25..b9fb15406f0 100644 --- a/public/app/core/directives/array_join.ts +++ b/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'; diff --git a/public/app/core/directives/body_class.js b/public/app/core/directives/body_class.js new file mode 100644 index 00000000000..f9e748ea6c2 --- /dev/null +++ b/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)); + }); + } + }; + }); + +}); diff --git a/public/app/core/directives/config_modal.js b/public/app/core/directives/config_modal.js new file mode 100644 index 00000000000..36129b0cc96 --- /dev/null +++ b/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(); + }); + } + }; + }); +}); diff --git a/public/app/directives/confirmClick.js b/public/app/core/directives/confirm_click.js similarity index 75% rename from public/app/directives/confirmClick.js rename to public/app/core/directives/confirm_click.js index a3fe1af29b4..f73a847af45 100644 --- a/public/app/directives/confirmClick.js +++ b/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) { }, }; }); -}); \ No newline at end of file +}); diff --git a/public/app/core/directives/dash_edit_link.js b/public/app/core/directives/dash_edit_link.js new file mode 100644 index 00000000000..9eaa3ab6287 --- /dev/null +++ b/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 = $('
'); + + 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); + } + }; + }); +}); diff --git a/public/app/directives/dashUpload.js b/public/app/core/directives/dash_upload.js similarity index 89% rename from public/app/directives/dashUpload.js rename to public/app/core/directives/dash_upload.js index 89d52c75916..4a42d38eea7 100644 --- a/public/app/directives/dashUpload.js +++ b/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) { diff --git a/public/app/core/directives/dropdown_typeahead.js b/public/app/core/directives/dropdown_typeahead.js new file mode 100644 index 00000000000..c306b9ff8e5 --- /dev/null +++ b/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 = ''; + + var buttonTemplate = ''; + + 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); + } + }; + }); +}); diff --git a/public/app/core/directives/giveFocus.ts b/public/app/core/directives/give_focus.ts similarity index 93% rename from public/app/core/directives/giveFocus.ts rename to public/app/core/directives/give_focus.ts index 12f2d57d6e7..1a00f2b7078 100644 --- a/public/app/core/directives/giveFocus.ts +++ b/public/app/core/directives/give_focus.ts @@ -1,7 +1,7 @@ /// import angular = require('angular'); -import coreModule from '../core_module'; +import coreModule = require('../core_module'); coreModule.directive('giveFocus', function() { return function(scope, element, attrs) { diff --git a/public/app/core/directives/grafana_version_check.js b/public/app/core/directives/grafana_version_check.js new file mode 100644 index 00000000000..ad50b330587 --- /dev/null +++ b/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(' ' + + ' ' + + 'New version available: ' + response.data.version + + ''); + } + }); + } + }; + }); +}); diff --git a/public/app/core/directives/metric_segment.js b/public/app/core/directives/metric_segment.js new file mode 100644 index 00000000000..6fdc171c1f8 --- /dev/null +++ b/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 = ''; + + var buttonTemplate = ''; + + 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: '', + 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); + } + } + }; + }); +}); diff --git a/public/app/core/directives/misc.js b/public/app/core/directives/misc.js new file mode 100644 index 00000000000..5733774c3bd --- /dev/null +++ b/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 = ''; + _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 ? (' ' + attrs.tip + '') : ''; + var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : ''; + + var template = '
' + + ' ' + + '' + + ' '; + 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 ? (' ' + attrs.tip + '') : ''; + var label = ''; + + var template = '' + + ' '; + + 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 = [ + '' + ]; + + angular.forEach(items, function (item, index) { + if (item.divider) { + return ul.splice(index + 1, 0, '
  • '); + } + + var li = '' + + '' + (item.text || '') + ''; + + if (item.submenu && item.submenu.length) { + li += buildTemplate(item.submenu).join('\n'); + } + + 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'); + } + }; + }); + +}); diff --git a/public/app/core/directives/ng_model_on_blur.js b/public/app/core/directives/ng_model_on_blur.js new file mode 100644 index 00000000000..3c0a6cd70de --- /dev/null +++ b/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); + }; + } + }; + }); +}); diff --git a/public/app/core/directives/password_strenght.js b/public/app/core/directives/password_strenght.js new file mode 100644 index 00000000000..b53113f9365 --- /dev/null +++ b/public/app/core/directives/password_strenght.js @@ -0,0 +1,45 @@ +define([ + '../core_module', +], +function (coreModule) { + 'use strict'; + + coreModule.directive('passwordStrength', function() { + var template = '
    ' + + '{{strengthText}}' + + '
    '; + 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); + } + }; + }); +}); diff --git a/public/app/core/directives/spectrum_picker.js b/public/app/core/directives/spectrum_picker.js new file mode 100644 index 00000000000..92fc3982d7a --- /dev/null +++ b/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: "", + 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'); + }); + } + }; + }); +}); diff --git a/public/app/directives/tags.js b/public/app/core/directives/tags.js similarity index 94% rename from public/app/directives/tags.js rename to public/app/core/directives/tags.js index f408a5e3864..01f4c4f3c37 100644 --- a/public/app/directives/tags.js +++ b/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) { diff --git a/public/app/core/directives/topnav.js b/public/app/core/directives/topnav.js new file mode 100644 index 00000000000..38aa9a9e6b3 --- /dev/null +++ b/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: + '', + link: function(scope, elem, attrs) { + scope.icon = attrs.icon; + scope.contextSrv = contextSrv; + + scope.toggle = function() { + $rootScope.appEvent('toggle-sidemenu'); + }; + } + }; + }); + +}); diff --git a/public/app/core/directives/value_select_dropdown.js b/public/app/core/directives/value_select_dropdown.js new file mode 100644 index 00000000000..4f5076abd2f --- /dev/null +++ b/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(); + }, + }; + }); + +}); diff --git a/public/app/core/filters/filters.ts b/public/app/core/filters/filters.ts index 333ecd545f6..7ebba903686 100644 --- a/public/app/core/filters/filters.ts +++ b/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) { diff --git a/public/app/directives/all.js b/public/app/directives/all.js deleted file mode 100644 index b724c6d2bc4..00000000000 --- a/public/app/directives/all.js +++ /dev/null @@ -1,18 +0,0 @@ -define([ - './dashUpload', - './dashEditLink', - './ngModelOnBlur', - './misc', - './confirmClick', - './configModal', - './spectrumPicker', - './tags', - './bodyClass', - './valueSelectDropdown', - './metric.segment', - './grafanaVersionCheck', - './dropdown.typeahead', - './topnav', - './annotationTooltip', - './passwordStrenght', -], function () {}); diff --git a/public/app/directives/bodyClass.js b/public/app/directives/bodyClass.js deleted file mode 100644 index 495390a59e8..00000000000 --- a/public/app/directives/bodyClass.js +++ /dev/null @@ -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)); - }); - } - }; - }); - -}); diff --git a/public/app/directives/configModal.js b/public/app/directives/configModal.js deleted file mode 100644 index 35a70173798..00000000000 --- a/public/app/directives/configModal.js +++ /dev/null @@ -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(); - }); - } - }; - }); -}); diff --git a/public/app/directives/dashEditLink.js b/public/app/directives/dashEditLink.js deleted file mode 100644 index f184af76f20..00000000000 --- a/public/app/directives/dashEditLink.js +++ /dev/null @@ -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 = $('
    '); - - 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); - } - }; - }); -}); diff --git a/public/app/directives/dropdown.typeahead.js b/public/app/directives/dropdown.typeahead.js deleted file mode 100644 index 59b59ccea3a..00000000000 --- a/public/app/directives/dropdown.typeahead.js +++ /dev/null @@ -1,115 +0,0 @@ -define([ - 'angular', - 'lodash', - 'jquery', -], -function (angular, _, $) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('dropdownTypeahead', function($compile) { - - var inputTemplate = ''; - - var buttonTemplate = ''; - - 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); - } - }; - }); -}); diff --git a/public/app/directives/grafanaVersionCheck.js b/public/app/directives/grafanaVersionCheck.js deleted file mode 100644 index c98ce269381..00000000000 --- a/public/app/directives/grafanaVersionCheck.js +++ /dev/null @@ -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(' ' + - ' ' + - 'New version available: ' + response.data.version + - ''); - } - }); - } - }; - }); -}); diff --git a/public/app/directives/metric.segment.js b/public/app/directives/metric.segment.js deleted file mode 100644 index b9f80b919f7..00000000000 --- a/public/app/directives/metric.segment.js +++ /dev/null @@ -1,216 +0,0 @@ -define([ - 'angular', - 'lodash', - 'jquery', -], -function (angular, _, $) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('metricSegment', function($compile, $sce) { - var inputTemplate = ''; - - var buttonTemplate = ''; - - 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: '', - 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); - } - } - }; - }); -}); diff --git a/public/app/directives/misc.js b/public/app/directives/misc.js deleted file mode 100644 index 6e4b83bf726..00000000000 --- a/public/app/directives/misc.js +++ /dev/null @@ -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 = ''; - _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 ? (' ' + attrs.tip + '') : ''; - var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : ''; - - var template = '
    ' + - ' ' + - '' + - ' '; - 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 ? (' ' + attrs.tip + '') : ''; - var label = ''; - - var template = '' + - ' '; - - 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 = [ - '' - ]; - - angular.forEach(items, function (item, index) { - if (item.divider) { - return ul.splice(index + 1, 0, '
  • '); - } - - var li = '' + - '' + (item.text || '') + ''; - - if (item.submenu && item.submenu.length) { - li += buildTemplate(item.submenu).join('\n'); - } - - 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'); - } - }; - }); - -}); diff --git a/public/app/directives/ngModelOnBlur.js b/public/app/directives/ngModelOnBlur.js deleted file mode 100644 index 8057c071bdb..00000000000 --- a/public/app/directives/ngModelOnBlur.js +++ /dev/null @@ -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); - }; - } - }; - }); -}); diff --git a/public/app/directives/passwordStrenght.js b/public/app/directives/passwordStrenght.js deleted file mode 100644 index f75a8fe8854..00000000000 --- a/public/app/directives/passwordStrenght.js +++ /dev/null @@ -1,47 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('passwordStrength', function() { - var template = '
    ' + - '{{strengthText}}' + - '
    '; - 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); - } - }; - }); -}); diff --git a/public/app/directives/spectrumPicker.js b/public/app/directives/spectrumPicker.js deleted file mode 100644 index c6d3de6f884..00000000000 --- a/public/app/directives/spectrumPicker.js +++ /dev/null @@ -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: "", - 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'); - }); - } - }; - }); -}); diff --git a/public/app/directives/topnav.js b/public/app/directives/topnav.js deleted file mode 100644 index 88b98d55984..00000000000 --- a/public/app/directives/topnav.js +++ /dev/null @@ -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: - '', - link: function(scope, elem, attrs) { - scope.icon = attrs.icon; - scope.contextSrv = contextSrv; - - scope.toggle = function() { - $rootScope.appEvent('toggle-sidemenu'); - }; - } - }; - }); - -}); diff --git a/public/app/directives/valueSelectDropdown.js b/public/app/directives/valueSelectDropdown.js deleted file mode 100644 index f54381b442b..00000000000 --- a/public/app/directives/valueSelectDropdown.js +++ /dev/null @@ -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(); - }, - }; - }); - -}); diff --git a/public/app/partials/login.html b/public/app/partials/login.html index 2d9ed8d3b47..630f295ecd3 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -17,7 +17,7 @@