Files
SenMeshNet/electron/bridges/inspection/aggregationEngine.cjs
zhaolei a5744e720b
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
feat(plan-management): implement alert-remediation-plan system v1.3
- 8 new store modules + matcher + initialResponse + aggregation + remediation + AlertStateMonitor + planGenerator

- alert chain auto-integrated after runInspection: freeze snapshot + generate response + real-time agg + auto-start monitor

- AlertAIPanel component + MonitoringDetailPanel expandable alert list

- PlanManagementPage: full page with list/edit/AI generate/templates/commands
2026-10-30 16:55:55 +08:00

262 lines
7.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
/**
* 聚合规则存储 + 聚合升级引擎
*
* 规则结构:
* {
* id: "agg_core_switch_offline",
* name: "核心交换机批量离线升级",
* group_by: ["catalog_type", "tier"], // 维度(D6:catalog_type + tier)
* window_minutes: 5,
* threshold_count: 3,
* alert_type_filter: ["connectivity"],
* level_filter: ["critical"],
* target_level: "critical",
* enabled: true,
* }
*
* 触发方式:实时(D2)—— 每来一条新告警就检查窗口内计数
*/
const { STORAGE_KEY_AGGREGATION_RULES } = require("./storageKeys.cjs");
let _rules = null;
function _ensureLoaded(readFn) {
if (_rules === null) {
const raw = readFn(STORAGE_KEY_AGGREGATION_RULES);
_rules = raw ? _safeParse(raw, []) : _getDefaultRules();
}
}
function _safeParse(text, def) {
if (text === null || text === undefined) return def;
if (typeof text === "object") return text;
try { return JSON.parse(text); }
catch { return def; }
}
function _save(writeFn) {
writeFn(STORAGE_KEY_AGGREGATION_RULES, JSON.stringify(_rules));
}
function _getDefaultRules() {
const now = Date.now();
return [
{
id: "agg_core_switch_offline",
name: "核心设备批量离线升级",
group_by: ["catalog_type", "tier"],
window_minutes: 5,
threshold_count: 3,
alert_type_filter: ["connectivity"],
level_filter: ["critical"],
target_level: "critical",
enabled: true,
builtin: true,
created_at: now,
updated_at: now,
},
{
id: "agg_core_performance_spike",
name: "核心设备性能批量异常升级",
group_by: ["catalog_type", "tier"],
window_minutes: 10,
threshold_count: 5,
alert_type_filter: ["performance_high"],
level_filter: ["warning", "critical"],
target_level: "critical",
enabled: true,
builtin: true,
created_at: now,
updated_at: now,
},
];
}
// ---------- 规则 CRUD ----------
function listRules(readFn) {
_ensureLoaded(readFn);
return [..._rules];
}
function getRule(readFn, ruleId) {
_ensureLoaded(readFn);
return _rules.find((r) => r.id === ruleId) || null;
}
function createRule(readFn, writeFn, data) {
_ensureLoaded(readFn);
const rule = {
id: data.id || `agg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
name: data.name,
group_by: data.group_by || ["catalog_type", "tier"],
window_minutes: data.window_minutes || 5,
threshold_count: data.threshold_count || 3,
alert_type_filter: data.alert_type_filter || [],
level_filter: data.level_filter || [],
target_level: data.target_level || "critical",
enabled: data.enabled !== false,
builtin: false,
created_at: Date.now(),
updated_at: Date.now(),
};
_rules.push(rule);
_save(writeFn);
return { ...rule };
}
function updateRule(readFn, writeFn, ruleId, updates) {
_ensureLoaded(readFn);
const idx = _rules.findIndex((r) => r.id === ruleId);
if (idx === -1) return null;
if (_rules[idx].builtin) return null;
_rules[idx] = { ..._rules[idx], ...updates, updated_at: Date.now() };
_save(writeFn);
return { ..._rules[idx] };
}
function deleteRule(readFn, writeFn, ruleId) {
_ensureLoaded(readFn);
const idx = _rules.findIndex((r) => r.id === ruleId);
if (idx === -1) return false;
if (_rules[idx].builtin) return false;
_rules.splice(idx, 1);
_save(writeFn);
return true;
}
// ---------- 实时聚合检查(D2:每来一条新告警就调用)----------
/**
* 检查窗口内活跃告警是否命中聚合规则
* @param {object} readFn - 存储读取函数
* @param {Array} activeAlerts - alertRecordStore 返回的活跃告警列表
* @param {object} deviceProfiles - { [device_id]: { catalog_type, tier, ... } }
* @returns {Array} 命中的聚合规则 [{ rule, groupKey, matchingAlerts }]
*/
function checkAggregation(readFn, activeAlerts, deviceProfiles) {
_ensureLoaded(readFn);
const hits = [];
const now = Date.now();
for (const rule of _rules) {
if (!rule.enabled) continue;
const windowMs = rule.window_minutes * 60 * 1000;
const windowStart = now - windowMs;
// 过滤时间窗口内的活跃告警
const inWindow = activeAlerts.filter((a) => a.triggered_at >= windowStart);
// 按 group_by 维度分组
const groups = new Map();
for (const alert of inWindow) {
const profile = deviceProfiles?.[alert.device_id] || {};
let keyParts = [];
for (const dim of rule.group_by) {
if (dim === "catalog_type") keyParts.push(profile.catalog_type || alert.alert_catalog || "unknown");
else if (dim === "tier") keyParts.push(profile.tier || "unknown");
else keyParts.push("*");
}
const groupKey = keyParts.join("::");
// alert_type / level 过滤
if (rule.alert_type_filter?.length > 0) {
const at = alert.alert_type || "";
if (!rule.alert_type_filter.includes(at)) continue;
}
if (rule.level_filter?.length > 0 && !rule.level_filter.includes(alert.level)) {
continue;
}
if (!groups.has(groupKey)) groups.set(groupKey, []);
groups.get(groupKey).push(alert);
}
// 检查阈值
for (const [groupKey, matchingAlerts] of groups) {
if (matchingAlerts.length >= rule.threshold_count) {
hits.push({ rule, groupKey, matchingAlerts });
}
}
}
return hits;
}
/**
* 升级告警(产出新 alertRecord)
* @param {object} readFn - 存储读取函数
* @param {object} writeFn - 存储写入函数
* @param {string} reason - 升级原因
* @param {object} data - { source_alert_ids, target_level, message }
* @returns {object} 新告警记录
*/
function escalateAlert(readFn, writeFn, reason, data) {
const { alertRecordStore } = require("./alertRecordStore.cjs");
const alert = alertRecordStore._createAlert
? _createEscalationAlert(reason, data)
: _createEscalationAlert(reason, data);
// 直接写到 alertRecordStore(复用内部函数)
const alerts = _loadAlerts(readFn);
alerts.push(alert);
_saveAlerts(writeFn, alerts);
return alert;
}
// alertRecordStore 内部结构复用(直接操作存储避免循环依赖)
function _loadAlerts(readFn) {
const raw = readFn("netcatty_inspection_alert_records_v1");
return raw ? (typeof raw === "string" ? JSON.parse(raw) : raw) : [];
}
function _saveAlerts(writeFn, alerts) {
writeFn("netcatty_inspection_alert_records_v1", JSON.stringify(alerts));
}
function _createEscalationAlert(reason, data) {
const now = Date.now();
const deviceNames = (data.source_alert_ids || []).join(", ");
return {
id: `alert_esc_${now}_${Math.random().toString(36).slice(2, 8)}`,
rule_id: `rule_escalation_${reason}`,
rule_name: reason === "aggregation" ? "聚合升级" : "超时升级",
device_id: "group_escalation",
device_name: `[升级告警] ${deviceNames}`,
metric: "escalation",
metric_value: data.source_alert_ids?.length || 0,
threshold: data.target_level || "critical",
level: data.target_level || "critical",
message: data.message || `${reason}: ${data.source_alert_ids?.length || 0} 台设备告警触发升级`,
triggered_at: now,
status: "active",
acknowledged_by: null,
acknowledged_at: null,
resolved_at: null,
resolved_reason: null,
note: null,
// v1.3 扩展字段
escalation: {
trigger: reason, // aggregation | escalation_fallback_timeout
source_alert_ids: data.source_alert_ids || [],
target_level: data.target_level || "critical",
escalated_at: now,
},
};
}
function _resetForTest() { _rules = null; }
module.exports = {
listRules,
getRule,
createRule,
updateRule,
deleteRule,
checkAggregation,
escalateAlert,
_resetForTest,
};