import { useState, useEffect, useCallback, useMemo } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell, } from 'recharts'; import { RefreshCw, ChevronRight, Bell, AlertTriangle, Info, Activity, Server, Layers } from 'lucide-react'; import { kubernetesApi } from '@/api'; // Color palette for charts const CHART_COLORS = ['#EF4444', '#F59E0B', '#3B82F6', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316', '#6366F1', '#84CC16', '#06B6D4']; export default function EventsPage() { const { clusterCode } = useParams<{ clusterCode: string }>(); const navigate = useNavigate(); const [loading, setLoading] = useState(false); const [events, setEvents] = useState([]); const [clusters, setClusters] = useState([]); const [namespaces, setNamespaces] = useState([]); const [selectedNs, setSelectedNs] = useState('all'); const [filterType, setFilterType] = useState('all'); const [timeWindow, setTimeWindow] = useState('0'); // 0 = all const [activeCluster, setActiveCluster] = useState(clusterCode || ''); // Aggregation data const [summary, setSummary] = useState(null); const [selectedReason, setSelectedReason] = useState(''); const [selectedKind, setSelectedKind] = useState(''); // Expanded row const [expandedRow, setExpandedRow] = useState(null); useEffect(() => { kubernetesApi.getClusters().then(( res: any) => { const list = res?.list || []; setClusters(list); if (!activeCluster && list.length > 0) { const code = list[0].code || String(list[0].id); setActiveCluster(code); navigate(`/kubernetes/${code}/events`, { replace: true }); } }).catch(() => {}); }, []); useEffect(() => { if (!activeCluster) return; kubernetesApi.getNamespaces(activeCluster).then(( res: any) => { setNamespaces(Array.isArray(res) ? res : (res?.list || res?.data || [])); }).catch(() => {}); }, [activeCluster]); const loadEvents = useCallback(async () => { if (!activeCluster) return; setLoading(true); try { const ns = selectedNs === 'all' ? undefined : selectedNs; const ft = filterType === 'all' ? undefined : filterType; const res: Record = await kubernetesApi.getEvents(activeCluster, ns, ft); setEvents(Array.isArray(res) ? res : (res?.list || [])); } catch { setEvents([]); } finally { setLoading(false); } }, [activeCluster, selectedNs, filterType]); const loadSummary = useCallback(async () => { if (!activeCluster) return; try { const ns = selectedNs === 'all' ? undefined : selectedNs; const hours = parseInt(timeWindow) || undefined; const res: Record = await kubernetesApi.getEventsSummary(activeCluster, ns, hours); setSummary(res || null); } catch { setSummary(null); } }, [activeCluster, selectedNs, timeWindow]); useEffect(() => { loadEvents(); }, [loadEvents]); useEffect(() => { loadSummary(); }, [loadSummary]); // Filter events by selected reason/kind from aggregation click const displayEvents = useMemo(() => { let filtered = events; const hours = parseInt(timeWindow, 10); if (hours > 0) { const since = Date.now() - hours * 3600 * 1000; filtered = filtered.filter((e) => { const ts = e.last_time || e.lastTime || e.last_timestamp; return !ts || new Date(ts).getTime() >= since; }); } if (selectedReason) { filtered = filtered.filter(e => e.reason === selectedReason); } if (selectedKind) { filtered = filtered.filter(e => (e.object || '').startsWith(selectedKind + '/')); } return filtered; }, [events, selectedReason, selectedKind, timeWindow]); const warningCount = useMemo(() => events.filter(e => e.type === 'Warning').length, [events]); const normalCount = useMemo(() => events.filter(e => e.type === 'Normal').length, [events]); // Chart data from summary const reasonData = useMemo(() => { const list = summary?.by_reason || []; return list.map(( r: any) => ({ name: r.reason || 'Unknown', count: r.count })); }, [summary]); const nsData = useMemo(() => { const list = summary?.by_namespace || []; return list.map(( n: any) => ({ name: n.namespace || '', count: n.count })); }, [summary]); const kindData = useMemo(() => { const list = summary?.by_kind || []; return list.map(( k: any) => ({ name: k.kind || 'Unknown', count: k.count })); }, [summary]); const formatTime = (ts: string) => { if (!ts) return '-'; return ts.replace('T', ' ').substring(0, 19); }; const formatRelative = (ts: string) => { if (!ts) return ''; const diff = Date.now() - new Date(ts).getTime(); if (diff < 0) return ''; const mins = Math.floor(diff / 60000); if (mins < 1) return '刚刚'; if (mins < 60) return `${mins}分钟前`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs}小时前`; return `${Math.floor(hrs / 24)}天前`; }; return (
{/* Breadcrumb */}
集群 {activeCluster && <>{activeCluster}} 事件
{/* Header + Filters */}

事件中心

{/* Summary Stats Cards */}

事件总数

{summary?.total ?? events.length}

告警事件

{summary?.warning_count ?? warningCount}

Warning 累计触发次数

正常事件

{summary?.normal_count ?? normalCount}

告警类型数

{reasonData.length}

不同 Reason 数量

{/* Active Filter Badge */} {(selectedReason || selectedKind) && (
已选过滤: {selectedReason && ( setSelectedReason('')}> 原因: {selectedReason} ✕ )} {selectedKind && ( setSelectedKind('')}> 类型: {selectedKind} ✕ )}
)} {/* Charts Row */} {reasonData.length > 0 && (
{/* By Reason Chart */}

告警原因 Top {reasonData.length}

setSelectedReason(prev => prev === d?.name ? '' : d?.name)}> {reasonData.map(( _: any, i: number) => ( ))}

点击柱状图过滤事件列表

{/* By Namespace Chart */} {nsData.length > 0 && (

命名空间分布 Top {nsData.length}

)} {/* By Kind Chart */} {kindData.length > 0 && (

资源类型分布

setSelectedKind(prev => prev === d?.name ? '' : d?.name)}> {kindData.map(( _: any, i: number) => ( ))}

点击柱状图过滤事件列表

)}
)} {/* Recent Warnings Alert Panel */} {summary?.recent_warnings?.length > 0 && (

最新告警事件 ({summary.recent_warnings.length})

{summary.recent_warnings.map(( w: any, i: number) => (
{w.reason} {w.object} {w.namespace} ×{w.count} {formatRelative(w.last_time)}

{w.message}

))}
)} {/* Events Table */}
事件列表 ({displayEvents.length})
类型 原因 对象 消息 次数 命名空间 最后时间 {displayEvents.length === 0 ? ( {loading ? ⏳ : '暂无事件'} ) : displayEvents.slice(0, 200).map((e, i) => { const rowKey = `${e.name}-${i}`; const isExpanded = expandedRow === rowKey; return ( setExpandedRow(isExpanded ? null : rowKey)} > {e.type === 'Warning' ? ( W ) : ( N )} {e.reason || '-'} {e.object || '-'}
{e.message || '-'}
{isExpanded && (
首次出现: {formatTime(e.first_time)} ({formatRelative(e.first_time)})
最后出现: {formatTime(e.last_time)} ({formatRelative(e.last_time)})
事件名称: {e.name}
)}
{e.count > 1 ? {e.count} : 1} {e.namespace || '-'}
{formatTime(e.last_time)}
{formatRelative(e.last_time)}
); })}
{displayEvents.length > 200 && (
仅显示前 200 条,共 {displayEvents.length} 条事件
)}
); }