feat: 新增设备巡检模块实现完整的巡检告警功能
新增设备巡检模块核心功能,包括: - 巡检数据存储与调度系统 - 华为/H3C交换机及Linux服务器采集器 - 设备扩展配置与告警规则管理 - 主机详情页巡检配置入口 - 产品名称自定义设置功能 - 网络设备SSH exec通道兼容处理
This commit is contained in:
280
electron/bridges/inspection/alertRecordStore.cjs
Normal file
280
electron/bridges/inspection/alertRecordStore.cjs
Normal file
@@ -0,0 +1,280 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* 告警记录存储
|
||||
*
|
||||
* 告警记录结构:
|
||||
* {
|
||||
* id: "alert_xxx",
|
||||
* rule_id: "rule_cpu_critical",
|
||||
* rule_name: "CPU 使用率过高",
|
||||
* device_id: "host_xxx",
|
||||
* device_name: "核心交换机-01",
|
||||
* metric: "cpu_usage",
|
||||
* metric_value: "85",
|
||||
* threshold: "80",
|
||||
* level: "critical",
|
||||
* message: "核心交换机-01 CPU 使用率 85%,超过阈值 80%",
|
||||
* triggered_at: timestamp,
|
||||
* status: "active",
|
||||
* acknowledged_by: null,
|
||||
* acknowledged_at: null,
|
||||
* resolved_at: null,
|
||||
* resolved_reason: null,
|
||||
* note: null,
|
||||
* }
|
||||
*/
|
||||
|
||||
const { STORAGE_KEY_ALERT_RECORDS, STORAGE_KEY_ALERT_COUNTERS } = require("./storageKeys.cjs");
|
||||
const { ALERT_RECORD_STATUS, ALERT_LEVELS } = require("./types.cjs");
|
||||
|
||||
const MAX_ALERTS = 2000;
|
||||
|
||||
let _alerts = null;
|
||||
let _counters = null; // 防抖动计数器 { "deviceId_ruleId": count }
|
||||
|
||||
function _ensureLoaded(readFn) {
|
||||
if (_alerts === null) {
|
||||
const raw = readFn(STORAGE_KEY_ALERT_RECORDS);
|
||||
_alerts = raw ? _safeParse(raw, []) : [];
|
||||
}
|
||||
if (_counters === null) {
|
||||
const raw = readFn(STORAGE_KEY_ALERT_COUNTERS);
|
||||
_counters = raw ? _safeParse(raw, {}) : {};
|
||||
}
|
||||
}
|
||||
|
||||
function _safeParse(text, def) {
|
||||
try { return JSON.parse(text); }
|
||||
catch (e) { return def; }
|
||||
}
|
||||
|
||||
function _saveAlerts(writeFn) {
|
||||
if (_alerts.length > MAX_ALERTS) {
|
||||
// 只保留最近的
|
||||
_alerts = _alerts.sort((a, b) => b.triggered_at - a.triggered_at).slice(0, MAX_ALERTS);
|
||||
}
|
||||
writeFn(STORAGE_KEY_ALERT_RECORDS, JSON.stringify(_alerts));
|
||||
}
|
||||
|
||||
function _saveCounters(writeFn) {
|
||||
writeFn(STORAGE_KEY_ALERT_COUNTERS, JSON.stringify(_counters));
|
||||
}
|
||||
|
||||
// ---------- 告警触发(带防抖动) ----------
|
||||
|
||||
/**
|
||||
* 处理告警:检查是否满足持续次数,满足则创建/更新告警记录
|
||||
* @param {object} device - 设备信息 { device_id, device_name }
|
||||
* @param {object} rule - 告警规则
|
||||
* @param {number|string} metricValue - 当前指标值
|
||||
* @param {boolean} isTriggered - 是否触发(指标值 > 阈值)
|
||||
* @returns {object|null} 新创建或恢复的告警记录,无变化则返回 null
|
||||
*/
|
||||
function processAlert(readFn, writeFn, device, rule, metricValue, isTriggered) {
|
||||
_ensureLoaded(readFn);
|
||||
|
||||
const counterKey = `${device.device_id}_${rule.id}`;
|
||||
const activeAlert = _findActiveAlert(device.device_id, rule.id);
|
||||
|
||||
if (isTriggered) {
|
||||
// 触发:计数 +1
|
||||
_counters[counterKey] = (_counters[counterKey] || 0) + 1;
|
||||
_saveCounters(writeFn);
|
||||
|
||||
// 达到持续次数且没有活跃告警 → 创建告警
|
||||
if (_counters[counterKey] >= rule.duration && !activeAlert) {
|
||||
const alert = _createAlert(device, rule, metricValue);
|
||||
_alerts.push(alert);
|
||||
_saveAlerts(writeFn);
|
||||
return { ...alert, action: "triggered" };
|
||||
}
|
||||
|
||||
// 已有活跃告警 → 更新指标值
|
||||
if (activeAlert) {
|
||||
activeAlert.metric_value = String(metricValue);
|
||||
activeAlert.message = _buildMessage(device.device_name, rule, metricValue);
|
||||
_saveAlerts(writeFn);
|
||||
return { ...activeAlert, action: "updated" };
|
||||
}
|
||||
|
||||
return null;
|
||||
} else {
|
||||
// 未触发:重置计数
|
||||
if (_counters[counterKey]) {
|
||||
delete _counters[counterKey];
|
||||
_saveCounters(writeFn);
|
||||
}
|
||||
|
||||
// 有活跃告警 → 自动恢复
|
||||
if (activeAlert && activeAlert.status === ALERT_RECORD_STATUS.ACTIVE) {
|
||||
activeAlert.status = ALERT_RECORD_STATUS.RESOLVED;
|
||||
activeAlert.resolved_at = Date.now();
|
||||
activeAlert.resolved_reason = "auto_recover";
|
||||
_saveAlerts(writeFn);
|
||||
return { ...activeAlert, action: "resolved" };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _findActiveAlert(deviceId, ruleId) {
|
||||
return _alerts.find(
|
||||
(a) =>
|
||||
a.device_id === deviceId &&
|
||||
a.rule_id === ruleId &&
|
||||
(a.status === ALERT_RECORD_STATUS.ACTIVE ||
|
||||
a.status === ALERT_RECORD_STATUS.ACKNOWLEDGED)
|
||||
);
|
||||
}
|
||||
|
||||
function _createAlert(device, rule, metricValue) {
|
||||
return {
|
||||
id: `alert_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
||||
rule_id: rule.id,
|
||||
rule_name: rule.name,
|
||||
device_id: device.device_id,
|
||||
device_name: device.device_name || device.device_id,
|
||||
metric: rule.metric,
|
||||
metric_value: String(metricValue),
|
||||
threshold: rule.threshold,
|
||||
level: rule.level,
|
||||
message: _buildMessage(device.device_name || device.device_id, rule, metricValue),
|
||||
triggered_at: Date.now(),
|
||||
status: ALERT_RECORD_STATUS.ACTIVE,
|
||||
acknowledged_by: null,
|
||||
acknowledged_at: null,
|
||||
resolved_at: null,
|
||||
resolved_reason: null,
|
||||
note: null,
|
||||
};
|
||||
}
|
||||
|
||||
function _buildMessage(deviceName, rule, metricValue) {
|
||||
const unit = rule.metric.includes("_usage") ? "%" : "";
|
||||
return `${deviceName} ${rule.name},当前值 ${metricValue}${unit},阈值 ${rule.threshold}${unit}`;
|
||||
}
|
||||
|
||||
// ---------- 告警查询 ----------
|
||||
|
||||
function listAlerts(readFn, options = {}) {
|
||||
_ensureLoaded(readFn);
|
||||
let result = [..._alerts];
|
||||
|
||||
if (options.status) {
|
||||
result = result.filter((a) => a.status === options.status);
|
||||
}
|
||||
if (options.level) {
|
||||
result = result.filter((a) => a.level === options.level);
|
||||
}
|
||||
if (options.device_id) {
|
||||
result = result.filter((a) => a.device_id === options.device_id);
|
||||
}
|
||||
|
||||
return result.sort((a, b) => b.triggered_at - a.triggered_at).slice(0, options.limit || 100);
|
||||
}
|
||||
|
||||
function getActiveAlerts(readFn) {
|
||||
return listAlerts(readFn, { status: ALERT_RECORD_STATUS.ACTIVE });
|
||||
}
|
||||
|
||||
function getAlert(readFn, alertId) {
|
||||
_ensureLoaded(readFn);
|
||||
return _alerts.find((a) => a.id === alertId) || null;
|
||||
}
|
||||
|
||||
// ---------- 告警处理 ----------
|
||||
|
||||
function acknowledgeAlert(readFn, writeFn, alertId, operator = "system") {
|
||||
_ensureLoaded(readFn);
|
||||
const alert = _alerts.find((a) => a.id === alertId);
|
||||
if (!alert) return null;
|
||||
|
||||
alert.status = ALERT_RECORD_STATUS.ACKNOWLEDGED;
|
||||
alert.acknowledged_by = operator;
|
||||
alert.acknowledged_at = Date.now();
|
||||
_saveAlerts(writeFn);
|
||||
return { ...alert };
|
||||
}
|
||||
|
||||
function resolveAlert(readFn, writeFn, alertId, reason = "manual", operator = "system") {
|
||||
_ensureLoaded(readFn);
|
||||
const alert = _alerts.find((a) => a.id === alertId);
|
||||
if (!alert) return null;
|
||||
|
||||
alert.status = ALERT_RECORD_STATUS.RESOLVED;
|
||||
alert.resolved_at = Date.now();
|
||||
alert.resolved_reason = reason;
|
||||
alert.acknowledged_by = alert.acknowledged_by || operator;
|
||||
alert.acknowledged_at = alert.acknowledged_at || Date.now();
|
||||
_saveAlerts(writeFn);
|
||||
return { ...alert };
|
||||
}
|
||||
|
||||
function closeAlert(readFn, writeFn, alertId, note = "") {
|
||||
_ensureLoaded(readFn);
|
||||
const alert = _alerts.find((a) => a.id === alertId);
|
||||
if (!alert) return null;
|
||||
|
||||
alert.status = ALERT_RECORD_STATUS.CLOSED;
|
||||
alert.note = note;
|
||||
if (!alert.resolved_at) {
|
||||
alert.resolved_at = Date.now();
|
||||
alert.resolved_reason = "closed";
|
||||
}
|
||||
_saveAlerts(writeFn);
|
||||
return { ...alert };
|
||||
}
|
||||
|
||||
function updateNote(readFn, writeFn, alertId, note) {
|
||||
_ensureLoaded(readFn);
|
||||
const alert = _alerts.find((a) => a.id === alertId);
|
||||
if (!alert) return null;
|
||||
|
||||
alert.note = note;
|
||||
_saveAlerts(writeFn);
|
||||
return { ...alert };
|
||||
}
|
||||
|
||||
// ---------- 统计 ----------
|
||||
|
||||
function getAlertStats(readFn) {
|
||||
_ensureLoaded(readFn);
|
||||
|
||||
const active = _alerts.filter((a) => a.status === ALERT_RECORD_STATUS.ACTIVE);
|
||||
return {
|
||||
total: _alerts.length,
|
||||
active: active.length,
|
||||
active_critical: active.filter((a) => a.level === ALERT_LEVELS.CRITICAL).length,
|
||||
active_warning: active.filter((a) => a.level === ALERT_LEVELS.WARNING).length,
|
||||
active_info: active.filter((a) => a.level === ALERT_LEVELS.INFO).length,
|
||||
acknowledged: _alerts.filter((a) => a.status === ALERT_RECORD_STATUS.ACKNOWLEDGED).length,
|
||||
resolved: _alerts.filter((a) => a.status === ALERT_RECORD_STATUS.RESOLVED).length,
|
||||
closed: _alerts.filter((a) => a.status === ALERT_RECORD_STATUS.CLOSED).length,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- 重置(测试用) ----------
|
||||
function _resetForTest() {
|
||||
_alerts = null;
|
||||
_counters = null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// 告警触发
|
||||
processAlert,
|
||||
// 查询
|
||||
listAlerts,
|
||||
getActiveAlerts,
|
||||
getAlert,
|
||||
// 处理
|
||||
acknowledgeAlert,
|
||||
resolveAlert,
|
||||
closeAlert,
|
||||
updateNote,
|
||||
// 统计
|
||||
getAlertStats,
|
||||
// 内部
|
||||
_resetForTest,
|
||||
};
|
||||
Reference in New Issue
Block a user