usb-server/internal/web/handler.go

325 lines
8.3 KiB
Go

package web
import (
"embed"
"encoding/json"
"io/fs"
"log"
"net/http"
"github.com/duffy/usb-server/internal/config"
"github.com/duffy/usb-server/internal/token"
)
//go:embed static
var staticFiles embed.FS
// Handler provides the web UI and API
type Handler struct {
cfg *config.Config
cfgPath string
mux *http.ServeMux
// Callbacks for device operations
GetDevices func() interface{}
AttachDevice func(clientID, busID string) error
DetachDevice func(clientID, busID string) error
SetAutoConnect func(vendorID, productID string, enabled bool) error
IsAutoConnect func(vendorID, productID string) bool
InstallService func() error
UninstallService func() error
GetStatus func() map[string]interface{}
}
// NewHandler creates a new web handler
func NewHandler(cfg *config.Config, cfgPath string) *Handler {
h := &Handler{
cfg: cfg,
cfgPath: cfgPath,
mux: http.NewServeMux(),
}
h.setupRoutes()
return h
}
func (h *Handler) setupRoutes() {
// Static files
staticFS, _ := fs.Sub(staticFiles, "static")
h.mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
h.mux.HandleFunc("/", h.handleIndex)
// API endpoints
h.mux.HandleFunc("/api/status", h.handleStatus)
h.mux.HandleFunc("/api/devices", h.handleDevices)
h.mux.HandleFunc("/api/attach", h.handleAttach)
h.mux.HandleFunc("/api/detach", h.handleDetach)
h.mux.HandleFunc("/api/config", h.handleConfig)
h.mux.HandleFunc("/api/generate-token", h.handleGenerateToken)
h.mux.HandleFunc("/api/apply-tokens", h.handleApplyTokens)
h.mux.HandleFunc("/api/auto-connect", h.handleAutoConnect)
h.mux.HandleFunc("/api/service/install", h.handleServiceInstall)
h.mux.HandleFunc("/api/service/uninstall", h.handleServiceUninstall)
}
// ServeHTTP implements http.Handler
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.mux.ServeHTTP(w, r)
}
func (h *Handler) handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
data, err := staticFiles.ReadFile("static/index.html")
if err != nil {
http.Error(w, "Internal error", 500)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(data)
}
func (h *Handler) handleStatus(w http.ResponseWriter, r *http.Request) {
status := map[string]interface{}{
"connected": false,
"mode": h.cfg.Mode,
"name": h.cfg.Name,
}
if h.GetStatus != nil {
status = h.GetStatus()
}
writeJSON(w, status)
}
func (h *Handler) handleDevices(w http.ResponseWriter, r *http.Request) {
if h.GetDevices == nil {
writeJSON(w, map[string]interface{}{"mode": h.cfg.Mode})
return
}
writeJSON(w, h.GetDevices())
}
func (h *Handler) handleAttach(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", 405)
return
}
var req struct {
ClientID string `json:"client_id"`
BusID string `json:"bus_id"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": "invalid request"})
return
}
if h.AttachDevice == nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": "not in use mode"})
return
}
if err := h.AttachDevice(req.ClientID, req.BusID); err != nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": err.Error()})
return
}
writeJSON(w, map[string]interface{}{"ok": true})
}
func (h *Handler) handleDetach(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", 405)
return
}
var req struct {
ClientID string `json:"client_id"`
BusID string `json:"bus_id"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": "invalid request"})
return
}
if h.DetachDevice == nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": "not in use mode"})
return
}
if err := h.DetachDevice(req.ClientID, req.BusID); err != nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": err.Error()})
return
}
writeJSON(w, map[string]interface{}{"ok": true})
}
func (h *Handler) handleConfig(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
writeJSON(w, h.cfg)
return
}
if r.Method == "POST" {
var updates struct {
RelayAddr string `json:"relay_addr"`
Mode string `json:"mode"`
Name string `json:"name"`
WebPort int `json:"web_port"`
}
if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": "invalid request"})
return
}
if updates.RelayAddr != "" {
h.cfg.RelayAddr = updates.RelayAddr
}
if updates.Mode == "share" || updates.Mode == "use" {
h.cfg.Mode = updates.Mode
}
if updates.Name != "" {
h.cfg.Name = updates.Name
}
if updates.WebPort > 0 {
h.cfg.WebPort = updates.WebPort
}
if err := h.cfg.Save(h.cfgPath); err != nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": err.Error()})
return
}
writeJSON(w, map[string]interface{}{"ok": true})
return
}
http.Error(w, "Method not allowed", 405)
}
func (h *Handler) handleGenerateToken(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", 405)
return
}
tokens, err := token.Generate()
if err != nil {
writeJSON(w, map[string]interface{}{"error": err.Error()})
return
}
hash := tokens.Hash()
// Save tokens to config
h.cfg.Token1 = tokens.Token1
h.cfg.Token2 = tokens.Token2
h.cfg.Token3 = tokens.Token3
h.cfg.Hash = hash
h.cfg.Save(h.cfgPath)
writeJSON(w, map[string]interface{}{
"token1": tokens.Token1,
"token2": tokens.Token2,
"token3": tokens.Token3,
"hash": hash,
})
}
func (h *Handler) handleApplyTokens(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", 405)
return
}
var req struct {
Token1 string `json:"token1"`
Token2 string `json:"token2"`
Token3 string `json:"token3"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": "invalid request"})
return
}
hash := token.HashFromTokens(req.Token1, req.Token2, req.Token3)
h.cfg.Token1 = req.Token1
h.cfg.Token2 = req.Token2
h.cfg.Token3 = req.Token3
h.cfg.Hash = hash
h.cfg.Save(h.cfgPath)
writeJSON(w, map[string]interface{}{"ok": true, "hash": hash})
}
func (h *Handler) handleAutoConnect(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", 405)
return
}
var req struct {
VendorID string `json:"vendor_id"`
ProductID string `json:"product_id"`
Enabled bool `json:"enabled"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": "invalid request"})
return
}
if h.SetAutoConnect == nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": "not in use mode"})
return
}
if err := h.SetAutoConnect(req.VendorID, req.ProductID, req.Enabled); err != nil {
writeJSON(w, map[string]interface{}{"ok": false, "error": err.Error()})
return
}
writeJSON(w, map[string]interface{}{"ok": true})
}
func (h *Handler) handleServiceInstall(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", 405)
return
}
if h.InstallService == nil {
writeJSON(w, map[string]interface{}{"error": "service management not available"})
return
}
if err := h.InstallService(); err != nil {
writeJSON(w, map[string]interface{}{"error": err.Error()})
return
}
writeJSON(w, map[string]interface{}{"message": "Service installiert und gestartet"})
}
func (h *Handler) handleServiceUninstall(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", 405)
return
}
if h.UninstallService == nil {
writeJSON(w, map[string]interface{}{"error": "service management not available"})
return
}
if err := h.UninstallService(); err != nil {
writeJSON(w, map[string]interface{}{"error": err.Error()})
return
}
writeJSON(w, map[string]interface{}{"message": "Service deinstalliert"})
}
func writeJSON(w http.ResponseWriter, v interface{}) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(v); err != nil {
log.Printf("[web] JSON encode error: %v", err)
}
}