mirror of
https://github.com/la5nta/pat
synced 2026-08-07 22:32:06 -04:00
The embedded filesystem was in package main, making it inaccessible to alternative main packages that need to serve the HTTP interface. Moving the embed directive and web handlers to a dedicated web package allows any package to import and use the web UI functionality. This also improves separation of concerns by isolating static file serving, template handling, and dev server proxying in a single package, and simplifies the API by removing the staticContent parameter from NewHandler. Ref #512 and #15
86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
// Package web provides HTTP handlers for serving the web UI.
|
|
package web
|
|
|
|
import (
|
|
"embed"
|
|
"html/template"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/la5nta/pat/internal/buildinfo"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
//go:embed dist/**
|
|
var embeddedFS embed.FS
|
|
|
|
func devServerAddr() string { return strings.TrimSuffix(os.Getenv("PAT_WEB_DEV_ADDR"), "/") }
|
|
|
|
// DistHandler returns an HTTP handler that serves the static files for the web UI.
|
|
func DistHandler() http.Handler {
|
|
switch target := devServerAddr(); {
|
|
case target != "":
|
|
targetURL, err := url.Parse(target)
|
|
if err != nil {
|
|
log.Fatalf("invalid proxy target URL: %v", err)
|
|
}
|
|
return httputil.NewSingleHostReverseProxy(targetURL)
|
|
default:
|
|
return http.FileServer(http.FS(embeddedFS))
|
|
}
|
|
}
|
|
|
|
// UIHandler returns an HTTP handler that serves the UI pages with the given callsign.
|
|
func UIHandler(mycall string) http.Handler {
|
|
r := mux.NewRouter()
|
|
r.HandleFunc("/ui", templateHandler("dist/index.html", mycall)).Methods("GET")
|
|
r.HandleFunc("/ui/config", templateHandler("dist/config.html", mycall)).Methods("GET")
|
|
r.HandleFunc("/ui/template", templateHandler("dist/template.html", mycall)).Methods("GET")
|
|
return r
|
|
}
|
|
|
|
func templateHandler(templatePath string, mycall string) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
// Redirect to config if no callsign is set and we're not already on config page
|
|
if mycall == "" && r.URL.Path != "/ui/config" {
|
|
http.Redirect(w, r, "/ui/config", http.StatusFound)
|
|
return
|
|
}
|
|
t, err := loadTemplate(templatePath)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
tmplData := struct{ AppName, Version, Mycall string }{buildinfo.AppName, buildinfo.VersionString(), mycall}
|
|
if err := t.Execute(w, tmplData); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func loadTemplate(templatePath string) (*template.Template, error) {
|
|
if devServer := devServerAddr(); devServer != "" {
|
|
// Dev mode: fetch from dev server
|
|
resp, err := http.Get(devServer + "/" + templatePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return template.New(path.Base(templatePath)).Parse(string(data))
|
|
}
|
|
|
|
// Load from embedded FS
|
|
return template.ParseFS(embeddedFS, templatePath)
|
|
}
|