mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-26 02:51:07 +01:00
Add required websocket check at the beginning of both journalctl and tail endpoint handlers to ensure these endpoints only accept websocket connections.
84 lines
2.7 KiB
Go
84 lines
2.7 KiB
Go
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"
|
|
)
|
|
|
|
// e.g. ws://localhost:8889/api/v1/proxmox/tail?node=pve&vmid=127&file=/var/log/immich/web.log&file=/var/log/immich/ml.log&limit=10
|
|
|
|
type TailRequest struct {
|
|
Node string `form:"node" binding:"required"` // Node name
|
|
VMID *int `form:"vmid"` // Container VMID (optional - if not provided, streams node journalctl)
|
|
Files []string `form:"file" binding:"required,dive,filepath"` // File paths
|
|
Limit int `form:"limit" default:"100" binding:"min=1,max=1000"` // Limit output lines (1-1000)
|
|
} // @name ProxmoxTailRequest
|
|
|
|
// @x-id "tail"
|
|
// @BasePath /api/v1
|
|
// @Summary Get tail output
|
|
// @Description Get tail output for node or LXC container. If vmid is not provided, streams node tail.
|
|
// @Tags proxmox,websocket
|
|
// @Accept json
|
|
// @Produce application/json
|
|
// @Param query query TailRequest true "Request"
|
|
// @Success 200 string plain "Tail 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/tail [get]
|
|
func Tail(c *gin.Context) {
|
|
if !httpheaders.IsWebsocket(c.Request.Header) {
|
|
c.JSON(http.StatusBadRequest, apitypes.Error("websocket required"))
|
|
return
|
|
}
|
|
|
|
var request TailRequest
|
|
if err := c.ShouldBindQuery(&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
|
|
}
|
|
|
|
c.Status(http.StatusContinue)
|
|
|
|
var reader io.ReadCloser
|
|
var err error
|
|
if request.VMID == nil {
|
|
reader, err = node.NodeTail(c.Request.Context(), request.Files, request.Limit)
|
|
} else {
|
|
reader, err = node.LXCTail(c.Request.Context(), *request.VMID, request.Files, request.Limit)
|
|
}
|
|
if err != nil {
|
|
c.Error(apitypes.InternalServerError(err, "failed to get journalctl output"))
|
|
return
|
|
}
|
|
defer reader.Close()
|
|
|
|
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 journalctl output"))
|
|
return
|
|
}
|
|
}
|