Skip to content
File

Blob: ui/embed.go

go61 lines
1// Package ui serves the embedded client and operator frontends.
2package ui
3 
4import (
5 "embed"
6 "fmt"
7 "io/fs"
8 "net/http"
9 "strings"
10)
11 
12//go:embed all:dist
13var output embed.FS
14 
15func page(name string) http.Handler {
16 body, err := output.ReadFile("dist/" + name + "/index.html")
17 if err != nil {
18 panic(fmt.Errorf("read %s UI (run make ui): %w", name, err))
19 }
20 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21 if r.Method != http.MethodGet && r.Method != http.MethodHead {
22 w.Header().Set("Allow", "GET, HEAD")
23 http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
24 return
25 }
26 w.Header().Set("Content-Type", "text/html; charset=utf-8")
27 w.Header().Set("Cache-Control", "no-cache")
28 if r.Method != http.MethodHead {
29 w.Write(body)
30 }
31 })
32}
33 
34func ClientPage() http.Handler { return page("client") }
35func OperatorPage() http.Handler { return page("operator") }
36 
37// Assets serves common build assets relative to /ui/ or /_internal/ui/.
38// The caller strips its prefix and applies the interface's access controls.
39func Assets() http.Handler {
40 files, err := fs.Sub(output, "dist")
41 if err != nil {
42 panic(err)
43 }
44 server := http.FileServerFS(files)
45 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46 name := strings.TrimPrefix(r.URL.Path, "/")
47 if !fs.ValidPath(name) || strings.HasSuffix(name, "/") ||
48 (!strings.HasPrefix(name, "assets/") && !strings.HasPrefix(name, "fonts/")) {
49 http.NotFound(w, r)
50 return
51 }
52 if r.Method != http.MethodGet && r.Method != http.MethodHead {
53 w.Header().Set("Allow", "GET, HEAD")
54 http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
55 return
56 }
57 w.Header().Set("Cache-Control", "private, max-age=31536000, immutable")
58 server.ServeHTTP(w, r)
59 })
60}