# 2026-05-29 推送页面迭代 Round 3 — 设计文档

## 背景与目标

推送页面 Round 1（WS 进度/分组/历史/文件操作/校验）和 Round 2（同步说明/在线标识/失败重试/Telegram 通知/文件预览）已完成全部 10 项功能。基于实际运维场景，仍有 6 项体验/效率缺陷：

1. **推送无法取消** — 推送 50 台发现配错，只能等全部超时，无中止机制
2. **推送页无法直接调度** — 想凌晨推送需跳到调度页重新配置源路径/服务器/模式，操作割裂
3. **无推送模板/收藏** — 常用推送组合每次手动选，重复劳动
4. **推送历史无法筛选** — 历史只有最近 15 条无分页，无法按服务器/状态/日期过滤
5. **无浏览器推送完成通知** — 用户离开页面后不知推送结果（Telegram 有但浏览器没有）
6. **不支持拖拽上传 ZIP** — 文件选择器不够直观

## 方案对比

### G1 推送取消

| 方案 | 优点 | 缺点 |
|------|------|------|
| A: Redis 取消标记 + 引擎轮询 | 简单可靠，无进程间通信 | 最多延迟一个 rsync 周期(5min)才取消 |
| B: asyncio.Task.cancel() | 即时取消 | 多 worker 下无法跨进程取消 |
| C: kill rsync 子进程 | 最直接 | 需跟踪 PID，复杂且风险高 |

**选定方案 A** — Redis `sync:cancel:{batch_id}` 键，sync_engine 的 `_rsync_push()` 每次执行前检查。前端显示"取消推送"按钮，取消后已完成的保留结果，未启动的跳过。简单可靠，无进程间通信问题。

### G2 推送页面直接调度

| 方案 | 优点 | 缺点 |
|------|------|------|
| A: 推送按钮旁加"定时推送"按钮 | 复用当前表单配置 | 需新增调度 API 调用 |
| B: 调度选择器嵌入推送页 | 更灵活 | 页面变重 |

**选定方案 A** — "开始推送"按钮旁增加"⏰ 定时推送"按钮，弹出时间选择器后直接调用 `POST /api/schedules/`。推送页已有所需全部字段（server_ids, source_path, sync_mode）。

### G3 推送模板/收藏

| 方案 | 优点 | 缺点 |
|------|------|------|
| A: 浏览器 localStorage | 无后端改动 | 仅本机可见 |
| B: 后端 PushTemplate 表 | 跨设备共享 | 需 DB 迁移 + API |

**选定方案 A** — localStorage 存储推送模板。模板包含名称、源路径、目标路径、同步模式、服务器 ID 列表。运维人员通常固定在一台电脑操作，localStorage 足够。若未来需跨设备可迁移到后端。

### G4 推送历史筛选

| 方案 | 优点 | 缺点 |
|------|------|------|
| A: 后端分页 + 前端筛选下拉 | 大数据量友好 | 需改 API |
| B: 纯前端筛选 | 无后端改动 | 15 条太少无法筛选 |

**选定方案 A** — 后端 `GET /api/servers/logs` 已有 `limit` 参数，增加 `status`/`server_id`/`page` 参数，前端加筛选控件+分页。

### G5 浏览器推送完成通知

| 方案 | 优点 | 缺点 |
|------|------|------|
| A: Notification API | 系统级通知，即使页面在后台也可见 | 需用户授权 |
| B: 页面 title 闪烁 | 无需授权 | 仅标签页可见 |

**选定方案 A** — Web Notification API。推送开始时请求授权，完成时发浏览器通知。降级：授权拒绝时 title 闪烁。

### G6 拖拽上传 ZIP

| 方案 | 优点 | 缺点 |
|------|------|------|
| A: 拖拽区域 + click 回退 | 直观 | 需前端改动 |

**选定方案 A** — 将文件上传区改为拖拽区 + click 选择，标准 HTML5 拖拽实现。

## 选定方案及理由

全部选方案 A（最简方案），理由：
- G1: Redis 标记是跨 worker 最可靠的取消方案
- G2: 复用推送表单 + 现有调度 API，零后端新端点
- G3: localStorage 模板无需 DB 迁移，符合运维单人操作场景
- G4: 后端分页是大数据量唯一正确方案
- G5: Notification API 是唯一真正"后台通知"的方案
- G6: 拖拽上传是现代标准 UX

## 接口/数据模型

### G1: 推送取消
- Redis key: `sync:cancel:{batch_id}` (SET, TTL=3600s)
- `sync_engine_v2._rsync_push()`: 执行前检查 Redis key，存在则返回 `{exit_code: -1, stderr: "推送已取消"}`
- `sync_engine_v2.sync_files()`: 每个任务启动前检查取消标记
- 前端: `POST /api/sync/cancel` — `{batch_id: str}` → 写 Redis key + 审计日志
- WS 消息: 取消的服务器 status=`"cancelled"`

### G2: 推送页面直接调度
- 无新端点，复用 `POST /api/schedules/`
- 前端: "⏰ 定时推送"按钮 → 弹出 `datetime-local` 选择器 → 调用调度 API

### G3: 推送模板
- localStorage key: `nexus_push_templates` — JSON 数组
- 模板结构: `{name, source_path, target_path, sync_mode, server_ids[]}`
- 前端: 模板下拉选择 + 保存/删除按钮

### G4: 历史筛选
- 后端 `GET /api/servers/logs` 增加: `status: Optional[str]`, `server_id: Optional[int]`, `page: int = 1`, `per_page: int = 20`
- 返回增加: `total`, `page`, `pages`
- 前端: 状态/服务器筛选 + 分页控件

### G5: 浏览器通知
- 推送开始时 `Notification.requestPermission()`
- 推送完成时 `new Notification("Nexus 推送完成", {body: "X 成功, Y 失败"})`
- 降级: 授权拒绝时 `document.title` 闪烁

### G6: 拖拽上传
- `<div>` 拖拽区域 + `dragover/drop` 事件
- 保留原有 `<input type="file">` 作为 click 回退
- 拖拽时高亮边框

## 安全与性能约束

- G1: 取消端点需 JWT 认证 + 审计日志；Redis key 有 TTL 防止泄漏
- G2: 复用调度 API，已有 JWT 认证 + 审计
- G3: localStorage 无安全风险（不含密码/密钥）
- G4: 分页查询有索引 `idx_sync_logs_srv_start`
- G5: Notification 需用户主动授权，非强制
- G6: 拖拽文件仍走现有 upload-zip 端点校验

## 验收标准

1. G1: 推送进行中 → 点击"取消推送" → 未启动的服务器跳过 → 已完成的保留结果
2. G2: 配置推送 → 点击"定时推送" → 选择时间 → 调度页出现对应任务
3. G3: 保存模板 → 刷新页面 → 下拉选择模板 → 表单自动填充
4. G4: 历史区筛选状态/服务器 → 分页翻页 → 结果正确
5. G5: 推送完成 → 浏览器弹出通知（页面在后台也可见）
6. G6: 拖拽 ZIP 到上传区 → 文件上传解压成功
