Files
Nexus/docs/project/nexus-functional-development-guide-appendix.md
T

507 lines
18 KiB
Markdown
Raw Normal View History

# 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 | 1200 |
| `concurrency` | `int` | 10 | 150 |
| `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` | 150 条远程绝对路径 |
| `dest_dir` | 目标目录绝对路径 |
**FileRead / FileWrite**
| 字段 | FileRead | FileWrite |
|------|----------|-----------|
| `max_size` | 默认 1MBmax 5MB | — |
| `content` | — | max 5MB |
| `etag` | — | 乐观锁,冲突 409 |
**FileChmod**
| 字段 | 说明 |
|------|------|
| `mode` | 34 位八进制 `^[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、scheduleslimit 各 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 预设 usernameCredentialsPage
| 端点 | 新增字段 |
|------|----------|
| `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 时同步更新对应附录章节。