初始化提交
This commit is contained in:
@@ -0,0 +1,521 @@
|
||||
import type { ServerMetrics } from '../../types';
|
||||
import { formatBytes, formatUptime, formatPercent } from '../../utils/format';
|
||||
import { CircularProgress } from '../Common/CircularProgress';
|
||||
import './ServerDetail.css';
|
||||
|
||||
interface ServerDetailProps {
|
||||
metrics: ServerMetrics;
|
||||
serverName: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const ServerDetail = ({ metrics, serverName, onClose }: ServerDetailProps) => {
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-header">
|
||||
<h2>{serverName} - 详细信息</h2>
|
||||
<button className="btn-close" onClick={onClose}>×</button>
|
||||
</div>
|
||||
|
||||
<div className="modal-body">
|
||||
<section className="detail-section">
|
||||
<h3>系统信息</h3>
|
||||
<div className="system-info-container">
|
||||
{/* 系统基本信息 */}
|
||||
<div className="system-info-group">
|
||||
<h4 className="info-group-title">基本信息</h4>
|
||||
<div className="system-info-grid">
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">主机名</span>
|
||||
<span className="system-info-value">{metrics.hostname}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">操作系统</span>
|
||||
<span className="system-info-value">{metrics.os.distro}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">架构</span>
|
||||
<span className="system-info-value">{metrics.os.architecture}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="system-info-item system-info-item-full">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">内核版本</span>
|
||||
<span className="system-info-value system-info-value-long">{metrics.os.kernel}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 运行时信息 */}
|
||||
<div className="system-info-group">
|
||||
<h4 className="info-group-title">运行时信息</h4>
|
||||
<div className="system-info-grid">
|
||||
<div className="system-info-item system-info-item-highlight">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">运行时间</span>
|
||||
<span className="system-info-value highlight">{formatUptime(metrics.uptimeSeconds)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">进程数量</span>
|
||||
<span className="system-info-value">{metrics.system.processCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
{metrics.system.packageCount > 0 && (
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">已安装软件包</span>
|
||||
<span className="system-info-value">{metrics.system.packageCount} <span className="system-info-sub">({metrics.system.packageManager})</span></span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 性能指标 */}
|
||||
<div className="system-info-group">
|
||||
<h4 className="info-group-title">性能指标</h4>
|
||||
<div className="system-info-grid">
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">磁盘读取</span>
|
||||
<span className="system-info-value">{metrics.system.diskReadSpeed.toFixed(2)} MB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">磁盘写入</span>
|
||||
<span className="system-info-value">{metrics.system.diskWriteSpeed.toFixed(2)} MB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
{metrics.system.temperature && metrics.system.temperature > 0 && (
|
||||
<div className="system-info-item system-info-item-temperature">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">系统温度</span>
|
||||
<span className="system-info-value temperature-value">{metrics.system.temperature.toFixed(1)}°C</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(metrics.system.networkRxSpeed !== undefined || metrics.system.networkTxSpeed !== undefined) && (
|
||||
<>
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">网络下载</span>
|
||||
<span className="system-info-value">{(metrics.system.networkRxSpeed || 0).toFixed(2)} MB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">网络上传</span>
|
||||
<span className="system-info-value">{(metrics.system.networkTxSpeed || 0).toFixed(2)} MB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 延迟信息 */}
|
||||
{metrics.latency && (
|
||||
<div className="system-info-group">
|
||||
<h4 className="info-group-title">延迟检测</h4>
|
||||
<div className="system-info-grid">
|
||||
<div className="system-info-item system-info-item-highlight">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">客户端到服务器</span>
|
||||
<span className="system-info-value highlight">
|
||||
{metrics.latency.clientToServer >= 0 ? `${metrics.latency.clientToServer} ms` : '超时'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{metrics.latency.external && (
|
||||
<>
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">百度 (baidu.com)</span>
|
||||
<span className="system-info-value">{metrics.latency.external['baidu.com'] || '超时'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">谷歌 (google.com)</span>
|
||||
<span className="system-info-value">{metrics.latency.external['google.com'] || '超时'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="system-info-item">
|
||||
<div className="system-info-content">
|
||||
<span className="system-info-label">GitHub (github.com)</span>
|
||||
<span className="system-info-value">{metrics.latency.external['github.com'] || '超时'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="detail-section">
|
||||
<h3>CPU</h3>
|
||||
<div className="cpu-section-content">
|
||||
<div className="cpu-circular-group">
|
||||
<div className="cpu-circular">
|
||||
<CircularProgress
|
||||
value={metrics.cpu.usagePercent}
|
||||
label=""
|
||||
size={140}
|
||||
color={metrics.cpu.usagePercent > 80 ? '#f87171' : '#86efac'}
|
||||
subLabel={formatPercent(metrics.cpu.usagePercent)}
|
||||
/>
|
||||
</div>
|
||||
{metrics.cpu.loadAverages && metrics.cpu.loadAverages.length > 0 && metrics.cpu.cores > 0 && (
|
||||
<div className="cpu-load-circular">
|
||||
<CircularProgress
|
||||
value={Math.min((metrics.cpu.loadAverages[0] / metrics.cpu.cores) * 100, 100)}
|
||||
label="负载"
|
||||
size={140}
|
||||
color={metrics.cpu.loadAverages[0] / metrics.cpu.cores > 0.8 ? '#fde047' : '#a7f3d0'}
|
||||
subLabel={metrics.cpu.loadAverages[0].toFixed(2)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="detail-grid">
|
||||
<div className="detail-item">
|
||||
<span className="item-label">型号</span>
|
||||
<span className="item-value">{metrics.cpu.model}</span>
|
||||
</div>
|
||||
<div className="detail-item">
|
||||
<span className="item-label">核心数</span>
|
||||
<span className="item-value">{metrics.cpu.cores}</span>
|
||||
</div>
|
||||
<div className="detail-item">
|
||||
<span className="item-label">使用率</span>
|
||||
<span className="item-value highlight">{formatPercent(metrics.cpu.usagePercent)}</span>
|
||||
</div>
|
||||
{metrics.cpu.temperature && metrics.cpu.temperature > 0 && (
|
||||
<div className="detail-item">
|
||||
<span className="item-label">温度</span>
|
||||
<span className="item-value">{metrics.cpu.temperature.toFixed(1)}°C</span>
|
||||
</div>
|
||||
)}
|
||||
{metrics.cpu.loadAverages && metrics.cpu.loadAverages.length > 0 && (
|
||||
<div className="detail-item">
|
||||
<span className="item-label">负载平均值</span>
|
||||
<span className="item-value">{metrics.cpu.loadAverages.join(' / ')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{metrics.cpu.perCoreUsage && metrics.cpu.perCoreUsage.length > 0 && (
|
||||
<div className="per-core-section">
|
||||
<h4>各核心使用率</h4>
|
||||
<div className="core-grid-circular">
|
||||
{metrics.cpu.perCoreUsage.map((core) => (
|
||||
<div key={core.core} className="core-item-circular">
|
||||
<CircularProgress
|
||||
value={core.percent}
|
||||
label={`Core ${core.core}`}
|
||||
size={140}
|
||||
color={core.percent > 80 ? '#f87171' : '#86efac'}
|
||||
subLabel={formatPercent(core.percent)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="detail-section">
|
||||
<h3>内存</h3>
|
||||
<div className="memory-section-content">
|
||||
<div className="memory-circular">
|
||||
<CircularProgress
|
||||
value={metrics.memory.usedPercent}
|
||||
label=""
|
||||
size={140}
|
||||
color={metrics.memory.usedPercent > 80 ? '#fde047' : '#6ee7b7'}
|
||||
subLabel={formatPercent(metrics.memory.usedPercent)}
|
||||
/>
|
||||
</div>
|
||||
<div className="detail-grid">
|
||||
<div className="detail-item">
|
||||
<span className="item-label">总容量</span>
|
||||
<span className="item-value">{formatBytes(metrics.memory.totalBytes)}</span>
|
||||
</div>
|
||||
<div className="detail-item">
|
||||
<span className="item-label">已使用</span>
|
||||
<span className="item-value">{formatBytes(metrics.memory.usedBytes)}</span>
|
||||
</div>
|
||||
<div className="detail-item">
|
||||
<span className="item-label">可用</span>
|
||||
<span className="item-value">{formatBytes(metrics.memory.freeBytes)}</span>
|
||||
</div>
|
||||
<div className="detail-item">
|
||||
<span className="item-label">使用率</span>
|
||||
<span className="item-value highlight">{formatPercent(metrics.memory.usedPercent)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="detail-section">
|
||||
<h3>存储</h3>
|
||||
<div className="storage-grid-circular">
|
||||
{metrics.storage.map((disk, index) => (
|
||||
<div key={index} className="storage-item-circular">
|
||||
<CircularProgress
|
||||
value={disk.usedPercent}
|
||||
label={disk.mount}
|
||||
size={160}
|
||||
color={disk.usedPercent > 90 ? '#f87171' : '#a7f3d0'}
|
||||
subLabel={formatPercent(disk.usedPercent)}
|
||||
/>
|
||||
<div className="storage-details-mini">
|
||||
<div>总: {formatBytes(disk.totalBytes)}</div>
|
||||
<div>用: {formatBytes(disk.usedBytes)}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{metrics.gpu && metrics.gpu.length > 0 && metrics.gpu[0].status !== 'not_available' && (
|
||||
<section className="detail-section">
|
||||
<h3>GPU</h3>
|
||||
{metrics.gpu.map((gpu, index) => (
|
||||
<div key={index} className="gpu-item">
|
||||
<div className="gpu-header">
|
||||
<strong>{gpu.name}</strong>
|
||||
<span className={`gpu-status ${gpu.status}`}>{gpu.status}</span>
|
||||
</div>
|
||||
<div className="gpu-bar-container">
|
||||
<div className="gpu-bar-label">利用率</div>
|
||||
<div className="gpu-bar-bg">
|
||||
<div
|
||||
className="gpu-bar"
|
||||
style={{ width: `${gpu.utilizationPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="gpu-bar-value">{formatPercent(gpu.utilizationPercent)}</div>
|
||||
</div>
|
||||
<div className="detail-grid">
|
||||
<div className="detail-item">
|
||||
<span className="item-label">显存总量</span>
|
||||
<span className="item-value">{gpu.memoryTotalMB} MB</span>
|
||||
</div>
|
||||
<div className="detail-item">
|
||||
<span className="item-label">显存使用</span>
|
||||
<span className="item-value">{gpu.memoryUsedMB} MB</span>
|
||||
</div>
|
||||
{gpu.temperature && gpu.temperature > 0 && (
|
||||
<div className="detail-item">
|
||||
<span className="item-label">温度</span>
|
||||
<span className="item-value">{gpu.temperature.toFixed(1)}°C</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{metrics.network && metrics.network.length > 0 && (
|
||||
<section className="detail-section">
|
||||
<h3>网络接口</h3>
|
||||
<div className="network-list">
|
||||
{metrics.network.map((iface, index) => (
|
||||
<div key={index} className="network-item">
|
||||
<div className="network-header">
|
||||
<div className="network-name-group">
|
||||
<div className="network-name-info">
|
||||
<strong className="network-name">{iface.name}</strong>
|
||||
{iface.ipAddress && (
|
||||
<span className="network-ip has-ip">
|
||||
{iface.ipAddress}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="network-details">
|
||||
{(iface.macAddress || iface.ipAddress) && (
|
||||
<div className="network-detail-row">
|
||||
{iface.macAddress && (
|
||||
<div className="network-detail-item">
|
||||
<span className="network-detail-label">MAC 地址</span>
|
||||
<span className="network-detail-value">{iface.macAddress}</span>
|
||||
</div>
|
||||
)}
|
||||
{iface.ipAddress && (
|
||||
<div className="network-detail-item">
|
||||
<span className="network-detail-label">IP 地址</span>
|
||||
<span className="network-detail-value">{iface.ipAddress}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="network-traffic">
|
||||
<div className="traffic-item">
|
||||
<div className="traffic-header">
|
||||
<span className="traffic-label">接收流量</span>
|
||||
<span className="traffic-value">{formatBytes(iface.rxBytes)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="traffic-item">
|
||||
<div className="traffic-header">
|
||||
<span className="traffic-label">发送流量</span>
|
||||
<span className="traffic-value">{formatBytes(iface.txBytes)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{metrics.system.topProcesses && metrics.system.topProcesses.length > 0 && (
|
||||
<section className="detail-section">
|
||||
<h3>Top 5 进程</h3>
|
||||
<div className="process-list">
|
||||
{metrics.system.topProcesses.map((proc, index) => (
|
||||
<div key={index} className="process-item">
|
||||
<div className="process-rank">{index + 1}</div>
|
||||
<div className="process-content">
|
||||
<div className="process-header">
|
||||
<div className="process-name-group">
|
||||
<strong className="process-name">{proc.name}</strong>
|
||||
<span className="process-pid">PID: {proc.pid}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="process-metrics">
|
||||
<div className="process-metric-item">
|
||||
<div className="metric-header">
|
||||
<span className="metric-label">CPU</span>
|
||||
<span className="metric-value">{proc.cpu}%</span>
|
||||
</div>
|
||||
<div className="metric-bar-container">
|
||||
<div
|
||||
className="metric-bar cpu-bar"
|
||||
style={{ width: `${Math.min(proc.cpu, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="process-metric-item">
|
||||
<div className="metric-header">
|
||||
<span className="metric-label">内存</span>
|
||||
<span className="metric-value">{proc.memory}% ({proc.memoryMB.toFixed(1)} MB)</span>
|
||||
</div>
|
||||
<div className="metric-bar-container">
|
||||
<div
|
||||
className="metric-bar memory-bar"
|
||||
style={{ width: `${Math.min(proc.memory, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{metrics.system.dockerStats && metrics.system.dockerStats.available && (
|
||||
<section className="detail-section">
|
||||
<h3>Docker 信息</h3>
|
||||
{metrics.system.dockerStats.version && (
|
||||
<div className="detail-grid">
|
||||
<div className="detail-item">
|
||||
<span className="item-label">Docker 版本</span>
|
||||
<span className="item-value">{metrics.system.dockerStats.version}</span>
|
||||
</div>
|
||||
<div className="detail-item">
|
||||
<span className="item-label">运行中的容器</span>
|
||||
<span className="item-value">{metrics.system.dockerStats.running}</span>
|
||||
</div>
|
||||
<div className="detail-item">
|
||||
<span className="item-label">停止的容器</span>
|
||||
<span className="item-value">{metrics.system.dockerStats.stopped}</span>
|
||||
</div>
|
||||
<div className="detail-item">
|
||||
<span className="item-label">镜像数量</span>
|
||||
<span className="item-value">{metrics.system.dockerStats.imageCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{metrics.system.dockerStats.runningNames && metrics.system.dockerStats.runningNames.length > 0 && (
|
||||
<div className="docker-containers-section">
|
||||
<h4>运行中的容器</h4>
|
||||
<div className="docker-names-list">
|
||||
{metrics.system.dockerStats.runningNames.map((name, index) => (
|
||||
<span key={index} className="docker-name-tag running">
|
||||
{name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{metrics.system.dockerStats.stoppedNames && metrics.system.dockerStats.stoppedNames.length > 0 && (
|
||||
<div className="docker-containers-section">
|
||||
<h4>停止的容器</h4>
|
||||
<div className="docker-names-list">
|
||||
{metrics.system.dockerStats.stoppedNames.map((name, index) => (
|
||||
<span key={index} className="docker-name-tag stopped">
|
||||
{name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{metrics.system.dockerStats.imageNames && metrics.system.dockerStats.imageNames.length > 0 && (
|
||||
<div className="docker-containers-section">
|
||||
<h4>镜像列表</h4>
|
||||
<div className="docker-names-list">
|
||||
{metrics.system.dockerStats.imageNames.map((name, index) => (
|
||||
<span key={index} className="docker-name-tag image">
|
||||
{name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{metrics.system.systemLogs && metrics.system.systemLogs.length > 0 && (
|
||||
<section className="detail-section">
|
||||
<h3>系统日志</h3>
|
||||
<div className="logs-container">
|
||||
{metrics.system.systemLogs.map((log, index) => (
|
||||
<div key={index} className="log-line">
|
||||
{log}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user