diff --git a/conf/defaults.ini b/conf/defaults.ini
index 93158d3b6cb..4457cee9dba 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -1568,6 +1568,12 @@ concurrent_render_request_limit = 30
# Default is 5m. This should be more than enough for most deployments.
# Change the value only if image rendering is failing and you see `Failed to get the render key from cache` in Grafana logs.
render_key_lifetime = 5m
+# Default width for panel screenshot
+default_image_width = 1000
+# Default height for panel screenshot
+default_image_height = 500
+# Default scale for panel screenshot
+default_image_scale = 1
[panels]
# here for to support old env variables, can remove after a few months
diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md
index bf00cb8c985..30a55bf5abf 100644
--- a/docs/sources/setup-grafana/configure-grafana/_index.md
+++ b/docs/sources/setup-grafana/configure-grafana/_index.md
@@ -2168,6 +2168,18 @@ If the remote HTTP image renderer service runs on a different server than the Gr
Concurrent render request limit affects when the /render HTTP endpoint is used. Rendering many images at the same time can overload the server,
which this setting can help protect against by only allowing a certain number of concurrent requests. Default is `30`.
+### default_image_width
+
+Configures the width of the rendered image. The default width is `1000`.
+
+### default_image_height
+
+Configures the height of the rendered image. The default height is `500`.
+
+### default_image_scale
+
+Configures the scale of the rendered image. The default scale is `1`.
+
## [panels]
### enable_alpha
diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts
index f90928cde68..2ff143918db 100644
--- a/packages/grafana-runtime/src/config.ts
+++ b/packages/grafana-runtime/src/config.ts
@@ -99,6 +99,9 @@ export class GrafanaBootConfig implements GrafanaConfig {
licenseInfo: LicenseInfo = {} as LicenseInfo;
rendererAvailable = false;
rendererVersion = '';
+ rendererDefaultImageWidth = 1000;
+ rendererDefaultImageHeight = 500;
+ rendererDefaultImageScale = 1;
secretsManagerPluginEnabled = false;
supportBundlesEnabled = false;
http2Enabled = false;
diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go
index 95d8e94b6e8..8c9bcd6ee8a 100644
--- a/pkg/api/dtos/frontend_settings.go
+++ b/pkg/api/dtos/frontend_settings.go
@@ -208,6 +208,9 @@ type FrontendSettingsDTO struct {
AnonymousDeviceLimit int64 `json:"anonymousDeviceLimit"`
RendererAvailable bool `json:"rendererAvailable"`
RendererVersion string `json:"rendererVersion"`
+ RendererDefaultImageWidth int `json:"rendererDefaultImageWidth"`
+ RendererDefaultImageHeight int `json:"rendererDefaultImageHeight"`
+ RendererDefaultImageScale float64 `json:"rendererDefaultImageScale"`
SecretsManagerPluginEnabled bool `json:"secretsManagerPluginEnabled"`
Http2Enabled bool `json:"http2Enabled"`
GrafanaJavascriptAgent setting.GrafanaJavascriptAgent `json:"grafanaJavascriptAgent"`
diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go
index 0f0e6ec68d6..bc4ca18ffb1 100644
--- a/pkg/api/frontendsettings.go
+++ b/pkg/api/frontendsettings.go
@@ -245,6 +245,9 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
AnonymousDeviceLimit: hs.Cfg.AnonymousDeviceLimit,
RendererAvailable: hs.RenderService.IsAvailable(c.Req.Context()),
RendererVersion: hs.RenderService.Version(),
+ RendererDefaultImageWidth: hs.Cfg.RendererDefaultImageWidth,
+ RendererDefaultImageHeight: hs.Cfg.RendererDefaultImageHeight,
+ RendererDefaultImageScale: hs.Cfg.RendererDefaultImageScale,
SecretsManagerPluginEnabled: secretsManagerPluginEnabled,
Http2Enabled: hs.Cfg.Protocol == setting.HTTP2Scheme,
GrafanaJavascriptAgent: hs.Cfg.GrafanaJavascriptAgent,
diff --git a/pkg/api/render.go b/pkg/api/render.go
index 35ef1563d06..0855022b510 100644
--- a/pkg/api/render.go
+++ b/pkg/api/render.go
@@ -24,16 +24,14 @@ func (hs *HTTPServer) RenderToPng(c *contextmodel.ReqContext) {
queryParams := fmt.Sprintf("?%s", c.Req.URL.RawQuery)
- width, err := strconv.Atoi(queryReader.Get("width", "800"))
- if err != nil {
- c.Handle(hs.Cfg, 400, "Render parameters error", fmt.Errorf("cannot parse width as int: %s", err))
- return
+ width := c.QueryInt("width")
+ if width == 0 {
+ width = hs.Cfg.RendererDefaultImageWidth
}
- height, err := strconv.Atoi(queryReader.Get("height", "400"))
- if err != nil {
- c.Handle(hs.Cfg, 400, "Render parameters error", fmt.Errorf("cannot parse height as int: %s", err))
- return
+ height := c.QueryInt("height")
+ if height == 0 {
+ height = hs.Cfg.RendererDefaultImageHeight
}
timeout, err := strconv.Atoi(queryReader.Get("timeout", "60"))
@@ -42,10 +40,9 @@ func (hs *HTTPServer) RenderToPng(c *contextmodel.ReqContext) {
return
}
- scale, err := strconv.ParseFloat(queryReader.Get("scale", "1"), 64)
- if err != nil {
- c.Handle(hs.Cfg, 400, "Render parameters error", fmt.Errorf("cannot parse scale as float: %s", err))
- return
+ scale := c.QueryFloat64("scale")
+ if scale == 0 {
+ scale = hs.Cfg.RendererDefaultImageScale
}
headers := http.Header{}
diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go
index c00b5e50091..b4d946569e7 100644
--- a/pkg/setting/setting.go
+++ b/pkg/setting/setting.go
@@ -149,6 +149,9 @@ type Cfg struct {
RendererAuthToken string
RendererConcurrentRequestLimit int
RendererRenderKeyLifeTime time.Duration
+ RendererDefaultImageWidth int
+ RendererDefaultImageHeight int
+ RendererDefaultImageScale float64
// Security
DisableInitAdminCreation bool
@@ -1739,6 +1742,9 @@ func (cfg *Cfg) readRenderingSettings(iniFile *ini.File) error {
cfg.RendererConcurrentRequestLimit = renderSec.Key("concurrent_render_request_limit").MustInt(30)
cfg.RendererRenderKeyLifeTime = renderSec.Key("render_key_lifetime").MustDuration(5 * time.Minute)
+ cfg.RendererDefaultImageWidth = renderSec.Key("default_image_width").MustInt(1000)
+ cfg.RendererDefaultImageHeight = renderSec.Key("default_image_height").MustInt(500)
+ cfg.RendererDefaultImageScale = renderSec.Key("default_image_scale").MustFloat64(1)
cfg.ImagesDir = filepath.Join(cfg.DataPath, "png")
cfg.CSVsDir = filepath.Join(cfg.DataPath, "csv")
cfg.PDFsDir = filepath.Join(cfg.DataPath, "pdf")
diff --git a/pkg/web/context.go b/pkg/web/context.go
index 138945f3dcd..706da507ece 100644
--- a/pkg/web/context.go
+++ b/pkg/web/context.go
@@ -205,3 +205,9 @@ func (ctx *Context) GetCookie(name string) string {
val, _ := url.QueryUnescape(cookie.Value)
return val
}
+
+// QueryFloat64 returns query result in float64 type.
+func (ctx *Context) QueryFloat64(name string) float64 {
+ n, _ := strconv.ParseFloat(ctx.Query(name), 64)
+ return n
+}
diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx
index 9f3a559ff04..205e09e8a65 100644
--- a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx
+++ b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx
@@ -105,7 +105,7 @@ describe('ShareModal', () => {
render();
const base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash';
- const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
+ const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&scale=1&tz=UTC';
expect(
await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage })
).toHaveAttribute('href', base + params);
@@ -116,7 +116,7 @@ describe('ShareModal', () => {
render();
const base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js';
- const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
+ const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&scale=1&tz=UTC';
expect(
await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage })
).toHaveAttribute('href', base + params);
@@ -151,7 +151,10 @@ describe('ShareModal', () => {
);
expect(
await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage })
- ).toHaveAttribute('href', base + path + '?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
+ ).toHaveAttribute(
+ 'href',
+ base + path + '?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&scale=1&tz=UTC'
+ );
});
it('should shorten url', async () => {
@@ -168,7 +171,7 @@ describe('ShareModal', () => {
render();
const base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash';
- const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
+ const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&scale=1&tz=UTC';
expect(
await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage })
).toHaveAttribute('href', base + params);
@@ -209,7 +212,7 @@ describe('when appUrl is set in the grafana config', () => {
await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage })
).toHaveAttribute(
'href',
- `http://dashboards.grafana.com/render/d-solo/${mockDashboard.uid}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}&width=1000&height=500&tz=UTC`
+ `http://dashboards.grafana.com/render/d-solo/${mockDashboard.uid}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}&width=1000&height=500&scale=1&tz=UTC`
);
});
});
diff --git a/public/app/features/dashboard/components/ShareModal/utils.ts b/public/app/features/dashboard/components/ShareModal/utils.ts
index 196af5376d4..90b1664c492 100644
--- a/public/app/features/dashboard/components/ShareModal/utils.ts
+++ b/public/app/features/dashboard/components/ShareModal/utils.ts
@@ -121,7 +121,12 @@ export function buildImageUrl(
let soloUrl = buildSoloUrl(useCurrentTimeRange, dashboardUid, selectedTheme, panel);
let imageUrl = soloUrl.replace(config.appSubUrl + '/dashboard-solo/', config.appSubUrl + '/render/dashboard-solo/');
imageUrl = imageUrl.replace(config.appSubUrl + '/d-solo/', config.appSubUrl + '/render/d-solo/');
- imageUrl += '&width=1000&height=500' + getLocalTimeZone();
+ imageUrl +=
+ `&width=${config.rendererDefaultImageWidth}` +
+ `&height=${config.rendererDefaultImageHeight}` +
+ `&scale=${config.rendererDefaultImageScale}` +
+ getLocalTimeZone();
+
return imageUrl;
}