package main import ( _ "embed" "fmt" "log" "net/http" "github.com/gorilla/websocket" ) //go:embed ui/index.html var uiHTML string var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }} func startUI(dataDir, nodeID, serverAddr string) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") fmt.Fprint(w, uiHTML) }) http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Printf("UI WS upgrade error: %v", err) return } defer conn.Close() log.Printf("UI client connected from %s", r.RemoteAddr) // Register notifier to forward activation results from main WS to this UI connection notifierID := registerUINotifier(func(msg map[string]interface{}) { log.Printf("UI notifier forwarding to browser: %+v", msg) if err := conn.WriteJSON(msg); err != nil { log.Printf("UI notify error: %v", err) } else { log.Printf("UI notifier sent successfully") } }) defer unregisterUINotifier(notifierID) for { var msg map[string]interface{} if err := conn.ReadJSON(&msg); err != nil { log.Printf("UI client disconnected: %v", err) break } action, _ := msg["action"].(string) log.Printf("UI received action: %s", action) switch action { case "check": act, err := loadActivation(dataDir) if err == nil && act.Activated { conn.WriteJSON(map[string]interface{}{"action": "activated", "studentName": act.StudentName}) } else { conn.WriteJSON(map[string]interface{}{"action": "not_activated"}) } case "activate": code, _ := msg["code"].(string) log.Printf("UI handling activate with code: %s", code) if err := sendMessage(WSMessage{Action: "activate", NodeID: nodeID, Code: code}); err != nil { log.Printf("UI sendMessage failed: %v", err) conn.WriteJSON(map[string]interface{}{"action": "activation_failed", "error": err.Error()}) } else { log.Printf("UI sendMessage succeeded, waiting for server response...") } case "instances": listInstances(dataDir, conn) } } }) port := "7070" log.Printf("UI starting on http://localhost:%s", port) if err := http.ListenAndServe("127.0.0.1:"+port, nil); err != nil { log.Fatalf("UI server error: %v", err) } } func listInstances(dataDir string, conn *websocket.Conn) { instances, err := loadInstances(dataDir) if err != nil { log.Printf("loadInstances error: %v", err) conn.WriteJSON(map[string]interface{}{"action": "instances_list", "instances": []interface{}{}}) return } var list []map[string]interface{} for _, inst := range instances { status := getInstanceStatus(dataDir, inst.ID) if status != inst.Status { inst.Status = status _ = upsertInstance(dataDir, inst) } list = append(list, map[string]interface{}{ "id": inst.ID, "templateName": inst.TemplateName, "port": inst.Port, "status": inst.Status, "url": instanceURL(inst), }) } conn.WriteJSON(map[string]interface{}{"action": "instances_list", "instances": list}) }