✨ feat(status): 新增请求日志并重设计状态页
- 新增 server.request_log 配置:记录业务请求元数据(IP/方法/路径/状态/耗时, key 自动脱敏)与 4xx/5xx 错误摘要(前 4KB),按保留天数自动清理 - 移除 /self-info 与 /vacuum 接口及相关文档 - 状态页重设计:毛玻璃卡片+吸顶玻璃栏、服务状态条、KPI 卡与请求分类 条形图、可折叠 API 文档分组、行内测试面板;深浅色分别调校, 含 reduced-motion / reduced-transparency / 无 backdrop-filter 兜底
This commit is contained in:
1 parent
bf49805bb6
commit
bfb4290c15
11 files changed
+1060
-838
No files matched your search
+25
-30
@@ -2,7 +2,6 @@ package handlers
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fanqie/internal/algos"
|
||||
"fanqie/internal/config"
|
||||
"fanqie/internal/fqapi"
|
||||
@@ -12,35 +11,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// HandleVacuum 处理数据库压缩请求
|
||||
func HandleVacuum(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
// 只允许 POST 请求
|
||||
if r.Method != http.MethodPost {
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "仅支持 POST 请求",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 执行 VACUUM
|
||||
err := fqapi.VacuumDatabase()
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": false,
|
||||
"message": "压缩失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "数据库压缩完成",
|
||||
})
|
||||
}
|
||||
|
||||
// 状态页模板(embed 内嵌,动态数据由 html/template 自动转义)
|
||||
//
|
||||
//go:embed templates/status.html
|
||||
@@ -86,12 +56,28 @@ func HandleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取设备密钥池状态
|
||||
poolStatus := getDeviceKeyPoolStatus()
|
||||
|
||||
// 获取最近请求(日志未启用时为空)
|
||||
var recent []map[string]interface{}
|
||||
for _, e := range fqapi.GetRecentRequests(20) {
|
||||
recent = append(recent, map[string]interface{}{
|
||||
"Time": time.Unix(e.Ts, 0).Format("15:04:05"),
|
||||
"IP": e.IP,
|
||||
"Method": e.Method,
|
||||
"Path": e.Path,
|
||||
"Query": truncateStr(e.Query, 80),
|
||||
"Status": e.Status,
|
||||
"IsErr": e.Status >= 400,
|
||||
"Duration": fmt.Sprintf("%dms", e.DurationMs),
|
||||
})
|
||||
}
|
||||
|
||||
statusTmpl.Execute(w, map[string]interface{}{
|
||||
"NowTime": time.Now().Format("2006-01-02 15:04:05"),
|
||||
"Uptime": uptimeStr,
|
||||
"StartTime": startTime.Format("2006-01-02 15:04:05"),
|
||||
"Signature": signatureDetails,
|
||||
"DBTotal": getDBTotalSize(),
|
||||
"DBUsedPct": getDBUsagePercentage(),
|
||||
"DBUsed": getDBUsedSize(),
|
||||
"DBFree": getDBFreeSize(),
|
||||
"PoolStatus": poolStatus["status"],
|
||||
@@ -112,9 +98,18 @@ func HandleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
"CacheStatus": getCacheStatus(),
|
||||
"CleanupStatus": getCleanupStatus(),
|
||||
"CleanupInterval": getCleanupInterval(),
|
||||
"RecentRequests": recent,
|
||||
})
|
||||
}
|
||||
|
||||
// truncateStr 截断字符串,超长加省略号
|
||||
func truncateStr(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n] + "…"
|
||||
}
|
||||
|
||||
// getCacheStatus 获取缓存状态
|
||||
func getCacheStatus() string {
|
||||
cfg := config.GetConfig()
|
||||
|
||||
Reference in new issue
Block a user