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 自动生成双轨预案草稿