package main import ( "embed" "errors" "flag" "fmt" "io/fs" "log" "net/http" "time" "github.com/gorilla/websocket" ) //go:embed webui/* var webUI embed.FS var wsUpgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } func runServe(portName string, baud int, args []string) error { serveFlags := flag.NewFlagSet("serve", flag.ExitOnError) addr := serveFlags.String("addr", ":8080", "dashboard HTTP listen address") apiAddr := serveFlags.String("api-addr", ":8081", "external API HTTP listen address (empty to disable)") accelInterval := serveFlags.Duration("accel-interval", defaultAccelStreamInterval, "accel WebSocket sample period on API server") interval := serveFlags.Duration("interval", 2*time.Second, "UART poll interval") if err := serveFlags.Parse(args); err != nil { return err } if portName == "" { return fmt.Errorf("serve requires -port (master UART)") } link := newManagedSerial(portName, baud) link.quiet = true hub := newWSHub() streamCtl := newAccelStreamCtl() tapCtl := newTapNotifyCtl() stop := make(chan struct{}) var dashSrv *http.Server var apiSrv *http.Server registerShutdown(func() { close(stop) shutdownHTTPServer(dashSrv) shutdownAPIServer(apiSrv) if err := link.Close(); err != nil { log.Printf("UART close: %v", err) } }) go runPoller(link, portName, hub, streamCtl, tapCtl, *interval, stop) go runBatteryPoller(link, hub, 5*time.Second, stop) go runCacheStatusDashboardPoller(link, hub, *accelInterval, stop) if *apiAddr != "" { apiSrv = runAPIServer(portName, link, *apiAddr, *accelInterval, hub, streamCtl, tapCtl, stop) } mux := http.NewServeMux() mountServeAPI(mux, link, hub, streamCtl, tapCtl) mux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { conn, err := wsUpgrader.Upgrade(w, r, nil) if err != nil { log.Printf("websocket upgrade: %v", err) return } hub.register(conn) defer hub.unregister(conn) defer conn.Close() for { if _, _, err := conn.ReadMessage(); err != nil { return } } }) ui, err := fs.Sub(webUI, "webui") if err != nil { return err } mux.Handle("/", http.FileServer(http.FS(ui))) log.Printf("dashboard http://localhost%s (UART %s @ %d baud, poll %s, live-stream %s, auto-reconnect)", *addr, portName, baud, interval.String(), accelInterval.String()) if *apiAddr == "" { log.Printf("external API disabled (-api-addr \"\")") } dashSrv = &http.Server{Addr: *addr, Handler: mux} go func() { if err := dashSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Printf("dashboard server: %v", err) } }() waitForShutdown() return nil }