From c2620720d2a499a1beff48a646e120b010abcd8d Mon Sep 17 00:00:00 2001
From: MIST <52695829+MISTLXC@users.noreply.github.com>
Date: Wed, 24 Jun 2026 17:38:08 +0800
Subject: [PATCH] feat(operational): one-click operational data export with
9-sheet Excel (#411)
Add an async export feature on the Dashboard page -- global admins can
generate and download a multi-sheet operational data report (.xlsx
packaged as .zip). The export covers 9 sheets:
1. Overview - interval KPIs, system snapshot, 7-day trend, period comparison,
model details (configured providers only), agent activity ranking top 10
2. Token Usage - daily breakdown by runtime_provider with avg tokens/msg
3. Skill Stats - skill list with usage count, last-call time, bound agents
4. User Stats - per-(workspace, user) aggregated tokens, duration, last active
5. User Conversations - detail rows pairing user-asst messages
6. Security and Audit - unified view across 6 sources (guard rules, audit logs,
approvals, grants, config, business audit events)
7. Channel Stats - per-channel conversation count, tokens, unique users
8. Model Config - enabled plus API-key-configured models with parameters
9. Cron Jobs - execution records with duration and token usage
Backend highlights:
- generate/progress/download endpoints guarded by PreAuthorize hasRole ADMIN
- single AtomicBoolean lock (409 when busy), 90-day frontend cap, 5-min deadline
- metadata-based tool-call counting, deleted=0 filtering everywhere
- value label mapping (chat to dialogue, TRUE to enabled, etc.)
- one-time downloadToken, file auto-cleanup after 24h or download
Frontend highlights:
- SVG ring progress bar with smooth dashoffset transition plus slow rotation
- visibility gated by workspaceStore.isGlobalAdmin (v-if on button)
- 1-second polling driving progress state machine (idle/generating/done)
- Element Plus date-picker (30-day default, 90-day max)
---
.../controller/OperationalDataController.java | 112 ++
.../model/ExportInProgressException.java | 10 +
.../mate/operational/model/ExportTask.java | 70 +
.../service/OperationalDataExportService.java | 1301 +++++++++++++++++
mateclaw-ui/src/api/index.ts | 22 +
mateclaw-ui/src/i18n/locales/en-US.ts | 14 +-
mateclaw-ui/src/i18n/locales/zh-CN.ts | 14 +-
mateclaw-ui/src/views/Dashboard.vue | 353 ++++-
8 files changed, 1846 insertions(+), 50 deletions(-)
create mode 100644 mateclaw-server/src/main/java/vip/mate/operational/controller/OperationalDataController.java
create mode 100644 mateclaw-server/src/main/java/vip/mate/operational/model/ExportInProgressException.java
create mode 100644 mateclaw-server/src/main/java/vip/mate/operational/model/ExportTask.java
create mode 100644 mateclaw-server/src/main/java/vip/mate/operational/service/OperationalDataExportService.java
diff --git a/mateclaw-server/src/main/java/vip/mate/operational/controller/OperationalDataController.java b/mateclaw-server/src/main/java/vip/mate/operational/controller/OperationalDataController.java
new file mode 100644
index 00000000..2d65a058
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/operational/controller/OperationalDataController.java
@@ -0,0 +1,112 @@
+package vip.mate.operational.controller;
+
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.core.io.Resource;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import vip.mate.operational.model.ExportTask;
+import vip.mate.operational.service.OperationalDataExportService;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.time.LocalDate;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 运营数据导出 Controller — 仅全局管理员可访问。
+ *
+ * 不暴露标准 REST API 端点,下载通过一次性 token 保护。
+ */
+@RestController
+@RequestMapping("/api/v1/operational-data")
+public class OperationalDataController {
+
+ private final OperationalDataExportService exportService;
+
+ public OperationalDataController(OperationalDataExportService exportService) {
+ this.exportService = exportService;
+ }
+
+ @PostMapping("/generate")
+ @PreAuthorize("hasRole('ADMIN')")
+ public ResponseEntity