# Nexus 6.0 — 功能开发指南 · 附录（详细参考）

> **主文档**: [nexus-functional-development-guide.md](nexus-functional-development-guide.md)  
> **版本**: 2026-06-04  
> **用途**: 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 | `/` | 列表 |
| POST | `/` | 创建（cron + server_ids + source/target + sync_mode 或 script_id） |
| PUT | `/{id}` | 更新 |
| DELETE | `/{id}` | 删除 |

后台 `schedule_runner` 每分钟扫描 `enabled=1` 且 cron 到期项 → 调 SyncEngine 或 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（若可用）。

---

## 附录 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 时同步更新对应附录章节。
