diff --git a/.jsfmtrc b/.jsfmtrc
new file mode 100644
index 00000000000..3ca3b4a3b50
--- /dev/null
+++ b/.jsfmtrc
@@ -0,0 +1,21 @@
+{
+ "preset" : "default",
+
+ "lineBreak" : {
+ "before" : {
+ "VariableDeclarationWithoutInit" : 0,
+ },
+
+ "after": {
+ "AssignmentOperator": -1,
+ "ArgumentListArrayExpression": ">=1"
+ }
+ },
+
+ "whiteSpace" : {
+ "before" : {
+ },
+ "after" : {
+ }
+ }
+}
diff --git a/src/app/controllers/sharePanelCtrl.js b/src/app/controllers/sharePanelCtrl.js
index 87903378416..b55da84263a 100644
--- a/src/app/controllers/sharePanelCtrl.js
+++ b/src/app/controllers/sharePanelCtrl.js
@@ -11,31 +11,64 @@ function (angular, _) {
$scope.init = function() {
$scope.editor = { index: 0 };
-
- var currentUrl = $location.absUrl();
- var panelId = $scope.panel.id;
- var range = timeSrv.timeRange(false);
- var from = range.from;
- var to = range.to;
- if (_.isDate(from)) {
- from = from.getTime();
- }
- if (_.isDate(to)) {
- to = to.getTime();
- }
-
- $scope.shareUrl = currentUrl + "?panelId=" + panelId + "&fullscreen";
- $scope.shareUrl += "&from=" + from;
- $scope.shareUrl += "&to=" + to;
-
$scope.forCurrent = true;
$scope.toPanel = true;
+ $scope.buildUrl();
+ };
+
+ $scope.buildUrl = function() {
+ var baseUrl = $location.absUrl();
+ var queryStart = baseUrl.indexOf('?');
+
+ if (queryStart !== -1) {
+ baseUrl = baseUrl.substring(0, queryStart);
+ }
+
+ var panelId = $scope.panel.id;
+ var range = timeSrv.timeRange(false);
+ var params = $location.search();
+
+ if (_.isString(range.to) && range.to.indexOf('now')) {
+ range = timeSrv.timeRange();
+ }
+
+ params.from = range.from;
+ params.to = range.to;
+
+ if (_.isDate(params.from)) { params.from = params.from.getTime(); }
+ if (_.isDate(params.to)) { params.to = params.to.getTime(); }
+
+ if (!$scope.forCurrent) {
+ delete params.from;
+ delete params.to;
+ }
+
+ if ($scope.toPanel) {
+ params.panelId = panelId;
+ params.fullscreen = true;
+ } else {
+ delete params.panelId;
+ delete params.fullscreen;
+ }
+
+ var paramsArray = [];
+ _.each(params, function(value, key) {
+ var str = key;
+ if (value !== true) {
+ str += '=' + encodeURIComponent(value);
+ }
+ paramsArray.push(str);
+ });
+
+ $scope.shareUrl = baseUrl + "?" + paramsArray.join('&') ;
+
$timeout(function() {
var input = $element.find('[data-share-panel-url]');
input.focus();
input.select();
- });
+ }, 10);
+
};
$scope.init();
diff --git a/src/app/partials/dasheditor.html b/src/app/partials/dasheditor.html
index 5cfad2d8d79..ae8608b97d1 100644
--- a/src/app/partials/dasheditor.html
+++ b/src/app/partials/dasheditor.html
@@ -39,7 +39,6 @@
Press enter to a add tag
-
diff --git a/src/app/partials/share-panel.html b/src/app/partials/share-panel.html
index efbfcb42ab7..49c13315e69 100644
--- a/src/app/partials/share-panel.html
+++ b/src/app/partials/share-panel.html
@@ -15,13 +15,14 @@
diff --git a/src/config.sample.js b/src/config.sample.js
index ab60995d251..07f29d7be28 100644
--- a/src/config.sample.js
+++ b/src/config.sample.js
@@ -2,108 +2,110 @@
// config.js is where you will find the core Grafana configuration. This file contains parameter that
// must be set before Grafana is run for the first time.
-define(['settings'],
-function (Settings) {
+define(['settings'], function(Settings) {
"use strict";
return new Settings({
- /* Data sources
- * ========================================================
- * Datasources are used to fetch metrics, annotations, and serve as dashboard storage
- * - You can have multiple of the same type.
- * - grafanaDB: true marks it for use for dashboard storage
- * - default: true marks the datasource as the default metric source (if you have multiple)
- * - basic authentication: use url syntax http://username:password@domain:port
- */
+ /* Data sources
+ * ========================================================
+ * Datasources are used to fetch metrics, annotations, and serve as dashboard storage
+ * - You can have multiple of the same type.
+ * - grafanaDB: true marks it for use for dashboard storage
+ * - default: true marks the datasource as the default metric source (if you have multiple)
+ * - basic authentication: use url syntax http://username:password@domain:port
+ */
- // InfluxDB example setup (the InfluxDB databases specified need to exist)
- /*
- datasources: {
- influxdb: {
- type: 'influxdb',
- url: "http://my_influxdb_server:8086/db/database_name",
- username: 'admin',
- password: 'admin',
+ // InfluxDB example setup (the InfluxDB databases specified need to exist)
+ /*
+ datasources: {
+ influxdb: {
+ type: 'influxdb',
+ url: "http://my_influxdb_server:8086/db/database_name",
+ username: 'admin',
+ password: 'admin',
+ },
+ grafana: {
+ type: 'influxdb',
+ url: "http://my_influxdb_server:8086/db/grafana",
+ username: 'admin',
+ password: 'admin',
+ grafanaDB: true
+ },
},
- grafana: {
- type: 'influxdb',
- url: "http://my_influxdb_server:8086/db/grafana",
- username: 'admin',
- password: 'admin',
- grafanaDB: true
- },
- },
- */
+ */
- // Graphite & Elasticsearch example setup
- /*
- datasources: {
- graphite: {
- type: 'graphite',
- url: "http://my.graphite.server.com:8080",
+ // Graphite & Elasticsearch example setup
+ /*
+ datasources: {
+ graphite: {
+ type: 'graphite',
+ url: "http://my.graphite.server.com:8080",
+ },
+ elasticsearch: {
+ type: 'elasticsearch',
+ url: "http://my.elastic.server.com:9200",
+ index: 'grafana-dash',
+ grafanaDB: true,
+ }
},
- elasticsearch: {
- type: 'elasticsearch',
- url: "http://my.elastic.server.com:9200",
- index: 'grafana-dash',
- grafanaDB: true,
+ */
+
+ // OpenTSDB & Elasticsearch example setup
+ /*
+ datasources: {
+ opentsdb: {
+ type: 'opentsdb',
+ url: "http://opentsdb.server:4242",
+ },
+ elasticsearch: {
+ type: 'elasticsearch',
+ url: "http://my.elastic.server.com:9200",
+ index: 'grafana-dash',
+ grafanaDB: true,
+ }
+ },
+ */
+
+ /* Global configuration options
+ * ========================================================
+ */
+
+ // specify the limit for dashboard search results
+ search: {
+ max_results: 20
+ },
+
+ // default home dashboard
+ default_route: '/dashboard/file/default.json',
+
+ // set to false to disable unsaved changes warning
+ unsaved_changes_warning: true,
+
+ // set the default timespan for the playlist feature
+ // Example: "1m", "1h"
+ playlist_timespan: "1m",
+
+ // If you want to specify password before saving, please specify it below
+ // The purpose of this password is not security, but to stop some users from accidentally changing dashboards
+ admin: {
+ password: ''
+ },
+
+ // Change window title prefix from 'Grafana - '
+ window_title_prefix: 'Grafana - ',
+
+ // Add your own custom panels
+ plugins: {
+ // list of plugin panels
+ panels: [],
+ // requirejs modules in plugins folder that should be loaded
+ // for example custom datasources
+ dependencies: [],
}
- },
- */
- // OpenTSDB & Elasticsearch example setup
- /*
- datasources: {
- opentsdb: {
- type: 'opentsdb',
- url: "http://opentsdb.server:4242",
- },
- elasticsearch: {
- type: 'elasticsearch',
- url: "http://my.elastic.server.com:9200",
- index: 'grafana-dash',
- grafanaDB: true,
- }
- },
- */
-
- /* Global configuration options
- * ========================================================
- */
-
- // specify the limit for dashboard search results
- search: {
- max_results: 20
- },
-
- // default home dashboard
- default_route: '/dashboard/file/default.json',
-
- // set to false to disable unsaved changes warning
- unsaved_changes_warning: true,
-
- // set the default timespan for the playlist feature
- // Example: "1m", "1h"
- playlist_timespan: "1m",
-
- // If you want to specify password before saving, please specify it below
- // The purpose of this password is not security, but to stop some users from accidentally changing dashboards
- admin: {
- password: ''
- },
-
- // Change window title prefix from 'Grafana - '
- window_title_prefix: 'Grafana - ',
-
- // Add your own custom panels
- plugins: {
- // list of plugin panels
- panels: [],
- // requirejs modules in plugins folder that should be loaded
- // for example custom datasources
- dependencies: [],
- }
-
- });
+ });
});
+
+
+
diff --git a/src/css/less/forms.less b/src/css/less/forms.less
index ab17dc81c94..e1affd09ae9 100644
--- a/src/css/less/forms.less
+++ b/src/css/less/forms.less
@@ -2,6 +2,8 @@ input[type=text].input-fluid {
width: 100%;
box-sizing: border-box;
padding: 14px;
+ -moz-box-sizing: border-box;
+ height: 100%;
}
input[type="checkbox"].cr1 {
diff --git a/src/test/specs/helpers.js b/src/test/specs/helpers.js
index 040905375b7..1859bfc10a1 100644
--- a/src/test/specs/helpers.js
+++ b/src/test/specs/helpers.js
@@ -8,6 +8,7 @@ define([
var self = this;
this.datasource = {};
+ this.$element = {};
this.annotationsSrv = {};
this.timeSrv = new TimeSrvStub();
this.templateSrv = new TemplateSrvStub();
@@ -16,18 +17,23 @@ define([
get: function() { return self.datasource; }
};
- this.providePhase = function() {
+ this.providePhase = function(mocks) {
return module(function($provide) {
$provide.value('datasourceSrv', self.datasourceSrv);
$provide.value('annotationsSrv', self.annotationsSrv);
$provide.value('timeSrv', self.timeSrv);
$provide.value('templateSrv', self.templateSrv);
+ $provide.value('$element', self.$element);
+ _.each(mocks, function(key, value) {
+ $provide.value(key, value);
+ });
});
};
this.createControllerPhase = function(controllerName) {
- return inject(function($controller, $rootScope, $q) {
+ return inject(function($controller, $rootScope, $q, $location) {
self.scope = $rootScope.$new();
+ self.$location = $location;
self.scope.panel = {};
self.scope.row = { panels:[] };
self.scope.dashboard = {};
diff --git a/src/test/specs/sharePanelCtrl-specs.js b/src/test/specs/sharePanelCtrl-specs.js
new file mode 100644
index 00000000000..7a7f741ea52
--- /dev/null
+++ b/src/test/specs/sharePanelCtrl-specs.js
@@ -0,0 +1,59 @@
+define([
+ './helpers',
+ 'controllers/sharePanelCtrl'
+], function(helpers) {
+ 'use strict';
+
+ describe('SharePanelCtrl', function() {
+ var ctx = new helpers.ControllerTestContext();
+
+ beforeEach(module('grafana.controllers'));
+
+ beforeEach(ctx.providePhase());
+ beforeEach(ctx.createControllerPhase('SharePanelCtrl'));
+
+ describe('shareUrl with current time range and panel', function() {
+
+ it('should generate share url relative time', function() {
+ ctx.$location.path('/test');
+ ctx.scope.panel = { id: 22 };
+ ctx.timeSrv.time = { from: 'now-1h', to: 'now' };
+
+ ctx.scope.buildUrl();
+ expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&panelId=22&fullscreen');
+ });
+
+ it('should generate share url absolute time', function() {
+ ctx.$location.path('/test');
+ ctx.scope.panel = { id: 22 };
+ ctx.timeSrv.time = { from: new Date(2012,1,1), to: new Date(2014,3,5) };
+
+ ctx.scope.buildUrl();
+ expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1328050800000&to=1396648800000&panelId=22&fullscreen');
+ });
+
+ it('should generate share url with time as JSON strings', function() {
+ ctx.$location.path('/test');
+ ctx.scope.panel = { id: 22 };
+ ctx.timeSrv.time = { from: new Date(2012,1,1).toJSON(), to: new Date(2014,3,5).toJSON() };
+
+ ctx.scope.buildUrl();
+ expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1328050800000&to=1396648800000&panelId=22&fullscreen');
+ });
+
+ it('should remove panel id when toPanel is false', function() {
+ ctx.$location.path('/test');
+ ctx.scope.panel = { id: 22 };
+ ctx.scope.toPanel = false;
+ ctx.timeSrv.time = { from: 'now-1h', to: 'now' };
+
+ ctx.scope.buildUrl();
+ expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now');
+ });
+
+ });
+
+ });
+
+});
+
diff --git a/src/test/test-main.js b/src/test/test-main.js
index 6862c9fa8ae..468c4fc8603 100644
--- a/src/test/test-main.js
+++ b/src/test/test-main.js
@@ -131,6 +131,7 @@ require([
'specs/grafanaGraph-specs',
'specs/graph-tooltip-specs',
'specs/seriesOverridesCtrl-specs',
+ 'specs/sharePanelCtrl-specs',
'specs/timeSrv-specs',
'specs/templateSrv-specs',
'specs/templateValuesSrv-specs',