usb-server/internal/web/handler.go

293 lines
7.4 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
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/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) 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)
}
}