Browse Source

fix: detect Huawei PoE capability via entity model fallback

DeepSeek Harness 2 days ago
parent
commit
bb1b120fa8

+ 20 - 5
collector/port_status.py

@@ -44,6 +44,7 @@ O = {
 }
 POE_STD = '1.3.6.1.2.1.105.1.1.1.1.1'                   # RFC 3621 pethPsePortAdminEnable
 POE_HW_JUDGE = '1.3.6.1.4.1.2011.5.25.195.4.1.2'        # HUAWEI-POE-MIB hwPoePortJudgeIsSupport (TruthValue)
+POE_ENTITY = '1.3.6.1.2.1.47.1.1.1.1.2'                 # ENTITY-MIB entPhysicalDescr (model description)
 def parse(lines, base):
     out = []
     for ln in lines:
@@ -66,13 +67,26 @@ def collect(t):
     iftype = n1('ifType'); ifadmin = n1('ifAdmin'); ifoper = n1('ifOper')
     ifspeed = n1('ifSpeed'); ifhigh = n1('ifHighSpeed'); lastchg = n1('ifLastChange')
     conn = n1('ifConnector'); dup = n1('duplex')
-    # PoE capability: standard RFC 3621 PSE table, plus Huawei's TruthValue
-    # capability table.  A row in the standard table means a PSE port exists;
-    # Huawei reports true(1)/false(2) for each interface.  This is capability,
-    # not current PD power, so it stays stable when no device is attached.
+    # PoE capability: standard RFC 3621 PSE table, Huawei's TruthValue
+    # capability table, and finally the ENTITY-MIB model description.  Some
+    # Huawei firmware hides the HUAWEI-POE-MIB subtree from SNMP views even
+    # though the switch is a PoE+ model; the entity description still contains
+    # the exact model (for example "S5735-L24P4XE-A-V2 ... PoE+"), so it is a
+    # reliable capability fallback.  This is capability, not current PD power.
     std_poe = parse(walk(args, ip, POE_STD), POE_STD)
     hw_poe = parse(walk(args, ip, POE_HW_JUDGE), POE_HW_JUDGE)
     poe = bool(std_poe) or any(num(v) == 1 for _, v in hw_poe)
+    poe_source = 'std' if std_poe else ('huawei' if poe else '')
+    poe_model = ''
+    if not poe:
+        ent_poe = parse(walk(args, ip, POE_ENTITY), POE_ENTITY)
+        for _, v in ent_poe:
+            txt = strval(v)
+            if 'POE' in txt.upper() or 'PSE' in txt.upper():
+                poe = True
+                poe_source = 'entity'
+                poe_model = txt
+                break
     # FDB per port -> count and sample macs
     bpif = {num(s.split('.')[-1]): num(v) for s, v in parse(walk(args, ip, O['bpIf']), O['bpIf'])}
     st = {num(s.split('.')[-1]): num(v) for s, v in parse(walk(args, ip, O['fdbStatus']), O['fdbStatus'])}
@@ -90,7 +104,8 @@ def collect(t):
     d.update({'ok': True, 'ifname': ifname, 'ifdescr': ifdescr, 'ifalias': ifalias, 'iftype': iftype,
               'ifadmin': ifadmin, 'ifoper': ifoper, 'ifspeed': ifspeed, 'ifhigh': ifhigh,
               'lastchg': lastchg, 'conn': conn, 'dup': dup, 'mac_cnt': dict(mac_cnt),
-              'mac_by_if': {k: v for k, v in mac_by_if.items()}, 'poe': poe})
+              'mac_by_if': {k: v for k, v in mac_by_if.items()}, 'poe': poe,
+              'poe_source': poe_source, 'poe_model': poe_model})
     return d
 
 def main():

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

@@ -238,8 +238,7 @@ zabbix-deploy/
     但任何目标标签和其他线路不被遮挡;同一车间内部线路互不交叉,跨区直连核心的链路除外。点任意交换机高亮完整上联链路,点空白恢复。
   - **数据流动效果**:正常在线链路叠加从核心向下游流动的细虚线(下行/离线链路不流动),
     100M/1G/10G/20G+ 的流动速度依次加快,仅作视觉提示,不改变任何连接关系。
-  - **PoE 交换机**:采集器通过华为 `hwPoePortJudgeIsSupport` 或 RFC3621 `pethPsePortAdminEnable` 判断 PoE 能力,
-    交换机名称最前面显示 `⚡️`;当前网络若没有 PoE 设备则不显示。
+  - **PoE 交换机**:采集器优先通过 RFC3621 `pethPsePortAdminEnable` 和华为 `hwPoePortJudgeIsSupport` 判断;部分华为固件不开放 HUAWEI-POE-MIB 时,再用 ENTITY-MIB `entPhysicalDescr` 的型号描述(如 `S5735-L24P4XE-A-V2 ... PoE+`)兜底。支持 PoE 的交换机名称最前面显示 `⚡️`;当前离线或 SNMP 无响应的设备不显示。
 - **节点标签**:`车间 · 楼层机柜U · 角色 · IP`;双击节点跳转该主机的问题页。
 - **告警/离线/延迟着色**:告警交换机整个节点填充红色,离线交换机同样整节点红色;延迟 **3–5ms 为橙色边框**,**大于 5ms 才为红色边框**。告警不会再只画一个红框。告警面板只显示拓扑内交换机,不混入服务器/测试机等“拓扑外”主机。
 - **显示终端(端口矩阵)**:勾选「显示终端」后,接入/汇聚交换机下方按实际端口清单绘制圆点;外圈颜色表示该端口**支持的最大速率**(100M 橙、1G 绿、2.5G 青、10G 紫、20G+ 深紫、未知灰),圆心实心颜色表示该端口**当前实际链路速率**。例如:10G 口插 1G 线 = 紫色外圈 + 绿色实心;10G 口未插线 = 空心紫色圈;2.5G 口 = 青色圈。端口较多时按实际面板自动分两行/多行,上联端口单独成行;历史快照没有端口清单时退回通用 24 口两行矩阵(12+12),不丢已有终端位置。端口颜色与上方链路速率颜色是两套语义,不要混用。

+ 1 - 1
web-modules/WecomCenter/assets/l2topology/app.js

@@ -1683,7 +1683,7 @@ if (!colors['2500']) colors['2500'] = '#17C3B2';
   /* ---------------- side panels & stats ---------------- */
   function updateStats(snap) {
     var s = snap.stats || {};
-    var txt = '● ' + (s.switches || 0) + '  |  ⇄ ' + (s.edges || 0) + '  |  ' +
+    var txt = '● ' + (s.switches || 0) + '  |  ⇄ ' + (s.edges || 0) + '  |  ⚡ ' + (s.poe_switches || 0) + ' ' + t('l2.poe', 'PoE') + '  |  ' +
       (s.term_total || 0) + ' ' + t('l2.terminals', 'terminals') + '  |  ' +
       (s.switches_offline || 0) + ' ' + t('l2.offline_short', 'offline SW') + '  |  ⚠ ' + (s.alarms || 0);
     var up = snap.generated_iso ? ('  ·  ' + t('l2.updated', 'Updated') + ' ' + snap.generated_iso) : '';

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

@@ -6,7 +6,7 @@
 */
 use Modules\WecomCenter\Lib\I18n;
 
-$this->addCssFile('modules/WecomCenter/assets/l2topology/style.css?v=202611052015');
+$this->addCssFile('modules/WecomCenter/assets/l2topology/style.css?v=202611052032');
 $base = '/modules/WecomCenter/assets/l2topology/';
 
 $snapJson = (string) ($data['snap_json'] ?? 'null');
@@ -107,7 +107,7 @@ window.L2_META = {
 };
 </script>
 <script src="<?php echo $base; ?>cytoscape.min.js"></script>
-<script src="<?php echo $base; ?>app.js?v=202611052015"></script>
+<script src="<?php echo $base; ?>app.js?v=202611052032"></script>
 <script>
 (function(){
   var tabs = document.querySelectorAll('.side-tab');