From d02de5ddb9022b5e72229b45da358fac58c79d2b Mon Sep 17 00:00:00 2001 From: Khushi Jain Date: Mon, 26 Feb 2024 17:57:34 +0530 Subject: [PATCH] Image Rendering: Add settings for default width, height and scale (#82040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Image width & height * ability to change default width, height and scale * default ini * Update conf/defaults.ini Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update pkg/setting/setting.go Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update pkg/setting/setting.go Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Added docs, changed frontend * Update conf/defaults.ini Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update conf/defaults.ini Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update conf/defaults.ini Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update pkg/api/dtos/frontend_settings.go Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update pkg/api/frontendsettings.go Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update pkg/api/render.go Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * add query float 64 * Update packages/grafana-runtime/src/config.ts Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update public/app/features/dashboard/components/ShareModal/utils.ts Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * spacing * fix tests * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> --------- Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> --- conf/defaults.ini | 6 ++++++ .../setup-grafana/configure-grafana/_index.md | 12 +++++++++++ packages/grafana-runtime/src/config.ts | 3 +++ pkg/api/dtos/frontend_settings.go | 3 +++ pkg/api/frontendsettings.go | 3 +++ pkg/api/render.go | 21 ++++++++----------- pkg/setting/setting.go | 6 ++++++ pkg/web/context.go | 6 ++++++ .../components/ShareModal/ShareLink.test.tsx | 13 +++++++----- .../dashboard/components/ShareModal/utils.ts | 7 ++++++- 10 files changed, 62 insertions(+), 18 deletions(-) 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; }