summary history files

linux-firewall-log-analyser/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Linux Firewall Log Analyser</title>
  <style>
* {
  box-sizing: border-box;
}

body {
  font-family: Helvetica, Arial, sans-serif;
  line-height: 1.5;
  margin: 0;
  padding: 20px;
  background: #f5f5f5;
  color: #333;
}

.container {
  max-width: 1400px;
  margin: 0 auto;
  background: white;
  padding: 30px;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

h1 {
  margin: 0 0 20px 0;
  font-size: 24px;
  font-weight: 600;
}

h2 {
  font-size: 18px;
  margin: 30px 0 15px 0;
  font-weight: 600;
  border-bottom: 1px solid #ddd;
  padding-bottom: 8px;
}

.input-section {
  margin-bottom: 30px;
}

textarea {
  width: 100%;
  min-height: 200px;
  padding: 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-family: monospace;
  font-size: 16px;
  resize: vertical;
}

input[type="file"] {
  margin: 10px 0;
  font-size: 16px;
}

button {
  background: #0066cc;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  margin-right: 10px;
  margin-top: 10px;
}

button:hover {
  background: #0052a3;
}

button.secondary {
  background: #666;
}

button.secondary:hover {
  background: #555;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 15px;
  margin-bottom: 30px;
}

.stat-card {
  background: #f9f9f9;
  padding: 15px;
  border-radius: 4px;
  border-left: 4px solid #0066cc;
}

.stat-card.drop {
  border-left-color: #dc3545;
}

.stat-card.fwd {
  border-left-color: #ffc107;
}

.stat-card.in {
  border-left-color: #28a745;
}

.stat-label {
  font-size: 12px;
  color: #666;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.stat-value {
  font-size: 28px;
  font-weight: 600;
  margin-top: 5px;
}

.filters {
  background: #f9f9f9;
  padding: 15px;
  border-radius: 4px;
  margin-bottom: 30px;
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
  align-items: center;
}

.filters label {
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 5px;
}

.filters input[type="checkbox"] {
  margin: 0;
}

.filters input[type="text"] {
  padding: 6px 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 16px;
}

.charts-section {
  margin-bottom: 30px;
}

.chart-container {
  margin-bottom: 30px;
}

.chart-title {
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 10px;
  color: #555;
}

svg {
  width: 100%;
  height: auto;
  background: #fafafa;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
}

.bar {
  fill: #0066cc;
}

.bar.drop {
  fill: #dc3545;
}

.bar.fwd {
  fill: #ffc107;
}

.bar.in {
  fill: #28a745;
}

.bar:hover {
  opacity: 0.8;
}

.axis {
  stroke: #999;
  stroke-width: 1;
}

.axis-text {
  font-size: 11px;
  fill: #666;
}

.grid-line {
  stroke: #e0e0e0;
  stroke-width: 1;
}

.tables-section {
  margin-bottom: 30px;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 20px;
  font-size: 14px;
}

th, td {
  text-align: left;
  padding: 10px;
  border-bottom: 1px solid #e0e0e0;
}

th {
  background: #f5f5f5;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
}

th:hover {
  background: #eeeeee;
}

tr:hover {
  background: #f9f9f9;
}

.raw-log {
  background: #f5f5f5;
  padding: 15px;
  border-radius: 4px;
  font-family: monospace;
  font-size: 13px;
  max-height: 400px;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

.log-line {
  padding: 2px 0;
  border-bottom: 1px solid #e8e8e8;
}

.log-line.drop {
  background: rgba(220, 53, 69, 0.1);
  border-left: 3px solid #dc3545;
  padding-left: 5px;
}

.log-line.fwd {
  background: rgba(255, 193, 7, 0.1);
  border-left: 3px solid #ffc107;
  padding-left: 5px;
}

.log-line.in {
  background: rgba(40, 167, 69, 0.1);
  border-left: 3px solid #28a745;
  padding-left: 5px;
}

.port-scan-alert {
  background: #fff3cd;
  border: 1px solid #ffc107;
  color: #856404;
  padding: 10px 15px;
  border-radius: 4px;
  margin-bottom: 20px;
}

.hidden {
  display: none;
}
  </style>
</head>
<body>
  <div class="container">
    <h1>Linux Firewall Log Analyser</h1>

    <div class="input-section">
      <textarea id="logInput" placeholder="Paste firewall logs here...&#10;[3065201.841366] IN IN=eth1 OUT= MAC=... SRC=... DST=..."></textarea>
      <div>
        <input type="file" id="fileInput" accept=".log,.txt">
      </div>
      <button id="analyzeBtn">Analyse Logs</button>
      <button id="exportBtn" class="secondary hidden">Export Report</button>
      <button id="clearBtn" class="secondary">Clear</button>
    </div>

    <div id="resultsSection" class="hidden">
      <div id="portScanAlerts"></div>

      <div class="stats-grid">
        <div class="stat-card">
          <div class="stat-label">Total Events</div>
          <div class="stat-value" id="totalEvents">0</div>
        </div>
        <div class="stat-card drop">
          <div class="stat-label">DROP</div>
          <div class="stat-value" id="dropCount">0</div>
        </div>
        <div class="stat-card fwd">
          <div class="stat-label">FWD</div>
          <div class="stat-value" id="fwdCount">0</div>
        </div>
        <div class="stat-card in">
          <div class="stat-label">IN</div>
          <div class="stat-value" id="inCount">0</div>
        </div>
        <div class="stat-card">
          <div class="stat-label">Unique Sources</div>
          <div class="stat-value" id="uniqueSources">0</div>
        </div>
        <div class="stat-card">
          <div class="stat-label">Time Range</div>
          <div class="stat-value" id="timeRange" style="font-size: 18px;">-</div>
        </div>
      </div>

      <div class="filters">
        <label><input type="checkbox" id="filterDrop" checked> DROP</label>
        <label><input type="checkbox" id="filterFwd" checked> FWD</label>
        <label><input type="checkbox" id="filterIn" checked> IN</label>
        <label><input type="checkbox" id="filterOut" checked> OUT</label>
        <input type="text" id="ipFilter" placeholder="Filter by IP...">
        <input type="text" id="portFilter" placeholder="Filter by port...">
        <button id="applyFilters" style="margin-top: 0;">Apply Filters</button>
      </div>

      <div class="charts-section">
        <div class="chart-container">
          <div class="chart-title">Event Timeline (by kernel uptime)</div>
          <svg id="timelineChart" height="200"></svg>
        </div>

        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
          <div class="chart-container">
            <div class="chart-title">Top 10 Source IPs</div>
            <svg id="srcChart" height="300"></svg>
          </div>
          <div class="chart-container">
            <div class="chart-title">Top 10 Destination Ports</div>
            <svg id="dstPortChart" height="300"></svg>
          </div>
        </div>
      </div>

      <div class="tables-section">
        <h2>Top Source IPs</h2>
        <table id="srcTable">
          <thead>
            <tr>
              <th data-sort="ip">Source IP</th>
              <th data-sort="count">Events</th>
              <th data-sort="drop">DROP</th>
              <th data-sort="ports">Unique Ports</th>
            </tr>
          </thead>
          <tbody id="srcTableBody"></tbody>
        </table>

        <h2>Top Destination Ports</h2>
        <table id="portTable">
          <thead>
            <tr>
              <th data-sort="port">Port</th>
              <th data-sort="count">Events</th>
              <th data-sort="drop">DROP</th>
              <th data-sort="syn">SYN</th>
            </tr>
          </thead>
          <tbody id="portTableBody"></tbody>
        </table>

        <h2>Raw Log Entries</h2>
        <div class="raw-log" id="rawLog"></div>
      </div>
    </div>
  </div>

<script type="module">
let parsedData = [];
let filteredData = [];
let portScanThreshold = 10;
let timeWindow = 60;

const logInput = document.getElementById('logInput');
const fileInput = document.getElementById('fileInput');
const analyzeBtn = document.getElementById('analyzeBtn');
const exportBtn = document.getElementById('exportBtn');
const clearBtn = document.getElementById('clearBtn');
const resultsSection = document.getElementById('resultsSection');
const portScanAlerts = document.getElementById('portScanAlerts');

const filterDrop = document.getElementById('filterDrop');
const filterFwd = document.getElementById('filterFwd');
const filterIn = document.getElementById('filterIn');
const filterOut = document.getElementById('filterOut');
const ipFilter = document.getElementById('ipFilter');
const portFilter = document.getElementById('portFilter');
const applyFilters = document.getElementById('applyFilters');

function parseLogLine(line) {
  if (!line.trim()) return null;

  const match = line.match(/^\[([\d.]+)\]\s+(\w+)(?:\s+(INVALID))?\s+(.*)$/);
  if (!match) return null;

  const timestamp = parseFloat(match[1]);
  const action = match[2];
  const invalid = !!match[3];
  const rest = match[4];

  const entry = {
    timestamp,
    action,
    invalid,
    raw: line
  };

  const keyValueRegex = /(\w+)=([^\s]+)/g;
  let kv;
  while ((kv = keyValueRegex.exec(rest)) !== null) {
    const key = kv[1];
    const value = kv[2];

    if (key === 'SRC' || key === 'DST') {
      entry[key.toLowerCase()] = value;
    } else if (key === 'SPT' || key === 'DPT') {
      entry[key.toLowerCase()] = parseInt(value, 10);
    } else if (key === 'LEN' || key === 'TTL' || key === 'HOPLIMIT' || key === 'WINDOW') {
      entry[key.toLowerCase()] = parseInt(value, 10);
    } else if (key === 'IN' || key === 'OUT') {
      entry[`iface_${key.toLowerCase()}`] = value;
    } else if (key === 'MAC') {
      entry.mac = value;
    } else if (key === 'PROTO') {
      entry.proto = value;
    } else if (key === 'ID' || key === 'FLOWLBL') {
      entry.id = value;
    }
  }

  const flagMatch = rest.match(/(SYN|ACK|FIN|PSH|RST|URG)(?:\s+(SYN|ACK|FIN|PSH|RST|URG))*(?:\s+(SYN|ACK|FIN|PSH|RST|URG))*(?:\s+(SYN|ACK|FIN|PSH|RST|URG))*(?:\s+(SYN|ACK|FIN|PSH|RST|URG))*(?:\s+(SYN|ACK|FIN|PSH|RST|URG))*/);
  if (flagMatch) {
    entry.flags = rest.match(/\b(SYN|ACK|FIN|PSH|RST|URG)\b/g) || [];
  } else {
    entry.flags = [];
  }

  return entry;
}

function detectPortScans(data) {
  const scans = [];
  const bySource = {};

  data.forEach(entry => {
    if (!entry.src || !entry.dpt) return;

    if (!bySource[entry.src]) {
      bySource[entry.src] = [];
    }
    bySource[entry.src].push(entry);
  });

  Object.keys(bySource).forEach(src => {
    const entries = bySource[src].sort((a, b) => a.timestamp - b.timestamp);
    const windows = [];
    let currentWindow = [];

    entries.forEach(entry => {
      if (currentWindow.length === 0) {
        currentWindow.push(entry);
      } else {
        const timeDiff = entry.timestamp - currentWindow[0].timestamp;
        if (timeDiff <= timeWindow) {
          currentWindow.push(entry);
        } else {
          if (currentWindow.length >= portScanThreshold) {
            const uniquePorts = new Set(currentWindow.map(e => e.dpt)).size;
            if (uniquePorts >= portScanThreshold) {
              windows.push({
                src,
                count: currentWindow.length,
                ports: uniquePorts,
                start: currentWindow[0].timestamp,
                end: currentWindow[currentWindow.length - 1].timestamp
              });
            }
          }
          currentWindow = [entry];
        }
      }
    });

    if (currentWindow.length >= portScanThreshold) {
      const uniquePorts = new Set(currentWindow.map(e => e.dpt)).size;
      if (uniquePorts >= portScanThreshold) {
        windows.push({
          src,
          count: currentWindow.length,
          ports: uniquePorts,
          start: currentWindow[0].timestamp,
          end: currentWindow[currentWindow.length - 1].timestamp
        });
      }
    }

    scans.push(...windows);
  });

  return scans;
}

function analyzeData() {
  const text = logInput.value;
  const lines = text.split('\n');

  parsedData = lines.map(parseLogLine).filter(x => x !== null);
  filteredData = [...parsedData];

  updateStats();
  detectAndShowScans();
  updateCharts();
  updateTables();
  updateRawLog();

  resultsSection.classList.remove('hidden');
  exportBtn.classList.remove('hidden');
}

function updateStats() {
  const total = filteredData.length;
  const drops = filteredData.filter(e => e.action === 'DROP').length;
  const fwds = filteredData.filter(e => e.action === 'FWD').length;
  const ins = filteredData.filter(e => e.action === 'IN').length;
  const outs = filteredData.filter(e => e.action === 'OUT').length;

  const uniqueSrcs = new Set(filteredData.map(e => e.src).filter(Boolean)).size;

  let timeRange = '-';
  if (filteredData.length > 0) {
    const times = filteredData.map(e => e.timestamp);
    const min = Math.min(...times);
    const max = Math.max(...times);
    timeRange = `${(max - min).toFixed(1)}s`;
  }

  document.getElementById('totalEvents').textContent = total;
  document.getElementById('dropCount').textContent = drops;
  document.getElementById('fwdCount').textContent = fwds + outs;
  document.getElementById('inCount').textContent = ins;
  document.getElementById('uniqueSources').textContent = uniqueSrcs;
  document.getElementById('timeRange').textContent = timeRange;
}

function detectAndShowScans() {
  const scans = detectPortScans(filteredData);
  portScanAlerts.innerHTML = '';

  if (scans.length > 0) {
    scans.forEach(scan => {
      const div = document.createElement('div');
      div.className = 'port-scan-alert';
      div.textContent = `Potential port scan detected from ${scan.src}: ${scan.count} events targeting ${scan.ports} unique ports between [${scan.start.toFixed(3)}] and [${scan.end.toFixed(3)}]`;
      portScanAlerts.appendChild(div);
    });
  }
}

function getActionFilteredData() {
  return filteredData.filter(e => {
    if (e.action === 'DROP' && !filterDrop.checked) return false;
    if (e.action === 'FWD' && !filterFwd.checked) return false;
    if (e.action === 'IN' && !filterIn.checked) return false;
    if (e.action === 'OUT' && !filterOut.checked) return false;

    const ip = ipFilter.value.trim();
    if (ip && e.src && !e.src.includes(ip) && e.dst && !e.dst.includes(ip)) return false;

    const port = portFilter.value.trim();
    if (port && e.spt !== parseInt(port) && e.dpt !== parseInt(port)) return false;

    return true;
  });
}

function updateCharts() {
  const data = getActionFilteredData();
  drawTimeline(data);
  drawTopSources(data);
  drawTopPorts(data);
}

function drawTimeline(data) {
  const svg = document.getElementById('timelineChart');
  svg.innerHTML = '';

  if (data.length === 0) return;

  const width = svg.clientWidth || 800;
  const height = 200;
  const padding = { top: 20, right: 20, bottom: 40, left: 50 };
  const chartWidth = width - padding.left - padding.right;
  const chartHeight = height - padding.top - padding.bottom;

  const times = data.map(e => e.timestamp);
  const minTime = Math.min(...times);
  const maxTime = Math.max(...times);
  const timeRange = maxTime - minTime || 1;

  const bucketCount = 20;
  const buckets = Array(bucketCount).fill(0).map(() => ({ drop: 0, fwd: 0, in: 0, out: 0 }));

  data.forEach(e => {
    const bucketIdx = Math.min(Math.floor((e.timestamp - minTime) / timeRange * bucketCount), bucketCount - 1);
    if (e.action === 'DROP') buckets[bucketIdx].drop++;
    else if (e.action === 'FWD') buckets[bucketIdx].fwd++;
    else if (e.action === 'IN') buckets[bucketIdx].in++;
    else if (e.action === 'OUT') buckets[bucketIdx].out++;
  });

  const maxCount = Math.max(...buckets.map(b => b.drop + b.fwd + b.in + b.out)) || 1;
  const barWidth = chartWidth / bucketCount;

  svg.setAttribute('viewBox', `0 0 ${width} ${height}`);

  for (let i = 0; i <= 5; i++) {
    const y = padding.top + chartHeight - (i / 5) * chartHeight;
    const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
    line.setAttribute('x1', padding.left);
    line.setAttribute('x2', width - padding.right);
    line.setAttribute('y1', y);
    line.setAttribute('y2', y);
    line.setAttribute('class', 'grid-line');
    svg.appendChild(line);

    const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
    text.setAttribute('x', padding.left - 10);
    text.setAttribute('y', y + 4);
    text.setAttribute('text-anchor', 'end');
    text.setAttribute('class', 'axis-text');
    text.textContent = Math.round((i / 5) * maxCount);
    svg.appendChild(text);
  }

  buckets.forEach((bucket, i) => {
    const x = padding.left + i * barWidth;
    let y = padding.top + chartHeight;
    const barH = (val) => (val / maxCount) * chartHeight;

    ['in', 'fwd', 'drop', 'out'].forEach(type => {
      const h = barH(bucket[type]);
      if (h > 0) {
        const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
        rect.setAttribute('x', x + 1);
        rect.setAttribute('y', y - h);
        rect.setAttribute('width', barWidth - 2);
        rect.setAttribute('height', h);
        rect.setAttribute('class', `bar ${type}`);
        svg.appendChild(rect);
        y -= h;
      }
    });
  });

  const xAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');
  xAxis.setAttribute('x1', padding.left);
  xAxis.setAttribute('x2', width - padding.right);
  xAxis.setAttribute('y1', height - padding.bottom);
  xAxis.setAttribute('y2', height - padding.bottom);
  xAxis.setAttribute('class', 'axis');
  svg.appendChild(xAxis);

  const startText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  startText.setAttribute('x', padding.left);
  startText.setAttribute('y', height - 10);
  startText.setAttribute('class', 'axis-text');
  startText.textContent = minTime.toFixed(1);
  svg.appendChild(startText);

  const endText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  endText.setAttribute('x', width - padding.right);
  endText.setAttribute('y', height - 10);
  endText.setAttribute('text-anchor', 'end');
  endText.setAttribute('class', 'axis-text');
  endText.textContent = maxTime.toFixed(1);
  svg.appendChild(endText);
}

function drawTopSources(data) {
  const svg = document.getElementById('srcChart');
  svg.innerHTML = '';

  const counts = {};
  data.forEach(e => {
    if (!e.src) return;
    counts[e.src] = (counts[e.src] || 0) + 1;
  });

  const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, 10);
  drawBarChart(svg, sorted, 'ip');
}

function drawTopPorts(data) {
  const svg = document.getElementById('dstPortChart');
  svg.innerHTML = '';

  const counts = {};
  data.forEach(e => {
    if (!e.dpt) return;
    counts[e.dpt] = (counts[e.dpt] || 0) + 1;
  });

  const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, 10);
  drawBarChart(svg, sorted, 'port');
}

function drawBarChart(svg, data, type) {
  if (data.length === 0) return;

  const width = svg.clientWidth || 400;
  const height = 300;
  const padding = { top: 20, right: 20, bottom: 40, left: 120 };
  const chartWidth = width - padding.left - padding.right;
  const chartHeight = height - padding.top - padding.bottom;

  const maxValue = data[0][1];
  const barHeight = chartHeight / data.length;

  svg.setAttribute('viewBox', `0 0 ${width} ${height}`);

  data.forEach(([label, value], i) => {
    const y = padding.top + i * barHeight;
    const barWidth = (value / maxValue) * chartWidth;

    const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
    rect.setAttribute('x', padding.left);
    rect.setAttribute('y', y + 2);
    rect.setAttribute('width', barWidth);
    rect.setAttribute('height', barHeight - 4);
    rect.setAttribute('class', 'bar');
    svg.appendChild(rect);

    const labelText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
    labelText.setAttribute('x', padding.left - 10);
    labelText.setAttribute('y', y + barHeight / 2 + 4);
    labelText.setAttribute('text-anchor', 'end');
    labelText.setAttribute('class', 'axis-text');
    labelText.textContent = label;
    svg.appendChild(labelText);

    const valText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
    valText.setAttribute('x', padding.left + barWidth + 5);
    valText.setAttribute('y', y + barHeight / 2 + 4);
    valText.setAttribute('class', 'axis-text');
    valText.textContent = value;
    svg.appendChild(valText);
  });
}

function updateTables() {
  const data = getActionFilteredData();
  updateSrcTable(data);
  updatePortTable(data);
}

function updateSrcTable(data) {
  const tbody = document.getElementById('srcTableBody');
  tbody.innerHTML = '';

  const stats = {};
  data.forEach(e => {
    if (!e.src) return;
    if (!stats[e.src]) {
      stats[e.src] = { count: 0, drop: 0, ports: new Set() };
    }
    stats[e.src].count++;
    if (e.action === 'DROP') stats[e.src].drop++;
    if (e.dpt) stats[e.src].ports.add(e.dpt);
  });

  const rows = Object.entries(stats)
    .map(([ip, s]) => ({ ip, count: s.count, drop: s.drop, ports: s.ports.size }))
    .sort((a, b) => b.count - a.count)
    .slice(0, 50);

  rows.forEach(row => {
    const tr = document.createElement('tr');
    tr.innerHTML = `
      <td>${row.ip}</td>
      <td>${row.count}</td>
      <td>${row.drop}</td>
      <td>${row.ports}</td>
    `;
    tbody.appendChild(tr);
  });
}

function updatePortTable(data) {
  const tbody = document.getElementById('portTableBody');
  tbody.innerHTML = '';

  const stats = {};
  data.forEach(e => {
    if (!e.dpt) return;
    if (!stats[e.dpt]) {
      stats[e.dpt] = { count: 0, drop: 0, syn: 0 };
    }
    stats[e.dpt].count++;
    if (e.action === 'DROP') stats[e.dpt].drop++;
    if (e.flags && e.flags.includes('SYN')) stats[e.dpt].syn++;
  });

  const rows = Object.entries(stats)
    .map(([port, s]) => ({ port, count: s.count, drop: s.drop, syn: s.syn }))
    .sort((a, b) => b.count - a.count)
    .slice(0, 50);

  rows.forEach(row => {
    const tr = document.createElement('tr');
    tr.innerHTML = `
      <td>${row.port}</td>
      <td>${row.count}</td>
      <td>${row.drop}</td>
      <td>${row.syn}</td>
    `;
    tbody.appendChild(tr);
  });
}

function updateRawLog() {
  const container = document.getElementById('rawLog');
  container.innerHTML = '';

  const data = getActionFilteredData().slice(0, 500);

  data.forEach(e => {
    const div = document.createElement('div');
    div.className = `log-line ${e.action.toLowerCase()}`;
    div.textContent = e.raw;
    container.appendChild(div);
  });

  if (filteredData.length > 500) {
    const div = document.createElement('div');
    div.style.padding = '10px';
    div.style.color = '#666';
    div.textContent = `... and ${filteredData.length - 500} more entries`;
    container.appendChild(div);
  }
}

function handleFile(e) {
  const file = e.target.files[0];
  if (!file) return;

  const reader = new FileReader();
  reader.onload = (event) => {
    logInput.value = event.target.result;
    analyzeData();
  };
  reader.readAsText(file);
}

function applyCurrentFilters() {
  updateStats();
  detectAndShowScans();
  updateCharts();
  updateTables();
  updateRawLog();
}

function exportReport() {
  const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Linux Firewall Log Analyser Report</title>
<style>
* { box-sizing: border-box; }
body { font-family: Helvetica, Arial, sans-serif; line-height: 1.5; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 1400px; margin: 0 auto; background: white; padding: 30px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { margin: 0 0 20px 0; font-size: 24px; }
h2 { font-size: 18px; margin: 30px 0 15px 0; border-bottom: 1px solid #ddd; padding-bottom: 8px; }
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 30px; }
.stat-card { background: #f9f9f9; padding: 15px; border-radius: 4px; border-left: 4px solid #0066cc; }
.stat-card.drop { border-left-color: #dc3545; }
.stat-card.fwd { border-left-color: #ffc107; }
.stat-card.in { border-left-color: #28a745; }
.stat-label { font-size: 12px; color: #666; text-transform: uppercase; }
.stat-value { font-size: 28px; font-weight: 600; margin-top: 5px; }
.port-scan-alert { background: #fff3cd; border: 1px solid #ffc107; color: #856404; padding: 10px 15px; border-radius: 4px; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; font-size: 14px; }
th, td { text-align: left; padding: 10px; border-bottom: 1px solid #e0e0e0; }
th { background: #f5f5f5; font-weight: 600; }
.raw-log { background: #f5f5f5; padding: 15px; border-radius: 4px; font-family: monospace; font-size: 13px; max-height: 400px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
.log-line { padding: 2px 0; border-bottom: 1px solid #e8e8e8; }
.log-line.drop { background: rgba(220, 53, 69, 0.1); border-left: 3px solid #dc3545; padding-left: 5px; }
.log-line.fwd { background: rgba(255, 193, 7, 0.1); border-left: 3px solid #ffc107; padding-left: 5px; }
.log-line.in { background: rgba(40, 167, 69, 0.1); border-left: 3px solid #28a745; padding-left: 5px; }
svg { width: 100%; height: auto; background: #fafafa; border: 1px solid #e0e0e0; border-radius: 4px; margin-bottom: 20px; }
.bar { fill: #0066cc; }
.bar.drop { fill: #dc3545; }
.bar.fwd { fill: #ffc107; }
.bar.in { fill: #28a745; }
.axis { stroke: #999; stroke-width: 1; }
.axis-text { font-size: 11px; fill: #666; }
.grid-line { stroke: #e0e0e0; stroke-width: 1; }
</style>
</head>
<body>
<div class="container">
<h1>Linux Firewall Log Analyser Report</h1>
<p>Generated on ${new Date().toLocaleString()}</p>
<div id="content"></div>
</div>
<script>
const data = ${JSON.stringify(filteredData)};
const scans = ${JSON.stringify(detectPortScans(filteredData))};

document.getElementById('content').innerHTML = \`
\${scans.map(s => \`<div class="port-scan-alert">Potential port scan detected from \${s.src}: \${s.count} events targeting \${s.ports} unique ports</div>\`).join('')}

<div class="stats-grid">
<div class="stat-card"><div class="stat-label">Total Events</div><div class="stat-value">\${data.length}</div></div>
<div class="stat-card drop"><div class="stat-label">DROP</div><div class="stat-value">\${data.filter(e => e.action === 'DROP').length}</div></div>
<div class="stat-card fwd"><div class="stat-label">FWD</div><div class="stat-value">\${data.filter(e => e.action === 'FWD').length}</div></div>
<div class="stat-card in"><div class="stat-label">IN</div><div class="stat-value">\${data.filter(e => e.action === 'IN').length}</div></div>
</div>

<h2>Top Source IPs</h2>
<table>
<thead><tr><th>Source IP</th><th>Events</th><th>DROP</th></tr></thead>
<tbody>
\${Object.entries(data.filter(e => e.src).reduce((a, e) => { a[e.src] = (a[e.src] || 0) + 1; return a; }, {})).sort((a,b) => b[1]-a[1]).slice(0,20).map(([ip, c]) => \`<tr><td>\${ip}</td><td>\${c}</td><td>\${data.filter(e => e.src === ip && e.action === 'DROP').length}</td></tr>\`).join('')}
</tbody>
</table>

<h2>Raw Log Entries</h2>
<div class="raw-log">
\${data.slice(0, 1000).map(e => \`<div class="log-line \${e.action.toLowerCase()}">\${e.raw}</div>\`).join('')}
\${data.length > 1000 ? '<div>... and ' + (data.length - 1000) + ' more entries</div>' : ''}
</div>
\`;
</script>
</body>
</html>`;

  const blob = new Blob([html], { type: 'text/html' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'firewall-report.html';
  a.click();
  URL.revokeObjectURL(url);
}

fileInput.addEventListener('change', handleFile);
analyzeBtn.addEventListener('click', analyzeData);
exportBtn.addEventListener('click', exportReport);
clearBtn.addEventListener('click', () => {
  logInput.value = '';
  parsedData = [];
  filteredData = [];
  resultsSection.classList.add('hidden');
  exportBtn.classList.add('hidden');
  portScanAlerts.innerHTML = '';
});

applyFilters.addEventListener('click', applyCurrentFilters);

document.querySelectorAll('#srcTable th[data-sort]').forEach(th => {
  th.addEventListener('click', () => {
    const sort = th.dataset.sort;
    const tbody = document.getElementById('srcTableBody');
    const rows = Array.from(tbody.querySelectorAll('tr'));
    rows.sort((a, b) => {
      const aVal = a.cells[sort === 'ip' ? 0 : sort === 'count' ? 1 : sort === 'drop' ? 2 : 3].textContent;
      const bVal = b.cells[sort === 'ip' ? 0 : sort === 'count' ? 1 : sort === 'drop' ? 2 : 3].textContent;
      if (sort === 'ip') return aVal.localeCompare(bVal);
      return parseInt(bVal) - parseInt(aVal);
    });
    rows.forEach(r => tbody.appendChild(r));
  });
});

document.querySelectorAll('#portTable th[data-sort]').forEach(th => {
  th.addEventListener('click', () => {
    const sort = th.dataset.sort;
    const tbody = document.getElementById('portTableBody');
    const rows = Array.from(tbody.querySelectorAll('tr'));
    rows.sort((a, b) => {
      const aVal = a.cells[sort === 'port' ? 0 : sort === 'count' ? 1 : sort === 'drop' ? 2 : 3].textContent;
      const bVal = b.cells[sort === 'port' ? 0 : sort === 'count' ? 1 : sort === 'drop' ? 2 : 3].textContent;
      if (sort === 'port') return parseInt(aVal) - parseInt(bVal);
      return parseInt(bVal) - parseInt(aVal);
    });
    rows.forEach(r => tbody.appendChild(r));
  });
});
</script>
</body>
</html>