فهرست منبع

Merge branch 'develop' into panel_repeat

Torkel Ödegaard 10 سال پیش
والد
کامیت
a8d9ec426b

+ 2 - 1
CHANGELOG.md

@@ -1,13 +1,14 @@
 # 2.0.0 (unreleased)
 
 **New features**
+- [Issue #1622](https://github.com/grafana/grafana/issues/1622). Share Panel: The share modal now has an embed option, gives you an iframe that you can use to embedd a single graph on another web site
 - [Issue #718](https://github.com/grafana/grafana/issues/718).   Dashboard: When saving a dashboard and another user has made changes inbetween the user is promted with a warning if he really wants to overwrite the other's changes
 - [Issue #1331](https://github.com/grafana/grafana/issues/1331). Graph & Singlestat: New axis/unit format selector and more units (kbytes, Joule, Watt, eV), and new design for graph axis & grid tab and single stat options tab views
 - [Issue #1241](https://github.com/grafana/grafana/issues/1242). Timepicker: New option in timepicker (under dashboard settings), to change ``now`` to be for example ``now-1m``, usefull when you want to ignore last minute because it contains incomplete data
 - [Issue #171](https://github.com/grafana/grafana/issues/171).   Panel: Different time periods, panels can override dashboard relative time and/or add a time shift
 - [Issue #1488](https://github.com/grafana/grafana/issues/1488). Dashboard: Clone dashboard / Save as
 - [Issue #1458](https://github.com/grafana/grafana/issues/1458). User: persisted user option for dark or light theme  (no longer an option on a dashboard)
-- [Issue #452](https://github.com/grafana/grafana/issues/452).   Graph: Adds logarithmic scale option (log base 10)
+- [Issue #452](https://github.com/grafana/grafana/issues/452).   Graph: Adds logarithmic scale option for base 10, base 16 and base 1024
 
 **Enhancements**
 - [Issue #1366](https://github.com/grafana/grafana/issues/1366). Graph & Singlestat: Support for additional units, Fahrenheit (°F) and Celsius (°C), Humidity (%H), kW, watt-hour (Wh), kilowatt-hour (kWh), velocities (m/s, km/h, mpg, knot)

+ 5 - 4
pkg/api/render.go

@@ -12,12 +12,13 @@ import (
 
 func RenderToPng(c *middleware.Context) {
 	queryReader := util.NewUrlQueryReader(c.Req.URL)
-	queryParams := fmt.Sprintf("?render=1&%s=%d&%s", middleware.SESS_KEY_USERID, c.UserId, c.Req.URL.RawQuery)
+	queryParams := fmt.Sprintf("?%s", c.Req.URL.RawQuery)
 
 	renderOpts := &renderer.RenderOpts{
-		Url:    c.Params("*") + queryParams,
-		Width:  queryReader.Get("width", "800"),
-		Height: queryReader.Get("height", "400"),
+		Url:       c.Params("*") + queryParams,
+		Width:     queryReader.Get("width", "800"),
+		Height:    queryReader.Get("height", "400"),
+		SessionId: c.Session.ID(),
 	}
 
 	renderOpts.Url = setting.ToAbsUrl(renderOpts.Url)

+ 7 - 4
pkg/components/renderer/renderer.go

@@ -14,9 +14,10 @@ import (
 )
 
 type RenderOpts struct {
-	Url    string
-	Width  string
-	Height string
+	Url       string
+	Width     string
+	Height    string
+	SessionId string
 }
 
 func RenderToPng(params *RenderOpts) (string, error) {
@@ -26,7 +27,9 @@ func RenderToPng(params *RenderOpts) (string, error) {
 	pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, getHash(params.Url)))
 	pngPath = pngPath + ".png"
 
-	cmd := exec.Command(binPath, scriptPath, "url="+params.Url, "width="+params.Width, "height="+params.Height, "png="+pngPath)
+	cmd := exec.Command(binPath, scriptPath, "url="+params.Url, "width="+params.Width,
+		"height="+params.Height, "png="+pngPath, "cookiename="+setting.SessionOptions.CookieName,
+		"domain="+setting.Domain, "sessionid="+params.SessionId)
 	stdout, err := cmd.StdoutPipe()
 
 	if err != nil {

+ 0 - 7
pkg/middleware/auth.go

@@ -22,13 +22,6 @@ func getRequestUserId(c *Context) int64 {
 		return userId.(int64)
 	}
 
-	// TODO: figure out a way to secure this
-	if c.Req.URL.Query().Get("render") == "1" {
-		userId := c.QueryInt64(SESS_KEY_USERID)
-		c.Session.Set(SESS_KEY_USERID, userId)
-		return userId
-	}
-
 	return 0
 }
 

+ 1 - 1
src/app/features/dashboard/dashboardNavCtrl.js

@@ -42,7 +42,7 @@ function (angular, _, moment) {
 
     $scope.shareDashboard = function() {
       $scope.appEvent('show-modal', {
-        src: './app/features/dashboard/partials/shareModal.html',
+        src: './app/features/dashboard/partials/shareDashboard.html',
         scope: $scope.$new(),
       });
     };

+ 48 - 0
src/app/features/dashboard/partials/shareDashboard.html

@@ -0,0 +1,48 @@
+<div class="modal-body gf-box gf-box-no-margin" ng-controller="SharePanelCtrl">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
+			<i class="fa fa-share"></i>
+			Share
+		</div>
+
+		<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
+			<div ng-repeat="tab in ['Link']" data-title="{{tab}}">
+			</div>
+		</div>
+
+		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+			<i class="fa fa-remove"></i>
+		</button>
+	</div>
+
+	<div class="gf-box-body" ng-if="editor.index === 0">
+		<br>
+		<div class="gf-form">
+			<div class="gf-form-row">
+				<editor-checkbox text="Current time range" model="options.forCurrent" change="buildUrl()"></editor-checkbox>
+			</div>
+		</div>
+		<div class="gf-form">
+			<div class="gf-form-row">
+				<editor-checkbox text="Include template variables" model="options.includeTemplateVars" change="buildUrl()"></editor-checkbox>
+			</div>
+		</div>
+
+		<br>
+		<div class="gf-form">
+			<div class="gf-form-row">
+				<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
+				<span class="gf-fluid-input">
+					<input type="text" data-share-panel-url class="input" ng-model='shareUrl'></input>
+				</span>
+			</div>
+			<div>
+
+				<div class="editor-row" style="margin-top: 5px;" ng-if="options.toPanel">
+					<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a>
+				</div>
+			</div>
+		</div>
+	</div>
+
+</div>

+ 0 - 53
src/app/features/dashboard/partials/shareModal.html

@@ -1,53 +0,0 @@
-<div class="modal-body gf-box gf-box-no-margin" ng-controller="SharePanelCtrl">
-	<div class="gf-box-header">
-		<div class="gf-box-title">
-			<i class="fa fa-share"></i>
-			Share
-		</div>
-
-		<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
-			<div ng-repeat="tab in ['Link']" data-title="{{tab}}">
-			</div>
-		</div>
-
-		<button class="gf-box-header-close-btn" ng-click="dismiss();">
-			<i class="fa fa-remove"></i>
-		</button>
-	</div>
-
-	<div class="gf-box-body">
-
-		<br>
-		<div class="gf-form">
-			<div class="gf-form-row">
-					<editor-checkbox text="Current time range" model="options.forCurrent" change="buildUrl()"></editor-checkbox>
-				</div>
-			</div>
-			<div class="gf-form" ng-if="panel">
-				<div class="gf-form-row">
-					<editor-checkbox text="This panel only" model="options.toPanel" change="buildUrl()"></editor-checkbox>
-				</div>
-			</div>
-			<div class="gf-form">
-				<div class="gf-form-row">
-					<editor-checkbox text="Include template variables" model="options.includeTemplateVars" change="buildUrl()"></editor-checkbox>
-				</div>
-			</div>
-
-			<br>
-			<div class="gf-form">
-				<div class="gf-form-row">
-					<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
-					<span class="gf-fluid-input">
-						<input type="text" data-share-panel-url class="input" ng-model='shareUrl'></input>
-					</span>
-				</div>
-			<div>
-
-			<div class="editor-row" style="margin-top: 5px;" ng-if="toPanel">
-				<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image<a>
-			</div>
-		</div>
-
-	</div>
-</div>

+ 65 - 0
src/app/features/dashboard/partials/sharePanel.html

@@ -0,0 +1,65 @@
+<div class="modal-body gf-box gf-box-no-margin" ng-controller="SharePanelCtrl">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
+			<i class="fa fa-share"></i>
+			Share Panel
+		</div>
+
+		<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
+			<div ng-repeat="tab in ['Link', 'Embed']" data-title="{{tab}}">
+			</div>
+		</div>
+
+		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+			<i class="fa fa-remove"></i>
+		</button>
+	</div>
+
+	<div class="gf-box-body" ng-if="editor.index === 0">
+		<br>
+		<div class="gf-form">
+			<div class="gf-form-row">
+				<editor-checkbox text="Current time range" model="options.forCurrent" change="buildUrl()"></editor-checkbox>
+			</div>
+		</div>
+		<div class="gf-form">
+			<div class="gf-form-row">
+				<editor-checkbox text="Include template variables" model="options.includeTemplateVars" change="buildUrl()"></editor-checkbox>
+			</div>
+		</div>
+
+		<br>
+		<div class="gf-form">
+			<div class="gf-form-row">
+				<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
+				<span class="gf-fluid-input">
+					<input type="text" data-share-panel-url class="input" ng-model='shareUrl'></input>
+				</span>
+			</div>
+			<div>
+
+				<div class="editor-row" style="margin-top: 5px;" ng-if="options.toPanel">
+					<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a>
+				</div>
+			</div>
+		</div>
+	</div>
+
+	<div class="gf-box-body" ng-if="editor.index === 1">
+		<h5>IFrame embedding</h5>
+		<p><em>The html code below can be pasted and included in another web page. Unless anonymous access
+			is enabled the user viewing that page need to be signed into grafana for the graph to load.
+		</em>
+		</p>
+		<div class="gf-form">
+			<div class="gf-form-row">
+				<span class="gf-fluid-input">
+					<textarea rows="5" data-share-panel-url class="input" ng-model='iframeHtml'></textarea>
+				</span>
+			</div>
+			<button class="btn btn-inverse" data-clipboard-text="{{iframeHtml}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
+			<br>
+			<br>
+		</div>
+	</div>
+</div>

+ 4 - 1
src/app/features/dashboard/sharePanelCtrl.js

@@ -72,8 +72,11 @@ function (angular, _, require, config) {
       });
 
       $scope.shareUrl = baseUrl + "?" + paramsArray.join('&');
-      $scope.imageUrl = $scope.shareUrl.replace('/dashboard/db/', '/render/dashboard/solo/');
 
+      $scope.soloUrl = $scope.shareUrl.replace('/dashboard/db/', '/dashboard/solo/');
+      $scope.iframeHtml = '<iframe src="' + $scope.soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
+
+      $scope.imageUrl = $scope.shareUrl.replace('/dashboard/db/', '/render/dashboard/solo/');
       $scope.imageUrl += '&width=1000';
       $scope.imageUrl += '&height=500';
     };

+ 1 - 1
src/app/features/panel/panelSrv.js

@@ -27,7 +27,7 @@ function (angular, _, config) {
 
       $scope.sharePanel = function() {
         $scope.appEvent('show-modal', {
-          src: './app/features/dashboard/partials/shareModal.html',
+          src: './app/features/dashboard/partials/sharePanel.html',
           scope: $scope.$new()
         });
       };

+ 14 - 2
src/app/features/panel/soloPanelCtrl.js

@@ -7,10 +7,22 @@ function (angular, $) {
 
   var module = angular.module('grafana.routes');
 
-  module.controller('SoloPanelCtrl', function($scope, backendSrv, $routeParams, dashboardSrv, timeSrv, $location, templateValuesSrv) {
+  module.controller('SoloPanelCtrl',
+    function(
+      $scope,
+      backendSrv,
+      $routeParams,
+      dashboardSrv,
+      timeSrv,
+      $location,
+      templateValuesSrv,
+      contextSrv) {
+
     var panelId;
 
     $scope.init = function() {
+      contextSrv.sidemenu = false;
+
       var params = $location.search();
       panelId = parseInt(params.panelId);
 
@@ -26,7 +38,7 @@ function (angular, $) {
       $scope.dashboard = dashboardSrv.create(dashboard.model);
 
       $scope.row = {
-        height: $(window).height() + 'px',
+        height: ($(window).height() - 10) + 'px',
       };
 
       $scope.test = "Hej";

+ 18 - 8
src/app/features/panellinkeditor/module.html

@@ -2,23 +2,23 @@
   <div class="section">
 		<h5>Drilldown / detail link<tip>These links appear in the dropdown menu in the panel menu. </tip></h5>
 
-		<div class="tight-form" ng-repeat="link in panel.links"j>
+		<div class="tight-form" ng-repeat="link in panel.links">
 			<ul class="tight-form-list">
 				<li class="tight-form-item">
 					<i class="fa fa-remove pointer" ng-click="deleteLink(link)"></i>
 				</li>
 
-				<li class="tight-form-item">title</li>
+				<li class="tight-form-item" style="width: 80px;">Link title</li>
 				<li>
 					<input type="text" ng-model="link.title" class="input-medium tight-form-input">
 				</li>
 
-				<li class="tight-form-item">type</li>
+				<li class="tight-form-item">Type</li>
 				<li>
 					<select class="input-medium tight-form-input" style="width: 101px;" ng-model="link.type" ng-options="f for f in ['dashboard','absolute']"></select>
 				</li>
 
-				<li class="tight-form-item" ng-show="link.type === 'dashboard'">dashboard</li>
+				<li class="tight-form-item" ng-show="link.type === 'dashboard'">Dashboard</li>
 				<li ng-show="link.type === 'dashboard'">
 					<input type="text"
 					ng-model="link.dashboard"
@@ -26,20 +26,30 @@
 					class="input-large tight-form-input">
 				</li>
 
-				<li class="tight-form-item" ng-show="link.type === 'absolute'">url</li>
+				<li class="tight-form-item" ng-show="link.type === 'absolute'">Url</li>
 				<li ng-show="link.type === 'absolute'">
-					<input type="text" ng-model="link.url" class="input-large tight-form-input">
+					<input type="text" ng-model="link.url" class="input-xlarge tight-form-input">
 				</li>
+			</ul>
+			<div class="clearfix"></div>
+		</div>
 
-				<li class="tight-form-item">params
+		<div class="tight-form">
+			<ul class="tight-form-list" role="menu">
+				<li class="tight-form-item">
+					<i class="fa fa-remove invisible"></i>
+				</li>
+				<li class="tight-form-item" style="width: 80px;">
+					Params
 					<tip>Use var-variableName=value to pass templating variables.</tip>
 				</li>
 				<li>
-					<input type="text" ng-model="link.params" class="input-medium tight-form-input">
+					<input type="text" ng-model="link.params" class="input-xxlarge tight-form-input">
 				</li>
 			</ul>
 			<div class="clearfix"></div>
 		</div>
+
 	</div>
 </div>
 

+ 17 - 13
src/app/panels/graph/graph.js

@@ -351,7 +351,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
             show: scope.panel['y-axis'],
             min: scope.panel.grid.leftMin,
             index: 1,
-            logBase: scope.panel.grid.leftLogBase,
+            logBase: scope.panel.grid.leftLogBase || 1,
             max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.leftMax,
           };
 
@@ -360,7 +360,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
           if (_.findWhere(data, {yaxis: 2})) {
             var secondY = _.clone(defaults);
             secondY.index = 2,
-            secondY.logBase = scope.panel.grid.rightLogBase;
+            secondY.logBase = scope.panel.grid.rightLogBase || 2,
             secondY.position = 'right';
             secondY.min = scope.panel.grid.rightMin;
             secondY.max = scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.rightMax;
@@ -375,7 +375,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
         }
 
         function applyLogScale(axis, data) {
-          if (axis.logBase !== 10) {
+          if (axis.logBase === 1) {
             return;
           }
 
@@ -391,26 +391,30 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
                 }
               }
             }
-
-            if (max === null) {
+            if (max === void 0) {
               max = Number.MAX_VALUE;
             }
           }
 
-          axis.min = axis.min !== null ? axis.min : 1;
-          axis.ticks = [1];
-          var tick = 1;
+          axis.min = axis.min !== null ? axis.min : 0;
+          axis.ticks = [0, 1];
+          var nextTick = 1;
 
           while (true) {
-            tick = tick * axis.logBase;
-            axis.ticks.push(tick);
-            if (tick > max) {
+            nextTick = nextTick * axis.logBase;
+            axis.ticks.push(nextTick);
+            if (nextTick > max) {
               break;
             }
           }
 
-          axis.transform = function(v) { return Math.log(v+0.001); };
-          axis.inverseTransform  = function (v) { return Math.pow(10,v); };
+          if (axis.logBase === 10) {
+            axis.transform = function(v) { return Math.log(v+0.1); };
+            axis.inverseTransform  = function (v) { return Math.pow(10,v); };
+          } else {
+            axis.transform = function(v) { return Math.log(v+0.1) / Math.log(axis.logBase); };
+            axis.inverseTransform  = function (v) { return Math.pow(axis.logBase,v); };
+          }
         }
 
         function configureAxisMode(axis, format) {

+ 1 - 1
src/app/panels/graph/module.js

@@ -116,7 +116,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
     _.defaults($scope.panel.grid, _d.grid);
     _.defaults($scope.panel.legend, _d.legend);
 
-    $scope.logScales = {'linear': 1, 'log (base 10)': 10};
+    $scope.logScales = {'linear': 1, 'log (base 16)': 16, 'log (base 10)': 10, 'log (base 1024)': 1024};
 
     $scope.hiddenSeries = {};
     $scope.seriesList = [];

+ 6 - 0
src/css/less/forms.less

@@ -51,5 +51,11 @@ input[type="checkbox"]:checked+label {
     height: 100%;
     box-sizing: border-box;
   }
+  textarea {
+    width: 100%;
+    padding: 5px 6px;
+    height: 100%;
+    box-sizing: border-box;
+  }
 }
 

+ 20 - 1
src/test/specs/graph-specs.js

@@ -28,7 +28,7 @@ define([
             scope.height = '200px';
             scope.panel = {
               legend: {},
-              grid: {},
+              grid: { },
               y_formats: [],
               seriesOverrides: [],
               tooltip: {
@@ -140,6 +140,25 @@ define([
       });
     });
 
+    graphScenario('when logBase is log 10', function(ctx) {
+      ctx.setup(function(scope) {
+        scope.panel.grid = {
+          leftMax: null,
+          rightMax: null,
+          leftMin: null,
+          rightMin: null,
+          leftLogBase: 10,
+        };
+      });
+
+      it('should apply axis transform and ticks', function() {
+        var axis = ctx.plotOptions.yaxes[0];
+        expect(axis.transform(100)).to.be(Math.log(100+0.0001));
+        expect(axis.ticks[0]).to.be(1);
+        expect(axis.ticks[1]).to.be(10);
+      });
+    });
+
     graphScenario('should use timeStep for barWidth', function(ctx) {
       ctx.setup(function(scope, data) {
         scope.panel.bars = true;

+ 6 - 0
src/test/specs/soloPanelCtrl-specs.js

@@ -10,11 +10,13 @@ define([
     var backendSrv = {};
     var routeParams = {};
     var search = {};
+    var contextSrv = {};
 
     beforeEach(module('grafana.routes'));
     beforeEach(module('grafana.services'));
     beforeEach(ctx.providePhase({
       $routeParams: routeParams,
+      contextSrv: contextSrv,
       $location: {
         search: function() {
           return search;
@@ -57,6 +59,10 @@ define([
         expect(ctx.scope.panel.some).to.be('prop');
       });
 
+      it('should hide sidemenu', function() {
+        expect(contextSrv.sidemenu).to.be(false);
+      });
+
     });
 
   });

+ 8 - 2
vendor/phantomjs/render.js

@@ -9,13 +9,19 @@ args.forEach(function(arg) {
   params[parts[1]] = parts[2];
 });
 
-var usage = "url=<url> png=<filename> width=<width> height=<height>";
+var usage = "url=<url> png=<filename> width=<width> height=<height> cookiename=<cookiename> sessionid=<sessionid> domain=<domain>";
 
-if (!params.url || !params.png) {
+if (!params.url || !params.png || !params.cookiename || ! params.sessionid || !params.domain) {
   console.log(usage);
   phantom.exit();
 }
 
+phantom.addCookie({
+  'name': params.cookiename,
+  'value': params.sessionid,
+  'domain': params.domain
+});
+
 page.viewportSize = {
   width: params.width || '800',
   height: params.height || '400'