import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, BarChart, Bar, } from 'recharts'; import { Cpu, HardDrive, Activity, MemoryStick, Database } from 'lucide-react'; // 云平台颜色映射 const CLOUD_COLORS: Record = { '阿里云': '#ff6a00', aliyun: '#ff6a00', AWS: '#ff9900', aws: '#ff9900', '腾讯云': '#006eff', tencent: '#006eff', '华为云': '#ff0000', huawei: '#ff0000', Azure: '#0078d4', azure: '#0078d4', }; const FALLBACK_CLOUD_COLORS = ['#ff6a00', '#ff9900', '#006eff', '#ff0000', '#0078d4', '#10b981', '#a855f7', '#ec4899']; // 计算月度环比变化(基于 month_trend 最后两个月) export function computeMonthChange(monthTrend: any[]): { pct: number; label: string; direction: 'up' | 'down' | 'neutral'; } { if (!monthTrend || monthTrend.length < 2) { return { pct: 0, label: '暂无数据', direction: 'neutral' }; } const cur = Number(monthTrend[monthTrend.length - 1].cost ?? 0); const prev = Number(monthTrend[monthTrend.length - 2].cost ?? 0); if (prev === 0) { return { pct: 0, label: cur > 0 ? '新增费用' : '持平', direction: 'neutral' }; } const pct = ((cur - prev) / prev) * 100; const direction = pct > 0.05 ? 'up' : pct < -0.05 ? 'down' : 'neutral'; const label = `${pct > 0 ? '+' : ''}${pct.toFixed(1)}% 较上月`; return { pct, label, direction }; } // 根据平均值生成24小时趋势数据(无实时指标时使用) function generateMetricsData(avgCpu: number, avgMemory: number, avgDisk: number, points = 24) { const data = []; for (let i = points - 1; i >= 0; i--) { const d = new Date(Date.now() - i * 60 * 60 * 1000); data.push({ time: `${d.getHours().toString().padStart(2, '0')}:00`, cpu: Number(Math.max(0, avgCpu + (Math.random() - 0.5) * 15).toFixed(1)), memory: Number(Math.max(0, avgMemory + (Math.random() - 0.5) * 10).toFixed(1)), disk: Number(Math.max(0, avgDisk + (Math.random() - 0.5) * 5).toFixed(1)), }); } return data; } // 成本费用卡片 export function CostOverviewCard({ costStats }: { costStats: any }) { const cloudCosts: any[] = costStats?.cloud_costs ?? []; const monthTrend: any[] = costStats?.month_trend ?? []; const costData = cloudCosts.map((c, i) => ({ name: c.cloud, value: c.cost, color: CLOUD_COLORS[c.cloud] || FALLBACK_CLOUD_COLORS[i % FALLBACK_CLOUD_COLORS.length], })); const monthlyData = monthTrend.map((m) => ({ month: m.month, cost: m.cost })); const totalCost = costStats?.total_cost ?? costData.reduce((sum, item) => sum + item.value, 0); const costChange = computeMonthChange(monthTrend); return (
云成本概览 本月各云平台费用支出

¥{Number(totalCost).toLocaleString()}

{costChange.label}

费用分布

{costData.map((entry, index) => ( ))} `¥${value.toLocaleString()}`} />

月度趋势

`¥${value.toLocaleString()}`} />
{costData.map((item) => (
{item.name}
¥{Number(item.value).toLocaleString()}
))}
); } // Top5 资源使用卡片 export function TopResourceUsageCard({ topUsage }: { topUsage: any }) { const topCpuServers = (topUsage?.cpu ?? []).slice(0, 5); const topMemoryServers = (topUsage?.memory ?? []).slice(0, 5); const topDiskServers = (topUsage?.disk ?? []).slice(0, 5); const renderList = ( items: any[], color: string, BarColor: string, icon: React.ReactNode, label: string ) => (
{icon} {label}
{items.map((item, index) => { const usage = Number(item.usage ?? 0); return (
{index + 1}

{item.hostname}

80 ? 'bg-red-400' : usage > 60 ? 'bg-amber-400' : BarColor }`} style={{ width: `${usage}%` }} />
80 ? 'text-red-400' : usage > 60 ? 'text-amber-400' : 'text-slate-400' }`}> {usage.toFixed(1)}%
); })}
); return (
Top5 资源使用 CPU、内存、磁盘使用率最高
{/* CPU Top5 */} {renderList(topCpuServers, 'blue', 'bg-blue-400', , 'CPU')} {/* Memory Top5 */} {renderList(topMemoryServers, 'emerald', 'bg-emerald-400', , '内存')} {/* Disk Top5 */} {renderList(topDiskServers, 'purple', 'bg-purple-400', , '磁盘')}
); } // 将后端 MetricData[] 时间序列转换为图表所需格式 { time, cpu, memory } // Logan.Li function buildMetricsFromReal(metrics: any[], avgDisk: number): { time: string; cpu: number; memory: number; disk: number }[] { if (!metrics || metrics.length === 0) return []; // 按 timestamp 分组:同一时刻有 cpu + memory 两条记录 const buckets: Record = {}; for (const m of metrics) { const ts = m.timestamp; if (!ts) continue; // 提取 HH:MM 格式作为 X 轴标签 const d = new Date(ts); const label = `${d.getHours().toString().padStart(2, '0')}:00`; if (!buckets[label]) buckets[label] = {}; if (m.name === 'cpu') buckets[label].cpu = Number(m.value) || 0; if (m.name === 'memory') buckets[label].memory = Number(m.value) || 0; } return Object.entries(buckets) .sort((a, b) => a[0].localeCompare(b[0])) .map(([time, v]) => ({ time, cpu: Number((v.cpu ?? 0).toFixed(1)), memory: Number((v.memory ?? 0).toFixed(1)), disk: Number(avgDisk.toFixed(1)), // 磁盘无历史序列,用平均值 })); } // 资源使用率趋势卡片 export function ResourceUsageCard({ avgCpu, avgMemory, avgDisk, metrics, }: { avgCpu: number; avgMemory: number; avgDisk: number; metrics?: any[]; }) { // 优先使用真实 Agent 指标时间序列;无数据时回退到平均值生成(带随机波动) // Logan.Li const realData = buildMetricsFromReal(metrics ?? [], avgDisk); const data = realData.length > 0 ? realData : generateMetricsData(avgCpu, avgMemory, avgDisk, 24); return (
资源使用率趋势 过去24小时系统资源使用情况
CPU 内存 磁盘
); }