refactor: improving structure, moving things into a core module

This commit is contained in:
Torkel Ödegaard
2015-09-15 08:17:40 +02:00
parent 9dec50832d
commit 5545cdbf4d
39 changed files with 1193 additions and 1244 deletions

View File

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

View File

@@ -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'

View File

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

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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));
});
}
};
});
});

View File

@@ -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();
});
}
};
});
});

View File

@@ -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) {

View File

@@ -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);
}
};
});
});

View File

@@ -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) {

View File

@@ -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);
}
};
});
});

View File

@@ -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) {

View File

@@ -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>');
}
});
}
};
});
});

View File

@@ -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);
}
}
};
});
});

View File

@@ -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');
}
};
});
});

View File

@@ -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);
};
}
};
});
});

View File

@@ -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);
}
};
});
});

View File

@@ -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');
});
}
};
});
});

View File

@@ -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) {

View File

@@ -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');
};
}
};
});
});

View File

@@ -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();
},
};
});
});

View File

@@ -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) {

View File

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

View File

@@ -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));
});
}
};
});
});

View File

@@ -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();
});
}
};
});
});

View File

@@ -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);
}
};
});
});

View File

@@ -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);
}
};
});
});

View File

@@ -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>');
}
});
}
};
});
});

View File

@@ -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);
}
}
};
});
});

View File

@@ -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');
}
};
});
});

View File

@@ -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);
};
}
};
});
});

View File

@@ -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);
}
};
});
});

View File

@@ -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');
});
}
};
});
});

View File

@@ -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');
};
}
};
});
});

View File

@@ -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();
},
};
});
});

View File

@@ -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>

View File

@@ -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">

View File

@@ -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();

View File

@@ -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',