Swagger: Add a custom swagger/api page (#91785)

Co-authored-by: Kristian Bremberg <kristian.bremberg@grafana.com>
This commit is contained in:
Ryan McKinley
2024-08-14 09:03:00 +03:00
committed by GitHub
parent dacf11b048
commit 427dad26a2
18 changed files with 3838 additions and 1751 deletions

View File

@@ -220,7 +220,7 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/public/plugins/:pluginId/*", hs.getPluginAssets)
// add swagger support
registerSwaggerUI(r)
hs.registerSwaggerUI(r)
r.Post("/api/user/auth-tokens/rotate", routing.Wrap(hs.RotateUserAuthToken))
r.Get("/user/auth-tokens/rotate", routing.Wrap(hs.RotateUserAuthTokenRedirect))

View File

@@ -41,6 +41,7 @@ type EntryPointAssets struct {
JSFiles []EntryPointAsset `json:"jsFiles"`
Dark string `json:"dark"`
Light string `json:"light"`
Swagger []EntryPointAsset `json:"swagger"`
}
type EntryPointAsset struct {
@@ -58,4 +59,7 @@ func (a *EntryPointAssets) SetContentDeliveryURL(prefix string) {
for i, p := range a.JSFiles {
a.JSFiles[i].FilePath = prefix + p.FilePath
}
for i, p := range a.Swagger {
a.Swagger[i].FilePath = prefix + p.FilePath
}
}

View File

@@ -2,31 +2,42 @@ package api
import (
"net/http"
"strings"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/api/webassets"
"github.com/grafana/grafana/pkg/middleware"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/util/errhttp"
)
func registerSwaggerUI(r routing.RouteRegister) {
func (hs *HTTPServer) registerSwaggerUI(r routing.RouteRegister) {
// Deprecated
r.Get("/swagger-ui", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "swagger", http.StatusMovedPermanently)
})
// Deprecated
r.Get("/openapi3", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "swagger?show=v3", http.StatusMovedPermanently)
http.Redirect(w, r, "swagger", http.StatusMovedPermanently)
})
// The swagger based api navigator
r.Get("/swagger", func(c *contextmodel.ReqContext) {
data := map[string]any{
"Nonce": c.RequestNonce,
ctx := c.Context.Req.Context()
assets, err := webassets.GetWebAssets(ctx, hs.Cfg, hs.License)
if err != nil {
errhttp.Write(ctx, err, c.Resp)
return
}
// Add CSP for unpkg.com to allow loading of Swagger UI assets
if existingCSP := c.Resp.Header().Get("Content-Security-Policy"); existingCSP != "" {
newCSP := strings.Replace(existingCSP, "style-src", "style-src https://unpkg.com/", 1)
c.Resp.Header().Set("Content-Security-Policy", newCSP)
data := map[string]any{
"Nonce": c.RequestNonce,
"Assets": assets,
"FavIcon": "public/img/fav32.png",
"AppleTouchIcon": "public/img/apple-touch-icon.png",
}
if hs.Cfg.CSPEnabled {
data["CSPEnabled"] = true
data["CSPContent"] = middleware.ReplacePolicyVariables(hs.Cfg.CSPTemplate, hs.Cfg.AppURL, c.RequestNonce)
}
c.HTML(http.StatusOK, "swagger", data)

File diff suppressed because it is too large Load Diff

View File

@@ -20,9 +20,10 @@ type ManifestInfo struct {
Integrity string `json:"integrity,omitempty"`
// The known entrypoints
App *EntryPointInfo `json:"app,omitempty"`
Dark *EntryPointInfo `json:"dark,omitempty"`
Light *EntryPointInfo `json:"light,omitempty"`
App *EntryPointInfo `json:"app,omitempty"`
Dark *EntryPointInfo `json:"dark,omitempty"`
Light *EntryPointInfo `json:"light,omitempty"`
Swagger *EntryPointInfo `json:"swagger,omitempty"`
}
type EntryPointInfo struct {
@@ -128,18 +129,28 @@ func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) {
if entryPoints.Light == nil || len(entryPoints.Light.Assets.CSS) == 0 {
return nil, fmt.Errorf("missing light entry")
}
// if entryPoints.Swagger == nil || len(entryPoints.Swagger.Assets.JS) == 0 {
// return nil, fmt.Errorf("missing swagger entry")
// }
rsp := &dtos.EntryPointAssets{
JSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.JS)),
Dark: entryPoints.Dark.Assets.CSS[0],
Light: entryPoints.Light.Assets.CSS[0],
Swagger: make([]dtos.EntryPointAsset, 0, len(entryPoints.Swagger.Assets.JS)),
}
entryPointJSAssets := make([]dtos.EntryPointAsset, 0)
for _, entry := range entryPoints.App.Assets.JS {
entryPointJSAssets = append(entryPointJSAssets, dtos.EntryPointAsset{
rsp.JSFiles = append(rsp.JSFiles, dtos.EntryPointAsset{
FilePath: entry,
Integrity: integrity[entry],
})
}
return &dtos.EntryPointAssets{
JSFiles: entryPointJSAssets,
Dark: entryPoints.Dark.Assets.CSS[0],
Light: entryPoints.Light.Assets.CSS[0],
}, nil
for _, entry := range entryPoints.Swagger.Assets.JS {
rsp.Swagger = append(rsp.Swagger, dtos.EntryPointAsset{
FilePath: entry,
Integrity: integrity[entry],
})
}
return rsp, nil
}

View File

@@ -3,6 +3,7 @@ package webassets
import (
"context"
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/require"
@@ -14,76 +15,64 @@ func TestReadWebassets(t *testing.T) {
dto, err := json.MarshalIndent(assets, "", " ")
require.NoError(t, err)
//fmt.Printf("%s\n", string(dto))
// fmt.Printf("%s\n", string(dto))
require.JSONEq(t, `{
"jsFiles": [
{
"filePath": "public/build/runtime.20ed8c01880b812ed29f.js",
"integrity": "sha256-rcdxIHk6cWgu4jiFa1a+pWlileYD/R72GaS8ZACBUdw= sha384-I/VJZQkt+TuJTvu61ihdWPds7EHfLrW5CxeQ0x9gtSqoPg9Z17Uawz1yoYaTdxqQ sha512-4CPAbh4KdTmGxHoQw4pgpYmgAquupVfwfo6UBV2cGU3vGFnEwkhq320037ETwWs+n9xB/bAMOvrdabp1SA1+8g=="
},
{
"filePath": "public/build/3951.4e474348841d792ab1ba.js",
"integrity": "sha256-dHqXXTRA3osYhHr9rol8hOV0nC4VP0pr5tbMp5VD95Q= sha384-4QJaSTibnxdYeYsLnmXtd1+If6IkAmXlLR0uYHN5+N+fS0FegHRH7MIFaRGjiO1B sha512-vRLEeEGbxBCx0z+l/m14fSK49reqWGA9zQzsCrD+TQQBmP07YIoRPwopMMyxtKljbbRFV0bW2bUZ7ZvzOZYoIQ=="
},
{
"filePath": "public/build/3651.4e8f7603e9778e1e9b59.js",
"integrity": "sha256-+N7caL91pVANd7C/aquAneRTjBQenCwaEKqj+3qkjxc= sha384-GQR7GyHPEwwEVph9gGYWEWvMYxkITwcOjieehbPidXZrybuQyw9cpDkjnWo1tj/w sha512-zyPM+8AxyLuECEXjb9w6Z2Sy8zmJdkfTWQphcvAb8AU4ZdkCqLmyjmOs/QQlpfKDe0wdOLyR3V9QgTDDlxtVlQ=="
},
{
"filePath": "public/build/1272.8c79fc44bf7cd993c953.js",
"integrity": "sha256-d7MRVimV83v4YQ5rdURfTaaFtiedXP3EMLT06gvvBuQ= sha384-8tRpYHQ+sEkZ8ptiIbKAbKPpHTJVnmaWDN56vJoWWUCzV1Q2w034wcJNKDJDJdAs sha512-cIZWoJHusF8qODBOj2j4b18ewcLLMo/92YQSwYQjln2G5e3o1bSO476ox2I2iecJ/tnhQK5j01h9BzTt3dNTrA=="
},
{
"filePath": "public/build/6902.070074e8f5a989b8f4c3.js",
"integrity": "sha256-TMo/uTZueyEHtkBzlLZzhwYKWF0epE4qbouo5xcwZkU= sha384-xylZJMtJ7+EsUBBdQZvPh+BeHJ3BnfclqI2vx/8QC9jvfYe/lhRsWW9OMJsxE/Aq sha512-EOmf+KZQMFPoTWAROL8bBLFfHhgvDH8ONycq37JaV7lz+sQOTaWBN2ZD0F/mMdOD5zueTg/Y1RAUP6apoEcHNQ=="
},
{
"filePath": "public/build/app.0439db6f56ee4aa501b2.js",
"integrity": "sha256-q6muaKY7BuN2Ff+00aw69628MXatcFnLNzWRnAD98DI= sha384-gv6lAbkngOHR05bvyOR8dm/J3wIjQQWSjyxK7W8vt2rG9uxcjvvDQV7aI6YbUhfX sha512-o/0mSlJ/OoqrpGdOIWCE3ZCe8n+qqLbgNCERtx9G8FIzsv++CvIWSGbbILjOTGfnEfEQWcKMH0macVpVBSe1Og=="
}
{
"filePath": "public/build/runtime~app.1140e4861852d9e6f2e2.js",
"integrity": "sha256-HwsNc3NYC6Ee9cwVjOiEvhAmcaoe8HIwlwiVu/gc+D4= sha384-tt6bizrzSNPJH+uQGvcBokWaWp34sh0jp015BO7ZAO7HujvBjlduOid/30hz64DK sha512-hC1VmDBaqkULR+iPfPyAY4ZwAa5BXrahre0UrvO7+gLIpv810eu3uCGm5YTm+lFb+YFuBPlJDC4X2DarrEc5sg=="
},
{
"filePath": "public/build/app.5e6b0a8f6800cdea4a51.js",
"integrity": "sha256-xLLqXfNJ3+ybFt0Y3519SRZ8BkZvYm30epIE2y5EaIs= sha384-A/LoksVaN1WL6LO53SVm4KcBciyP6leDAdLLrxk13g0iPRMqOPEF+r7qEl0QnHp1 sha512-amLUBch//DibrSurQ2Zt5oUDvdhl48z3Scw7gqp2ASR0jcsLeev3RHcUJKrpW0GDTMRbUEAhIqFL9he2iYzg9g=="
}
],
"dark": "public/build/grafana.dark.a28b24b45b2bbcc628cc.css",
"light": "public/build/grafana.light.3572f6d5f8b7daa8d8d0.css"
}`, string(dto))
"dark": "public/build/grafana.dark.d27cc3e33cf31ab577e7.css",
"light": "public/build/grafana.light.e6be3c7d879fd499516e.css",
"swagger": [
{
"filePath": "public/build/runtime~swagger.793c8e6ede4824f9b730.js",
"integrity": "sha256-cXK7bq3M16fgI1WgELNFabHXVvn84rPN960QwrmTKiQ= sha384-Ysrs6mXl6RUK5nyttGRFQh4ABbpqxnsckFdJFI7FTU9ZQnyXl04DCam0ADj6t43G sha512-l5lei1D6HlvLrxCVovMdwNhMLDc0zO7u/fJwIvASbi49HjuJRsn6cTw/ZBfeghtqBJF0mZeg+fXo0ZKriY8Wlg=="
},
{
"filePath": "public/build/swagger.789d2ab30a8124d2a92c.js",
"integrity": "sha256-zvXf9SHyzCHFvuOGphlImo8zbJ6jnJVXQOzUFNMPMW8= sha384-GLZ2Z9nV1opFHaQXC6pehHmy4XOBImvDk1w7b8QdYeyobarSrA93CzxjfrtCnA2Y sha512-CZ2oIrYuWGaGU+EZr4/KEFerOZU9XS2NELNNsIwtzs7Zcw/rroa4fU0PiZvXbeT+B2kUy1rGLAQ5nwgR0sQlNA=="
}
]
}`, string(dto))
assets.SetContentDeliveryURL("https://grafana-assets.grafana.net/grafana/10.3.0-64123/")
dto, err = json.MarshalIndent(assets, "", " ")
require.NoError(t, err)
//fmt.Printf("%s\n", string(dto))
fmt.Printf("%s\n", string(dto))
require.JSONEq(t, `{
"cdn": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/",
"jsFiles": [
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/runtime.20ed8c01880b812ed29f.js",
"integrity": "sha256-rcdxIHk6cWgu4jiFa1a+pWlileYD/R72GaS8ZACBUdw= sha384-I/VJZQkt+TuJTvu61ihdWPds7EHfLrW5CxeQ0x9gtSqoPg9Z17Uawz1yoYaTdxqQ sha512-4CPAbh4KdTmGxHoQw4pgpYmgAquupVfwfo6UBV2cGU3vGFnEwkhq320037ETwWs+n9xB/bAMOvrdabp1SA1+8g=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/3951.4e474348841d792ab1ba.js",
"integrity": "sha256-dHqXXTRA3osYhHr9rol8hOV0nC4VP0pr5tbMp5VD95Q= sha384-4QJaSTibnxdYeYsLnmXtd1+If6IkAmXlLR0uYHN5+N+fS0FegHRH7MIFaRGjiO1B sha512-vRLEeEGbxBCx0z+l/m14fSK49reqWGA9zQzsCrD+TQQBmP07YIoRPwopMMyxtKljbbRFV0bW2bUZ7ZvzOZYoIQ=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/3651.4e8f7603e9778e1e9b59.js",
"integrity": "sha256-+N7caL91pVANd7C/aquAneRTjBQenCwaEKqj+3qkjxc= sha384-GQR7GyHPEwwEVph9gGYWEWvMYxkITwcOjieehbPidXZrybuQyw9cpDkjnWo1tj/w sha512-zyPM+8AxyLuECEXjb9w6Z2Sy8zmJdkfTWQphcvAb8AU4ZdkCqLmyjmOs/QQlpfKDe0wdOLyR3V9QgTDDlxtVlQ=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/1272.8c79fc44bf7cd993c953.js",
"integrity": "sha256-d7MRVimV83v4YQ5rdURfTaaFtiedXP3EMLT06gvvBuQ= sha384-8tRpYHQ+sEkZ8ptiIbKAbKPpHTJVnmaWDN56vJoWWUCzV1Q2w034wcJNKDJDJdAs sha512-cIZWoJHusF8qODBOj2j4b18ewcLLMo/92YQSwYQjln2G5e3o1bSO476ox2I2iecJ/tnhQK5j01h9BzTt3dNTrA=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/6902.070074e8f5a989b8f4c3.js",
"integrity": "sha256-TMo/uTZueyEHtkBzlLZzhwYKWF0epE4qbouo5xcwZkU= sha384-xylZJMtJ7+EsUBBdQZvPh+BeHJ3BnfclqI2vx/8QC9jvfYe/lhRsWW9OMJsxE/Aq sha512-EOmf+KZQMFPoTWAROL8bBLFfHhgvDH8ONycq37JaV7lz+sQOTaWBN2ZD0F/mMdOD5zueTg/Y1RAUP6apoEcHNQ=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/app.0439db6f56ee4aa501b2.js",
"integrity": "sha256-q6muaKY7BuN2Ff+00aw69628MXatcFnLNzWRnAD98DI= sha384-gv6lAbkngOHR05bvyOR8dm/J3wIjQQWSjyxK7W8vt2rG9uxcjvvDQV7aI6YbUhfX sha512-o/0mSlJ/OoqrpGdOIWCE3ZCe8n+qqLbgNCERtx9G8FIzsv++CvIWSGbbILjOTGfnEfEQWcKMH0macVpVBSe1Og=="
}
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/runtime~app.1140e4861852d9e6f2e2.js",
"integrity": "sha256-HwsNc3NYC6Ee9cwVjOiEvhAmcaoe8HIwlwiVu/gc+D4= sha384-tt6bizrzSNPJH+uQGvcBokWaWp34sh0jp015BO7ZAO7HujvBjlduOid/30hz64DK sha512-hC1VmDBaqkULR+iPfPyAY4ZwAa5BXrahre0UrvO7+gLIpv810eu3uCGm5YTm+lFb+YFuBPlJDC4X2DarrEc5sg=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/app.5e6b0a8f6800cdea4a51.js",
"integrity": "sha256-xLLqXfNJ3+ybFt0Y3519SRZ8BkZvYm30epIE2y5EaIs= sha384-A/LoksVaN1WL6LO53SVm4KcBciyP6leDAdLLrxk13g0iPRMqOPEF+r7qEl0QnHp1 sha512-amLUBch//DibrSurQ2Zt5oUDvdhl48z3Scw7gqp2ASR0jcsLeev3RHcUJKrpW0GDTMRbUEAhIqFL9he2iYzg9g=="
}
],
"dark": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.dark.a28b24b45b2bbcc628cc.css",
"light": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.light.3572f6d5f8b7daa8d8d0.css"
}`, string(dto))
"dark": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.dark.d27cc3e33cf31ab577e7.css",
"light": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.light.e6be3c7d879fd499516e.css",
"swagger": [
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/runtime~swagger.793c8e6ede4824f9b730.js",
"integrity": "sha256-cXK7bq3M16fgI1WgELNFabHXVvn84rPN960QwrmTKiQ= sha384-Ysrs6mXl6RUK5nyttGRFQh4ABbpqxnsckFdJFI7FTU9ZQnyXl04DCam0ADj6t43G sha512-l5lei1D6HlvLrxCVovMdwNhMLDc0zO7u/fJwIvASbi49HjuJRsn6cTw/ZBfeghtqBJF0mZeg+fXo0ZKriY8Wlg=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/swagger.789d2ab30a8124d2a92c.js",
"integrity": "sha256-zvXf9SHyzCHFvuOGphlImo8zbJ6jnJVXQOzUFNMPMW8= sha384-GLZ2Z9nV1opFHaQXC6pehHmy4XOBImvDk1w7b8QdYeyobarSrA93CzxjfrtCnA2Y sha512-CZ2oIrYuWGaGU+EZr4/KEFerOZU9XS2NELNNsIwtzs7Zcw/rroa4fU0PiZvXbeT+B2kUy1rGLAQ5nwgR0sQlNA=="
}
]
}`, string(dto))
}
func TestReadWebassetsFromCDN(t *testing.T) {