import { ReactNode } from 'react' import { RefreshCw } from 'lucide-react' import { useTheme } from '../contexts/ThemeContext' export type StatsRangeKey = '30m' | '1h' | '1d' | '1w' export type ChartPoint = { ts: number value: number } export type ResourceChartConfig = { title: string icon: ReactNode points: ChartPoint[] current: number detail?: string max?: number unitLabel?: string formatValue: (value: number) => string } const rangeLabels: Record = { '30m': '30分钟', '1h': '1小时', '1d': '1天', '1w': '1周', } export const statsRanges: Record = { '30m': 30 * 60 * 1000, '1h': 60 * 60 * 1000, '1d': 24 * 60 * 60 * 1000, '1w': 7 * 24 * 60 * 60 * 1000, } export default function ResourceStatsPanel({ range, onRangeChange, onRefresh, charts, }: { range: StatsRangeKey onRangeChange: (range: StatsRangeKey) => void onRefresh: () => void charts: ResourceChartConfig[] }) { return (

统计信息

{(Object.keys(rangeLabels) as StatsRangeKey[]).map((item) => ( ))}
{charts.map((chart, index) => ( ))}
) } function DetailedChart({ chart, className }: { chart: ResourceChartConfig; className: string }) { const values = chart.points.map((point) => point.value) const avg = values.length > 0 ? values.reduce((sum, value) => sum + value, 0) / values.length : 0 const peak = values.length > 0 ? Math.max(...values) : 0 return (
{chart.icon} {chart.title}
{chart.detail &&

{chart.detail}

}
) } function Stat({ label, value }: { label: string; value: string }) { return (
{label}
{value}
) } function LineAreaChart({ points, max, formatValue, unitLabel, }: { points: ChartPoint[] max?: number formatValue: (value: number) => string unitLabel?: string }) { const { theme } = useTheme() const isDark = theme === 'dark' const width = 520 const height = 150 const left = 50 const right = 10 const top = 8 const bottom = 28 const innerWidth = width - left - right const innerHeight = height - top - bottom const values = points.length > 0 ? points : [{ ts: Date.now(), value: 0 }] const maxValue = Math.max(max || 0, ...values.map((point) => point.value), 1) const minTs = values[0]?.ts || Date.now() const maxTs = values[values.length - 1]?.ts || minTs + 1 const span = Math.max(maxTs - minTs, 1) const coords = values.map((point, index) => { const x = left + ((point.ts - minTs) / span) * innerWidth const y = top + innerHeight - (point.value / maxValue) * innerHeight return `${Number.isFinite(x) ? x : left},${Number.isFinite(y) ? y : top + innerHeight}` }) const fallbackX = left const fallbackY = top + innerHeight const line = coords.length > 1 ? coords.join(' ') : `${fallbackX},${fallbackY} ${left + innerWidth},${fallbackY}` const area = `${left},${top + innerHeight} ${line} ${left + innerWidth},${top + innerHeight}` const yTicks = [1, 0.5, 0] const xTicks = [0, 0.5, 1] // Dark mode colors const gridStroke = isDark ? '#374151' : '#e5e7eb' const gridStrokeV = isDark ? '#1f2937' : '#edf0f2' const axisStroke = isDark ? '#9ca3af' : '#888' const lineStroke = isDark ? '#f9fafb' : '#444' const gradientTop = isDark ? '#f9fafb' : '#555' const gradientBottom = isDark ? '#374151' : '#555' return ( {yTicks.map((tick) => { const y = top + (1 - tick) * innerHeight return ( {formatValue(maxValue * tick)} ) })} {xTicks.map((tick) => { const x = left + tick * innerWidth const ts = minTs + tick * span return ( {formatTime(ts)} ) })} {unitLabel && ( {unitLabel} )} ) } function chartBorderClass(index: number) { const right = index % 2 === 0 ? 'xl:border-r' : '' const top = index > 1 ? 'border-t' : '' return `${right} ${top} border-gray-200` } function formatTime(ts: number) { return new Date(ts).toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }) }