Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 135 additions & 10 deletions html/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@2.0.1/dist/chartjs-plugin-zoom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.23/jspdf.plugin.autotable.min.js"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
<link rel="stylesheet" href="../styles/dashboard.css" />
<link rel="stylesheet" href="../styles/streaming.css" />
<link rel="stylesheet" href="../styles/heatmap.css" />
<style>
/* ── Reactive Scroll-To-Top Button (Dashboard) ───────────────────────── */
#scrollTopBtn {
position: fixed;
bottom: 2rem;
Expand Down Expand Up @@ -188,7 +186,8 @@ <h3 id="gapCount" class="text-2xl font-black text-white">0</h3>
</div>

<div class="grid grid-cols-12 gap-6">
<div class="col-span-12 lg:col-span-4 space-y-6">
<!-- Left Column - Streaming Section (6 columns) -->
<div class="col-span-12 lg:col-span-6 space-y-6">
<div class="glass-card p-6 border-l-4 border-blue-500">
<h4 class="text-[10px] font-bold uppercase text-slate-500 mb-4 tracking-widest">Evidence Ingestion</h4>
<div id="dropArea" class="border-2 border-dashed border-slate-800 rounded-xl p-8 text-center hover:border-blue-500 cursor-pointer">
Expand Down Expand Up @@ -239,15 +238,101 @@ <h4 class="text-[10px] font-bold uppercase text-blue-500 tracking-widest flex it
<pre id="previewContent" class="font-mono text-[10px] text-slate-300 bg-slate-900/50 p-4 rounded-lg overflow-x-auto custom-scroll max-h-64 leading-relaxed"></pre>
</div>
</div>

<!-- Real-time Streaming Section -->
<div class="streaming-container">
<div class="streaming-header">
<div class="streaming-title">
<i class="fas fa-broadcast-tower"></i>
<div>
<h3>LIVE FORENSIC STREAM</h3>
<p>Real-time anomaly detection</p>
</div>
</div>
<div class="streaming-controls">
<button id="startStreamingBtn" class="streaming-btn streaming-btn-primary">▶ START</button>
<button id="pauseStreamingBtn" class="streaming-btn streaming-btn-warning" disabled>⏸ PAUSE</button>
<button id="stopStreamingBtn" class="streaming-btn streaming-btn-danger" disabled>⏹ STOP</button>
<button id="clearStreamingBtn" class="streaming-btn streaming-btn-secondary">🗑 CLEAR</button>
</div>
</div>

<!-- Filter Row -->
<div class="streaming-filter-row">
<div class="filter-group">
<button class="filter-chip active" data-filter="all">ALL</button>
<button class="filter-chip active" data-filter="timeVoid">TIME VOIDS</button>
<button class="filter-chip active" data-filter="logShaving">LOG SHAVING</button>
<button class="filter-chip active" data-filter="critical">CRITICAL</button>
</div>
<div class="stats-mini">
🔍 <span id="visibleLinesCount">0</span> visible
</div>
</div>

<!-- Progress Bar -->
<div class="streaming-progress-compact">
<div class="progress-header-compact">
<span id="streamProgressMessage">Ready</span>
<span id="streamProgressPercent">0%</span>
</div>
<div class="progress-bar-bg-compact">
<div id="streamProgressFill" class="progress-bar-fill-compact" style="width: 0%"></div>
</div>
</div>

<!-- Stats Row -->
<div class="stats-row">
<div class="stat-item-compact">
<span class="stat-icon">📊</span>
<div>
<span class="stat-label-compact">ANOMALIES</span>
<span id="anomalyCount" class="stat-value-compact">0</span>
</div>
</div>
<div class="stat-item-compact">
<span class="stat-icon">⚠️</span>
<div>
<span class="stat-label-compact">CRITICAL</span>
<span id="stat-critical" class="stat-value-compact critical">0</span>
</div>
</div>
<div class="stat-item-compact">
<span class="stat-icon">⏱️</span>
<div>
<span class="stat-label-compact">TIME VOIDS</span>
<span id="stat-timeVoid" class="stat-value-compact time-void">0</span>
</div>
</div>
<div class="stat-item-compact">
<span class="stat-icon">✂️</span>
<div>
<span class="stat-label-compact">SHAVING</span>
<span id="stat-logShaving" class="stat-value-compact shaving">0</span>
</div>
</div>
</div>

<!-- Log Container -->
<div id="streamingLogContainer" class="streaming-log-container">
<div class="streaming-info-message">
<i class="fas fa-microchip"></i>
<span>Select a log file and click START</span>
</div>
</div>
</div>

<div id="signatureCard" class="hidden glass-card p-6 border-l-4 border-amber-500">
<h4 class="text-[10px] font-bold uppercase text-amber-500 mb-4 flex items-center gap-2">
<i class="fas fa-crosshairs"></i> Anomaly Signature
</h4>
<div id="tacticalReasoning" class="text-[10px] text-slate-300 leading-relaxed font-mono italic"></div>
</div>
</div>
<div class="col-span-12 lg:col-span-8">
<div class="glass-card p-6 h-[450px]">

<!-- Right Column - Chart Section (6 columns) -->
<div class="col-span-12 lg:col-span-6">
<div class="glass-card p-6" style="height: 480px;">
<div class="flex justify-between items-center mb-6">
<h4 class="text-[10px] font-bold uppercase text-slate-500 tracking-widest">Forensic Data Visualization</h4>
<div class="flex gap-2">
Expand All @@ -259,6 +344,43 @@ <h4 class="text-[10px] font-bold uppercase text-slate-500 tracking-widest">Foren
</div>
</div>
</div>

<!-- Forensic Heatmap Section -->
<div class="forensic-heatmap">
<div class="forensic-heatmap-header">
<div class="forensic-heatmap-title">
<i class="fas fa-chart-line"></i>
<div>
<h3>ANOMALY PATTERN ANALYSIS</h3>
<p>Heatmap visualization - click any cell to drill down</p>
</div>
</div>
<div class="heatmap-controls">
<button id="heatmapHourlyBtn" class="heatmap-btn active">Hourly</button>
<button id="heatmapDailyBtn" class="heatmap-btn">Daily</button>
<button id="heatmapWeeklyBtn" class="heatmap-btn">Weekly</button>
</div>
</div>

<div id="hourlyView">
<div id="hourlyHeatmap"></div>
</div>

<div id="dailyView" class="hidden">
<div id="dailyHeatmap"></div>
</div>

<div id="weeklyView" class="hidden">
<div id="weeklyHeatmap"></div>
</div>

<div class="heatmap-legend">
<div class="legend-item"><div class="legend-color low"></div><span>Low</span></div>
<div class="legend-item"><div class="legend-color medium"></div><span>Medium</span></div>
<div class="legend-item"><div class="legend-color high"></div><span>High</span></div>
<div class="legend-item"><div class="legend-color critical"></div><span>Critical</span></div>
</div>
</div>
</div>

<div id="view-registry" class="tab-view hidden glass-card overflow-hidden">
Expand Down Expand Up @@ -305,7 +427,7 @@ <h2 class="text-4xl font-black text-white uppercase mb-4 tracking-tighter">Expor
<button onclick="exportForensicJSON()" class="px-8 py-3 bg-blue-600 hover:bg-blue-500 text-white font-black uppercase text-xs rounded transition-all">Download JSON</button>
<button onclick="exportRegistryCSV()" class="px-8 py-3 bg-slate-800 hover:bg-slate-700 text-white font-black uppercase text-xs rounded border border-white/10 transition-all">Export CSV</button>
<button onclick="exportForensicPDF()" class="px-8 py-3 bg-red-600 hover:bg-red-500 text-white font-black uppercase text-xs rounded transition-all">
<i class="fas fa-file-pdf mr-2"></i> Download PDF
<i class="fas fa-file-pdf mr-2"></i> Download PDF
</button>
</div>
</div>
Expand Down Expand Up @@ -340,7 +462,7 @@ <h4 class="text-xs font-bold uppercase text-slate-400">Forensic Case Vault</h4>
<i class="fas fa-arrow-up arrow-icon"></i>
</button>

<!-- Terms of Service Modal - Enhanced -->
<!-- Terms of Service Modal -->
<div id="tosModal" class="fixed inset-0 z-[300] hidden items-center justify-center bg-black/80 backdrop-blur-md transition-all duration-300" style="backdrop-filter: blur(12px);">
<div class="tos-content relative w-full max-w-2xl max-h-[85vh] overflow-hidden flex flex-col p-0">
<button onclick="closeTOS()" class="absolute top-5 right-5 w-8 h-8 rounded-full flex items-center justify-center text-slate-500 hover:text-red-400 hover:bg-red-500/10 transition-all duration-200 z-10" aria-label="Close Terms of Service">
Expand Down Expand Up @@ -423,6 +545,9 @@ <h2 class="text-white text-lg font-bold mb-4">Are you sure you want to remove yo

<script src="../js/dashboard.js"></script>
<script src="../js/theme.js"></script>
<script src="../js/streaming.js"></script>
<script src="../js/anomalyFilters.js"></script>
<script src="../js/visualization.js"></script>

<!-- Preview Script -->
<script>
Expand Down
170 changes: 170 additions & 0 deletions js/anomalyFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/**
* Anomaly Filters - Real-time filtering for streaming anomalies
*/

let activeFilters = {
timeVoid: true,
logShaving: true,
hashMismatch: true,
critical: true
};

let filterStats = {
timeVoid: 0,
logShaving: 0,
hashMismatch: 0,
critical: 0
};

function initAnomalyFilters() {
const filters = document.querySelectorAll('.filter-chip');
filters.forEach(chip => {
chip.addEventListener('click', () => {
const filterType = chip.dataset.filter;
if (filterType === 'all') {
resetAllFilters();
} else {
toggleFilter(filterType);
}
applyFilters();
});
});
}

function toggleFilter(filterType) {
if (activeFilters.hasOwnProperty(filterType)) {
activeFilters[filterType] = !activeFilters[filterType];
updateFilterUI(filterType);
}
}

function resetAllFilters() {
activeFilters = {
timeVoid: true,
logShaving: true,
hashMismatch: true,
critical: true
};

document.querySelectorAll('.filter-chip').forEach(chip => {
if (chip.dataset.filter !== 'all') {
chip.classList.add('active');
}
});

const allChip = document.querySelector('.filter-chip[data-filter="all"]');
if (allChip) allChip.classList.add('active');
}

function updateFilterUI(filterType) {
const chip = document.querySelector(`.filter-chip[data-filter="${filterType}"]`);
if (chip) {
if (activeFilters[filterType]) {
chip.classList.add('active');
} else {
chip.classList.remove('active');
}
}

const allChip = document.querySelector('.filter-chip[data-filter="all"]');
const allActive = Object.values(activeFilters).every(v => v === true);
if (allChip) {
if (allActive) {
allChip.classList.add('active');
} else {
allChip.classList.remove('active');
}
}
}

function applyFilters() {
const logLines = document.querySelectorAll('.streaming-log-line');
let visibleCount = 0;

logLines.forEach(line => {
let shouldShow = true;

if (!activeFilters.timeVoid && line.classList.contains('anomaly-time-void')) {
shouldShow = false;
}
if (!activeFilters.logShaving && line.classList.contains('anomaly-log-shaving')) {
shouldShow = false;
}
if (!activeFilters.hashMismatch && line.classList.contains('anomaly-hash-mismatch')) {
shouldShow = false;
}
if (!activeFilters.critical && line.classList.contains('anomaly-critical')) {
shouldShow = false;
}

if (shouldShow) {
line.style.display = 'block';
visibleCount++;
} else {
line.style.display = 'none';
}
});

updateVisibleCount(visibleCount);
}

function updateVisibleCount(count) {
const countEl = document.getElementById('visibleLinesCount');
if (countEl) {
countEl.textContent = count;
}
}

function updateStats(anomalyType) {
if (filterStats.hasOwnProperty(anomalyType)) {
filterStats[anomalyType]++;
const statEl = document.getElementById(`stat-${anomalyType}`);
if (statEl) {
statEl.textContent = filterStats[anomalyType];
}
}
}

function resetStats() {
filterStats = {
timeVoid: 0,
logShaving: 0,
hashMismatch: 0,
critical: 0
};

['timeVoid', 'logShaving', 'hashMismatch', 'critical'].forEach(type => {
const statEl = document.getElementById(`stat-${type}`);
if (statEl) {
statEl.textContent = '0';
}
});
}

function detectAnomalyType(line) {
const lowerLine = line.toLowerCase();

if (lowerLine.includes('time void') || lowerLine.includes('temporal gap') || lowerLine.includes('blackout')) {
return 'timeVoid';
}
if (lowerLine.includes('log shaving') || lowerLine.includes('truncated') || lowerLine.includes('cut')) {
return 'logShaving';
}
if (lowerLine.includes('hash') || lowerLine.includes('checksum') || lowerLine.includes('integrity')) {
return 'hashMismatch';
}
if (lowerLine.includes('critical') || lowerLine.includes('severe') || lowerLine.includes('tampering')) {
return 'critical';
}
return null;
}

function getAnomalyClass(anomalyType) {
switch(anomalyType) {
case 'timeVoid': return 'anomaly-time-void';
case 'logShaving': return 'anomaly-log-shaving';
case 'hashMismatch': return 'anomaly-hash-mismatch';
case 'critical': return 'anomaly-critical';
default: return '';
}
}
Loading