From 8a88fcde10818aa4abd7499c7026f898cadd5ec7 Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Mon, 22 Dec 2025 18:23:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9C=8D=E5=8A=A1=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=92=8C=E8=B5=84=E6=BA=90=E7=9B=91=E6=8E=A7=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/monitoring/ResourceMonitor.tsx | 294 ++++++++++++++++++ .../monitoring/ServiceStatusCard.tsx | 124 ++++++++ src/components/monitoring/index.ts | 9 + 3 files changed, 427 insertions(+) create mode 100644 src/components/monitoring/ResourceMonitor.tsx create mode 100644 src/components/monitoring/ServiceStatusCard.tsx create mode 100644 src/components/monitoring/index.ts diff --git a/src/components/monitoring/ResourceMonitor.tsx b/src/components/monitoring/ResourceMonitor.tsx new file mode 100644 index 00000000..5596770e --- /dev/null +++ b/src/components/monitoring/ResourceMonitor.tsx @@ -0,0 +1,294 @@ +/** + * 资源监控组件 + * + * 显示系统资源使用情况,包括: + * - CPU 使用率(带历史图表) + * - 内存使用率(带历史图表) + * - 磁盘使用率(带历史图表) + */ + +'use client' + +import React from 'react' +import { Cpu, HardDrive, MemoryStick, TrendingUp, TrendingDown, Minus } from 'lucide-react' +import Card from '@/components/ui/Card' +import { Title3, Body, Caption1 } from '@/components/ui/Typography' +import ProgressBar from '@/components/ui/ProgressBar' + +export interface ResourceData { + current: number // 当前使用百分比 + history: number[] // 历史数据点(最近N个) + total?: number // 总量(MB) + used?: number // 已用(MB) + free?: number // 空闲(MB) +} + +export interface ResourceMonitorProps { + cpu?: ResourceData + memory?: ResourceData + disk?: ResourceData + cpuCores?: number + className?: string + showHistory?: boolean + historyPoints?: number +} + +export default function ResourceMonitor({ + cpu, + memory, + disk, + cpuCores, + className = '', + showHistory = true, + historyPoints = 20, +}: ResourceMonitorProps) { + return ( +
+ {/* CPU 监控 */} + {cpu && ( + } + title="CPU 使用率" + subtitle={cpuCores ? `${cpuCores} 核心` : undefined} + data={cpu} + showHistory={showHistory} + historyPoints={historyPoints} + unit="%" + warningThreshold={70} + criticalThreshold={90} + /> + )} + + {/* 内存监控 */} + {memory && ( + } + title="内存使用率" + subtitle={memory.total ? `${formatMB(memory.used || 0)} / ${formatMB(memory.total)}` : undefined} + data={memory} + showHistory={showHistory} + historyPoints={historyPoints} + unit="%" + warningThreshold={75} + criticalThreshold={90} + /> + )} + + {/* 磁盘监控 */} + {disk && ( + } + title="磁盘使用率" + subtitle={disk.total ? `${formatMB(disk.used || 0)} / ${formatMB(disk.total)}` : undefined} + data={disk} + showHistory={showHistory} + historyPoints={historyPoints} + unit="%" + warningThreshold={80} + criticalThreshold={95} + /> + )} +
+ ) +} + +interface ResourceCardProps { + icon: React.ReactNode + title: string + subtitle?: string + data: ResourceData + showHistory: boolean + historyPoints: number + unit: string + warningThreshold: number + criticalThreshold: number +} + +function ResourceCard({ + icon, + title, + subtitle, + data, + showHistory, + historyPoints, + unit, + warningThreshold, + criticalThreshold, +}: ResourceCardProps) { + const getStatusType = (value: number) => { + if (value >= criticalThreshold) return 'error' + if (value >= warningThreshold) return 'warning' + return 'success' + } + + const getTrend = () => { + if (data.history.length < 2) return 'stable' + const recent = data.history.slice(-5) + const avg = recent.reduce((a, b) => a + b, 0) / recent.length + const diff = data.current - avg + if (diff > 5) return 'up' + if (diff < -5) return 'down' + return 'stable' + } + + const trend = getTrend() + + return ( + +
+
+
{icon}
+
+ {title} + {subtitle && ( + + {subtitle} + + )} +
+
+
+ + {data.current.toFixed(1)}{unit} + + {trend === 'up' && } + {trend === 'down' && } + {trend === 'stable' && } +
+
+ + {/* 进度条 */} + + + {/* 历史图表 */} + {showHistory && data.history.length > 0 && ( +
+ +
+ )} +
+ ) +} + +interface MiniChartProps { + data: number[] + warningThreshold: number + criticalThreshold: number +} + +function MiniChart({ data, warningThreshold, criticalThreshold }: MiniChartProps) { + const height = 40 + const width = 200 + const padding = 2 + + if (data.length === 0) return null + + const max = 100 + const min = 0 + const range = max - min + + const points = data.map((value, index) => { + const x = padding + (index / (data.length - 1 || 1)) * (width - padding * 2) + const y = height - padding - ((value - min) / range) * (height - padding * 2) + return `${x},${y}` + }).join(' ') + + const getColor = (value: number) => { + if (value >= criticalThreshold) return 'var(--color-red)' + if (value >= warningThreshold) return 'var(--color-orange)' + return 'var(--color-green)' + } + + // 创建渐变区域 + const areaPoints = `${padding},${height - padding} ${points} ${width - padding},${height - padding}` + + return ( +
+ + {/* 警告线 */} + + + {/* 危险线 */} + + + {/* 填充区域 */} + + + {/* 折线 */} + + + {/* 当前值点 */} + {data.length > 0 && ( + + )} + +
+ + {data.length > 0 ? `最低 ${Math.min(...data).toFixed(0)}%` : ''} + + + {data.length > 0 ? `最高 ${Math.max(...data).toFixed(0)}%` : ''} + +
+
+ ) +} + +function formatMB(mb: number): string { + if (mb >= 1024) { + return `${(mb / 1024).toFixed(1)} GB` + } + return `${mb.toFixed(0)} MB` +} diff --git a/src/components/monitoring/ServiceStatusCard.tsx b/src/components/monitoring/ServiceStatusCard.tsx new file mode 100644 index 00000000..6e9f1cf1 --- /dev/null +++ b/src/components/monitoring/ServiceStatusCard.tsx @@ -0,0 +1,124 @@ +/** + * 服务状态卡片组件 + * + * 显示单个服务的状态信息,包括: + * - 服务名称和状态指示器 + * - 响应时间显示 + * - 错误信息显示 + */ + +'use client' + +import React from 'react' +import { CheckCircle, XCircle, AlertTriangle, Loader2, Clock } from 'lucide-react' +import Card from '@/components/ui/Card' +import { Title3, Body, Caption1 } from '@/components/ui/Typography' +import Badge from '@/components/ui/Badge' + +export interface ServiceStatus { + name: string + status: 'healthy' | 'unhealthy' | 'degraded' | 'unknown' | 'loading' + responseTime?: number // 毫秒 + lastChecked?: Date + errorMessage?: string + details?: Record +} + +interface ServiceStatusCardProps { + service: ServiceStatus + className?: string +} + +export default function ServiceStatusCard({ service, className = '' }: ServiceStatusCardProps) { + const getStatusIcon = () => { + switch (service.status) { + case 'healthy': + return + case 'unhealthy': + return + case 'degraded': + return + case 'loading': + return + default: + return + } + } + + const getStatusBadge = () => { + const statusMap = { + healthy: { type: 'success' as const, label: '正常' }, + unhealthy: { type: 'error' as const, label: '异常' }, + degraded: { type: 'warning' as const, label: '降级' }, + unknown: { type: 'info' as const, label: '未知' }, + loading: { type: 'info' as const, label: '检查中' }, + } + const config = statusMap[service.status] + return {config.label} + } + + const getResponseTimeColor = () => { + if (!service.responseTime) return 'var(--color-label-tertiary)' + if (service.responseTime < 100) return 'var(--color-green)' + if (service.responseTime < 500) return 'var(--color-orange)' + return 'var(--color-red)' + } + + return ( + +
+
+ {getStatusIcon()} + {service.name} +
+ {getStatusBadge()} +
+ + {/* 响应时间 */} + {service.responseTime !== undefined && ( +
+ + + {service.responseTime}ms + + + 响应时间 + +
+ )} + + {/* 最后检查时间 */} + {service.lastChecked && ( + + 最后检查: {service.lastChecked.toLocaleTimeString()} + + )} + + {/* 错误信息 */} + {service.errorMessage && ( +
+ + {service.errorMessage} + +
+ )} + + {/* 详细信息 */} + {service.details && Object.keys(service.details).length > 0 && ( +
+ {Object.entries(service.details).map(([key, value]) => ( +
+ {key} + + {typeof value === 'boolean' ? (value ? '是' : '否') : String(value)} + +
+ ))} +
+ )} +
+ ) +} diff --git a/src/components/monitoring/index.ts b/src/components/monitoring/index.ts new file mode 100644 index 00000000..c92418c8 --- /dev/null +++ b/src/components/monitoring/index.ts @@ -0,0 +1,9 @@ +/** + * 监控组件导出 + */ + +export { default as ServiceStatusCard } from './ServiceStatusCard' +export type { ServiceStatus } from './ServiceStatusCard' + +export { default as ResourceMonitor } from './ResourceMonitor' +export type { ResourceData, ResourceMonitorProps } from './ResourceMonitor'