feat: 新增设备巡检模块实现完整的巡检告警功能

新增设备巡检模块核心功能,包括:
- 巡检数据存储与调度系统
- 华为/H3C交换机及Linux服务器采集器
- 设备扩展配置与告警规则管理
- 主机详情页巡检配置入口
- 产品名称自定义设置功能
- 网络设备SSH exec通道兼容处理
This commit is contained in:
2026-10-09 10:37:48 +08:00
parent 9202506c46
commit c64e00d835
75 changed files with 12087 additions and 446 deletions

View File

@@ -0,0 +1,174 @@
"use strict";
/**
* 华为 / H3C 交换机采集器
*
* 采集指标:
* - cpu_usage CPU 使用率(%)
* - memory_usage 内存使用率(%)
* - port_status 端口状态 { up_count, down_count }
* - power_status 电源状态 { normal_count, abnormal_count }
* - fan_status 风扇状态
*
* 实现说明:
* 使用批量命令执行,一次连接执行所有命令,避免 VTY 超时
*/
const BaseAdapter = require("./baseAdapter.cjs");
class HuaweiH3CSwitchAdapter extends BaseAdapter {
constructor(connectionInfo, options = {}) {
super(connectionInfo, options);
this.adapterName = "huawei_h3c_switch";
if (options.execCommands) {
this.execCommands = options.execCommands;
}
}
async execCommands(commands, options = {}) {
const { execSshCommands } = require("../inspectionExec.cjs");
return await execSshCommands(this.connectionInfo, commands, options);
}
getSupportedMetrics() {
return [
"cpu_usage",
"memory_usage",
"port_status",
"power_status",
"fan_status",
];
}
async collectMetrics() {
const metrics = {};
const DEBUG = process.env.INSPECTION_DEBUG === "1";
if (DEBUG) console.log("[HuaweiH3C] 开始采集交换机指标");
const commands = [
"display cpu-usage",
"display memory",
"display interface brief",
"display power",
"display fan",
];
try {
const { results } = await this.execCommands(commands, { timeoutMs: 180000 });
if (DEBUG) {
console.log("[HuaweiH3C] 原始输出:");
for (let i = 0; i < results.length; i++) {
// 简化日志,不输出完整原始数据
const out = results[i]?.output || "";
console.log(`[HuaweiH3C] 命令${i + 1}: ${commands[i]} → ${out.length} 字符`);
}
}
if (results && results.length >= 5) {
metrics.cpu_usage = this.parseCpuUsage(results[0]?.output || "");
metrics.memory_usage = this.parseMemoryUsage(results[1]?.output || "");
metrics.port_status = this.parsePorts(results[2]?.output || "");
metrics.power_status = this.parsePower(results[3]?.output || "");
metrics.fan_status = this.parseFan(results[4]?.output || "");
}
if (DEBUG) console.log("[HuaweiH3C] Final:", JSON.stringify(metrics));
} catch (e) {
if (DEBUG) console.log("[HuaweiH3C] Error:", e.message);
throw e;
}
return metrics;
}
parseCpuUsage(output) {
// H3C Comware 5/7: "7% in last 5 seconds"
let m = output.match(/(\d+)\s*%\s*in\s+last\s+5\s+seconds/i);
if (m) return parseFloat(m[1]);
// 1 minute
m = output.match(/(\d+)\s*%\s*in\s+last\s+1\s+minute/i);
if (m) return parseFloat(m[1]);
// 华为格式
m = output.match(/CPU\s+Usage\s*:\s*(\d+)%/i);
if (m) return parseFloat(m[1]);
// 5 Seconds
m = output.match(/5\s*Seconds\s*[::]\s*(\d+)\s*%/i);
if (m) return parseFloat(m[1]);
return null;
}
parseMemoryUsage(output) {
// H3C: "Used Rate: 45%"
let m = output.match(/Used\s*Rate\s*[::]?\s*(\d+(?:\.\d+)?)\s*%/i);
if (m) return parseFloat(m[1]);
// 华为/其他: "Memory Using Percentage Is: 21%"
m = output.match(/Memory\s+Using\s+Percentage\s+Is\s*[::]?\s*(\d+(?:\.\d+)?)\s*%/i);
if (m) return parseFloat(m[1]);
// H3C bytes 格式: "System Total Memory(bytes): 65546160" / "Total Used Memory(bytes): 29528544"
const totalM = output.match(/System\s+Total\s+Memory\s*\(\s*bytes\s*\)\s*[::]?\s*(\d+)/i);
const usedM = output.match(/Total\s+Used\s+Memory\s*\(\s*bytes\s*\)\s*[::]?\s*(\d+)/i);
if (totalM && usedM) {
const total = parseFloat(totalM[1]);
const used = parseFloat(usedM[1]);
if (total > 0) return Math.round((used / total) * 100 * 10) / 10;
}
// MB 格式: "Memory Size: xxx MB"
const totalMB = output.match(/Memory\s*(?:Size|Total)\s*[::]?\s*(\d+)\s*MB/i);
const usedMB = output.match(/Memory\s*Used\s*[::]?\s*(\d+)\s*MB/i);
if (totalMB && usedMB) {
const total = parseFloat(totalMB[1]);
const used = parseFloat(usedMB[1]);
if (total > 0) return Math.round((used / total) * 100 * 10) / 10;
}
return null;
}
parsePorts(output) {
const lines = output.split("\n");
let upCount = 0, downCount = 0;
const ports = [];
for (const line of lines) {
// H3C 格式: "GE1/0/1 UP 100M(a)"
// 匹配 GE开头 + UP/DOWN
const geMatch = line.match(/^(GE\d+\/\d+\/\d+)\s+(UP|DOWN|up|down)/i);
if (geMatch) {
const phy = geMatch[2].toLowerCase();
ports.push({ name: geMatch[1], phy });
if (phy === "up") upCount++;
else downCount++;
continue;
}
// 匹配 GigabitEthernet 全称
const gigMatch = line.match(/^(GigabitEthernet\d+[\d\/]+)\s+(UP|DOWN|up|down)/i);
if (gigMatch) {
const phy = gigMatch[2].toLowerCase();
ports.push({ name: gigMatch[1], phy });
if (phy === "up") upCount++;
else downCount++;
}
}
return { up_count: upCount, down_count: downCount, ports };
}
parsePower(output) {
let normal = 0, abnormal = 0;
for (const line of output.split("\n")) {
if (/Normal|Supply|OK/i.test(line)) normal++;
else if (/Absent|Fault|Error/i.test(line)) abnormal++;
}
return { normal_count: normal, abnormal_count: abnormal };
}
parseFan(output) {
let normal = 0, abnormal = 0;
for (const line of output.split("\n")) {
if (/Normal|Auto|Running|OK/i.test(line)) normal++;
else if (/Fault|Absent|Error/i.test(line)) abnormal++;
}
return { normal_count: normal, abnormal_count: abnormal };
}
}
module.exports = HuaweiH3CSwitchAdapter;