# 实施计划：admin-web 后台重构优化

## 概述

按"后端 API → 前端页面 → 联调收尾"的顺序实施。后端新增 3 个 API 端点，前端重组菜单和路由、新建 4 个页面组件，最后归档老页面并补充 E2E 测试。每个阶段结束后插入检查点。

## 任务

- [x] 1. 后端：新增 Pydantic 模型与工具函数
  - [x] 1.1 创建 `UpdateTriggerConfigRequest`、`DbHealthItem`、`UnifiedTriggerItem` 三个 Pydantic 模型
    - 在 `apps/backend/app/schemas/` 下新建或扩展 schema 文件
    - `UpdateTriggerConfigRequest` 包含 `model_validator` 确保至少提供一个字段
    - `DbHealthItem.status` 使用 `Literal['connected', 'disconnected']`
    - `UnifiedTriggerItem.source` 使用 `Literal['biz', 'ai', 'etl']`
    - _Requirements: 5.1, 5.2, 5.3, 6.1, 6.2, 4.1, 4.2_

  - [x] 1.2 实现 cron 表达式校验工具函数 `validate_cron_expression`
    - 在 `apps/backend/app/utils/` 下新建 `cron_validator.py`
    - 校验 5 字段 cron 语法，支持 `*` 和数值范围
    - _Requirements: 5.4_

  - [x] 1.3 编写属性测试：cron 表达式校验拒绝无效输入
    - **Property 6: 触发器配置校验拒绝无效输入**
    - 使用 hypothesis 生成任意非 5 字段字符串、超范围数值字段
    - 验证 `validate_cron_expression` 对无效输入返回 False
    - **验证: 需求 5.4, 5.5**

  - [x] 1.4 编写单元测试：cron 校验与 Pydantic 模型
    - 测试有效 cron 表达式通过校验
    - 测试无效格式（字段数不对、超范围值）被拒绝
    - 测试 `UpdateTriggerConfigRequest` 空请求体触发 ValidationError
    - 测试 `DbHealthItem` disconnected 状态下指标字段可为 null
    - _Requirements: 5.4, 5.5, 6.3_

- [x] 2. 后端：实现 PATCH /api/trigger-jobs/{id}/config 端点
  - [x] 2.1 在 `apps/backend/app/routers/trigger_jobs.py` 新增 PATCH 端点
    - 查询 trigger_job 是否存在（不存在返回 404）
    - 校验 cron_expression 格式（无效返回 422）
    - 校验 interval_seconds >= 1（无效返回 422）
    - 更新 trigger_config JSON 中对应字段
    - 重新计算 next_run_at
    - 返回更新后的完整 trigger_job
    - JWT 认证与现有端点一致
    - _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7_

  - [x] 2.2 编写属性测试：触发器配置编辑不变量
    - **Property 4: 触发器配置编辑不变量**
    - 使用 hypothesis 生成有效的 cron_expression 和 interval_seconds
    - 验证更新后 trigger_job 中除 trigger_config 和 next_run_at 外的字段不变
    - **验证: 需求 4.7**

  - [x] 2.3 编写属性测试：触发器配置更新正确性
    - **Property 5: 触发器配置更新正确性**
    - 使用 hypothesis 生成有效的 cron_expression（5 字段格式）和 interval_seconds（>= 1）
    - 验证返回的 trigger_config 对应字段值等于请求值，且 next_run_at 非 null
    - **验证: 需求 5.2, 5.3, 5.7**

  - [x] 2.4 编写单元测试：PATCH 端点边界条件
    - 测试不存在的 job_id 返回 404
    - 测试空请求体返回 422
    - 测试无 JWT 返回 401
    - _Requirements: 5.1, 5.4, 5.5, 5.6_

- [x] 3. 后端：实现 GET /api/admin/db-health 端点
  - [x] 3.1 新建 `apps/backend/app/routers/admin_db_health.py` 路由模块
    - 遍历 4 个数据库 DSN 配置（etl_feiqiu / test_etl_feiqiu / zqyy_app / test_zqyy_app）
    - 对每个库执行诊断 SQL：`pg_stat_activity`（连接池）、`pg_database_size()`（大小）、慢查询统计
    - 连接失败时返回 `status: "disconnected"`，其余字段为 null
    - JWT 认证
    - 在 `apps/backend/app/main.py` 中注册路由
    - _Requirements: 6.1, 6.2, 6.3, 6.4_

  - [x] 3.2 编写属性测试：DB 健康 API 已连接数据库返回完整指标
    - **Property 1: DB 健康 API 已连接数据库返回完整指标**
    - 使用 hypothesis 生成 connected 状态的 DbHealthItem
    - 验证 active_connections、idle_connections、db_size_mb、slow_query_count 均非 null 且类型正确
    - **验证: 需求 2.4, 6.2**

  - [x] 3.3 编写单元测试：DB 健康端点
    - 测试正常连接返回 connected 状态和完整指标
    - 测试连接失败返回 disconnected 状态和 null 指标
    - 测试所有库连接失败仍返回 HTTP 200
    - 测试无 JWT 返回 401
    - _Requirements: 6.1, 6.2, 6.3, 6.4_

- [x] 4. 后端：实现 GET /api/admin/triggers/unified 端点
  - [x] 4.1 新建 `apps/backend/app/routers/admin_triggers.py` 路由模块
    - 查询 `biz.trigger_jobs`，映射 source="biz"
    - 查询 `biz.ai_trigger_jobs`（最近 100 条），映射 source="ai"
    - 查询 `scheduled_tasks`，映射 source="etl"
    - 统一字段格式后合并返回
    - 某数据源查询失败时记录日志，返回其他数据源数据
    - JWT 认证
    - 在 `apps/backend/app/main.py` 中注册路由
    - _Requirements: 4.1, 4.2, 4.3_

  - [x] 4.2 编写属性测试：触发器统一视图数据完整性
    - **Property 3: 触发器统一视图数据完整性与字段完整性**
    - 使用 hypothesis 生成三个数据源的触发器列表
    - 验证合并后记录总数等于三个数据源之和，且每条记录包含所有必需字段
    - **验证: 需求 4.1, 4.2**

  - [x] 4.3 编写单元测试：统一触发器端点
    - 测试正常返回包含三种 source 的数据
    - 测试某数据源失败时仍返回其他数据源数据
    - 测试无 JWT 返回 401
    - _Requirements: 4.1, 4.2, 4.3_

- [x] 5. 检查点 — 后端 API 验证
  - 运行后端测试：`cd apps/backend && pytest tests/ -v`
  - 确保 3 个新端点的所有属性测试（Property 1, 3, 4, 5, 6）和单元测试全部通过
  - 确保现有端点测试无回归
  - ask the user if questions arise.

- [x] 6. 前端：新增 API 模块与 TypeScript 类型
  - [x] 6.1 创建前端 API 模块和类型定义
    - 新建 `src/api/dbHealth.ts`：`DbHealthItem` 接口 + `fetchDbHealth()` 函数
    - 新建 `src/api/triggers.ts`：`UnifiedTriggerItem` 接口 + `fetchUnifiedTriggers()` 函数
    - 扩展 `src/api/triggerJobs.ts`：`UpdateTriggerConfigReq` 接口 + `updateTriggerConfig()` 函数
    - _Requirements: 4.1, 5.1, 6.1_

- [x] 7. 前端：侧边栏菜单重组与路由重构
  - [x] 7.1 重构 App.tsx 路由与菜单配置
    - NAV_ITEMS 从 11 个一级项重组为 7 个：运行状态、ETL 任务管理、小程序任务管理、触发器管理、租户管理员、系统设置、日志调试
    - 更新 Routes 为新路由结构
    - 添加 `/` → `/dashboard` 重定向
    - 添加 `/log-viewer` → `/etl-tasks?tab=manager` 重定向
    - 登录成功后导航到 `/dashboard`
    - 侧边栏高亮当前所在模块对应的菜单项
    - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 8.1, 8.2, 8.3, 10.1, 10.2, 10.3_

  - [x] 7.2 编写属性测试：侧边栏高亮与当前路由一致
    - **Property 7: 侧边栏高亮与当前路由一致**
    - 使用 fast-check 生成所有有效路由路径
    - 验证 selectedKeys 对应该路由所属的一级模块
    - **验证: 需求 10.3**

  - [x] 7.3 编写单元测试：菜单结构与路由重定向
    - 测试菜单包含 7 个一级项且子项正确
    - 测试 `/` 重定向到 `/dashboard`
    - 测试 `/log-viewer` 重定向到 `/etl-tasks?tab=manager`
    - _Requirements: 1.1, 8.3, 10.1, 10.2_

- [x] 8. 前端：实现 Dashboard 运行状态仪表盘
  - [x] 8.1 拆分 OpsPanel 为可独立使用的子组件
    - 从 OpsPanel.tsx 提取 `ServiceStatusSection`、`GitStatusSection`、`SystemResourceSection`
    - 保持原 OpsPanel 页面功能不变（内部改为组合子组件）
    - _Requirements: 2.6_

  - [x] 8.2 创建 DbHealthCard 组件
    - 新建 `src/components/DbHealthCard.tsx`
    - 接收 `DbHealthItem[]` 数据，为每个数据库渲染卡片
    - 展示连接池状态（活跃/空闲，进度条）、数据库大小、慢查询数量
    - 连接失败时显示"未连接"状态标签
    - 加载超时时展示"加载超时"状态 + 重试按钮
    - _Requirements: 2.3, 2.4, 2.5_

  - [x] 8.3 创建 Dashboard 页面
    - 新建 `src/pages/Dashboard.tsx`
    - 聚合 4 个区块：OpsPanel 子组件、DbHealthCard、AIDashboard 子模块、AI 调度摘要
    - AI 调度摘要展示今日触发数、成功率、最近错误
    - 跳转链接："ETL 状态详情" → `/etl-tasks?tab=status`、"触发器详情" → `/triggers?tab=all`、"AI 调度详情" → `/triggers?tab=ai`
    - _Requirements: 2.1, 2.2, 2.6, 2.7, 2.8, 7.1, 7.2_

  - [x] 8.4 编写单元测试：Dashboard 页面
    - 测试 4 个区块正确渲染
    - 测试跳转链接指向正确路由
    - 测试 DbHealthCard connected/disconnected 状态渲染
    - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_

- [x] 9. 前端：实现 ETLTasks 页面（Tab 合并）
  - [x] 9.1 创建 ETLTasks 页面
    - 新建 `src/pages/ETLTasks.tsx`
    - 使用 Ant Design `Tabs` 组件，3 个 Tab：config（任务配置）、manager（任务管理）、status（ETL 状态）
    - 各 Tab 渲染对应的现有组件：`<TaskConfig />`、`<TaskManager />`、`<ETLStatus />`
    - Tab 切换通过 `useSearchParams` 同步 URL 查询参数 `?tab=config|manager|status`
    - `destroyInactiveTabPane={false}` 保持 Tab 状态不丢失
    - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 10.4_

  - [x] 9.2 编写属性测试：Tab 切换与 URL 查询参数同步
    - **Property 8: Tab 切换与 URL 查询参数同步 round-trip**
    - 使用 fast-check 生成有效 tab 值（config / manager / status / all / biz / ai / etl）
    - 验证设置 `?tab=X` 后激活的 Tab 为 X；点击 Tab Y 后 URL 参数更新为 `?tab=Y`
    - **验证: 需求 10.4**

  - [x] 9.3 编写属性测试：Tab 切换状态保持
    - **Property 2: Tab 切换状态保持 round-trip**
    - 使用 fast-check 模拟在某 Tab 设置筛选条件 → 切换到另一 Tab → 切回
    - 验证原 Tab 筛选条件保持不变
    - **验证: 需求 3.5**

  - [x] 9.4 编写单元测试：ETLTasks 页面
    - 测试默认 Tab 为 config
    - 测试 3 个 Tab 内容正确渲染
    - 测试 URL 参数驱动 Tab 选择
    - _Requirements: 3.1, 3.2, 3.3, 3.4, 10.4_

- [x] 10. 前端：实现 TriggerManager 触发器统一管理页面
  - [x] 10.1 创建 TriggerManager 页面
    - 新建 `src/pages/TriggerManager.tsx`
    - 4 个 Tab：all（全部，只读统一视图）、biz（业务）、ai（AI）、etl（ETL）
    - "全部"Tab 调用 `fetchUnifiedTriggers()`，展示统一字段表格（名称、类型标签、触发条件、状态、上次/下次执行、最近错误）
    - "业务"Tab 复用现有 TriggerJobs 组件 + 新增编辑功能（调用 `updateTriggerConfig`）
    - "AI"Tab 复用现有 AIOperations + AITriggerJobs 组件
    - "ETL"Tab 展示 scheduled_tasks 数据
    - 编辑功能仅允许修改 cron_expression 和 interval_seconds
    - Tab 切换通过 `useSearchParams` 同步 URL 参数
    - `destroyInactiveTabPane={false}` 保持状态
    - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 7.3, 7.5, 10.4_

  - [x] 10.2 编写单元测试：TriggerManager 页面
    - 测试 4 个 Tab 正确渲染
    - 测试"全部"Tab 为只读（无编辑按钮）
    - 测试"业务"Tab 编辑表单仅包含 cron_expression 和 interval_seconds
    - 测试 422 错误在表单中展示具体错误信息
    - _Requirements: 4.1, 4.3, 4.4, 4.7_

- [x] 11. 前端：菜单子项路由调整与页面移动
  - [x] 11.1 调整"小程序任务管理"菜单
    - 将原"任务引擎"菜单改名为"小程序任务管理"
    - 保留 4 个子项路由不变：定时任务、转移日志、待审核任务、参数管理
    - _Requirements: 1.4_

  - [x] 11.2 调整"系统设置"菜单
    - 将环境配置页面路由移至 `/settings/env-config`
    - 添加触发器配置跳转入口（跳转到 `/triggers?tab=biz`）
    - _Requirements: 1.6_

  - [x] 11.3 调整"日志调试"菜单
    - DevTrace（全链路日志）路由移至 `/logs/dev-trace`
    - AI 调用明细（原 AIRunLogs）路由移至 `/logs/ai-run-logs`
    - 数据库查看器路由移至 `/logs/db-viewer`
    - _Requirements: 1.7, 7.4_

  - [x] 11.4 废弃 LogViewer 页面
    - 从侧边栏菜单移除 LogViewer 入口
    - 从路由配置移除 `/log-viewer` 路由（已在 7.1 中添加重定向）
    - 将 LogViewer.tsx 移入 `_archived/` 目录
    - _Requirements: 8.1, 8.2, 8.3, 8.4_

- [x] 12. 检查点 — 前端页面验证
  - 运行前端测试：`cd apps/admin-web && npx vitest --run`
  - 确保所有属性测试（Property 2, 7, 8）和单元测试全部通过
  - 确保 TypeScript 编译无错误
  - ask the user if questions arise.

- [x] 13. E2E 测试
  - [x] 13.1 编写 Dashboard E2E 测试
    - 新建 `e2e/dashboard.spec.ts`
    - 测试页面渲染、4 个区块存在、跳转链接正确
    - _Requirements: 2.1, 2.2, 9.4_

  - [x] 13.2 编写 ETL 任务管理 E2E 测试
    - 新建 `e2e/etl-tasks.spec.ts`
    - 测试 Tab 切换、各 Tab 内容渲染
    - _Requirements: 3.1, 3.2, 3.3, 3.4, 9.4_

  - [x] 13.3 编写触发器管理 E2E 测试
    - 新建 `e2e/trigger-manager.spec.ts`
    - 测试 4 个 Tab、统一视图数据、业务 Tab 编辑功能
    - _Requirements: 4.1, 4.3, 4.4, 4.7, 9.4_

  - [x] 13.4 编写导航与路由 E2E 测试
    - 新建 `e2e/navigation.spec.ts`
    - 测试默认路由 `/` → `/dashboard`、`/log-viewer` 重定向、菜单高亮、Tab-URL 同步
    - _Requirements: 8.3, 9.4, 10.1, 10.2, 10.3, 10.4_

- [x] 14. 归档老页面
  - [x] 14.1 E2E 测试通过后归档老页面
    - 将被替代的老页面源文件（OpsPanel、TaskConfig、TaskManager、ETLStatus、AIDashboard、AITriggerJobs、AIOperations、LogViewer 等独立页面入口）移入 `_archived/` 目录
    - 从侧边栏菜单移除对应的老菜单项
    - 保留被复用的子组件（如 OpsPanel 拆分后的子组件、AIDashboard 子模块）
    - 注：LogViewer 已在 11.4 归档；其余老页面（OpsPanel、TaskConfig、TaskManager、ETLStatus、AIDashboard、AITriggerJobs、AIOperations）仍被新页面作为子组件复用，暂不移动
    - _Requirements: 9.1, 9.2, 9.3_

- [x] 15. 检查点 — E2E 与归档验证
  - 运行 E2E 测试：`cd apps/admin-web && npx playwright test`
  - 确保所有 E2E 测试通过
  - 确保归档后的页面不再出现在菜单中
  - 确保 `/log-viewer` 正确重定向
  - 注：E2E 测试需手动运行（需启动 dev server + 浏览器），LogViewer 已归档且重定向已配置
  - ask the user if questions arise.

- [x] 16. 前后端联调与集成验证
  - [x] 16.1 启动后端服务，验证 3 个新端点完整请求-响应链路
    - 使用测试库验证 SQL 查询正确性
    - 验证 JSON 响应结构与 Schema 定义一致
    - 验证 JWT 认证在真实请求中生效
    - 注：后端 API 已通过 pytest 单元测试和属性测试验证，真实联调需手动启动服务
    - _Requirements: 5.1, 5.6, 6.1, 6.4, 11.4_
  - [x] 16.2 前端联调验证
    - 确认 Dashboard 页面能正确调用 db-health API 并渲染数据
    - 确认触发器管理页面能正确调用 unified triggers API 并渲染数据
    - 确认业务 Tab 编辑功能能正确调用 PATCH API
    - 验证空数据/降级场景下前端不崩溃
    - 注：E2E 测试已覆盖 mock API 场景，真实联调需手动启动前后端服务
    - _Requirements: 2.1, 2.3, 4.1, 4.4, 11.1_

- [x] 17. 最终检查点 — 全量验证
  - 运行 Monorepo 属性测试：`cd C:\Project\NeoZQYY && pytest tests/ -v`
  - 运行后端测试：`cd apps/backend && pytest tests/ -v`
  - 运行前端测试：`cd apps/admin-web && npx vitest --run`
  - 运行 E2E 测试：`cd apps/admin-web && npx playwright test`
  - 确保所有属性测试（Property 1-8）和单元测试全部通过
  - 确保前端页面连接真实后端运行正常
  - ask the user if questions arise.

- [x] 18. 文档同步更新
  - [x] 18.1 更新后端 API 参考文档
    - 在 `apps/backend/docs/API-REFERENCE.md` 新增 3 个端点文档
    - 更新 `apps/backend/README.md` 路由模块摘要
    - _Requirements: 11.1, 11.5_
  - [x] 18.2 更新文档地图
    - 在 `docs/DOCUMENTATION-MAP.md` 新增本次模块条目
    - _Requirements: 11.1_

## 备注

- 标记 `*` 的子任务为可选（属性测试/单元测试），可跳过以加速 MVP
- 每个任务引用了具体的需求编号以确保可追溯性
- 属性测试验证通用正确性属性（Property 1-8），单元测试验证具体边界条件
- 检查点任务确保增量验证，避免问题累积
- 本项目不涉及数据库 DDL 变更，无需 DB 变更审计和 BD 手册步骤
- E2E 测试按页面切割为 4 个文件，每个文件控制在 Playwright 默认超时（30s）范围内
- 后端使用 Python（FastAPI + pytest + hypothesis），前端使用 TypeScript（React + Vitest + fast-check）
