You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

102 lines
2.3 KiB

package main
import (
"embed"
"io"
"io/fs"
"net/http"
"path"
"strings"
)
//go:embed all:web/dist all:web/public
var adminFS embed.FS
// getAdminSubFS returns the embedded filesystem for the admin UI.
func getAdminSubFS() (fs.FS, error) {
// Try dist first (built assets)
distFS, err := fs.Sub(adminFS, "web/dist")
if err == nil {
// Check if dist has content
entries, _ := fs.ReadDir(distFS, ".")
if len(entries) > 0 {
return distFS, nil
}
}
// Fall back to public (template)
return fs.Sub(adminFS, "web/public")
}
// serveAdminUI serves the embedded admin web UI.
func (s *AdminServer) serveAdminUI(w http.ResponseWriter, r *http.Request) {
fsys, err := getAdminSubFS()
if err != nil {
http.Error(w, "Admin UI not available", http.StatusInternalServerError)
return
}
// Strip /admin prefix from path
urlPath := r.URL.Path
if strings.HasPrefix(urlPath, "/admin") {
urlPath = strings.TrimPrefix(urlPath, "/admin")
}
urlPath = strings.TrimPrefix(urlPath, "/")
// Default to index.html
if urlPath == "" {
urlPath = "index.html"
}
// Try to open the file
f, err := fsys.Open(urlPath)
if err != nil {
// For SPA routing, serve index.html for non-existent paths
urlPath = "index.html"
f, err = fsys.Open(urlPath)
if err != nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
}
defer f.Close()
// Check if it's a directory
stat, err := f.Stat()
if err != nil {
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
if stat.IsDir() {
// Try index.html in the directory
f.Close()
urlPath = path.Join(urlPath, "index.html")
f, err = fsys.Open(urlPath)
if err != nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
defer f.Close()
}
// Set content type based on extension
switch path.Ext(urlPath) {
case ".html":
w.Header().Set("Content-Type", "text/html; charset=utf-8")
case ".css":
w.Header().Set("Content-Type", "text/css; charset=utf-8")
case ".js":
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
case ".json":
w.Header().Set("Content-Type", "application/json; charset=utf-8")
case ".svg":
w.Header().Set("Content-Type", "image/svg+xml")
case ".png":
w.Header().Set("Content-Type", "image/png")
case ".ico":
w.Header().Set("Content-Type", "image/x-icon")
}
// Serve the file content directly
io.Copy(w, f)
}