Files
lr-openwrt-tool/src/components/dashboard/NetworkTrafficChart.tsx
T
2026-05-12 11:59:54 +01:00

252 lines
6.0 KiB
TypeScript

import { useEffect, useState } from 'react';
import {
Area,
AreaChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import { Card } from '@/components/ui/Card';
import { vpsApi } from '@/services/vpsApi';
type TrafficMode =
| 'wireguard'
| 'udp2raw';
type NetworkTrafficChartProps = {
title?: string;
subtitle?: string;
mode?: TrafficMode;
compact?: boolean;
};
type TrafficPoint = {
time: string;
downloadMbps: number;
uploadMbps: number;
};
const MAX_POINTS = 24;
export function NetworkTrafficChart({
title = 'Tráfego de Rede',
subtitle = 'Débito RX/TX em direto',
mode = 'wireguard',
compact = false,
}: NetworkTrafficChartProps) {
const [points, setPoints] = useState<
TrafficPoint[]
>([]);
const [error, setError] = useState('');
useEffect(() => {
let mounted = true;
async function loadTraffic() {
try {
const response =
mode === 'udp2raw'
? await vpsApi.udp2rawTraffic()
: await vpsApi.networkTraffic();
if (!mounted) {
return;
}
const point: TrafficPoint = {
time: new Date(
response.updatedAt,
).toLocaleTimeString(),
downloadMbps:
response.downloadMbps,
uploadMbps:
response.uploadMbps,
};
setPoints((currentPoints) => [
...currentPoints.slice(
-(MAX_POINTS - 1),
),
point,
]);
setError('');
} catch (err) {
if (mounted) {
setError(String(err));
}
}
}
setPoints([]);
setError('');
loadTraffic();
const intervalId = window.setInterval(
loadTraffic,
3000,
);
return () => {
mounted = false;
window.clearInterval(intervalId);
};
}, [mode]);
const latestPoint =
points[points.length - 1];
return (
<Card className="relative h-full min-h-[240px] overflow-hidden">
<div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-blue-400/30 to-transparent" />
<div className="mb-4 flex items-start justify-between gap-4">
<div className="min-w-0">
<h3 className="truncate font-semibold text-white">
{title}
</h3>
<p className="mt-1 truncate text-xs text-slate-500">
{subtitle}
</p>
</div>
<span className="shrink-0 rounded-lg border border-white/10 bg-slate-900 px-3 py-1 text-xs text-slate-400">
3s
</span>
</div>
{error && (
<div className="mb-3 rounded-xl border border-red-500/20 bg-red-500/10 p-3 text-xs text-red-300">
{error}
</div>
)}
{compact && latestPoint && (
<div className="mb-3 grid grid-cols-2 gap-2">
<div className="min-w-0 rounded-xl border border-white/10 bg-white/[0.025] p-3">
<p className="text-[10px] font-semibold uppercase tracking-wide text-slate-500">
Entrada
</p>
<p className="mt-1 truncate font-mono text-sm font-bold text-green-300">
{latestPoint.downloadMbps.toFixed(3)} Mbps
</p>
</div>
<div className="min-w-0 rounded-xl border border-white/10 bg-white/[0.025] p-3">
<p className="text-[10px] font-semibold uppercase tracking-wide text-slate-500">
Saída
</p>
<p className="mt-1 truncate font-mono text-sm font-bold text-blue-300">
{latestPoint.uploadMbps.toFixed(3)} Mbps
</p>
</div>
</div>
)}
<div
className={
compact
? 'h-[calc(100%-8.5rem)] min-h-[130px]'
: 'h-[calc(100%-4.5rem)] min-h-[320px]'
}
>
<ResponsiveContainer
width="100%"
height="100%"
>
<AreaChart
data={points}
margin={
compact
? {
top: 8,
right: 8,
left: -30,
bottom: 0,
}
: {
top: 8,
right: 16,
left: -8,
bottom: 0,
}
}
>
<CartesianGrid
strokeDasharray="3 3"
stroke="rgba(148,163,184,.12)"
/>
<XAxis
dataKey="time"
stroke="#94a3b8"
fontSize={compact ? 10 : 12}
minTickGap={compact ? 36 : 24}
tick={compact ? false : undefined}
/>
<YAxis
stroke="#94a3b8"
fontSize={compact ? 10 : 12}
width={compact ? 42 : 56}
tickFormatter={(value) =>
`${value} Mbps`
}
/>
<Tooltip
contentStyle={{
background: '#0b1522',
border:
'1px solid rgba(255,255,255,.1)',
borderRadius: 12,
}}
formatter={(value) => [
`${Number(value).toFixed(
3,
)} Mbps`,
'',
]}
/>
<Area
type="monotone"
dataKey="downloadMbps"
name={
mode === 'udp2raw'
? 'Entrada'
: 'Download'
}
stroke="#19d16f"
fill="#19d16f"
fillOpacity={0.2}
/>
<Area
type="monotone"
dataKey="uploadMbps"
name={
mode === 'udp2raw'
? 'Saída'
: 'Upload'
}
stroke="#3b82f6"
fill="#3b82f6"
fillOpacity={0.16}
/>
</AreaChart>
</ResponsiveContainer>
</div>
</Card>
);
}