72 lines
4.1 KiB
Markdown
72 lines
4.1 KiB
Markdown
|
|
# 2026-05-29 推送页面 Round 3 迭代
|
|||
|
|
|
|||
|
|
## 变更摘要
|
|||
|
|
1. G1: 推送取消 — Redis 取消标记 + 前端"取消推送"按钮 + WS cancelled 状态
|
|||
|
|
2. G2: 推送页面直接调度 — "⏰ 定时推送"按钮 + 时间选择模态框 + 调用调度 API
|
|||
|
|
3. G3: 推送模板/收藏 — localStorage 模板存储 + 下拉选择 + 保存/删除
|
|||
|
|
4. G4: 推送历史筛选分页 — 后端 page/per_page 参数 + 前端状态/服务器筛选 + 分页控件
|
|||
|
|
5. G5: 浏览器推送完成通知 — Web Notification API + title 闪烁降级
|
|||
|
|
6. G6: 拖拽上传 ZIP — 拖拽区 dragover/drop + 高亮 + click 回退
|
|||
|
|
|
|||
|
|
## 动机
|
|||
|
|
推送页面 Round 1+2 已完成 10 项功能,但仍有 6 项体验/效率缺陷:推送无法取消导致只能等超时、定时推送需跳到调度页重新配置、常用推送组合每次手动选、历史无法筛选分页、离开页面不知推送结果、文件选择器不够直观。
|
|||
|
|
|
|||
|
|
## 涉及文件
|
|||
|
|
|
|||
|
|
### 后端
|
|||
|
|
- `server/api/schemas.py` — 新增 `SyncCancel` 模型 (batch_id: str, max_length=32)
|
|||
|
|
- `server/api/sync_v2.py` — 新增 `POST /api/sync/cancel` 端点
|
|||
|
|
- Redis SET `sync:cancel:{batch_id}` EX 3600
|
|||
|
|
- JWT 认证 + 审计日志
|
|||
|
|
- 返回 `{cancelled: true, batch_id: ...}`
|
|||
|
|
- `server/api/websocket.py` — `broadcast_sync_progress()` 增加 `cancelled: int = 0` 参数
|
|||
|
|
- WS 消息新增 `cancelled` 字段,前端用于正确计算进度条
|
|||
|
|
- `server/application/services/sync_engine_v2.py` — 推送取消检查
|
|||
|
|
- `_sync_one()`: 获取信号量后、rsync 前检查 `sync:cancel:{batch_id}`
|
|||
|
|
- 取消的服务器 status="cancelled",创建 SyncLog 记录
|
|||
|
|
- 新增 `cancelled` 计数器,与 `completed`/`failed` 并列
|
|||
|
|
- 正常路径 WS 广播也传递 `cancelled` 计数
|
|||
|
|
- Redis 异常不再静默吞错,改用 `logger.warning()`
|
|||
|
|
- `server/api/servers.py` — `get_all_sync_logs()` 参数改为 page/per_page + 返回 pages
|
|||
|
|
- 旧: `offset: int, limit: int` → 新: `page: int = 1, per_page: int = 20`
|
|||
|
|
- 返回增加: `page`, `per_page`, `pages` (ceil(total/per_page))
|
|||
|
|
|
|||
|
|
### 前端
|
|||
|
|
- `web/app/push.html` — 6 项改动
|
|||
|
|
- G1: 推送进度区"取消推送"按钮 + `cancelPush()` + WS cancelled 状态渲染 ⊘
|
|||
|
|
- G2: "⏰ 定时推送"按钮 + 时间选择模态框 + `doSchedulePush()`
|
|||
|
|
- G3: 模板下拉框 + `saveTemplate()`/`applyTemplate()`/`deleteTemplate()` (localStorage)
|
|||
|
|
- G4: 历史区状态筛选下拉 + 服务器ID输入 + 分页控件
|
|||
|
|
- G5: 推送完成时 `new Notification()` + 授权拒绝时 title 闪烁降级
|
|||
|
|
- G6: 拖拽上传区 dragover/drop/dragleave + 高亮边框 + click 回退
|
|||
|
|
- 安全: `esc()` 函数增加单引号转义 `'`,防止 onclick 属性 JS 注入
|
|||
|
|
|
|||
|
|
## 安全影响
|
|||
|
|
- G1 (取消): Redis key 有 1h TTL 防泄漏;batch_id 为服务器生成的 UUID hex;取消端点有 JWT 认证 + 审计
|
|||
|
|
- G2 (调度): 复用现有调度 API,已有 JWT 认证 + 审计
|
|||
|
|
- G3 (模板): localStorage 仅存路径/服务器ID,不含密码/密钥
|
|||
|
|
- G4 (分页): SQLAlchemy 参数化查询,无 SQL 注入风险
|
|||
|
|
- G5 (通知): Notification API 需用户主动授权,非强制
|
|||
|
|
- G6 (拖拽): 文件仍走 upload-zip 端点校验
|
|||
|
|
|
|||
|
|
## 审计修复
|
|||
|
|
- F1: 取消服务器计数器 → 新增 `cancelled` 计数器,WS 广播含 `cancelled` 字段,前端进度条正确计算
|
|||
|
|
- F2: 静默异常 → `except Exception: pass` 改为 `logger.warning()`
|
|||
|
|
- F3: 异常链抑制 → 移除 `from None`
|
|||
|
|
- F4: XSS 防御 → `esc()` 增加单引号转义
|
|||
|
|
|
|||
|
|
## 需要迁移/重启
|
|||
|
|
- 是:后端 Python 需重启(supervisorctl restart nexus)
|
|||
|
|
- 否:无数据库 schema 迁移
|
|||
|
|
|
|||
|
|
## 验证方式
|
|||
|
|
1. G1: 推送 10 台 → 点击"取消推送" → 未启动的显示"已取消" ⊘ → 进度条正确
|
|||
|
|
2. G2: 配置推送 → 点击"⏰ 定时推送" → 选择时间 → 调度页出现任务
|
|||
|
|
3. G3: 保存模板 → 刷新 → 选择模板 → 表单自动填充
|
|||
|
|
4. G4: 历史区筛选"失败" → 仅显示失败 → 分页翻页
|
|||
|
|
5. G5: 推送完成 → 浏览器弹出通知
|
|||
|
|
6. G6: 拖拽 ZIP 到上传区 → 高亮 → 释放上传成功
|
|||
|
|
|
|||
|
|
## 进度条
|
|||
|
|
☑实现 ☑WSL验证 ☑审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☑changelog
|