initial commit
This commit is contained in:
1 parent
5b935301e5
commit
85453852d5
105 files changed
+24846
No files matched your search
@@ -0,0 +1,99 @@
|
||||
// Package web serves the embedded admin console as a single-page app.
|
||||
//
|
||||
// The assets are compiled into the binary, so a deployment is one file plus a
|
||||
// config; there is no node process in production and no CDN dependency. When
|
||||
// the assets are absent (a `go build` without `npm run build`) the server says
|
||||
// so plainly instead of returning a confusing 404.
|
||||
package web
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed all:dist
|
||||
var dist embed.FS
|
||||
|
||||
// Available reports whether compiled assets are present in this binary.
|
||||
func Available() bool {
|
||||
_, err := dist.Open("dist/index.html")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Handler serves the console, falling back to index.html for client-side
|
||||
// routes so a deep link like /logs works on a hard refresh.
|
||||
func Handler(basePath string) (http.Handler, error) {
|
||||
sub, err := fs.Sub(dist, "dist")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("web: open assets: %w", err)
|
||||
}
|
||||
if _, err := fs.Stat(sub, "index.html"); err != nil {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
_, _ = w.Write([]byte(missingAssets))
|
||||
}), nil
|
||||
}
|
||||
files := http.FileServer(http.FS(sub))
|
||||
prefix := "/"
|
||||
if basePath != "" && basePath != "/" {
|
||||
prefix = "/" + strings.Trim(basePath, "/") + "/"
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Hashed asset filenames are immutable, so they can be cached hard.
|
||||
if strings.HasPrefix(r.URL.Path, prefix+"assets/") {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
}
|
||||
clean := strings.TrimPrefix(r.URL.Path, strings.TrimSuffix(prefix, "/"))
|
||||
clean = strings.TrimPrefix(clean, "/")
|
||||
if clean == "" {
|
||||
clean = "index.html"
|
||||
}
|
||||
if _, err := fs.Stat(sub, clean); err != nil {
|
||||
// Unknown path: hand it to the SPA router.
|
||||
b, rerr := fs.ReadFile(sub, "index.html")
|
||||
if rerr != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(b)
|
||||
return
|
||||
}
|
||||
files.ServeHTTP(w, r)
|
||||
}), nil
|
||||
}
|
||||
|
||||
const missingAssets = `<!doctype html>
|
||||
<html><head><meta charset="utf-8"><title>fancy-dns</title>
|
||||
<style>body{font:16px/1.6 system-ui,sans-serif;max-width:40rem;margin:4rem auto;padding:0 1rem;color:#0f172a;background:#f8fafc}
|
||||
code{background:#e2e8f0;padding:.15em .4em;border-radius:.25rem}</style></head>
|
||||
<body><h1>fancy-dns</h1>
|
||||
<p>The admin API is running, but the web console was not built into this binary.</p>
|
||||
<p>Build it with <code>make web</code> (or <code>cd web && npm ci && npm run build</code>), then rebuild the binary.</p>
|
||||
<p>The JSON API is available under <code>/api</code>.</p></body></html>`
|
||||
|
||||
// Version is the asset build stamp, exposed in the UI footer.
|
||||
func Version() string {
|
||||
b, err := dist.ReadFile("dist/.build-version")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(b))
|
||||
}
|
||||
|
||||
// CacheControlMaxAge is the immutable-asset max age in seconds.
|
||||
const CacheControlMaxAge = 31536000
|
||||
|
||||
// maxAgeHeader builds the Cache-Control value for a hashed asset.
|
||||
func maxAgeHeader(seconds int) string {
|
||||
return "public, max-age=" + strconv.Itoa(seconds) + ", immutable"
|
||||
}
|
||||
Reference in new issue
Block a user