fix web ui smoke and brand assets

This commit is contained in:
hectorzhao
2026-06-30 11:13:30 +08:00
parent 0dfb2988b2
commit 9920575bba
103 changed files with 6650 additions and 135 deletions
+158 -5
View File
@@ -4,11 +4,11 @@
## 当前状态
- 当前任务:需求修改/二期变更:供应商启停移除、客户启停入口、落地网关 CPS 数字校验、当前通话 SIP 状态
- 本次变更摘要:供应商启停移除、客户启停入口、落地网关 CPS 数字校验、当前通话 SIP 状态,以及登录 500 的完整 Argon2id vendor artifact 修复,已提交并发布到 B 新 release `s45-vendor-cps-sipstate-20260629113500`
- 总体状态:S30 已完成;本地 KVM 开发环境 V2 闭环原冻结 release 为 `s28-v2-20260621220924`B 二期冻结基线为 `s42-phase2-business-prefix-20260624140000`,当前 release 为 `s45-vendor-cps-sipstate-20260629113500`B preflight 通过,API ready 返回 okHTTPS 首页加载前端资产 `index-CBRZZA7t.js` / `index-B2uYBtS3.css`Redis `cfg:active_version=s28-v1`A 当前 EVALSHA 脚本在 Redis 中存在;A 已加载二期 Lua 热路径 SHA1 `aff9995dbda2a2425e95a3e25b5d7ce194348ce7`OpenSIPS 配置 SHA-256 为 `f163fa37f7aaa492e12ed496cb82f07d9b0f1030a6ed297855a22d2c4bf1c8a5`A/B/T 目标服务均 activeB 登录依赖 Argon2id 完整 vendor 目录已纳入 release artifact;当前仍未导入真实 80 万手机号段;阿里云迁移前需按 S30 Runbook 重新演练
- 最后更新:2026-06-29 12:12 +08:00
- 当前阻塞:无阻塞。遗留问题:本次未执行数据库 schema 变更,未改 A OpenSIPS/Lua/RTPEngine 或 T 配置;真实 80 万手机号段尚未导入;OpenSIPS dialog 残留根因仍待单独分析;真实 B 服务器浏览器登录态逐页按钮、真实录音文件播放和 Nginx/X-Accel 链路仍需用户测试复核;S40 呼叫时脚本内置 UAS 端口提示已占用但现有 UAS 正常接起且通话/CDR/计费成功;A/T 各有一个无关 `fwupd-refresh.service` failed unit,未处理。
- 当前任务:菜单展开 Logo 增加 SIP 文字并发布到 B
- 本次变更摘要:已核对 `tests/reports/` 下 8.2-8.10 远程测试结果;修复客户充值接口负数扣款、active call hangup 超长/无分隔 ID 参数校验、Fastify 路由参数长度限制,以及既有环境号码库内置角色权限补齐迁移。追加修复业务前缀管理页面 `normalizeBusinessPrefix is not defined`、号码库页面 `emptyNumberLibraryRows is not defined`、客户网关编辑触发 React #137 的前端运行时错误。最新 s53 在菜单展开 Logo 后增加 `SIP` 文字,收起菜单仍展示 `logo2`,并发布 Web-only release。新增测试门禁:`pnpm lint` 覆盖 `apps/web/src/**``pnpm test:remote-web-ui` 使用真实浏览器登录并逐个点击核心菜单及每页一个安全主操作,客户网关编辑动作会验证业务前缀 checkbox 可 false->true->false 往返切换。B 当前 release 为 `s53-brand-logo-sip-text-20260630105500`;准备提交 Git。
- 总体状态:S30 已完成;本地 KVM 开发环境 V2 闭环原冻结 release 为 `s28-v2-20260621220924`B 二期冻结基线为 `s42-phase2-business-prefix-20260624140000`,当前 release 为 `s53-brand-logo-sip-text-20260630105500`2026-06-30 开机后已恢复 A/B/T 服务:A `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-redis-auth-proxy``lisglosips-node-exporter``lisglosips-recording-finalize.timer` active`lisglosips-redis-hotpath-load.service` Result=successB `mysql``redis-server``nginx``lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``lisglosips@config-publisher``heplify-server``lisglosips-prometheus``grafana-server` activepreflight okAPI ready okT `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-s28-uas``apache2``mariadb/mysql` active;三机 `systemctl --failed` 均为 0。B preflight、远程 smoke、8.2/8.7/8.8 定向复测均通过;业务前缀、号码库、客户网关编辑与前缀选择 Web-only 修复后,s53 当前首页引用 Web JS `index-CrjpbrU-.js`、CSS `index-CrJGI5_R.css`,并包含 `/favicon.ico``/brand/logo1.png``/brand/logo2.png`;远程 smoke `REMOTE_SMOKE_20260630T031146Z.md` 通过;完整按钮级远程浏览器巡检 `REMOTE_WEB_UI_SMOKE_20260629T104040Z.md` 通过,覆盖 15 个核心菜单及安全主操作,客户网关管理编辑报告包含 `business-prefix-checkbox-toggle-ok: false->true->false`;B 本机号码库四个列表接口均返回 200,当前 total 均为 0,符合真实号段尚未导入状态;本地代码验证 `lint``typecheck`、全量 `test``build``node --check tests/web/remote-web-ui-smoke.mjs` 均通过;Redis `cfg:active_version=s28-v1`A 当前 EVALSHA 脚本在 Redis 中存在;A 已加载二期 Lua 热路径 SHA1 `aff9995dbda2a2425e95a3e25b5d7ce194348ce7`;当前仍未导入真实 80 万手机号段;阿里云迁移前需按 S30 Runbook 重新演练
- 最后更新:2026-06-30 11:12 +08:00
- 当前阻塞:本轮 8.2/8.7/8.8、业务前缀页面、号码库页面、客户网关编辑运行时错误、测试门禁修复、B SSH/HTTPS 来源限制移除无阻塞。遗留问题:未提交 Git;未改 A OpenSIPS/Lua/RTPEngine 或 T 配置;8.9 轻量 API burst p95 超过 10 秒需结合服务端日志和测试方式继续定位;SIP CPS 探针中 BYE 被 `403 Rate Limited` 属 A 侧限流策略问题,修复前需说明影响、备份 A 配置并安排维护窗口;8.10 备份、隔离恢复、真实回滚、OpenSIPS 配置恢复受权限或维护窗口阻塞;真实 80 万手机号段尚未导入;OpenSIPS dialog 残留根因仍待单独分析。
- 当前环境:A/B/T 为本地 KVM 开发服务器,通过 Tailscale 联调;开发完成后再迁移阿里云
- 私密访问资料:`.codex-private/SERVER_ACCESS.md`,只在实际连接时读取,禁止回显
@@ -90,6 +90,159 @@
## 交接记录
### 2026-06-30 11:12 - Web-only s53 菜单展开 Logo 增加 SIP
- 状态:已完成
- 操作服务器:本地、B;未改数据库 schema,未重启 API/Worker,未改 A/T
- 完成内容:菜单展开状态下将品牌位调整为 `logo1 + SIP`,菜单收起状态仍只显示 `logo2`;保持 favicon 与 logo 资源路径不变。
- 修改文件:`apps/web/src/App.jsx``apps/web/src/styles.css``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-CrjpbrU-.js` / `index-CrJGI5_R.css`;上传 Web tar `s53-brand-logo-sip-text-20260630105500-web.tar.gz`SHA256 `fd2cd44a1828de9ae71bf29f73171091d8690a2b293edee87eb79b78eb3db07a`B 从 s52 复制生成 `/opt/lisglosips/releases/s53-brand-logo-sip-text-20260630105500`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s53 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过;B `nginx -t` 通过并已 reloadB current 确认为 `/opt/lisglosips/releases/s53-brand-logo-sip-text-20260630105500`;远程 smoke `REMOTE_SMOKE_20260630T031146Z.md` 通过。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s52-brand-logo-assets-20260630102000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:未做完整按钮级浏览器巡检。
### 2026-06-30 10:45 - Web-only s52 接入菜单 Logo 与 favicon
- 状态:已完成
- 操作服务器:本地、B;未改数据库 schema,未重启 API/Worker,未改 A/T
- 完成内容:将根目录 `logo/logo1.png``logo/logo2.png``logo/fav.ico` 复制到 Web public 资源目录;菜单展开时使用 `/brand/logo1.png`,菜单收起时使用 `/brand/logo2.png`,浏览器标签使用 `/favicon.ico`;调整侧边栏品牌区 CSS,使展开/收起 logo 尺寸稳定,移动端保持展开 logo。
- 修改文件:`apps/web/index.html``apps/web/src/App.jsx``apps/web/src/styles.css``apps/web/public/brand/logo1.png``apps/web/public/brand/logo2.png``apps/web/public/favicon.ico``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-CMgxUenC.js` / `index-DZaQiQoS.css`dist 包含 `favicon.ico``brand/logo1.png``brand/logo2.png`;上传 Web tar `s52-brand-logo-assets-20260630102000-web.tar.gz`SHA256 `cc1fdb2aa29550b6634a4189000507a8b62d5b33bd13bd4d0c715dd3acff3f58`B 从 s51 复制生成 `/opt/lisglosips/releases/s52-brand-logo-assets-20260630102000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s52 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过;B `nginx -t` 通过并已 reload;远程 smoke `REMOTE_SMOKE_20260630T024412Z.md` 通过;`https://100.90.90.91/favicon.ico``/brand/logo1.png``/brand/logo2.png` 均返回 200。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:未做完整按钮级浏览器巡检;未提交 Git。
### 2026-06-30 10:03 - 移除 B SSH/HTTPS 来源 IP 白名单限制
- 状态:已完成
- 操作服务器:B;未改数据库 schema,未发布新 release,未改 SSH Key 登录策略
- 问题分类:B 主机 nftables 来源白名单限制。B `lisglosips_filter` 原规则只允许 `admin_ipv4={100.91.249.119,100.98.167.119}``tailscale0` 访问 TCP 22/443,导致 `100.107.171.69` 无法访问 `https://100.90.90.91/`
- 完成内容:备份 `/etc/nftables.d/lisglosips.nft`,删除 `admin_ipv4` 集合;将 SSH/HTTPS 规则改为 `tcp dport { 22, 443 } counter accept comment "SSH and HTTPS"`,即不再按来源 IP 限制 SSH/HTTPS;保留 A 到 B Redis/HEP 的来源限制规则不变。
- 修改文件:B `/etc/nftables.d/lisglosips.nft``IMPLEMENTATION_STATUS.md`
- 执行的关键命令:B `cp -a /etc/nftables.d/lisglosips.nft /var/backups/lisglosips-firewall/20260630T100129/lisglosips.nft`、修改 nftables 配置、`nft -c -f /etc/nftables.conf``nft -f /etc/nftables.conf``nft list table inet lisglosips_filter`
- 验证结果:B 生效规则已显示 `tcp dport { 22, 443 } counter accept comment "SSH and HTTPS"`,无 `ip saddr @admin_ipv4` 条件;本机到 `100.90.90.91:443``100.90.90.91:22``TcpTestSucceeded=True`B 本机 `curl -k -I https://127.0.0.1/` 返回 HTTP 200`nginx``ssh` 均 active。A/T 未发现同类 SSH/HTTPS 来源白名单限制。
- 回滚方式:B 执行 `sudo cp -a /var/backups/lisglosips-firewall/20260630T100129/lisglosips.nft /etc/nftables.d/lisglosips.nft && sudo nft -c -f /etc/nftables.conf && sudo nft -f /etc/nftables.conf`,即可恢复原来源白名单。
- 未解决问题:尚未从 `100.107.171.69` 客户端侧复测浏览器访问;未提交 Git。
### 2026-06-30 09:44 - A/B/T 写入开机后服务恢复脚本
- 状态:已完成
- 操作服务器:A、B、T;未改数据库 schema,未发布新 release,未改防火墙或 SSH 配置
- 完成内容:在仓库新增三台机器的同源启动脚本,并分别安装到各自服务器 `/usr/local/sbin/lisglosips-start-services.sh`,属主 `root:root`、权限 `0755`。脚本会启动对应机器的 LisgloSIPS 运行服务并执行基础健康检查;可在机器本地手动执行,也可通过 SSH 远程执行。
- 修改文件:`infra/ops/startup/lisglosips-start-a.sh``infra/ops/startup/lisglosips-start-b.sh``infra/ops/startup/lisglosips-start-t.sh``IMPLEMENTATION_STATUS.md`
- 执行的关键命令:本地 `scp` 上传脚本;三机 `sudo install -o root -g root -m 0755 /tmp/lisglosips-start-services.sh /usr/local/sbin/lisglosips-start-services.sh``bash -n /usr/local/sbin/lisglosips-start-services.sh``sudo /usr/local/sbin/lisglosips-start-services.sh`
- 验证结果:A 脚本执行后 `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-redis-auth-proxy``lisglosips-node-exporter``lisglosips-recording-finalize.timer` active,热路径 `Result=success`failed units 为 0B 脚本执行后 current 为 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000`MySQL/Redis/Nginx/API/CDR Worker/Recording Worker/Config Publisher/HEP/Prometheus/Grafana activeAPI ready okpreflight okfailed units 为 0T 脚本执行后 `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-s28-uas``apache2``mariadb` activefailed units 为 0。
- 回滚方式:如需撤回脚本,可在对应机器执行 `sudo rm -f /usr/local/sbin/lisglosips-start-services.sh`;运行态停止方式同 09:31 记录。仓库新增脚本可按 Git 回退。
- 未解决问题:脚本是手动恢复脚本,不是开机自启动 unit;未做 A/B/T 实呼端到端验收;未提交 Git。
### 2026-06-30 09:31 - A/B/T 测试服务器开机后服务恢复
- 状态:已完成
- 操作服务器:A、B、T;未改数据库 schema,未发布新 release,未改防火墙或 SSH 配置
- 完成内容:读取三机当前 systemd 状态后恢复运行服务;A 主服务已 active,主动执行 `lisglosips-redis-hotpath-load.service` 确认热路径 Lua 载入成功;B 开机后 `lisglosips@recording-worker``lisglosips@config-publisher` 为 inactive,已启动并确认 activeT 主服务均已 active,执行 start/reset-failed 保持模拟环境可用。
- 修改文件:`IMPLEMENTATION_STATUS.md`
- 执行的关键命令:A `systemctl start lisglosips-redis-hotpath-load.service opensips rtpengine-daemon rtpengine-recording-daemon lisglosips-redis-auth-proxy lisglosips-node-exporter lisglosips-recording-finalize.timer``opensips -C -f /etc/opensips/opensips.cfg`B `systemctl start mysql redis-server nginx heplify-server lisglosips-prometheus grafana-server lisglosips@api lisglosips@cdr-worker lisglosips@recording-worker lisglosips@config-publisher``nginx -t``lisglosips-release-preflight.sh`T `systemctl reset-failed opensips``systemctl start mariadb apache2 rtpengine-daemon rtpengine-recording-daemon opensips lisglosips-s28-uas``opensips -C -f /etc/opensips/opensips.cfg`
- 验证结果:A `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-redis-auth-proxy``lisglosips-node-exporter``lisglosips-recording-finalize.timer` 均 active`lisglosips-redis-hotpath-load.service` Result=success`systemctl --failed` 为 0B current 为 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000`MySQL/Redis/Nginx/API/CDR Worker/Recording Worker/Config Publisher/HEP/Prometheus/Grafana 均 activeAPI ready 返回 okpreflight ok,最近备份目录 `/data/backups/mysql/20260630T012836Z``/data/backups/redis/20260630T012836Z`,远程 smoke `REMOTE_SMOKE_20260630T012830Z.md` 通过;T `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-s28-uas``apache2``mariadb/mysql` active`systemctl --failed` 为 0。
- 回滚方式:本次仅恢复运行态服务。若需停止,可分别执行 A `sudo systemctl stop opensips rtpengine-recording-daemon rtpengine-daemon lisglosips-redis-auth-proxy`B `sudo systemctl stop lisglosips@api lisglosips@cdr-worker lisglosips@recording-worker lisglosips@config-publisher`T `sudo systemctl stop opensips lisglosips-s28-uas rtpengine-recording-daemon rtpengine-daemon`;应用 release 未变更,仍可按 S30 Runbook 回滚 B current。
- 未解决问题:未做 A/B/T 实呼端到端验收;真实 80 万手机号段尚未导入;8.9/8.10 既有遗留问题未处理;未提交 Git。
### 2026-06-29 18:43 - Web-only s51 优化客户网关列表与前缀选择交互
- 状态:已完成
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端交互 bug 与界面优化。客户网关编辑弹窗中前缀 Checkbox 的 `onChange` 传入原生 event,页面将 event 当作布尔值,导致取消勾选无效;列表展示了用户要求隐藏的认证方式与认证目标列,名称/客户列偏窄。
- 完成内容:客户网关列表隐藏“认证方式”和“IP地址 / 账号名称”;“名称”列宽调整为 240px,“客户”列宽调整为 220px;公共 `Checkbox``Radio``Switch``onChange` 统一传 `(checked, event)`,修复取消勾选无效并保持旧调用方兼容;客户网关业务前缀选择区增加已选计数、全选、清空、空状态和更清晰的网格样式;远程浏览器巡检增加客户网关编辑前缀 checkbox 往返切换断言,并将登录页等待从 `networkidle` 改为更稳定的表单/已登录页面并行等待。
- 修改文件:`apps/web/src/components/ui.jsx``apps/web/src/pages/CustomerGatewaysPage.jsx``apps/web/src/styles.css``tests/web/remote-web-ui-smoke.mjs``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-C_61kpb7.js` / `index-DWBaYpdZ.css`;上传 Web tar `s51-customer-gateway-list-prefix-ui-20260629184000-web.tar.gz`SHA256 `cb11ad80b84ded7a5605ddedf24058dba854f5fa41de845b972f0643fadfbd0c`B 从 s50 复制生成 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s51 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 test` 通过,26 个测试文件 93 条测试通过;`corepack pnpm@10.33.0 build` 通过;`node --check tests/web/remote-web-ui-smoke.mjs` 通过。B `nginx -t` 通过并已 reload;远程 smoke `REMOTE_SMOKE_20260629T103420Z.md` 通过;真实 Chrome 完整按钮级巡检 `REMOTE_WEB_UI_SMOKE_20260629T104040Z.md` 通过,客户网关管理编辑动作验证 `business-prefix-checkbox-toggle-ok: false->true->false`,无 console error/pageerror/空白页/API 数据不可用。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s50-web-checkbox-action-smoke-20260629182000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:充值记录、话单中心在当前测试数据下没有可见启用的安全动作按钮,脚本按规则记录为 skipped;未提交 Git。
### 2026-06-29 18:22 - Web-only s50 修复 Checkbox 子节点透传并完成按钮级浏览器巡检
- 状态:已完成
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端 bug。s49 只处理了公共 `Input`,但客户网关编辑弹窗里的业务前缀多选使用 `<Checkbox>前缀 / 名称</Checkbox>`,公共 `Checkbox` 未接收 `children`,导致 `children` 继续透传到原生 `<input type="checkbox">` 并触发 React #137
- 完成内容:修复 `apps/web/src/components/ui.jsx``Checkbox`,支持 `label``children` 作为显示文本且不再把 `children` 传给原生 input;同步防护 `Radio``Switch``Slider`,避免同类组件出现相同问题;巡检脚本增加逐页 `CHECK/ACTION` 输出和 `LISGLOSIPS_WEB_UI_STEP_TIMEOUT_MS` 单步超时,避免完整按钮巡检长时间静默。
- 修改文件:`apps/web/src/components/ui.jsx``tests/web/remote-web-ui-smoke.mjs``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-CI4aNWXC.js` / `index-B2uYBtS3.css`;上传 Web tar `s50-web-checkbox-action-smoke-20260629182000-web.tar.gz`SHA256 `18492ce4860aca32808b0cf871035e6255a4a3924d0c20f9647ffca1eaf20aa5`B 从 s49 复制生成 `/opt/lisglosips/releases/s50-web-checkbox-action-smoke-20260629182000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s50 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 test` 通过,26 个测试文件 93 条测试通过;`corepack pnpm@10.33.0 build` 通过;`node --check tests/web/remote-web-ui-smoke.mjs` 通过。B `nginx -t` 通过并已 reload;远程 smoke `REMOTE_SMOKE_20260629T101122Z.md` 通过;真实 Chrome 完整按钮级巡检 `REMOTE_WEB_UI_SMOKE_20260629T101911Z.md` 通过,客户网关管理编辑、业务前缀编辑、号码库刷新等动作均无 console error/pageerror/空白页/API 数据不可用。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s49-web-input-action-smoke-20260629175000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:客户管理、充值记录、话单中心在当前测试数据下没有可见启用的安全动作按钮,脚本按规则记录为 skipped;未提交 Git。
### 2026-06-29 17:50 - Web-only s49 修复客户网关编辑 React #137 并扩展按钮级 Web UI 巡检
- 状态:已完成;后续完整按钮级巡检发现修复不完整,根因已在 s50 修复
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端 bug。客户网关页面点击编辑时报 React #137,参数为 `input`,属于原生 `<input>` 收到 children 的运行时错误。为避免任意调用方把 `children` 透传到原生 input,已在公共 `Input` 组件中显式丢弃 `children`
- 完成内容:修复 `apps/web/src/components/ui.jsx``Input` 组件,避免 children 透传到 `<input>`;扩展 `tests/web/remote-web-ui-smoke.mjs`,每个核心菜单渲染后继续点击一个安全主操作(如编辑、查看详情、刷新),覆盖客户网关编辑弹窗、业务前缀编辑、号码库刷新等二级交互;更新 `docs/TEST_PLAN_AND_CASES.md` 的 WEB-005,明确按钮级交互也纳入 pageerror/console error 门禁。
- 修改文件:`apps/web/src/components/ui.jsx``tests/web/remote-web-ui-smoke.mjs``docs/TEST_PLAN_AND_CASES.md``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-D90saQl_.js` / `index-B2uYBtS3.css`;上传 Web tar `s49-web-input-action-smoke-20260629175000-web.tar.gz`SHA256 `9ee7ad3c99d90d0fe0bf09482a47c2b76aedfc298d9aa909397850c5ac317359`B 从 s48 复制生成 `/opt/lisglosips/releases/s49-web-input-action-smoke-20260629175000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s49 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过;`node --check tests/web/remote-web-ui-smoke.mjs` 通过。B `nginx -t` 通过,HTTPS 首页已引用 `/assets/index-D90saQl_.js`;远程 smoke `REMOTE_SMOKE_20260629T094744Z.md` 通过。完整 `test:remote-web-ui` 仍需提供 `LISGLOSIPS_AUTH_PASSWORD` 后执行。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s48-web-number-library-constants-20260629160000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:s49 只修复 `Input`,未覆盖 `Checkbox` 子节点透传;完整按钮级巡检在客户网关编辑处仍复现 React #137,已由 s50 修复;未提交 Git。
### 2026-06-29 17:39 - 补齐前端测试门禁:lint 覆盖 Web 源码与 Playwright 菜单巡检
- 状态:已完成本地测试体系修复;未发布服务器
- 操作服务器:本地;未改 B/A/T
- 问题分类:测试覆盖缺口。此前 `eslint.config.mjs` 忽略 `apps/web/src/**``remote-smoke` 只检查 HTML 壳和健康接口,未执行真实 React 路由/菜单渲染,因此 `normalizeBusinessPrefix``emptyNumberLibraryRows``toneForStatus` 这类前端未定义标识不能在发布前被自动发现。
- 完成内容:移除 ESLint 对 `apps/web/src/**` 的忽略,并为前端 JSX 增加 browser globals/JSX parser 配置;新增 `tests/web/remote-web-ui-smoke.mjs`,使用 Playwright 通过 API 验证码登录、注入 refresh cookie,打开真实 Web 页面并逐个点击核心菜单,捕获 `pageerror`、console error、空白页、回到登录页和 `API 数据不可用`;新增 `test:remote-web-ui` 脚本;更新 `tests/README.md``docs/TEST_PLAN_AND_CASES.md` 的 WEB-005/WEB-006,使真实浏览器菜单巡检成为 Web 发布验收项。
- 顺手修复/清理:前端 lint 首次覆盖后发现 `OperationLogsPage.jsx``toneForStatus` 未定义,已补齐;清理若干 Web demo 残留未使用 import/常量/函数,避免前端 lint 被历史死代码阻断;测试脚本依赖新增 `playwright@1.57.0`
- 修改文件:`eslint.config.mjs``package.json``pnpm-lock.yaml``tests/web/remote-web-ui-smoke.mjs``tests/README.md``docs/TEST_PLAN_AND_CASES.md``apps/web/src/pages/ActiveCallsPage.jsx``apps/web/src/pages/CustomerGatewaysPage.jsx``apps/web/src/pages/CustomersPage.jsx``apps/web/src/pages/OperationLogsPage.jsx``apps/web/src/pages/RoutesPage.jsx``apps/web/src/pages/SettingsPage.jsx``apps/web/src/pages/SipOpsPage.jsx``apps/web/src/pages/VendorLineGroupsPage.jsx``apps/web/src/pages/VendorsPage.jsx``apps/web/src/utils/formatters.js``IMPLEMENTATION_STATUS.md`
- 验证结果:`corepack pnpm@10.33.0 lint` 通过,确认前端源码已纳入 lint`corepack pnpm@10.33.0 build` 通过,生成 Web JS `index-gFC83nZO.js``corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 test` 通过,26 个测试文件 93 条测试通过;`corepack pnpm@10.33.0 prisma:validate` 通过;`node --check tests/web/remote-web-ui-smoke.mjs` 通过。`corepack pnpm@10.33.0 test:remote-web-ui` 在未设置 `LISGLOSIPS_AUTH_PASSWORD` 时按设计退出并提示必须提供密码,未进行完整远程浏览器登录巡检。
- 回滚方式:回退上述测试配置、脚本、文档和前端 lint 清理文件即可恢复旧测试行为;本次未改数据库、未重启服务、未切换 B release。
- 未解决问题:需要在具备 Web 管理员密码环境变量的验收环境执行 `pnpm test:remote-web-ui` 形成正式远程报告;未提交 Git。
### 2026-06-29 16:10 - Web-only s48 修复号码库页面常量误放导致空白
- 状态:已完成
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端 bug。号码库页面 `NumberLibraryPage.jsx` 使用 `numberLibraryTabs``numberLibraryImportExamples``emptyNumberLibraryRows``emptyNumberLibraryTotals``normalizeNumberLibraryList`,但这些常量/函数误放在 `BusinessPrefixesPage.jsx` 文件末尾,导致点击号码库菜单时抛出 `ReferenceError: emptyNumberLibraryRows is not defined` 并出现空白页。这不是后端 API、浏览器登录态或权限问题。
- 完成内容:将号码库页面依赖的 tabs、导入示例、空数据初始值和列表归一化函数移回 `NumberLibraryPage.jsx`;从 `BusinessPrefixesPage.jsx` 删除误放的号码库代码,保留业务前缀自身的 `normalizeBusinessPrefix()`
- 修改文件:`apps/web/src/pages/NumberLibraryPage.jsx``apps/web/src/pages/BusinessPrefixesPage.jsx``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-wPzMqk8x.js` / `index-B2uYBtS3.css`;上传 Web tar `s48-web-number-library-constants-20260629160000-web.tar.gz`SHA256 `3e2b5e8cc160dc1d40bb5504a4f7960c903f7485c6b295407e0163986161bb1a`B 从 s47 复制生成 `/opt/lisglosips/releases/s48-web-number-library-constants-20260629160000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s48 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过。B `nginx -t` 通过,HTTPS 首页已引用 `/assets/index-wPzMqk8x.js`;远程 smoke `REMOTE_SMOKE_20260629T080917Z.md` 通过;B 本机 API 复测 `/number-library/cities?take=1``/phone-segments?take=1``/area-codes?take=1``/carrier-prefix-rules?take=1` 均返回 200。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s47-web-business-prefix-normalizer-20260629154500` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:浏览器可能缓存旧 JS,若仍看到同样错误,需强制刷新或清理站点缓存后再测;真实 80 万手机号段尚未导入,号码库列表为空是当前已知数据状态;未提交 Git。
### 2026-06-29 15:55 - Web-only s47 修复业务前缀管理页面归一化函数缺失
- 状态:已完成
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端 bug。业务前缀管理页面 `loadPrefixes()` 调用 `normalizeBusinessPrefix`,但 `BusinessPrefixesPage.jsx` 未定义该函数,导致接口请求成功后在前端数据归一化阶段抛出 `ReferenceError`,页面误显示“API 数据不可用”。这不是后端 API、浏览器登录态或权限问题。
- 完成内容:在业务前缀页面补齐 `normalizeBusinessPrefix()`,并引入 `formatDate``zhStatus`,字段映射与现有页面保持一致:`prefix``name``description``priority`、中文状态、使用客户网关数、创建日期。
- 修改文件:`apps/web/src/pages/BusinessPrefixesPage.jsx``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-MxyRK-fA.js` / `index-B2uYBtS3.css`;上传 Web tar `s47-web-business-prefix-normalizer-20260629154500-web.tar.gz`SHA256 `d23a227e1836f333594d1ea8f8e3bd944b82ee57b23b610d3b03f3dd2670b328`B 从 s46 复制生成 `/opt/lisglosips/releases/s47-web-business-prefix-normalizer-20260629154500`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s47 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过。B `nginx -t` 通过,HTTPS 首页已引用 `/assets/index-MxyRK-fA.js`;远程 smoke `REMOTE_SMOKE_20260629T075504Z.md` 通过,首页、live/ready health、captcha 均通过。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s46-bugfix-recharge-active-numberlib-20260629151000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:浏览器可能缓存旧 JS,若页面仍显示同样错误,需强制刷新或清理站点缓存后再测;未提交 Git。
### 2026-06-29 15:41 - 发布 s46 修复 8.2/8.7/8.8 到 B 测试环境
- 状态:已完成
- 操作服务器:本地、B;未改 A/T 配置,未执行 A/B/T 三机实呼链路变更
- 测试结果最终分类:8.2 负数客户充值返回 `MONEY_INVALID` 是确定 bug,已修复为客户余额可正向充值也可负数扣款,仍拒绝 0 和负零;8.7 超长 active call dialog id 返回 404 是确定 bug,根因包含服务层校验不足和 Fastify 默认 `maxParamLength=100` 导致 129 位路径参数先被路由层拒绝,已修复为 400 `ACTIVE_CALL_ID_INVALID`;8.8 管理员菜单缺少号码库是既有 B 环境 RBAC 数据缺失 `number_library.view/manage`,不是浏览器缓存或登录态问题,已用迁移补齐内置角色权限;8.9 轻量 API burst p95 超过 10 秒和 BYE 被 `403 Rate Limited` 未纳入本轮代码修复,分别需继续查 B API 性能日志和 A 侧 OpenSIPS 限流策略;8.10 阻塞项属于权限/维护窗口限制,不是本轮业务代码缺陷。
- 完成内容:客户充值接口改为客户侧允许负数扣款、供应商侧继续只允许正数;active call hangup ID 校验收紧为 1-128 位安全字符且必须包含 `@``.``:`,并将 API Fastify `routerOptions.maxParamLength` 调整到 256,让超长 ID 进入应用校验返回 400;新增 active-calls e2e 覆盖 129 位 ID;新增 Prisma 迁移补齐 `number_library.view/manage` 权限及 `ROLE_SUPER_ADMIN``ROLE_OPERATOR``ROLE_TECH_OPS` 的角色权限关联。
- 修改文件:`apps/api/src/main.ts``apps/api/src/modules/recharges/recharges.service.ts``apps/api/src/modules/recharges/recharges.e2e.spec.ts``apps/api/src/modules/active-calls/active-calls.service.ts``apps/api/src/modules/active-calls/active-calls.service.spec.ts``apps/api/src/modules/active-calls/active-calls.e2e.spec.ts``prisma/migrations/20260629150000_number_library_builtin_role_permissions/migration.sql``IMPLEMENTATION_STATUS.md`
- 发布情况:B 已从 `s45-vendor-cps-sipstate-20260629113500` 复制生成并切换到 `/opt/lisglosips/releases/s46-bugfix-recharge-active-numberlib-20260629151000`;本次因大包上传链路多次卡住,采用基于 s45 的补丁 release,替换构建后的 API JS/SourceMap 和新增迁移 SQL,而非完整源码包重建 artifact;B `/opt/lisglosips/current` 已指向 s46。发布前触发 B 备份,MySQL `/data/backups/mysql/20260629T070448Z`、Redis `/data/backups/redis/20260629T070448Z`,两者 SHA256 校验通过。
- 数据库迁移说明:首次 `prisma migrate deploy` 在 s46 未切 current 前因 Prisma engine 权限 `EACCES` 失败,已修正 s46 engine 权限;第二次因迁移 SQL 漏写 `updated_at` 触发 P3018,current 仍未切换,修正迁移后执行 `prisma migrate resolve --rolled-back 20260629150000_number_library_builtin_role_permissions` 并重新 `migrate deploy`,最终迁移成功。迁移只新增/补齐权限主数据和角色权限关联,不改业务表结构。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`typecheck` 通过;`prisma:validate` 通过;全量 `test` 通过,26 个测试文件 93 条测试通过;`build` 通过,Web 产物仍为 `index-CBRZZA7t.js` / `index-B2uYBtS3.css`。B preflight 通过,API/MySQL/Redis/Nginx/Worker/监控服务 activeAPI ready 为 `ok`;远程 smoke `REMOTE_SMOKE_20260629T074052Z.md` 通过;B 本机 API 定向复测通过:号码库权限 6 条角色关联存在、`GET /api/v2/number-library/cities?take=1` 返回 200、129 位 active-call hangup 返回 400 `ACTIVE_CALL_ID_INVALID`、临时客户 `cus_aeef878b32534914a9b64ae0196d` 正向充值 10 后负数扣款 `-3.250000`,余额从 `10.000000``6.750000``-0.000000` 返回 400 `MONEY_INVALID`
- 回滚方式:应用回滚可执行 `/opt/lisglosips/current/infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/s45-vendor-cps-sipstate-20260629113500`,或手动将 `/opt/lisglosips/current` 指回 s45 后重启 `lisglosips@api lisglosips@cdr-worker lisglosips@recording-worker lisglosips@config-publisher` 并 reload Nginx;数据库回滚通常可保留权限主数据,若必须撤回菜单权限,删除 `role_permissions``ROLE_SUPER_ADMIN``ROLE_OPERATOR``ROLE_TECH_OPS``number_library.view/manage` 的 6 条关联,必要时先用上述 MySQL/Redis 备份在隔离环境验证;本轮远程验证留下的 `codex_s46_` 临时客户和对应余额流水可按测试数据保留审计或在维护窗口按客户 ID 清理。
- 未解决问题:未提交 Git8.9 API p95 和 BYE 限流仍待单独定位;8.10 阻塞项仍需权限或维护窗口;本轮是 B API/RBAC 修复,不涉及 A/T SIP 热路径,因此未做新的三机实呼验收,若后续调整 A 侧限流则必须补做 A/B/T 端到端验收。
### 2026-06-29 14:59 - 远程测试报告核对与本地修复:负数扣款、active call ID、号码库权限
- 状态:已完成本地修复,待用户确认是否发布到 B
- 操作服务器:本地;未连接 A/B/T,未读取或回显任何服务器凭据
- 测试结果分类:8.2 负数客户充值返回 `MONEY_INVALID` 是确定 bug8.7 超长/无效 active call dialog id 返回 404 是确定 bug;8.8 管理员菜单缺少号码库不是浏览器缓存问题,前端已有菜单和权限裁剪,判断为 B 既有 RBAC 数据未补齐导致的发布/数据问题;8.9 轻量 API burst p95 超过 10 秒是待定位性能问题,SIP CPS 探针 BYE 被限流是 A 侧限流策略待确认问题;8.10 备份、隔离恢复、真实回滚、OpenSIPS 配置恢复为 sudo 权限或维护窗口阻塞,不是代码缺陷,灰度呼叫和健康回归已通过。
- 完成内容:客户充值接口改为允许 `POST /customers/:id/recharges` 传负数金额作为扣款,继续拒绝 0、负零、超精度和超范围金额;供应商充值接口保持只允许正数。当前通话 hangup ID 校验收紧为 1-128 位安全字符且必须包含 `@``.``:`,避免无意义超长字符串进入 OpenSIPS MI。新增 Prisma 数据迁移补齐 `number_library.view/manage` 权限,并为 `ROLE_SUPER_ADMIN``ROLE_OPERATOR``ROLE_TECH_OPS` 写入号码库权限关联,修复既有环境 seed 未重跑时管理员缺菜单的问题。
- 修改文件:`apps/api/src/modules/recharges/recharges.service.ts``apps/api/src/modules/recharges/recharges.e2e.spec.ts``apps/api/src/modules/active-calls/active-calls.service.ts``apps/api/src/modules/active-calls/active-calls.service.spec.ts``prisma/migrations/20260629150000_number_library_builtin_role_permissions/migration.sql``IMPLEMENTATION_STATUS.md`
- 验证结果:定向 `corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/recharges/recharges.e2e.spec.ts apps/api/src/modules/active-calls/active-calls.service.spec.ts --hookTimeout=60000 --maxWorkers=1` 通过,2 个测试文件 10 条测试通过;`corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 prisma:validate` 通过;`corepack pnpm@10.33.0 test` 通过,25 个测试文件 92 条测试通过;`corepack pnpm@10.33.0 build` 通过,Web 产物仍为 `index-CBRZZA7t.js` / `index-B2uYBtS3.css`
- 发布情况:未提交 Git,未发布 B,未执行数据库迁移;B 当前 release 仍为 `s45-vendor-cps-sipstate-20260629113500`
- 回滚方式:回退上述源码和测试文件即可恢复旧本地行为;若已在 B 执行新增 Prisma 数据迁移,可通过删除 `role_permissions``ROLE_SUPER_ADMIN`/`ROLE_OPERATOR`/`ROLE_TECH_OPS``number_library.view/manage` 的关联来撤回菜单权限补齐,通常无需删除 `permissions` 主数据;应用层按 S30 Runbook 切回上一 release。
- 未解决问题:8.9 API p95 需服务端日志/指标进一步定位;BYE 被 CPS 限流涉及 A OpenSIPS 防护策略,修改前必须备份 A 配置并说明重启影响;8.10 阻塞项需 sudo 或维护窗口;号码库真实 80 万号段尚未导入。
### 2026-06-29 12:12 - 需求修改/二期变更发布到 B:供应商启停移除、客户启停、CPS 与当前通话 SIP 状态
- 状态:已完成