5.5 KiB
5.5 KiB
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
<select id="targetServers">上方加<input id="serverSearch" placeholder="搜索服务器...">- 新增
filterServerOptions()函数:读取搜索词,遍历<select>的<option>,按名称/域名匹配隐藏/显示 filterServers()加载完成后调用filterServerOptions()- 搜索框
oninput事件绑定filterServerOptions()
Step 2: H5 源路径直接输入(后端+前端)
后端:
-
server/api/schemas.py— 新增ValidateSourcePath(BaseModel):class ValidateSourcePath(BaseModel): path: str = Field(..., min_length=1) -
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} - 审计日志
- 安全:
前端:
- 上传区域下方加:
<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> validateSourcePath(): 调用 API → 成功后设置_uploadSourcePath+ 显示验证结果clearUpload()同时清空源路径输入框
Step 3: H6 批量重试(纯前端)
文件: web/app/push.html
- 进度条区域,
countFailed旁加:<button id="retryAllBtn" class="hidden text-xs text-brand-light hover:underline" onclick="retryAllFailed()">🔄 重试全部失败</button> updateProgressFromWS/Result()— 有失败时显示retryAllBtn,文本显示失败数- 新增
retryAllFailed():- 收集所有可见的
srv_retry_{id}按钮的retryJobId - 逐个调用
POST /api/retries/{id}/retry - 显示进度 toast
- 收集所有可见的
Step 4: H4 推送排错面板(后端+前端)
后端:
-
server/api/schemas.py— 新增SyncDiagnose(BaseModel):class SyncDiagnose(BaseModel): server_id: int = Field(..., ge=1) target_path: Optional[str] = None -
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[]} - 审计日志
- 检查项(依次执行,遇短路可提前返回):
a. SSH 连通性: 尝试
前端:
showProgress()每行加srv_diag_{id}诊断按钮(仅失败时显示)- 新增
diagnoseServer(serverId)— 调用 API → 弹出模态框显示检查结果 - 模态框
#diagModal: 4 项检查,每项 ✓/✗ 图标 + 详情
Step 5: H3 推送对比 Diff 视图(后端+前端)
后端:
-
server/api/schemas.py— 新增FileSyncDiff(BaseModel):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 -
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} - 审计日志
- 安全:
前端:
renderPreviewResult()verbose 文件列表中每行加 "📄 diff" 按钮- 新增
showFileDiff(serverId, relativePath)— 调用 API → 弹出 diff 模态框 - 模态框
#diffModal: 文件名 + 左右对比或逐行显示(新增绿底、删除红底、上下文灰底)
依赖与配置变更
无新依赖。无数据库 schema 迁移。
测试要点
- H2: 输入 "web" → 下拉只显示名称/域名含 "web" 的服务器
- H5: 输入
/tmp/nexus_upload_xxx→ 验证成功 → 可推送 - H5: 输入
/etc→ 验证拒绝(敏感路径) - H6: 推送 3 台失败 2 台 → 点"重试全部失败" → 2 台重试
- H4: 推送失败 → 点"🔍 诊断" → SSH/磁盘/权限检查结果
- H3: 预览 → 文件列表 → 点 "diff" → 显示逐行对比
- H3: 本地有文件远程无 → 显示为纯新增(全绿)
- H3: 本地无远程有 → 显示为纯删除(全红)
回滚方式
所有改动均为增量(新增函数/端点/前端元素),回滚只需:
git revert提交supervisorctl restart nexus