"use client"
import { useState, useEffect, useCallback } from "react"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
import { Button } from "./ui/button"
import { Badge } from "./ui/badge"
import { Activity, TrendingDown, TrendingUp, Minus, RefreshCw, Wifi, FileText } from "lucide-react"
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line } from "recharts"
import { useIsMobile } from "../hooks/use-mobile"
import { fetchApi } from "@/lib/api-config"
const TIMEFRAME_OPTIONS = [
{ value: "hour", label: "1 Hour" },
{ value: "6hour", label: "6 Hours" },
{ value: "day", label: "24 Hours" },
{ value: "3day", label: "3 Days" },
{ value: "week", label: "7 Days" },
]
const TARGET_OPTIONS = [
{ value: "gateway", label: "Gateway (Router)", realtime: false },
{ value: "cloudflare", label: "Cloudflare (1.1.1.1)", realtime: true },
{ value: "google", label: "Google DNS (8.8.8.8)", realtime: true },
]
interface LatencyHistoryPoint {
timestamp: number
value: number
min?: number
max?: number
packet_loss?: number
}
interface LatencyStats {
min: number
max: number
avg: number
current: number
}
interface RealtimeResult {
target: string
target_ip: string
latency_avg: number | null
latency_min: number | null
latency_max: number | null
packet_loss: number
status: string
timestamp?: number
}
interface LatencyDetailModalProps {
open: boolean
onOpenChange: (open: boolean) => void
currentLatency?: number
}
const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
const entry = payload[0]
const packetLoss = entry?.payload?.packet_loss
return (
{label}
Latency:
{entry.value} ms
{packetLoss !== undefined && packetLoss > 0 && (
)}
)
}
return null
}
const getStatusColor = (latency: number) => {
if (latency >= 200) return "#ef4444"
if (latency >= 100) return "#f59e0b"
return "#22c55e"
}
const getStatusInfo = (latency: number | null) => {
if (latency === null || latency === 0) return { status: "N/A", color: "bg-gray-500/10 text-gray-500 border-gray-500/20" }
if (latency < 50) return { status: "Excellent", color: "bg-green-500/10 text-green-500 border-green-500/20" }
if (latency < 100) return { status: "Good", color: "bg-green-500/10 text-green-500 border-green-500/20" }
if (latency < 200) return { status: "Fair", color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" }
return { status: "Poor", color: "bg-red-500/10 text-red-500 border-red-500/20" }
}
const getStatusText = (latency: number | null): string => {
if (latency === null || latency === 0) return "N/A"
if (latency < 50) return "Excellent"
if (latency < 100) return "Good"
if (latency < 200) return "Fair"
return "Poor"
}
interface ReportData {
target: string
targetLabel: string
isRealtime: boolean
stats: LatencyStats
realtimeResults: RealtimeResult[]
data: LatencyHistoryPoint[]
timeframe: string
}
const generateLatencyReport = (report: ReportData) => {
const now = new Date().toLocaleString()
const statusText = report.isRealtime
? getStatusText(report.realtimeResults[report.realtimeResults.length - 1]?.latency_avg ?? null)
: getStatusText(report.stats.current)
const statusColorMap: Record = {
"Excellent": "#22c55e",
"Good": "#22c55e",
"Fair": "#f59e0b",
"Poor": "#ef4444",
"N/A": "#888888"
}
const statusColor = statusColorMap[statusText] || "#888888"
const timeframeLabel = TIMEFRAME_OPTIONS.find(t => t.value === report.timeframe)?.label || report.timeframe
// Build test results table for realtime mode
const realtimeTableRows = report.realtimeResults.map((r, i) => `
${i + 1}
${new Date(r.timestamp || Date.now()).toLocaleTimeString()}
${r.latency_avg !== null ? r.latency_avg + ' ms' : 'Failed'}
${r.latency_min !== null ? r.latency_min + ' ms' : '-'}
${r.latency_max !== null ? r.latency_max + ' ms' : '-'}
${r.packet_loss}%
${getStatusText(r.latency_avg)}
`).join('')
// Build history summary for gateway mode
const historyStats = report.data.length > 0 ? {
samples: report.data.length,
avgPacketLoss: (report.data.reduce((acc, d) => acc + (d.packet_loss || 0), 0) / report.data.length).toFixed(2),
startTime: new Date(report.data[0].timestamp * 1000).toLocaleString(),
endTime: new Date(report.data[report.data.length - 1].timestamp * 1000).toLocaleString(),
} : null
const html = `
Network Latency Report - ProxMenux Monitor
1. Executive Summary
Overall Status
${statusText}
Current Latency
${report.isRealtime
? (report.realtimeResults[report.realtimeResults.length - 1]?.latency_avg ?? 'N/A') + (report.realtimeResults[report.realtimeResults.length - 1]?.latency_avg ? ' ms' : '')
: report.stats.current + ' ms'}
Analysis Summary
${report.isRealtime
? `This report contains ${report.realtimeResults.length} real-time latency test(s) performed against ${report.targetLabel}. ${
report.realtimeResults.length > 0
? `The average latency across all tests is ${(report.realtimeResults.reduce((acc, r) => acc + (r.latency_avg || 0), 0) / report.realtimeResults.length).toFixed(1)} ms.`
: ''
}`
: `This report analyzes ${report.data.length} latency samples collected over ${timeframeLabel.toLowerCase()} against the network gateway. The average latency during this period was ${report.stats.avg} ms with a minimum of ${report.stats.min} ms and maximum of ${report.stats.max} ms.`
}
Performance Rating
${
statusText === 'Excellent' ? 'Network latency is excellent. No action required.' :
statusText === 'Good' ? 'Network latency is within acceptable parameters.' :
statusText === 'Fair' ? 'Network latency is elevated. Consider investigating network congestion or routing issues.' :
statusText === 'Poor' ? 'Network latency is critically high. Immediate investigation recommended.' :
'Unable to determine network status.'
}
2. Latency Statistics
Current
${report.isRealtime
? (report.realtimeResults[report.realtimeResults.length - 1]?.latency_avg ?? 'N/A') + ' ms'
: report.stats.current + ' ms'}
Minimum
${report.isRealtime
? (report.realtimeResults.length > 0 ? Math.min(...report.realtimeResults.map(r => r.latency_min || Infinity)).toFixed(1) : 'N/A') + ' ms'
: report.stats.min + ' ms'}
Average
${report.isRealtime
? (report.realtimeResults.length > 0 ? (report.realtimeResults.reduce((acc, r) => acc + (r.latency_avg || 0), 0) / report.realtimeResults.length).toFixed(1) : 'N/A') + ' ms'
: report.stats.avg + ' ms'}
Maximum
${report.isRealtime
? (report.realtimeResults.length > 0 ? Math.max(...report.realtimeResults.map(r => r.latency_max || 0)).toFixed(1) : 'N/A') + ' ms'
: report.stats.max + ' ms'}
${!report.isRealtime && historyStats ? `
Sample Count
${historyStats.samples}
Period Start
${historyStats.startTime}
Period End
${historyStats.endTime}
` : ''}
${report.isRealtime && report.realtimeResults.length > 0 ? `
3. Test Results
#
Time
Avg Latency
Min
Max
Packet Loss
Status
${realtimeTableRows}
` : ''}
${report.isRealtime ? '4' : '3'}. Reference Thresholds
Excellent (< 50ms): Optimal network performance for all applications including real-time gaming and video calls.
Good (50-100ms): Acceptable latency for most applications. Minor impact on real-time interactions.
Fair (100-200ms): Noticeable delay in interactive applications. May affect VoIP and gaming quality.
Poor (> 200ms): Significant latency causing degraded user experience. Investigation recommended.
${report.isRealtime ? '5' : '4'}. Methodology
Test Method: ICMP Echo Request (Ping)
Target: ${report.targetLabel} ${report.target === 'gateway' ? '(Default network gateway)' : `(${report.target === 'cloudflare' ? '1.1.1.1' : '8.8.8.8'})`}
Samples per Test: 3 consecutive pings
Metrics Collected: Round-trip time (RTT) minimum, average, maximum, and packet loss percentage
`
const printWindow = window.open('', '_blank')
if (printWindow) {
printWindow.document.write(html)
printWindow.document.close()
}
}
export function LatencyDetailModal({ open, onOpenChange, currentLatency }: LatencyDetailModalProps) {
const [timeframe, setTimeframe] = useState("hour")
const [target, setTarget] = useState("gateway")
const [data, setData] = useState([])
const [stats, setStats] = useState({ min: 0, max: 0, avg: 0, current: 0 })
const [loading, setLoading] = useState(true)
const [realtimeResults, setRealtimeResults] = useState([])
const [realtimeTesting, setRealtimeTesting] = useState(false)
const isMobile = useIsMobile()
const isRealtime = TARGET_OPTIONS.find(t => t.value === target)?.realtime ?? false
// Fetch history for gateway
useEffect(() => {
if (open && target === "gateway") {
fetchHistory()
}
}, [open, timeframe, target])
// Auto-test when switching to realtime target
useEffect(() => {
if (open && isRealtime) {
// Clear previous results and run initial test
setRealtimeResults([])
runRealtimeTest()
}
}, [open, target])
const fetchHistory = async () => {
setLoading(true)
try {
const result = await fetchApi<{ data: LatencyHistoryPoint[]; stats: LatencyStats; target: string }>(
`/api/network/latency/history?target=${target}&timeframe=${timeframe}`
)
if (result && result.data) {
setData(result.data)
setStats(result.stats)
}
} catch (err) {
// Silently fail
} finally {
setLoading(false)
}
}
const runRealtimeTest = useCallback(async () => {
if (realtimeTesting) return
setRealtimeTesting(true)
try {
const result = await fetchApi(`/api/network/latency/current?target=${target}`)
if (result) {
const newResult = { ...result, timestamp: Date.now() }
setRealtimeResults(prev => [...prev.slice(-19), newResult]) // Keep last 20 results
}
} catch (err) {
// Silently fail
} finally {
setRealtimeTesting(false)
}
}, [target, realtimeTesting])
const formatTime = (timestamp: number) => {
const date = new Date(timestamp * 1000)
if (timeframe === "hour" || timeframe === "6hour") {
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
} else if (timeframe === "day") {
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
} else {
return date.toLocaleDateString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" })
}
}
const formatRealtimeTime = (timestamp: number) => {
return new Date(timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" })
}
// Gateway view data
const chartData = data.map((d) => ({
...d,
time: formatTime(d.timestamp),
}))
// Realtime view data
const realtimeChartData = realtimeResults.map(r => ({
time: formatRealtimeTime(r.timestamp || Date.now()),
value: r.latency_avg || 0,
packet_loss: r.packet_loss,
}))
const lastRealtimeResult = realtimeResults[realtimeResults.length - 1]
const realtimeLatency = lastRealtimeResult?.latency_avg ?? null
const currentLat = isRealtime
? realtimeLatency
: (currentLatency && currentLatency > 0 ? Math.round(currentLatency * 10) / 10 : stats.current)
const currentStatus = getStatusInfo(currentLat)
const chartColor = getStatusColor(currentLat || 0)
const values = data.map((d) => d.value).filter(v => v !== null && v !== undefined)
const yMin = 0
const yMax = values.length > 0 ? Math.ceil(Math.max(...values) * 1.2) : 200
const realtimeValues = realtimeResults.map(r => r.latency_avg).filter(v => v !== null) as number[]
const realtimeYMax = realtimeValues.length > 0 ? Math.ceil(Math.max(...realtimeValues) * 1.2) : 200
return (
Network Latency
{TARGET_OPTIONS.map((opt) => (
{opt.label}
))}
{!isRealtime && (
{TIMEFRAME_OPTIONS.map((opt) => (
{opt.label}
))}
)}
{isRealtime && (
{realtimeTesting ? 'Testing...' : 'Test Again'}
)}
generateLatencyReport({
target,
targetLabel: TARGET_OPTIONS.find(t => t.value === target)?.label || target,
isRealtime,
stats,
realtimeResults,
data,
timeframe
})}
disabled={isRealtime ? realtimeResults.length === 0 : data.length === 0}
className="gap-2"
>
Report
{/* Realtime mode indicator */}
{isRealtime && (
Real-time test mode - Results are not stored. Click "Test Again" for new measurements.
)}
{/* Stats bar */}
Current
{currentLat !== null ? `${currentLat} ms` : '---'}
{isRealtime ? (
<>
Min
{lastRealtimeResult?.latency_min !== null ? `${lastRealtimeResult?.latency_min} ms` : '---'}
Max
{lastRealtimeResult?.latency_max !== null ? `${lastRealtimeResult?.latency_max} ms` : '---'}
Packet Loss
0 ? 'text-red-500' : 'text-foreground'}`}>
{lastRealtimeResult?.packet_loss !== undefined ? `${lastRealtimeResult.packet_loss}%` : '---'}
>
) : (
<>
>
)}
{/* Chart */}
{isRealtime ? (
// Realtime chart - shows test results from this session
realtimeChartData.length === 0 ? (
{realtimeTesting ? (
<>
Running latency test...
>
) : (
<>
Click "Test Again" to run a latency test
>
)}
) : (
`${v}ms`}
width={isMobile ? 45 : 50}
/>
} />
)
) : (
// Gateway historical chart
loading ? (
) : chartData.length === 0 ? (
No latency data available for this period
Data is collected every 60 seconds
) : (
`${v}ms`}
width={isMobile ? 45 : 50}
/>
} />
)
)}
{/* Test history for realtime mode */}
{isRealtime && realtimeResults.length > 0 && (
{realtimeResults.length} test{realtimeResults.length > 1 ? 's' : ''} this session
)}
)
}