记录凭据轮询/pending 契约对照与生产验证,更新功能指南与附录 J,并将 pending 相关类型集中到 api.ts。 Co-authored-by: Cursor <cursoragent@cursor.com>
18 KiB
Nexus 6.0 — 功能开发指南 · 附录(详细参考)
主文档: 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)
- 前端生成
batch_id(12 hex) - 先
WebSocket连接/ws/sync?token=&batch_id= - 再
POST /api/sync/files带相同batch_id - 消息: per-server 状态(pending/running/success/failed/cancelled)、进度百分比、错误摘要
- 取消:
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 推送流程(时序)
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(摘要)
{
"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 统一入口
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 | #/ |
主题切换 + 登出 |
环境变量
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 轮询顺序
- 解密全部
password_presets(按 id 升序),再全部ssh_key_presets - 每条使用预设
username(缺省root)调用try_ssh_login - 首条成功 →
create_server+ 审计create_server;全部失败 →pending_serversupsert + 审计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.pyPydantic + path validatorsync_v2.py路由 +_audit_synctests/test_api.py或test_sync_*.py- 前端
http.post('/sync/...')+ types - 更新附录 A + 主文档 §10
- changelog ≥10 行
新 Files 操作
useFilesActions方法 +FilesDialogsUI- 热键(若适用)注册到
useFilesHotkeys - browse 缓存 invalidate
- E2E 05 或专项用例
新页面(第 15 页)
pages/XxxPage.vuerouter/index.ts+App.vuenavacceptance_catalog.py+ Playwright 用例run_nexus_acceptance报告可 rollup
维护: 改 API/composable 时同步更新对应附录章节。