520 lines
16 KiB
TypeScript
520 lines
16 KiB
TypeScript
|
|
'use client';
|
|||
|
|
import { SiteHeader, PageInfo } from '@/components/site-header';
|
|||
|
|
import { SidebarInset, SidebarProvider } from '@nice/ui/components/sidebar';
|
|||
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nice/ui/components/card';
|
|||
|
|
import { Badge } from '@nice/ui/components/badge';
|
|||
|
|
import { Button } from '@nice/ui/components/button';
|
|||
|
|
import { Progress } from '@nice/ui/components/progress';
|
|||
|
|
import {
|
|||
|
|
IconServer,
|
|||
|
|
IconUsers,
|
|||
|
|
IconFiles,
|
|||
|
|
IconCloudUpload,
|
|||
|
|
IconTrendingUp,
|
|||
|
|
IconTrendingDown,
|
|||
|
|
IconActivity,
|
|||
|
|
IconDatabase,
|
|||
|
|
IconCpu,
|
|||
|
|
IconShield,
|
|||
|
|
IconBell,
|
|||
|
|
IconCalendar,
|
|||
|
|
IconClock,
|
|||
|
|
IconAlertTriangle,
|
|||
|
|
IconCircleCheck,
|
|||
|
|
IconCircleX,
|
|||
|
|
IconRefresh,
|
|||
|
|
IconNetwork,
|
|||
|
|
IconBrain,
|
|||
|
|
IconFolder,
|
|||
|
|
} from '@tabler/icons-react';
|
|||
|
|
import { useTRPC } from '@fenghuo/client';
|
|||
|
|
import { useQuery } from '@tanstack/react-query';
|
|||
|
|
import { useEffect } from 'react';
|
|||
|
|
import { SystemStatus } from '@fenghuo/common';
|
|||
|
|
import { useSetPageInfo } from '@/components/providers/dashboard-provider';
|
|||
|
|
|
|||
|
|
// 用户和文件数据的模拟数据(保留,因为这部分数据不在systemStatus中)
|
|||
|
|
const additionalStats = {
|
|||
|
|
users: {
|
|||
|
|
total: 1248,
|
|||
|
|
online: 89,
|
|||
|
|
newToday: 12,
|
|||
|
|
activeThisWeek: 324,
|
|||
|
|
growth: '+5.2%',
|
|||
|
|
peakOnline: 156,
|
|||
|
|
},
|
|||
|
|
files: {
|
|||
|
|
total: 45280,
|
|||
|
|
size: '2.4TB',
|
|||
|
|
uploaded: 156,
|
|||
|
|
shared: 89,
|
|||
|
|
downloadedToday: 1289,
|
|||
|
|
uploadedToday: 234,
|
|||
|
|
growth: '+8.1%',
|
|||
|
|
},
|
|||
|
|
database: {
|
|||
|
|
connections: 45,
|
|||
|
|
maxConnections: 200,
|
|||
|
|
queryTime: 2.3,
|
|||
|
|
status: 'healthy',
|
|||
|
|
size: '890MB',
|
|||
|
|
backupStatus: 'completed',
|
|||
|
|
lastBackup: '4小时前',
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const recentActivities = [
|
|||
|
|
{
|
|||
|
|
id: 1,
|
|||
|
|
user: '张三',
|
|||
|
|
action: '上传了文件',
|
|||
|
|
target: 'project.pdf',
|
|||
|
|
time: '5分钟前',
|
|||
|
|
type: 'upload',
|
|||
|
|
ip: '192.168.1.100',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 2,
|
|||
|
|
user: '李四',
|
|||
|
|
action: '创建了文章',
|
|||
|
|
target: 'API设计指南',
|
|||
|
|
time: '10分钟前',
|
|||
|
|
type: 'create',
|
|||
|
|
ip: '192.168.1.101',
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const systemAlerts = [
|
|||
|
|
{ id: 1, type: 'warning', level: 'medium', message: '磁盘空间使用率超过80%', time: '1小时前', resolved: false },
|
|||
|
|
{ id: 2, type: 'info', level: 'low', message: '系统备份已完成', time: '2小时前', resolved: true },
|
|||
|
|
{ id: 3, type: 'success', level: 'low', message: '安全扫描未发现威胁', time: '3小时前', resolved: true },
|
|||
|
|
{ id: 4, type: 'error', level: 'high', message: 'Email服务响应异常', time: '4小时前', resolved: false },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
function StatsCard({
|
|||
|
|
title,
|
|||
|
|
value,
|
|||
|
|
description,
|
|||
|
|
icon: Icon,
|
|||
|
|
trend,
|
|||
|
|
trendValue,
|
|||
|
|
className = '',
|
|||
|
|
status,
|
|||
|
|
}: {
|
|||
|
|
title: string;
|
|||
|
|
value: string | number;
|
|||
|
|
description: string;
|
|||
|
|
icon: any;
|
|||
|
|
trend?: 'up' | 'down';
|
|||
|
|
trendValue?: string;
|
|||
|
|
className?: string;
|
|||
|
|
status?: 'good' | 'warning' | 'error';
|
|||
|
|
}) {
|
|||
|
|
const getStatusColor = () => {
|
|||
|
|
switch (status) {
|
|||
|
|
case 'good':
|
|||
|
|
return 'text-green-500';
|
|||
|
|
case 'warning':
|
|||
|
|
return 'text-yellow-500';
|
|||
|
|
case 'error':
|
|||
|
|
return 'text-red-500';
|
|||
|
|
default:
|
|||
|
|
return 'text-muted-foreground';
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Card className={className}>
|
|||
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|||
|
|
<CardTitle className="text-sm font-medium">{title}</CardTitle>
|
|||
|
|
<Icon className={`h-4 w-4 ${getStatusColor()}`} />
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="text-2xl font-bold">{value}</div>
|
|||
|
|
<div className="flex items-center space-x-2 text-xs text-muted-foreground">
|
|||
|
|
<span>{description}</span>
|
|||
|
|
{trend && trendValue && (
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
{trend === 'up' ? (
|
|||
|
|
<IconTrendingUp className="h-3 w-3 text-green-500" />
|
|||
|
|
) : (
|
|||
|
|
<IconTrendingDown className="h-3 w-3 text-red-500" />
|
|||
|
|
)}
|
|||
|
|
<span className={trend === 'up' ? 'text-green-500' : 'text-red-500'}>{trendValue}</span>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function SystemStatusCard({ systemStatus, systemStatusLoading, refetchSystemStatus }: { systemStatus?: SystemStatus, systemStatusLoading: boolean, refetchSystemStatus: () => void }) {
|
|||
|
|
// 如果没有数据,显示加载状态
|
|||
|
|
if (systemStatusLoading || !systemStatus) {
|
|||
|
|
return (
|
|||
|
|
<Card className="col-span-2">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center justify-between">
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<IconServer className="h-5 w-5" />
|
|||
|
|
<span>服务器状态</span>
|
|||
|
|
</div>
|
|||
|
|
<Badge variant="secondary">加载中...</Badge>
|
|||
|
|
</CardTitle>
|
|||
|
|
<CardDescription>正在获取系统监控数据</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="text-center text-muted-foreground py-8">
|
|||
|
|
正在加载系统状态...
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Card className="col-span-2">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center justify-between">
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<IconServer className="h-5 w-5" />
|
|||
|
|
<span>服务器状态</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<Badge variant={systemStatus?.isRunning ? 'default' : 'destructive'}>
|
|||
|
|
{systemStatus?.isRunning ? '运行中' : '离线'}
|
|||
|
|
</Badge>
|
|||
|
|
<Button variant="outline" size="sm" onClick={refetchSystemStatus}>
|
|||
|
|
<IconRefresh className="h-4 w-4" />
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</CardTitle>
|
|||
|
|
<CardDescription>实时系统监控数据</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
<div className="grid grid-cols-2 gap-4">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<span className="text-sm font-medium">运行时间</span>
|
|||
|
|
<span className="text-sm text-muted-foreground">{systemStatus?.uptimeFormatted}</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<span className="text-sm font-medium">进程数</span>
|
|||
|
|
<span className="text-sm text-muted-foreground">{systemStatus?.processCount}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<IconCpu className="h-4 w-4" />
|
|||
|
|
<span className="text-sm">CPU使用率</span>
|
|||
|
|
</div>
|
|||
|
|
<span className="text-sm font-medium">{systemStatus?.cpuUsage.toFixed(1)}%</span>
|
|||
|
|
</div>
|
|||
|
|
<Progress value={systemStatus?.cpuUsage} className="h-2" />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<IconBrain className="h-4 w-4" />
|
|||
|
|
<span className="text-sm">内存使用率</span>
|
|||
|
|
</div>
|
|||
|
|
<span className="text-sm font-medium">{systemStatus?.memory.usagePercent.toFixed(1)}%</span>
|
|||
|
|
</div>
|
|||
|
|
<Progress value={systemStatus?.memory.usagePercent} className="h-2" />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<IconFolder className="h-4 w-4" />
|
|||
|
|
<span className="text-sm">磁盘使用率</span>
|
|||
|
|
</div>
|
|||
|
|
<span className="text-sm font-medium">{systemStatus?.disk.usagePercent.toFixed(1)}%</span>
|
|||
|
|
</div>
|
|||
|
|
<Progress value={systemStatus?.disk.usagePercent} className="h-2" />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="grid grid-cols-3 gap-4 pt-2">
|
|||
|
|
<div className="text-center">
|
|||
|
|
<div className="text-lg font-bold text-primary">{systemStatus?.loadAverage.oneMinute}</div>
|
|||
|
|
<div className="text-xs text-muted-foreground">1分钟负载</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-center">
|
|||
|
|
<div className="text-lg font-bold text-primary">{systemStatus?.loadAverage.fiveMinutes}</div>
|
|||
|
|
<div className="text-xs text-muted-foreground">5分钟负载</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-center">
|
|||
|
|
<div className="text-lg font-bold text-primary">{systemStatus?.loadAverage.fifteenMinutes}</div>
|
|||
|
|
<div className="text-xs text-muted-foreground">15分钟负载</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function UserActivityCard() {
|
|||
|
|
const getActivityIcon = (type: string) => {
|
|||
|
|
switch (type) {
|
|||
|
|
case 'upload':
|
|||
|
|
return <IconCloudUpload className="h-4 w-4 text-blue-500" />;
|
|||
|
|
case 'create':
|
|||
|
|
return <IconFiles className="h-4 w-4 text-green-500" />;
|
|||
|
|
case 'share':
|
|||
|
|
return <IconUsers className="h-4 w-4 text-purple-500" />;
|
|||
|
|
case 'permission':
|
|||
|
|
return <IconShield className="h-4 w-4 text-orange-500" />;
|
|||
|
|
case 'login':
|
|||
|
|
return <IconUsers className="h-4 w-4 text-teal-500" />;
|
|||
|
|
case 'download':
|
|||
|
|
return <IconDatabase className="h-4 w-4 text-indigo-500" />;
|
|||
|
|
default:
|
|||
|
|
return <IconActivity className="h-4 w-4" />;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Card className="col-span-2">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center space-x-2">
|
|||
|
|
<IconActivity className="h-5 w-5" />
|
|||
|
|
<span>用户活动</span>
|
|||
|
|
</CardTitle>
|
|||
|
|
<CardDescription>最近用户操作记录</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="space-y-4">
|
|||
|
|
{recentActivities.map((activity) => (
|
|||
|
|
<div key={activity.id} className="flex items-center space-x-3">
|
|||
|
|
<div className="flex-shrink-0">{getActivityIcon(activity.type)}</div>
|
|||
|
|
<div className="flex-1 min-w-0">
|
|||
|
|
<p className="text-sm font-medium">
|
|||
|
|
<span className="text-blue-600">{activity.user}</span> {activity.action}{' '}
|
|||
|
|
<span className="font-semibold">{activity.target}</span>
|
|||
|
|
</p>
|
|||
|
|
<div className="flex items-center space-x-4 text-xs text-muted-foreground">
|
|||
|
|
<div className="flex items-center space-x-1">
|
|||
|
|
<IconClock className="h-3 w-3" />
|
|||
|
|
<span>{activity.time}</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center space-x-1">
|
|||
|
|
<IconNetwork className="h-3 w-3" />
|
|||
|
|
<span>{activity.ip}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
<div className="mt-4 pt-4 border-t">
|
|||
|
|
<Button variant="outline" size="sm" className="w-full">
|
|||
|
|
查看完整日志
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function SystemAlertsCard() {
|
|||
|
|
const getAlertIcon = (type: string, level: string) => {
|
|||
|
|
switch (type) {
|
|||
|
|
case 'warning':
|
|||
|
|
return <IconAlertTriangle className="h-4 w-4 text-yellow-500" />;
|
|||
|
|
case 'error':
|
|||
|
|
return <IconCircleX className="h-4 w-4 text-red-500" />;
|
|||
|
|
case 'success':
|
|||
|
|
return <IconCircleCheck className="h-4 w-4 text-green-500" />;
|
|||
|
|
case 'info':
|
|||
|
|
return <IconBell className="h-4 w-4 text-blue-500" />;
|
|||
|
|
default:
|
|||
|
|
return <IconBell className="h-4 w-4 text-muted-foreground" />;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getLevelBadge = (level: string) => {
|
|||
|
|
switch (level) {
|
|||
|
|
case 'high':
|
|||
|
|
return <Badge variant="destructive">高</Badge>;
|
|||
|
|
case 'medium':
|
|||
|
|
return <Badge variant="secondary">中</Badge>;
|
|||
|
|
case 'low':
|
|||
|
|
return <Badge variant="outline">低</Badge>;
|
|||
|
|
default:
|
|||
|
|
return <Badge variant="outline">未知</Badge>;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Card>
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center justify-between">
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<IconBell className="h-5 w-5" />
|
|||
|
|
<span>系统警告</span>
|
|||
|
|
</div>
|
|||
|
|
<Badge variant="destructive">{systemAlerts.filter((alert) => !alert.resolved).length}</Badge>
|
|||
|
|
</CardTitle>
|
|||
|
|
<CardDescription>重要系统消息和警告</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
{systemAlerts.map((alert) => (
|
|||
|
|
<div key={alert.id} className="flex items-start space-x-3">
|
|||
|
|
<div className="flex-shrink-0 mt-1">{getAlertIcon(alert.type, alert.level)}</div>
|
|||
|
|
<div className="flex-1 min-w-0">
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<p className={`text-sm ${!alert.resolved ? 'font-medium' : ''}`}>{alert.message}</p>
|
|||
|
|
{getLevelBadge(alert.level)}
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center space-x-2 mt-1">
|
|||
|
|
<p className="text-xs text-muted-foreground">{alert.time}</p>
|
|||
|
|
{alert.resolved && (
|
|||
|
|
<Badge variant="outline" className="text-xs">
|
|||
|
|
已解决
|
|||
|
|
</Badge>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
<div className="mt-4 pt-4 border-t">
|
|||
|
|
<Button variant="outline" size="sm" className="w-full">
|
|||
|
|
查看所有警告
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function DatabaseStatusCard() {
|
|||
|
|
const connectionPercent = Math.round((additionalStats.database.connections / additionalStats.database.maxConnections) * 100);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Card>
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle className="flex items-center space-x-2">
|
|||
|
|
<IconDatabase className="h-5 w-5" />
|
|||
|
|
<span>数据库状态</span>
|
|||
|
|
</CardTitle>
|
|||
|
|
<CardDescription>数据库性能和连接状态</CardDescription>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<span className="text-sm font-medium">数据库状态</span>
|
|||
|
|
<Badge variant={additionalStats.database.status === 'healthy' ? 'default' : 'destructive'}>
|
|||
|
|
{additionalStats.database.status === 'healthy' ? '健康' : '异常'}
|
|||
|
|
</Badge>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<span className="text-sm">连接数</span>
|
|||
|
|
<span className="text-sm font-medium">
|
|||
|
|
{additionalStats.database.connections} / {additionalStats.database.maxConnections}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<Progress value={connectionPercent} className="h-2" />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="grid grid-cols-2 gap-4">
|
|||
|
|
<div className="text-center">
|
|||
|
|
<div className="text-lg font-bold text-primary">{additionalStats.database.queryTime}ms</div>
|
|||
|
|
<div className="text-xs text-muted-foreground">平均查询时间</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-center">
|
|||
|
|
<div className="text-lg font-bold text-primary">{additionalStats.database.size}</div>
|
|||
|
|
<div className="text-xs text-muted-foreground">数据库大小</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<span className="text-sm">备份状态</span>
|
|||
|
|
<Badge variant={additionalStats.database.backupStatus === 'completed' ? 'default' : 'secondary'}>
|
|||
|
|
{additionalStats.database.backupStatus === 'completed' ? '已完成' : '进行中'}
|
|||
|
|
</Badge>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-xs text-muted-foreground">最后备份: {additionalStats.database.lastBackup}</div>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default function MonitoringPage() {
|
|||
|
|
const trpc = useTRPC();
|
|||
|
|
useSetPageInfo({
|
|||
|
|
title: '系统监控',
|
|||
|
|
subtitle: '实时监控系统运行状态和用户活动',
|
|||
|
|
})
|
|||
|
|
const { data: systemStatus, isLoading: systemStatusLoading, refetch: refetchSystemStatus } = useQuery(
|
|||
|
|
trpc.system_monitor.getSystemStatus.queryOptions(),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="flex flex-1 flex-col">
|
|||
|
|
|
|||
|
|
<div className="@container/main flex flex-1 flex-col gap-6 p-6">
|
|||
|
|
{/* 统计卡片 */}
|
|||
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|||
|
|
<StatsCard
|
|||
|
|
title="在线用户"
|
|||
|
|
value={additionalStats.users.online}
|
|||
|
|
description={`总用户 ${additionalStats.users.total.toLocaleString()}`}
|
|||
|
|
icon={IconUsers}
|
|||
|
|
trend="up"
|
|||
|
|
trendValue={additionalStats.users.growth}
|
|||
|
|
status="good"
|
|||
|
|
/>
|
|||
|
|
<StatsCard
|
|||
|
|
title="文件总数"
|
|||
|
|
value={additionalStats.files.total.toLocaleString()}
|
|||
|
|
description={`总存储 ${additionalStats.files.size}`}
|
|||
|
|
icon={IconFiles}
|
|||
|
|
trend="up"
|
|||
|
|
trendValue={additionalStats.files.growth}
|
|||
|
|
status="good"
|
|||
|
|
/>
|
|||
|
|
<StatsCard
|
|||
|
|
title="服务器负载"
|
|||
|
|
value={systemStatus ? `${systemStatus.loadAverage.currentLoadPercent.toFixed(1)}%` : '--'}
|
|||
|
|
description={systemStatus ? `内存 ${systemStatus.memory.usagePercent.toFixed(1)}%` : '加载中...'}
|
|||
|
|
icon={IconServer}
|
|||
|
|
status={
|
|||
|
|
!systemStatus
|
|||
|
|
? 'warning'
|
|||
|
|
: systemStatus.cpuUsage > 80
|
|||
|
|
? 'error'
|
|||
|
|
: systemStatus.cpuUsage > 60
|
|||
|
|
? 'warning'
|
|||
|
|
: 'good'
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 详细信息第一行 */}
|
|||
|
|
<div className="grid gap-6 md:grid-cols-3">
|
|||
|
|
<SystemStatusCard
|
|||
|
|
systemStatus={systemStatus}
|
|||
|
|
systemStatusLoading={systemStatusLoading}
|
|||
|
|
refetchSystemStatus={refetchSystemStatus}
|
|||
|
|
/>
|
|||
|
|
<SystemAlertsCard />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 详细信息第二行 */}
|
|||
|
|
<div className="grid gap-6 md:grid-cols-2">
|
|||
|
|
<UserActivityCard />
|
|||
|
|
<DatabaseStatusCard />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|