package handlers import ( "encoding/json" "log" "net/http" "sync" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "gorm.io/gorm" "ezops/internal/models" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true // allow all origins in dev }, ReadBufferSize: 1024, WriteBufferSize: 1024, } // WSMessage WebSocket消息结构 type WSMessage struct { Status int `json:"status"` // 0=data, 1=resize Data string `json:"data"` Cols int `json:"cols"` Rows int `json:"rows"` } // WebShellHandler WebShell处理器 type WebShellHandler struct { db *gorm.DB clients sync.Map // serverID -> []*websocket.Conn } // NewWebShellHandler 创建WebShell处理器 func NewWebShellHandler(db *gorm.DB) *WebShellHandler { return &WebShellHandler{db: db} } // Terminal WebSocket终端连接 // @Summary WebShell终端 // @Description 通过WebSocket连接到服务器的SSH终端 // @Tags WebShell // @Param id query int true "服务器ID" // @Param cols query int false "终端列数" // @Param rows query int false "终端行数" // @Router /ws/terminal [get] func (h *WebShellHandler) Terminal(c *gin.Context) { serverID := c.Query("id") if serverID == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "missing server id"}) return } // Verify server exists var server models.Server if err := h.db.First(&server, serverID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "server not found"}) return } // Upgrade to WebSocket conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { log.Printf("[WebShell] upgrade error: %v", err) return } defer conn.Close() log.Printf("[WebShell] connected to server %s (%s:%d)", server.Hostname, server.IP, server.Port) // Send welcome message welcome := WSMessage{ Status: 0, Data: "\r\n\x1b[32mEZOps WebShell\x1b[0m - Connected to " + server.Hostname + " (" + server.IP + ")\r\n", } conn.WriteJSON(welcome) // Demo mode: echo input back with a prompt // In production this would connect to actual SSH via crypto/ssh or a PTY bridge for { msgType, data, err := conn.ReadMessage() if err != nil { log.Printf("[WebShell] read error: %v", err) break } if msgType == websocket.TextMessage { var msg WSMessage if err := json.Unmarshal(data, &msg); err != nil { continue } switch msg.Status { case 0: // data // Process command input resp := WSMessage{Status: 0} switch msg.Data { case "\r", "\n": resp.Data = "\r\n[" + server.Hostname + " ~]$ " case "\u0003": // Ctrl+C resp.Data = "^C\r\n[" + server.Hostname + " ~]$ " case "\u0004": // Ctrl+D resp.Data = "\r\n\x1b[33mConnection closed.\x1b[0m\r\n" conn.WriteJSON(resp) goto done default: resp.Data = msg.Data // echo back } conn.WriteJSON(resp) case 1: // resize log.Printf("[WebShell] resize to cols=%d rows=%d", msg.Cols, msg.Rows) } } } done: log.Printf("[WebShell] disconnected from server %s", server.Hostname) }