feat(access-logs): 日志明细支持按状态码筛选并折叠次要搜索项,修复首页来源分布无数据

- 修复 PostgreSQL/SQLite 日志库下首页「来源分布」卡片无数据:RegionCounts 对空
  节点 ID 误拼 node_id = '' 恒空条件,改为空节点 ID 表示全节点聚合(对齐 CH 语义),
  并过滤空白归属地
- /access-logs?tab=list 新增状态码筛选:状态码下拉含常用 2xx/3xx/4xx/5xx 选项,
  校验 100-599,非法值返回 400;ClickHouse 与 PostgreSQL/SQLite 日志库均支持
- 搜索框折叠:默认仅显示来源 IP 与状态码,节点 ID/访问域名/请求路径折叠进
  「更多筛选」
This commit is contained in:
ryan
2026-08-13 09:59:32 +08:00
parent f1577bf092
commit 75d1fcf345
18 changed files with 322 additions and 75 deletions
@@ -4,12 +4,12 @@
package observability
import (
"net/http"
"strconv"
"errors"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/apiutil"
"github.com/Rain-kl/Wavelet/internal/shared/response"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
// GetAccessLogOverviewHandler 获取访问日志概览。
@@ -45,7 +45,7 @@ func GetAccessLogOverviewHandler(c *gin.Context) {
// GetAccessLogsHandler 分页列出访问日志。
// @Summary 列出访问日志
// @Description 分页返回 OpenFlare 访问日志,支持按节点、IP、主机与路径筛选,需要管理员权限
// @Description 分页返回 OpenFlare 访问日志,支持按节点、IP、主机、路径与状态码筛选,需要管理员权限
// @Tags openflare-observability
// @Produce json
// @Security SessionCookie
@@ -53,6 +53,7 @@ func GetAccessLogOverviewHandler(c *gin.Context) {
// @Param remote_addr query string false "客户端 IP"
// @Param host query string false "请求 Host"
// @Param path query string false "请求路径"
// @Param status_code query int false "HTTP 状态码(100-599)"
// @Param p query int false "页码"
// @Param page_size query int false "每页条数"
// @Param sort_by query string false "排序字段"
@@ -64,7 +65,11 @@ func GetAccessLogOverviewHandler(c *gin.Context) {
// @Failure 500 {object} response.Any "内部错误"
// @Router /api/v1/d/access-logs [get]
func GetAccessLogsHandler(c *gin.Context) {
logs, err := ListAccessLogs(c.Request.Context(), readAccessLogQuery(c))
query, err := readAccessLogQuery(c)
if apiutil.AbortBadRequestOnError(c, err) {
return
}
logs, err := ListAccessLogs(c.Request.Context(), query)
if apiutil.AbortBadRequestOnError(c, err) {
return
}
@@ -93,7 +98,10 @@ func GetAccessLogsHandler(c *gin.Context) {
// @Failure 500 {object} response.Any "内部错误"
// @Router /api/v1/d/access-logs/folds [get]
func GetFoldedAccessLogsHandler(c *gin.Context) {
query := readAccessLogQuery(c)
query, err := readAccessLogQuery(c)
if apiutil.AbortBadRequestOnError(c, err) {
return
}
query.FoldMinutes = readQueryInt(c, "fold_minutes")
logs, err := ListFoldedAccessLogs(c.Request.Context(), query)
if apiutil.AbortBadRequestOnError(c, err) {
@@ -270,8 +278,8 @@ func CleanupAccessLogsHandler(c *gin.Context) {
c.JSON(http.StatusOK, response.OK(result))
}
func readAccessLogQuery(c *gin.Context) AccessLogQuery {
return AccessLogQuery{
func readAccessLogQuery(c *gin.Context) (AccessLogQuery, error) {
query := AccessLogQuery{
NodeID: c.Query("node_id"),
RemoteAddr: c.Query("remote_addr"),
Host: c.Query("host"),
@@ -281,6 +289,14 @@ func readAccessLogQuery(c *gin.Context) AccessLogQuery {
SortBy: c.Query("sort_by"),
SortOrder: c.Query("sort_order"),
}
if raw := c.Query("status_code"); raw != "" {
code, err := strconv.Atoi(raw)
if err != nil || code < 100 || code > 599 {
return AccessLogQuery{}, errors.New(errInvalidStatusCode)
}
query.StatusCode = code
}
return query, nil
}
func readQueryInt(c *gin.Context, key string) int {