252 lines
6.0 KiB
TypeScript
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>
|
|
);
|
|
} |