feat(plan-management): implement alert-remediation-plan system v1.3
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
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
- 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
This commit is contained in:
@@ -21,6 +21,16 @@ const dailyReportStore = require("./dailyReportStore.cjs");
|
||||
const reportScheduler = require("./reportScheduler.cjs");
|
||||
const reportExport = require("./reportExport.cjs");
|
||||
const { executeBoundedSshCommand } = require("../boundedSshExec.cjs");
|
||||
|
||||
// ---------- P0 新增:预案管理与告警联动 ----------
|
||||
const planTemplateStore = require("./planTemplateStore.cjs");
|
||||
const devicePlanStore = require("./devicePlanStore.cjs");
|
||||
const commandCatalogStore = require("./commandCatalogStore.cjs");
|
||||
const aggregationEngine = require("./aggregationEngine.cjs");
|
||||
const remediationEngine = require("./remediationEngine.cjs");
|
||||
const alertStateMonitor = require("./alertStateMonitor.cjs");
|
||||
const initialResponseGenerator = require("./initialResponseGenerator.cjs");
|
||||
const planGenerator = require("./planGenerator.cjs");
|
||||
const {
|
||||
ALERT_LEVEL_LABELS,
|
||||
DEVICE_TIER_LABELS,
|
||||
@@ -737,6 +747,114 @@ function registerInspectionHandlers(ipcMain, options) {
|
||||
broadcastAlertChanged();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// P1 新增:预案匹配 + 初始响应包 + 聚合升级(D9 匹配时刻冻结 snapshot)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
// 收集本巡检周期内所有新增/更新的告警(包括 executionError)
|
||||
const allNewAlerts = [...triggeredAlerts];
|
||||
|
||||
// 构建设备画像(从 deviceExt + _hostConfigs)
|
||||
let deviceExt = null;
|
||||
try { deviceExt = deviceStore.getExtension(readFn, hostId); } catch {}
|
||||
const hcCached = _hostConfigs?.[hostId];
|
||||
const deviceProfile = {
|
||||
host_id: hostId,
|
||||
label: hcCached?.label || deviceExt?.label || hcCached?.hostname || deviceInfo.device_name,
|
||||
hostname: hcCached?.host || hcCached?.hostname,
|
||||
catalog_type: deviceExt?.catalog_id || catalogId || "unknown",
|
||||
tier: deviceExt?.tier || "",
|
||||
group: deviceExt?.group || "",
|
||||
location: deviceExt?.location || "",
|
||||
};
|
||||
|
||||
// 对每条告警做预案匹配(D9:匹配时刻冻结 snapshot)
|
||||
const aiBridge = require("../aiBridge.cjs");
|
||||
for (const alert of allNewAlerts) {
|
||||
if (!alert || !alert.id) continue;
|
||||
|
||||
try {
|
||||
// 1. 预案匹配
|
||||
const matchResult = devicePlanStore.matchPlan(readFn, alert, deviceProfile);
|
||||
if (matchResult?.plan) {
|
||||
console.info(`[alertMatcher] 告警 ${alert.id} 匹配预案 ${matchResult.plan.id}(score=${matchResult.score})`);
|
||||
alertRecordStore.setMatchedPlanSnapshot(readFn, writeFn, alert.id, matchResult.plan);
|
||||
|
||||
// 2. human_action ↔ remediation 完整性检查
|
||||
const mappingCheck = devicePlanStore.validateHumanActionRemediationMapping(matchResult.plan);
|
||||
if (mappingCheck.warnings.length > 0) {
|
||||
console.warn(`[alertMatcher] 预案 ${matchResult.plan.id} 完整性警告:`, mappingCheck.warnings);
|
||||
}
|
||||
|
||||
// 3. 异步触发初始响应包生成(不阻塞巡检主流程)
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
const response = await initialResponseGenerator.generate(
|
||||
alert, deviceProfile, matchResult.plan, { aiBridge }
|
||||
);
|
||||
alertRecordStore.setInitialResponse(readFn, writeFn, alert.id, response);
|
||||
console.info(`[initialResponse] 告警 ${alert.id} 响应包生成完成 (status=${response.status})`);
|
||||
broadcastAlertChanged();
|
||||
} catch (e) {
|
||||
console.warn(`[initialResponse] 告警 ${alert.id} 响应包生成失败:`, e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[alertMatcher] 告警 ${alert.id} 预案匹配失败:`, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 实时聚合检查(D2:每来一条新告警就检查窗口计数)
|
||||
setImmediate(() => {
|
||||
try {
|
||||
const allActiveAlerts = alertRecordStore.listAlerts(readFn, { limit: 500 });
|
||||
const deviceProfiles = { [hostId]: deviceProfile };
|
||||
// 合并 _hostConfigs 里的其他设备画像
|
||||
if (_hostConfigs) {
|
||||
for (const [hid, hc] of Object.entries(_hostConfigs)) {
|
||||
if (!deviceProfiles[hid]) {
|
||||
const ext = deviceStore.getExtension(readFn, hid);
|
||||
deviceProfiles[hid] = {
|
||||
host_id: hid,
|
||||
hostname: hc.host || hc.hostname,
|
||||
catalog_type: ext?.catalog_id || hc.catalog_id || "unknown",
|
||||
tier: ext?.tier || "",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
const hits = aggregationEngine.checkAggregation(readFn, allActiveAlerts, deviceProfiles);
|
||||
if (hits.length > 0) {
|
||||
console.warn(`[aggregation] 触发 ${hits.length} 条聚合规则`);
|
||||
for (const hit of hits) {
|
||||
aggregationEngine.escalateAlert(readFn, writeFn, "aggregation", {
|
||||
source_alert_ids: hit.matchingAlerts.map((a) => a.id),
|
||||
target_level: hit.rule.target_level,
|
||||
message: `聚合升级:${hit.matchingAlerts.length} 台设备在 5 分钟内同时 ${hit.rule.alert_type_filter?.join("/") || "告警"}`,
|
||||
});
|
||||
}
|
||||
broadcastAlertChanged();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("[aggregation] 聚合检查失败:", e.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 5. AlertStateMonitor 自动启动(首次有告警时)
|
||||
try {
|
||||
if (!alertStateMonitor.isRunning()) {
|
||||
alertStateMonitor.start({
|
||||
readFn, writeFn,
|
||||
alertRecordStore,
|
||||
aggregationEngine,
|
||||
remediationEngine,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("[AlertStateMonitor] 自动启动失败:", e.message);
|
||||
}
|
||||
|
||||
return {
|
||||
success: result.status === INSPECTION_STATUS.SUCCESS,
|
||||
status: result.status,
|
||||
@@ -2259,6 +2377,116 @@ function registerInspectionHandlers(ipcMain, options) {
|
||||
return _scheduler.scanAllNow();
|
||||
});
|
||||
|
||||
// ---------- P0 新增:预案模板 ----------
|
||||
ipcMain.handle("inspection:listPlanTemplates", () => planTemplateStore.listTemplates(readFn));
|
||||
ipcMain.handle("inspection:getPlanTemplate", (_e, id) => planTemplateStore.getTemplate(readFn, id));
|
||||
ipcMain.handle("inspection:createPlanTemplate", (_e, data) => planTemplateStore.createTemplate(readFn, writeFn, data));
|
||||
ipcMain.handle("inspection:updatePlanTemplate", (_e, id, updates) => planTemplateStore.updateTemplate(readFn, writeFn, id, updates));
|
||||
ipcMain.handle("inspection:deletePlanTemplate", (_e, id) => planTemplateStore.deleteTemplate(readFn, writeFn, id));
|
||||
|
||||
// ---------- P0 新增:设备预案 ----------
|
||||
ipcMain.handle("inspection:listDevicePlans", (_e, filters) => devicePlanStore.listPlans(readFn, filters || {}));
|
||||
ipcMain.handle("inspection:getDevicePlan", (_e, id) => devicePlanStore.getPlan(readFn, id));
|
||||
ipcMain.handle("inspection:createDevicePlan", (_e, data) => devicePlanStore.createPlan(readFn, writeFn, data));
|
||||
ipcMain.handle("inspection:updateDevicePlan", (_e, id, updates) => devicePlanStore.updatePlan(readFn, writeFn, id, updates));
|
||||
ipcMain.handle("inspection:deleteDevicePlan", (_e, id) => devicePlanStore.deletePlan(readFn, writeFn, id));
|
||||
ipcMain.handle("inspection:approveDevicePlan", (_e, id, actor) => devicePlanStore.approvePlan(readFn, writeFn, id, actor));
|
||||
ipcMain.handle("inspection:rejectDevicePlan", (_e, id, reason, actor) => devicePlanStore.rejectPlan(readFn, writeFn, id, reason, actor));
|
||||
ipcMain.handle("inspection:matchPlanForAlert", (_e, alert, deviceProfile) => devicePlanStore.matchPlan(readFn, alert, deviceProfile));
|
||||
|
||||
// ---------- P0 新增:聚合规则 ----------
|
||||
ipcMain.handle("inspection:listAggregationRules", () => aggregationEngine.listRules(readFn));
|
||||
ipcMain.handle("inspection:createAggregationRule", (_e, data) => aggregationEngine.createRule(readFn, writeFn, data));
|
||||
ipcMain.handle("inspection:updateAggregationRule", (_e, id, updates) => aggregationEngine.updateRule(readFn, writeFn, id, updates));
|
||||
ipcMain.handle("inspection:deleteAggregationRule", (_e, id) => aggregationEngine.deleteRule(readFn, writeFn, id));
|
||||
|
||||
// ---------- P0 新增:命令目录 ----------
|
||||
ipcMain.handle("inspection:listCommandCatalog", (_e, filters) => commandCatalogStore.listCommands(readFn, filters || {}));
|
||||
ipcMain.handle("inspection:getCommandCatalog", (_e, id) => commandCatalogStore.getCommand(readFn, id));
|
||||
|
||||
// ---------- P0 新增:AI 预案生成 ----------
|
||||
ipcMain.handle("inspection:generatePlan", async (_e, description) => {
|
||||
try {
|
||||
const aiBridge = require("../aiBridge.cjs");
|
||||
const result = await planGenerator.generatePlan(description, {
|
||||
aiBridge,
|
||||
commandCatalogStore,
|
||||
devicePlanStore,
|
||||
templateStore: planTemplateStore,
|
||||
});
|
||||
return result;
|
||||
} catch (e) {
|
||||
return { ok: false, error: e?.message || String(e) };
|
||||
}
|
||||
});
|
||||
|
||||
// ---------- P0 新增:初始响应包(手动触发)----------
|
||||
ipcMain.handle("inspection:regenerateInitialResponse", async (_e, alertId) => {
|
||||
try {
|
||||
const alert = alertRecordStore.getAlert(readFn, alertId);
|
||||
if (!alert) return { ok: false, error: "告警不存在" };
|
||||
const snapshot = alert.matched_plan_snapshot;
|
||||
const deviceProfile = alert.device_profile || { catalog_type: alert.alert_catalog };
|
||||
const aiBridge = require("../aiBridge.cjs");
|
||||
const response = await initialResponseGenerator.generate(alert, deviceProfile, snapshot, {
|
||||
aiBridge,
|
||||
});
|
||||
alertRecordStore.setInitialResponse(readFn, writeFn, alertId, response);
|
||||
broadcastAlertChanged();
|
||||
return { ok: true, response };
|
||||
} catch (e) {
|
||||
return { ok: false, error: e?.message || String(e) };
|
||||
}
|
||||
});
|
||||
|
||||
// ---------- P0 新增:处置执行 ----------
|
||||
ipcMain.handle("inspection:startRemediation", async (_e, alertId) => {
|
||||
try {
|
||||
const alert = alertRecordStore.getAlert(readFn, alertId);
|
||||
if (!alert) return { ok: false, error: "告警不存在" };
|
||||
if (!alert.matched_plan_snapshot) return { ok: false, error: "告警未匹配预案" };
|
||||
|
||||
const result = await remediationEngine.startRemediation(alert, {
|
||||
operator: "manual",
|
||||
onComplete: (runState) => {
|
||||
alertRecordStore.setRemediation(readFn, writeFn, alertId, {
|
||||
status: runState.status,
|
||||
started_at: runState.started_at,
|
||||
steps: runState.steps,
|
||||
});
|
||||
broadcastAlertChanged();
|
||||
},
|
||||
onStepComplete: (step, idx, runState) => {
|
||||
alertRecordStore.setRemediation(readFn, writeFn, alertId, {
|
||||
status: runState.status,
|
||||
started_at: runState.started_at,
|
||||
steps: runState.steps,
|
||||
});
|
||||
},
|
||||
});
|
||||
broadcastAlertChanged();
|
||||
return { ok: true, result };
|
||||
} catch (e) {
|
||||
return { ok: false, error: e?.message || String(e) };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("inspection:pauseRemediation", (_e, alertId) => {
|
||||
return remediationEngine.pause(alertId);
|
||||
});
|
||||
ipcMain.handle("inspection:resumeRemediation", (_e, alertId) => {
|
||||
return remediationEngine.resume(alertId);
|
||||
});
|
||||
ipcMain.handle("inspection:cancelRemediation", (_e, alertId) => {
|
||||
return remediationEngine.cancel(alertId);
|
||||
});
|
||||
ipcMain.handle("inspection:getRemediationStatus", (_e, alertId) => {
|
||||
return remediationEngine.getStatus(alertId);
|
||||
});
|
||||
|
||||
// ---------- P0 新增:AlertStateMonitor ----------
|
||||
ipcMain.handle("inspection:getAlertStateMonitorStatus", () => alertStateMonitor.getStatus());
|
||||
|
||||
console.log("[Inspection] IPC handlers 已注册");
|
||||
|
||||
// ─── Electron 启动时自动启动 reportScheduler ───
|
||||
|
||||
Reference in New Issue
Block a user