Files
Nexus/docs/design/plans/2026-05-29-push-round3.md
T

136 lines
4.9 KiB
Markdown
Raw Normal View History

# 2026-05-29 推送页面迭代 Round 3 — 技术文档
## 涉及文件清单
| 文件 | 改动类型 | 涉及功能 |
|------|---------|---------|
| `server/application/services/sync_engine_v2.py` | 修改 | G1 取消检查 |
| `server/api/sync_v2.py` | 新增端点 | G1 取消端点 |
| `server/api/schemas.py` | 新增模型 | G1 取消请求 |
| `server/api/servers.py` | 修改参数 | G4 历史筛选分页 |
| `web/app/push.html` | 修改 | G1-G6 全部前端 |
| `docs/changelog/2026-05-29-push-round3.md` | 新增 | changelog |
## 实现步骤
### Step 1: G1 推送取消(后端+前端)
**后端**:
1. `server/api/schemas.py` — 新增:
```python
class SyncCancel(BaseModel):
batch_id: str = Field(..., min_length=1, max_length=32)
```
2. `server/api/sync_v2.py` — 新增 `POST /api/sync/cancel`:
- Redis `SET sync:cancel:{batch_id} 1 EX 3600`
- 审计日志
- 返回 `{"cancelled": true, "batch_id": ...}`
3. `server/application/services/sync_engine_v2.py`:
- `_rsync_push()`: 执行前检查 `sync:cancel:{batch_id}`,存在则返回 `{"exit_code": -1, "stderr": "推送已取消"}`
- `sync_files()`: `_sync_one()` 任务启动前(`async with sem` 之后、rsync 之前)检查取消标记
- 取消的服务器 status=`"cancelled"`,不创建 retry job
**前端**:
1. `doPush()` 执行期间显示"取消推送"按钮
2. `cancelPush()` — 调用 `POST /api/sync/cancel`
3. `updateProgressFromWS()` — 处理 `status=cancelled`
4. 取消后按钮隐藏,已完成的服务器结果保留
### Step 2: G6 拖拽上传 ZIP(纯前端)
**文件**: `web/app/push.html`
1. 将 `#sourceUpload` 区域改为拖拽区:
- `<div>` 加 `dragover/dragleave/drop` 事件
- `dragover`: 高亮边框 `border-brand`
- `drop`: 取 `ev.dataTransfer.files[0]`,调用 `uploadZip()` 逻辑
2. 保留 `<input type="file">` 隐藏,拖拽区 click 触发 `zipFile.click()`
3. 文件类型校验:仅 `.zip`
### Step 3: G5 浏览器推送完成通知(纯前端)
**文件**: `web/app/push.html`
1. `doPush()` 开始时请求 `Notification.requestPermission()`
2. 推送完成(`doPush()` finally 块前)发送通知:
```js
if (Notification.permission === 'granted') {
new Notification('Nexus 推送完成', {body: `${completed} 成功, ${failed} 失败`});
}
```
3. 降级:授权拒绝时 `document.title` 闪烁(3 次交替 `🔔 Nexus` / `Nexus — 推送`
### Step 4: G2 推送页面直接调度(纯前端)
**文件**: `web/app/push.html`
1. "开始推送"按钮旁加"⏰ 定时推送"按钮
2. 点击弹出简单模态框:`datetime-local` 选择器 + 确认按钮
3. 确认后调用 `POST /api/schedules/`body 包含:
- `name`: "手动推送-{时间}"
- `schedule_type`: "push"
- `run_mode`: "once"
- `fire_at`: 选择的时间
- `source_path`, `server_ids`, `sync_mode`: 从当前表单取
4. 成功后 toast 提示 + 链接到调度页面
### Step 5: G3 推送模板/收藏(纯前端)
**文件**: `web/app/push.html`
1. localStorage key: `nexus_push_templates` — JSON 数组
2. 模板结构: `{name, source_path, target_path, sync_mode, server_ids[]}`
3. UI:
- 表单上方"模板"下拉框 + "保存当前配置"按钮
- 选择模板 → 自动填充 source_path/target_path/sync_mode/server_ids
- 下拉旁"删除"按钮删除模板
4. `saveTemplate()`: 弹出 prompt 输入名称 → 存入 localStorage
5. `loadTemplate()`: 从 localStorage 读取模板列表 → 渲染下拉框
6. `applyTemplate()`: 填充表单字段
7. `deleteTemplate()`: 从 localStorage 移除
### Step 6: G4 推送历史筛选(后端+前端)
**后端**:
1. `server/api/servers.py` — `list_sync_logs()`:
- 新增参数: `status: Optional[str]`, `server_id: Optional[int]`, `page: int = 1`
- 增加过滤条件到 SQLAlchemy 查询
- 增加 `COUNT(*)` 查询获取总数
- 返回增加: `total`, `page`, `pages`
**前端**:
1. 历史区增加筛选控件:
- 状态下拉: 全部/成功/失败
- 服务器搜索输入框
2. `loadHistory()` 增加参数传递
3. 分页控件: 上一页/下一页 + 页码显示
## 依赖与配置变更
无新依赖。无数据库 schema 迁移。
G1 需要已有的 Redis 连接。
## 测试要点
1. G1: 推送 10 台 → 取消 → 仅已完成的有结果,其余显示"已取消"
2. G1: 推送完成后再取消 → 无效果(batch_id 已失效)
3. G2: 定时推送 → 调度页出现对应任务 → 到时间自动执行
4. G3: 保存模板 → 刷新 → 选择模板 → 表单填充正确
5. G3: 删除模板 → 下拉框更新
6. G4: 筛选"失败" → 仅显示失败记录 → 分页翻页
7. G5: 推送完成 → 浏览器通知弹出 → 点击通知回到页面
8. G6: 拖拽 ZIP → 上传解压成功 → 文件管理器显示
9. G6: 拖拽非 ZIP → 提示仅支持 .zip
## 回滚方式
所有改动均为增量(新增端点/前端元素),回滚只需:
1. `git revert` 提交
2. `supervisorctl restart nexus`