mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-21 17:37:56 +00:00
feat: 审计 CSV 导出 — 用户/API Key/集群操作日志一键下载
新增端点 (read 级, 审计 viewer 角色即可导出): - GET /audit/users.csv: 账号清单 + 最后登录时间 - GET /audit/apikeys.csv: 密钥清单 (前缀+scope+最后使用+过期) - GET /audit/cluster-log.csv: 令牌环操作日志时间线 (seq/time_utc/node/kind/detail/data_json 六列, detail 为人读摘要, data_json 保留无损原始载荷) 安全设计: - RFC4180 转义 (引号/逗号/换行) - 公式注入防御: =/+/@/tab/- 开头单元格加 ' 前缀 - ISO8601 UTC 时间戳, Excel 直接排序 - 明文密钥不可逆, 仅导出展示前缀 前端: - UsersView: 账号表/API 密钥表各加「⤓ 导出 CSV」按钮 - ClusterView: 日志导出下拉新增 CSV 选项 (走服务端生成) - api.ts: downloadAuditCsv() 统一下载管道 测试: csvEscape 全用例 / users+apikeys CSV 内容断言 / 未认证 401
This commit is contained in:
@ -117,3 +117,45 @@ func (l *ClusterLog) Snapshot() []LogEntry {
|
||||
copy(out, l.Log)
|
||||
return out
|
||||
}
|
||||
|
||||
// DetailOf produces a human-readable one-line summary of a log entry's
|
||||
// payload, matching the frontend's detailOf formatting. Used by the audit CSV
|
||||
// export and anywhere a flat text rendering of an entry is needed.
|
||||
func DetailOf(e LogEntry) string {
|
||||
if len(e.Data) == 0 {
|
||||
return ""
|
||||
}
|
||||
var d map[string]any
|
||||
if err := json.Unmarshal(e.Data, &d); err != nil {
|
||||
return string(e.Data)
|
||||
}
|
||||
str := func(key string) string { s, _ := d[key].(string); return s }
|
||||
switch e.Kind {
|
||||
case LogForwardAdd, LogForwardRemove:
|
||||
s := str("local") + " → " + str("remote")
|
||||
if id := str("taskId"); id != "" {
|
||||
if len(id) > 8 {
|
||||
id = id[len(id)-8:]
|
||||
}
|
||||
s += " · " + id
|
||||
}
|
||||
return s
|
||||
case LogNodeJoin:
|
||||
if addr := str("addr"); addr != "" {
|
||||
return str("node") + " @ " + addr
|
||||
}
|
||||
return str("node")
|
||||
case LogNodeLeave:
|
||||
return str("node")
|
||||
case LogLeaderChange:
|
||||
return "→ " + str("leader")
|
||||
case LogTaskClaimed:
|
||||
return fmt.Sprintf("%s→%s:%v", str("local"), str("remote"), d["port"])
|
||||
default:
|
||||
if len(d) > 0 {
|
||||
b, _ := json.Marshal(d)
|
||||
return string(b)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user