console: display missing images in documentation

And don't embed SVG. This is wasteful.
This commit is contained in:
Vincent Bernat
2025-09-02 07:11:00 +02:00
parent 37ed015448
commit 032cd1336d
4 changed files with 54 additions and 19 deletions

View File

@@ -14,13 +14,21 @@ import (
var embeddedAssets embed.FS var embeddedAssets embed.FS
func (c *Component) assetsHandlerFunc(w http.ResponseWriter, req *http.Request) { func (c *Component) assetsHandlerFunc(w http.ResponseWriter, req *http.Request) {
assets := c.embedOrLiveFS(embeddedAssets, "data/frontend")
upath := req.URL.Path upath := req.URL.Path
if !strings.HasPrefix(upath, "/") { if !strings.HasPrefix(upath, "/") {
upath = "/" + upath upath = "/" + upath
req.URL.Path = upath req.URL.Path = upath
} }
// Serve assets using a file server
// Serve /doc/images
if strings.HasPrefix(upath, "/docs/images/") {
docs := c.embedOrLiveFS(embeddedDocs, "data/docs")
http.ServeFileFS(w, req, docs, req.URL.Path[len("/docs/images/"):])
http.FileServer(http.FS(docs)).ServeHTTP(w, req)
}
// Serve /assets
assets := c.embedOrLiveFS(embeddedAssets, "data/frontend")
if strings.HasPrefix(upath, "/assets/") { if strings.HasPrefix(upath, "/assets/") {
http.FileServer(http.FS(assets)).ServeHTTP(w, req) http.FileServer(http.FS(assets)).ServeHTTP(w, req)
return return

View File

@@ -12,6 +12,7 @@ identified with a specific icon:
## Unreleased ## Unreleased
- 🩹 *console*: display missing images in documentation
- 🌱 *build*: accept building with a not up-to-date toolchain - 🌱 *build*: accept building with a not up-to-date toolchain
- 🌱 *docker*: update ClickHouse to 25.8 (not mandatory) - 🌱 *docker*: update ClickHouse to 25.8 (not mandatory)

View File

@@ -6,7 +6,6 @@ package console
import ( import (
"bytes" "bytes"
"embed" "embed"
"encoding/base64"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -116,7 +115,7 @@ func (c *Component) docsHandlerFunc(gc *gin.Context) {
parser.WithAutoHeadingID(), parser.WithAutoHeadingID(),
parser.WithASTTransformers( parser.WithASTTransformers(
util.Prioritized(&internalLinkTransformer{}, 500), util.Prioritized(&internalLinkTransformer{}, 500),
util.Prioritized(&imageEmbedder{docs}, 500), util.Prioritized(&imageLinkTransformer{docs}, 500),
), ),
), ),
) )
@@ -152,11 +151,11 @@ func (r *internalLinkTransformer) Transform(node *ast.Document, _ text.Reader, _
ast.Walk(node, replaceLinks) ast.Walk(node, replaceLinks)
} }
type imageEmbedder struct { type imageLinkTransformer struct {
root fs.FS root fs.FS
} }
func (r *imageEmbedder) Transform(node *ast.Document, _ text.Reader, _ parser.Context) { func (r *imageLinkTransformer) Transform(node *ast.Document, _ text.Reader, _ parser.Context) {
replaceLinks := func(n ast.Node, entering bool) (ast.WalkStatus, error) { replaceLinks := func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering { if !entering {
return ast.WalkContinue, nil return ast.WalkContinue, nil
@@ -164,19 +163,9 @@ func (r *imageEmbedder) Transform(node *ast.Document, _ text.Reader, _ parser.Co
switch node := n.(type) { switch node := n.(type) {
case *ast.Image: case *ast.Image:
path := string(node.Destination) path := string(node.Destination)
if strings.Contains(path, "/") || !strings.HasSuffix(path, ".svg") { if !strings.Contains(path, "/") {
break node.Destination = []byte(fmt.Sprintf("images/%s", path))
} }
f, err := r.root.Open(path)
if err != nil {
break
}
content, err := io.ReadAll(f)
if err != nil {
break
}
encoded := fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(content))
node.Destination = []byte(encoded)
} }
return ast.WalkContinue, nil return ast.WalkContinue, nil
} }

View File

@@ -9,6 +9,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
"akvorado/common/helpers"
) )
func TestServeDocs(t *testing.T) { func TestServeDocs(t *testing.T) {
@@ -22,7 +24,7 @@ func TestServeDocs(t *testing.T) {
Expect string Expect string
}{ }{
{"usage", `<a href=\"configuration\">configuration section</a>`}, {"usage", `<a href=\"configuration\">configuration section</a>`},
{"intro", `data:image/svg`}, {"intro", `images/design.svg`},
} }
for _, tc := range cases { for _, tc := range cases {
t.Run(fmt.Sprintf("%s-%s", name, tc.Path), func(t *testing.T) { t.Run(fmt.Sprintf("%s-%s", name, tc.Path), func(t *testing.T) {
@@ -50,3 +52,38 @@ func TestServeDocs(t *testing.T) {
} }
} }
} }
func TestServeImages(t *testing.T) {
for _, live := range []bool{false, true} {
name := "livefs"
if !live {
name = "embeddedfs"
}
t.Run(name, func(t *testing.T) {
conf := DefaultConfiguration()
conf.ServeLiveFS = live
_, h, _, _ := NewMock(t, conf)
resp, err := http.Get(fmt.Sprintf("http://%s/docs/images/design.svg",
h.LocalAddr()))
if err != nil {
t.Fatalf("GET /docs/images/design.svg:\n%+v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("GET /docs/images/design.svg: got status code %d, not 200",
resp.StatusCode)
}
expected := `<?xml version="1.0" encoding="UTF-8"?>`
got := make([]byte, len(expected))
if _, err := io.ReadFull(resp.Body, got); err != nil {
t.Fatalf("GET /docs/images/design.svg ReadFull() error:\n%+v", err)
}
if diff := helpers.Diff(string(got), expected); diff != "" {
t.Errorf("GET /docs/images/design.svg:\n%s",
diff)
}
})
}
}