package proxmoxapi import ( "io" "net/http" "github.com/gin-gonic/gin" "github.com/yusing/godoxy/internal/proxmox" "github.com/yusing/goutils/apitypes" "github.com/yusing/goutils/http/httpheaders" "github.com/yusing/goutils/http/websocket" ) type StatsRequest struct { Node string `uri:"node" binding:"required"` VMID int `uri:"vmid" binding:"required"` } // @x-id "nodeStats" // @BasePath /api/v1 // @Summary Get proxmox node stats // @Description Get proxmox node stats in json // @Tags proxmox,websocket // @Produce application/json // @Param node path string true "Node name" // @Success 200 {object} proxmox.NodeStats "Stats output" // @Failure 400 {object} apitypes.ErrorResponse "Invalid request" // @Failure 403 {object} apitypes.ErrorResponse "Unauthorized" // @Failure 404 {object} apitypes.ErrorResponse "Node not found" // @Failure 500 {object} apitypes.ErrorResponse "Internal server error" // @Router /proxmox/stats/{node} [get] func NodeStats(c *gin.Context) { nodeName := c.Param("node") if nodeName == "" { c.JSON(http.StatusBadRequest, apitypes.Error("node name is required")) return } node, ok := proxmox.Nodes.Get(nodeName) if !ok { c.JSON(http.StatusNotFound, apitypes.Error("node not found")) return } isWs := httpheaders.IsWebsocket(c.Request.Header) reader, err := node.NodeStats(c.Request.Context(), isWs) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to get stats")) return } defer reader.Close() if !isWs { var line [512]byte n, err := reader.Read(line[:]) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to copy stats")) return } c.Data(http.StatusOK, "application/json", line[:n]) return } manager, err := websocket.NewManagerWithUpgrade(c) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to upgrade to websocket")) return } defer manager.Close() writer := manager.NewWriter(websocket.TextMessage) _, err = io.Copy(writer, reader) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to copy stats")) return } } // @x-id "vmStats" // @BasePath /api/v1 // @Summary Get proxmox VM stats // @Description Get proxmox VM stats in format of "STATUS|CPU%%|MEM USAGE/LIMIT|MEM%%|NET I/O|BLOCK I/O" // @Tags proxmox,websocket // @Produce text/plain // @Param path path StatsRequest true "Request" // @Success 200 string plain "Stats output" // @Failure 400 {object} apitypes.ErrorResponse "Invalid request" // @Failure 403 {object} apitypes.ErrorResponse "Unauthorized" // @Failure 404 {object} apitypes.ErrorResponse "Node not found" // @Failure 500 {object} apitypes.ErrorResponse "Internal server error" // @Router /proxmox/stats/{node}/{vmid} [get] func VMStats(c *gin.Context) { var request StatsRequest if err := c.ShouldBindUri(&request); err != nil { c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err)) return } node, ok := proxmox.Nodes.Get(request.Node) if !ok { c.JSON(http.StatusNotFound, apitypes.Error("node not found")) return } isWs := httpheaders.IsWebsocket(c.Request.Header) reader, err := node.LXCStats(c.Request.Context(), request.VMID, isWs) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to get stats")) return } defer reader.Close() if !isWs { var line [128]byte n, err := reader.Read(line[:]) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to copy stats")) return } c.Data(http.StatusOK, "text/plain; charset=utf-8", line[:n]) return } manager, err := websocket.NewManagerWithUpgrade(c) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to upgrade to websocket")) return } defer manager.Close() writer := manager.NewWriter(websocket.TextMessage) _, err = io.Copy(writer, reader) if err != nil { c.Error(apitypes.InternalServerError(err, "failed to copy stats")) return } }