// LogHistogram.tsx — Log Volume 时间分布面积图 // Logan.Li import { useMemo } from 'react'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; import type { LogEngine } from './types-v3'; interface LogHistogramProps { data: { timestamp: string; count: number }[]; engine: LogEngine; onBarClick?: (start: string, end: string) => void; loading?: boolean; totalBytes?: number; // 新增:处理字节数 // Logan.Li lineLimitReached?: boolean; // 新增:是否达到行限制 coverage?: string; // 新增:时间覆盖率 } const ENGINE_COLORS: Record = { loki: { fill: 'url(#gradientLoki)', stroke: '#06b6d4' }, elasticsearch: { fill: 'url(#gradientES)', stroke: '#22c55e' }, sls: { fill: 'url(#gradientSLS)', stroke: '#f97316' }, }; function formatTimeLabel(ts: string): string { const parts = ts.split(':'); return parts.length >= 2 ? `${parts[0].slice(-2)}:${parts[1]}` : ts.slice(-5); } function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`; return `${(bytes / 1024 / 1024).toFixed(2)} MB`; } export function LogHistogram({ data, engine, onBarClick, loading, totalBytes, lineLimitReached, coverage }: LogHistogramProps) { const colors = ENGINE_COLORS[engine]; const chartData = useMemo(() => { return data.map(d => ({ ...d, label: formatTimeLabel(d.timestamp) })); }, [data]); if (loading) { return (
Loading log volume...
); } if (chartData.length === 0) { return (
Enter a query to display volume
); } return (
{/* 统计提示行 */} {(lineLimitReached || coverage || totalBytes) && (
{lineLimitReached && ( Line limit reached {coverage && `, received logs cover ${coverage}`} )} {totalBytes !== undefined && ( Total bytes processed: {formatBytes(totalBytes)} )}
)}
[`${value} 条`, 'logs']} labelFormatter={(label: string) => { const item = chartData.find(d => d.label === label); return item ? item.timestamp : label; }} /> { if (onBarClick && entry?.timestamp) { const start = entry.timestamp; const end = start.slice(0, 13) + ':59:59'; onBarClick(start, end); } }} style={{ cursor: onBarClick ? 'pointer' : 'default' }} />
); }