31 KiB
设置页 → 远程连接 — WebUI 服务 (F-WEBUI)
本文档覆盖「设置 → 远程连接」页面 WebUI Tab 的全部功能,包括服务启停、远程访问控制、认证管理、QR 码登录、配置持久化、页面结构、状态同步、扩展系统 WebUI 贡献。 基于静态代码分析和动态 UI 验证综合整理。
Channels Tab 相关内容见 channels.md。
(F-WEBUI-01) WebUI 服务启停 [已实现]
用户故事:作为桌面端用户,我希望通过一个开关快速启用或禁用 WebUI 服务,以便在需要时通过浏览器远程访问 AionUi。
前置条件:运行在 Electron 桌面端(isElectronDesktop() === true)
正常流程(用户视角):
- 用户进入「设置 → 远程连接」页面,默认选中 "WebUI" Tab
- 页面显示 WebUI 服务卡片,卡片内第一行为"启用 WebUI",右侧有 Switch 开关
- 用户打开开关(OFF → ON)
- 开关显示 loading 旋转,旁边出现橙色"启动中..."文字
- 启动成功后:开关变为蓝色 checked 状态,旁边显示绿色"✓ 运行中",toast 提示"WebUI 启动成功"
- 下方出现"访问地址"行(见 F-WEBUI-03)
- 如为首次启动且有初始密码,密码行显示明文(见 F-WEBUI-06)
- 用户关闭开关(ON → OFF)
- 访问地址行和 QR 码区域消失,toast 提示"WebUI 已停止"
异常情况:
- 启动 IPC 调用失败(网络错误/主进程异常):Switch 回滚到关闭状态,toast 提示"操作失败"(
settings.webui.operationFailed) - 启动 IPC 超时(3 秒未返回):UI 乐观地认为服务已启动,显示为运行中,同时持久化 enabled=true(已知局限:可能导致 UI 与实际状态不一致,且下次启动时循环恢复失败服务)
- 端口被占用:服务器自动尝试下一个端口(
port + 1),递增上限固定为DEFAULT_PORT + 10(默认端口:生产 25808,开发 25809,上限 25818/25819);当用户通过 CLI/环境变量指定端口超出此范围时,不触发递增,直接报错 - 停止操作为 fire-and-forget 模式:UI 和 ConfigStorage 先行更新为停止状态 → toast 提示"WebUI 已停止"(
settings.webui.stopSuccess)→ 然后才异步调用webui.stop.invoke()。Toast 出现时服务器可能仍在运行 - 服务器已在运行时再次收到启动请求:先停止旧实例(关闭所有 WebSocket 连接、释放端口),再启动新实例
验收标准:
- Switch 开关可切换 WebUI 服务启停
- 启动过程显示 loading 状态 + "启动中…" 文字(以 i18n
settings.webui.starting实际值为准) - 启动成功后显示绿色"✓ 运行中"标记
- 启动/停止成功各有对应 toast 提示(
settings.webui.startSuccess/settings.webui.stopSuccess) - 启动失败时 Switch 回滚到关闭状态 + toast 提示(
settings.webui.operationFailed) - 端口被占用时自动递增,上限固定为
DEFAULT_PORT + 10
(F-WEBUI-02) 启动恢复与配置持久化 [已实现]
用户故事:作为用户,我希望启用 WebUI 后下次打开应用时能自动恢复服务,无需每次手动开启。
正常流程(用户视角):
- 用户启用 WebUI 服务(F-WEBUI-01)
- 系统在启动成功后将
webui.desktop.enabled = true写入 ConfigStorage - 用户关闭并重新打开应用
- 应用在后台自动启动 WebUI 服务(非阻塞,不影响应用启动速度)
- 进入设置页面时显示为已启动状态
配置来源:
端口解析(覆盖式优先级):CLI 参数(--port / --webui-port)> 环境变量(AIONUI_PORT / PORT)> 配置文件(userData/webui.config.json)> 默认值(25808/25809)
远程访问(多源 OR 聚合 — 只要任一来源为 true 即启用):isRemoteMode || 环境变量(AIONUI_ALLOW_REMOTE / AIONUI_REMOTE / AIONUI_HOST=0.0.0.0)|| 配置文件(allowRemote: true)|| ConfigStorage 偏好
两种启动路径的配置差异:
| 启动路径 | 端口解析 | 远程访问解析 |
|---|---|---|
| CLI/服务器模式 | 经过 resolveWebUIPort(CLI > env > config > default) |
经过 resolveRemoteAccess(多源 OR 聚合) |
| 桌面自动恢复 | 仅从 ConfigStorage 读取(不经过 resolve) | 仅从 ConfigStorage 读取(不经过 resolve) |
异常情况:
- 自动恢复失败(端口全部占用/其他错误):仅记录日志,不影响应用正常启动
- 配置文件 JSON 解析失败:使用空配置
{},不报错 - ConfigStorage 读取失败:默认为未启用
验收标准:
- 启用 WebUI 后,重启应用时自动恢复 WebUI 服务 [验证策略:集成测试]
- 禁用 WebUI 后,重启应用不会自动启动服务 [验证策略:集成测试]
- 启动失败不影响应用正常使用 [验证策略:集成测试]
- 持久化在启动成功后才写入(失败不写入,避免循环恢复失败)[验证策略:evaluate_script 读 ConfigStorage]
(F-WEBUI-03) 访问地址展示 [已实现]
用户故事:作为用户,我希望 WebUI 启动后能看到访问地址,方便在浏览器中打开或分享给他人。
前置条件:WebUI 服务正在运行(status.running === true)
正常流程(用户视角):
- WebUI 启动后,服务卡片内出现"访问地址"行
- 根据"允许远程访问"开关状态显示不同地址:
- 远程访问关闭:显示
http://localhost:{port} - 远程访问开启:显示
http://{局域网IP}:{port}(如http://192.168.3.15:25809)
- 远程访问关闭:显示
- 地址以蓝色链接样式显示,点击在系统默认浏览器中打开
- 地址旁有复制按钮,点击复制到剪贴板,toast 提示"复制成功"
异常情况:
- 点击链接打开浏览器失败:仅记录日志,无用户可见提示
- 局域网 IP 获取失败(无网卡/无 IPv4 地址):退回显示
localhost
验收标准:
- 仅在 WebUI 运行时显示访问地址行
- 远程访问开启时显示局域网 IP 地址
- 远程访问关闭时显示 localhost
- 点击地址在系统浏览器中打开
- 复制按钮可复制地址 + toast 提示
(F-WEBUI-04) 远程访问控制 [已实现]
用户故事:作为用户,我希望控制是否允许局域网内其他设备访问 WebUI,以便用手机或其他电脑访问 AionUi。
正常流程(用户视角):
- 服务卡片内"允许远程访问"行,右侧有 Switch 开关
- 开关下方显示说明文字("使用远程软件/服务器可安全远程访问")和蓝色"查看方式"链接
- 用户打开开关(OFF → ON)
- 如果 WebUI 正在运行:系统执行停止→重启流程(因为需要切换监听地址从
127.0.0.1到0.0.0.0),显示 loading,toast 提示"WebUI 已重启" - 访问地址从
localhost变为局域网 IP,QR 码区域出现(见 F-WEBUI-08) - 如果 WebUI 未运行:仅保存偏好,不触发重启
异常情况:
- 重启失败(停止后无法重新启动):系统二次确认服务状态(调用 getStatus),如确认未运行则回滚开关 + toast(
settings.webui.operationFailed) - 重启超时:最大等待 6s(停止 1.5s + 启动 3s + 状态确认 1.5s),超时后同上处理
- 持久化失败:回滚开关状态 + toast(
settings.webui.operationFailed) - "查看方式"链接:点击打开 GitHub Wiki 远程访问指南页面(
shell.openExternal)
验收标准:
- 切换开关可启用/禁用远程访问
- WebUI 运行中切换时,执行自动重启流程
- 重启成功有 toast 提示(
settings.webui.restartSuccess) - 重启失败回滚开关状态 + toast 提示(
settings.webui.operationFailed) - 设置在 WebUI 未运行时也可修改并持久化,下次启动生效
- "查看方式"链接可打开指南页面
(F-WEBUI-05) 用户名管理 [已实现]
用户故事:作为用户,我希望能查看和修改 WebUI 的登录用户名,以便个性化我的登录凭据。
正常流程(用户视角):
- 登录信息卡片(始终可见,无论 WebUI 是否启用)显示"用户名:"行
- 当前用户名明文显示(默认 "admin")
- 旁有复制按钮(复制用户名)和编辑按钮(铅笔图标)
- 点击编辑按钮弹出"设置新用户名"弹窗
- 输入框预填当前用户名
- 用户输入新用户名,点击"确定"
- 提交成功:toast"用户名修改成功",弹窗关闭,显示更新为新用户名
表单校验规则:
| 规则 | 前端校验 | 后端校验 | 错误提示 |
|---|---|---|---|
| 必填 | 是 | - | 前端 i18n 提示 |
| 最少 3 字符 | 是 | 是 | 前端 i18n / 后端英文原文(未 i18n 化) |
| 最多 32 字符 | 是 | 是 | 前端 i18n / 后端英文原文(未 i18n 化) |
仅允许 [a-zA-Z0-9_-] |
是 | 是 | 前端 i18n / 后端英文原文(未 i18n 化) |
不能以 _ 或 - 开头/结尾 |
是 | 是 | 前端 i18n / 后端英文原文(未 i18n 化) |
| 用户名已存在(不同用户) | - | 是 | "Username already exists"(英文原文) |
异常情况:
- 前端校验失败:表单内行内提示,不提交
- 后端校验失败:toast 显示后端返回的英文原文错误信息(注:与密码修改不同,用户名后端错误码未做前端 i18n 翻译)
- IPC 调用异常:toast(
settings.webui.usernameChangeFailed) - 用户名与当前相同:后端直接返回成功,不执行更新
- 修改成功后:所有已存在的登录 token 失效(通过 JWT secret 轮转实现,为被动失效 — 已建立的 WebSocket 连接不会立即断开,需等到下次请求或心跳时才被拒绝)
验收标准:
- 用户名默认显示"admin"
- 复制按钮可复制用户名
- 编辑按钮打开"设置新用户名"弹窗,预填当前值
- 前端校验规则全部生效
- 提交成功后 toast 提示 + 弹窗关闭 + UI 更新
- 提交失败有明确错误提示
- 修改后已有 WebUI 登录会话被踢出
(F-WEBUI-06) 密码显示与初始密码 [已实现]
用户故事:作为用户,我希望在首次启动 WebUI 时能看到系统生成的初始密码,之后密码显示为遮罩状态,以保护安全。
正常流程(用户视角):
- 登录信息卡片中"初始密码:"行(始终可见)
- 首次启动(有初始密码):显示明文密码(12 至 16 字符,含两端;含小写 + 大写字母 + 数字 + 特殊字符
!@#$%^&*) - 非首次启动(无初始密码/已修改密码):显示"**"(密码遮罩)
- 旁有编辑按钮(铅笔图标),hover 时 tooltip 提示"忘记密码?点击设置新密码(不需要当前密码)"
- 注意:密码行仅有编辑按钮,无复制功能(与用户名行不同,用户名行有复制 + 编辑两个按钮)
异常情况:
- 服务运行中但无初始密码且无缓存密码:显示"**",用户需通过编辑按钮设置新密码
- 密码可见状态(
canShowPlainPassword)是组件内存状态,页面刷新后重置为遮罩
验收标准:
- 首次启动时密码明文可见
- 用户修改密码后切换为遮罩显示
- 编辑按钮 tooltip 提示修改密码不需要当前密码
- 页面刷新后密码回到遮罩状态
- 密码行仅有编辑按钮,无复制按钮
(F-WEBUI-07) 密码修改 [已实现]
用户故事:作为用户,我希望能设置自定义密码替换系统生成的初始密码,或在忘记密码时直接设置新密码。
正常流程(用户视角):
- 点击密码行的编辑按钮
- 弹出"设置新密码"弹窗
- 输入新密码(Password 输入框,带可见性切换图标,placeholder"请输入新密码(至少8位)")
- 输入确认密码(placeholder"请再次输入新密码")
- 点击"确定"
- 提交成功:toast"密码修改成功",弹窗关闭,密码行切换为"**"
表单校验规则:
| 规则 | 校验端 | 错误提示 |
|---|---|---|
| 新密码必填 | 前端 | "请输入新密码" |
| 最少 8 字符 | 前端+后端 | i18n 提示 / PASSWORD_TOO_SHORT |
| 最多 128 字符 | 后端 | PASSWORD_TOO_LONG |
| 弱密码黑名单 | 后端 | PASSWORD_TOO_COMMON |
| 确认密码必填 | 前端 | "请再次输入新密码" |
| 两次密码一致 | 前端 | "两次密码不一致" |
弱密码黑名单:password, 12345678, 123456789, qwertyui, abcdefgh
异常情况:
- 前端校验失败:表单内行内提示,不提交
- 后端返回多个错误码(
'; '分隔):前端翻译后合并显示(如PASSWORD_TOO_SHORT→ i18n 短密码提示) - 后端返回未知错误码:显示原始错误信息,或兜底"密码修改失败"
- IPC 调用异常:toast"密码修改失败"
技术说明:
- 不需要当前密码验证:changePassword API 直接设置新密码(安全性依赖 Electron 本地环境)
- 密码修改后全部 token 失效:通过 JWT secret 轮转(
invalidateAllTokens生成新 secret)实现。这是被动失效机制 — 旧 token 不会被主动推送下线,而是在下次请求时验证失败被拒绝。已建立的 WebSocket 连接不会立即断开,需等到心跳或重连时才会被拒绝 - 后端清除初始密码:
clearInitialAdminPassword()清除内存中的初始密码,后续 getStatus 不再返回 initialPassword
验收标准:
- 点击编辑按钮打开"设置新密码"弹窗
- 不需要输入当前密码
- 新密码和确认密码均有 Password 可见性切换
- 前端校验(必填、最短 8 位、两次一致)生效
- 后端校验(最长 128 位、弱密码黑名单)生效
- 提交成功后 toast 提示(
settings.webui.passwordChanged)+ 弹窗关闭 + 密码切换为遮罩 - 修改后已有 WebUI 登录 token 被动失效
(F-WEBUI-08) QR 码登录 [已实现]
用户故事:作为用户,我希望在允许远程访问时能通过手机扫描二维码快速登录 WebUI,无需手动输入地址和密码。
前置条件:WebUI 服务运行中(status.running === true)且允许远程访问(status.allowRemote === true)
正常流程(用户视角):
- 登录信息卡片下方出现分隔线 + "二维码登录"区块
- 说明文字:"使用手机扫描二维码,即可在手机浏览器中自动登录"
- 系统自动生成二维码(140x140 SVG,纠错级别 M)
- 二维码下方显示有效期(本地化时间格式,24h 或 12h 取决于系统 locale;使用
toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })) - 旁有复制按钮(复制二维码对应的 URL)和刷新按钮
- 用户用手机扫描二维码,自动在手机浏览器打开并登录 WebUI
- 二维码在过期前自动刷新(token 5 分钟过期,定时器 4 分钟触发刷新)
QR 码 URL 格式:http://{lanIP}:{port}/qr-login?token={64位hex}
Token 安全机制:
| 安全特性 | 说明 |
|---|---|
| 随机生成 | crypto.randomBytes(32) → 64 位 hex |
| 有效期 | 5 分钟(QR_TOKEN_EXPIRY = 5 * 60 * 1000) |
| 一次性使用 | 验证后标记 used: true 并立即从内存中删除 |
| IP 限制 | 非远程模式下仅允许本地/局域网 IP 使用 |
| 内存存储 | 存储在进程内 Map,进程重启后全部失效 |
自动生成/清除逻辑:
- 服务运行 + 允许远程 + 无 QR URL → 自动生成
- 服务停止或关闭远程访问 → 自动清除二维码 + 取消定时器
异常情况:
- 生成失败(WebUI 未运行/IPC 异常):QR 区域显示占位文案(
settings.webui.qrGenerateFailed)+ toast(同一 i18n key)。注意:初始状态(尚未生成)和生成失败共用同一占位文案,组件无法区分两种状态 - Token 已过期:扫码后提示"QR token has expired"
- Token 已使用(重复扫码):提示"QR token has already been used"
- 非本地 IP 使用 local-only token:拒绝,提示"QR login is only allowed from local network"
- 加载中:QR 区域显示
common.loading对应文案(以 i18n 实际值为准),刷新按钮显示旋转动画
验收标准:
- 仅在 WebUI 运行 + 允许远程访问时显示 QR 区域
- 二维码自动生成,无需用户操作
- 显示有效期(本地化时间格式)
- QR 码在过期前自动刷新 [验证策略:代码审查 + 单元测试]
- 可手动点击刷新按钮
- 可复制二维码链接
- 扫码后可在手机浏览器中自动登录
- Token 为一次性使用,重复扫码失败
- 服务停止或关闭远程时 QR 区域消失
(F-WEBUI-09) 页面结构 — WebUI / Channels 双 Tab [已实现]
用户故事:作为用户,我希望在"远程连接"设置页面中方便地切换 WebUI 服务配置和 Channel 渠道配置。
正常流程(用户视角):
- 桌面端:页面顶部显示两个 Tab
- "WebUI"(图标:地球)— 默认选中
- "Channels"(图标:通讯 + 7 个渠道 logo 缩略图:Telegram/Lark/DingTalk/WeChat/WeCom/Slack/Discord)
- 点击 Tab 切换内容区域
- Channels Tab 内容通过懒加载方式加载,加载中显示"加载中..."
- 非桌面端(WebUI 浏览器访问):无 Tab 切换,直接显示 Channels 配置内容(无 WebUI 服务管理功能)
页面布局:
- 导航路径:左侧栏"远程连接"(位于"应用"分组下,在"显示"和"桌面宠物"之间)
- URL hash:
#/settings/webui - WebUI Tab 内部结构:
- 标题"WebUI"(h2)+ 功能描述 + 3 步引导提示条
- WebUI 服务卡片(含蓝色提示横幅 + 启用开关 + 访问地址 + 远程访问开关)
- 登录信息卡片(含用户名 + 密码 + QR 码登录)
验收标准:
- 桌面端显示 WebUI / Channels 双 Tab
- 默认选中 WebUI Tab
- Tab 切换后内容区域渲染完成,Channels 懒加载期间显示 loading 占位
- 仅在 Electron 桌面端渲染 WebUI 服务配置区域(含 Tab 切换)
- 非桌面端仅显示 Channels,无 Tab 切换
(F-WEBUI-10) 状态实时同步 [已实现]
用户故事:作为用户,我希望设置页面能实时反映 WebUI 服务的最新状态,即使状态由其他来源(如应用菜单、启动恢复)触发变更。
正常流程(用户视角):
- 组件挂载时自动加载 WebUI 当前状态(偏好设置 + 服务运行状态)
- 主进程触发的状态变更(启动/停止/端口变化)通过事件实时推送到设置页面
- UI 自动更新所有相关元素(Switch 状态、运行标记、访问地址、QR 码区域)
IPC 双通道机制:
- Electron 环境:优先使用
electronAPI.webuiGetStatus()(直接 IPC,无超时问题) - 非 Electron 后备:使用
webui.getStatus.invoke()(bridge 模式,1.5s 超时) - 实时事件:
webui.statusChanged.on()监听主进程推送的状态变更
异常情况:
- 偏好设置读取失败:默认为未启用/不允许远程
- getStatus 调用失败或超时:使用兜底状态(
running: false, port: DEFAULT_PORT, adminUsername: 'admin') - 主进程 emit 的状态事件丢失:不影响 UI 已有状态,仅下次操作时刷新
验收标准:
- 页面进入时自动加载最新状态
- 状态变更实时反映到 UI(无需手动刷新)
- 状态加载失败时使用合理的兜底默认值
(F-WEBUI-11) 扩展系统 WebUI 贡献 [已实现] [验证策略:单元测试]
用户故事:作为扩展开发者,我希望能通过扩展 manifest 声明 API 路由和静态资源,自动注册到 WebUI 服务器。
正常流程(开发者视角):
- 在扩展的
manifest.contributes.webui中声明apiRoutes和/或staticAssets - 应用加载扩展时,
resolveWebuiContributions校验并注册贡献 - API 路由和静态资源在 WebUI 服务器上可用
安全校验规则:
| 规则 | 说明 |
|---|---|
| 命名空间化 | 路径必须以 /{extensionName}/ 开头 |
| 保留路径保护 | 不允许使用 /, /api, /login, /logout, /qr-login, /static, /assets |
| 路径冲突检测 | 跨扩展重复路径,后者被跳过 |
| 路径遍历防护 | isPathWithinDirectory 确保所有文件在扩展目录内 |
| 入口存在性检查 | 同时查找 dist 和 source 路径 |
| 静态资源目录存在性 | existsSync 检查目录 |
已知局限:
wsHandlers和middleware已声明但运行时不支持(仅console.warn提示)
验收标准:
- 合法的扩展 API 路由和静态资源可被注册
- 命名空间化和保留路径校验生效
- 路径遍历尝试被阻止
- 路径冲突时后者被跳过(不覆盖先注册的)
附录 A:WebUI 状态矩阵
| WebUI 开关 | 允许远程 | 访问地址 | QR 码区域 | 登录信息 |
|---|---|---|---|---|
| OFF | (any) | 不显示 | 不显示 | 显示 |
| ON | OFF | http://localhost:{port} |
不显示 | 显示 |
| ON | ON | http://{lanIP}:{port} |
显示 | 显示 |
附录 B:IPC 通信链路
┌──────────────────────────────────────────────────────────────────────┐
│ 渲染进程 (Renderer) - WebuiModalContent.tsx │
│ │
│ 状态加载: │
│ ├─ ConfigStorage.get('webui.desktop.enabled') │
│ ├─ ConfigStorage.get('webui.desktop.allowRemote') │
│ ├─ electronAPI.webuiGetStatus() [优先] │
│ └─ webui.getStatus.invoke() [后备, 1.5s timeout] │
│ │
│ 服务启停: │
│ ├─ webui.start.invoke({ port, allowRemote }) [3s timeout] │
│ ├─ webui.stop.invoke() [fire-and-forget] │
│ └─ webui.statusChanged.on(callback) [实时监听] │
│ │
│ 认证管理 (双通道: electronAPI 优先, bridge 后备): │
│ ├─ webuiChangePassword / webui.changePassword.invoke │
│ ├─ webuiChangeUsername / webui.changeUsername.invoke │
│ └─ webuiGenerateQRToken / webui.generateQRToken.invoke │
│ │
│ 外部操作: │
│ ├─ shell.openExternal.invoke(url) [打开浏览器/指南] │
│ └─ navigator.clipboard.writeText(text) [复制] │
└──────────────────────────────────────────────────────────────────────┘
│ IPC Bridge + Direct IPC
┌─────────────────────▼────────────────────────────────────────────────┐
│ 主进程 (Main) - webuiBridge.ts │
│ │
│ Bridge providers: │
│ ├─ webui.getStatus → WebuiService.getStatus() │
│ ├─ webui.start → startWebServerWithInstance() │
│ ├─ webui.stop → server.close() + cleanupWebAdapter() │
│ ├─ webui.changePassword → WebuiService.changePassword() │
│ ├─ webui.changeUsername → WebuiService.changeUsername() │
│ ├─ webui.generateQRToken → generateQRLoginUrlDirect() │
│ └─ webui.verifyQRToken → verifyQRTokenDirect() │
│ │
│ Emitters (主→渲染): │
│ ├─ webui.statusChanged.emit({ running, port, localUrl }) │
│ └─ webui.resetPasswordResult.emit({ success, newPassword }) │
└─────────────────────┬────────────────────────────────────────────────┘
│
┌─────────────────────▼────────────────────────────────────────────────┐
│ 服务层 │
│ WebuiService: getStatus / changePassword / changeUsername / │
│ resetPassword / getLanIP │
│ webuiQR: generateQRLoginUrlDirect / verifyQRTokenDirect │
│ AuthService: validatePasswordStrength / validateUsername / │
│ generateRandomPassword / hashPassword / invalidateAllTokens │
└──────────────────────────────────────────────────────────────────────┘
附录 C:Toast 通知汇总
| 触发操作 | Toast 类型 | i18n Key |
|---|---|---|
| WebUI 启动成功 | success | settings.webui.startSuccess |
| WebUI 停止成功 | success | settings.webui.stopSuccess |
| 远程访问切换重启成功 | success | settings.webui.restartSuccess |
| 启动/停止/重启失败 | error | settings.webui.operationFailed |
| 复制成功 | success | common.copySuccess |
| 用户名修改成功 | success | settings.webui.usernameChanged |
| 用户名修改失败 | error | settings.webui.usernameChangeFailed |
| 密码修改成功 | success | settings.webui.passwordChanged |
| 密码修改失败 | error | settings.webui.passwordChangeFailed |
| QR 码生成失败 | error | settings.webui.qrGenerateFailed |
附录 D:已知局限汇总
| # | 功能点 | 局限描述 | 来源 |
|---|---|---|---|
| 1 | F-WEBUI-01 | 启动 IPC 超时 3s 后乐观设置 running: true,同时持久化 enabled=true,可能导致 UI 与实际状态不一致且下次启动循环恢复失败服务 |
R2 初稿 + C-04 |
| 2 | F-WEBUI-01 | 停止操作 fire-and-forget,Toast 出现时服务器可能仍在运行,停止失败用户无感知 | R2 初稿 + C-05 |
| 3 | F-WEBUI-04 | 远程访问切换需重启服务器,期间短暂不可用(最长 6s:停止 1.5s + 启动 3s + 状态确认 1.5s) | R2 初稿 + C-06 |
| 4 | F-WEBUI-06 | 密码首次可见状态是组件内存状态,页面刷新后丢失 | R2 初稿 |
| 5 | F-WEBUI-06 | 密码行仅有编辑按钮,无复制功能(与用户名行功能不对称) | C-07 |
| 6 | F-WEBUI-07 | 修改密码不需要输入当前密码,安全性依赖 Electron 本地环境信任 | R2 初稿 |
| 7 | F-WEBUI-05 | 用户名后端校验错误信息为硬编码英文,未做前端 i18n 翻译(密码修改有 errorCodeMap 翻译) | C-10 |
| 8 | F-WEBUI-05/07 | Token 失效通过 JWT secret 轮转实现(被动失效),已建立的 WebSocket 连接不会立即断开 | C-13 |
| 9 | F-WEBUI-08 | QR token 存储在进程内存 Map,主进程重启后全部失效 | R2 初稿 |
| 10 | F-WEBUI-08 | isLocalIP 不覆盖 IPv6 ULA 地址(fd00::/8) |
R2 初稿 |
| 11 | F-WEBUI-08 | QR 码初始状态(尚未生成)和生成失败共用同一占位文案,组件无法区分两种状态 | C-11 |
| 12 | F-WEBUI-02 | UI 未暴露端口修改入口,仅可通过配置文件/CLI/环境变量修改 | R2 初稿 |
| 13 | F-WEBUI-02 | 桌面自动恢复(restoreDesktopWebUIFromPreferences)不经过 resolveWebUIPort/resolveRemoteAccess,CLI 和环境变量在此路径下不生效 |
C-08 |
| 14 | F-WEBUI-01 | 端口递增上限固定为 DEFAULT_PORT + 10,用户指定端口超出此范围时递增不生效 |
C-01 |