import React, { useState, useEffect } from 'react'; import { X, Server, Cpu, HardDrive, Activity, AlertTriangle, CheckCircle2, XCircle, Clock, Loader2, ChevronRight, } from 'lucide-react'; // ============ 类型定义 ============ export type PanelType = | 'deviceList' // 设备列表(按状态筛选) | 'alertList' // 告警列表(按级别筛选) | 'deviceDetail' // 单设备详情 | 'taskList'; // 巡检任务列表(按状态筛选) export interface DetailPanelProps { open: boolean; onClose: () => void; panelType: PanelType; panelTitle: string; // 设备列表 deviceList?: Array<{ id: string; name: string; ip?: string; type: string; status: 'online' | 'offline' | 'warning' | 'critical'; cpu?: number; memory?: number; lastUpdate?: number; alertCount?: number; }>; // 告警列表 alertList?: Array<{ id: string; deviceId: string; deviceName: string; level: 'critical' | 'warning' | 'info'; message: string; time: string; metric?: string; metricValue?: string; threshold?: string; }>; // 设备详情 deviceDetail?: { id: string; name: string; ip?: string; type: string; status: 'online' | 'offline' | 'warning' | 'critical'; cpu?: number; memory?: number; disk?: Array<{ mount: string; usage: number }>; lastUpdate?: number; alerts?: Array<{ id: string; level: string; message: string; time: string; }>; }; // 任务列表 taskList?: Array<{ deviceId: string; deviceName: string; ip: string; status: 'running' | 'success' | 'failed' | 'pending'; lastScanAt?: number; durationMs?: number; errorMessage?: string; interval?: number; }>; // 点击设备回调 onDeviceClick?: (deviceId: string) => void; // 点击告警回调 onAlertClick?: (alertId: string) => void; // 告警阈值(从主进程告警规则动态派生) thresholds?: { cpuCritical: number; cpuWarning: number; memCritical: number; memWarning: number; diskCritical: number; diskWarning: number; }; } // ============ 辅助函数 ============ const statusColors: Record = { online: { bg: 'bg-emerald-500/15', text: 'text-emerald-400', dot: 'bg-emerald-500', label: '在线' }, normal: { bg: 'bg-emerald-500/15', text: 'text-emerald-400', dot: 'bg-emerald-500', label: '正常' }, offline: { bg: 'bg-slate-500/15', text: 'text-slate-400', dot: 'bg-slate-500', label: '离线' }, warning: { bg: 'bg-amber-500/15', text: 'text-amber-400', dot: 'bg-amber-500', label: '警告' }, critical: { bg: 'bg-red-500/15', text: 'text-red-400', dot: 'bg-red-500', label: '严重' }, running: { bg: 'bg-sky-500/15', text: 'text-sky-400', dot: 'bg-sky-500', label: '执行中' }, success: { bg: 'bg-emerald-500/15', text: 'text-emerald-400', dot: 'bg-emerald-500', label: '成功' }, failed: { bg: 'bg-red-500/15', text: 'text-red-400', dot: 'bg-red-500', label: '失败' }, pending: { bg: 'bg-slate-500/15', text: 'text-slate-400', dot: 'bg-slate-500', label: '待执行' }, }; const levelColors: Record = { critical: { bg: 'bg-red-500/15', text: 'text-red-400', dot: 'bg-red-500', label: '严重' }, major: { bg: 'bg-orange-500/15', text: 'text-orange-400', dot: 'bg-orange-500', label: '重要' }, warning: { bg: 'bg-amber-500/15', text: 'text-amber-400', dot: 'bg-amber-500', label: '警告' }, minor: { bg: 'bg-yellow-500/15', text: 'text-yellow-400', dot: 'bg-yellow-500', label: '次要' }, info: { bg: 'bg-sky-500/15', text: 'text-sky-400', dot: 'bg-sky-500', label: '提示' }, }; function formatRelativeTime(ts?: number): string { if (!ts) return '-'; const diff = Date.now() - ts; if (diff < 60000) return '刚刚'; if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`; if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`; return `${Math.floor(diff / 86400000)} 天前`; } // ============ 组件 ============ export default function MonitoringDetailPanel({ open, onClose, panelType, panelTitle, deviceList = [], alertList = [], deviceDetail, taskList = [], onDeviceClick, thresholds = { cpuCritical: 90, cpuWarning: 80, memCritical: 85, memWarning: 80, diskCritical: 90, diskWarning: 80 }, }: DetailPanelProps) { // ============ 设备列表渲染 ============ const renderDeviceList = () => (
{deviceList.length === 0 && (
暂无数据
)} {deviceList.map((d) => { const sc = statusColors[d.status] || statusColors.online; return (
onDeviceClick?.(d.id)} >
{d.name}
{d.ip && {d.ip}} {d.type} {sc.label}
{(d.cpu !== undefined || d.memory !== undefined) && (
{d.cpu !== undefined && (
CPU thresholds.cpuCritical ? 'text-red-400' : d.cpu > thresholds.cpuWarning ? 'text-amber-400' : 'text-emerald-400'}> {d.cpu}%
)} {d.memory !== undefined && (
内存 thresholds.memCritical ? 'text-red-400' : d.memory > thresholds.memWarning ? 'text-amber-400' : 'text-emerald-400'}> {d.memory}%
)}
)}
); })}
); // ============ 告警列表渲染 ============ const renderAlertList = () => (
{alertList.length === 0 && (
暂无告警
)} {alertList.map((a) => { const lc = levelColors[a.level] || levelColors.info; return (
{lc.label}
{a.time}
{a.message}
设备:{a.deviceName} {a.metric && a.metricValue && ( {a.metric}: {a.metricValue} {a.threshold && (阈值 {a.threshold})} )}
); })}
); // ============ 设备详情渲染 ============ const renderDeviceDetail = () => { if (!deviceDetail) return null; const sc = statusColors[deviceDetail.status] || statusColors.online; return (
{/* 基本信息 */}
基本信息
设备名称
{deviceDetail.name}
设备类型
{deviceDetail.type}
IP 地址
{deviceDetail.ip || '-'}
运行状态
{sc.label}
{/* 资源使用 */}
资源使用
{/* CPU */}
CPU 使用率 thresholds.cpuCritical ? 'text-red-400' : deviceDetail.cpu && deviceDetail.cpu > thresholds.cpuWarning ? 'text-amber-400' : 'text-foreground'}> {deviceDetail.cpu ?? '-'}%
thresholds.cpuCritical ? '#f87171' : deviceDetail.cpu && deviceDetail.cpu > thresholds.cpuWarning ? '#fbbf24' : '#34d399', }} />
{/* 内存 */}
内存使用率 thresholds.memCritical ? 'text-red-400' : deviceDetail.memory && deviceDetail.memory > thresholds.memWarning ? 'text-amber-400' : 'text-foreground'}> {deviceDetail.memory ?? '-'}%
thresholds.memCritical ? '#f87171' : deviceDetail.memory && deviceDetail.memory > thresholds.memWarning ? '#fbbf24' : '#34d399', }} />
{/* 磁盘 */} {deviceDetail.disk && deviceDetail.disk.length > 0 && (
磁盘使用率
{deviceDetail.disk.map((d, i) => (
{d.mount} thresholds.diskCritical ? 'text-red-400' : d.usage > thresholds.diskWarning ? 'text-amber-400' : 'text-foreground'}> {d.usage}%
thresholds.diskCritical ? '#f87171' : d.usage > thresholds.diskWarning ? '#fbbf24' : '#34d399', }} />
))}
)}
{/* 更新时间 */}
最后更新:{formatRelativeTime(deviceDetail.lastUpdate)}
{/* 告警历史 */} {deviceDetail.alerts && deviceDetail.alerts.length > 0 && (
告警记录
{deviceDetail.alerts.map((a) => { const lc = levelColors[a.level] || levelColors.info; return (
{a.message}
{a.time}
); })}
)}
); }; // ============ 任务列表渲染 ============ const renderTaskList = () => (
{taskList.length === 0 && (
暂无数据
)} {taskList.map((t) => { const sc = statusColors[t.status] || statusColors.pending; const durationStr = t.durationMs ? `${(t.durationMs / 1000).toFixed(1)}s` : null; const intervalStr = t.interval ? `${t.interval}秒` : null; return (
{t.status === 'running' && } {t.status === 'success' && } {t.status === 'failed' && } {t.status === 'pending' && } {t.deviceName}
{sc.label}
{t.ip}
{t.lastScanAt && 上次 {formatRelativeTime(t.lastScanAt)}} {durationStr && 耗时 {durationStr}} {intervalStr && 间隔 {intervalStr}}
{t.errorMessage && (
{t.errorMessage}
)}
); })}
); // ============ 主渲染 ============ return ( <> {/* 遮罩 */}
{/* 面板 */}
{/* 标题栏 */}
{panelTitle}
{/* 内容区 */}
{panelType === 'deviceList' && renderDeviceList()} {panelType === 'alertList' && renderAlertList()} {panelType === 'deviceDetail' && renderDeviceDetail()} {panelType === 'taskList' && renderTaskList()}
); }