95 KiB
Web 接口兼容审计与 DBX 对照
审计日期:2026-10-04。范围:生产前端 src/ 的命令调用、Web HTTP/WS/SSE 适配器、nyaterm-web 的命令与专用路由、共享 Core,以及 temp/dbx 的桌面/Web 实现。下方审计结论、统计和接口清单保留修复前基线;实现状态以新增的“修复进展”节为准。候选缺口不等同于已复现错误。
修复进展(2026-10-04)
本轮已完成主要公共 UI 缺口与浏览器替代流程,并追加 Web 文件、上传和附着恢复修复。合并状态和本轮验证以 Web Beta 合并准备报告 为准。web-api-inventory.json 现由 pnpm web:audit 重新生成,包含当前调用位置及静态边界分类;下方 276 个命令、462 个调用点及 184 个未匹配命令和末尾表格保留为原始审计基线。不得将历史“未匹配”当作当前实现状态或错误数量。
| 功能 | 已实现行为 |
|---|---|
| 命令建议与快捷命令 | 返回真实 {commands,categories};保存、upsert、计数、搜索、导入、变更事件与持久历史可用;恢复建议和快捷命令面板。共享导入保留既有排序、创建时间和使用次数。Web 仅把显式提交记入历史,shell 确认候选真实返回 false,不从终端输出猜测命令。 |
| 普通配置 | 实现语言保存、组递归删除、连接清空、连接/组/凭据排序、known_hosts 清空以及连接图标/监控资产更新。语言仍属于服务端实例共享 UI 配置,不是独立浏览器偏好。 |
| SFTP | 图片/PDF 二进制预览、链接创建/目标替换、非递归权限与数值 UID/GID 修改、普通文件复制/移动、缺失项查询。预览上限 25 MiB,受调用方更低限额约束;普通流式复制上限 1 GiB,支持所属会话间复制/移动;目录仅支持同会话移动。支持重复项询问、跳过、改名和覆盖,取消/失败清理临时文件。 |
| 终端生命周期 | Web 后台标签不再调度桌面 renderer detach;保留浏览器输出策略。隐藏持久录制与深度历史入口,浏览器可导出当前 xterm 缓冲区文本(含正确的软换行拼接)。 |
| 秘密解锁 | verify_master_password 真正验证已配置主密码;未配置时验证 Web 登录密码。使用固定长度摘要常量时间比较及尝试限速,错误密码返回 false,不更换加密密钥。四种语言的 UI 已说明 Web 验证方式。 |
| OTP | 加密保存/删除、遮蔽列表、秘密读取、TOTP/HOTP 生成及 URI 解析;HOTP 成功生成后原子更新计数器。浏览器解析二维码图片。 |
| 笔记 | 树、CRUD、移动/重命名/删除、版本冲突与事件;浏览器下载 JSON snapshot。版本冲突保留 Revision conflict 语义并返回 HTTP 409。 |
| 翻译与远端监控 | 抽离 Tauri 依赖并共享翻译/监控实现;开放资源、GPU、NPU、进程监控及白名单信号操作。命令通过当前用户所属 SSH 会话的独立 exec 通道执行,限制时间、并发与输出,不进入交互 PTY。 |
| 浏览器文件操作 | 主题 JSON 导入导出、快捷命令 JSON/WindTerm 导入与 JSON 导出、连接/AI 图标、背景图片、剪贴板 PNG 上传及终端文本下载使用 File/Blob。图标和背景图有大小/尺寸限制;桌面背景文件路径在 Web 不触发服务端文件读取。 |
| 其他公共接口 | About 返回真实 Web runtime 与服务端 OS,隐藏 ConPTY;AI 审计写入强制脱敏,provider key 读取恢复。 |
SFTP 覆盖和链接目标替换使用 OpenSSH posix-rename@openssh.com 原子替换扩展;远端不支持时明确失败,保留原目标。仅发送需要修改的属性,避免库的默认零值覆盖文件大小、时间戳或所有者;未指定的 UID/GID 保持原值。目前所有者/组名解析、递归属性修改、目录复制和跨会话目录移动尚未实现。
秘密解锁沿用桌面的“界面揭示前验证”语义。服务端秘密 API 仍由 Web 登录与 CSRF 保护,没有新增逐次解锁授权 lease;主密码修改继续保持桌面流程。共享存储、配置、命令历史与笔记遵循现有单实例契约,会话、提示和远端操作按登录 owner 隔离。
保留边界
深度终端历史、持久录制、递归传输/完整任务取消、云备份、HTTP MCP、RDP 与 Docker 管理仍需独立服务端设计;本轮保持相关入口隔离。客户端本地终端、当前串口、托盘、系统快捷键、本机 CLI、native plugin 和文件 watcher 继续保持桌面专属。浏览器快捷命令导入支持 JSON/WindTerm,XTS 压缩包导入仍属于桌面流程。Web 尚无终端 CWD 跟踪,CWD 查询返回真实 null,不声称目录跟随已恢复。
前轮验证(历史记录)
- 共享 Core:355 项单元测试通过,包含迁移后的翻译、监控和快捷命令测试。
- Web:4 项集成测试全部通过,覆盖 HTTP 数据契约、配置持久化、真实密码/限速、RFC HOTP 计数、笔记冲突、SSH/SFTP/WS/SSE owner 边界、预览限额、流式复制移动、权限、链接原子替换及独立监控通道。
- 前端:相关回归测试共 55 项通过,覆盖命令建议、后台终端、笔记、文件剪贴板、终端菜单/搜索以及浏览器 PNG 上传和背景路径隔离。
pnpm build:web、TypeScript、pnpm lint、四语言格式检查及桌面cargo check --locked通过。lint 仍有原有CommandSuggestions.tsxhook dependency 警告;构建仍提示较大的 chunk 和旧 Browserslist 数据。- 本轮未进行真实浏览器交互/视觉验收、外部翻译服务实测或 GPU/NPU 硬件实测;自动化覆盖不能替代这些环境验证。
原始审计结论(历史记录)
当前问题是接口覆盖、数据契约和能力边界不完整,并不是 Web 性能不足。SSH/Telnet/VNC 的主连接路径已经存在,但公共 UI 中仍有未适配的入口;某些功能虽然不再报 501,却只返回空数据,不能算实现完成。
src/lib/backend 是前端的平台/传输适配层,真正的 Web 后端是 Rust 的 src-tauri/crates/nyaterm-web。supports() 是运行环境能力判断,不是 HTTP 接口实现,也不是服务端鉴权。UI 隐藏入口、调用层选择合适实现、服务端验证权限和参数,需要同时成立。
静态扫描发现 276 个直接使用字符串命令名的命令、462 个调用点:72 个能匹配 Web 服务端处理入口,20 个由前端 HTTP/浏览器适配器处理,184 个没有在这两个入口集合中找到。还有 18 处动态命令表达式。184 不等于 184 个 Web 报错:包括已被运行环境分支、面板过滤隔离的桌面命令,且动态命令不能仅靠字符串统计确认。72 中也包括占位处理,不代表功能全部可用。
已确认的公共入口缺口
| 优先级 | 功能与触发条件 | 当前接口/行为 | Web 适合度与处理建议 |
|---|---|---|---|
| P0 | 命令建议;输入命令、手动打开建议 | fuzzy_search_commands 未注册;get_command_history / fuzzy_search_history 只返回 [];get_quick_commands 返回 [],但调用方需要 {commands,categories} |
适合实现。复用 Core 的 config/quick_command.rs、core/history、utils/fuzzy,补齐读写、搜索、事件后恢复建议和设置。修正返回形状是必要条件 |
| P0 | 快捷命令;终端右键“保存为快捷命令”、AI 保存命令、底部快捷命令面板 | upsert_quick_command、save_quick_commands、increment_quick_command_use_count 未注册;部分入口不受侧栏面板过滤保护 |
适合实现。统一快捷命令 CRUD 与事件;浏览器文件导入导出单独适配。不能只隐藏侧栏或建议开关 |
| P0 | 后台 SSH/Telnet 标签空闲超过 120 秒 | xterminalHibernationController 调用未实现的 detach_session_renderer;没有 Web 平台分支 |
现有桌面协议不适合直接照搬。先关闭 Web 的深度 renderer detach;继续保留输出调度、渲染节流。将来实现 Web 专用的暂停/快照/重放协议。不能伪造 detach 成功,否则可能丢输出 |
| P0 | 凭据管理;已有主密码时点击解锁 | verify_master_password 未注册,而 SecretUnlockFooter 仍依赖它 |
需要专门设计 Web 登录与秘密解锁语义。必须保留真实验证,不能返回 true 或用隐藏错误替代。主密码变更/密钥迁移继续隔离 |
| P1 | 语言切换 | Header 调用未注册的 save_app_language |
适合实现。复用设置更新,明确浏览器 UI 偏好与服务器实例配置的归属 |
| P1 | 保存连接树;删除文件夹、清空连接、拖动排序 | delete_group、clear_all_connections、reorder_items 未注册 |
适合实现。共享配置操作、子组删除语义、排序语义和 connections-changed 事件;不能只删当前层或只在前端乐观更新 |
| P1 | known_hosts 的“清空” | get_known_hosts、单项删除已实现,clear_known_hosts 未注册 |
适合实现。保持现有确认对话框与服务端写入验证,清空后下次 SSH 仍应重新验证指纹 |
| P1 | 凭据拖动排序 | reorder_credentials 未注册 |
适合实现;共享排序规则,避免前端排序成功后刷新回退 |
| P1 | 会话快速切换器;输入查询 | fuzzy_search_candidates 未注册 |
适合实现。可在浏览器本地搜索,或用共享 Core 的无副作用搜索;不需要因为缺接口隐藏整个切换器 |
| P1 | AI 上下文、文件管理器“同步 CWD” | 调用 get_terminal_cwd;服务端只处理 try_get_terminal_cwd / get_session_cwd,且返回 null |
分两步。AI 上下文应接受“暂无 CWD”并停止错误请求;CWD 同步须有实际跟踪结果才能启用,不能仅增加别名就声称完成 |
| P1 | 终端搜索;切换“深度历史” | terminal_history_search 未实现,而模式按钮可见 |
适合后续实现,但要补有界终端历史、分页/搜索、会话 ownership。当前仅开放实际可用的缓冲区搜索;不要返回空成功来冒充深度历史搜索 |
| P1 | SFTP 图片/PDF 预览 | 动态生成 read_remote_file_bytes,没有服务端分支;文本预览已实现 |
适合实现。复用已有远程读流和 Core 的文件类型;校验 maxBytes、取消和响应形状。动态命令必须纳入审计 |
| P1 | SFTP 创建链接、修改权限/所有者/链接目标 | create_remote_symlink、update_remote_file_attributes、update_remote_symlink_target 未实现 |
适合实现。是远端 SFTP 操作,不依赖客户端本地文件系统;保持路径、权限和会话验证 |
| P1 | SFTP 新建文件/目录 | 前端传递 mode,Web 分支没有应用 mode | 接口存在仍有语义差异。应用用户选择的权限,失败应如实返回,不应静默忽略参数 |
| P1 | SFTP 文件复制/剪切/粘贴 | 动态 copy_file_entry / move_file_entry、find_missing_remote_entries 缺失 |
同一远端 rename/move 适合优先实现;跨会话复制和递归目录需任务、取消、重名处理。暂未实现的动作应单独 gate,不能把整个 SFTP 隐藏 |
| P1 | AI 命令插入/保存等动作的审计 | append_ai_audit 未注册;共享 Core 已有实现 |
适合实现。保留脱敏与审计字段,复用 Core;失败不应长期靠 .catch(() => {}) 掩盖 |
| P1 | AI 设置;主动查看 provider API key | reveal_ai_provider_api_key 未注册 |
适合在既定登录/秘密解锁策略下实现;列表仍保持掩码。与通用 secret getters 使用一致策略 |
| P1 | 新建/编辑连接的自定义图标 | 仍通过 native dialog + import_connection_icon(path);删除 delete_connection_custom_icon 缺失 |
浏览器 File 选择、图片校验/缩放和 data URL/上传替代路径参数;删除配置元数据适合服务端实现 |
| P1 | 主题导入导出、AI provider 图标、背景图片 | 仍使用 native dialog、read_theme_file / write_theme_file / import_ai_provider_icon / 本地图片路径 |
适合浏览器替代:File、JSON 解析、Blob 下载、受限图片资源。客户端文件路径不能被当作 Docker 主机路径;桌面保存的本地背景路径也不应触发 Web 读文件请求 |
| P2 | About 对话框 | get_support_info 未注册,打开即查询 |
适合返回 Web 版本/运行环境信息;ConPTY 等桌面信息不显示。服务器 OS 与浏览器 OS 需区分 |
| P2 | OTP 管理页 | get_otp_entries 在 HTTP adapter 返回空列表;保存/删除/生成/二维码导入缺失,OTP Tab 仍可见 |
TOTP 存储、生成适合服务端实现;二维码适合浏览器上传/解析。保留手工 SSH keyboard-interactive 提示不依赖 OTP 管理库。不要将占位空数组称为完成 |
| P2 | 笔记、翻译、远程 GPU/NPU/进程/Docker 管理 | 目前面板或设置大多被隔离,相关接口未注册;翻译仍有终端右键入口 | 都有适合 Web 的部分。笔记、翻译可复用 Core/API;监控和 Docker 针对用户 SSH 的远端主机执行,绝不能改成管理 Docker 部署宿主机。需要抽离 Tauri 依赖后实现;未完成前隔离所有入口 |
重点源码:src/hooks/useCommandHistory.ts,src/components/terminal/xterminalHibernationController.ts,src/components/terminal/TerminalContextMenu.tsx,src/components/terminal/TerminalSearchBar.tsx,src/components/dialog/terminal/SessionQuickSwitcherDialog.tsx,src/components/panel/file-explorer/FilePreviewContent.tsx,src/components/dialog/file-explorer/PropertiesDialog.tsx,src/components/panel/ai/AIAssistantPanel.tsx,src/components/panel/security-auth/SecretUnlockFooter.tsx。
适合 Web、浏览器替代和桌面专属的边界
| 分类 | 功能 | 判断 |
|---|---|---|
| 应直接补齐 Web API | 连接/组/排序、凭据、known_hosts、快捷命令、命令建议、笔记、OTP、远端 SFTP 元数据、AI 通用设置/审计、翻译 | 多数是 Core 数据操作或远程协议,不需要用户电脑原生能力。缺路由不是“不适合 Web” |
| 需要浏览器替代 | 文件导入导出、图标、主题、背景图、剪贴板、子窗口、导出当前终端文本 | 使用 File/Blob、Clipboard API、同源 iframe;浏览器权限/安全上下文单独处理。不能为了复用旧接口开放服务器任意本地路径 |
| 能做,但需要服务端专门设计 | 持久终端录制/深度搜索、递归传输/任务取消、远程监控与 Docker 管理、云同步备份、SSH 高级功能、Web HTTP MCP、RDP | 不属于浏览器本质限制;需要存储限额、任务生命周期、网络/会话归属和配置。当前可以隐藏未完成入口,但理由是阶段/协议差异,不是“不可能实现” |
| 保持桌面专属 | 用户电脑本地 Shell、当前串口协议、系统托盘、系统全局快捷键、原生窗口透明/拖动/最小化、打开本地目录、OS credential manager、桌面自更新、用户电脑的 Codex/Claude CLI、native plugin、桌面文件 watcher | 现有 Web 部署无法直接访问客户端 OS。不要把“本地终端”替换成 Docker 服务器 shell。WebSerial 等需要全新协议与浏览器支持,不能复用当前服务器串口调用 |
| 仅隔离桌面底层协议 | Tauri Channel、桌面输出 ACK credits、renderer detach/snapshot、native drag-drop 路径 | 在 transport/platform 层替换或明确拒绝;这些差异不应传播成大量业务 UI 分支 |
现有 ACK 适配是合理的:Web WS 没有桌面的字节确认协议,浏览器适配层完成本地 ACK 状态即可,不能持续请求 ack_session_output。与此不同,对存在实际副作用的命令(保存、删除、解锁、detach 等)不可伪造成功。
现有设置与面板 gates 已隔离许多功能,例如 AiAgentsTab、云同步、录制运行状态、网络隧道管理、桌面 VNC Channel。不应把这些已隔离调用算成当前可复现错误。不过,侧栏/设置 gate 不会自动覆盖右键菜单、快捷键、AI 动作、子页面和后台生命周期;本次发现的缺口正包含这些路径。
DBX 是怎样做的
DBX 没有使用 NyaTerm 的 supports() 或这条 import 路径,但并不是没有平台差异判断。它使用 isTauriRuntime(),在 transport 层、Vue 组件和 composables 中都能找到判断。
| 层次 | DBX 源码 | 做法与可借鉴点 |
|---|---|---|
| 统一 API | temp/dbx/apps/desktop/src/lib/backend/api.ts:12 |
getBackend() 懒加载 tauri.ts / http.ts;forward() 转发具名业务方法。业务 UI 不直接拼未知 IPC 命令 |
| 两套适配 | temp/dbx/apps/desktop/src/lib/backend/http.ts、tauri.ts |
导入共同的请求/响应类型。适合 Web 的功能有实际 HTTP 实现;纯桌面操作在 HTTP 方法中明确拒绝;部分只读 OS 查询返回明确空值 |
| 浏览器持久化 | temp/dbx/apps/desktop/src/lib/backend/browserAppStateStorage.ts、http.ts:2338 |
UI/打开标签等存 IndexedDB,localStorage 兜底;不将所有浏览器 UI 状态写成实例共享配置。NyaTerm 当前的 save_app_ui_settings 更新共享 settings 文档,值得单独评估 |
| 浏览器文件适配 | temp/dbx/apps/desktop/src/lib/backend/http.ts:979 |
离线导入明确要求 File;文件上传用 multipart,下载用浏览器链接。不能把客户端 path 等同于服务器 path |
| 服务端共享核心 | temp/dbx/crates/dbx-web/src/routes/history.rs |
Web 路由调共享 state.app.storage;历史保存/搜索/删除有实际实现,不是统一空数组 |
| 真正不支持的能力 | temp/dbx/crates/dbx-web/src/routes/ai.rs:122 |
Web 拒绝桌面 CLI provider;UI 也通过 runtime 隔离。这与把 REST provider 的 AI 功能整体隐藏不同 |
| MCP | temp/dbx/crates/dbx-web/src/web_mcp.rs、main.rs:1388 |
有独立 Web MCP endpoint,状态区分 management availability。说明 MCP 本身适合 Web,桌面 stdio/本机集成与 Web HTTP 服务是不同实现 |
| 契约测试 | temp/dbx/apps/desktop/src/lib/backend/__tests__/userSkillsCapabilityContract.spec.ts 等 |
同时检查桌面入口、HTTP 方法、Web 路由、拒绝行为;已经做过 browser state、cloud sync、import/export 等契约测试 |
因此,不建议照搬 DBX 的所有代码或删掉 NyaTerm 的能力判断。应该借鉴它对具名 API、响应类型、浏览器状态/文件替代、契约测试的组织。NyaTerm 现有统一传输与共享 Core 方向可保留。
桌面 Rust 的 temp/dbx/src-tauri/src/commands/history.rs:9 调用 state.storage.save_history_entry;Web 的 crates/dbx-web/src/routes/history.rs:30 调用 state.app.storage.save_history_entry。WebState 包含共享 Core 的 Arc<AppState>。两端共享的是实际存储/业务服务,Tauri command 与 Axum route 分别处理各自 transport;Rust 中不会导入 TypeScript 的 supports()。
对 supports() 的具体调整建议
- 将“环境固有限制”和“本版本尚未实现”明确区分。
nativeWindows/tray是环境差异;commandSuggestions/remoteMonitoring是当前实现状态。后者未来可启用,不应被固定成 Web 永久不支持。 - UI 能力判断保留在功能入口层;原生窗口、文件、剪贴板、输出 ACK 等替代集中到 adapter。避免把业务能力错误地归因于
nativeFiles/nativeWindows,例如远端目录下载、SSH 隧道并不是原生窗口功能。 - 使用具名、类型明确的操作覆盖主要业务调用,或定义命令请求/响应映射。当前
invoke<T>(任意字符串)中 T 只代表调用方断言,不能验证实际 HTTP 响应形状。 - 建立单一命令/能力清单,标记真实实现、浏览器替代、桌面专属、暂未实现。服务端按实现状态报告能力,前端 UI 消费它;不要依赖几份手写清单各自同步。
- 不为减少 F12 日志而普遍
.catch(() => {})、统一return []/null或关闭所有功能。无副作用的 optional OS 信息可以降级,关键副作用必须实现或显式不可用。 - 兼容验收覆盖完整入口:主页面、设置、侧栏、右键菜单、快捷键、AI 动作、子 iframe,以及 120 秒以上的后台会话。HTTP unit test 通过不等于 Docker 真实浏览器验收。
建议实现顺序
第一批:真实快捷命令与搜索 API、恢复建议、修正 DTO、会话切换搜索、连接树/语言/known_hosts 基础操作;同时隔离未适配的 renderer detach。秘密解锁先确认 Web 策略,禁止假成功。
第二批:SFTP 二进制预览、元数据修改、移动/复制;浏览器主题/图标/导入导出;AI 审计与密钥显式读取。补齐接口事件与跨 iframe 刷新。
第三批:OTP、笔记、翻译、SSH 远端监控/进程/Docker 管理。按模块抽 Core service,再接 desktop/Web adapter。
第四批:终端持久记录/深度搜索、递归传输任务、云备份、SSH 扩展、HTTP MCP/RDP 等独立协议和生命周期工作。未实现前按具体能力隐藏入口。
每批新增真实集成/契约测试:请求名/参数/响应形状/事件一致;Web 不调用 native command;拒绝路径不伪造成功;mutation 保持 ownership、CSRF 与现有加密流程。也验证 /nyaterm/ 等 base path 和浏览器 HTTP/HTTPS 差异。
全量静态命令清单
下面的“服务端入口”只表示能找到匹配处理,“前端适配”只表示在 HTTP/浏览器 adapter 中能找到对应命令;两者都不证明功能语义完整。“未匹配”包括已隔离的桌面专属调用,需要结合上面的触发路径判断。所有调用位置列在 JSON 快照中。
完整调用点:web-api-inventory.json。扫描使用 TypeScript AST 提取直接调用,服务端匹配检查 commands.rs 与 SFTP/AI allowlist,动态表达式另列。
动态命令表达式
这些表达式不包括在 276 个直接字符串命令统计中。通用转发的 cmd / command 也被记录,不能视为额外业务命令。
| 调用位置 | 表达式 |
|---|---|
| src/pages/RemoteFileEditorPage.tsx:336 | tab.backend === "local" ? "open_local_file_text" : "open_remote_file_text" |
| src/pages/RemoteFileEditorPage.tsx:508 | tab.backend === "local" ? "write_local_file_text" : "write_remote_file_text" |
| src/context/TransferContext.tsx:602 | request.moveSource ? "move_file_entry" : "copy_file_entry" |
| src/lib/invoke.ts:165 | cmd |
| src/lib/backend/tauri.ts:6 | ...args |
| src/lib/backend/api.ts:13 | command |
| src/components/terminal/TerminalContextMenu.tsx:240 | command |
| src/components/panel/RecordingPanel.tsx:132 | command |
| src/components/panel/NetworkPanel.tsx:774 | open ? "open_tunnel" : "close_tunnel" |
| src/components/panel/NetworkPanel.tsx:793 | groupDialog.tab === "proxy" ? "save_proxy_group" : "save_tunnel_group" |
| src/components/panel/NetworkPanel.tsx:811 | deleteGroupState.tab === "proxy" ? "delete_proxy_group" : "delete_tunnel_group" |
| src/components/dialog/file-explorer/NewItemDialog.tsx:89 | command |
| src/components/panel/file-explorer/FilePreviewContent.tsx:166 | command |
| src/components/panel/file-explorer/FileExplorer.tsx:1693 | backend === "local" ? "get_local_home_dir" : "get_home_dir" |
| src/components/panel/file-explorer/FileExplorer.tsx:2906 | backend === "local" ? "read_local_file_text" : "read_remote_file_text" |
| src/components/panel/file-explorer/FileExplorer.tsx:3456 | backend === "local" ? "open_local_file_text" : "open_remote_file_text" |
| src/components/panel/file-explorer/FileDocumentEditor.tsx:94 | pane.file.backend === "local" ? "write_local_file_text" : "write_remote_file_text" |
| src/components/panel/file-explorer/FileDocumentEditor.tsx:148 | pane.file.backend === "local" ? "open_local_file_text" : "open_remote_file_text" |