# Nexus 6.0 — 功能开发指南 · 附录(详细参考) > **主文档**: [nexus-functional-development-guide.md](nexus-functional-development-guide.md) > **版本**: 2026-06-06 > **用途**: API 请求体、前端调用链、E2E/L4 对照、组件映射 — 接续开发时查表用 --- ## 附录 A — `/api/sync` 端点与 Pydantic 模型 定义: `server/api/schemas.py` · 路由: `server/api/sync_v2.py` · 前缀: `/api/sync` | 方法 | 路径 | 请求模型 | 说明 | |------|------|----------|------| | POST | `/files` | `SyncFiles` | 批量 rsync 推送 | | POST | `/preview` | `SyncPreview` | dry-run 预览(单台代表服务器) | | POST | `/file-ops` | `FileOperation` | 远程 delete/rename/mkdir | | POST | `/file-clipboard` | `FileClipboardApply` | 远程 copy/move 批量粘贴 | | POST | `/browse` | `SyncBrowse` | 远程目录列表(POST 兼容路径) | | POST | `/browse-local` | `SyncBrowseLocal` | Nexus 本机 staging 目录浏览 | | POST | `/local-file-ops` | `LocalFileOperation` | staging 内 delete/rename | | POST | `/local-file-preview` | `LocalFilePreview` | staging 文件预览 | | POST | `/cancel` | `SyncCancel` | 按 batch_id 取消推送 | | POST | `/reconcile-stale-logs` | `ReconcileStaleLogs` | 将 stuck `running` 日志标失败 | | POST | `/validate-source-path` | `ValidateSourcePath` | 校验本机源目录 | | POST | `/diagnose` | `SyncDiagnose` | 单台推送失败诊断 | | POST | `/file-diff` | `FileSyncDiff` | 本地 vs 远程单文件 diff | | POST | `/upload` | multipart | 上传到 staging | | POST | `/upload-zip` | multipart | ZIP 解压到 staging | | POST | `/verify` | `SyncVerify` | 推送后 md5 校验 | | POST | `/download` | `FileDownload` | 远程文件下载流 | | POST | `/read-file` | `FileRead` | 读远程文本(≤5MB) | | POST | `/write-file` | `FileWrite` | 写远程文本 + 可选 etag 乐观锁 | | POST | `/chmod` | `FileChmod` | chmod/chown | | POST | `/compress` | `FileCompress` | tar.gz/zip 压缩 | | POST | `/decompress` | `FileDecompress` | 解压到绝对路径 | ### A.1 核心模型字段 **SyncFiles**(推送) | 字段 | 类型 | 默认 | 约束 | |------|------|------|------| | `server_ids` | `int[]` | — | min 1 | | `source_path` | `str` | — | Nexus 本机绝对路径,禁系统敏感路径 | | `target_path` | `str?` | null | 远程绝对路径;空则用各服务器 `target_path` | | `sync_mode` | `str` | `incremental` | `incremental\|full\|overwrite\|checksum` | | `batch_size` | `int` | 50 | 1–200 | | `concurrency` | `int` | 10 | 1–50 | | `batch_id` | `str?` | null | 12 位 hex;须先连 `/ws/sync` 再 POST | **SyncPreview** | 字段 | 说明 | |------|------| | `server_id` | 代表服务器(单台 dry-run) | | `verbose` | true 时返回文件列表(≤200 行) | | 其余 | 同 SyncFiles 的 path/mode | **FileOperation** | operation | 必填 | 远程命令 | |-----------|------|----------| | `delete` | `path` | `rm -rf` | | `rename` | `path`, `new_path` | `mv` | | `mkdir` | `path` | `mkdir -p` | **FileClipboardApply** | 字段 | 约束 | |------|------| | `mode` | `copy` → `cp -r`;`move` → `mv -t` | | `sources` | 1–50 条远程绝对路径 | | `dest_dir` | 目标目录绝对路径 | **FileRead / FileWrite** | 字段 | FileRead | FileWrite | |------|----------|-----------| | `max_size` | 默认 1MB,max 5MB | — | | `content` | — | max 5MB | | `etag` | — | 乐观锁,冲突 409 | **FileChmod** | 字段 | 说明 | |------|------| | `mode` | 3–4 位八进制 `^[0-7]{3,4}$` | | `owner` / `group` | 可选;group 须同时有 owner | | `recursive` | 目录递归 | ### A.2 推送 WS 协议(`/ws/sync`) 1. 前端生成 `batch_id`(12 hex) 2. **先** `WebSocket` 连接 `/ws/sync?token=&batch_id=` 3. **再** `POST /api/sync/files` 带相同 `batch_id` 4. 消息: per-server 状态(pending/running/success/failed/cancelled)、进度百分比、错误摘要 5. 取消: `POST /api/sync/cancel` `{ batch_id }` ### A.3 文件浏览双通道 | 通道 | 方法 | 优势 | |------|------|------| | REST | `GET /api/files/browse?server_id=&path=` | ETag + 304,前端首选 | | Sync 兼容 | `POST /api/sync/browse` | 旧网关/部署回退 | 前端 `useFilesBrowse.fetchBrowseListing` 先 GET,失败再 POST。 --- ## 附录 B — 文件页(Files)调用链 ### B.1 组件树 ``` FilesPage.vue ├── FilesToolbar.vue # 服务器选择、刷新、上传、新建 ├── FilesPermissionAlert.vue # files-capability / sudo 提示 ├── FilesStatusBar.vue # 条目统计、SSH 用户 ├── FilesBreadcrumb.vue # 路径面包屑 ├── FilesList.vue # v-data-table + 行操作 ├── FilesDialogs.vue # 删除/重命名/chmod/压缩等对话框 └── FileEditorWorkbench.vue # Monaco 编辑 + 预览 ``` `provideFilesPage(page)` → 子组件通过 `injectFilesPage()` 取上下文(`filesPageContext.ts`)。 ### B.2 Composable 依赖图 ``` useFilesPage ├── useFilesStore (Pinia) # selectedServer, currentPath, files, cache ├── useFilesSelection # selectedFiles 多选 ├── useFilesBrowse(fileFilter) # browse(), ETag, preload Monaco │ └── GET /api/files/browse | POST /api/sync/browse ├── useFilesPermissions # files-capability, setup-files-sudo │ └── GET /api/servers/{id}/files-capability │ └── POST /api/servers/{id}/setup-files-sudo ├── useFilesNavigation # 面包屑、goUp、openEntry、?server_id= ├── useFilesEditor # read/write/preview │ └── POST /api/sync/read-file | write-file ├── useFilesActions # CRUD、clipboard、upload、compress │ └── POST /api/sync/file-ops | file-clipboard | upload | chmod | compress… ├── useFilesClipboard # 跨目录 copy/cut/paste 状态 └── useFilesHotkeys # Ctrl+C/V、Delete、F5 刷新 ``` ### B.3 关键 API 映射(用户操作 → 端点) | 用户操作 | Composable | API | |----------|------------|-----| | 选服务器/进目录 | `useFilesBrowse.browse` | `GET /files/browse` | | 保存编辑器 | `useFilesEditor.save` | `POST /sync/write-file` | | 删除/重命名/新建目录 | `useFilesActions` | `POST /sync/file-ops` | | 复制/剪切/粘贴 | `useFilesActions` + clipboard | `POST /sync/file-clipboard` | | 上传 | `useFilesActions.doUpload` | `POST /sync/upload` | | chmod | `useFilesActions.doChmod` | `POST /sync/chmod` | | 压缩/解压 | `useFilesActions` | `POST /sync/compress` / `decompress` | | 下载 | `useFilesActions.downloadFile` | `POST /sync/download` | | 配置 sudo | `useFilesPermissions.setupFilesSudo` | `POST /servers/{id}/setup-files-sudo` | | 打开终端 | `useFilesNavigation.openInTerminal` | 路由 `#/terminal?server_id=` | ### B.4 Store 缓存策略(`stores/files.ts`) - Key: `{serverId}:{path}` - 存 ETag + entries + fetchedAt - `invalidateAfterMutation` 在写操作后清除当前目录及父目录缓存 - `pageReady`: 首次 browse 完成后为 true(避免空态闪烁) ### B.5 路由 Query 参数 | 参数 | 行为 | |------|------| | `?server_id=123` | `useFilesNavigation` 自动选中并 browse | | `?path=/var/www` | 初始路径(须与 server_id 同用) | --- ## 附录 C — 推送页(Push)调用链 ### C.1 Composable 依赖图 ``` usePushPage ├── usePushForm # source_path, target_path, sync_mode, batch 参数 ├── usePushServers # 服务器列表、分类、全选、在线过滤 ├── usePushLogs # sync_logs 历史分页 ├── usePushProgress # WS + doPush + cancel │ └── WS /ws/sync │ └── POST /api/sync/files, /cancel └── usePushPreview # 预览对话框、confirmAndPush └── POST /api/sync/preview (并发代表服务器) ``` ### C.2 推送流程(时序) ```mermaid sequenceDiagram participant UI as PushPage participant WS as /ws/sync participant API as /api/sync/files participant Eng as SyncEngineV2 UI->>UI: generate batch_id (12 hex) UI->>WS: connect(token, batch_id) UI->>API: POST /files {batch_id, server_ids, ...} Eng->>WS: per-server progress WS->>UI: update pushStatus Eng->>Eng: rsync over SSH (semaphore) UI->>API: POST /cancel (optional) ``` ### C.3 相关端点(Push 页间接使用) | 功能 | API | |------|-----| | 校验源路径 | `POST /sync/validate-source-path` | | ZIP 源 | `POST /sync/upload-zip` | | 失败诊断 | `POST /sync/diagnose` | | 对账 stuck 日志 | `POST /sync/reconcile-stale-logs` | | 推送后校验 | `POST /sync/verify` | --- ## 附录 D — 终端页(Terminal)调用链 ``` TerminalPage.vue ├── TerminalToolbar.vue ├── TerminalTabBar.vue ├── TerminalServerPicker.vue ├── TerminalArea.vue # xterm 实例 ├── TerminalCmdBar.vue # 命令输入 + 高亮 └── TerminalQuickBar.vue # 快捷命令 useTerminalSessions ├── POST /api/auth/webssh-token { server_id } ├── WebSocket /ws/terminal/{session_uuid}?token= ├── Koko: TERMINAL_INIT, DATA, RESIZE, CLOSE └── onBeforeUnmount → disposeAllSessions useTerminalQuickCommands → GET/POST /api/terminal/quick-commands useTerminalSettings → localStorage 主题/字体(非敏感) ``` **WebSSH JWT**: `purpose=webssh`, 绑定 `server_id`, 15min;路径 param `id` 须与 token 内 server_id 一致。 --- ## 附录 E — 其余 API 模块明细 ### E.1 调度 `/api/schedules`(`settings.py` 内) | 方法 | 路径 | 说明 | |------|------|------| | GET | `/` | 列表(含 `next_run`、`target_path`、`schedule_type`、`run_mode`) | | POST | `/` | 创建 push:`source_path`、`target_path?`、`sync_mode`、`cron_expr`/`fire_at`;script:`script_id`/`script_content` | | PUT | `/{id}` | 更新(allowlist 含 `target_path`) | | DELETE | `/{id}` | 删除 | `PushSchedule.target_path` 留空时 runner / `runNow` 与各机 `servers.target_path` 一致。 后台 `schedule_runner` 每分钟扫描 `enabled=1` 且到期项 → SyncEngineV2 或 ScriptService。 ### E.2 重试 `/api/retries` | 方法 | 路径 | 说明 | |------|------|------| | GET | `/` | 待重试队列 | | POST | `/{id}/retry` | 手动触发 | | DELETE | `/{id}` | 移出队列 | `retry_runner` 每 5min,`SELECT … FOR UPDATE SKIP LOCKED`,指数退避,max 3 次。 ### E.3 审计 `/api/audit` | 方法 | 路径 | Query | |------|------|-------| | GET | `/` | `page`, `page_size`, `action`, `username`, `date_from`, `date_to` | ### E.4 告警 `/api/alert-history` | 方法 | 路径 | 说明 | |------|------|------| | GET | `/` | 分页列表 | | GET | `/stats` | 汇总统计 | ### E.5 预设 | 前缀 | 用途 | |------|------| | `/api/presets` | 密码预设 CRUD | | `/api/ssh-key-presets` | SSH 密钥预设 CRUD | | `/api/scripts/credentials` | DB 凭据(脚本 `$DB_*`) | ### E.6 脚本 `/api/scripts` | 方法 | 路径 | 说明 | |------|------|------| | POST | `/exec` | `{ script_id?, command?, server_ids, timeout }` | | GET | `/executions` | 分页执行记录 | | POST | `/executions/{id}/stop` | SIGTERM/SIGKILL | | POST | `/executions/{id}/retry` | 重新执行 | | GET | `/exec-config` | 超时阈值等前端展示用 | ### E.7 Agent `/api/agent` **Heartbeat body(摘要)** ```json { "server_id": 1, "cpu": 12.5, "memory": 45.0, "disk": 60.0, "load": [0.1, 0.2, 0.15], "agent_version": "6.0.x", "system_info": { "hostname": "...", "os": "..." } } ``` **Script callback**: Agent 长任务完成后 POST,更新 Redis + MySQL execution。 ### E.8 搜索 `/api/search` `GET /?q=` — 并行查 servers、scripts、db_credentials、schedules(limit 各 10)。 --- ## 附录 F — 前端页面 ↔ 组件 ↔ Composable 映射 | 页面 | 主要 Composable | 关键子组件 | |------|-----------------|------------| | DashboardPage | `useWebSocket`, `useServerPagination` | StatCardsRow | | ServersPage | `useServerFormDialog`, `useServerPagination` | ServerFormDialog | | TerminalPage | `useTerminalSessions`, `useTerminalCmdBar` | TerminalArea, TerminalTabBar | | FilesPage | `useFilesPage` | FilesToolbar, FilesList, FileEditorWorkbench | | PushPage | `usePushPage` | PushToolbar, PushServerGrid, PushProgressList | | ScriptsPage | 页内逻辑 + Monaco | ScriptEditorDialog | | CredentialsPage | 三 Tab CRUD | — | | SchedulesPage | cron 表单 | ScheduleFormDialog | | RetriesPage | 列表 + retry | — | | CommandsPage | 双视图切换 | — | | AlertsPage | 过滤 + 分页 | — | | AuditPage | `normalizeAuditList` | — | | SettingsPage | TOTP/API Key/IP 白名单 | TerminalQuickCommandsSettings | | LoginPage | `useAuthStore.login` | — | ### F.1 全局布局(`App.vue`) - 侧栏 14 项导航(v-list) - 顶栏:全局搜索 → `GET /api/search` - 用户菜单:主题切换、退出 - `useWebSocket` 告警 toast + Dashboard 刷新触发 ### F.2 类型定义 所有 API 响应类型优先维护在 `frontend/src/types/api.ts`;新增字段先改类型再改 composable。 --- ## 附录 G — 验收与 E2E 对照 ### G.1 L4 统一入口 ```bash python3 scripts/run_nexus_acceptance.py --base http://127.0.0.1:8600 --with-ui ``` 阶段: | Phase | 内容 | |-------|------| | API | `tests/test_api.py` + `tests/test_api_extended.py` | | UI | `frontend/e2e/full-acceptance.spec.mjs`(15 用例) | | Unit | `--with-unit` 时跑路径/权限单测 | 功能 rollup 映射: `tests/acceptance_catalog.py` → `FEATURES[]`。 ### G.2 Playwright 用例 ↔ 页面 | 用例 ID | 路由 | 验证点 | |---------|------|--------| | 01 | `#/` | 登录 + Dashboard 卡片 | | 02 | `#/servers` | 添加对话框、批量选择 | | 03 | `#/` | 全局搜索输入 | | 04 | `#/terminal` | 终端页可见 | | 05 | `#/files` | 文件页可见 | | 06 | `#/push` | 预览按钮(不真推送) | | 07 | `#/scripts` | 新建脚本对话框 | | 08 | `#/credentials` | 三 Tab 切换 | | 09 | `#/schedules` | 新建调度对话框 | | 10 | `#/retries` | 重试列表 | | 11 | `#/commands` | 命令/会话视图 | | 12 | `#/alerts` | 告警页 | | 13 | `#/audit` | 审计页 | | 14 | `#/settings` | 设置区块 | | 15 | `#/` | 主题切换 + 登出 | **环境变量** ```bash NEXUS_E2E_BASE=http://127.0.0.1:8600 NEXUS_E2E_USER=admin NEXUS_E2E_PASSWORD=*** # 必填;测试账号勿开 TOTP ``` ### G.3 API 测试环境 与 E2E 共用 `.env` 中 `NEXUS_TEST_ADMIN_PASSWORD`(`seed_local_admin.py` 写入)。 ### G.4 本地验证脚本 `bash scripts/local_verify.sh` — ruff、import、pytest 子集、前端 typecheck(若可用)。 --- ## 附录 J — 凭据轮询与 pending servers 定义: `server/application/services/credential_poller.py` · 路由: `server/api/servers.py` · 表: `pending_servers` ### J.1 API | 方法 | 路径 | 请求体 | 成功响应 | 失败/待定 | |------|------|--------|----------|-----------| | POST | `/api/servers/add-by-ip` | `AddByIpRequest`: `domain`(必填), `port?`(默认 22), `name?` | `{ success: true, server, matched_preset, matched_username }` | `{ success: false, pending_id, errors[], message }` — 写入 pending | | GET | `/api/servers/pending` | — | `{ items: PendingServer[], total }` | — | | POST | `/api/servers/pending/{id}/retry` | `{}` | 同 add-by-ip | 同 add-by-ip;域名已存在 → **409** 并删除 pending | | DELETE | `/api/servers/pending/{id}` | — | **204** | 404 | **PollErrorItem**(`errors[]`): `preset_type`(`password` \| `ssh_key`)、`preset_name`、`username`、`error`。 **PendingServer**(列表项): `id`, `name`, `domain`, `port`, `attempts`, `last_error`, `last_attempt_at`, `created_at` 及可选 `agent_port`, `target_path`, `category`, `platform_id`, `node_id`。 ### J.2 轮询顺序 1. 解密全部 `password_presets`(按 id 升序),再全部 `ssh_key_presets` 2. 每条使用预设 `username`(缺省 `root`)调用 `try_ssh_login` 3. 首条成功 → `create_server` + 审计 `create_server`;全部失败 → `pending_servers` upsert + 审计 `add_by_ip_failed` ### J.3 前端(ServersPage) | 用户操作 | API | |----------|-----| | 打开「快速添加」对话框 | — | | 提交 IP | `POST /servers/add-by-ip` | | 失败弹窗展示 `errors[]` | — | | 页面加载 / 刷新失败列表 | `GET /servers/pending` | | 行内重试 | `POST /servers/pending/{id}/retry` | | 行内删除 | `DELETE /servers/pending/{id}` | 类型定义: `frontend/src/types/api.ts` — `PendingServerItem`, `AddByIpResponse`, `PollErrorItem`。 ### J.4 预设 username(CredentialsPage) | 端点 | 新增字段 | |------|----------| | `POST/PUT /api/presets/` | `username`(默认 `root`) | | `POST/PUT /api/ssh-key-presets/` | `username`(默认 `root`) | | `GET` 列表 | 每项含 `username` | --- ## 附录 H — 错误码与前端处理约定 | HTTP | 场景 | 前端处理 | |------|------|----------| | 401 | JWT 过期 | `fetchAuthed` 自动 refresh 一次 | | 202 | 需 TOTP | `TotpRequiredError` → 显示 TOTP 输入 | | 304 | Browse ETag 命中 | 用缓存 entries | | 409 | write-file etag 冲突 | 提示合并/覆盖 | | 429 | 登录锁定 | Login 页显示剩余时间 | | 503 | 安装模式 | 引导 `/app/install.html` | 统一错误文案: `formatApiError(err)` + `$snackbar`。 --- ## 附录 I — 新增功能检查清单(扩展) ### 新 Sync 相关端点 - [ ] `schemas.py` Pydantic + path validator - [ ] `sync_v2.py` 路由 + `_audit_sync` - [ ] `tests/test_api.py` 或 `test_sync_*.py` - [ ] 前端 `http.post('/sync/...')` + types - [ ] 更新附录 A + 主文档 §10 - [ ] changelog ≥10 行 ### 新 Files 操作 - [ ] `useFilesActions` 方法 + `FilesDialogs` UI - [ ] 热键(若适用)注册到 `useFilesHotkeys` - [ ] browse 缓存 invalidate - [ ] E2E 05 或专项用例 ### 新页面(第 15 页) - [ ] `pages/XxxPage.vue` - [ ] `router/index.ts` + `App.vue` nav - [ ] `acceptance_catalog.py` + Playwright 用例 - [ ] `run_nexus_acceptance` 报告可 rollup --- **维护**: 改 API/composable 时同步更新对应附录章节。