mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
refactor: improving structure, moving things into a core module
This commit is contained in:
@@ -73,7 +73,6 @@ function (angular, $, _, appLevelRequire) {
|
||||
'services/all',
|
||||
'features/all',
|
||||
'controllers/all',
|
||||
'directives/all',
|
||||
'components/partials',
|
||||
'routes/all',
|
||||
];
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
import angular = require('angular');
|
||||
|
||||
export default angular.module('grafana.core', []);
|
||||
export = angular.module('grafana.core', []);
|
||||
|
||||
@@ -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 {
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import angular = require('angular');
|
||||
import _ = require('lodash');
|
||||
import coreModule from '../core_module';
|
||||
import coreModule = require('../core_module');
|
||||
|
||||
export function arrayJoin() {
|
||||
'use strict';
|
||||
|
||||
48
public/app/core/directives/body_class.js
Normal file
48
public/app/core/directives/body_class.js
Normal 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));
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
46
public/app/core/directives/config_modal.js
Normal file
46
public/app/core/directives/config_modal.js
Normal 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();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -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) {
|
||||
103
public/app/core/directives/dash_edit_link.js
Normal file
103
public/app/core/directives/dash_edit_link.js
Normal 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);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,11 @@
|
||||
define([
|
||||
'angular',
|
||||
'kbn'
|
||||
'kbn',
|
||||
'../core_module',
|
||||
],
|
||||
function (angular, kbn) {
|
||||
function (kbn, coreModule) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('dashUpload', function(timer, alertSrv, $location) {
|
||||
coreModule.directive('dashUpload', function(timer, alertSrv, $location) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope) {
|
||||
113
public/app/core/directives/dropdown_typeahead.js
Normal file
113
public/app/core/directives/dropdown_typeahead.js
Normal 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);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular = require('angular');
|
||||
import coreModule from '../core_module';
|
||||
import coreModule = require('../core_module');
|
||||
|
||||
coreModule.directive('giveFocus', function() {
|
||||
return function(scope, element, attrs) {
|
||||
31
public/app/core/directives/grafana_version_check.js
Normal file
31
public/app/core/directives/grafana_version_check.js
Normal 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>');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
212
public/app/core/directives/metric_segment.js
Normal file
212
public/app/core/directives/metric_segment.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
122
public/app/core/directives/misc.js
Normal file
122
public/app/core/directives/misc.js
Normal 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');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
54
public/app/core/directives/ng_model_on_blur.js
Normal file
54
public/app/core/directives/ng_model_on_blur.js
Normal 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);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
45
public/app/core/directives/password_strenght.js
Normal file
45
public/app/core/directives/password_strenght.js
Normal 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);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
41
public/app/core/directives/spectrum_picker.js
Normal file
41
public/app/core/directives/spectrum_picker.js
Normal 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');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,10 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'bootstrap-tagsinput'
|
||||
'../core_module',
|
||||
'bootstrap-tagsinput',
|
||||
],
|
||||
function (angular, $) {
|
||||
function (angular, $, coreModule) {
|
||||
'use strict';
|
||||
|
||||
function djb2(str) {
|
||||
@@ -38,9 +39,7 @@ function (angular, $) {
|
||||
element.css("border-color", borderColor);
|
||||
}
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('tagColorFromName', function() {
|
||||
coreModule.directive('tagColorFromName', function() {
|
||||
return {
|
||||
scope: { tagColorFromName: "=" },
|
||||
link: function (scope, element) {
|
||||
@@ -49,9 +48,7 @@ function (angular, $) {
|
||||
};
|
||||
});
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('bootstrapTagsinput', function() {
|
||||
coreModule.directive('bootstrapTagsinput', function() {
|
||||
|
||||
function getItemProperty(scope, property) {
|
||||
if (!property) {
|
||||
50
public/app/core/directives/topnav.js
Normal file
50
public/app/core/directives/topnav.js
Normal 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');
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
284
public/app/core/directives/value_select_dropdown.js
Normal file
284
public/app/core/directives/value_select_dropdown.js
Normal 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();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
define([
|
||||
'./dashUpload',
|
||||
'./dashEditLink',
|
||||
'./ngModelOnBlur',
|
||||
'./misc',
|
||||
'./confirmClick',
|
||||
'./configModal',
|
||||
'./spectrumPicker',
|
||||
'./tags',
|
||||
'./bodyClass',
|
||||
'./valueSelectDropdown',
|
||||
'./metric.segment',
|
||||
'./grafanaVersionCheck',
|
||||
'./dropdown.typeahead',
|
||||
'./topnav',
|
||||
'./annotationTooltip',
|
||||
'./passwordStrenght',
|
||||
], function () {});
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -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>');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user