web.go raw

   1  package main
   2  
   3  import (
   4  	"embed"
   5  	"io"
   6  	"io/fs"
   7  	"net/http"
   8  	"path"
   9  	"strings"
  10  )
  11  
  12  //go:embed all:web/dist all:web/public
  13  var adminFS embed.FS
  14  
  15  // getAdminSubFS returns the embedded filesystem for the admin UI.
  16  func getAdminSubFS() (fs.FS, error) {
  17  	// Try dist first (built assets)
  18  	distFS, err := fs.Sub(adminFS, "web/dist")
  19  	if err == nil {
  20  		// Check if dist has content
  21  		entries, _ := fs.ReadDir(distFS, ".")
  22  		if len(entries) > 0 {
  23  			return distFS, nil
  24  		}
  25  	}
  26  
  27  	// Fall back to public (template)
  28  	return fs.Sub(adminFS, "web/public")
  29  }
  30  
  31  // serveAdminUI serves the embedded admin web UI.
  32  func (s *AdminServer) serveAdminUI(w http.ResponseWriter, r *http.Request) {
  33  	fsys, err := getAdminSubFS()
  34  	if err != nil {
  35  		http.Error(w, "Admin UI not available", http.StatusInternalServerError)
  36  		return
  37  	}
  38  
  39  	// Strip /admin prefix from path
  40  	urlPath := r.URL.Path
  41  	if strings.HasPrefix(urlPath, "/admin") {
  42  		urlPath = strings.TrimPrefix(urlPath, "/admin")
  43  	}
  44  	urlPath = strings.TrimPrefix(urlPath, "/")
  45  
  46  	// Default to index.html
  47  	if urlPath == "" {
  48  		urlPath = "index.html"
  49  	}
  50  
  51  	// Try to open the file
  52  	f, err := fsys.Open(urlPath)
  53  	if err != nil {
  54  		// For SPA routing, serve index.html for non-existent paths
  55  		urlPath = "index.html"
  56  		f, err = fsys.Open(urlPath)
  57  		if err != nil {
  58  			http.Error(w, "Not found", http.StatusNotFound)
  59  			return
  60  		}
  61  	}
  62  	defer f.Close()
  63  
  64  	// Check if it's a directory
  65  	stat, err := f.Stat()
  66  	if err != nil {
  67  		http.Error(w, "Internal error", http.StatusInternalServerError)
  68  		return
  69  	}
  70  	if stat.IsDir() {
  71  		// Try index.html in the directory
  72  		f.Close()
  73  		urlPath = path.Join(urlPath, "index.html")
  74  		f, err = fsys.Open(urlPath)
  75  		if err != nil {
  76  			http.Error(w, "Not found", http.StatusNotFound)
  77  			return
  78  		}
  79  		defer f.Close()
  80  	}
  81  
  82  	// Set content type based on extension
  83  	switch path.Ext(urlPath) {
  84  	case ".html":
  85  		w.Header().Set("Content-Type", "text/html; charset=utf-8")
  86  	case ".css":
  87  		w.Header().Set("Content-Type", "text/css; charset=utf-8")
  88  	case ".js":
  89  		w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
  90  	case ".json":
  91  		w.Header().Set("Content-Type", "application/json; charset=utf-8")
  92  	case ".svg":
  93  		w.Header().Set("Content-Type", "image/svg+xml")
  94  	case ".png":
  95  		w.Header().Set("Content-Type", "image/png")
  96  	case ".ico":
  97  		w.Header().Set("Content-Type", "image/x-icon")
  98  	}
  99  
 100  	// Serve the file content directly
 101  	io.Copy(w, f)
 102  }
 103