document.addEventListener("DOMContentLoaded", () => { let table = null; const allPackets = []; const protoCounts = { TCP: 0, UDP: 0, DNS: 0, Other: 0 }; const srcCounts = {}; let protocolChart = null; // --- New variables for smoother, dynamic updates --- let uiUpdateScheduled = false; const UI_UPDATE_INTERVAL = 1000; // Update heavy visuals once per second function initChart() { const ctx = document.getElementById('protocolChart')?.getContext('2d'); if (!ctx) return; protocolChart = new Chart(ctx, { type: 'doughnut', data: { labels: ['TCP', 'UDP', 'DNS', 'Other'], datasets: [{ data: [0, 0, 0, 0], backgroundColor: ['#0d6efd', '#ffc107', '#dc3545', '#6f42c1'] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); } function initDataTable() { const cols = ["Time", "Source", "Destination", "Protocol", "Length"]; let headerRow = ''; cols.forEach(txt => headerRow += `${txt}`); headerRow += ''; $("#table-header").html(headerRow); table = $('#packet-table').DataTable({ deferRender: true, scroller: true, scrollY: "60vh", scrollCollapse: true, paging: true, lengthChange: false, info: true, order: [[0, 'desc']], language: { search: "", searchPlaceholder: "Search..." }, createdRow: function(row, data, dataIndex) { if (allPackets[dataIndex]) { $(row).data('packet', allPackets[dataIndex]); } } }); } function getProtocol(pkt) { if (pkt.proto === "DNS") return "DNS"; if (pkt.proto === 6) return "TCP"; if (pkt.proto === 17) return "UDP"; return "Other"; } // --- Main Update Logic --- // This function updates all the heavy visual elements function updateHeavyVisuals() { // Update KPI cards (except total, which is updated instantly) $('#count-TCP').text(protoCounts.TCP); $('#count-UDP').text(protoCounts.UDP); $('#count-DNS').text(protoCounts.DNS); if (protocolChart) { protocolChart.data.datasets[0].data = [protoCounts.TCP, protoCounts.UDP, protoCounts.DNS, protoCounts.Other]; protocolChart.update('none'); // 'none' for smooth animation } const topSources = Object.entries(srcCounts).sort(([, a], [, b]) => b - a).slice(0, 5); const ul = $("#top-sources").empty(); topSources.forEach(([ip, count]) => { $("
  • ").addClass("list-group-item d-flex justify-content-between align-items-center").html(`${ip} ${count}`).appendTo(ul); }); } // This function processes all historical data once on load function processHistoricalData(historicalPackets) { historicalPackets.forEach(pkt => { allPackets.push(pkt); const proto = getProtocol(pkt); if (protoCounts.hasOwnProperty(proto)) protoCounts[proto]++; srcCounts[pkt.src] = (srcCounts[pkt.src] || 0) + 1; }); const rowsToAdd = allPackets.map(p => { const time = new Date(p.timestamp * 1000).toLocaleTimeString(); return [time, p.src, p.dst, getProtocol(p), p.length || '-']; }); table.rows.add(rowsToAdd).draw(); $('#count-All').text(allPackets.length); updateHeavyVisuals(); // Update heavy visuals once after history is loaded } // --- INITIALIZATION AND DATA LOADING --- initChart(); initDataTable(); fetch("/api/packets") .then(r => r.ok ? r.json() : []) .then(processHistoricalData) .catch(err => console.error("Error loading history:", err)); // --- LIVE SOCKET.IO UPDATES --- const socket = io(); socket.on('new_packet', pkt => { // 1. Instantly update the in-memory data allPackets.push(pkt); const proto = getProtocol(pkt); if (protoCounts.hasOwnProperty(proto)) protoCounts[proto]++; srcCounts[pkt.src] = (srcCounts[pkt.src] || 0) + 1; // 2. Instantly update the total count and add the row to the table $('#count-All').text(allPackets.length); const time = new Date(pkt.timestamp * 1000).toLocaleTimeString(); // ** THIS IS THE BUG FIX ** // It was `p.length` before, which is undefined. It is now `pkt.length`. const rowData = [time, pkt.src, pkt.dst, getProtocol(pkt), pkt.length || '-']; table.row.add(rowData).draw(false); // 3. Schedule a throttled update for the heavy visuals if (!uiUpdateScheduled) { uiUpdateScheduled = true; setTimeout(() => { updateHeavyVisuals(); uiUpdateScheduled = false; }, UI_UPDATE_INTERVAL); } }); // --- EVENT HANDLERS --- $("#packet-table tbody").on("click", "tr", function(){ const pkt = $(this).data('packet'); if (!pkt) return; let html = ''; $("#packet-detail-body").html(html); new bootstrap.Modal($("#packetDetailModal")).show(); }); $("#download-btn").on("click", () => { if (!allPackets.length) { return alert("No packet data to download."); } const ws = XLSX.utils.json_to_sheet(allPackets); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "NetworkData"); const ts = new Date().toISOString().replace(/[:.]/g,"-"); const name = `network_data_${ts}.xlsx`; XLSX.writeFile(wb, name); }); });