diff --git a/html/dashboard.html b/html/dashboard.html index 4afad06..67a9291 100644 --- a/html/dashboard.html +++ b/html/dashboard.html @@ -85,11 +85,34 @@
Gaps exceeding this threshold will be flagged as anomalies
+| File Source | Time Window | Duration | Analysis Signature | diff --git a/js/dashboard.js b/js/dashboard.js index f13a878..cc55435 100644 --- a/js/dashboard.js +++ b/js/dashboard.js @@ -1,6 +1,8 @@ let chart; let lastScanResults = null; let flaggedIncidents = new Set(); +let batchResults = []; // Store results from multiple files +let currentThreshold = 60; // Default threshold value // 1. IMPROVED VERTICAL SCANNER PLUGIN const verticalLinePlugin = { @@ -33,13 +35,27 @@ window.addEventListener("DOMContentLoaded", () => { flaggedIncidents = new Set(JSON.parse(savedFlags)); updateFlagCount(); } + + // Load saved threshold + const savedThreshold = localStorage.getItem("analysis_threshold"); + if (savedThreshold) { + currentThreshold = parseInt(savedThreshold); + document.getElementById("thresholdSlider").value = currentThreshold; + updateThresholdDisplay(currentThreshold); + } + loadLastSession(); }); function loadLastSession() { const savedData = localStorage.getItem("last_forensic_scan"); const savedMeta = localStorage.getItem("last_scan_metadata"); - if (savedData && savedMeta) { + const savedBatch = localStorage.getItem("last_batch_results"); + + if (savedBatch) { + batchResults = JSON.parse(savedBatch); + renderBatchResults(batchResults); + } else if (savedData && savedMeta) { lastScanResults = JSON.parse(savedData); const meta = JSON.parse(savedMeta); const timeEl = document.getElementById("lastScanTime"); @@ -50,34 +66,84 @@ function loadLastSession() { } } +// Update threshold display +function updateThresholdDisplay(value) { + currentThreshold = parseInt(value); + const display = document.getElementById("thresholdValue"); + + // Format display based on value + let displayText; + if (currentThreshold >= 3600) { + displayText = (currentThreshold / 3600).toFixed(1) + "h"; + } else if (currentThreshold >= 60) { + displayText = Math.floor(currentThreshold / 60) + "m"; + } else { + displayText = currentThreshold + "s"; + } + + if (display) display.innerText = displayText; + + // Save to localStorage + localStorage.setItem("analysis_threshold", currentThreshold); +} + async function analyzeLogs(event) { const fileInput = document.getElementById("logFile"); - const file = fileInput.files[0]; - if (!file) return showToast("Critical: No source file selected"); + const files = Array.from(fileInput.files); + + if (files.length === 0) { + return showToast("Critical: No source file selected"); + } const overlay = document.getElementById("scanOverlay"); const statusText = document.getElementById("loaderStatus"); overlay.classList.remove("hidden"); - const formData = new FormData(); - formData.append("file", file); - formData.append("threshold", 60); - + const token = localStorage.getItem("access_token"); + batchResults = []; // Reset batch results + try { - const token = localStorage.getItem("access_token"); - const res = await fetch("http://localhost:8000/analyze", { - method: "POST", - headers: { "Authorization": `Bearer ${token}` }, - body: formData, - }); - if (res.status === 401) { - localStorage.removeItem("access_token"); - window.location.href = "index.html"; - return; + // Process each file sequentially + for (let i = 0; i < files.length; i++) { + const file = files[i]; + statusText.innerText = `Processing file ${i + 1}/${files.length}: ${file.name}`; + + const formData = new FormData(); + formData.append("file", file); + formData.append("threshold", currentThreshold); + + try { + const res = await fetch("http://localhost:8000/analyze", { + method: "POST", + headers: { "Authorization": `Bearer ${token}` }, + body: formData, + }); + + if (res.status === 401) { + localStorage.removeItem("access_token"); + window.location.href = "index.html"; + return; + } + + if (!res.ok) throw new Error("Connection Refused"); + + const data = await res.json(); + + // Add file metadata to results + batchResults.push({ + fileName: file.name, + timestamp: new Date().toLocaleString().toUpperCase(), + data: data, + threshold: currentThreshold + }); + + } catch (e) { + console.error(`Error processing ${file.name}:`, e); + showToast(`Error processing ${file.name}`); + } } - if (!res.ok) throw new Error("Connection Refused"); - const data = await res.json(); + // Animation steps const steps = [ "Hashing Payload...", "Mapping Voids...", @@ -89,23 +155,120 @@ async function analyzeLogs(event) { await new Promise((r) => setTimeout(r, 400)); } - const meta = { - timestamp: new Date().toLocaleString().toUpperCase(), - fileName: file.name, - }; - localStorage.setItem("last_forensic_scan", JSON.stringify(data)); - localStorage.setItem("last_scan_metadata", JSON.stringify(meta)); - - lastScanResults = data; - renderResults(data); - showToast("Analysis Finalized"); + // Save results + if (batchResults.length === 1) { + // Single file - use legacy format + const result = batchResults[0]; + const meta = { + timestamp: result.timestamp, + fileName: result.fileName, + }; + localStorage.setItem("last_forensic_scan", JSON.stringify(result.data)); + localStorage.setItem("last_scan_metadata", JSON.stringify(meta)); + localStorage.removeItem("last_batch_results"); + + lastScanResults = result.data; + renderResults(result.data); + } else { + // Multiple files - batch mode + localStorage.setItem("last_batch_results", JSON.stringify(batchResults)); + localStorage.removeItem("last_forensic_scan"); + localStorage.removeItem("last_scan_metadata"); + + renderBatchResults(batchResults); + } + + showToast(`Analysis Complete: ${batchResults.length} file(s) processed`); + } catch (e) { showToast("Backend Link Error: Ensure server is online"); + console.error(e); } finally { overlay.classList.add("hidden"); } } +function renderBatchResults(results) { + if (!results || results.length === 0) return; + + // Calculate aggregate statistics + let totalGaps = 0; + let totalScore = 0; + let allIncidents = []; + + results.forEach(result => { + totalGaps += result.data.total_gaps; + totalScore += result.data.integrity_score; + + // Add file name to each incident + result.data.incidents.forEach(incident => { + allIncidents.push({ + ...incident, + fileName: result.fileName + }); + }); + }); + + const avgScore = (totalScore / results.length).toFixed(1); + const avgRisk = (100 - avgScore).toFixed(1); + + // Update KPI cards with aggregate data + document.getElementById("integrityScoreCard").innerText = avgScore + "%"; + document.getElementById("financialRisk").innerText = avgRisk + "%"; + document.getElementById("gapCount").innerText = totalGaps; + + // Update metadata + document.getElementById("lastScanTime").innerText = results[0].timestamp; + document.getElementById("lastFileName").innerText = `${results.length} files analyzed`; + + // Show batch summary + const batchSummary = document.getElementById("batchSummary"); + const summaryContent = document.getElementById("batchSummaryContent"); + + if (batchSummary && summaryContent) { + batchSummary.classList.remove("hidden"); + + summaryContent.innerHTML = results.map(result => { + const score = result.data.integrity_score; + const scoreColor = score >= 90 ? "text-emerald-400" : score >= 70 ? "text-amber-400" : "text-red-400"; + + return ` +
|---|---|---|---|
|
+
+
+ ${inc.fileName || 'N/A'}
+
+ |
+ ` : ''}
@@ -369,21 +540,41 @@ function updateChart(incidents) {
}
function handleSortChange(criteria) {
- if (!lastScanResults || !lastScanResults.incidents) {
+ let allIncidents;
+ let isBatch = false;
+
+ // Determine if we're in batch mode or single file mode
+ if (batchResults.length > 0) {
+ isBatch = true;
+ allIncidents = [];
+ batchResults.forEach(result => {
+ result.data.incidents.forEach(incident => {
+ allIncidents.push({
+ ...incident,
+ fileName: result.fileName
+ });
+ });
+ });
+ } else if (lastScanResults && lastScanResults.incidents) {
+ allIncidents = lastScanResults.incidents;
+ } else {
showToast("No data to sort");
return;
}
+
const placeholder = document.getElementById("sortPlaceholder");
+
if (criteria === "high") {
- lastScanResults.incidents.sort((a, b) => b.duration - a.duration);
+ allIncidents.sort((a, b) => b.duration - a.duration);
showToast("Prioritizing Critical Voids");
if (placeholder) placeholder.disabled = true;
} else if (criteria === "low") {
- lastScanResults.incidents.sort((a, b) => a.duration - b.duration);
+ allIncidents.sort((a, b) => a.duration - b.duration);
showToast("Prioritizing Minor Anomalies");
if (placeholder) placeholder.disabled = true;
}
- updateRegistryTable(lastScanResults.incidents);
+
+ updateRegistryTable(allIncidents, isBatch);
}
function toggleFlag(index) {
@@ -419,11 +610,17 @@ function updateFileName() {
const fileInput = document.getElementById("logFile");
const fileNameDisplay = document.getElementById("fileNameDisplay");
if (fileInput.files.length > 0) {
- fileNameDisplay.innerText = fileInput.files[0].name;
+ if (fileInput.files.length === 1) {
+ fileNameDisplay.innerText = fileInput.files[0].name;
+ } else {
+ fileNameDisplay.innerText = `${fileInput.files.length} files selected`;
+ }
fileNameDisplay.classList.remove("text-slate-500");
fileNameDisplay.classList.add("text-blue-400");
} else {
- fileNameDisplay.innerText = "Select Log Source";
+ fileNameDisplay.innerText = "Select Log Source(s)";
+ fileNameDisplay.classList.remove("text-blue-400");
+ fileNameDisplay.classList.add("text-slate-500");
}
}
@@ -433,21 +630,57 @@ function logout() {
}
function exportForensicJSON() {
- if (!lastScanResults) return showToast("Critical: No scan data available");
- const report = {
- header: {
- session_id: `CERT-${Math.random().toString(36).substr(2, 9).toUpperCase()}`,
- timestamp: new Date().toISOString(),
- operator: "L1_ADMIN_04",
- },
- integrity_summary: {
- file_source:
- document.getElementById("lastFileName")?.innerText || "Unknown",
- score: document.getElementById("integrityScoreCard")?.innerText || "0%",
- sha256_hash: `3A7C${Math.random().toString(16).substr(2, 12).toUpperCase()}`,
- },
- void_data: lastScanResults.incidents,
- };
+ if (!lastScanResults && batchResults.length === 0) {
+ return showToast("Critical: No scan data available");
+ }
+
+ let report;
+
+ if (batchResults.length > 0) {
+ // Batch mode export
+ report = {
+ header: {
+ session_id: `CERT-${Math.random().toString(36).substr(2, 9).toUpperCase()}`,
+ timestamp: new Date().toISOString(),
+ operator: "L1_ADMIN_04",
+ mode: "BATCH_ANALYSIS"
+ },
+ batch_summary: {
+ total_files: batchResults.length,
+ threshold_used: currentThreshold,
+ files: batchResults.map(r => ({
+ filename: r.fileName,
+ integrity_score: r.data.integrity_score,
+ total_gaps: r.data.total_gaps,
+ sha256_hash: `3A7C${Math.random().toString(16).substr(2, 12).toUpperCase()}`
+ }))
+ },
+ detailed_results: batchResults.map(r => ({
+ file_source: r.fileName,
+ analyzed_at: r.timestamp,
+ integrity_score: r.data.integrity_score,
+ void_data: r.data.incidents
+ }))
+ };
+ } else {
+ // Single file mode export
+ report = {
+ header: {
+ session_id: `CERT-${Math.random().toString(36).substr(2, 9).toUpperCase()}`,
+ timestamp: new Date().toISOString(),
+ operator: "L1_ADMIN_04",
+ },
+ integrity_summary: {
+ file_source:
+ document.getElementById("lastFileName")?.innerText || "Unknown",
+ score: document.getElementById("integrityScoreCard")?.innerText || "0%",
+ threshold_used: currentThreshold,
+ sha256_hash: `3A7C${Math.random().toString(16).substr(2, 12).toUpperCase()}`,
+ },
+ void_data: lastScanResults.incidents,
+ };
+ }
+
const blob = new Blob([JSON.stringify(report, null, 4)], {
type: "application/json",
});
@@ -460,12 +693,38 @@ function exportForensicJSON() {
}
function exportRegistryCSV() {
- if (!lastScanResults || !lastScanResults.incidents.length)
+ let allIncidents = [];
+ let isBatch = false;
+
+ if (batchResults.length > 0) {
+ isBatch = true;
+ batchResults.forEach(result => {
+ result.data.incidents.forEach(incident => {
+ allIncidents.push({
+ ...incident,
+ fileName: result.fileName
+ });
+ });
+ });
+ } else if (lastScanResults && lastScanResults.incidents.length) {
+ allIncidents = lastScanResults.incidents;
+ } else {
return showToast("Notice: Incident Registry is empty");
- let csv = "Incident,Start,End,Duration(s),Severity\n";
- lastScanResults.incidents.forEach((inc, i) => {
- csv += `VOID-${i + 1},${inc.start},${inc.end},${inc.duration},${inc.duration > 300 ? "CRITICAL" : "WARNING"}\n`;
- });
+ }
+
+ let csv;
+ if (isBatch) {
+ csv = "File,Incident,Start,End,Duration(s),Severity\n";
+ allIncidents.forEach((inc, i) => {
+ csv += `${inc.fileName},VOID-${i + 1},${inc.start},${inc.end},${inc.duration},${inc.duration > 300 ? "CRITICAL" : "WARNING"}\n`;
+ });
+ } else {
+ csv = "Incident,Start,End,Duration(s),Severity\n";
+ allIncidents.forEach((inc, i) => {
+ csv += `VOID-${i + 1},${inc.start},${inc.end},${inc.duration},${inc.duration > 300 ? "CRITICAL" : "WARNING"}\n`;
+ });
+ }
+
const blob = new Blob([csv], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
@@ -514,20 +773,43 @@ function exportChartAsJPG() {
// Search/Filter functionality for Incident Registry
function filterRegistry() {
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
- if (!lastScanResults || !lastScanResults.incidents) return;
- let filteredIncidents = lastScanResults.incidents;
+ let allIncidents;
+ let isBatch = false;
+
+ // Determine if we're in batch mode or single file mode
+ if (batchResults.length > 0) {
+ isBatch = true;
+ allIncidents = [];
+ batchResults.forEach(result => {
+ result.data.incidents.forEach(incident => {
+ allIncidents.push({
+ ...incident,
+ fileName: result.fileName
+ });
+ });
+ });
+ } else if (lastScanResults && lastScanResults.incidents) {
+ allIncidents = lastScanResults.incidents;
+ } else {
+ return;
+ }
+
+ let filteredIncidents = allIncidents;
if (searchTerm) {
- filteredIncidents = lastScanResults.incidents.filter(inc => {
+ filteredIncidents = allIncidents.filter(inc => {
// Search by timestamp (start or end time)
const timeMatch = inc.start.toLowerCase().includes(searchTerm) ||
inc.end.toLowerCase().includes(searchTerm);
// Search by duration
const durationMatch = inc.duration.toString().includes(searchTerm);
- return timeMatch || durationMatch;
+ // Search by filename (if in batch mode)
+ const fileMatch = isBatch && inc.fileName && inc.fileName.toLowerCase().includes(searchTerm);
+
+ return timeMatch || durationMatch || fileMatch;
});
}
- updateRegistryTable(filteredIncidents);
+ updateRegistryTable(filteredIncidents, isBatch);
}
\ No newline at end of file
diff --git a/main.py b/main.py
index f990fd5..ca6eddb 100644
--- a/main.py
+++ b/main.py
@@ -94,6 +94,14 @@ async def upload_log(
try:
numeric_threshold = int(threshold)
results = analyze_logs(temp_path, numeric_threshold)
+
+ # Add metadata to response
+ results["metadata"] = {
+ "filename": file.filename,
+ "threshold_used": numeric_threshold,
+ "analyzed_at": datetime.utcnow().isoformat()
+ }
+
return results
except Exception as e:
print(f"Analysis Error: {e}")
diff --git a/styles/dashboard.css b/styles/dashboard.css
index 8ab3766..311db7e 100644
--- a/styles/dashboard.css
+++ b/styles/dashboard.css
@@ -167,6 +167,49 @@ header {
border-radius: 0 12px 12px 0;
}
+/* ── Threshold Slider ────────────────────────────────────────────────────── */
+#thresholdSlider {
+ -webkit-appearance: none;
+ appearance: none;
+ background: var(--bg-secondary);
+ outline: none;
+ border-radius: 8px;
+ transition: all 0.3s ease;
+}
+
+#thresholdSlider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 18px;
+ height: 18px;
+ background: var(--accent-blue);
+ cursor: pointer;
+ border-radius: 50%;
+ box-shadow: 0 0 10px rgba(59, 130, 246, 0.5);
+ transition: all 0.2s ease;
+}
+
+#thresholdSlider::-webkit-slider-thumb:hover {
+ transform: scale(1.2);
+ box-shadow: 0 0 15px rgba(59, 130, 246, 0.8);
+}
+
+#thresholdSlider::-moz-range-thumb {
+ width: 18px;
+ height: 18px;
+ background: var(--accent-blue);
+ cursor: pointer;
+ border-radius: 50%;
+ border: none;
+ box-shadow: 0 0 10px rgba(59, 130, 246, 0.5);
+ transition: all 0.2s ease;
+}
+
+#thresholdSlider::-moz-range-thumb:hover {
+ transform: scale(1.2);
+ box-shadow: 0 0 15px rgba(59, 130, 246, 0.8);
+}
+
/* ── Light mode table rows ───────────────────────────────────────────────── */
html[data-theme="light"] #incidentBody tr:hover {
background: rgba(37, 99, 235, 0.05) !important;
diff --git a/test_logs/access.log b/test_logs/access.log
new file mode 100644
index 0000000..5ace9d1
--- /dev/null
+++ b/test_logs/access.log
@@ -0,0 +1,10 @@
+2026-05-02 08:00:00 GET /api/status 200 15ms
+2026-05-02 08:00:05 GET /api/users 200 23ms
+2026-05-02 08:00:10 POST /api/login 200 45ms
+2026-05-02 08:00:15 GET /api/dashboard 200 18ms
+2026-05-02 08:01:00 GET /api/reports 200 120ms
+2026-05-02 08:01:30 GET /api/analytics 200 89ms
+2026-05-02 08:12:00 GET /api/status 200 12ms
+2026-05-02 08:12:15 POST /api/data 201 67ms
+2026-05-02 08:12:30 GET /api/export 200 234ms
+2026-05-02 08:13:00 DELETE /api/cache 204 8ms
diff --git a/test_logs/auth.log b/test_logs/auth.log
new file mode 100644
index 0000000..e094286
--- /dev/null
+++ b/test_logs/auth.log
@@ -0,0 +1,11 @@
+2026-05-02 08:00:00 [INFO] User admin logged in from 192.168.1.100
+2026-05-02 08:00:15 [INFO] Authentication successful for user admin
+2026-05-02 08:00:30 [INFO] Session created: session_12345
+2026-05-02 08:01:00 [INFO] User admin accessed /dashboard
+2026-05-02 08:01:30 [INFO] User admin accessed /reports
+2026-05-02 08:02:00 [INFO] User admin accessed /settings
+2026-05-02 08:04:30 [WARN] Failed login attempt for user guest from 192.168.1.200
+2026-05-02 08:04:45 [WARN] Failed login attempt for user guest from 192.168.1.200
+2026-05-02 08:05:00 [WARN] IP 192.168.1.200 blocked due to multiple failed attempts
+2026-05-02 08:15:00 [INFO] User admin logged out
+2026-05-02 08:15:15 [INFO] Session terminated: session_12345
diff --git a/test_logs/syslog.log b/test_logs/syslog.log
new file mode 100644
index 0000000..8d2cf46
--- /dev/null
+++ b/test_logs/syslog.log
@@ -0,0 +1,10 @@
+2026-05-02 08:00:00 [SYSTEM] Service started: web-server
+2026-05-02 08:00:05 [SYSTEM] Service started: database
+2026-05-02 08:00:10 [SYSTEM] Service started: cache-server
+2026-05-02 08:05:00 [SYSTEM] Backup process initiated
+2026-05-02 08:05:30 [SYSTEM] Backup completed successfully
+2026-05-02 08:20:00 [ERROR] Database connection timeout
+2026-05-02 08:20:15 [ERROR] Retry attempt 1/3
+2026-05-02 08:20:30 [INFO] Database connection restored
+2026-05-02 08:25:00 [SYSTEM] Health check passed
+2026-05-02 08:30:00 [SYSTEM] Health check passed
|