diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 69cd01c..59fd6e7 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -20,9 +20,9 @@ import { startVisiblePolling } from '../utils/visiblePolling'; const { Text } = Typography; /** Dashboard refresh cadence — CPU, memory and traffic rates read as live. */ -const DASHBOARD_POLL_MS = 2000; -/** Ring buffer length for sparklines / speed chart (~2 min at 0.5 Hz). */ -const HISTORY_LEN = 60; +const DASHBOARD_POLL_MS = 1000; +/** Ring buffer length for sparklines / speed chart (~2 min at 1 Hz). */ +const HISTORY_LEN = 120; const formatRate = (bps: number) => `${formatBytes(bps)}/s`; const clampPct = (v: unknown) => { @@ -38,81 +38,101 @@ function pushHistory(buf: number[], value: number, max = HISTORY_LEN): number[] return next; } -/** Minimal SVG sparkline — no chart library dependency. */ +/** Compact metric sparkline — thin stroke, no soft blob fill. */ const Sparkline: React.FC<{ data: number[]; color: string; height?: number; - fillOpacity?: number; -}> = ({ data, color, height = 36, fillOpacity = 0.12 }) => { - const w = 120; +}> = ({ data, color, height = 32 }) => { + const w = 160; const h = height; if (data.length < 2) { - return ; + return ; } const min = Math.min(...data); const max = Math.max(...data); const span = max - min || 1; - const pts = data.map((v, i) => { + const coords = data.map((v, i) => { const x = (i / (data.length - 1)) * w; - const y = h - ((v - min) / span) * (h - 4) - 2; - return `${x.toFixed(2)},${y.toFixed(2)}`; + const y = h - ((v - min) / span) * (h - 6) - 3; + return [x, y] as const; }); - const line = pts.join(' '); - const area = `0,${h} ${line} ${w},${h}`; + // Straight segments only (no smooth curve) — reads like a real counter strip. + const line = coords.map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join(' '); + const baseline = coords.map(([x]) => `${x.toFixed(1)},${(h - 1).toFixed(1)}`).join(' '); + const last = coords[coords.length - 1]; return ( - - - + + + + ); }; -/** Dual-series area chart for upload / download rates. */ +/** Dual-series rate chart — solid grid, hairline strokes, no gradient wash. */ const SpeedChart: React.FC<{ up: number[]; down: number[]; upColor: string; downColor: string; height?: number; -}> = ({ up, down, upColor, downColor, height = 160 }) => { - const w = 400; +}> = ({ up, down, upColor, downColor, height = 168 }) => { + const w = 480; const h = height; const n = Math.max(up.length, down.length, 2); const all = [...up, ...down]; const max = Math.max(...all, 1); const toPts = (series: number[]) => { - const pad = series.length < n ? Array(n - series.length).fill(0).concat(series) : series; - return pad - .map((v, i) => { - const x = (i / (n - 1)) * w; - const y = h - (v / max) * (h - 8) - 4; - return `${x.toFixed(2)},${y.toFixed(2)}`; - }) - .join(' '); + const pad = series.length < n ? Array(n - series.length).fill(0).concat(series) : series.slice(-n); + return pad.map((v, i) => { + const x = (i / (n - 1)) * w; + const y = h - (Math.max(0, v) / max) * (h - 12) - 6; + return `${x.toFixed(1)},${y.toFixed(1)}`; + }).join(' '); }; const upLine = toPts(up); const downLine = toPts(down); - const upArea = `0,${h} ${upLine} ${w},${h}`; - const downArea = `0,${h} ${downLine} ${w},${h}`; return ( - - {[0.25, 0.5, 0.75].map((p) => ( + + {[0, 0.25, 0.5, 0.75, 1].map((p) => ( ))} - - - - + + ); }; @@ -139,7 +159,7 @@ const MetricCard: React.FC = ({ title, value, unit, detail, pea style={{ height: '100%', borderRadius: 12 }} >
- + {title} {peak ? ( @@ -151,8 +171,8 @@ const MetricCard: React.FC = ({ title, value, unit, detail, pea
= ({ title, value, unit, detail, pea ) : (
)} - + ); }; @@ -402,7 +422,7 @@ const Dashboard: React.FC = () => { >
- + {t('dashboard.traffic')}
@@ -460,7 +480,7 @@ const Dashboard: React.FC = () => { styles={{ body: { padding: isMobile ? 12 : 16 } }} style={{ height: '100%', borderRadius: 12 }} > - + {t('dashboard.activeConnections')}
{
- +