File size: 5,585 Bytes
136c0f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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 = '<tr>';
    cols.forEach(txt => headerRow += `<th>${txt}</th>`);
    headerRow += '</tr>';
    $("#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]) => {
      $("<li>").addClass("list-group-item d-flex justify-content-between align-items-center").html(`${ip} <span class="badge bg-primary rounded-pill">${count}</span>`).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 = '<ul class="list-group">';
    Object.entries(pkt).forEach(([k,v])=> {
      html += `<li class="list-group-item"><strong>${k}:</strong> ${v}</li>`;
    });
    html += '</ul>';
    $("#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);
  });
});