Initial commit: EduBox V2 platform
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WSMessage struct {
|
||||
Action string `json:"action"`
|
||||
NodeID string `json:"nodeId,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
InstanceID string `json:"instanceId,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
ComposeConfig string `json:"composeConfig,omitempty"`
|
||||
StudentName string `json:"studentName,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func startWebSocket(serverAddr, nodeID, dataDir string) {
|
||||
for {
|
||||
conn, _, err := websocket.DefaultDialer.Dial(serverAddr, nil)
|
||||
if err != nil {
|
||||
log.Printf("WS connect error: %v, retrying in 5s...", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("WS connected to %s", serverAddr)
|
||||
|
||||
// Register
|
||||
if err := conn.WriteJSON(WSMessage{Action: "register", NodeID: nodeID}); err != nil {
|
||||
log.Printf("WS register error: %v", err)
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
// Activation flow
|
||||
act, err := loadActivation(dataDir)
|
||||
if err != nil || !act.Activated {
|
||||
log.Println("Waiting for activation...")
|
||||
} else {
|
||||
log.Printf("Already activated as %s", act.StudentName)
|
||||
}
|
||||
|
||||
// Heartbeat goroutine
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := conn.WriteJSON(WSMessage{Action: "heartbeat", NodeID: nodeID}); err != nil {
|
||||
return
|
||||
}
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Read loop
|
||||
for {
|
||||
var msg WSMessage
|
||||
if err := conn.ReadJSON(&msg); err != nil {
|
||||
log.Printf("WS read error: %v", err)
|
||||
break
|
||||
}
|
||||
handleMessage(conn, msg, dataDir, nodeID)
|
||||
}
|
||||
|
||||
close(done)
|
||||
conn.Close()
|
||||
log.Println("WS disconnected, reconnecting in 5s...")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID string) {
|
||||
switch msg.Action {
|
||||
case "activate":
|
||||
// handled by UI, but server can also push activation response
|
||||
if msg.StudentName != "" {
|
||||
act := &Activation{Activated: true, StudentName: msg.StudentName, Code: msg.Code}
|
||||
saveActivation(dataDir, act)
|
||||
log.Printf("Activated as %s", msg.StudentName)
|
||||
}
|
||||
case "start":
|
||||
log.Printf("Start instance %s on port %d", msg.InstanceID, msg.Port)
|
||||
if err := writeCompose(dataDir, msg.InstanceID, msg.ComposeConfig); err != nil {
|
||||
log.Printf("writeCompose error: %v", err)
|
||||
conn.WriteJSON(WSMessage{Action: "instance_error", InstanceID: msg.InstanceID, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
if err := dockerComposeUp(dataDir, msg.InstanceID); err != nil {
|
||||
log.Printf("dockerComposeUp error: %v", err)
|
||||
conn.WriteJSON(WSMessage{Action: "instance_error", InstanceID: msg.InstanceID, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
conn.WriteJSON(WSMessage{Action: "instance_started", InstanceID: msg.InstanceID, Port: msg.Port})
|
||||
case "stop":
|
||||
log.Printf("Stop instance %s", msg.InstanceID)
|
||||
if err := dockerComposeDown(dataDir, msg.InstanceID); err != nil {
|
||||
log.Printf("dockerComposeDown error: %v", err)
|
||||
}
|
||||
case "reset":
|
||||
log.Printf("Reset instance %s", msg.InstanceID)
|
||||
dockerComposeRm(dataDir, msg.InstanceID)
|
||||
if err := writeCompose(dataDir, msg.InstanceID, msg.ComposeConfig); err != nil {
|
||||
log.Printf("writeCompose error: %v", err)
|
||||
return
|
||||
}
|
||||
if err := dockerComposeUp(dataDir, msg.InstanceID); err != nil {
|
||||
log.Printf("dockerComposeUp error: %v", err)
|
||||
conn.WriteJSON(WSMessage{Action: "instance_error", InstanceID: msg.InstanceID, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
conn.WriteJSON(WSMessage{Action: "instance_started", InstanceID: msg.InstanceID, Port: msg.Port})
|
||||
default:
|
||||
log.Printf("Unknown action: %s", msg.Action)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user