diff --git a/public/app/controllers/search.js b/public/app/controllers/search.js
index ff171c953a2..396b3f6a3d8 100644
--- a/public/app/controllers/search.js
+++ b/public/app/controllers/search.js
@@ -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() {
function djb2(str) {
diff --git a/public/app/directives/all.js b/public/app/directives/all.js
index 6190ef89099..3ef0b669d6c 100644
--- a/public/app/directives/all.js
+++ b/public/app/directives/all.js
@@ -16,4 +16,5 @@ define([
'./grafanaVersionCheck',
'./dropdown.typeahead',
'./topnav',
+ './giveFocus',
], function () {});
diff --git a/public/app/directives/giveFocus.js b/public/app/directives/giveFocus.js
new file mode 100644
index 00000000000..ef395d27fbd
--- /dev/null
+++ b/public/app/directives/giveFocus.js
@@ -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);
+ };
+ });
+
+});
diff --git a/public/app/directives/templateParamSelector.js b/public/app/directives/templateParamSelector.js
index d9ad33512b5..fa0e56acebc 100644
--- a/public/app/directives/templateParamSelector.js
+++ b/public/app/directives/templateParamSelector.js
@@ -4,84 +4,156 @@ define([
'lodash',
'jquery',
],
-function (angular, app, _, $) {
+function (angular, app, _) {
'use strict';
angular
.module('grafana.directives')
- .directive('templateParamSelector', function($compile) {
- var inputTemplate = '';
-
- var buttonTemplate = '{{variable.current.text}} ';
-
+ .directive('variableValueSelect', function($compile, $window, $timeout) {
return {
- link: function($scope, elem) {
- var $input = $(inputTemplate);
- var $button = $(buttonTemplate);
- var variable = $scope.variable;
+ scope: {
+ variable: "=",
+ onUpdated: "&"
+ },
+ 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);
- $button.appendTo(elem);
-
- 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;
+ scope.show = function() {
+ if (scope.selectorOpen) {
+ return;
}
- });
- var typeahead = $input.data('typeahead');
- typeahead.lookup = function () {
- var options = _.map(variable.options, function(option) { return option.text; });
- this.query = this.$element.val() || '';
- return this.process(options);
+ scope.selectorOpen = true;
+ scope.giveFocus = 1;
+ scope.oldCurrentText = variable.current.text;
+ scope.highlightIndex = -1;
+
+ 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() {
- $input.css('width', ($button.width() + 16) + 'px');
+ scope.queryChanged = function() {
+ scope.highlightIndex = -1;
+ scope.search.options = _.filter(scope.options, function(option) {
+ return option.text.toLowerCase().indexOf(scope.search.query.toLowerCase()) !== -1;
+ });
+ };
- $button.hide();
- $input.show();
- $input.focus();
+ scope.keyDown = function (evt) {
+ if (evt.keyCode === 27) {
+ 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');
- if (typeahead) {
- $input.val('');
- typeahead.lookup();
+ scope.moveHighlight = function(direction) {
+ scope.highlightIndex = (scope.highlightIndex + direction) % scope.search.options.length;
+ };
+
+ 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 ($input.val() !== '') { updateVariableValue($input.val()); }
- $input.hide();
- $button.show();
- $button.focus();
- });
+ if (selected.length === 0) {
+ option.selected = true;
+ selected = [option];
+ }
- $scope.$on('$destroy', function() {
- $button.unbind();
- typeahead.destroy();
- });
+ if (selected.length > 1 && selected.length !== scope.options.length) {
+ if (selected[0].text === 'All') {
+ 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();
+ });
+ },
};
});
+
});
diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js
index a279e9f3078..aeabe6b354e 100644
--- a/public/app/features/dashboard/all.js
+++ b/public/app/features/dashboard/all.js
@@ -16,5 +16,6 @@ define([
'./unsavedChangesSrv',
'./directives/dashSearchView',
'./graphiteImportCtrl',
+ './dynamicDashboardSrv',
'./importCtrl',
], function () {});
diff --git a/public/app/features/dashboard/dashboardCtrl.js b/public/app/features/dashboard/dashboardCtrl.js
index ff915a7dbf2..f81b808a0e1 100644
--- a/public/app/features/dashboard/dashboardCtrl.js
+++ b/public/app/features/dashboard/dashboardCtrl.js
@@ -15,7 +15,9 @@ function (angular, $, config) {
dashboardKeybindings,
timeSrv,
templateValuesSrv,
+ dynamicDashboardSrv,
dashboardSrv,
+ unsavedChangesSrv,
dashboardViewStateSrv,
contextSrv,
$timeout) {
@@ -46,6 +48,9 @@ function (angular, $, config) {
// template values service needs to initialize completely before
// the rest of the dashboard can load
templateValuesSrv.init(dashboard).finally(function() {
+ dynamicDashboardSrv.init(dashboard);
+ unsavedChangesSrv.init(dashboard, $scope);
+
$scope.dashboard = dashboard;
$scope.dashboardMeta = dashboard.meta;
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js
index 9324f18e33b..afe5e7964c6 100644
--- a/public/app/features/dashboard/dashboardSrv.js
+++ b/public/app/features/dashboard/dashboardSrv.js
@@ -10,7 +10,7 @@ function (angular, $, kbn, _, moment) {
var module = angular.module('grafana.services');
- module.factory('dashboardSrv', function(contextSrv) {
+ module.factory('dashboardSrv', function() {
function DashboardModel (data, meta) {
if (!data) {
@@ -59,10 +59,6 @@ function (angular, $, kbn, _, moment) {
meta.canStar = meta.canStar === false ? false : true;
meta.canDelete = meta.canDelete === false ? false : true;
- if (contextSrv.hasRole('Viewer')) {
- meta.canSave = false;
- }
-
if (!this.editable) {
meta.canEdit = false;
meta.canDelete = false;
@@ -180,6 +176,7 @@ function (angular, $, kbn, _, moment) {
var currentRow = this.rows[rowIndex];
currentRow.panels.push(newPanel);
+ return newPanel;
};
p.formatDate = function(date, format) {
diff --git a/public/app/features/dashboard/dynamicDashboardSrv.js b/public/app/features/dashboard/dynamicDashboardSrv.js
new file mode 100644
index 00000000000..cfa824e3402
--- /dev/null
+++ b/public/app/features/dashboard/dynamicDashboardSrv.js
@@ -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;
+ });
+ };
+
+ });
+
+});
diff --git a/public/app/features/dashboard/partials/dashboardTopNav.html b/public/app/features/dashboard/partials/dashboardTopNav.html
index 6f85d6b4e02..89a6a185759 100644
--- a/public/app/features/dashboard/partials/dashboardTopNav.html
+++ b/public/app/features/dashboard/partials/dashboardTopNav.html
@@ -27,7 +27,7 @@
-
+
diff --git a/public/app/features/dashboard/partials/variableValueSelect.html b/public/app/features/dashboard/partials/variableValueSelect.html
new file mode 100644
index 00000000000..30a61072626
--- /dev/null
+++ b/public/app/features/dashboard/partials/variableValueSelect.html
@@ -0,0 +1,26 @@
+
+ {{labelText}}:
+
+
+
diff --git a/public/app/features/dashboard/submenuCtrl.js b/public/app/features/dashboard/submenuCtrl.js
index 456cc1b76c1..545fed01d23 100644
--- a/public/app/features/dashboard/submenuCtrl.js
+++ b/public/app/features/dashboard/submenuCtrl.js
@@ -7,7 +7,7 @@ function (angular, _) {
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 = {
enable: true
};
@@ -26,8 +26,11 @@ function (angular, _) {
$rootScope.$broadcast('refresh');
};
- $scope.setVariableValue = function(param, option) {
- templateValuesSrv.setVariableValue(param, option);
+ $scope.variableUpdated = function(variable) {
+ templateValuesSrv.variableUpdated(variable).then(function() {
+ dynamicDashboardSrv.update($scope.dashboard);
+ $rootScope.$broadcast('refresh');
+ });
};
$scope.init();
diff --git a/public/app/features/dashboard/unsavedChangesSrv.js b/public/app/features/dashboard/unsavedChangesSrv.js
index eec884551ec..11a806c865d 100644
--- a/public/app/features/dashboard/unsavedChangesSrv.js
+++ b/public/app/features/dashboard/unsavedChangesSrv.js
@@ -1,129 +1,114 @@
define([
'angular',
'lodash',
- 'config',
],
-function(angular, _, config) {
+function(angular, _) {
'use strict';
- if (!config.unsaved_changes_warning) {
- return;
- }
-
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;
- var modalScope = $rootScope.$new();
+ function Tracker(dashboard, scope) {
+ var self = this;
- $rootScope.$on("dashboard-loaded", function(event, newDashboard) {
- // wait for different services to patch the dashboard (missing properties)
- $timeout(function() {
- self.original = newDashboard.getSaveModelClone();
- self.current = newDashboard;
- }, 1200);
- });
+ this.original = dashboard.getSaveModelClone();
+ this.current = dashboard;
+ this.originalPath = $location.path();
+ this.scope = scope;
- $rootScope.$on("dashboard-saved", function(event, savedDashboard) {
- self.original = savedDashboard.getSaveModelClone();
- self.current = savedDashboard;
- self.orignalPath = $location.path();
- });
+ // register events
+ scope.onAppEvent('dashboard-saved', function() {
+ self.original = self.current.getSaveModelClone();
+ self.originalPath = $location.path();
+ });
- $rootScope.$on("$routeChangeSuccess", function() {
- self.original = null;
- self.originalPath = $location.path();
- });
+ $window.onbeforeunload = function() {
+ if (self.ignoreChanges()) { return; }
+ if (self.hasChanges()) {
+ return "There are unsaved changes to this dashboard";
+ }
+ };
- this.ignoreChanges = function() {
- 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) {
+ scope.$on("$locationChangeStart", function(event, next) {
// check if we should look for changes
if (self.originalPath === $location.path()) { return true; }
if (self.ignoreChanges()) { return true; }
- if (self.has_unsaved_changes()) {
+ if (self.hasChanges()) {
event.preventDefault();
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() {
- var confirmModal = $modal({
- template: './app/partials/unsaved-changes.html',
- modalClass: 'confirm-modal',
- persist: true,
- show: false,
- scope: modalScope,
- keyboard: false
- });
+ // remove stuff that should not count in diff
+ p.cleanDashboardFromIgnoredChanges = function(dash) {
+ // ignore time and refresh
+ dash.time = 0;
+ dash.refresh = 0;
+ dash.schemaVersion = 0;
- $q.when(confirmModal).then(function(modalEl) {
- modalEl.modal('show');
- });
- };
-
- 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;
+ // filter row and panels properties that should be ignored
+ dash.rows = _.filter(dash.rows, function(row) {
+ if (row.repeatRowId) {
+ return false;
}
- });
- // ignore some panel and row stuff
- current.forEachPanel(function(panel, panelIndex, row, rowIndex) {
- var originalRow = original.rows[rowIndex];
- var originalPanel = original.getPanelById(panel.id);
- // ignore row collapse state
- if (originalRow) {
- row.collapse = originalRow.collapse;
- }
- if (originalPanel) {
- // ignore graph legend sort
- if (originalPanel.legend && panel.legend) {
- delete originalPanel.legend.sortDesc;
- delete originalPanel.legend.sort;
+ row.panels = _.filter(row.panels, function(panel) {
+ if (panel.repeatPanelId) {
+ return false;
+ }
+
+ // remove scopedVars
+ panel.scopedVars = null;
+
+ // ignore panel legend sort
+ if (panel.legend) {
delete panel.legend.sort;
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 originalTimepicker = _.findWhere(original.nav, { type: 'timepicker' });
@@ -141,28 +126,43 @@ function(angular, _, config) {
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 nextUrl = self.next.substring(baseLen);
+ var nextUrl = this.next.substring(baseLen);
$location.url(nextUrl);
};
- modalScope.ignore = function() {
- self.original = null;
- self.goto_next();
+ this.Tracker = Tracker;
+ this.init = function(dashboard, scope) {
+ // 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();
});
});
diff --git a/public/app/features/panel/panelHelper.js b/public/app/features/panel/panelHelper.js
index 39d8e00a9d6..cb549c6bf43 100644
--- a/public/app/features/panel/panelHelper.js
+++ b/public/app/features/panel/panelHelper.js
@@ -68,6 +68,7 @@ function (angular, _, kbn, $) {
targets: scope.panel.targets,
format: scope.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: scope.resolution,
+ scopedVars: scope.panel.scopedVars,
cacheTimeout: scope.panel.cacheTimeout
};
diff --git a/public/app/features/panel/panelMenu.js b/public/app/features/panel/panelMenu.js
index 1f7d6aba71c..27152ef2b94 100644
--- a/public/app/features/panel/panelMenu.js
+++ b/public/app/features/panel/panelMenu.js
@@ -11,7 +11,7 @@ function (angular, $, _) {
.directive('panelMenu', function($compile, linkSrv) {
var linkTemplate =
'' +
- '{{panel.title | interpolateTemplateVars}}' +
+ '{{panel.title | interpolateTemplateVars:this}}' +
'' +
' {{panelMeta.timeInfo}}' +
'';
diff --git a/public/app/features/panel/panelSrv.js b/public/app/features/panel/panelSrv.js
index 33f70ca25da..d29866033ee 100644
--- a/public/app/features/panel/panelSrv.js
+++ b/public/app/features/panel/panelSrv.js
@@ -11,6 +11,7 @@ function (angular, _, config) {
module.service('panelSrv', function($rootScope, $timeout, datasourceSrv, $q) {
this.init = function($scope) {
+
if (!$scope.panel.span) { $scope.panel.span = 12; }
$scope.inspector = {};
diff --git a/public/app/features/templating/editorCtrl.js b/public/app/features/templating/editorCtrl.js
index f17c383155d..f48452e4569 100644
--- a/public/app/features/templating/editorCtrl.js
+++ b/public/app/features/templating/editorCtrl.js
@@ -17,6 +17,8 @@ function (angular, _) {
options: [],
includeAll: false,
allFormat: 'glob',
+ multi: false,
+ multiFormat: 'glob',
};
$scope.init = function() {
@@ -75,7 +77,7 @@ function (angular, _) {
if ($scope.current.datasource === void 0) {
$scope.current.datasource = null;
$scope.current.type = 'query';
- $scope.current.allFormat = 'Glob';
+ $scope.current.allFormat = 'glob';
}
};
diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js
index 09aed015386..a6b5cf6f90a 100644
--- a/public/app/features/templating/templateSrv.js
+++ b/public/app/features/templating/templateSrv.js
@@ -29,11 +29,23 @@ function (angular, _) {
_.each(this.variables, function(variable) {
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);
};
+ 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._grafanaVariables[name] = value;
};
diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js
index 10ff556bf84..9dea95fb083 100644
--- a/public/app/features/templating/templateValuesSrv.js
+++ b/public/app/features/templating/templateValuesSrv.js
@@ -73,17 +73,15 @@ function (angular, _, kbn) {
templateSrv.setGrafanaVariable('$__auto_interval', interval);
};
- this.setVariableValue = function(variable, option, recursive) {
+ this.setVariableValue = function(variable, option) {
variable.current = option;
-
templateSrv.updateTemplateData();
+ return this.updateOptionsInChildVariables(variable);
+ };
- return this.updateOptionsInChildVariables(variable)
- .then(function() {
- if (!recursive) {
- $rootScope.$broadcast('refresh');
- }
- });
+ this.variableUpdated = function(variable) {
+ templateSrv.updateTemplateData();
+ return this.updateOptionsInChildVariables(variable);
};
this.updateOptionsInChildVariables = function(updatedVariable) {
@@ -130,11 +128,11 @@ function (angular, _, kbn) {
if (variable.current) {
var currentOption = _.findWhere(variable.options, { text: variable.current.text });
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]);
});
});
};
diff --git a/public/app/filters/all.js b/public/app/filters/all.js
index e75d70043d8..fd22061da66 100755
--- a/public/app/filters/all.js
+++ b/public/app/filters/all.js
@@ -56,8 +56,8 @@ define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, momen
});
module.filter('interpolateTemplateVars', function(templateSrv) {
- function interpolateTemplateVars(text) {
- return templateSrv.replaceWithText(text);
+ function interpolateTemplateVars(text, scope) {
+ return templateSrv.replaceWithText(text, scope.panel.scopedVars);
}
interpolateTemplateVars.$stateful = true;
diff --git a/public/app/panels/graph/axisEditor.html b/public/app/panels/graph/axisEditor.html
index ae69ec60929..d161330f5c0 100644
--- a/public/app/panels/graph/axisEditor.html
+++ b/public/app/panels/graph/axisEditor.html
@@ -190,7 +190,7 @@
-
diff --git a/public/app/partials/panelgeneral.html b/public/app/partials/panelgeneral.html
index f5a73bc0851..7a4b57b0db8 100644
--- a/public/app/partials/panelgeneral.html
+++ b/public/app/partials/panelgeneral.html
@@ -1,17 +1,51 @@
General options
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/public/app/partials/roweditor.html b/public/app/partials/roweditor.html
index 243546392cc..37e258a9b89 100644
--- a/public/app/partials/roweditor.html
+++ b/public/app/partials/roweditor.html
@@ -1,3 +1,4 @@
+