136 lines
4.9 KiB
Markdown
136 lines
4.9 KiB
Markdown
|
|
# 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`
|