- control-center/web: Vite+React+TS SPA (hash routing, token login via localStorage, marked-lexer markdown rendering without raw HTML injection) - pages: overview (stage x state matrix, SSE feed w/ 10s poll fallback), 8-lane kanban + task create, task detail stepper/pipeline/ops (assign/advance/send-back/external-submit), decision inbox, docs browse + semantic/keyword/hybrid search, global stage settings, audit placeholder - internal/controlcenter/webui: //go:embed dist, SPA fallback, index no-store; dist committed so go build works without node - server: GET / serves embedded SPA; pipeline view now includes artifact content for console rendering - archtest: allowlist controlcenter/server -> controlcenter/webui
53 lines
1.3 KiB
Go
53 lines
1.3 KiB
Go
// Package webui embeds the built Control Center SPA (control-center/web,
|
|
// output committed under dist/) and serves it with SPA fallback. The dist
|
|
// tree is committed so go build works without node.
|
|
package webui
|
|
|
|
import (
|
|
"embed"
|
|
"io/fs"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
//go:embed all:dist
|
|
var distFS embed.FS
|
|
|
|
// Handler serves static assets from dist; unknown paths fall back to
|
|
// index.html (client-side hash routing). index.html is always no-store so
|
|
// new deploys take effect immediately; hashed assets may be cached.
|
|
func Handler() http.Handler {
|
|
sub, err := fs.Sub(distFS, "dist")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fileServer := http.FileServer(http.FS(sub))
|
|
index, err := fs.ReadFile(sub, "index.html")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
p := strings.TrimPrefix(path.Clean("/"+r.URL.Path), "/")
|
|
if p == "" {
|
|
serveIndex(w, r, index)
|
|
return
|
|
}
|
|
if f, err := sub.Open(p); err == nil {
|
|
_ = f.Close()
|
|
fileServer.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
serveIndex(w, r, index)
|
|
})
|
|
}
|
|
|
|
func serveIndex(w http.ResponseWriter, r *http.Request, index []byte) {
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.WriteHeader(http.StatusOK)
|
|
if r.Method != http.MethodHead {
|
|
_, _ = w.Write(index)
|
|
}
|
|
}
|