FS: Fix rendering of public dashboards in MT frontend service (#115162)

* pass publicDashboardAccessToken to ST backend via bootdata

* slightly cleaner

* slightly tidy up go templating

* add HandleView middleware
This commit is contained in:
Ashley Harrison
2025-12-11 17:56:40 +00:00
committed by GitHub
parent ada14df9fd
commit d83b216a32
5 changed files with 29 additions and 2 deletions
+9
View File
@@ -187,6 +187,15 @@ func (hs *HTTPServer) registerRoutes() {
publicdashboardsapi.CountPublicDashboardRequest(),
hs.Index,
)
r.Get("/bootdata/:accessToken",
reqNoAuth,
hs.PublicDashboardsApi.Middleware.HandleView,
publicdashboardsapi.SetPublicDashboardAccessToken,
publicdashboardsapi.SetPublicDashboardOrgIdOnContext(hs.PublicDashboardsApi.PublicDashboardService),
publicdashboardsapi.CountPublicDashboardRequest(),
hs.GetBootdata,
)
}
r.Get("/explore", authorize(ac.EvalPermission(ac.ActionDatasourcesExplore)), hs.Index)
@@ -22,6 +22,7 @@ import (
fswebassets "github.com/grafana/grafana/pkg/services/frontend/webassets"
"github.com/grafana/grafana/pkg/services/hooks"
"github.com/grafana/grafana/pkg/services/licensing"
publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
@@ -164,6 +165,11 @@ func (s *frontendService) registerRoutes(m *web.Mux) {
// uses cache busting to ensure requests aren't cached.
s.routeGet(m, "/-/fe-boot-error", s.handleBootError)
s.routeGet(m, "/public-dashboards/:accessToken",
publicdashboardsapi.SetPublicDashboardAccessToken,
s.index.HandleRequest,
)
// All other requests return index.html
s.routeGet(m, "/*", s.index.HandleRequest)
}
+5 -1
View File
@@ -45,6 +45,8 @@ type IndexViewData struct {
// Nonce is a cryptographic identifier for use with Content Security Policy.
Nonce string
PublicDashboardAccessToken string
}
// Templates setup.
@@ -138,9 +140,12 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http.
return
}
reqCtx := contexthandler.FromContext(ctx)
// TODO -- restructure so the static stuff is under one variable and the rest is dynamic
data := p.data // copy everything
data.Nonce = nonce
data.PublicDashboardAccessToken = reqCtx.PublicDashboardAccessToken
if data.CSPEnabled {
data.CSPContent = middleware.ReplacePolicyVariables(p.data.CSPContent, p.data.AppSubUrl, data.Nonce)
@@ -150,7 +155,6 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http.
writer.Header().Set("Content-Security-Policy-Report-Only", policy)
}
reqCtx := contexthandler.FromContext(ctx)
p.runIndexDataHooks(reqCtx, &data)
writer.Header().Set("Content-Type", "text/html; charset=UTF-8")
+8 -1
View File
@@ -188,6 +188,7 @@
// Wrap in an IIFE to avoid polluting the global scope. Intentionally global-scope properties
// are explicitly assigned to the `window` object.
(() => {
const publicDashboardAccessToken = [[.PublicDashboardAccessToken]]
// Grafana can only fail to load once
// However, it can fail to load in multiple different places
// To avoid double reporting the error, we use this boolean to check if we've already failed
@@ -271,9 +272,15 @@
async function fetchBootData() {
const queryParams = new URLSearchParams(window.location.search);
let path = '/bootdata';
// call a special bootdata url with the public access token
// this is needed to set the access token and correct org for public dashboards on the ST backend
if (publicDashboardAccessToken) {
path += `/${publicDashboardAccessToken}`;
}
// pass the search params through to the bootdata request
// this allows for overriding the theme/language etc
const bootDataUrl = new URL('/bootdata', window.location.origin);
const bootDataUrl = new URL(path, window.location.origin);
for (const [key, value] of queryParams.entries()) {
bootDataUrl.searchParams.append(key, value);
}
+1
View File
@@ -24,6 +24,7 @@ export const getPublicDashboardRoutes = (): RouteDescriptor[] => {
{
path: '/public-dashboards/:accessToken',
pageClass: 'page-dashboard',
allowAnonymous: true,
routeName: DashboardRoutes.Public,
chromeless: true,
component: SafeDynamicImport(