Merge pull request 'merge from master' (#1) from master into main

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-01-03 07:12:01 +00:00
4 changed files with 468 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules
*.pdf
*.csv
*.report.html

250
analyzer.js Normal file
View File

@@ -0,0 +1,250 @@
const fs = require('fs');
const csv = require('csv-parser');
const PDFDocument = require('pdfkit');
const inputFile = process.argv[2];
if (!inputFile) {
console.error('Please provide a log file path as an argument.');
process.exit(1);
}
const stats = {
totalEvents: 0,
severityCounts: {},
dropEvents: 0,
dropBySrcIP: {},
thermalWarnings: 0,
possibleAttacks: 0,
attackDetails: [],
startTime: null,
endTime: null,
timeline: {} // Format: "YYYY-MM-DD HH:00": { Info: 0, Error: 0, ... }
};
const THREAT_KEYWORDS = ['attack', 'exploit', 'malware', 'bad password', 'auth fail', 'intrusion'];
console.log(`Starting analysis of ${inputFile}...`);
// Helper to update timeline
function updateTimeline(dateStr, timeStr, severity) {
try {
// Assuming Date format MM/DD/YYYY and Time HH:MM:SS
const hourKey = `${dateStr} ${timeStr.split(':')[0]}:00`;
if (!stats.timeline[hourKey]) stats.timeline[hourKey] = {};
stats.timeline[hourKey][severity] = (stats.timeline[hourKey][severity] || 0) + 1;
// Track start/end time
if (!stats.startTime) stats.startTime = `${dateStr} ${timeStr}`;
stats.endTime = `${dateStr} ${timeStr}`;
} catch (e) {
// Ignore parsing errors for timeline
}
}
fs.createReadStream(inputFile)
.pipe(csv({ skipLines: 1 }))
.on('data', (row) => {
stats.totalEvents++;
const level = (row['Level'] || 'Unknown').trim();
const message = (row['Messages'] || '').toString();
const date = row['Date'];
const time = row['Time'];
// Stats
stats.severityCounts[level] = (stats.severityCounts[level] || 0) + 1;
updateTimeline(date, time, level);
// Security Rules
if (message.includes('DROP')) {
stats.dropEvents++;
const srcMatch = message.match(/SRC=([\d\.]+)/);
if (srcMatch && srcMatch[1]) {
const ip = srcMatch[1];
stats.dropBySrcIP[ip] = (stats.dropBySrcIP[ip] || 0) + 1;
}
}
if (level === 'Emergency' && message.includes('THERMAL')) {
stats.thermalWarnings++;
}
const lowerMsg = message.toLowerCase();
for (const word of THREAT_KEYWORDS) {
if (lowerMsg.includes(word)) {
stats.possibleAttacks++;
// Increase capture limit for HTML report
if (stats.possibleAttacks <= 100) {
stats.attackDetails.push({ date, time, level, msg: message });
}
break;
}
}
})
.on('end', () => {
generateReports();
});
function generateReports() {
const reportBase = inputFile.replace(/\.csv$/i, '');
generatePDFReport(`${reportBase}.report.pdf`);
generateHTMLReport(`${reportBase}.report.html`);
console.log('\nAnalysis Complete.');
console.log(`PDF Report: ${reportBase}.report.pdf`);
console.log(`HTML Report: ${reportBase}.report.html`);
}
function generatePDFReport(filename) {
const doc = new PDFDocument();
doc.pipe(fs.createWriteStream(filename));
// Title
doc.fontSize(20).text('ASUS Log Security Analysis', { align: 'center' });
doc.moveDown();
// Summary
doc.fontSize(12).text(`Analyzed File: ${inputFile}`);
doc.text(`Time Period: ${stats.startTime} to ${stats.endTime}`);
doc.text(`Total Log Lines: ${stats.totalEvents.toLocaleString()}`);
doc.moveDown();
// Key Stats
doc.fontSize(14).text('Security Summary', { underline: true });
doc.fontSize(12).text(`Total Firewall Drops: ${stats.dropEvents.toLocaleString()}`);
doc.text(`Potential Threats: ${stats.possibleAttacks.toLocaleString()}`);
doc.text(`Thermal Warnings: ${stats.thermalWarnings}`);
doc.moveDown();
// Chart: Severity Breakdown (Simple Bar Chart)
doc.addPage();
doc.fontSize(14).text('Severity Breakdown', { align: 'center' });
doc.moveDown();
const severities = Object.entries(stats.severityCounts).sort(([,a], [,b]) => b - a);
const maxVal = Math.max(...Object.values(stats.severityCounts));
const startX = 50;
let startY = 150;
const barHeight = 20;
const maxBarWidth = 400;
severities.forEach(([label, count]) => {
const width = (count / maxVal) * maxBarWidth;
doc.rect(startX + 100, startY, width, barHeight).fill('blue');
doc.fillColor('black').text(label, startX, startY + 5);
doc.text(count.toLocaleString(), startX + 100 + width + 5, startY + 5);
startY += 30;
});
// Table: Top Blocked IPs
doc.addPage();
doc.fontSize(14).text('Top 10 Blocked Source IPs', { underline: true });
doc.moveDown();
const topIPs = Object.entries(stats.dropBySrcIP).sort(([,a], [,b]) => b - a).slice(0, 10);
topIPs.forEach(([ip, count], i) => {
doc.fontSize(12).text(`${i+1}. ${ip}: ${count.toLocaleString()} drops`);
});
doc.end();
}
function generateHTMLReport(filename) {
// Aggregating timeline for Chart.js
const labels = Object.keys(stats.timeline).sort();
const datasets = [];
// Check which severities exist
const allSeverities = new Set();
labels.forEach(t => Object.keys(stats.timeline[t]).forEach(s => allSeverities.add(s)));
const colors = { 'Info': '#36a2eb', 'Warning': '#ffcd56', 'Error': '#ff6384', 'Emergency': '#c45850' };
allSeverities.forEach(sev => {
const data = labels.map(t => stats.timeline[t][sev] || 0);
datasets.push({
label: sev,
data: data,
borderColor: colors[sev] || '#999',
fill: false
});
});
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<title>Log Analysis Report</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: sans-serif; margin: 20px; }
.card { border: 1px solid #ccc; padding: 15px; margin-bottom: 20px; border-radius: 5px; }
.chart-container { position: relative; height: 400px; width: 100%; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1>Log Analysis Report</h1>
<div class="card">
<h2>Summary</h2>
<p><strong>File:</strong> ${inputFile}</p>
<p><strong>Period:</strong> ${stats.startTime} - ${stats.endTime}</p>
<p><strong>Total Events:</strong> ${stats.totalEvents.toLocaleString()}</p>
<p><strong>Firewall Drops:</strong> ${stats.dropEvents.toLocaleString()}</p>
<p><strong>Threats:</strong> ${stats.possibleAttacks}</p>
</div>
<div class="card">
<h2>Event Timeline</h2>
<div class="chart-container">
<canvas id="timelineChart"></canvas>
</div>
</div>
<div class="card">
<h2>Recent Threat Details (Sample)</h2>
<input type="text" id="filterInput" onkeyup="filterTable()" placeholder="Search threats...">
<table id="threatTable">
<thead><tr><th>Date</th><th>Time</th><th>Level</th><th>Message</th></tr></thead>
<tbody>
${stats.attackDetails.map(d => `<tr><td>${d.date}</td><td>${d.time}</td><td>${d.level}</td><td>${d.msg}</td></tr>`).join('')}
</tbody>
</table>
</div>
<script>
const ctx = document.getElementById('timelineChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: ${JSON.stringify(labels)},
datasets: ${JSON.stringify(datasets)}
},
options: { responsive: true, maintainAspectRatio: false }
});
function filterTable() {
var input, filter, table, tr, td, i, txtValue;
input = document.getElementById("filterInput");
filter = input.value.toUpperCase();
table = document.getElementById("threatTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[3]; // Message column
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
</script>
</body>
</html>`;
fs.writeFileSync(filename, htmlContent);
}

198
package-lock.json generated Normal file
View File

@@ -0,0 +1,198 @@
{
"name": "networklog",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "networklog",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"csv-parser": "^3.2.0",
"pdfkit": "^0.17.2"
}
},
"node_modules/@swc/helpers": {
"version": "0.5.18",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz",
"integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/brotli": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
"integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.1.2"
}
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
"node_modules/csv-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz",
"integrity": "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA==",
"license": "MIT",
"bin": {
"csv-parser": "bin/csv-parser"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/dfa": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fontkit": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
"integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
"license": "MIT",
"dependencies": {
"@swc/helpers": "^0.5.12",
"brotli": "^1.3.2",
"clone": "^2.1.2",
"dfa": "^1.2.0",
"fast-deep-equal": "^3.1.3",
"restructure": "^3.0.0",
"tiny-inflate": "^1.0.3",
"unicode-properties": "^1.4.0",
"unicode-trie": "^2.0.0"
}
},
"node_modules/jpeg-exif": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz",
"integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==",
"license": "MIT"
},
"node_modules/linebreak": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
"license": "MIT",
"dependencies": {
"base64-js": "0.0.8",
"unicode-trie": "^2.0.0"
}
},
"node_modules/linebreak/node_modules/base64-js": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/pako": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
"license": "MIT"
},
"node_modules/pdfkit": {
"version": "0.17.2",
"resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.17.2.tgz",
"integrity": "sha512-UnwF5fXy08f0dnp4jchFYAROKMNTaPqb/xgR8GtCzIcqoTnbOqtp3bwKvO4688oHI6vzEEs8Q6vqqEnC5IUELw==",
"license": "MIT",
"dependencies": {
"crypto-js": "^4.2.0",
"fontkit": "^2.0.4",
"jpeg-exif": "^1.1.4",
"linebreak": "^1.1.0",
"png-js": "^1.0.0"
}
},
"node_modules/png-js": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz",
"integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g=="
},
"node_modules/restructure": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
"integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
"license": "MIT"
},
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/unicode-properties": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
"integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.0",
"unicode-trie": "^2.0.0"
}
},
"node_modules/unicode-trie": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
"license": "MIT",
"dependencies": {
"pako": "^0.2.5",
"tiny-inflate": "^1.0.0"
}
}
}
}

16
package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "networklog",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"csv-parser": "^3.2.0",
"pdfkit": "^0.17.2"
}
}