mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
134 lines
3.5 KiB
Go
134 lines
3.5 KiB
Go
/*
|
|
Copyright 2025-2026 linux.do
|
|
Modified by Arctel.net, 2026
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package logs
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/apps/admin"
|
|
"github.com/Rain-kl/Wavelet/internal/logger"
|
|
"github.com/Rain-kl/Wavelet/internal/util"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
const defaultLimit = 200
|
|
|
|
// logsResponse 历史日志查询响应
|
|
type logsResponse struct {
|
|
Lines []logger.LogEntry `json:"lines"`
|
|
HasMore bool `json:"has_more"`
|
|
NextCursor int `json:"next_cursor"` // 用于加载更早日志的 cursor
|
|
}
|
|
|
|
// GetLogs 获取历史日志
|
|
// @Summary 获取系统日志
|
|
// @Description 分页获取系统历史日志,cursor=0 获取最新日志,cursor>0 获取更早日志
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security SessionCookie
|
|
// @Param cursor query int false "日志游标,0=获取最新" default(0)
|
|
// @Param limit query int false "每页条数" default(200)
|
|
// @Success 200 {object} util.ResponseAny{data=logs.logsResponse} "日志列表"
|
|
// @Failure 401 {object} util.ResponseAny "未登录"
|
|
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
|
// @Router /api/v1/admin/logs [get]
|
|
func GetLogs(c *gin.Context) {
|
|
cursorStr := c.DefaultQuery("cursor", "0")
|
|
limitStr := c.DefaultQuery("limit", "200")
|
|
|
|
var cursor, limit int
|
|
if _, err := parsePositiveInt(cursorStr, &cursor); err != nil {
|
|
c.JSON(http.StatusBadRequest, util.Err(admin.InvalidCursorParam))
|
|
return
|
|
}
|
|
if _, err := parsePositiveInt(limitStr, &limit); err != nil || limit <= 0 {
|
|
limit = defaultLimit
|
|
}
|
|
if limit > 500 {
|
|
limit = 500
|
|
}
|
|
|
|
entries, hasMore := logger.GlobalRingBuffer.Query(cursor, limit)
|
|
|
|
resp := logsResponse{
|
|
Lines: entries,
|
|
HasMore: hasMore,
|
|
}
|
|
if len(entries) > 0 {
|
|
resp.NextCursor = entries[0].Index
|
|
}
|
|
|
|
c.JSON(http.StatusOK, util.OK(resp))
|
|
}
|
|
|
|
// wsMessage WebSocket 消息格式
|
|
type wsMessage struct {
|
|
Type string `json:"type"` // "log" | "error"
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
|
|
// HandleLogWebSocket WebSocket 端点,实时推送系统日志
|
|
// @Summary 系统日志实时推送
|
|
// @Description 通过 WebSocket 实时推送系统日志,需要管理员权限
|
|
// @Tags admin
|
|
// @Router /api/v1/admin/logs/ws [get]
|
|
func HandleLogWebSocket(c *gin.Context) {
|
|
upgrader := getUpgrader()
|
|
|
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
// 订阅 ring buffer
|
|
ch := logger.GlobalRingBuffer.Subscribe()
|
|
defer logger.GlobalRingBuffer.Unsubscribe(ch)
|
|
|
|
// 在独立 goroutine 中读取客户端消息(保持连接活跃 + 检测断开)
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
for {
|
|
_, _, err := conn.ReadMessage()
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// 主循环:推送日志
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case entry, ok := <-ch:
|
|
if !ok {
|
|
return
|
|
}
|
|
data, _ := json.Marshal(entry)
|
|
msg := wsMessage{Type: "log", Data: data}
|
|
payload, _ := json.Marshal(msg)
|
|
if err := conn.WriteMessage(1, payload); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|