feat: worker 日志 CSV 导出 + 集群日志导出菜单完善

- handleAuditWorkerLogsCsv: 全环 fan-out 收集每节点 frpc 日志,
  逐行展开为 CSV (node/worker_key/remote/worker_state/log_line),
  Excel 可直接过滤/pivot 审计
- gatherClusterWorkerLogs: 提取 JSON 与 CSV 共用的日志聚合逻辑
- 路由: GET /audit/worker-logs.csv (read 级)
- ClusterView: 导出 worker 日志改下拉菜单 (JSON/CSV 两格式)
- api.ts: downloadAuditCsv 支持 worker-logs 类型
- gofmt: handlers_audit.go 格式对齐
This commit is contained in:
JianFeeeee
2026-08-24 23:09:00 +08:00
parent ef98d9dca1
commit 2eaa26ba95
8 changed files with 102 additions and 40 deletions

View File

@ -251,7 +251,7 @@ export const api = {
// downloadAuditCsv streams one of the /audit/*.csv endpoints to a file.
export async function downloadAuditCsv(
kind: "users" | "apikeys" | "cluster-log",
kind: "users" | "apikeys" | "cluster-log" | "worker-logs",
): Promise<void> {
const resp = await fetch(`/api/manager/audit/${kind}.csv`, {
credentials: "same-origin",

View File

@ -154,9 +154,15 @@
</el-dropdown-menu>
</template>
</el-dropdown>
<button class="w4f-btn ghost small" :disabled="workerLogBusy" @click="exportWorkerLogs">
导出 worker 日志
</button>
<el-dropdown trigger="click" :disabled="workerLogBusy" @command="exportWorkerLogs">
<button class="w4f-btn ghost small" :disabled="workerLogBusy"> 导出 worker 日志</button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="json">JSON完整结构</el-dropdown-item>
<el-dropdown-item command="csv">CSV审计用逐行展开</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</h3>
<div class="log-list">
@ -365,9 +371,15 @@ const exportLog = async (fmt: string) => {
// logs (the actual forwards running on each node) into one downloadable JSON.
// Read-level: auditors use this to inspect worker output cluster-wide.
const workerLogBusy = ref(false)
const exportWorkerLogs = async () => {
const exportWorkerLogs = async (fmt = 'json') => {
workerLogBusy.value = true
try {
if (fmt === 'csv') {
// Server-side CSV: ring-wide fan-out, one row per log line.
await downloadAuditCsv('worker-logs')
ElMessage.success('已导出 worker 日志 CSV')
return
}
const bundle = await api.exportWorkerLogs()
const blob = new Blob([JSON.stringify(bundle, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)