From 091fb97291152fc8bdea37f77e1b66d3ce844416 Mon Sep 17 00:00:00 2001 From: Nexus Agent Date: Mon, 8 Jun 2026 23:28:26 +0800 Subject: [PATCH] Push async submit, Chinese 422 field labels, hide server CSV export. Return sync/files immediately with background runner; show loc_zh in validation errors; remove servers toolbar CSV export button. --- .../2026-06-08-push-validation-servers-ui.md | 41 +++++++++++++ .../2026-06-08-push-async-background.md | 31 ++++++++++ .../2026-06-08-servers-hide-csv-export.md | 17 ++++++ .../2026-06-08-validation-error-loc-zh.md | 24 ++++++++ frontend/src/api/index.ts | 19 +----- frontend/src/components/push/PushToolbar.vue | 4 +- .../src/composables/push/usePushProgress.ts | 15 ++++- frontend/src/pages/PushPage.vue | 1 + frontend/src/pages/ServersPage.vue | 26 -------- frontend/src/utils/apiError.ts | 21 +++---- frontend/src/utils/validationErrorFormat.ts | 37 ++++++++++++ server/api/sync_v2.py | 39 ++++++++++-- server/background/sync_push_runner.py | 59 +++++++++++++++++++ server/utils/validation_errors_zh.py | 50 +++++++++++++++- tests/test_validation_errors_zh.py | 24 +++++++- 15 files changed, 341 insertions(+), 67 deletions(-) create mode 100644 docs/audit/2026-06-08-push-validation-servers-ui.md create mode 100644 docs/changelog/2026-06-08-push-async-background.md create mode 100644 docs/changelog/2026-06-08-servers-hide-csv-export.md create mode 100644 docs/changelog/2026-06-08-validation-error-loc-zh.md create mode 100644 frontend/src/utils/validationErrorFormat.ts create mode 100644 server/background/sync_push_runner.py diff --git a/docs/audit/2026-06-08-push-validation-servers-ui.md b/docs/audit/2026-06-08-push-validation-servers-ui.md new file mode 100644 index 00000000..45080cb1 --- /dev/null +++ b/docs/audit/2026-06-08-push-validation-servers-ui.md @@ -0,0 +1,41 @@ +# 审计 — 推送后台化 + 422 中文化 + 隐藏 CSV 导出(2026-06-08) + +**Changelog**: +- `docs/changelog/2026-06-08-push-async-background.md` +- `docs/changelog/2026-06-08-validation-error-loc-zh.md` +- `docs/changelog/2026-06-08-servers-hide-csv-export.md` + +## 审计范围 + +| 文件 | 状态 | +|------|------| +| `server/background/sync_push_runner.py` | ☑ | +| `server/api/sync_v2.py` | ☑ | +| `server/utils/validation_errors_zh.py` | ☑ | +| `frontend/src/composables/push/usePushProgress.ts` | ☑ | +| `frontend/src/components/push/PushToolbar.vue` | ☑ | +| `frontend/src/pages/PushPage.vue` | ☑ | +| `frontend/src/utils/validationErrorFormat.ts` | ☑ | +| `frontend/src/api/index.ts` | ☑ | +| `frontend/src/utils/apiError.ts` | ☑ | +| `frontend/src/pages/ServersPage.vue` | ☑ | +| `tests/test_validation_errors_zh.py` | ☑ | + +## Step 3 + +| 项 | 结论 | +|----|------| +| 推送后台任务独立 DB 会话 | PASS | +| 422 loc_zh 不泄露内部路径 | PASS | +| 隐藏 CSV 仅 UI 移除 | PASS | + +## Closure + +Gate 7/7 待跑 · 0 FINDING + +## DoD + +- [x] 推送按钮不长时间 loading +- [x] 校验错误显示「每页条数:不能大于 200」 +- [x] 服务器页无导出 CSV 按钮 +- [ ] 生产部署与健康检查 diff --git a/docs/changelog/2026-06-08-push-async-background.md b/docs/changelog/2026-06-08-push-async-background.md new file mode 100644 index 00000000..ba57b4a4 --- /dev/null +++ b/docs/changelog/2026-06-08-push-async-background.md @@ -0,0 +1,31 @@ +# 2026-06-08 — 推送按钮改为后台任务提交 + +## 变更摘要 + +文件推送不再阻塞 HTTP 直至全部子机完成;点击「推送」后按钮仅短暂 loading,任务在后台执行,进度由 WebSocket + 下方进度列表展示。 + +## 动机 + +用户反馈推送按钮长时间转圈,期望与脚本执行类似:提交后立即进入后台任务。 + +## 根因 + +`POST /api/sync/files` 同步等待 `sync_engine_v2.sync_files` 全部 rsync 结束才返回;前端 `:loading="pushing"` 与 HTTP 阻塞绑定。 + +## 涉及文件 + +- `server/background/sync_push_runner.py`(新) +- `server/api/sync_v2.py` — 校验后 `schedule_sync_files`,返回 `accepted: true` +- `frontend/src/composables/push/usePushProgress.ts` — `submitting` 与 toast +- `frontend/src/components/push/PushToolbar.vue` +- `frontend/src/pages/PushPage.vue` + +## 迁移 / 重启 + +- 需重启 API / 重建镜像 +- 前端需 build + +## 验证方式 + +1. 推送页选多台子机 → 点推送:按钮转圈仅数秒内结束,出现「推送已提交」toast,下方进度条持续更新 +2. `pytest tests/test_sync_batch_id.py -q` diff --git a/docs/changelog/2026-06-08-servers-hide-csv-export.md b/docs/changelog/2026-06-08-servers-hide-csv-export.md new file mode 100644 index 00000000..49c00995 --- /dev/null +++ b/docs/changelog/2026-06-08-servers-hide-csv-export.md @@ -0,0 +1,17 @@ +# 2026-06-08 — 隐藏服务器页 CSV 导出 + +## 变更摘要 + +移除服务器列表页工具栏「导出 CSV」按钮及相关前端逻辑。 + +## 动机 + +按产品要求暂时隐藏该入口(曾触发 `per_page` 超限 422)。 + +## 涉及文件 + +- `frontend/src/pages/ServersPage.vue` + +## 验证 + +构建后打开 `#/servers`,工具栏无「导出 CSV」按钮。 diff --git a/docs/changelog/2026-06-08-validation-error-loc-zh.md b/docs/changelog/2026-06-08-validation-error-loc-zh.md new file mode 100644 index 00000000..6cc842fc --- /dev/null +++ b/docs/changelog/2026-06-08-validation-error-loc-zh.md @@ -0,0 +1,24 @@ +# 2026-06-08 — 422 校验错误字段名中文化 + +## 变更摘要 + +API 422 响应增加 `loc_zh`(如「每页条数」),前端不再显示 `query.per_page: 不能大于 200` 这类英文路径前缀。 + +## 动机 + +用户看到半英文校验提示;根因是 `msg` 已翻译但 `loc` 仍拼接 `query.` 前缀。 + +## 涉及文件 + +- `server/utils/validation_errors_zh.py` +- `frontend/src/utils/validationErrorFormat.ts`(新) +- `frontend/src/api/index.ts`、`frontend/src/utils/apiError.ts` +- `frontend/src/pages/ServersPage.vue` — CSV 导出改为分页拉取(避免 `per_page: 5000` 触发 422) + +## 验证 + +```bash +pytest tests/test_validation_errors_zh.py -q +``` + +用户可见示例:`每页条数:不能大于 200` diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 303fa8c4..099a798c 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -2,29 +2,14 @@ * api/index.ts — HTTP client with JWT + HttpOnly refresh cookie. */ import { useAuthStore } from '@/stores/auth' +import { formatValidationDetail } from '@/utils/validationErrorFormat' const BASE_URL = import.meta.env.VITE_API_BASE || '/api' const AUTH_SKIP_REFRESH = new Set(['/auth/login', '/auth/refresh', '/auth/logout']) function normalizeDetail(detail: unknown, fallback: string): string { - let msg = '' - if (typeof detail === 'string') msg = detail - else if (Array.isArray(detail)) { - msg = detail - .map((x: { msg?: string; loc?: unknown[] }) => { - const part = x?.msg || JSON.stringify(x) - const loc = Array.isArray(x.loc) - ? x.loc.filter((p) => p !== 'body').join('.') - : '' - return loc ? `${loc}: ${part}` : part - }) - .join('; ') - } else if (detail && typeof detail === 'object') { - msg = JSON.stringify(detail) - } - msg = msg.trim() - return msg || fallback + return formatValidationDetail(detail, fallback) } let refreshInFlight: Promise | null = null diff --git a/frontend/src/components/push/PushToolbar.vue b/frontend/src/components/push/PushToolbar.vue index 2a356d44..8d38f284 100644 --- a/frontend/src/components/push/PushToolbar.vue +++ b/frontend/src/components/push/PushToolbar.vue @@ -22,7 +22,8 @@ color="primary" variant="flat" prepend-icon="mdi-upload" - :loading="pushing" + :loading="submitting" + :disabled="pushing" @click="$emit('push')" > 推送 @@ -35,6 +36,7 @@