import './styles.css' // Configuration const UPDATE_INTERVAL = 250 // ms const HISTORY_DURATION = 300 // seconds const MAX_HISTORY_POINTS = Math.ceil((HISTORY_DURATION * 1000) / UPDATE_INTERVAL) // 1200 points const REQUEST_TIMEOUT = 5000 // ms // Hosts to monitor (IPv4 preferred endpoints) const HOSTS = [ { name: 'Google Cloud Console', url: 'https://console.cloud.google.com', color: '#4285f4' }, { name: 'AWS Console', url: 'https://console.aws.amazon.com', color: '#ff9900' }, { name: 'GitHub', url: 'https://github.com', color: '#f0f6fc' }, { name: 'Cloudflare', url: 'https://www.cloudflare.com', color: '#f38020' }, { name: 'Microsoft Azure', url: 'https://portal.azure.com', color: '#0078d4' }, { name: 'DigitalOcean', url: 'https://www.digitalocean.com', color: '#0080ff' }, { name: 'Fastly CDN', url: 'https://www.fastly.com', color: '#ff282d' }, { name: 'Akamai', url: 'https://www.akamai.com', color: '#0096d6' }, { name: 'Local Gateway', url: 'http://192.168.100.1', color: '#a855f7' }, { name: 'datavi.be', url: 'https://datavi.be', color: '#10b981' }, ] // State: history for each host const hostState = HOSTS.map(host => ({ ...host, history: [], // Array of { timestamp, latency } or { timestamp, error } lastLatency: null, status: 'pending', // 'online', 'offline', 'pending', 'error' })) // Measure latency using HEAD request async function measureLatency(url) { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT) const start = performance.now() try { // Add cache-busting parameter const targetUrl = new URL(url) targetUrl.searchParams.set('_cb', Date.now().toString()) await fetch(targetUrl.toString(), { method: 'HEAD', mode: 'no-cors', // Allow cross-origin without CORS headers cache: 'no-store', signal: controller.signal, }) const end = performance.now() clearTimeout(timeoutId) return { latency: Math.round(end - start), error: null } } catch (err) { const end = performance.now() clearTimeout(timeoutId) if (err.name === 'AbortError') { return { latency: null, error: 'timeout' } } // For no-cors mode, network errors indicate unreachable // But successful completion means we got through return { latency: null, error: 'unreachable' } } } // Get latency color based on value function getLatencyColor(latency, status) { if (status === 'offline' || status === 'error' || latency === null) { return 'var(--color-latency-offline)' } if (latency < 50) return 'var(--color-latency-excellent)' if (latency < 100) return 'var(--color-latency-good)' if (latency < 200) return 'var(--color-latency-moderate)' if (latency < 500) return 'var(--color-latency-poor)' return 'var(--color-latency-bad)' } // Get Tailwind class for latency function getLatencyClass(latency, status) { if (status === 'offline' || status === 'error' || latency === null) { return 'text-gray-500' } if (latency < 50) return 'text-green-500' if (latency < 100) return 'text-lime-500' if (latency < 200) return 'text-yellow-500' if (latency < 500) return 'text-orange-500' return 'text-red-500' } // Draw sparkline on canvas function drawSparkline(canvas, history, hostColor) { const ctx = canvas.getContext('2d') const width = canvas.width const height = canvas.height const padding = 4 // Clear canvas ctx.clearRect(0, 0, width, height) // Filter valid latency points const validPoints = history.filter(p => p.latency !== null) if (validPoints.length < 2) { // Draw placeholder line ctx.strokeStyle = 'rgba(255,255,255,0.1)' ctx.lineWidth = 1 ctx.beginPath() ctx.moveTo(padding, height / 2) ctx.lineTo(width - padding, height / 2) ctx.stroke() return } // Calculate min/max for scaling const latencies = validPoints.map(p => p.latency) const minLatency = Math.min(...latencies) const maxLatency = Math.max(...latencies) const range = maxLatency - minLatency || 1 // Draw background grid lines ctx.strokeStyle = 'rgba(255,255,255,0.05)' ctx.lineWidth = 1 for (let i = 0; i <= 4; i++) { const y = padding + (i / 4) * (height - 2 * padding) ctx.beginPath() ctx.moveTo(0, y) ctx.lineTo(width, y) ctx.stroke() } // Draw the sparkline ctx.strokeStyle = hostColor ctx.lineWidth = 1.5 ctx.lineCap = 'round' ctx.lineJoin = 'round' ctx.beginPath() const pointWidth = (width - 2 * padding) / (MAX_HISTORY_POINTS - 1) // Map all history points to x positions let firstPoint = true history.forEach((point, i) => { if (point.latency === null) return const x = padding + i * pointWidth const normalizedY = (point.latency - minLatency) / range const y = height - padding - normalizedY * (height - 2 * padding) if (firstPoint) { ctx.moveTo(x, y) firstPoint = false } else { ctx.lineTo(x, y) } }) ctx.stroke() // Draw latest point indicator const lastValidPoint = [...history].reverse().find(p => p.latency !== null) if (lastValidPoint) { const lastIndex = history.lastIndexOf(lastValidPoint) const x = padding + lastIndex * pointWidth const normalizedY = (lastValidPoint.latency - minLatency) / range const y = height - padding - normalizedY * (height - 2 * padding) ctx.fillStyle = hostColor ctx.beginPath() ctx.arc(x, y, 3, 0, Math.PI * 2) ctx.fill() } // Draw error regions (red zones where connectivity was lost) ctx.fillStyle = 'rgba(239, 68, 68, 0.3)' let inErrorRegion = false let errorStart = 0 history.forEach((point, i) => { const x = padding + i * pointWidth if (point.latency === null && !inErrorRegion) { inErrorRegion = true errorStart = x } else if (point.latency !== null && inErrorRegion) { inErrorRegion = false ctx.fillRect(errorStart, 0, x - errorStart, height) } }) if (inErrorRegion) { ctx.fillRect(errorStart, 0, width - errorStart, height) } } // Create the UI function createUI() { const app = document.getElementById('app') app.innerHTML = `
Real-time network latency monitor | Updates every ${UPDATE_INTERVAL}ms | History: ${HISTORY_DURATION}s