140 lines
6.0 KiB
JavaScript
140 lines
6.0 KiB
JavaScript
import { formatUptime, getUptimeColor } from '../hooks/useMonitor';
|
|
|
|
// 统计概览卡片
|
|
export default function StatsCard({ websites }) {
|
|
// 计算统计数据
|
|
const stats = {
|
|
total: websites?.length || 0,
|
|
online: 0,
|
|
offline: 0,
|
|
avgUptime24h: 0,
|
|
avgUptime7d: 0,
|
|
avgLatency: 0,
|
|
};
|
|
|
|
if (websites && websites.length > 0) {
|
|
let totalUptime24h = 0;
|
|
let totalUptime7d = 0;
|
|
let totalLatency = 0;
|
|
let latencyCount = 0;
|
|
|
|
websites.forEach(site => {
|
|
// 检查所有URL的状态
|
|
const hasOnlineUrl = site.url_statuses?.some(us => us.current_state?.is_up);
|
|
if (hasOnlineUrl) {
|
|
stats.online++;
|
|
} else {
|
|
stats.offline++;
|
|
}
|
|
|
|
totalUptime24h += site.uptime_24h || 0;
|
|
totalUptime7d += site.uptime_7d || 0;
|
|
|
|
site.url_statuses?.forEach(us => {
|
|
if (us.current_state?.latency) {
|
|
totalLatency += us.current_state.latency;
|
|
latencyCount++;
|
|
}
|
|
});
|
|
});
|
|
|
|
stats.avgUptime24h = totalUptime24h / stats.total;
|
|
stats.avgUptime7d = totalUptime7d / stats.total;
|
|
stats.avgLatency = latencyCount > 0 ? Math.round(totalLatency / latencyCount) : 0;
|
|
}
|
|
|
|
return (
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
{/* 监控网站数 */}
|
|
<div className="bg-white rounded-xl p-4 shadow-sm border border-gray-100">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-500">监控网站</p>
|
|
<p className="text-2xl font-bold text-gray-800 mt-1">{stats.total}</p>
|
|
</div>
|
|
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-50 to-blue-100 flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center mt-3 text-xs">
|
|
<span className="text-green-500 flex items-center">
|
|
<span className="w-2 h-2 rounded-full bg-green-500 mr-1"></span>
|
|
{stats.online} 在线
|
|
</span>
|
|
<span className="text-gray-300 mx-2">|</span>
|
|
<span className="text-red-500 flex items-center">
|
|
<span className="w-2 h-2 rounded-full bg-red-500 mr-1"></span>
|
|
{stats.offline} 离线
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 24小时可用率 */}
|
|
<div className="bg-white rounded-xl p-4 shadow-sm border border-gray-100">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-500">24h 可用率</p>
|
|
<p className={`text-2xl font-bold mt-1 ${getUptimeColor(stats.avgUptime24h)}`}>
|
|
{formatUptime(stats.avgUptime24h)}
|
|
</p>
|
|
</div>
|
|
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-50 to-emerald-100 flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="mt-3 h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-gradient-to-r from-emerald-400 to-green-500 rounded-full"
|
|
style={{ width: `${Math.min(stats.avgUptime24h, 100)}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 7天可用率 */}
|
|
<div className="bg-white rounded-xl p-4 shadow-sm border border-gray-100">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-500">7d 可用率</p>
|
|
<p className={`text-2xl font-bold mt-1 ${getUptimeColor(stats.avgUptime7d)}`}>
|
|
{formatUptime(stats.avgUptime7d)}
|
|
</p>
|
|
</div>
|
|
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-green-50 to-green-100 flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="mt-3 h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-gradient-to-r from-green-400 to-teal-500 rounded-full"
|
|
style={{ width: `${Math.min(stats.avgUptime7d, 100)}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 平均延迟 */}
|
|
<div className="bg-white rounded-xl p-4 shadow-sm border border-gray-100">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-gray-500">平均延迟</p>
|
|
<p className="text-2xl font-bold text-gray-800 mt-1">
|
|
{stats.avgLatency}
|
|
<span className="text-sm font-normal text-gray-500 ml-1">ms</span>
|
|
</p>
|
|
</div>
|
|
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-50 to-purple-100 flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|