From a5744e720bfc4b1d45e8452548a2ca54c1f1c805 Mon Sep 17 00:00:00 2001 From: zhaolei <256168157@qq.com> Date: Fri, 30 Oct 2026 16:55:55 +0800 Subject: [PATCH] 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 --- components/inspection/PlanManagementPage.tsx | 1082 +++++++++++++++++ components/monitoring/AlertAIPanel.tsx | 379 ++++++ .../monitoring/MonitoringDetailPanel.tsx | 100 +- electron/bridges/aiBridge.cjs | 38 + .../bridges/inspection/aggregationEngine.cjs | 261 ++++ .../bridges/inspection/alertRecordStore.cjs | 77 ++ .../bridges/inspection/alertStateMonitor.cjs | 151 +++ .../inspection/commandCatalogStore.cjs | 159 +++ .../bridges/inspection/devicePlanStore.cjs | 268 ++++ electron/bridges/inspection/index.cjs | 228 ++++ .../inspection/initialResponseGenerator.cjs | 317 +++++ electron/bridges/inspection/planGenerator.cjs | 307 +++++ .../bridges/inspection/planTemplateStore.cjs | 171 +++ .../bridges/inspection/remediationEngine.cjs | 283 +++++ electron/bridges/inspection/storageKeys.cjs | 6 + electron/bridges/inspection/types.cjs | 71 ++ electron/preload/api.cjs | 37 + infrastructure/inspection/inspectionApi.ts | 195 +++ 18 files changed, 4109 insertions(+), 21 deletions(-) create mode 100644 components/inspection/PlanManagementPage.tsx create mode 100644 components/monitoring/AlertAIPanel.tsx create mode 100644 electron/bridges/inspection/aggregationEngine.cjs create mode 100644 electron/bridges/inspection/alertStateMonitor.cjs create mode 100644 electron/bridges/inspection/commandCatalogStore.cjs create mode 100644 electron/bridges/inspection/devicePlanStore.cjs create mode 100644 electron/bridges/inspection/initialResponseGenerator.cjs create mode 100644 electron/bridges/inspection/planGenerator.cjs create mode 100644 electron/bridges/inspection/planTemplateStore.cjs create mode 100644 electron/bridges/inspection/remediationEngine.cjs diff --git a/components/inspection/PlanManagementPage.tsx b/components/inspection/PlanManagementPage.tsx new file mode 100644 index 0000000..ee40c36 --- /dev/null +++ b/components/inspection/PlanManagementPage.tsx @@ -0,0 +1,1082 @@ +import React, { useEffect, useState } from 'react'; +import { + Wrench, Zap, Bot, Plus, Trash2, Edit3, Eye, CheckCircle2, XCircle, Clock, + ChevronDown, ChevronRight, Sparkles, AlertTriangle, Shield, Activity, + Play, Pause, X, Loader2, Search, RefreshCw, Settings, FileText, +} from 'lucide-react'; +import { Button } from '../ui/button'; +import { Input } from '../ui/input'; +import { Badge } from '../ui/badge'; +import { Textarea } from '../ui/textarea'; +import { + Select, SelectContent, SelectItem, SelectTrigger, SelectValue, +} from '../ui/select'; +import { + Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, +} from '../ui/dialog'; +import { + Tabs, TabsContent, TabsList, TabsTrigger, +} from '../ui/tabs'; +import { + planManagementApi, + type DevicePlan, + type CommandCatalogItem, + type PlanTemplate, +} from '../../infrastructure/inspection/inspectionApi'; + +// ==================================================================== +// 预案管理页面 +// ==================================================================== + +export function PlanManagementPage() { + const [activeTab, setActiveTab] = useState<'plans' | 'templates' | 'commands' | 'generate'>('plans'); + const [plans, setPlans] = useState([]); + const [templates, setTemplates] = useState([]); + const [commands, setCommands] = useState([]); + const [filterStatus, setFilterStatus] = useState('all'); + const [filterLevel, setFilterLevel] = useState('all'); + const [filterDevice, setFilterDevice] = useState('all'); + const [searchQuery, setSearchQuery] = useState(''); + const [loading, setLoading] = useState(false); + const [editPlan, setEditPlan] = useState(null); + const [isNewPlan, setIsNewPlan] = useState(false); + const [detailPlan, setDetailPlan] = useState(null); + + async function loadAll() { + setLoading(true); + try { + const [p, t, c] = await Promise.all([ + planManagementApi.listPlans({}), + planManagementApi.listTemplates(), + planManagementApi.listCommandCatalog(), + ]); + setPlans(p || []); + setTemplates(t || []); + setCommands(c || []); + } catch (e) { + console.error('[PlanManagement] load failed:', e); + } finally { + setLoading(false); + } + } + + useEffect(() => { loadAll(); }, []); + + const filteredPlans = plans.filter((p) => { + if (filterStatus !== 'all' && p.status !== filterStatus) return false; + if (filterLevel !== 'all' && p.level !== filterLevel) return false; + if (filterDevice !== 'all' && p.device_type !== filterDevice) return false; + if (searchQuery.trim()) { + const q = searchQuery.toLowerCase(); + return ( + p.description?.toLowerCase().includes(q) || + p.id.toLowerCase().includes(q) || + p.device_type.toLowerCase().includes(q) + ); + } + return true; + }); + + // ---------- Actions ---------- + async function handleToggleEnabled(p: DevicePlan) { + await planManagementApi.updatePlan(p.id, { enabled: !p.enabled }); + loadAll(); + } + + async function handleDelete(p: DevicePlan) { + if (p.builtin) return; + if (!confirm(`确定删除预案 "${p.description || p.id}"?`)) return; + await planManagementApi.deletePlan(p.id); + loadAll(); + } + + async function handleApprove(p: DevicePlan) { + await planManagementApi.approvePlan(p.id, 'admin'); + loadAll(); + } + + async function handleReject(p: DevicePlan) { + const reason = prompt('拒绝原因:', ''); + if (!reason) return; + await planManagementApi.rejectPlan(p.id, reason, 'admin'); + loadAll(); + } + + function openNewPlan() { + setIsNewPlan(true); + setEditPlan(_createEmptyPlan()); + } + + function openEditPlan(p: DevicePlan) { + setIsNewPlan(false); + setEditPlan(JSON.parse(JSON.stringify(p))); + } + + async function handleSavePlan(data: DevicePlan) { + if (isNewPlan) { + await planManagementApi.createPlan(data); + } else { + await planManagementApi.updatePlan(data.id, data); + } + setEditPlan(null); + loadAll(); + } + + // ---------- Render ---------- + return ( +
+ {/* Toolbar */} +
+
+
+ +
+
+
预案管理
+
双轨预案体系:值班员流程 + AI 操作步骤
+
+
+
+ + +
+
+ + {/* Tabs */} + setActiveTab(v as any)} className="flex-1 flex flex-col overflow-hidden"> +
+ + + 设备预案 + {plans.filter((p) => p.status === 'draft').length > 0 && ( + + {plans.filter((p) => p.status === 'draft').length} + + )} + + + 模板 + + + 命令目录 + + + AI 生成 + + +
+ + {/* 预案列表 */} + +
+
+ + setSearchQuery(e.target.value)} + className="pl-7 h-8 text-xs" + /> +
+ + + +
+ + +
+ + {/* 模板列表 */} + + + + + {/* 命令目录 */} + + + + + {/* AI 生成 */} + + + +
+ + {/* 编辑对话框 */} + {editPlan && ( + setEditPlan(null)} + onSave={handleSavePlan} + /> + )} + + {/* 详情对话框 */} + {detailPlan && ( + setDetailPlan(null)} + onEdit={() => { openEditPlan(detailPlan); setDetailPlan(null); }} + /> + )} +
+ ); +} + +// ==================================================================== +// 预案列表 +// ==================================================================== + +function PlanList({ + plans, onToggle, onDelete, onApprove, onReject, onEdit, onView, +}: { + plans: DevicePlan[]; + onToggle: (p: DevicePlan) => void; + onDelete: (p: DevicePlan) => void; + onApprove: (p: DevicePlan) => void; + onReject: (p: DevicePlan) => void; + onEdit: (p: DevicePlan) => void; + onView: (p: DevicePlan) => void; +}) { + if (plans.length === 0) { + return ( +
+
+ +
暂无预案,点击右上角"新建预案"开始配置
+
+
+ ); + } + + return ( +
+
+ {plans.map((p) => { + const levelColor: Record = { + critical: 'text-red-400 bg-red-500/10 border-red-500/20', + warning: 'text-amber-400 bg-amber-500/10 border-amber-500/20', + info: 'text-sky-400 bg-sky-500/10 border-sky-500/20', + }; + const statusBadge: Record = { + active: { cls: 'bg-emerald-500/15 text-emerald-400', label: '生效', icon: CheckCircle2 }, + draft: { cls: 'bg-amber-500/15 text-amber-400', label: '待审核', icon: Clock }, + disabled: { cls: 'bg-slate-500/15 text-slate-400', label: '已禁用', icon: XCircle }, + }; + const sb = statusBadge[p.status] || statusBadge.active; + const SBIcon = sb.icon; + + return ( +
+
+
+
+ + {p.level === 'critical' ? '严重' : p.level === 'warning' ? '警告' : '提示'} + + + {sb.label} + + {p.source === 'ai' && ( + + AI 生成 + + )} + {p.builtin && ( + 内置 + )} + {!p.enabled && ( + 未启用 + )} +
+
+ {p.description || p.id} +
+
+ 设备类型:{p.device_type} + 处置步骤:{p.remediation.steps.length} + {p.escalation_fallback?.auto_escalate_minutes && ( + + 升级超时:{p.escalation_fallback.auto_escalate_minutes}min + + )} +
+ {p.status === 'draft' && p.reject_reason && ( +
+ 上次拒绝原因:{p.reject_reason} +
+ )} +
+ + {/* Actions */} +
+ {p.status === 'draft' && ( + <> + + + + )} + {p.status !== 'draft' && !p.builtin && ( + + )} + + + {!p.builtin && ( + + )} +
+
+
+ ); + })} +
+
+ ); +} + +// ==================================================================== +// 模板 / 命令目录(简化展示) +// ==================================================================== + +function TemplateList({ templates }: { templates: PlanTemplate[] }) { + return ( +
+ {templates.map((t) => ( +
+
+
{t.name}
+ {t.level} +
+
{t.description}
+
+
通知通道:{t.notification.channels.join(', ') || '无'}
+ {t.escalation_fallback && ( +
自动升级:{t.escalation_fallback.auto_escalate_minutes}min → {t.escalation_fallback.escalation_target_level}
+ )} +
超时:{t.remediation_defaults.execution_timeout_sec}s
+
+
+ ))} +
+ ); +} + +function CommandCatalogList({ commands }: { commands: CommandCatalogItem[] }) { + const [filterType, setFilterType] = useState('all'); + const filtered = filterType === 'all' ? commands : commands.filter((c) => c.device_types.includes(filterType)); + const deviceTypes = Array.from(new Set(commands.flatMap((c) => c.device_types))); + + return ( +
+
+ 过滤: + + {deviceTypes.map((dt) => ( + + ))} +
+
+ {filtered.map((c) => { + const execColor: Record = { + auto: 'bg-emerald-500/15 text-emerald-400', + confirm: 'bg-amber-500/15 text-amber-400', + manual_only: 'bg-slate-500/15 text-slate-400', + }; + return ( +
+
+
+
{c.id}
+
{c.name}
+
{c.command}
+
+
+ + {c.execution === 'auto' ? '自动' : c.execution === 'confirm' ? '确认' : '手动'} + + {c.risk_level} +
+
+
+ ); + })} +
+
+ ); +} + +// ==================================================================== +// AI 生成面板 +// ==================================================================== + +function PlanAIGenerator({ onGenerated }: { onGenerated: () => void }) { + const [input, setInput] = useState(''); + const [loading, setLoading] = useState(false); + const [result, setResult] = useState(null); + const [warnings, setWarnings] = useState([]); + const [error, setError] = useState(null); + + async function handleGenerate() { + if (!input.trim()) return; + setLoading(true); + setError(null); + setResult(null); + setWarnings([]); + try { + const r = await planManagementApi.generatePlan(input); + if (!r.ok || !r.plan) { + setError(r.error || '生成失败'); + } else { + setResult(r.plan); + setWarnings(r.warnings || []); + } + } catch (e: any) { + setError(e.message || String(e)); + } finally { + setLoading(false); + } + } + + async function handleSave() { + if (!result) return; + await planManagementApi.createPlan(result); + onGenerated(); + setInput(''); + setResult(null); + setWarnings([]); + } + + function handleEdit() { + if (!result) return; + // 把草稿直接加载到编辑面板(简化:创建后再编辑) + setInput(''); + setResult(null); + setWarnings([]); + } + + return ( +
+
+
+ + AI 预案生成 + 一句话描述场景,AI 自动生成双轨预案草稿 +
+ +