mirror of
https://github.com/grafana/grafana.git
synced 2025-01-27 00:37:04 -06:00
Merge branch 'panel_repeat'
This commit is contained in:
commit
293d0c3093
@ -140,25 +140,6 @@ function (angular, _, config) {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.directive('xngFocus', function() {
|
|
||||||
return function(scope, element, attrs) {
|
|
||||||
element.click(function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
|
|
||||||
scope.$watch(attrs.xngFocus,function (newValue) {
|
|
||||||
if (!newValue) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTimeout(function() {
|
|
||||||
element.focus();
|
|
||||||
var pos = element.val().length * 2;
|
|
||||||
element[0].setSelectionRange(pos, pos);
|
|
||||||
}, 200);
|
|
||||||
},true);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
module.directive('tagColorFromName', function() {
|
module.directive('tagColorFromName', function() {
|
||||||
|
|
||||||
function djb2(str) {
|
function djb2(str) {
|
||||||
|
@ -16,4 +16,5 @@ define([
|
|||||||
'./grafanaVersionCheck',
|
'./grafanaVersionCheck',
|
||||||
'./dropdown.typeahead',
|
'./dropdown.typeahead',
|
||||||
'./topnav',
|
'./topnav',
|
||||||
|
'./giveFocus',
|
||||||
], function () {});
|
], function () {});
|
||||||
|
26
public/app/directives/giveFocus.js
Normal file
26
public/app/directives/giveFocus.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
define([
|
||||||
|
'angular'
|
||||||
|
],
|
||||||
|
function (angular) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('grafana.directives').directive('giveFocus', function() {
|
||||||
|
return function(scope, element, attrs) {
|
||||||
|
element.click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.$watch(attrs.giveFocus,function (newValue) {
|
||||||
|
if (!newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(function() {
|
||||||
|
element.focus();
|
||||||
|
var pos = element.val().length * 2;
|
||||||
|
element[0].setSelectionRange(pos, pos);
|
||||||
|
}, 200);
|
||||||
|
},true);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -4,84 +4,156 @@ define([
|
|||||||
'lodash',
|
'lodash',
|
||||||
'jquery',
|
'jquery',
|
||||||
],
|
],
|
||||||
function (angular, app, _, $) {
|
function (angular, app, _) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular
|
angular
|
||||||
.module('grafana.directives')
|
.module('grafana.directives')
|
||||||
.directive('templateParamSelector', function($compile) {
|
.directive('variableValueSelect', function($compile, $window, $timeout) {
|
||||||
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 tabindex="1">{{variable.current.text}} <i class="fa fa-caret-down"></i></a>';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: function($scope, elem) {
|
scope: {
|
||||||
var $input = $(inputTemplate);
|
variable: "=",
|
||||||
var $button = $(buttonTemplate);
|
onUpdated: "&"
|
||||||
var variable = $scope.variable;
|
},
|
||||||
|
templateUrl: 'app/features/dashboard/partials/variableValueSelect.html',
|
||||||
|
link: function(scope, elem) {
|
||||||
|
var bodyEl = angular.element($window.document.body);
|
||||||
|
var variable = scope.variable;
|
||||||
|
|
||||||
$input.appendTo(elem);
|
scope.show = function() {
|
||||||
$button.appendTo(elem);
|
if (scope.selectorOpen) {
|
||||||
|
return;
|
||||||
function updateVariableValue(value) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
var selected = _.findWhere(variable.options, { text: value });
|
|
||||||
if (!selected) {
|
|
||||||
selected = { text: value, value: value };
|
|
||||||
}
|
|
||||||
$scope.setVariableValue($scope.variable, selected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$input.attr('data-provide', 'typeahead');
|
|
||||||
$input.typeahead({
|
|
||||||
minLength: 0,
|
|
||||||
items: 1000,
|
|
||||||
updater: function(value) {
|
|
||||||
$input.val(value);
|
|
||||||
$input.trigger('blur');
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
var typeahead = $input.data('typeahead');
|
scope.selectorOpen = true;
|
||||||
typeahead.lookup = function () {
|
scope.giveFocus = 1;
|
||||||
var options = _.map(variable.options, function(option) { return option.text; });
|
scope.oldCurrentText = variable.current.text;
|
||||||
this.query = this.$element.val() || '';
|
scope.highlightIndex = -1;
|
||||||
return this.process(options);
|
|
||||||
|
var currentValues = variable.current.value;
|
||||||
|
|
||||||
|
if (_.isString(currentValues)) {
|
||||||
|
currentValues = [currentValues];
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.options = _.map(variable.options, function(option) {
|
||||||
|
if (_.indexOf(currentValues, option.value) >= 0) {
|
||||||
|
option.selected = true;
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.search = {query: '', options: scope.options};
|
||||||
|
|
||||||
|
$timeout(function() {
|
||||||
|
bodyEl.on('click', scope.bodyOnClick);
|
||||||
|
}, 0, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
$button.click(function() {
|
scope.queryChanged = function() {
|
||||||
$input.css('width', ($button.width() + 16) + 'px');
|
scope.highlightIndex = -1;
|
||||||
|
scope.search.options = _.filter(scope.options, function(option) {
|
||||||
|
return option.text.toLowerCase().indexOf(scope.search.query.toLowerCase()) !== -1;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$button.hide();
|
scope.keyDown = function (evt) {
|
||||||
$input.show();
|
if (evt.keyCode === 27) {
|
||||||
$input.focus();
|
scope.hide();
|
||||||
|
}
|
||||||
|
if (evt.keyCode === 40) {
|
||||||
|
scope.moveHighlight(1);
|
||||||
|
}
|
||||||
|
if (evt.keyCode === 38) {
|
||||||
|
scope.moveHighlight(-1);
|
||||||
|
}
|
||||||
|
if (evt.keyCode === 13) {
|
||||||
|
scope.optionSelected(scope.search.options[scope.highlightIndex], {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var typeahead = $input.data('typeahead');
|
scope.moveHighlight = function(direction) {
|
||||||
if (typeahead) {
|
scope.highlightIndex = (scope.highlightIndex + direction) % scope.search.options.length;
|
||||||
$input.val('');
|
};
|
||||||
typeahead.lookup();
|
|
||||||
|
scope.optionSelected = function(option, event) {
|
||||||
|
option.selected = !option.selected;
|
||||||
|
|
||||||
|
var hideAfter = true;
|
||||||
|
var setAllExceptCurrentTo = function(newValue) {
|
||||||
|
_.each(scope.options, function(other) {
|
||||||
|
if (option !== other) { other.selected = newValue; }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (option.text === 'All') {
|
||||||
|
setAllExceptCurrentTo(false);
|
||||||
|
}
|
||||||
|
else if (!variable.multi) {
|
||||||
|
setAllExceptCurrentTo(false);
|
||||||
|
} else {
|
||||||
|
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||||
|
hideAfter = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setAllExceptCurrentTo(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
var selected = _.filter(scope.options, {selected: true});
|
||||||
|
|
||||||
$input.blur(function() {
|
if (selected.length === 0) {
|
||||||
if ($input.val() !== '') { updateVariableValue($input.val()); }
|
option.selected = true;
|
||||||
$input.hide();
|
selected = [option];
|
||||||
$button.show();
|
}
|
||||||
$button.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function() {
|
if (selected.length > 1 && selected.length !== scope.options.length) {
|
||||||
$button.unbind();
|
if (selected[0].text === 'All') {
|
||||||
typeahead.destroy();
|
selected[0].selected = false;
|
||||||
});
|
selected = selected.slice(1, selected.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$compile(elem.contents())($scope);
|
variable.current = {
|
||||||
}
|
text: _.pluck(selected, 'text').join(', '),
|
||||||
|
value: _.pluck(selected, 'value'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// only single value
|
||||||
|
if (variable.current.value.length === 1) {
|
||||||
|
variable.current.value = selected[0].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.updateLinkText();
|
||||||
|
scope.onUpdated();
|
||||||
|
|
||||||
|
if (hideAfter) {
|
||||||
|
scope.hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.hide = function() {
|
||||||
|
scope.selectorOpen = false;
|
||||||
|
bodyEl.off('click', scope.bodyOnClick);
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.bodyOnClick = function(e) {
|
||||||
|
var dropdown = elem.find('.variable-value-dropdown');
|
||||||
|
if (dropdown.has(e.target).length === 0) {
|
||||||
|
scope.$apply(scope.hide);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.updateLinkText = function() {
|
||||||
|
scope.labelText = variable.label || '$' + variable.name;
|
||||||
|
scope.linkText = variable.current.text;
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.$watchGroup(['variable.hideLabel', 'variable.name', 'variable.label', 'variable.current.text'], function() {
|
||||||
|
scope.updateLinkText();
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -16,5 +16,6 @@ define([
|
|||||||
'./unsavedChangesSrv',
|
'./unsavedChangesSrv',
|
||||||
'./directives/dashSearchView',
|
'./directives/dashSearchView',
|
||||||
'./graphiteImportCtrl',
|
'./graphiteImportCtrl',
|
||||||
|
'./dynamicDashboardSrv',
|
||||||
'./importCtrl',
|
'./importCtrl',
|
||||||
], function () {});
|
], function () {});
|
||||||
|
@ -15,7 +15,9 @@ function (angular, $, config) {
|
|||||||
dashboardKeybindings,
|
dashboardKeybindings,
|
||||||
timeSrv,
|
timeSrv,
|
||||||
templateValuesSrv,
|
templateValuesSrv,
|
||||||
|
dynamicDashboardSrv,
|
||||||
dashboardSrv,
|
dashboardSrv,
|
||||||
|
unsavedChangesSrv,
|
||||||
dashboardViewStateSrv,
|
dashboardViewStateSrv,
|
||||||
contextSrv,
|
contextSrv,
|
||||||
$timeout) {
|
$timeout) {
|
||||||
@ -46,6 +48,9 @@ function (angular, $, config) {
|
|||||||
// template values service needs to initialize completely before
|
// template values service needs to initialize completely before
|
||||||
// the rest of the dashboard can load
|
// the rest of the dashboard can load
|
||||||
templateValuesSrv.init(dashboard).finally(function() {
|
templateValuesSrv.init(dashboard).finally(function() {
|
||||||
|
dynamicDashboardSrv.init(dashboard);
|
||||||
|
unsavedChangesSrv.init(dashboard, $scope);
|
||||||
|
|
||||||
$scope.dashboard = dashboard;
|
$scope.dashboard = dashboard;
|
||||||
$scope.dashboardMeta = dashboard.meta;
|
$scope.dashboardMeta = dashboard.meta;
|
||||||
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
|
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
|
||||||
|
@ -10,7 +10,7 @@ function (angular, $, kbn, _, moment) {
|
|||||||
|
|
||||||
var module = angular.module('grafana.services');
|
var module = angular.module('grafana.services');
|
||||||
|
|
||||||
module.factory('dashboardSrv', function(contextSrv) {
|
module.factory('dashboardSrv', function() {
|
||||||
|
|
||||||
function DashboardModel (data, meta) {
|
function DashboardModel (data, meta) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@ -59,10 +59,6 @@ function (angular, $, kbn, _, moment) {
|
|||||||
meta.canStar = meta.canStar === false ? false : true;
|
meta.canStar = meta.canStar === false ? false : true;
|
||||||
meta.canDelete = meta.canDelete === false ? false : true;
|
meta.canDelete = meta.canDelete === false ? false : true;
|
||||||
|
|
||||||
if (contextSrv.hasRole('Viewer')) {
|
|
||||||
meta.canSave = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.editable) {
|
if (!this.editable) {
|
||||||
meta.canEdit = false;
|
meta.canEdit = false;
|
||||||
meta.canDelete = false;
|
meta.canDelete = false;
|
||||||
@ -180,6 +176,7 @@ function (angular, $, kbn, _, moment) {
|
|||||||
|
|
||||||
var currentRow = this.rows[rowIndex];
|
var currentRow = this.rows[rowIndex];
|
||||||
currentRow.panels.push(newPanel);
|
currentRow.panels.push(newPanel);
|
||||||
|
return newPanel;
|
||||||
};
|
};
|
||||||
|
|
||||||
p.formatDate = function(date, format) {
|
p.formatDate = function(date, format) {
|
||||||
|
172
public/app/features/dashboard/dynamicDashboardSrv.js
Normal file
172
public/app/features/dashboard/dynamicDashboardSrv.js
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
define([
|
||||||
|
'angular',
|
||||||
|
'lodash',
|
||||||
|
],
|
||||||
|
function (angular, _) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var module = angular.module('grafana.services');
|
||||||
|
|
||||||
|
module.service('dynamicDashboardSrv', function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.init = function(dashboard) {
|
||||||
|
this.iteration = new Date().getTime();
|
||||||
|
this.process(dashboard);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update = function(dashboard) {
|
||||||
|
this.iteration = this.iteration + 1;
|
||||||
|
this.process(dashboard);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.process = function(dashboard) {
|
||||||
|
if (dashboard.templating.list.length === 0) { return; }
|
||||||
|
this.dashboard = dashboard;
|
||||||
|
|
||||||
|
var i, j, row, panel;
|
||||||
|
for (i = 0; i < this.dashboard.rows.length; i++) {
|
||||||
|
row = this.dashboard.rows[i];
|
||||||
|
|
||||||
|
// repeat panels first
|
||||||
|
for (j = 0; j < row.panels.length; j++) {
|
||||||
|
panel = row.panels[j];
|
||||||
|
if (panel.repeat) {
|
||||||
|
this.repeatPanel(panel, row);
|
||||||
|
}
|
||||||
|
// clean up old left overs
|
||||||
|
else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) {
|
||||||
|
row.panels = _.without(row.panels, panel);
|
||||||
|
j = j - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle row repeats
|
||||||
|
if (row.repeat) {
|
||||||
|
this.repeatRow(row);
|
||||||
|
}
|
||||||
|
// clean up old left overs
|
||||||
|
else if (row.repeatRowId && row.repeatIteration !== this.iteration) {
|
||||||
|
this.dashboard.rows.splice(i, 1);
|
||||||
|
i = i - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// returns a new row clone or reuses a clone from previous iteration
|
||||||
|
this.getRowClone = function(sourceRow, index) {
|
||||||
|
if (index === 0) {
|
||||||
|
return sourceRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i, panel, row, copy;
|
||||||
|
var sourceRowId = _.indexOf(this.dashboard.rows, sourceRow) + 1;
|
||||||
|
|
||||||
|
// look for row to reuse
|
||||||
|
for (i = 0; i < this.dashboard.rows.length; i++) {
|
||||||
|
row = this.dashboard.rows[i];
|
||||||
|
if (row.repeatRowId === sourceRowId && row.repeatIteration !== this.iteration) {
|
||||||
|
copy = row;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!copy) {
|
||||||
|
copy = angular.copy(sourceRow);
|
||||||
|
this.dashboard.rows.push(copy);
|
||||||
|
|
||||||
|
// set new panel ids
|
||||||
|
for (i = 0; i < copy.panels.length; i++) {
|
||||||
|
panel = copy.panels[i];
|
||||||
|
panel.id = this.dashboard.getNextPanelId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy.repeat = null;
|
||||||
|
copy.repeatRowId = sourceRowId;
|
||||||
|
copy.repeatIteration = this.iteration;
|
||||||
|
return copy;
|
||||||
|
};
|
||||||
|
|
||||||
|
// returns a new panel clone or reuses a clone from previous iteration
|
||||||
|
this.repeatRow = function(row) {
|
||||||
|
var variables = this.dashboard.templating.list;
|
||||||
|
var variable = _.findWhere(variables, {name: row.repeat.replace('$', '')});
|
||||||
|
if (!variable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected, copy, i, panel;
|
||||||
|
if (variable.current.text === 'All') {
|
||||||
|
selected = variable.options.slice(1, variable.options.length);
|
||||||
|
} else {
|
||||||
|
selected = _.filter(variable.options, {selected: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(selected, function(option, index) {
|
||||||
|
copy = self.getRowClone(row, index);
|
||||||
|
|
||||||
|
for (i = 0; i < copy.panels.length; i++) {
|
||||||
|
panel = copy.panels[i];
|
||||||
|
panel.scopedVars = panel.scopedVars || {};
|
||||||
|
panel.scopedVars[variable.name] = option;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getPanelClone = function(sourcePanel, row, index) {
|
||||||
|
// if first clone return source
|
||||||
|
if (index === 0) {
|
||||||
|
return sourcePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i, tmpId, panel, clone;
|
||||||
|
|
||||||
|
// first try finding an existing clone to use
|
||||||
|
for (i = 0; i < row.panels.length; i++) {
|
||||||
|
panel = row.panels[i];
|
||||||
|
if (panel.repeatIteration !== this.iteration && panel.repeatPanelId === sourcePanel.id) {
|
||||||
|
clone = panel;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clone) {
|
||||||
|
clone = { id: this.dashboard.getNextPanelId() };
|
||||||
|
row.panels.push(clone);
|
||||||
|
}
|
||||||
|
|
||||||
|
// save id
|
||||||
|
tmpId = clone.id;
|
||||||
|
// copy properties from source
|
||||||
|
angular.extend(clone, sourcePanel);
|
||||||
|
// restore id
|
||||||
|
clone.id = tmpId;
|
||||||
|
clone.repeatIteration = this.iteration;
|
||||||
|
clone.repeatPanelId = sourcePanel.id;
|
||||||
|
clone.repeat = null;
|
||||||
|
return clone;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.repeatPanel = function(panel, row) {
|
||||||
|
var variables = this.dashboard.templating.list;
|
||||||
|
var variable = _.findWhere(variables, {name: panel.repeat});
|
||||||
|
if (!variable) { return; }
|
||||||
|
|
||||||
|
var selected;
|
||||||
|
if (variable.current.text === 'All') {
|
||||||
|
selected = variable.options.slice(1, variable.options.length);
|
||||||
|
} else {
|
||||||
|
selected = _.filter(variable.options, {selected: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(selected, function(option, index) {
|
||||||
|
var copy = self.getPanelClone(panel, row, index);
|
||||||
|
copy.scopedVars = {};
|
||||||
|
copy.scopedVars[variable.name] = option;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -27,7 +27,7 @@
|
|||||||
<li ng-show="dashboardMeta.canShare">
|
<li ng-show="dashboardMeta.canShare">
|
||||||
<a class="pointer" ng-click="shareDashboard()" bs-tooltip="'Share dashboard'" data-placement="bottom"><i class="fa fa-share-square-o"></i></a>
|
<a class="pointer" ng-click="shareDashboard()" bs-tooltip="'Share dashboard'" data-placement="bottom"><i class="fa fa-share-square-o"></i></a>
|
||||||
</li>
|
</li>
|
||||||
<li ng-show="dashboardMeta.canSave">
|
<li ng-show="dashboardMeta.canSave && contextSrv.isEditor">
|
||||||
<a ng-click="saveDashboard()" bs-tooltip="'Save dashboard'" data-placement="bottom"><i class="fa fa-save"></i></a>
|
<a ng-click="saveDashboard()" bs-tooltip="'Save dashboard'" data-placement="bottom"><i class="fa fa-save"></i></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
<span class="template-variable" ng-show="!variable.hideLabel" style="padding-right: 5px">
|
||||||
|
{{labelText}}:
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div style="position: relative; display: inline-block">
|
||||||
|
<a ng-click="show()" class="variable-value-link">
|
||||||
|
{{linkText}}
|
||||||
|
<i class="fa fa-caret-down"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div ng-if="selectorOpen" class="variable-value-dropdown">
|
||||||
|
<div class="variable-search-wrapper">
|
||||||
|
<span style="position: relative;">
|
||||||
|
<input type="text" placeholder="Search values..." ng-keydown="keyDown($event)" give-focus="giveFocus" tabindex="1" ng-model="search.query" spellcheck='false' ng-change="queryChanged()" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="variable-options-container" ng-if="!query.tagcloud">
|
||||||
|
<a class="variable-option pointer" bindonce ng-repeat="option in search.options"
|
||||||
|
ng-class="{'selected': option.selected, 'highlighted': $index === highlightIndex}" ng-click="optionSelected(option, $event)">
|
||||||
|
<span >{{option.text}}</label>
|
||||||
|
<span class="fa fa-fw variable-option-icon"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -7,7 +7,7 @@ function (angular, _) {
|
|||||||
|
|
||||||
var module = angular.module('grafana.controllers');
|
var module = angular.module('grafana.controllers');
|
||||||
|
|
||||||
module.controller('SubmenuCtrl', function($scope, $q, $rootScope, templateValuesSrv) {
|
module.controller('SubmenuCtrl', function($scope, $q, $rootScope, templateValuesSrv, dynamicDashboardSrv) {
|
||||||
var _d = {
|
var _d = {
|
||||||
enable: true
|
enable: true
|
||||||
};
|
};
|
||||||
@ -26,8 +26,11 @@ function (angular, _) {
|
|||||||
$rootScope.$broadcast('refresh');
|
$rootScope.$broadcast('refresh');
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.setVariableValue = function(param, option) {
|
$scope.variableUpdated = function(variable) {
|
||||||
templateValuesSrv.setVariableValue(param, option);
|
templateValuesSrv.variableUpdated(variable).then(function() {
|
||||||
|
dynamicDashboardSrv.update($scope.dashboard);
|
||||||
|
$rootScope.$broadcast('refresh');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.init();
|
$scope.init();
|
||||||
|
@ -1,129 +1,114 @@
|
|||||||
define([
|
define([
|
||||||
'angular',
|
'angular',
|
||||||
'lodash',
|
'lodash',
|
||||||
'config',
|
|
||||||
],
|
],
|
||||||
function(angular, _, config) {
|
function(angular, _) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
if (!config.unsaved_changes_warning) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var module = angular.module('grafana.services');
|
var module = angular.module('grafana.services');
|
||||||
|
|
||||||
module.service('unsavedChangesSrv', function($rootScope, $modal, $q, $location, $timeout) {
|
module.service('unsavedChangesSrv', function($modal, $q, $location, $timeout, contextSrv, $window) {
|
||||||
|
|
||||||
var self = this;
|
function Tracker(dashboard, scope) {
|
||||||
var modalScope = $rootScope.$new();
|
var self = this;
|
||||||
|
|
||||||
$rootScope.$on("dashboard-loaded", function(event, newDashboard) {
|
this.original = dashboard.getSaveModelClone();
|
||||||
// wait for different services to patch the dashboard (missing properties)
|
this.current = dashboard;
|
||||||
$timeout(function() {
|
this.originalPath = $location.path();
|
||||||
self.original = newDashboard.getSaveModelClone();
|
this.scope = scope;
|
||||||
self.current = newDashboard;
|
|
||||||
}, 1200);
|
|
||||||
});
|
|
||||||
|
|
||||||
$rootScope.$on("dashboard-saved", function(event, savedDashboard) {
|
// register events
|
||||||
self.original = savedDashboard.getSaveModelClone();
|
scope.onAppEvent('dashboard-saved', function() {
|
||||||
self.current = savedDashboard;
|
self.original = self.current.getSaveModelClone();
|
||||||
self.orignalPath = $location.path();
|
self.originalPath = $location.path();
|
||||||
});
|
});
|
||||||
|
|
||||||
$rootScope.$on("$routeChangeSuccess", function() {
|
$window.onbeforeunload = function() {
|
||||||
self.original = null;
|
if (self.ignoreChanges()) { return; }
|
||||||
self.originalPath = $location.path();
|
if (self.hasChanges()) {
|
||||||
});
|
return "There are unsaved changes to this dashboard";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.ignoreChanges = function() {
|
scope.$on("$locationChangeStart", function(event, next) {
|
||||||
if (!self.current || !self.current.meta) { return true; }
|
|
||||||
|
|
||||||
var meta = self.current.meta;
|
|
||||||
return !meta.canSave || meta.fromScript || meta.fromFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onbeforeunload = function() {
|
|
||||||
if (self.ignoreChanges()) { return; }
|
|
||||||
if (self.has_unsaved_changes()) {
|
|
||||||
return "There are unsaved changes to this dashboard";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.init = function() {
|
|
||||||
$rootScope.$on("$locationChangeStart", function(event, next) {
|
|
||||||
// check if we should look for changes
|
// check if we should look for changes
|
||||||
if (self.originalPath === $location.path()) { return true; }
|
if (self.originalPath === $location.path()) { return true; }
|
||||||
if (self.ignoreChanges()) { return true; }
|
if (self.ignoreChanges()) { return true; }
|
||||||
|
|
||||||
if (self.has_unsaved_changes()) {
|
if (self.hasChanges()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
self.next = next;
|
self.next = next;
|
||||||
|
|
||||||
$timeout(self.open_modal);
|
$timeout(function() {
|
||||||
|
self.open_modal();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = Tracker.prototype;
|
||||||
|
|
||||||
|
// for some dashboards and users
|
||||||
|
// changes should be ignored
|
||||||
|
p.ignoreChanges = function() {
|
||||||
|
if (!this.original) { return false; }
|
||||||
|
if (!contextSrv.isEditor) { return true; }
|
||||||
|
if (!this.current || !this.current.meta) { return true; }
|
||||||
|
|
||||||
|
var meta = this.current.meta;
|
||||||
|
return !meta.canSave || meta.fromScript || meta.fromFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.open_modal = function() {
|
// remove stuff that should not count in diff
|
||||||
var confirmModal = $modal({
|
p.cleanDashboardFromIgnoredChanges = function(dash) {
|
||||||
template: './app/partials/unsaved-changes.html',
|
// ignore time and refresh
|
||||||
modalClass: 'confirm-modal',
|
dash.time = 0;
|
||||||
persist: true,
|
dash.refresh = 0;
|
||||||
show: false,
|
dash.schemaVersion = 0;
|
||||||
scope: modalScope,
|
|
||||||
keyboard: false
|
|
||||||
});
|
|
||||||
|
|
||||||
$q.when(confirmModal).then(function(modalEl) {
|
// filter row and panels properties that should be ignored
|
||||||
modalEl.modal('show');
|
dash.rows = _.filter(dash.rows, function(row) {
|
||||||
});
|
if (row.repeatRowId) {
|
||||||
};
|
return false;
|
||||||
|
|
||||||
this.has_unsaved_changes = function() {
|
|
||||||
if (!self.original) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var current = self.current.getSaveModelClone();
|
|
||||||
var original = self.original;
|
|
||||||
|
|
||||||
// ignore timespan changes
|
|
||||||
current.time = original.time = {};
|
|
||||||
current.refresh = original.refresh;
|
|
||||||
// ignore version
|
|
||||||
current.version = original.version;
|
|
||||||
|
|
||||||
// ignore template variable values
|
|
||||||
_.each(current.templating.list, function(value, index) {
|
|
||||||
value.current = null;
|
|
||||||
value.options = null;
|
|
||||||
|
|
||||||
if (original.templating.list.length > index) {
|
|
||||||
original.templating.list[index].current = null;
|
|
||||||
original.templating.list[index].options = null;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// ignore some panel and row stuff
|
row.panels = _.filter(row.panels, function(panel) {
|
||||||
current.forEachPanel(function(panel, panelIndex, row, rowIndex) {
|
if (panel.repeatPanelId) {
|
||||||
var originalRow = original.rows[rowIndex];
|
return false;
|
||||||
var originalPanel = original.getPanelById(panel.id);
|
}
|
||||||
// ignore row collapse state
|
|
||||||
if (originalRow) {
|
// remove scopedVars
|
||||||
row.collapse = originalRow.collapse;
|
panel.scopedVars = null;
|
||||||
}
|
|
||||||
if (originalPanel) {
|
// ignore panel legend sort
|
||||||
// ignore graph legend sort
|
if (panel.legend) {
|
||||||
if (originalPanel.legend && panel.legend) {
|
|
||||||
delete originalPanel.legend.sortDesc;
|
|
||||||
delete originalPanel.legend.sort;
|
|
||||||
delete panel.legend.sort;
|
delete panel.legend.sort;
|
||||||
delete panel.legend.sortDesc;
|
delete panel.legend.sortDesc;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ignore collapse state
|
||||||
|
row.collapse = false;
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ignore template variable values
|
||||||
|
_.each(dash.templating.list, function(value) {
|
||||||
|
value.current = null;
|
||||||
|
value.options = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
p.hasChanges = function() {
|
||||||
|
var current = this.current.getSaveModelClone();
|
||||||
|
var original = this.original;
|
||||||
|
|
||||||
|
this.cleanDashboardFromIgnoredChanges(current);
|
||||||
|
this.cleanDashboardFromIgnoredChanges(original);
|
||||||
|
|
||||||
var currentTimepicker = _.findWhere(current.nav, { type: 'timepicker' });
|
var currentTimepicker = _.findWhere(current.nav, { type: 'timepicker' });
|
||||||
var originalTimepicker = _.findWhere(original.nav, { type: 'timepicker' });
|
var originalTimepicker = _.findWhere(original.nav, { type: 'timepicker' });
|
||||||
|
|
||||||
@ -141,28 +126,43 @@ function(angular, _, config) {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.goto_next = function() {
|
p.open_modal = function() {
|
||||||
|
var tracker = this;
|
||||||
|
|
||||||
|
var modalScope = this.scope.$new();
|
||||||
|
modalScope.ignore = function() {
|
||||||
|
tracker.original = null;
|
||||||
|
tracker.goto_next();
|
||||||
|
};
|
||||||
|
|
||||||
|
modalScope.save = function() {
|
||||||
|
tracker.scope.$emit('save-dashboard');
|
||||||
|
};
|
||||||
|
|
||||||
|
var confirmModal = $modal({
|
||||||
|
template: './app/partials/unsaved-changes.html',
|
||||||
|
modalClass: 'confirm-modal',
|
||||||
|
persist: false,
|
||||||
|
show: false,
|
||||||
|
scope: modalScope,
|
||||||
|
keyboard: false
|
||||||
|
});
|
||||||
|
|
||||||
|
$q.when(confirmModal).then(function(modalEl) {
|
||||||
|
modalEl.modal('show');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
p.goto_next = function() {
|
||||||
var baseLen = $location.absUrl().length - $location.url().length;
|
var baseLen = $location.absUrl().length - $location.url().length;
|
||||||
var nextUrl = self.next.substring(baseLen);
|
var nextUrl = this.next.substring(baseLen);
|
||||||
$location.url(nextUrl);
|
$location.url(nextUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
modalScope.ignore = function() {
|
this.Tracker = Tracker;
|
||||||
self.original = null;
|
this.init = function(dashboard, scope) {
|
||||||
self.goto_next();
|
// wait for different services to patch the dashboard (missing properties)
|
||||||
|
$timeout(function() { new Tracker(dashboard, scope); }, 1200);
|
||||||
};
|
};
|
||||||
|
|
||||||
modalScope.save = function() {
|
|
||||||
var unregister = $rootScope.$on('dashboard-saved', function() {
|
|
||||||
self.goto_next();
|
|
||||||
});
|
|
||||||
|
|
||||||
$timeout(unregister, 2000);
|
|
||||||
|
|
||||||
$rootScope.$emit('save-dashboard');
|
|
||||||
};
|
|
||||||
|
|
||||||
}).run(function(unsavedChangesSrv) {
|
|
||||||
unsavedChangesSrv.init();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -68,6 +68,7 @@ function (angular, _, kbn, $) {
|
|||||||
targets: scope.panel.targets,
|
targets: scope.panel.targets,
|
||||||
format: scope.panel.renderer === 'png' ? 'png' : 'json',
|
format: scope.panel.renderer === 'png' ? 'png' : 'json',
|
||||||
maxDataPoints: scope.resolution,
|
maxDataPoints: scope.resolution,
|
||||||
|
scopedVars: scope.panel.scopedVars,
|
||||||
cacheTimeout: scope.panel.cacheTimeout
|
cacheTimeout: scope.panel.cacheTimeout
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ function (angular, $, _) {
|
|||||||
.directive('panelMenu', function($compile, linkSrv) {
|
.directive('panelMenu', function($compile, linkSrv) {
|
||||||
var linkTemplate =
|
var linkTemplate =
|
||||||
'<span class="panel-title drag-handle pointer">' +
|
'<span class="panel-title drag-handle pointer">' +
|
||||||
'<span class="panel-title-text drag-handle">{{panel.title | interpolateTemplateVars}}</span>' +
|
'<span class="panel-title-text drag-handle">{{panel.title | interpolateTemplateVars:this}}</span>' +
|
||||||
'<span class="panel-links-icon"></span>' +
|
'<span class="panel-links-icon"></span>' +
|
||||||
'<span class="panel-time-info" ng-show="panelMeta.timeInfo"><i class="fa fa-clock-o"></i> {{panelMeta.timeInfo}}</span>' +
|
'<span class="panel-time-info" ng-show="panelMeta.timeInfo"><i class="fa fa-clock-o"></i> {{panelMeta.timeInfo}}</span>' +
|
||||||
'</span>';
|
'</span>';
|
||||||
|
@ -11,6 +11,7 @@ function (angular, _, config) {
|
|||||||
module.service('panelSrv', function($rootScope, $timeout, datasourceSrv, $q) {
|
module.service('panelSrv', function($rootScope, $timeout, datasourceSrv, $q) {
|
||||||
|
|
||||||
this.init = function($scope) {
|
this.init = function($scope) {
|
||||||
|
|
||||||
if (!$scope.panel.span) { $scope.panel.span = 12; }
|
if (!$scope.panel.span) { $scope.panel.span = 12; }
|
||||||
|
|
||||||
$scope.inspector = {};
|
$scope.inspector = {};
|
||||||
|
@ -17,6 +17,8 @@ function (angular, _) {
|
|||||||
options: [],
|
options: [],
|
||||||
includeAll: false,
|
includeAll: false,
|
||||||
allFormat: 'glob',
|
allFormat: 'glob',
|
||||||
|
multi: false,
|
||||||
|
multiFormat: 'glob',
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.init = function() {
|
$scope.init = function() {
|
||||||
@ -75,7 +77,7 @@ function (angular, _) {
|
|||||||
if ($scope.current.datasource === void 0) {
|
if ($scope.current.datasource === void 0) {
|
||||||
$scope.current.datasource = null;
|
$scope.current.datasource = null;
|
||||||
$scope.current.type = 'query';
|
$scope.current.type = 'query';
|
||||||
$scope.current.allFormat = 'Glob';
|
$scope.current.allFormat = 'glob';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,11 +29,23 @@ function (angular, _) {
|
|||||||
_.each(this.variables, function(variable) {
|
_.each(this.variables, function(variable) {
|
||||||
if (!variable.current || !variable.current.value) { return; }
|
if (!variable.current || !variable.current.value) { return; }
|
||||||
|
|
||||||
this._values[variable.name] = variable.current.value;
|
this._values[variable.name] = this.renderVariableValue(variable);
|
||||||
this._texts[variable.name] = variable.current.text;
|
this._texts[variable.name] = variable.current.text;
|
||||||
}, this);
|
}, this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.renderVariableValue = function(variable) {
|
||||||
|
var value = variable.current.value;
|
||||||
|
if (_.isString(value)) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
if (variable.multiFormat === 'regex values') {
|
||||||
|
return '(' + value.join('|') + ')';
|
||||||
|
}
|
||||||
|
return '{' + value.join(',') + '}';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.setGrafanaVariable = function (name, value) {
|
this.setGrafanaVariable = function (name, value) {
|
||||||
this._grafanaVariables[name] = value;
|
this._grafanaVariables[name] = value;
|
||||||
};
|
};
|
||||||
|
@ -73,17 +73,15 @@ function (angular, _, kbn) {
|
|||||||
templateSrv.setGrafanaVariable('$__auto_interval', interval);
|
templateSrv.setGrafanaVariable('$__auto_interval', interval);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setVariableValue = function(variable, option, recursive) {
|
this.setVariableValue = function(variable, option) {
|
||||||
variable.current = option;
|
variable.current = option;
|
||||||
|
|
||||||
templateSrv.updateTemplateData();
|
templateSrv.updateTemplateData();
|
||||||
|
return this.updateOptionsInChildVariables(variable);
|
||||||
|
};
|
||||||
|
|
||||||
return this.updateOptionsInChildVariables(variable)
|
this.variableUpdated = function(variable) {
|
||||||
.then(function() {
|
templateSrv.updateTemplateData();
|
||||||
if (!recursive) {
|
return this.updateOptionsInChildVariables(variable);
|
||||||
$rootScope.$broadcast('refresh');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateOptionsInChildVariables = function(updatedVariable) {
|
this.updateOptionsInChildVariables = function(updatedVariable) {
|
||||||
@ -130,11 +128,11 @@ function (angular, _, kbn) {
|
|||||||
if (variable.current) {
|
if (variable.current) {
|
||||||
var currentOption = _.findWhere(variable.options, { text: variable.current.text });
|
var currentOption = _.findWhere(variable.options, { text: variable.current.text });
|
||||||
if (currentOption) {
|
if (currentOption) {
|
||||||
return self.setVariableValue(variable, currentOption, true);
|
return self.setVariableValue(variable, currentOption);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.setVariableValue(variable, variable.options[0], true);
|
return self.setVariableValue(variable, variable.options[0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -56,8 +56,8 @@ define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, momen
|
|||||||
});
|
});
|
||||||
|
|
||||||
module.filter('interpolateTemplateVars', function(templateSrv) {
|
module.filter('interpolateTemplateVars', function(templateSrv) {
|
||||||
function interpolateTemplateVars(text) {
|
function interpolateTemplateVars(text, scope) {
|
||||||
return templateSrv.replaceWithText(text);
|
return templateSrv.replaceWithText(text, scope.panel.scopedVars);
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolateTemplateVars.$stateful = true;
|
interpolateTemplateVars.$stateful = true;
|
||||||
|
@ -190,7 +190,7 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="tight-form">
|
<div class="tight-form">
|
||||||
<ul class="tight-form-list">
|
<ul class="tight-form-list">
|
||||||
<li class="tight-form-item" style="width: 100px">
|
<li class="tight-form-item" style="width: 105px">
|
||||||
<strong>Legend values</strong>
|
<strong>Legend values</strong>
|
||||||
</li>
|
</li>
|
||||||
<li class="tight-form-item">
|
<li class="tight-form-item">
|
||||||
|
@ -112,7 +112,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (elem.width() === 0) {
|
if (elem.width() === 0) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,7 +277,6 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
|
|||||||
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
|
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTimeAxis(options) {
|
function addTimeAxis(options) {
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Panels, draggable needs to be disabled in fullscreen because Firefox bug -->
|
<!-- Panels, draggable needs to be disabled in fullscreen because Firefox bug -->
|
||||||
<div ng-repeat="(name, panel) in row.panels" class="panel"
|
<div ng-repeat="(name, panel) in row.panels track by panel.id" class="panel"
|
||||||
ui-draggable="{{!dashboardViewState.fullscreen}}" drag="panel.id"
|
ui-draggable="{{!dashboardViewState.fullscreen}}" drag="panel.id"
|
||||||
ui-on-Drop="onDrop($data, row, panel)"
|
ui-on-Drop="onDrop($data, row, panel)"
|
||||||
drag-handle-class="drag-handle" panel-width>
|
drag-handle-class="drag-handle" panel-width>
|
||||||
|
@ -1,17 +1,51 @@
|
|||||||
<div class="editor-row">
|
<div class="editor-row">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h5>General options</h5>
|
<h5>General options</h5>
|
||||||
<div class="editor-option">
|
<div class="tight-form">
|
||||||
<label class="small">Title</label><input type="text" class="input-medium" ng-model='panel.title'></input>
|
<ul class="tight-form-list">
|
||||||
</div>
|
<li class="tight-form-item">
|
||||||
<div class="editor-option">
|
Title
|
||||||
<label class="small">Span</label> <select class="input-mini" ng-model="panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
|
</li>
|
||||||
</div>
|
<li>
|
||||||
<div class="editor-option">
|
<input type="text" class="input-xlarge tight-form-input" ng-model='panel.title'></input>
|
||||||
<label class="small">Height</label><input type="text" class="input-small" ng-model='panel.height'></select>
|
</li>
|
||||||
</div>
|
<li class="tight-form-item">
|
||||||
<editor-opt-bool text="Transparent" model="panel.transparent"></editor-opt-bool>
|
Span
|
||||||
</div>
|
</li>
|
||||||
|
<li>
|
||||||
|
<select class="input-mini tight-form-input" ng-model="panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
|
||||||
|
</li>
|
||||||
|
<li class="tight-form-item">
|
||||||
|
Height
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="text" class="input-small tight-form-input" ng-model='panel.height'></input>
|
||||||
|
</li>
|
||||||
|
<li class="tight-form-item">
|
||||||
|
<label class="checkbox-label" for="panel.transparent">Transparent</label>
|
||||||
|
<input class="cr1" id="panel.transparent" type="checkbox" ng-model="panel.transparent" ng-checked="panel.transparent">
|
||||||
|
<label for="panel.transparent" class="cr1"></label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h5>Templating options</h5>
|
||||||
|
<div class="tight-form">
|
||||||
|
<ul class="tight-form-list">
|
||||||
|
<li class="tight-form-item">
|
||||||
|
Repeat Panel
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<select class="input-small tight-form-input last" ng-model="panel.repeat" ng-options="f.name as f.name for f in dashboard.templating.list">
|
||||||
|
<option value=""></option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<panel-link-editor panel="panel"></panel-link-editor>
|
<panel-link-editor panel="panel"></panel-link-editor>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
<div class="gf-box-header">
|
<div class="gf-box-header">
|
||||||
<div class="gf-box-title">
|
<div class="gf-box-title">
|
||||||
<i class="fa fa-th-list"></i>
|
<i class="fa fa-th-list"></i>
|
||||||
@ -16,15 +17,48 @@
|
|||||||
|
|
||||||
<div class="gf-box-body">
|
<div class="gf-box-body">
|
||||||
|
|
||||||
<div class="editor-row" ng-if="editor.index == 0">
|
<div class="editor-row">
|
||||||
<div class="editor-option">
|
<div class="section">
|
||||||
<label class="small">Title</label><input type="text" class="input-xlarge" ng-model='row.title'></input>
|
<h5>Row details</h5>
|
||||||
|
<div class="tight-form">
|
||||||
|
<ul class="tight-form-list">
|
||||||
|
<li class="tight-form-item">
|
||||||
|
Title
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="text" class="input-xlarge tight-form-input" ng-model='row.title'></input>
|
||||||
|
</li>
|
||||||
|
<li class="tight-form-item">
|
||||||
|
Height
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="text" class="input-small tight-form-input" ng-model='row.height'></input>
|
||||||
|
</li>
|
||||||
|
<li class="tight-form-item">
|
||||||
|
<label class="checkbox-label" for="row.showTitle">Show Title</label>
|
||||||
|
<input class="cr1" id="row.showTitle" type="checkbox" ng-model="row.showTitle" ng-checked="row.showTitle">
|
||||||
|
<label for="row.showTitle" class="cr1"></label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-option">
|
<div class="section">
|
||||||
<label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
|
<h5>Templating options</h5>
|
||||||
|
<div class="tight-form">
|
||||||
|
<ul class="tight-form-list">
|
||||||
|
<li class="tight-form-item">
|
||||||
|
Repeat Row
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<select class="input-small tight-form-input last" ng-model="row.repeat" ng-options="f.name as f.name for f in dashboard.templating.list">
|
||||||
|
<option value=""></option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<editor-opt-bool text="Editable" model="row.editable"></editor-opt-bool>
|
|
||||||
<editor-opt-bool text="Show title" model="row.showTitle"></editor-opt-bool>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<div class="search-field-wrapper">
|
<div class="search-field-wrapper">
|
||||||
<span style="position: relative;">
|
<span style="position: relative;">
|
||||||
<input type="text" placeholder="Find dashboards by name" xng-focus="giveSearchFocus" tabindex="1"
|
<input type="text" placeholder="Find dashboards by name" give-focus="giveSearchFocus" tabindex="1"
|
||||||
ng-keydown="keyDown($event)" ng-model="query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="search()" />
|
ng-keydown="keyDown($event)" ng-model="query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="search()" />
|
||||||
</span>
|
</span>
|
||||||
<div class="search-switches">
|
<div class="search-switches">
|
||||||
|
@ -1,19 +1,28 @@
|
|||||||
<div class="submenu-controls" ng-controller="SubmenuCtrl">
|
<div class="submenu-controls" ng-controller="SubmenuCtrl">
|
||||||
<div class="tight-form borderless">
|
<div class="tight-form borderless">
|
||||||
|
|
||||||
|
|
||||||
<ul class="tight-form-list" ng-if="dashboard.templating.list.length > 0">
|
<ul class="tight-form-list" ng-if="dashboard.templating.list.length > 0">
|
||||||
<li ng-repeat-start="variable in variables" class="tight-form-item template-param-name">
|
<li ng-repeat="variable in variables" class="tight-form-item template-param-name dropdown">
|
||||||
<span class="template-variable ">
|
<variable-value-select variable="variable" on-updated="variableUpdated(variable)"></variable-value-select>
|
||||||
${{variable.name}}:
|
|
||||||
</span>
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- <li class="dropdown" ng-repeat-end> -->
|
||||||
|
<!-- <a class="tight-form-item" tabindex="1" data-toggle="dropdown">{{variable.current.text}} <i class="fa fa-caret-down"></i></a> -->
|
||||||
|
<!-- <div class="dropdown-menu variable-values-dropdown"> -->
|
||||||
|
<!-- <input type="text" class="fluid-width"> -->
|
||||||
|
<!-- <div class="variable-values-list"> -->
|
||||||
|
<!-- <div class="variable-values-list-item" ng-repeat="option in variable.options"> -->
|
||||||
|
<!-- <editor-checkbox text="{{option.text}}" model="asd" change="buildUrl()"></editor-checkbox> -->
|
||||||
|
<!-- </div> -->
|
||||||
|
<!-- </div> -->
|
||||||
|
<!-- </div> -->
|
||||||
|
<!-- </li> -->
|
||||||
|
<!-- -->
|
||||||
|
<!--
|
||||||
<li ng-repeat-end template-param-selector>
|
<li ng-repeat-end template-param-selector>
|
||||||
</li>
|
</li>
|
||||||
|
-->
|
||||||
|
|
||||||
<li class="tight-form-item" style="width: 15px">
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="tight-form-list" ng-if="dashboard.annotations.list.length > 0">
|
<ul class="tight-form-list" ng-if="dashboard.annotations.list.length > 0">
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
{{variable.query}}
|
{{variable.query}}
|
||||||
</td>
|
</td>
|
||||||
<td style="width: 1%">
|
<td style="width: 1%">
|
||||||
<a ng-click="edit(variable)" class="btn btn-success btn-small">
|
<a ng-click="edit(variable)" class="btn btn-inverse btn-small">
|
||||||
<i class="fa fa-edit"></i>
|
<i class="fa fa-edit"></i>
|
||||||
Edit
|
Edit
|
||||||
</a>
|
</a>
|
||||||
@ -56,97 +56,119 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="editor.index == 1 || (editor.index == 2 && !currentIsNew)">
|
<div ng-if="editor.index == 1 || (editor.index == 2 && !currentIsNew)">
|
||||||
<div class="editor-option">
|
<div class="editor-row">
|
||||||
<div class="editor-row">
|
<div class="section">
|
||||||
<div class="editor-option">
|
<h5>General</h5>
|
||||||
<label class="small">Variable name</label>
|
|
||||||
<input type="text" class="input-medium" ng-model='current.name' placeholder="name" required></input>
|
|
||||||
</div>
|
|
||||||
<div class="editor-option">
|
|
||||||
<label class="small">Type</label>
|
|
||||||
<select class="input-medium" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
|
|
||||||
</div>
|
|
||||||
<div class="editor-option" ng-show="current.type === 'query'">
|
|
||||||
<label class="small">Datasource</label>
|
|
||||||
<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
|
|
||||||
tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
|
|
||||||
model="current.refresh"></editor-opt-bool>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-show="current.type === 'interval'">
|
|
||||||
<div class="editor-row">
|
<div class="editor-row">
|
||||||
<div class="editor-option">
|
<div class="editor-option">
|
||||||
<label class="small">Values</label>
|
<label class="small">Variable name</label>
|
||||||
<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="name"></input>
|
<input type="text" class="input-medium" ng-model='current.name' placeholder="name" required></input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="editor-row">
|
|
||||||
<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
|
|
||||||
<div class="editor-option" ng-show="current.auto">
|
|
||||||
<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
|
|
||||||
<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-show="current.type === 'custom'">
|
|
||||||
<div class="editor-row">
|
|
||||||
<div class="editor-option">
|
<div class="editor-option">
|
||||||
<label class="small">Values seperated by comma</label>
|
<label class="small">Type</label>
|
||||||
<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
|
<select class="input-small" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
|
||||||
|
</div>
|
||||||
|
<div class="editor-option" ng-show="current.type === 'query'">
|
||||||
|
<label class="small">Datasource</label>
|
||||||
|
<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="current.type === 'interval'">
|
||||||
|
<div class="editor-row">
|
||||||
|
<div class="editor-option">
|
||||||
|
<label class="small">Values</label>
|
||||||
|
<input type="text" class="input-large" ng-model='current.query' ng-blur="runQuery()" placeholder="name"></input>
|
||||||
|
</div>
|
||||||
|
<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
|
||||||
|
<div class="editor-option" ng-show="current.auto">
|
||||||
|
<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
|
||||||
|
<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="current.type === 'custom'">
|
||||||
|
<div class="editor-row">
|
||||||
|
<div class="editor-option">
|
||||||
|
<label class="small">Values seperated by comma</label>
|
||||||
|
<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="current.type === 'query'">
|
||||||
|
<h5>Values Query</h5>
|
||||||
|
<div class="editor-row">
|
||||||
|
<div class="editor-option form-inline">
|
||||||
|
<label class="small">Variable values query</label>
|
||||||
|
<input type="text" class="input-xxlarge" ng-model='current.query' placeholder="apps.servers.*"></input>
|
||||||
|
<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'Execute query'" data-placement="right"><i class="fa fa-play"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-row" style="margin: 15px 0">
|
||||||
|
<div class="editor-option form-inline">
|
||||||
|
<label class="small">regex (optional, if you want to extract part of a series name or metric node segment)</label>
|
||||||
|
<input type="text" class="input-xxlarge" ng-model='current.regex' placeholder="/.*-(.*)-.*/"></input>
|
||||||
|
<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'execute query'" data-placement="right"><i class="fa fa-play"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-row" style="margin: 15px 0">
|
||||||
|
<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
|
||||||
|
tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
|
||||||
|
model="current.refresh"></editor-opt-bool>
|
||||||
|
|
||||||
|
<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
|
||||||
|
<div class="editor-option" ng-show="current.includeAll">
|
||||||
|
<label class="small">All format</label>
|
||||||
|
<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
|
||||||
|
</div>
|
||||||
|
<div class="editor-option" ng-show="current.includeAll">
|
||||||
|
<label class="small">All value</label>
|
||||||
|
<input type="text" class="input-xlarge" ng-model='current.options[0].value'></input>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-show="current.type === 'query'">
|
<div class="section">
|
||||||
<div class="editor-row">
|
<div class="section">
|
||||||
<div class="editor-option form-inline">
|
<h5>Display options</h5>
|
||||||
<label class="small">Variable values query</label>
|
<div class="editor-option">
|
||||||
<input type="text" class="input-xxlarge" ng-model='current.query' placeholder="apps.servers.*"></input>
|
<label class="small">Variable label</label>
|
||||||
<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'Execute query'" data-placement="right"><i class="fa fa-play"></i></button>
|
<input type="text" class="input-medium" ng-model='current.label' placeholder=""></input>
|
||||||
|
</div>
|
||||||
|
<editor-opt-bool text="Hide Label" model="current.hideLabel"></editor-opt-bool>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h5>Multi-value selection <tip>Enables multiple values to be selected at the same time</tip></h5>
|
||||||
|
<editor-opt-bool text="Enable" model="current.multi"></editor-opt-bool>
|
||||||
|
<div class="editor-option" ng-show="current.multi">
|
||||||
|
<label class="small">Multi value format</label>
|
||||||
|
<select class="input-medium" ng-model="current.multiFormat" ng-options="f for f in ['glob', 'regex values']"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="editor-row" style="margin: 15px 0">
|
<div class="editor-row" style="margin: 15px 0">
|
||||||
<div class="editor-option form-inline">
|
<div class="editor-option" >
|
||||||
<label class="small">regex (optional, if you want to extract part of a series name or metric node segment)</label>
|
<label class="small">Variable values (shows max 20)</label>
|
||||||
<input type="text" class="input-xxlarge" ng-model='current.regex' placeholder="/.*-(.*)-.*/"></input>
|
<ul class="grafana-options-list">
|
||||||
<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'execute query'" data-placement="right"><i class="fa fa-play"></i></button>
|
<li ng-repeat="option in current.options | limitTo: 20">
|
||||||
|
{{option.text}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="editor-row" style="margin: 15px 0">
|
<button type="button" class="btn btn-success pull-right" ng-show="editor.index === 2" ng-click="update();">Update</button>
|
||||||
<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
|
<button type="button" class="btn btn-success pull-right" ng-show="editor.index === 1" ng-click="add();">Add</button>
|
||||||
<div class="editor-option" ng-show="current.includeAll">
|
|
||||||
<label class="small">All format</label>
|
<div class="clearfix"></div>
|
||||||
<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
|
|
||||||
</div>
|
|
||||||
<div class="editor-option" ng-show="current.includeAll">
|
|
||||||
<label class="small">All value</label>
|
|
||||||
<input type="text" class="input-xlarge" ng-model='current.options[0].value'></input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="editor-option">
|
|
||||||
<div class="editor-row">
|
|
||||||
<div class="editor-option" >
|
|
||||||
<label class="small">Variable values (showing 20/{{current.options.length}})</label>
|
|
||||||
<ul class="grafana-options-list">
|
|
||||||
<li ng-repeat="option in current.options | limitTo: 20">
|
|
||||||
{{option.text}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" ng-show="editor.index === 2" ng-click="update();">Update</button>
|
|
||||||
<button type="button" class="btn btn-success" ng-show="editor.index === 1" ng-click="add();">Add</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ function (angular, _, $, config, kbn, moment) {
|
|||||||
maxDataPoints: options.maxDataPoints,
|
maxDataPoints: options.maxDataPoints,
|
||||||
};
|
};
|
||||||
|
|
||||||
var params = this.buildGraphiteParams(graphOptions);
|
var params = this.buildGraphiteParams(graphOptions, options.scopedVars);
|
||||||
|
|
||||||
if (options.format === 'png') {
|
if (options.format === 'png') {
|
||||||
return $q.when(this.url + '/render' + '?' + params.join('&'));
|
return $q.when(this.url + '/render' + '?' + params.join('&'));
|
||||||
@ -231,7 +231,7 @@ function (angular, _, $, config, kbn, moment) {
|
|||||||
'#Y', '#Z'
|
'#Y', '#Z'
|
||||||
];
|
];
|
||||||
|
|
||||||
GraphiteDatasource.prototype.buildGraphiteParams = function(options) {
|
GraphiteDatasource.prototype.buildGraphiteParams = function(options, scopedVars) {
|
||||||
var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
|
var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
|
||||||
var clean_options = [], targets = {};
|
var clean_options = [], targets = {};
|
||||||
var target, targetValue, i;
|
var target, targetValue, i;
|
||||||
@ -252,7 +252,7 @@ function (angular, _, $, config, kbn, moment) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
targetValue = templateSrv.replace(target.target);
|
targetValue = templateSrv.replace(target.target, scopedVars);
|
||||||
targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat);
|
targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat);
|
||||||
targets[this._seriesRefLetters[i]] = targetValue;
|
targets[this._seriesRefLetters[i]] = targetValue;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,11 @@ input[type="checkbox"].cr1 {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor-option label.cr1 {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px 0 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
label.cr1 {
|
label.cr1 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 19px;
|
height: 19px;
|
||||||
|
@ -19,3 +19,60 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.variable-value-link {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-value-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 27px;
|
||||||
|
min-width: 150px;
|
||||||
|
max-height: 400px;
|
||||||
|
background: @grafanaPanelBackground;
|
||||||
|
box-shadow: 0px 0px 55px 0px black;
|
||||||
|
border: 1px solid @grafanaTargetFuncBackground;
|
||||||
|
z-index: 1000;
|
||||||
|
font-size: @baseFontSize;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-options-container {
|
||||||
|
max-height: 350px;
|
||||||
|
overflow: auto;
|
||||||
|
display: block;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-option {
|
||||||
|
display: block;
|
||||||
|
padding: 0 8px;
|
||||||
|
|
||||||
|
&:hover, &.highlighted {
|
||||||
|
background-color: @blueDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
line-height: 26px;
|
||||||
|
float: right;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
.variable-option-icon:before {
|
||||||
|
content: "\f00c";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.variable-search-wrapper {
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 7px 8px;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
@monoFontFamily: Menlo, Monaco, Consolas, "Courier New", monospace;
|
@monoFontFamily: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||||
|
|
||||||
@baseFontSize: 14px;
|
@baseFontSize: 14px;
|
||||||
@baseFontWeight: 400;
|
@baseFontWeight: 400;
|
||||||
@baseFontFamily: @sansFontFamily;
|
@baseFontFamily: @sansFontFamily;
|
||||||
@baseLineHeight: 20px;
|
@baseLineHeight: 20px;
|
||||||
@altFontFamily: @serifFontFamily;
|
@altFontFamily: @serifFontFamily;
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
define([
|
define([
|
||||||
'helpers',
|
|
||||||
'features/dashboard/dashboardSrv'
|
'features/dashboard/dashboardSrv'
|
||||||
], function(helpers) {
|
], function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('dashboardSrv', function() {
|
describe('dashboardSrv', function() {
|
||||||
var _dashboardSrv;
|
var _dashboardSrv;
|
||||||
var contextSrv = new helpers.ContextSrvStub();
|
|
||||||
|
|
||||||
beforeEach(module('grafana.services'));
|
beforeEach(module('grafana.services'));
|
||||||
beforeEach(module(function($provide) {
|
|
||||||
$provide.value('contextSrv', contextSrv);
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(inject(function(dashboardSrv) {
|
beforeEach(inject(function(dashboardSrv) {
|
||||||
_dashboardSrv = dashboardSrv;
|
_dashboardSrv = dashboardSrv;
|
||||||
@ -29,7 +24,7 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have meta', function() {
|
it('should have meta', function() {
|
||||||
expect(model.meta.canSave).to.be(false);
|
expect(model.meta.canSave).to.be(true);
|
||||||
expect(model.meta.canShare).to.be(true);
|
expect(model.meta.canShare).to.be(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
180
public/test/specs/dynamicDashboardSrv-specs.js
Normal file
180
public/test/specs/dynamicDashboardSrv-specs.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
define([
|
||||||
|
'features/dashboard/dynamicDashboardSrv',
|
||||||
|
'features/dashboard/dashboardSrv'
|
||||||
|
], function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function dynamicDashScenario(desc, func) {
|
||||||
|
|
||||||
|
describe(desc, function() {
|
||||||
|
var ctx = {};
|
||||||
|
|
||||||
|
ctx.setup = function (setupFunc) {
|
||||||
|
|
||||||
|
beforeEach(module('grafana.services'));
|
||||||
|
|
||||||
|
beforeEach(inject(function(dynamicDashboardSrv, dashboardSrv) {
|
||||||
|
ctx.dynamicDashboardSrv = dynamicDashboardSrv;
|
||||||
|
ctx.dashboardSrv = dashboardSrv;
|
||||||
|
|
||||||
|
var model = {
|
||||||
|
rows: [],
|
||||||
|
templating: { list: [] }
|
||||||
|
};
|
||||||
|
|
||||||
|
setupFunc(model);
|
||||||
|
ctx.dash = ctx.dashboardSrv.create(model);
|
||||||
|
ctx.dynamicDashboardSrv.init(ctx.dash);
|
||||||
|
ctx.rows = ctx.dash.rows;
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
func(ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicDashScenario('given dashboard with panel repeat', function(ctx) {
|
||||||
|
ctx.setup(function(dash) {
|
||||||
|
dash.rows.push({
|
||||||
|
panels: [{id: 2, repeat: 'apps'}]
|
||||||
|
});
|
||||||
|
dash.templating.list.push({
|
||||||
|
name: 'apps',
|
||||||
|
current: {
|
||||||
|
text: 'se1, se2',
|
||||||
|
value: ['se1', 'se2']
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{text: 'se1', value: 'se1', selected: true},
|
||||||
|
{text: 'se2', value: 'se2', selected: true},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should repeat panel one time', function() {
|
||||||
|
expect(ctx.rows[0].panels.length).to.be(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should mark panel repeated', function() {
|
||||||
|
expect(ctx.rows[0].panels[0].repeat).to.be('apps');
|
||||||
|
expect(ctx.rows[0].panels[1].repeatPanelId).to.be(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set scopedVars on panels', function() {
|
||||||
|
expect(ctx.rows[0].panels[0].scopedVars.apps.value).to.be('se1');
|
||||||
|
expect(ctx.rows[0].panels[1].scopedVars.apps.value).to.be('se2');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('After a second iteration', function() {
|
||||||
|
var repeatedPanelAfterIteration1;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
repeatedPanelAfterIteration1 = ctx.rows[0].panels[1];
|
||||||
|
ctx.rows[0].panels[0].fill = 10;
|
||||||
|
ctx.dynamicDashboardSrv.update(ctx.dash);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have reused same panel instances', function() {
|
||||||
|
expect(ctx.rows[0].panels[1]).to.be(repeatedPanelAfterIteration1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reused panel should copy properties from source', function() {
|
||||||
|
expect(ctx.rows[0].panels[1].fill).to.be(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have same panel count', function() {
|
||||||
|
expect(ctx.rows[0].panels.length).to.be(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('After a second iteration and selected values reduced', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
ctx.dash.templating.list[0].options[1].selected = false;
|
||||||
|
ctx.dynamicDashboardSrv.update(ctx.dash);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clean up repeated panel', function() {
|
||||||
|
expect(ctx.rows[0].panels.length).to.be(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
dynamicDashScenario('given dashboard with row repeat', function(ctx) {
|
||||||
|
ctx.setup(function(dash) {
|
||||||
|
dash.rows.push({
|
||||||
|
repeat: 'servers',
|
||||||
|
panels: [{id: 2}]
|
||||||
|
});
|
||||||
|
dash.templating.list.push({
|
||||||
|
name: 'servers',
|
||||||
|
current: {
|
||||||
|
text: 'se1, se2',
|
||||||
|
value: ['se1', 'se2']
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{text: 'se1', value: 'se1', selected: true},
|
||||||
|
{text: 'se2', value: 'se2', selected: true},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should repeat row one time', function() {
|
||||||
|
expect(ctx.rows.length).to.be(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep panel ids on first row', function() {
|
||||||
|
expect(ctx.rows[0].panels[0].id).to.be(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should mark second row as repeated', function() {
|
||||||
|
expect(ctx.rows[0].repeat).to.be('servers');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear repeat field on repeated row', function() {
|
||||||
|
expect(ctx.rows[1].repeat).to.be(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a repeartRowId based on repeat row index', function() {
|
||||||
|
expect(ctx.rows[1].repeatRowId).to.be(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set scopedVars on row panels', function() {
|
||||||
|
expect(ctx.rows[0].panels[0].scopedVars.servers.value).to.be('se1');
|
||||||
|
expect(ctx.rows[1].panels[0].scopedVars.servers.value).to.be('se2');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('After a second iteration', function() {
|
||||||
|
var repeatedRowAfterFirstIteration;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
repeatedRowAfterFirstIteration = ctx.rows[1];
|
||||||
|
ctx.rows[0].height = 500;
|
||||||
|
ctx.dynamicDashboardSrv.update(ctx.dash);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should still only have 2 rows', function() {
|
||||||
|
expect(ctx.rows.length).to.be(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('should have updated props from source', function() {
|
||||||
|
expect(ctx.rows[1].height).to.be(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reuse row instance', function() {
|
||||||
|
expect(ctx.rows[1]).to.be(repeatedRowAfterFirstIteration);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('After a second iteration and selected values reduced', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
ctx.dash.templating.list[0].options[1].selected = false;
|
||||||
|
ctx.dynamicDashboardSrv.update(ctx.dash);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove repeated second row', function() {
|
||||||
|
expect(ctx.rows.length).to.be(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -45,6 +45,34 @@ define([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('render variable to string values', function() {
|
||||||
|
it('single value should return value', function() {
|
||||||
|
var result = _templateSrv.renderVariableValue({current: {value: 'test'}});
|
||||||
|
expect(result).to.be('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multi value and glob format should render glob string', function() {
|
||||||
|
var result = _templateSrv.renderVariableValue({
|
||||||
|
multiFormat: 'glob',
|
||||||
|
current: {
|
||||||
|
value: ['test','test2'],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(result).to.be('{test,test2}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multi value and regex format should render regex string', function() {
|
||||||
|
var result = _templateSrv.renderVariableValue({
|
||||||
|
multiFormat: 'regex values',
|
||||||
|
current: {
|
||||||
|
value: ['test','test2'],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(result).to.be('(test|test2)');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('can check if variable exists', function() {
|
describe('can check if variable exists', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
_templateSrv.init([{ name: 'test', current: { value: 'oogle' } }]);
|
_templateSrv.init([{ name: 'test', current: { value: 'oogle' } }]);
|
||||||
|
83
public/test/specs/unsavedChangesSrv-specs.js
Normal file
83
public/test/specs/unsavedChangesSrv-specs.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
define([
|
||||||
|
'features/dashboard/unsavedChangesSrv',
|
||||||
|
'features/dashboard/dashboardSrv'
|
||||||
|
], function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("unsavedChangesSrv", function() {
|
||||||
|
var _unsavedChangesSrv;
|
||||||
|
var _dashboardSrv;
|
||||||
|
var _location;
|
||||||
|
var _contextSrvStub = { isEditor: true };
|
||||||
|
var _rootScope;
|
||||||
|
var tracker;
|
||||||
|
var dash;
|
||||||
|
var scope;
|
||||||
|
|
||||||
|
beforeEach(module('grafana.services'));
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('contextSrv', _contextSrvStub);
|
||||||
|
$provide.value('$window', {});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(inject(function(unsavedChangesSrv, $location, $rootScope, dashboardSrv) {
|
||||||
|
_unsavedChangesSrv = unsavedChangesSrv;
|
||||||
|
_dashboardSrv = dashboardSrv;
|
||||||
|
_location = $location;
|
||||||
|
_rootScope = $rootScope;
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
dash = _dashboardSrv.create({
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
panels: [{ test: "asd", legend: { } }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
scope = _rootScope.$new();
|
||||||
|
scope.appEvent = sinon.spy();
|
||||||
|
scope.onAppEvent = sinon.spy();
|
||||||
|
|
||||||
|
tracker = new _unsavedChangesSrv.Tracker(dash, scope);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('No changes should not have changes', function() {
|
||||||
|
expect(tracker.hasChanges()).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Simple change should be registered', function() {
|
||||||
|
dash.property = "google";
|
||||||
|
expect(tracker.hasChanges()).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should ignore a lot of changes', function() {
|
||||||
|
dash.time = {from: '1h'};
|
||||||
|
dash.refresh = true;
|
||||||
|
dash.schemaVersion = 10;
|
||||||
|
expect(tracker.hasChanges()).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should ignore row collapse change', function() {
|
||||||
|
dash.rows[0].collapse = true;
|
||||||
|
expect(tracker.hasChanges()).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should ignore panel legend changes', function() {
|
||||||
|
dash.rows[0].panels[0].legend.sortDesc = true;
|
||||||
|
dash.rows[0].panels[0].legend.sort = "avg";
|
||||||
|
expect(tracker.hasChanges()).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should ignore panel repeats', function() {
|
||||||
|
dash.rows[0].panels.push({repeatPanelId: 10});
|
||||||
|
expect(tracker.hasChanges()).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should ignore row repeats', function() {
|
||||||
|
dash.rows.push({repeatRowId: 10});
|
||||||
|
expect(tracker.hasChanges()).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -140,6 +140,8 @@ require([
|
|||||||
'specs/dashboardSrv-specs',
|
'specs/dashboardSrv-specs',
|
||||||
'specs/dashboardViewStateSrv-specs',
|
'specs/dashboardViewStateSrv-specs',
|
||||||
'specs/soloPanelCtrl-specs',
|
'specs/soloPanelCtrl-specs',
|
||||||
|
'specs/dynamicDashboardSrv-specs',
|
||||||
|
'specs/unsavedChangesSrv-specs',
|
||||||
];
|
];
|
||||||
|
|
||||||
var pluginSpecs = (config.plugins.specs || []).map(function (spec) {
|
var pluginSpecs = (config.plugins.specs || []).map(function (spec) {
|
||||||
|
Loading…
Reference in New Issue
Block a user