Эх сурвалжийг харах

fix(wecom): repair topology toolbar mojibake and guard installer

Replace GBK-double-encoded toolbar glyphs with HTML numeric entities, bump module to 2.0.1, and make install.sh reject mojibake sources before copying.
clm0081 2 өдөр өмнө
parent
commit
26a9dbef45

+ 3 - 2
docs/使用说明.md

@@ -51,7 +51,7 @@ docker compose up -d
        - /opt/zabbix/zabbix-server/web-modules:/usr/share/zabbix/modules
    ```
 2. 把 `web-modules/WecomCenter` 整个目录拷到宿主那台目录下(即 `.../web-modules/WecomCenter/`)。
-   - 也可用自带脚本:`cd web-modules && ./install.sh`(自动探测运行中的 `zabbix-*` web 容器并复制进去;或 `./install.sh /usr/share/zabbix/modules`)。脚本会顺带建 L2 表,并在 `module` 表已就绪时**自动登记/启用** `wecom_center`。
+   - 也可用自带脚本:`cd web-modules && ./install.sh`(自动探测运行中的 `zabbix-*` web 容器并复制进去;或 `./install.sh /usr/share/zabbix/modules`)。脚本会顺带建 L2 表,并在 `module` 表已就绪时**自动登记/启用** `wecom_center`;复制前还会扫描 PHP/JS 源文件,发现 UTF-8 双重编码乱码会直接报错退出。
 3. `docker compose up -d` 重建 web 容器使挂载生效(只重启 web,数据不动)。
 4. 若脚本提示 `module` 表尚未就绪,等 Zabbix 首次启动完成后,进 **Administration → General → Modules → Scan directory → 启用 `WecomCenter`**;也可以手工执行:
    ```sql
@@ -190,7 +190,7 @@ zabbix-deploy/
 │   ├── install.sh                 # 一步搬运脚本(docker 自动探测 / 指定目录)
 │   ├── README.md                  # 开发者向说明
 │   └── WecomCenter/               # 插件本体
-│       ├── manifest.json          # v2.0,注册 2 个 action
+│       ├── manifest.json          # v2.0.1(manifest_version 2.0),注册 2 个 action
 │       ├── Module.php             # 入口:注册视图、i18n、注入左侧菜单
 │       ├── actions/               # View(GET) / Save(POST) 控制器
 │       ├── views/                 # 页面模板
@@ -297,6 +297,7 @@ refresh_alarms.sh     # 仅刷新实时告警 JSON(每分钟;不写历史)
 - 采集器为宿主机进程(需 SNMP 工具、且要读交换机网络),不在容器内运行,按 10.4 安装。
 
 ### 10.6 故障排查
+- 按钮出现 `鈼?`、`鉂氣潥`、`锛?` 等乱码:不是浏览器字体问题,而是 `views/topology.view.php` 被按 GBK/ANSI 重新编码。2.0.1 起已全部改为 HTML 数字实体;请重跑 `install.sh` 覆盖旧文件,安装脚本会检测并拒绝带 mojibake 的包。
 - 展示页空白:确认 `assets/l2topology/snapshot.json` 存在(跑一次 `run_full.sh`)。
 - 时间轴没有历史:确认 cron 在跑、`l2_topology_history` 行数在增长;历史写入间隔最少 5 分钟,改配置页后需等下一个周期。
 - 交换机没被采集:检查配置页模板是否勾选正确、该主机是否挂了该模板、SNMP 可达。

+ 7 - 4
docs/新会话功能测试与代码审查提示词.md

@@ -60,12 +60,14 @@
   - `render()` 与 `buildHistory()` 的初始化顺序;自动刷新是否破坏已打开面板。
   - Cytoscape 布局:空角色不占位、核心/分核心/防火墙/行为管理顺序、链路标签遮挡、导出 canvas 图例。
   - 事件监听是否重复绑定/内存泄漏;`innerHTML` 插入配置文本是否转义。
+- `views/topology.view.php`
+  - 工具栏图标是否用 HTML 数字实体(如 `◷` / `▾`);全文件不得出现 `鈼`、`鉂`、`锛`、`?/span>` 等 GBK 双重编码乱码或坏标签。
 - `assets/l2topology/style.css`
   - 明暗主题变量、popover 在窄屏/移动端的溢出、按钮可点击区域、颜色对比度。
 
 ### C. 部署与初始化
 - `docker-compose.yaml`:`wecom-module-init` 等待 `module` 表的时序、幂等、失败重试、`moduleid` 处理、L2 表 DDL。
-- `web-modules/install.sh`:容器/目录两种模式、MySQL 凭据来源、模块登记、collector/cron 安装、权限、失败提示。
+- `web-modules/install.sh`:容器/目录两种模式、MySQL 凭据来源、模块登记、collector/cron 安装、权限、失败提示、mojibake 源文件阻断检查。
 - `Module.php` / `manifest.json`:action 注册、menu 注入、locale、schema 初始化是否一致。
 - 文档:`README.md`、`docs/使用说明.md`、`web-modules/README.md` 与实际行为是否一致(尤其“全新 compose 默认启用”“L2 初始化”“时间选择器”)。
 
@@ -101,9 +103,10 @@
 2. 找一台告警交换机:整节点应为红色填充;找延迟 3–5ms:橙色边框;>5ms:红色边框。
 3. 检查链路颜色:100M 橙、1G 绿、2.5G 青、10G 蓝、20G+ 紫、未知红、down 红虚线。
 4. 检查车间框/楼层框:GRD→1F→3F→4F→7F;跨网络车间拆成两个框;链路不穿越目标标签。
-5. 勾选「显示终端」:端口矩阵外圈=最大速率、实心=当前速率;无端口清单时显示 24 口兜底。
-6. 点「导出图片」:下载 PNG,打开确认图例已烘焙进去、白底日间模式、无动画虚线。
-7. 点空白、点节点、搜索 IP/MAC/名称、缩放/适应窗口;确认无异常。
+5. 检查顶部工具栏所有按钮:快照触发器应显示 `◷ 实时(最新) ▾`,暂停 `❚❚`,缩放 `+`/`−`,立即刷新/适应窗口/导出图片为正常中文;不能出现 `鈼?`、`鉂氣潥`、`锛?`、`?/span>` 等乱码或坏标签。
+6. 勾选「显示终端」:端口矩阵外圈=最大速率、实心=当前速率;无端口清单时显示 24 口兜底。
+7. 点「导出图片」:下载 PNG,打开确认图例已烘焙进去、白底日间模式、无动画虚线。
+8. 点空白、点节点、搜索 IP/MAC/名称、缩放/适应窗口;确认无异常。
 
 ### T4. 历史与数据
 1. 触发一次 `L2_FORCE_FULL=1 bash /opt/zabbix/l2collector/run_full.sh`(或等下一个周期)。

+ 6 - 5
docs/项目总结与验收.md

@@ -2,7 +2,7 @@
 
 > 更新时间:2026-11-06
 > 适用:Zabbix 7.4.x(已在 7.4.16 + MySQL 8.0.43 验证)
-> 模块目录:`web-modules/WecomCenter`
+> 模块目录:`web-modules/WecomCenter`(manifest `2.0.1`)
 
 ## 1. 一句话定位
 
@@ -93,11 +93,12 @@ cd web-modules
 ## 6. 本次验证记录
 
 - PHP lint:`L2Store.php`、`WecomCenterSave.php`、`topology.view.php`、`wecom.center.view.php`、`locale/en.php`、`locale/zh_CN.php` 全部通过。
-- L2 初始化按钮:真实页面点击返回「L2 拓扑初始化完成:配置已存在,历史快照 71 条。」,无 JS 错误。
+- L2 初始化按钮:真实页面点击返回「L2 拓扑初始化完成:配置已存在,历史快照 73 条。」,无 JS 错误。
+- 拓扑工具栏按钮编码回归:`◷ 实时(最新) ▾`、`❚❚`、`+`、`−` 均正常显示,没有 `鈼?` / `鉂氣潥` / `锛?` 乱码;`topology.view.php` 的图标已改用 HTML 数字实体,`install.sh` 已增加 mojibake 源文件阻断检查。
 - 单面板时间选择器(Playwright):
-  - 日期 `["实时(最新)","2026-11-06 (11)","2026-11-05 (60)"]`;
-  - 选日期后 label `2026-11-06 · 选择时间`,时间列表 11 项;
-  - 点 `01:32:03` 后 URL 带 `ts=...`,label 正确显示;
+  - 日期 `["实时(最新)","2026-11-06 (15)","2026-11-05 (60)"]`;
+  - 选日期后 label `2026-11-06 · 选择时间`,时间列表 15 项;
+  - 点 `01:42:01` 后 URL 带 `ts=...`,label 正确显示;
   - `pageerror` / `console.error` 为空。
 - `docker compose config -q`:仓库 compose 与生产 `/opt/zabbix/docker-compose.yaml` 均通过。
 - 生产侧车实测日志:

+ 5 - 1
web-modules/README.md

@@ -50,7 +50,7 @@
 
 ```
 WecomCenter/
-├── manifest.json                 # 模块清单(v2.0),注册 2 个 action
+├── manifest.json                 # 模块清单(v2.0.1),注册 2 个 action
 ├── Module.php                    # 模块入口:注册视图目录、初始化 i18n、注入左侧菜单
 ├── actions/
 │   ├── WecomCenterView.php       # GET 控制器,渲染页面
@@ -173,6 +173,10 @@ WecomCenter/
 
 > 实测:`10962(walk) → 10229(旧)` 预览正确显示 **186 device(s)**(带设备名 chips)。当前环境旧模板 10229 挂 0 台,所以反向 `10229 → 10962` 为空操作——这是数据现状,不是功能问题。
 
+## 按钮乱码(已修复)
+
+如果 L2 拓扑工具栏出现 `鈼?`、`鉂氣潥`、`锛?`、`?/span>` 之类的文字,那不是浏览器字体问题,而是 `topology.view.php` 里的 UTF-8 图标被按 GBK/ANSI 重新编码过。2.0.1 起图标全部改用 HTML 数字实体(`◷` / `▾` / `❚` / `+` / `−`),`install.sh` 也会在复制前扫描 PHP/JS,发现同类 mojibake 直接报错并停止安装。以后升级请直接重跑 `install.sh`,不要用文本编辑器“另存为”模块文件。
+
 ## 已知限制(如实说明)
 
 - **核心 vs 接入的 `Link down` 无法在原型层面区分**(只有一个原型):两者统一设为“一般严重(Average=3)”,71 条核心链路不会被单独拔高;如需对核心链路更敏感,后续可加事件规则/标签区分。

+ 1 - 1
web-modules/WecomCenter/manifest.json

@@ -4,7 +4,7 @@
 	"type": "module",
 	"name": "WeCom Center",
 	"namespace": "WecomCenter",
-	"version": "2.0.0",
+	"version": "2.0.1",
 	"author": "COBCO IT / FlyingRome",
 	"description": "ROMAN customisation center for Zabbix: alert optimisation, WeCom robot/media config, template import, map coordinates, and a DB-backed L2 physical topology explorer.",
 	"url": "https://git.flyingrome.net/FlyingRome/zabbix-deploy",

+ 8 - 8
web-modules/WecomCenter/views/topology.view.php

@@ -1,12 +1,12 @@
 <?php declare(strict_types = 0);
 /*
-** L2 Topology page 鈥?display-only map (all settings live in the config center).
+** L2 Topology page — display-only map (all settings live in the config center).
 ** @var CView $this
 ** @var array  $data
 */
 use Modules\WecomCenter\Lib\I18n;
 
-$this->addCssFile('modules/WecomCenter/assets/l2topology/style.css?v=202611060155');
+$this->addCssFile('modules/WecomCenter/assets/l2topology/style.css?v=202611060205');
 $base = '/modules/WecomCenter/assets/l2topology/';
 
 $snapJson = (string) ($data['snap_json'] ?? 'null');
@@ -61,9 +61,9 @@ if (!function_exists('l2e')) { function l2e($v) { return htmlspecialchars((strin
     <div class="l2-ts-label"><?php echo l2e(I18n::t('l2.time_pick_title')); ?>
       <span class="l2-ts-picker" id="l2-ts-picker">
         <button type="button" class="l2-ts-trigger" id="l2-ts-trigger" aria-haspopup="dialog" aria-expanded="false" title="<?php echo l2e(I18n::t('l2.time_pick_title')); ?>">
-          <span class="l2-ts-clock" aria-hidden="true">鈼?/span>
+          <span class="l2-ts-clock" aria-hidden="true">&#9719;</span>
           <span class="l2-ts-current" id="l2-ts-current"><?php echo l2e($pickerLabel); ?></span>
-          <span class="l2-ts-caret" aria-hidden="true">鈱?/span>
+          <span class="l2-ts-caret" aria-hidden="true">&#9662;</span>
         </button>
         <div class="l2-ts-pop" id="l2-ts-pop" role="dialog" aria-label="<?php echo l2e(I18n::t('l2.time_pick_title')); ?>">
           <div class="l2-ts-days" id="l2-ts-days"></div>
@@ -77,10 +77,10 @@ if (!function_exists('l2e')) { function l2e($v) { return htmlspecialchars((strin
 
     <span class="l2-spacer"></span>
     <span id="l2-countdown" class="l2-muted"></span>
-    <button class="l2-btn" id="l2-pause" title="pause">鉂氣潥</button>
+    <button class="l2-btn" id="l2-pause" title="pause">&#10074;&#10074;</button>
     <button class="l2-btn primary" id="l2-refresh-btn"><?php echo l2e(I18n::t('l2.refresh_now')); ?></button>
-    <button class="l2-btn" id="l2-zin">锛?/button>
-    <button class="l2-btn" id="l2-zout">锛?/button>
+    <button class="l2-btn" id="l2-zin">&#43;</button>
+    <button class="l2-btn" id="l2-zout">&#8722;</button>
     <button class="l2-btn" id="l2-fit"><?php echo l2e(I18n::t('l2.fit')); ?></button>
     <button class="l2-btn" id="l2-export"><?php echo l2e(I18n::t('l2.export_img')); ?></button>
   </div>
@@ -124,7 +124,7 @@ window.L2_META = {
 };
 </script>
 <script src="<?php echo $base; ?>cytoscape.min.js"></script>
-<script src="<?php echo $base; ?>app.js?v=202611060155"></script>
+<script src="<?php echo $base; ?>app.js?v=202611060205"></script>
 <script>
 (function(){
   var tabs = document.querySelectorAll('.side-tab');

+ 9 - 0
web-modules/install.sh

@@ -37,6 +37,15 @@ if [ ! -d "$SRC" ]; then
   exit 1
 fi
 
+# Guard against double-encoded UTF-8 in the package. A good source contains
+# none of the classic GBK-mojibake fragments below; if it does, stop before
+# copying a template that would render garbage button labels.
+if grep -rEl '鈼|鉂|锛|鈱|鈥|�' "$SRC" --include='*.php' --include='*.js' >/dev/null 2>&1; then
+  echo "ERROR: mojibake found in $SRC (repair the UTF-8 source before installing)." >&2
+  grep -rEl '鈼|鉂|锛|鈱|鈥|�' "$SRC" --include='*.php' --include='*.js' >&2 || true
+  exit 1
+fi
+
 DEST=""
 CONTAINER=""
 if [ $# -ge 1 ]; then