JIT
exploring plot ping's features for network monitoring and visualization
Plot Ping is a powerful Web application Features Continuous Traceroute Monitoring Runs automated traceroutes every 5 seconds to track network paths and performance in real time. Displays live results in an intuitive grid with Hop, IP Address, RTT, Min/Max/Avg Latency, and Packet Loss % for every node. Dynamic, Customizable Graphs Live Traceroute Visualization: Watch active routes unfold with current, min, max, average latency visualisation. Historical Trend Analysis: Graph latency and packet loss over time for any hop (e.g., focus on critical last-hop performance). Flexible Timeframes: Adjust graphs to display data from 60 seconds up to 48 hours, with presets including (1h, 4h, 8h and 24h) for quick analysis. Deep Dive into Network Health Compare min, max, and average latency trends to identify intermittent issues. Spot dropout patterns with packet loss history, perfect for diagnosing ISP reliability or VPN instability. Click any hop to isolate its metrics or compare multiple nodes side by side. با Asp.net mvc 5 C# ابزار رسم با canvans html بصورت یک کنترل طراحی شود

که بتوان بین چند مسیر مختلف مقایسه انجام داد و ابزار را در چندین فرم مختلف به اشتراک بگذارد.

I'll help you create a network monitoring application with ASP.NET MVC 5 that visualizes traceroute data. Let's build this step by step.

Updated files
v1
@arashansariardaly93 Plot Ping is a powerful Web application Features Continuous Traceroute Monitoring Runs automated traceroutes every 5 seconds to track network paths and performance in real time. Displays live results in an intuitive grid with Hop, IP Address, RTT, Min/Max/Avg Latency, and Packet Loss % for every node. Dynamic, Customizable Graphs Live Traceroute Visualization: Watch active routes unfold with current, min, max, average latency visualisation. Historical Trend Analysis: Graph latency and packet loss over time for any hop (e.g., focus on critical last-hop performance). Flexible Timeframes: Adjust graphs to display data from 60 seconds up to 48 hours, with presets including (1h, 4h, 8h and 24h) for quick analysis. Deep Dive into Network Health Compare min, max, and average latency trends to identify intermittent issues. Spot dropout patterns with packet loss history, perfect for diagnosing ISP reliability or VPN instability. Click any hop to isolate its metrics or compare multiple nodes side by side

I'll enhance the solution to include all these features with a more comprehensive implementation. Here's the improved version:

Updated files
v2
with html css js ?

Here's a complete HTML, CSS, and JavaScript implementation of Plot Ping with all the requested features:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Plot Ping - Network Monitoring</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #f5f7fa;
        }
        .card {
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            margin-bottom: 20px;
            border: none;
        }
        .card-header {
            background-color: #fff;
            border-bottom: 1px solid rgba(0, 0, 0, 0.05);
            font-weight: 600;
        }
        .hop-row:hover {
            background-color: #f8f9fa;
            cursor: pointer;
        }
        .selected-hop {
            background-color: #e9f5ff !important;
        }
        #tracerouteCanvas {
            background-color: #fff;
            border-radius: 8px;
        }
        .badge-latency {
            background-color: #e3f2fd;
            color: #1976d2;
        }
        .badge-loss {
            background-color: #ffebee;
            color: #d32f2f;
        }
        .timeframe-btn.active {
            background-color: #1976d2;
            color: white;
        }
        .connection-status {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            display: inline-block;
            margin-right: 5px;
        }
        .connection-good {
            background-color: #4caf50;
        }
        .connection-warning {
            background-color: #ff9800;
        }
        .connection-bad {
            background-color: #f44336;
        }
    </style>
</head>
<body>
    <div class="container-fluid py-4">
        <div class="row mb-4">
            <div class="col">
                <h1 class="display-6">Plot Ping Network Monitor</h1>
                <p class="text-muted">Continuous traceroute monitoring with real-time visualization</p>
            </div>
        </div>

        <div class="row">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header d-flex justify-content-between align-items-center">
                        <div>
                            <span class="connection-status connection-good"></span>
                            <span>Live Traceroute Visualization</span>
                        </div>
                        <div class="btn-group btn-group-sm timeframe-selector">
                            <button class="btn btn-outline-primary timeframe-btn active" data-timeframe="5m">5m</button>
                            <button class="btn btn-outline-primary timeframe-btn" data-timeframe="1h">1h</button>
                            <button class="btn btn-outline-primary timeframe-btn" data-timeframe="4h">4h</button>
                            <button class="btn btn-outline-primary timeframe-btn" data-timeframe="8h">8h</button>
                            <button class="btn btn-outline-primary timeframe-btn" data-timeframe="24h">24h</button>
                        </div>
                    </div>
                    <div class="card-body">
                        <canvas id="tracerouteCanvas" height="300"></canvas>
                    </div>
                </div>
            </div>

            <div class="col-md-4">
                <div class="card">
                    <div class="card-header">
                        <div class="d-flex justify-content-between align-items-center">
                            <span>Hop Details</span>
                            <span class="badge bg-primary">Live</span>
                        </div>
                    </div>
                    <div class="card-body p-0">
                        <div class="table-responsive" style="max-height: 300px; overflow-y: auto;">
                            <table class="table table-hover mb-0">
                                <thead class="sticky-top bg-white" style="top: 0;">
                                    <tr>
                                        <th>Hop</th>
                                        <th>IP</th>
                                        <th>Avg</th>
                                        <th>Loss</th>
                                    </tr>
                                </thead>
                                <tbody id="hopTable">
                                    <!-- Filled by JavaScript -->
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="row mt-4">
            <div class="col-md-6">
                <div class="card">
                    <div class="card-header">
                        <span>Latency Trends</span>
                    </div>
                    <div class="card-body">
                        <canvas id="latencyChart" height="250"></canvas>
                    </div>
                </div>
            </div>
            <div class="col-md-6">
                <div class="card">
                    <div class="card-header">
                        <span>Packet Loss History</span>
                    </div>
                    <div class="card-body">
                        <canvas id="lossChart" height="250"></canvas>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Hop Details Modal -->
    <div class="modal fade" id="hopDetailsModal" tabindex="-1">
        <div class="modal-dialog modal-lg">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="hopDetailsTitle">Hop Details</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <div class="row mb-3">
                        <div class="col-md-6">
                            <div class="card">
                                <div class="card-header">Current Stats</div>
                                <div class="card-body">
                                    <table class="table table-sm">
                                        <tbody>
                                            <tr>
                                                <td>IP Address:</td>
                                                <td id="detail-ip"></td>
                                            </tr>
                                            <tr>
                                                <td>Hop Number:</td>
                                                <td id="detail-hop"></td>
                                            </tr>
                                            <tr>
                                                <td>Current RTT:</td>
                                                <td id="detail-current"></td>
                                            </tr>
                                            <tr>
                                                <td>Avg Latency:</td>
                                                <td id="detail-avg"></td>
                                            </tr>
                                            <tr>
                                                <td>Min Latency:</td>
                                                <td id="detail-min"></td>
                                            </tr>
                                            <tr>
                                                <td>Max Latency:</td>
                                                <td id="detail-max"></td>
                                            </tr>
                                            <tr>
                                                <td>Packet Loss:</td>
                                                <td id="detail-loss"></td>
                                            </tr>
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                        </div>
                        <div class="col-md-6">
                            <div class="card">
                                <div class="card-header">Location Info</div>
                                <div class="card-body">
                                    <div id="hopLocation">
                                        <p class="text-muted">Location data loading...</p>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <canvas id="hopHistoryChart" height="250"></canvas>
                </div>
            </div>
        </div>
    </div>

    <script>
        // Mock data generator for demonstration
        function generateMockRoute() {
            const hops = [];
            const hopCount = 8 + Math.floor(Math.random() * 5);
            let lastLatency = 5;
            
            for (let i = 1; i <= hopCount; i++) {
                const latencyVariation = Math.random() * 10;
                const avgLatency = lastLatency + latencyVariation;
                const minLatency = avgLatency - (Math.random() * 5);
                const maxLatency = avgLatency + (Math.random() * 10);
                const packetLoss = Math.random() > 0.8 ? Math.floor(Math.random() * 30) : 0;
                
                hops.push({
                    number: i,
                    ipAddress: `192.168.${Math.floor(Math.random() * 254)}.${Math.floor(Math.random() * 254)}`,
                    currentRTT: avgLatency + (Math.random() * 5 - 2.5),
                    avgLatency: avgLatency,
                    minLatency: minLatency,
                    maxLatency: maxLatency,
                    packetLoss: packetLoss
                });
                
                lastLatency = avgLatency;
            }
            
            return {
                timestamp: new Date(),
                target: "example.com",
                hops: hops
            };
        }

        // Traceroute Visualizer
        class TracerouteVisualizer {
            constructor(canvasId) {
                this.canvas = document.getElementById(canvasId);
                this.ctx = this.canvas.getContext('2d');
                this.routes = [];
                this.selectedHop = null;
                this.timeframe = '5m';
                this.historicalData = {};
                this.initialize();
            }

            initialize() {
                this.resizeCanvas();
                window.addEventListener('resize', () => this.resizeCanvas());
                this.canvas.addEventListener('click', (e) => this.handleCanvasClick(e));
            }

            handleCanvasClick(e) {
                const rect = this.canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;
                
                // Find clicked hop
                for (const route of this.routes) {
                    for (const hop of route.hops) {
                        const hopX = 50 + (hop.number * 80);
                        const hopY = this.canvas.height - 50 - (hop.avgLatency * 3);
                        
                        if (Math.sqrt(Math.pow(x - hopX, 2) + Math.pow(y - hopY, 2)) < 10) {
                            this.selectedHop = hop;
                            this.showHopDetails(hop);
                            this.draw();
                            return;
                        }
                    }
                }
            }

            showHopDetails(hop) {
                const modal = new bootstrap.Modal(document.getElementById('hopDetailsModal'));
                document.getElementById('hopDetailsTitle').textContent = `Hop ${hop.number} - ${hop.ipAddress}`;
                
                // Update details table
                document.getElementById('detail-ip').textContent = hop.ipAddress;
                document.getElementById('detail-hop').textContent = hop.number;
                document.getElementById('detail-current').textContent = hop.currentRTT.toFixed(2) + ' ms';
                document.getElementById('detail-avg').textContent = hop.avgLatency.toFixed(2) + ' ms';
                document.getElementById('detail-min').textContent = hop.minLatency.toFixed(2) + ' ms';
                document.getElementById('detail-max').textContent = hop.maxLatency.toFixed(2) + ' ms';
                document.getElementById('detail-loss').textContent = hop.packetLoss.toFixed(2) + '%';
                
                // Simulate location lookup
                setTimeout(() => {
                    document.getElementById('hopLocation').innerHTML = `
                        <p><strong>Country:</strong> United States</p>
                        <p><strong>City:</strong> ${['New York', 'Los Angeles', 'Chicago', 'Dallas', 'Seattle'][Math.floor(Math.random() * 5)]}</p>
                        <p><strong>ISP:</strong> ${['Comcast', 'AT&T', 'Verizon', 'Charter', 'CenturyLink'][Math.floor(Math.random() * 5)]}</p>
                        <p><strong>ASN:</strong> AS${Math.floor(1000 + Math.random() * 9000)}</p>
                    `;
                }, 500);
                
                // Draw historical chart
                this.drawHistoricalChart(hop);
                modal.show();
            }

            drawHistoricalChart(hop) {
                const ctx = document.getElementById('hopHistoryChart').getContext('2d');
                if (this.historyChart) this.historyChart.destroy();
                
                // Generate mock historical data
                const dataPoints = 20;
                const baseLatency = hop.avgLatency;
                const data = [];
                
                for (let i = 0; i < dataPoints; i++) {
                    const timeOffset = -i * 3 * 60 * 1000; // 3 minute intervals
                    const timestamp = new Date(new Date().getTime() + timeOffset);
                    const variation = Math.random() * 15;
                    
                    data.push({
                        timestamp: timestamp,
                        avgLatency: baseLatency + (Math.random() * 10 - 5),
                        minLatency: baseLatency - 2 + (Math.random() * 3),
                        maxLatency: baseLatency + 5 + (Math.random() * 10),
                        packetLoss: Math.random() > 0.7 ? Math.floor(Math.random() * 20) : 0
                    });
                }
                
                const labels = data.map(d => d.timestamp.toLocaleTimeString());
                const avgData = data.map(d => d.avgLatency);
                const minData = data.map(d => d.minLatency);
                const maxData = data.map(d => d.maxLatency);
                const lossData = data.map(d => d.packetLoss);

                this.historyChart = new Chart(ctx, {
                    type: 'line',
                    data: {
                        labels: labels,
                        datasets: [
                            {
                                label: 'Avg Latency (ms)',
                                data: avgData,
                                borderColor: '#3498db',
                                backgroundColor: 'rgba(52, 152, 219, 0.1)',
                                tension: 0.1,
                                fill: true
                            },
                            {
                                label: 'Min Latency (ms)',
                                data: minData,
                                borderColor: '#2ecc71',
                                backgroundColor: 'transparent',
                                borderDash: [5, 5],
                                tension: 0.1
                            },
                            {
                                label: 'Max Latency (ms)',
                                data: maxData,
                                borderColor: '#e74c3c',
                                backgroundColor: 'transparent',
                                borderDash: [5, 5],
                                tension: 0.1
                            },
                            {
                                label: 'Packet Loss (%)',
                                data: lossData,
                                borderColor: '#f39c12',
                                backgroundColor: 'transparent',
                                borderWidth: 1,
                                yAxisID: 'y1',
                                tension: 0.1
                            }
                        ]
                    },
                    options: {
                        responsive: true,
                        interaction: {
                            mode: 'index',
                            intersect: false,
                        },
                        scales: {
                            y: {
                                type: 'linear',
                                display: true,
                                position: 'left',
                                title: {
                                    display: true,
                                    text: 'Latency (ms)'
                                }
                            },
                            y1: {
                                type: 'linear',
                                display: true,
                                position: 'right',
                                grid: {
                                    drawOnChartArea: false,
                                },
                                title: {
                                    display: true,
                                    text: 'Packet Loss (%)'
                                },
                                min: 0,
                                max: 100
                            }
                        }
                    }
                });
            }

            resizeCanvas() {
                this.canvas.width = this.canvas.clientWidth;
                this.canvas.height = this.canvas.clientHeight;
                this.draw();
            }

            addRoute(routeData) {
                this.routes.push(routeData);
                
                // Store historical data
                routeData.hops.forEach(hop => {
                    if (!this.historicalData[hop.ipAddress]) {
                        this.historicalData[hop.ipAddress] = [];
                    }
                    this.historicalData[hop.ipAddress].push({
                        timestamp: routeData.timestamp,
                        avgLatency: hop.avgLatency,
                        minLatency: hop.minLatency,
                        maxLatency: hop.maxLatency,
                        packetLoss: hop.packetLoss
                    });
                });
                
                this.draw();
                this.updateHopTable();
                this.updateTrendCharts();
            }

            draw() {
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                
                if (this.routes.length === 0) return;
                
                this.drawAxes();
                this.drawRoutes();
                this.drawSelectedHop();
            }

            drawSelectedHop() {
                if (!this.selectedHop) return;
                
                const route = this.routes[this.routes.length - 1];
                const hop = route.hops.find(h => h.ipAddress === this.selectedHop.ipAddress);
                if (!hop) return;
                
                const x = 50 + (hop.number * 80);
                const y = this.canvas.height - 50 - (hop.avgLatency * 3);
                
                this.ctx.strokeStyle = '#000';
                this.ctx.lineWidth = 2;
                this.ctx.beginPath();
                this.ctx.arc(x, y, 12, 0, Math.PI * 2);
                this.ctx.stroke();
            }

            drawAxes() {
                // Draw grid lines
                this.ctx.strokeStyle = '#eee';
                this.ctx.lineWidth = 1;
                
                // Horizontal grid lines (latency)
                const gridSteps = 10;
                const gridHeight = this.canvas.height - 80;
                const stepSize = gridHeight / gridSteps;
                
                for (let i = 0; i <= gridSteps; i++) {
                    const y = this.canvas.height - 50 - (i * stepSize);
                    this.ctx.beginPath();
                    this.ctx.moveTo(50, y);
                    this.ctx.lineTo(this.canvas.width - 30, y);
                    this.ctx.stroke();
                    
                    // Label
                    this.ctx.fillStyle = '#666';
                    this.ctx.font = '10px Arial';
                    this.ctx.textAlign = 'right';
                    this.ctx.fillText((i * (100/gridSteps)).toFixed(0), 45, y + 4);
                }
                
                // Vertical axis
                this.ctx.strokeStyle = '#333';
                this.ctx.lineWidth = 1;
                this.ctx.beginPath();
                this.ctx.moveTo(50, 30);
                this.ctx.lineTo(50, this.canvas.height - 50);
                this.ctx.lineTo(this.canvas.width - 30, this.canvas.height - 50);
                this.ctx.stroke();
                
                // Axis labels
                this.ctx.fillStyle = '#333';
                this.ctx.font = '12px Arial';
                this.ctx.textAlign = 'center';
                this.ctx.fillText('Hops', this.canvas.width / 2, this.canvas.height - 20);
                
                this.ctx.save();
                this.ctx.translate(20, this.canvas.height / 2);
                this.ctx.rotate(-Math.PI / 2);
                this.ctx.textAlign = 'center';
                this.ctx.fillText('Latency (ms)', 0, 0);
                this.ctx.restore();
            }

            drawRoutes() {
                if (this.routes.length === 0) return;
                
                const latestRoute = this.routes[this.routes.length - 1];
                
                this.ctx.strokeStyle = '#3498db';
                this.ctx.lineWidth = 2;
                this.ctx.beginPath();
                
                latestRoute.hops.forEach((hop, hopIdx) => {
                    const x = 50 + (hop.number * 80);
                    const y = this.canvas.height - 50 - (hop.avgLatency * 3);
                    
                    if (hopIdx === 0) {
                        this.ctx.moveTo(x, y);
                    } else {
                        this.ctx.lineTo(x, y);
                    }
                    
                    // Draw hop point
                    this.ctx.fillStyle = this.getHopColor(hop);
                    this.ctx.beginPath();
                    this.ctx.arc(x, y, 5, 0, Math.PI * 2);
                    this.ctx.fill();
                    
                    // Draw min/max indicators
                    this.ctx.strokeStyle = '#3498db80';
                    this.ctx.beginPath();
                    this.ctx.moveTo(x, this.canvas.height - 50 - (hop.minLatency * 3));
                    this.ctx.lineTo(x, this.canvas.height - 50 - (hop.maxLatency * 3));
                    this.ctx.stroke();
                    
                    // Draw hop number
                    this.ctx.fillStyle = '#333';
                    this.ctx.font = '10px Arial';
                    this.ctx.textAlign = 'center';
                    this.ctx.fillText(hop.number.toString(), x, this.canvas.height - 35);
                    
                    this.ctx.strokeStyle = '#3498db';
                });
                
                this.ctx.stroke();
            }

            getHopColor(hop) {
                if (hop.packetLoss > 20) return '#e74c3c';
                if (hop.packetLoss > 5) return '#f39c12';
                return '#2ecc71';
            }

            updateHopTable() {
                if (this.routes.length === 0) return;
                
                const latestRoute = this.routes[this.routes.length - 1];
                const tableBody = document.getElementById('hopTable');
                tableBody.innerHTML = '';
                
                latestRoute.hops.forEach(hop => {
                    const row = document.createElement('tr');
                    row.className = 'hop-row';
                    if (this.selectedHop && hop.ipAddress === this.selectedHop.ipAddress) {
                        row.classList.add('selected-hop');
                    }
                    
                    row.innerHTML = `
                        <td>${hop.number}</td>
                        <td>${hop.ipAddress}</td>
                        <td><span class="badge badge-latency">${hop.avgLatency.toFixed(2)} ms</span></td>
                        <td><span class="badge badge-loss">${hop.packetLoss.toFixed(2)}%</span></td>
                    `;
                    
                    row.addEventListener('click', () => {
                        this.selectedHop = hop;
                        this.showHopDetails(hop);
                        this.draw();
                        document.querySelectorAll('.hop-row').forEach(r => r.classList.remove('selected-hop'));
                        row.classList.add('selected-hop');
                    });
                    
                    tableBody.appendChild(row);
                });
            }

            updateTrendCharts() {
                if (this.routes.length < 3) return;
                
                const latestRoute = this.routes[this.routes.length - 1];
                const previousRoute = this.routes[this.routes.length - 2];
                const oldestRoute = this.routes[this.routes.length - 3];
                
                // Update latency chart
                const latencyCtx = document.getElementById('latencyChart').getContext('2d');
                if (this.latencyChart) this.latencyChart.destroy();
                
                const labels = latestRoute.hops.map(h => `Hop ${h.number}`);
                const latestData = latestRoute.hops.map(h => h.avgLatency);
                const previousData = previousRoute.hops.map(h => h.avgLatency);
                const oldestData = oldestRoute.hops.map(h => h.avgLatency);
                
                this.latencyChart = new Chart(latencyCtx, {
                    type: 'line',
                    data: {
                        labels: labels,
                        datasets: [
                            {
                                label: 'Current',
                                data: latestData,
                                borderColor: '#3498db',
                                backgroundColor: 'rgba(52, 152, 219, 0.1)',
                                tension: 0.1,
                                fill: true
                            },
                            {
                                label: 'Previous',
                                data: previousData,
                                borderColor: '#e74c3c',
                                backgroundColor: 'rgba(231, 76, 60, 0.1)',
                                tension: 0.1,
                                borderDash: [5, 5]
                            },
                            {
                                label: 'Older',
                                data: oldestData,
                                borderColor: '#95a5a6',
                                backgroundColor: 'transparent',
                                tension: 0.1,
                                borderDash: [5, 5]
                            }
                        ]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Latency Comparison'
                            }
                        },
                        scales: {
                            y: {
                                beginAtZero: true,
                                title: {
                                    display: true,
                                    text: 'Latency (ms)'
                                }
                            }
                        }
                    }
                });
                
                // Update loss chart
                const lossCtx = document.getElementById('lossChart').getContext('2d');
                if (this.lossChart) this.lossChart.destroy();
                
                const lossData = latestRoute.hops.map(h => h.packetLoss);
                
                this.lossChart = new Chart(lossCtx, {
                    type: 'bar',
                    data: {
                        labels: labels,
                        datasets: [{
                            label: 'Packet Loss',
                            data: lossData,
                            backgroundColor: lossData.map(loss => 
                                loss > 20 ? '#e74c3c' : 
                                loss > 5 ? '#f39c12' : '#2ecc71'
                            ),
                            borderColor: '#fff',
                            borderWidth: 1
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Packet Loss by Hop'
                            }
                        },
                        scales: {
                            y: {
                                beginAtZero: true,
                                max: 100,
                                title: {
                                    display: true,
                                    text: 'Packet Loss (%)'
                                }
                            }
                        }
                    }
                });
            }
        }

        // Initialize the application
        document.addEventListener('DOMContentLoaded', () => {
            const visualizer = new TracerouteVisualizer('tracerouteCanvas');
            
            // Timeframe selector
            document.querySelectorAll('.timeframe-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    document.querySelectorAll('.timeframe-btn').forEach(b => b.classList.remove('active'));
                    btn.classList.add('active');
                    visualizer.timeframe = btn.dataset.timeframe;
                });
            });
            
            // Simulate periodic updates
            setInterval(() => {
                visualizer.addRoute(generateMockRoute());
            }, 5000);
            
            // Initial data
            visualizer.addRoute(generateMockRoute());
            setTimeout(() => visualizer.addRoute(generateMockRoute()), 1000);
            setTimeout(() => visualizer.addRoute(generateMockRoute()), 2000);
        });
    </script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

This implementation includes all the requested features:

  1. Continuous Traceroute Monitoring:

    • Simulates traceroutes every 5 seconds with random data
    • Displays live results in a table with Hop, IP Address, Avg Latency, and Packet Loss
  2. Dynamic Visualization:

    • Interactive canvas showing the traceroute path
    • Color-coded hops based on packet loss
    • Min/Max latency indicators for each hop
  3. Historical Analysis:

    • Timeframe selector (5m, 1h, 4h, 8h, 24h)
    • Detailed hop history modal with latency trends
    • Comparison charts showing current vs previous routes
  4. Interactive Features:

    • Click on hops in the visualization or table to see detailed information
    • Responsive design that works on different screen sizes
    • Color-coded indicators for connection quality
  5. Additional Features:

    • Simulated location data for each hop
    • Connection status indicator
    • Clean, modern UI with Bootstrap 5

The implementation is pure HTML/CSS/JavaScript with no backend requirements, using Chart.js for the graphs and Bootstrap for the UI components.*

multi ping host

Here's a complete multi-host ping monitoring solution with HTML, CSS, and JavaScript:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Multi-Host Ping Monitor</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #f8f9fa;
        }
        .card {
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
            margin-bottom: 20px;
            border: none;
        }
        .card-header {
            background-color: #fff;
            border-bottom: 1px solid rgba(0, 0, 0, 0.05);
            font-weight: 600;
        }
        .host-row {
            transition: all 0.2s;
        }
        .host-row:hover {
            background-color: #f8f9fa;
        }
        .status-indicator {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            display: inline-block;
            margin-right: 8px;
        }
        .status-up {
            background-color: #28a745;
        }
        .status-warning {
            background-color: #ffc107;
        }
        .status-down {
            background-color: #dc3545;
        }
        .ping-value {
            font-family: 'Courier New', monospace;
            font-weight: bold;
        }
        .ping-good {
            color: #28a745;
        }
        .ping-medium {
            color: #ffc107;
        }
        .ping-bad {
            color: #dc3545;
        }
        .chart-container {
            position: relative;
            height: 200px;
        }
        .add-host-form {
            background-color: #f8f9fa;
            border-radius: 8px;
            padding: 20px;
            margin-bottom: 20px;
        }
        .timeframe-btn.active {
            background-color: #0d6efd;
            color: white;
        }
    </style>
</head>
<body>
    <div class="container-fluid py-4">
        <div class="row mb-4">
            <div class="col">
                <h1 class="display-6">Multi-Host Ping Monitor</h1>
                <p class="text-muted">Real-time ping monitoring for multiple hosts</p>
            </div>
        </div>

        <div class="row">
            <div class="col-md-12">
                <div class="add-host-form">
                    <div class="row">
                        <div class="col-md-6">
                            <div class="input-group mb-3">
                                <input type="text" id="hostInput" class="form-control" placeholder="Enter hostname or IP" aria-label="Host to ping">
                                <button class="btn btn-primary" type="button" id="addHostBtn">Add Host</button>
                            </div>
                        </div>
                        <div class="col-md-6">
                            <div class="btn-group btn-group-sm timeframe-selector float-end">
                                <button class="btn btn-outline-primary timeframe-btn active" data-timeframe="5m">5m</button>
                                <button class="btn btn-outline-primary timeframe-btn" data-timeframe="1h">1h</button>
                                <button class="btn btn-outline-primary timeframe-btn" data-timeframe="4h">4h</button>
                                <button class="btn btn-outline-primary timeframe-btn" data-timeframe="8h">8h</button>
                                <button class="btn btn-outline-primary timeframe-btn" data-timeframe="24h">24h</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="row" id="hostCardsContainer">
            <!-- Host cards will be added here dynamically -->
        </div>

        <div class="row mt-4">
            <div class="col-md-12">
                <div class="card">
                    <div class="card-header">
                        <h5>Summary Dashboard</h5>
                    </div>
                    <div class="card-body">
                        <div class="chart-container">
                            <canvas id="summaryChart"></canvas>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Host Details Modal -->
    <div class="modal fade" id="hostDetailsModal" tabindex="-1">
        <div class="modal-dialog modal-lg">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="hostDetailsTitle">Host Details</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <div class="row mb-4">
                        <div class="col-md-6">
                            <div class="card">
                                <div class="card-header">Current Stats</div>
                                <div class="card-body">
                                    <table class="table table-sm">
                                        <tbody>
                                            <tr>
                                                <td>Host:</td>
                                                <td id="detail-host"></td>
                                            </tr>
                                            <tr>
                                                <td>Status:</td>
                                                <td id="detail-status"></td>
                                            </tr>
                                            <tr>
                                                <td>Current Ping:</td>
                                                <td id="detail-current-ping"></td>
                                            </tr>
                                            <tr>
                                                <td>Avg Ping:</td>
                                                <td id="detail-avg-ping"></td>
                                            </tr>
                                            <tr>
                                                <td>Min Ping:</td>
                                                <td id="detail-min-ping"></td>
                                            </tr>
                                            <tr>
                                                <td>Max Ping:</td>
                                                <td id="detail-max-ping"></td>
                                            </tr>
                                            <tr>
                                                <td>Packet Loss:</td>
                                                <td id="detail-packet-loss"></td>
                                            </tr>
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                        </div>
                        <div class="col-md-6">
                            <div class="card">
                                <div class="card-header">Location Info</div>
                                <div class="card-body">
                                    <div id="hostLocation">
                                        <p class="text-muted">Location data loading...</p>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="chart-container" style="height: 300px;">
                        <canvas id="hostHistoryChart"></canvas>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        // Main application class
        class MultiPingMonitor {
            constructor() {
                this.hosts = {};
                this.timeframe = '5m';
                this.summaryChart = null;
                this.initialize();
            }

            initialize() {
                // Load saved hosts from localStorage
                this.loadHosts();
                
                // Set up event listeners
                document.getElementById('addHostBtn').addEventListener('click', () => this.addHost());
                document.getElementById('hostInput').addEventListener('keypress', (e) => {
                    if (e.key === 'Enter') this.addHost();
                });
                
                // Timeframe selector
                document.querySelectorAll('.timeframe-btn').forEach(btn => {
                    btn.addEventListener('click', () => {
                        document.querySelectorAll('.timeframe-btn').forEach(b => b.classList.remove('active'));
                        btn.classList.add('active');
                        this.timeframe = btn.dataset.timeframe;
                        this.updateAllCharts();
                    });
                });
                
                // Start monitoring
                this.startMonitoring();
                
                // Initial render
                this.renderHostCards();
                this.renderSummaryChart();
            }

            loadHosts() {
                const savedHosts = JSON.parse(localStorage.getItem('pingMonitorHosts') || '{}');
                for (const host in savedHosts) {
                    this.hosts[host] = savedHosts[host];
                }
            }

            saveHosts() {
                localStorage.setItem('pingMonitorHosts', JSON.stringify(this.hosts));
            }

            addHost() {
                const hostInput = document.getElementById('hostInput');
                const host = hostInput.value.trim();
                
                if (!host) return;
                if (this.hosts[host]) {
                    alert('This host is already being monitored!');
                    return;
                }
                
                // Add new host
                this.hosts[host] = {
                    history: [],
                    currentPing: null,
                    avgPing: null,
                    minPing: null,
                    maxPing: null,
                    packetLoss: 0,
                    status: 'unknown',
                    lastUpdated: null
                };
                
                // Save and render
                this.saveHosts();
                this.renderHostCards();
                hostInput.value = '';
                
                // Immediately ping the new host
                this.pingHost(host);
            }

            removeHost(host) {
                delete this.hosts[host];
                this.saveHosts();
                this.renderHostCards();
                this.renderSummaryChart();
            }

            startMonitoring() {
                // Ping all hosts immediately
                for (const host in this.hosts) {
                    this.pingHost(host);
                }
                
                // Then set up interval for continuous monitoring
                setInterval(() => {
                    for (const host in this.hosts) {
                        this.pingHost(host);
                    }
                }, 5000); // Ping every 5 seconds
            }

            pingHost(host) {
                // In a real app, this would make an actual ping request to a backend
                // For this demo, we'll simulate ping responses
                
                const now = new Date();
                let pingTime;
                let status;
                
                // Simulate different behaviors
                if (Math.random() > 0.95) {
                    // 5% chance of timeout (no response)
                    pingTime = null;
                    status = 'down';
                } else {
                    // Random ping time between 5ms and 300ms
                    pingTime = 5 + Math.random() * 295;
                    
                    // Occasionally add spikes
                    if (Math.random() > 0.9) {
                        pingTime += 100 + Math.random() * 400;
                    }
                    
                    // Determine status
                    if (pingTime > 200) {
                        status = 'warning';
                    } else {
                        status = 'up';
                    }
                }
                
                // Update host data
                const hostData = this.hosts[host];
                const historyEntry = {
                    timestamp: now,
                    ping: pingTime,
                    status: status
                };
                
                hostData.history.push(historyEntry);
                hostData.currentPing = pingTime;
                hostData.status = status;
                hostData.lastUpdated = now;
                
                // Keep only the last 1000 entries to prevent memory issues
                if (hostData.history.length > 1000) {
                    hostData.history = hostData.history.slice(-1000);
                }
                
                // Calculate stats
                this.calculateHostStats(host);
                
                // Update UI
                this.updateHostCard(host);
                this.renderSummaryChart();
            }

            calculateHostStats(host) {
                const hostData = this.hosts[host];
                const validPings = hostData.history.filter(entry => entry.ping !== null).map(entry => entry.ping);
                
                if (validPings.length > 0) {
                    hostData.avgPing = validPings.reduce((a, b) => a + b, 0) / validPings.length;
                    hostData.minPing = Math.min(...validPings);
                    hostData.maxPing = Math.max(...validPings);
                } else {
                    hostData.avgPing = null;
                    hostData.minPing = null;
                    hostData.maxPing = null;
                }
                
                // Calculate packet loss (percentage of null pings in history)
                const totalPings = hostData.history.length;
                const failedPings = hostData.history.filter(entry => entry.ping === null).length;
                hostData.packetLoss = (failedPings / totalPings) * 100;
            }

            renderHostCards() {
                const container = document.getElementById('hostCardsContainer');
                container.innerHTML = '';
                
                for (const host in this.hosts) {
                    const hostData = this.hosts[host];
                    const card = document.createElement('div');
                    card.className = 'col-md-4';
                    card.innerHTML = `
                        <div class="card host-card" data-host="${host}">
                            <div class="card-header d-flex justify-content-between align-items-center">
                                <div>
                                    <span class="status-indicator status-${hostData.status}"></span>
                                    <strong>${host}</strong>
                                </div>
                                <button class="btn btn-sm btn-outline-danger remove-host-btn">×</button>
                            </div>
                            <div class="card-body">
                                <div class="row">
                                    <div class="col-6">
                                        <div class="mb-2">
                                            <small class="text-muted">Current</small>
                                            <div class="ping-value ping-${this.getPingClass(hostData.currentPing)}">
                                                ${hostData.currentPing ? hostData.currentPing.toFixed(2) + ' ms' : 'Timeout'}
                                            </div>
                                        </div>
                                        <div class="mb-2">
                                            <small class="text-muted">Avg</small>
                                            <div>${hostData.avgPing ? hostData.avgPing.toFixed(2) + ' ms' : 'N/A'}</div>
                                        </div>
                                    </div>
                                    <div class="col-6">
                                        <div class="mb-2">
                                            <small class="text-muted">Min/Max</small>
                                            <div>${hostData.minPing ? hostData.minPing.toFixed(2) + ' / ' + hostData.maxPing.toFixed(2) + ' ms' : 'N/A'}</div>
                                        </div>
                                        <div class="mb-2">
                                            <small class="text-muted">Loss</small>
                                            <div>${hostData.packetLoss.toFixed(2)}%</div>
                                        </div>
                                    </div>
                                </div>
                                <div class="chart-container mt-2" style="height: 80px;">
                                    <canvas class="host-chart"></canvas>
                                </div>
                            </div>
                        </div>
                    `;
                    container.appendChild(card);
                    
                    // Add event listeners
                    card.querySelector('.host-card').addEventListener('click', () => this.showHostDetails(host));
                    card.querySelector('.remove-host-btn').addEventListener('click', (e) => {
                        e.stopPropagation();
                        this.removeHost(host);
                    });
                    
                    // Render chart for this host
                    this.renderHostChart(host);
                }
            }

            updateHostCard(host) {
                const card = document.querySelector(`.host-card[data-host="${host}"]`);
                if (!card) return;
                
                const hostData = this.hosts[host];
                
                // Update status indicator
                const statusIndicator = card.querySelector('.status-indicator');
                statusIndicator.className = `status-indicator status-${hostData.status}`;
                
                // Update ping values
                const currentPingEl = card.querySelector('.ping-value');
                currentPingEl.textContent = hostData.currentPing ? hostData.currentPing.toFixed(2) + ' ms' : 'Timeout';
                currentPingEl.className = `ping-value ping-${this.getPingClass(hostData.currentPing)}`;
                
                card.querySelector('div:nth-child(1) > div:nth-child(2)').textContent = 
                    hostData.avgPing ? hostData.avgPing.toFixed(2) + ' ms' : 'N/A';
                
                card.querySelector('div:nth-child(2) > div:nth-child(1)').textContent = 
                    hostData.minPing ? hostData.minPing.toFixed(2) + ' / ' + hostData.maxPing.toFixed(2) + ' ms' : 'N/A';
                
                card.querySelector('div:nth-child(2) > div:nth-child(2)').textContent = 
                    hostData.packetLoss.toFixed(2) + '%';
                
                // Update chart
                this.renderHostChart(host);
            }

            renderHostChart(host) {
                const card = document.querySelector(`.host-card[data-host="${host}"]`);
                if (!card) return;
                
                const canvas = card.querySelector('.host-chart');
                const ctx = canvas.getContext('2d');
                const hostData = this.hosts[host];
                
                // Destroy existing chart if it exists
                if (hostData.chart) {
                    hostData.chart.destroy();
                }
                
                // Get data for current timeframe
                const cutoff = this.getCutoffTime();
                const recentData = hostData.history.filter(entry => entry.timestamp >= cutoff);
                
                // Prepare chart data
                const labels = recentData.map(entry => entry.timestamp.toLocaleTimeString());
                const data = recentData.map(entry => entry.ping);
                
                hostData.chart = new Chart(ctx, {
                    type: 'line',
                    data: {
                        labels: labels,
                        datasets: [{
                            data: data,
                            borderColor: '#0d6efd',
                            backgroundColor: 'rgba(13, 110, 253, 0.1)',
                            borderWidth: 1,
                            pointRadius: 0,
                            fill: true,
                            tension: 0.1
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: {
                                display: false
                            },
                            tooltip: {
                                callbacks: {
                                    label: (context) => {
                                        return context.parsed.y ? context.parsed.y.toFixed(2) + ' ms' : 'Timeout';
                                    }
                                }
                            }
                        },
                        scales: {
                            x: {
                                display: false
                            },
                            y: {
                                display: false,
                                suggestedMin: 0,
                                suggestedMax: 300
                            }
                        },
                        interaction: {
                            intersect: false,
                            mode: 'index'
                        }
                    }
                });
            }

            renderSummaryChart() {
                const ctx = document.getElementById('summaryChart').getContext('2d');
                
                // Destroy existing chart if it exists
                if (this.summaryChart) {
                    this.summaryChart.destroy();
                }
                
                // Prepare data
                const hosts = Object.keys(this.hosts);
                if (hosts.length === 0) return;
                
                const cutoff = this.getCutoffTime();
                const datasets = [];
                
                hosts.forEach(host => {
                    const hostData = this.hosts[host];
                    const recentData = hostData.history.filter(entry => entry.timestamp >= cutoff);
                    
                    datasets.push({
                        label: host,
                        data: recentData.map(entry => ({
                            x: entry.timestamp,
                            y: entry.ping
                        })),
                        borderColor: this.getHostColor(host, hosts.length),
                        backgroundColor: 'transparent',
                        borderWidth: 1,
                        pointRadius: 0,
                        tension: 0.1
                    });
                });
                
                this.summaryChart = new Chart(ctx, {
                    type: 'line',
                    data: {
                        datasets: datasets
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            tooltip: {
                                callbacks: {
                                    label: (context) => {
                                        return `${context.dataset.label}: ${context.parsed.y ? context.parsed.y.toFixed(2) + ' ms' : 'Timeout'}`;
                                    }
                                }
                            }
                        },
                        scales: {
                            x: {
                                type: 'time',
                                time: {
                                    tooltipFormat: 'HH:mm:ss',
                                    displayFormats: {
                                        minute: 'HH:mm'
                                    }
                                },
                                title: {
                                    display: true,
                                    text: 'Time'
                                }
                            },
                            y: {
                                title: {
                                    display: true,
                                    text: 'Ping (ms)'
                                },
                                suggestedMin: 0,
                                suggestedMax: 300
                            }
                        }
                    }
                });
            }

            updateAllCharts() {
                for (const host in this.hosts) {
                    this.renderHostChart(host);
                }
                this.renderSummaryChart();
            }

            showHostDetails(host) {
                const hostData = this.hosts[host];
                const modal = new bootstrap.Modal(document.getElementById('hostDetailsModal'));
                
                // Set host details
                document.getElementById('hostDetailsTitle').textContent = host;
                document.getElementById('detail-host').textContent = host;
                document.getElementById('detail-status').innerHTML = `
                    <span class="status-indicator status-${hostData.status}"></span>
                    ${this.getStatusText(hostData.status)}
                `;
                document.getElementById('detail-current-ping').textContent = 
                    hostData.currentPing ? hostData.currentPing.toFixed(2) + ' ms' : 'Timeout';
                document.getElementById('detail-avg-ping').textContent = 
                    hostData.avgPing ? hostData.avgPing.toFixed(2) + ' ms' : 'N/A';
                document.getElementById('detail-min-ping').textContent = 
                    hostData.minPing ? hostData.minPing.toFixed(2) + ' ms' : 'N/A';
                document.getElementById('detail-max-ping').textContent = 
                    hostData.maxPing ? hostData.maxPing.toFixed(2) + ' ms' : 'N/A';
                document.getElementById('detail-packet-loss').textContent = 
                    hostData.packetLoss.toFixed(2) + '%';
                
                // Simulate location lookup
                setTimeout(() => {
                    document.getElementById('hostLocation').innerHTML = `
                        <p><strong>IP:</strong> ${this.fakeIP(host)}</p>
                        <p><strong>Country:</strong> ${this.fakeCountry()}</p>
                        <p><strong>City:</strong> ${this.fakeCity()}</p>
                        <p><strong>ISP:</strong> ${this.fakeISP()}</p>
                    `;
                }, 500);
                
                // Render history chart
                this.renderHostHistoryChart(host);
                modal.show();
            }

            renderHostHistoryChart(host) {
                const ctx = document.getElementById('hostHistoryChart').getContext('2d');
                const hostData = this.hosts[host];
                const cutoff = this.getCutoffTime();
                const recentData = hostData.history.filter(entry => entry.timestamp >= cutoff);
                
                // Destroy existing chart if it exists
                if (hostData.historyChart) {
                    hostData.historyChart.destroy();
                }
                
                const labels = recentData.map(entry => entry.timestamp);
                const pingData = recentData.map(entry => entry.ping);
                
                hostData.historyChart = new Chart(ctx, {
                    type: 'line',
                    data: {
                        labels: labels,
                        datasets: [
                            {
                                label: 'Ping (ms)',
                                data: pingData,
                                borderColor: '#0d6efd',
                                backgroundColor: 'rgba(13, 110, 253, 0.1)',
                                borderWidth: 1,
                                tension: 0.1,
                                fill: true
                            }
                        ]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            tooltip: {
                                callbacks: {
                                    label: (context) => {
                                        return context.parsed.y ? context.parsed.y.toFixed(2) + ' ms' : 'Timeout';
                                    }
                                }
                            }
                        },
                        scales: {
                            x: {
                                type: 'time',
                                time: {
                                    tooltipFormat: 'HH:mm:ss',
                                    displayFormats: {
                                        minute: 'HH:mm'
                                    }
                                },
                                title: {
                                    display: true,
                                    text: 'Time'
                                }
                            },
                            y: {
                                title: {
                                    display: true,
                                    text: 'Ping (ms)'
                                },
                                suggestedMin: 0,
                                suggestedMax: 300
                            }
                        }
                    }
                });
            }

            getCutoffTime() {
                const now = new Date();
                switch (this.timeframe) {
                    case '5m': return new Date(now.getTime() - 5 * 60 * 1000);
                    case '1h': return new Date(now.getTime() - 60 * 60 * 1000);
                    case '4h': return new Date(now.getTime() - 4 * 60 * 60 * 1000);
                    case '8h': return new Date(now.getTime() - 8 * 60 * 60 * 1000);
                    case '24h': return new Date(now.getTime() - 24 * 60 * 60 * 1000);
                    default: return new Date(now.getTime() - 5 * 60 * 1000);
                }
            }

            getPingClass(ping) {
                if (ping === null) return 'bad';
                if (ping > 200) return 'bad';
                if (ping > 100) return 'medium';
                return 'good';
            }

            getStatusText(status) {
                switch (status) {
                    case 'up': return 'Online';
                    case 'warning': return 'High Latency';
                    case 'down': return 'Offline';
                    default: return 'Unknown';
                }
            }

            getHostColor(host, totalHosts) {
                // Generate consistent colors for each host
                const colors = [
                    '#0d6efd', '#6610f2', '#6f42c1', '#d63384', '#dc3545',
                    '#fd7e14', '#ffc107', '#198754', '#20c997', '#0dcaf0'
                ];
                
                // Simple hash to get consistent color for each host
                let hash = 0;
                for (let i = 0; i < host.length; i++) {
                    hash = host.charCodeAt(i) + ((hash << 5) - hash);
                }
                
                return colors[Math.abs(hash) % colors.length];
            }

            // Helper functions for fake data
            fakeIP(host) {
                if (host.match(/^\d+\.\d+\.\d+\.\d+$/)) return host;
                return `192.168.${Math.floor(Math.random() * 254)}.${Math.floor(Math.random() * 254)}`;
            }

            fakeCountry() {
                const countries = ['United States', 'Germany', 'Japan', 'United Kingdom', 'Canada', 'Australia'];
                return countries[Math.floor(Math.random() * countries.length)];
            }

            fakeCity() {
                const cities = ['New York', 'London', 'Tokyo', 'Berlin', 'Sydney', 'Toronto'];
                return cities[Math.floor(Math.random() * cities.length)];
            }

            fakeISP() {
                const isps = ['Comcast', 'AT&T', 'Verizon', 'Deutsche Telekom', 'NTT', 'British Telecom'];
                return isps[Math.floor(Math.random() * isps.length)];
            }
        }

        // Initialize the application when DOM is loaded
        document.addEventListener('DOMContentLoaded', () => {
            new MultiPingMonitor();
        });
    </script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

This multi-host ping monitor includes:

  1. Multi-Host Monitoring:

    • Add any number of hosts (IPs or hostnames) to monitor
    • Each host is pinged every 5 seconds
    • Hosts persist between page reloads (using localStorage)
  2. Real-Time Visualization:

    • Color-coded status indicators (green=good, yellow=warning, red=down)
    • Mini charts showing ping history for each host
    • Detailed summary chart comparing all hosts
  3. Comprehensive Statistics:

    • Current, average, minimum, and maximum ping times
    • Packet loss percentage calculation
    • Status history tracking
  4. Interactive Features:

    • Click any host card to see detailed information
    • Timeframe selector (5m, 1h, 4h, 8h, 24h)
    • Remove hosts with one click
    • Responsive design that works on all screen sizes
  5. Detailed Host View:

    • Current stats table
    • Simulated location information
    • Full history chart with interactive tooltips
  6. Visual Design:

    • Clean, modern UI with Bootstrap 5
    • Color-coded ping values based on latency
    • Compact cards that show all essential information
    • Smooth animations and transitions

The implementation is pure client-side HTML/CSS/JavaScript with no backend requirements, using Chart.js for the graphs and Bootstrap for the UI components. In a production environment, you would replace the simulated ping responses with actual network requests to a backend service.*