All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 41s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 1m31s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 1m38s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m27s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 2m0s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 1m42s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 1m13s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m48s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 2m10s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 1m12s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m51s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m29s
- 将侧边栏菜单改为顶部标签页导航设计 - 添加客户端操作系统和架构信息显示 - 实现客户端自动更新检查和应用功能 - 添加底部页脚显示版本和GitHub链接 - 更新主题颜色为紫色渐变风格 - 优化首页和插件页面的UI布局结构 - 修改路由配置将更新页面重命名为设置页面 - 在认证协议中添加客户端平台信息字段 - 重构App.vue中的导航和状态管理逻辑
97 lines
2.3 KiB
Go
97 lines
2.3 KiB
Go
package handler
|
|
|
|
import (
|
|
"io"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// LogHandler 日志处理器
|
|
type LogHandler struct {
|
|
app AppInterface
|
|
}
|
|
|
|
// NewLogHandler 创建日志处理器
|
|
func NewLogHandler(app AppInterface) *LogHandler {
|
|
return &LogHandler{app: app}
|
|
}
|
|
|
|
// StreamLogs 流式传输客户端日志
|
|
// @Summary 流式传输客户端日志
|
|
// @Description 通过 Server-Sent Events 实时接收客户端日志
|
|
// @Tags Logs
|
|
// @Produce text/event-stream
|
|
// @Security Bearer
|
|
// @Param id path string true "客户端 ID"
|
|
// @Param lines query int false "初始日志行数" default(100)
|
|
// @Param follow query bool false "是否持续推送新日志" default(true)
|
|
// @Param level query string false "日志级别过滤 (info, warn, error)"
|
|
// @Success 200 {object} protocol.LogEntry
|
|
// @Router /api/client/{id}/logs [get]
|
|
func (h *LogHandler) StreamLogs(c *gin.Context) {
|
|
clientID := c.Param("id")
|
|
|
|
// 检查客户端是否在线
|
|
online, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
|
if !online {
|
|
c.JSON(400, gin.H{"code": 400, "message": "client not online"})
|
|
return
|
|
}
|
|
|
|
// 解析查询参数
|
|
lines := 100
|
|
if v := c.Query("lines"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
lines = n
|
|
}
|
|
}
|
|
|
|
follow := true
|
|
if v := c.Query("follow"); v == "false" {
|
|
follow = false
|
|
}
|
|
|
|
level := c.Query("level")
|
|
|
|
// 生成会话 ID
|
|
sessionID := uuid.New().String()
|
|
|
|
// 启动日志流
|
|
logCh, err := h.app.GetServer().StartClientLogStream(clientID, sessionID, lines, follow, level)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"code": 500, "message": err.Error()})
|
|
return
|
|
}
|
|
|
|
// 设置 SSE 响应头
|
|
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
|
c.Writer.Header().Set("Cache-Control", "no-cache")
|
|
c.Writer.Header().Set("Connection", "keep-alive")
|
|
c.Writer.Header().Set("X-Accel-Buffering", "no")
|
|
|
|
// 获取客户端断开信号
|
|
clientGone := c.Request.Context().Done()
|
|
|
|
// 流式传输日志
|
|
c.Stream(func(w io.Writer) bool {
|
|
select {
|
|
case <-clientGone:
|
|
h.app.GetServer().StopClientLogStream(sessionID)
|
|
return false
|
|
case entry, ok := <-logCh:
|
|
if !ok {
|
|
return false
|
|
}
|
|
c.SSEvent("log", entry)
|
|
return true
|
|
case <-time.After(30 * time.Second):
|
|
// 发送心跳
|
|
c.SSEvent("heartbeat", gin.H{"ts": time.Now().UnixMilli()})
|
|
return true
|
|
}
|
|
})
|
|
}
|