mirror of
https://github.com/la5nta/pat
synced 2026-08-13 17:46:54 -04:00
87 lines
2.4 KiB
Go
87 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)
|
||
|
|
}
|