Files
Nexus/docs/design/plans/2026-05-29-push-round4.md
2026-07-08 22:31:31 +08:00

139 lines
5.5 KiB
Markdown

# 2026-05-29 推送页面迭代 Round 4 — 技术文档
## 涉及文件清单
| 文件 | 改动类型 | 涉及功能 |
|------|---------|---------|
| `server/api/schemas.py` | 新增模型 | H3 diff + H4 diagnose + H5 validate |
| `server/api/sync_v2.py` | 新增端点 | H3 file-diff + H4 diagnose + H5 validate |
| `web/app/push.html` | 修改 | H2 搜索 + H3 diff UI + H4 诊断 UI + H5 源路径 + H6 批量重试 |
| `docs/changelog/2026-05-29-push-round4.md` | 新增 | changelog |
## 实现步骤
### Step 1: H2 服务器搜索过滤(纯前端)
**文件**: `web/app/push.html`
1. `<select id="targetServers">` 上方加 `<input id="serverSearch" placeholder="搜索服务器...">`
2. 新增 `filterServerOptions()` 函数:读取搜索词,遍历 `<select>``<option>`,按名称/域名匹配隐藏/显示
3. `filterServers()` 加载完成后调用 `filterServerOptions()`
4. 搜索框 `oninput` 事件绑定 `filterServerOptions()`
### Step 2: H5 源路径直接输入(后端+前端)
**后端**:
1. `server/api/schemas.py` — 新增 `ValidateSourcePath(BaseModel)`:
```python
class ValidateSourcePath(BaseModel):
path: str = Field(..., min_length=1)
```
2. `server/api/sync_v2.py` — 新增 `POST /api/sync/validate-source-path`:
- 安全: `os.path.realpath()` 校验,禁止 `/etc`, `/root`, `/home`, `/var` 等敏感前缀
- 验证: 存在 + 是目录 + 统计文件数和大小
- 返回: `{valid, is_dir, file_count, size_bytes}`
- 审计日志
**前端**:
1. 上传区域下方加:
```html
<div class="flex items-center gap-2 mt-2">
<span class="text-xs text-slate-500">或输入源路径:</span>
<input id="sourcePathInput" placeholder="/path/to/files" class="flex-1 ...">
<button onclick="validateSourcePath()">验证</button>
</div>
```
2. `validateSourcePath()`: 调用 API → 成功后设置 `_uploadSourcePath` + 显示验证结果
3. `clearUpload()` 同时清空源路径输入框
### Step 3: H6 批量重试(纯前端)
**文件**: `web/app/push.html`
1. 进度条区域,`countFailed` 旁加:
```html
<button id="retryAllBtn" class="hidden text-xs text-brand-light hover:underline" onclick="retryAllFailed()">🔄 重试全部失败</button>
```
2. `updateProgressFromWS/Result()` — 有失败时显示 `retryAllBtn`,文本显示失败数
3. 新增 `retryAllFailed()`:
- 收集所有可见的 `srv_retry_{id}` 按钮的 `retryJobId`
- 逐个调用 `POST /api/retries/{id}/retry`
- 显示进度 toast
### Step 4: H4 推送排错面板(后端+前端)
**后端**:
1. `server/api/schemas.py` — 新增 `SyncDiagnose(BaseModel)`:
```python
class SyncDiagnose(BaseModel):
server_id: int = Field(..., ge=1)
target_path: Optional[str] = None
```
2. `server/api/sync_v2.py` — 新增 `POST /api/sync/diagnose`:
- 检查项(依次执行,遇短路可提前返回):
a. SSH 连通性: 尝试 `exec_ssh_command(server, "echo ok", timeout=10)`
b. 磁盘空间: `df -h {target_path}` → 解析 avail/usage%
c. 目标路径: `ls -ld {target_path}` → 解析权限 + owner
d. 写入测试: `touch {target_path}/.nexus_test_$$ && rm {target_path}/.nexus_test_$$`
- 返回: `{ssh_ok, ssh_error?, disk_avail, disk_used_pct, path_exists, path_perms, path_writable, errors[]}`
- 审计日志
**前端**:
1. `showProgress()` 每行加 `srv_diag_{id}` 诊断按钮(仅失败时显示)
2. 新增 `diagnoseServer(serverId)` — 调用 API → 弹出模态框显示检查结果
3. 模态框 `#diagModal`: 4 项检查,每项 ✓/✗ 图标 + 详情
### Step 5: H3 推送对比 Diff 视图(后端+前端)
**后端**:
1. `server/api/schemas.py` — 新增 `FileSyncDiff(BaseModel)`:
```python
class FileSyncDiff(BaseModel):
server_id: int = Field(..., ge=1)
source_path: str = Field(..., min_length=1)
relative_path: str = Field(..., min_length=1)
target_path: Optional[str] = None
```
2. `server/api/sync_v2.py` — 新增 `POST /api/sync/file-diff`:
- 安全: `os.path.realpath(local_file)` 必须 `startswith(source_path)` 且 source_path 必须 `startswith("/tmp/nexus_upload_")`
- 读本地文件: 最多 100KB
- 读远程文件: `cat {shlex.quote(remote_file)}` via SSH,最多 100KB
- diff 计算: Python `difflib.unified_diff()` → 返回 `diff_lines: [{type: "add"|"del"|"ctx", content}]`
- 返回: `{file_name, local_exists, remote_exists, local_size, remote_size, diff_lines, truncated}`
- 审计日志
**前端**:
1. `renderPreviewResult()` verbose 文件列表中每行加 "📄 diff" 按钮
2. 新增 `showFileDiff(serverId, relativePath)` — 调用 API → 弹出 diff 模态框
3. 模态框 `#diffModal`: 文件名 + 左右对比或逐行显示(新增绿底、删除红底、上下文灰底)
## 依赖与配置变更
无新依赖。无数据库 schema 迁移。
## 测试要点
1. H2: 输入 "web" → 下拉只显示名称/域名含 "web" 的服务器
2. H5: 输入 `/tmp/nexus_upload_xxx` → 验证成功 → 可推送
3. H5: 输入 `/etc` → 验证拒绝(敏感路径)
4. H6: 推送 3 台失败 2 台 → 点"重试全部失败" → 2 台重试
5. H4: 推送失败 → 点"🔍 诊断" → SSH/磁盘/权限检查结果
6. H3: 预览 → 文件列表 → 点 "diff" → 显示逐行对比
7. H3: 本地有文件远程无 → 显示为纯新增(全绿)
8. H3: 本地无远程有 → 显示为纯删除(全红)
## 回滚方式
所有改动均为增量(新增函数/端点/前端元素),回滚只需:
1. `git revert` 提交
2. `supervisorctl restart nexus`