Files

518 lines
24 KiB
HTML
Raw Permalink Normal View History

2026-04-03 14:38:38 +03:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
2026-04-05 13:06:25 +03:00
<title>ESP32</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%230c110e'/%3E%3Ccircle cx='16' cy='23' r='2.5' fill='%234ec96a'/%3E%3Cpath d='M9 17.5 Q16 10.5 23 17.5' fill='none' stroke='%234ec96a' stroke-width='2.5' stroke-linecap='round'/%3E%3Cpath d='M4 12.5 Q16 1.5 28 12.5' fill='none' stroke='%234ec96a' stroke-width='2.5' stroke-linecap='round' opacity='0.5'/%3E%3C/svg%3E">
2026-04-03 14:38:38 +03:00
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet" />
2026-04-03 14:38:38 +03:00
<link rel="stylesheet" href="/static/dashboard.css" />
</head>
<body>
<div id="app">
<!-- Topbar -->
<div id="topbar">
<span class="title">ESP32 Recon</span>
<span class="sep">/</span>
<span class="build-info">WiFi Recon Cluster</span>
<div id="live-dot"></div>
<span id="live-label">LIVE</span>
</div>
<!-- Navbar -->
<div id="navbar">
<button class="tab-btn active" id="tab-feed" onclick="showFeed()">Feed</button>
<button class="tab-btn" id="tab-networks" onclick="showNetworks()">Networks</button>
<button class="tab-btn" id="tab-crossnode" onclick="showCrossNode()">Cross-node</button>
<button class="tab-btn" id="tab-clients" onclick="showClients()">Clients</button>
<button class="tab-btn" id="tab-presence" onclick="showPresence()">Presence</button>
<button class="tab-btn" id="tab-alerts" onclick="showAlerts()">Alerts</button>
2026-04-05 20:59:22 +03:00
<button class="tab-btn" id="tab-sessions" onclick="showSessions()">Sessions</button>
2026-04-05 11:29:59 +03:00
<button class="tab-btn" id="tab-analysis" onclick="showAnalysis()">Analysis</button>
<button class="tab-btn" id="tab-search" onclick="showSearch()">Search</button>
<button class="tab-btn" id="tab-ble" onclick="showBle()">BLE</button>
2026-04-03 14:38:38 +03:00
</div>
<!-- Sidebar -->
<div id="sidebar">
<div class="sidebar-section">
<div class="sidebar-label">Summary</div>
<div class="stat-row"><span>nodes</span><span id="sum-nodes"></span></div>
<div class="stat-row"><span>online</span><span id="sum-online"></span></div>
<div class="stat-row"><span>events (session)</span><span id="sum-events"></span></div>
<div class="stat-row muted"><span>data window</span><span>30 days</span></div>
2026-04-03 14:38:38 +03:00
</div>
<div class="sidebar-section">
<div class="sidebar-label">Nodes</div>
<div id="node-list"></div>
</div>
</div>
<!-- Main -->
<div id="main">
<!-- Feed view -->
<div id="view-general" class="view active">
<div class="section-header">
<span class="section-title">Live Feed</span>
<span class="section-count" id="general-count"></span>
</div>
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th>Time</th>
<th>Node</th>
<th>SSID</th>
<th>BSSID</th>
<th>RSSI</th>
<th>Ch</th>
<th>Enc</th>
<th>Imp</th>
</tr>
</thead>
<tbody id="general-tbody"></tbody>
</table>
<div class="empty" id="general-empty" style="display:none">No events yet.</div>
</div>
</div>
<!-- Node detail view -->
<div id="view-detail" class="view">
<button id="back-btn" onclick="showGeneral()">&#8592; All nodes</button>
<div class="section-header">
<span class="section-title">Node</span>
<span class="section-count" id="detail-node-id"></span>
</div>
<div id="detail-stats-grid">
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">Status</div><div class="value" id="ds-status"></div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">Total Events</div><div class="value" id="ds-total"></div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">Unique SSIDs</div><div class="value" id="ds-ssids"></div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">Unique BSSIDs</div><div class="value" id="ds-bssids"></div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">Avg RSSI</div><div class="value" id="ds-rssi-avg"></div><div class="sub" id="ds-rssi-range"></div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">First Seen</div><div class="value value-sm" id="ds-first"></div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">Last Seen</div><div class="value value-sm" id="ds-last"></div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">Uptime</div><div class="value" id="ds-uptime"></div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">Free Heap</div><div class="value" id="ds-heap"></div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
<div class="stat-outer"><div class="stat-dot"></div><div class="stat-card"><div class="stat-ray"></div><div class="label">AP Signal</div><div class="value" id="ds-ap-rssi"></div><div class="sub">node → router</div><div class="line topl"></div><div class="line leftl"></div><div class="line bottoml"></div><div class="line rightl"></div></div></div>
2026-04-03 14:38:38 +03:00
</div>
<div class="section-header" style="margin-top:4px">
<span class="section-title">Recent Events</span>
<span class="section-count" id="detail-count"></span>
</div>
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th>Time</th>
<th>SSID</th>
<th>BSSID</th>
<th>RSSI</th>
<th>Ch</th>
<th>Enc</th>
<th>Imp</th>
<th>Conf</th>
</tr>
</thead>
<tbody id="detail-tbody"></tbody>
</table>
</div>
</div>
<!-- Clients view -->
<div id="view-clients" class="view">
<div class="section-header">
<span class="section-title">Client Devices</span>
<span class="section-count" id="clients-count"></span>
<span style="margin-left:auto; font-size:10px; color:var(--text-muted)">
note: modern devices use randomised MACs — one physical device may appear as multiple entries
</span>
</div>
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th>MAC</th>
2026-04-05 13:06:25 +03:00
<th id="clients-th-vendor" class="group-col" onclick="toggleClientsGroup('vendor')" title="Group by vendor">Vendor ⊞</th>
<th id="clients-th-probing" class="group-col" onclick="toggleClientsGroup('ssid')" title="Group by probed SSID">Probing for ⊞</th>
2026-04-03 14:38:38 +03:00
<th>Best RSSI</th>
<th>Avg RSSI</th>
<th>Nodes</th>
<th>Times Seen</th>
<th>Last Seen</th>
</tr>
</thead>
<tbody id="clients-tbody"></tbody>
</table>
<div class="empty" id="clients-empty" style="display:none">No probe events yet. Nodes need to be flashed with probe sniffing enabled.</div>
</div>
</div>
<!-- Cross-node view -->
<div id="view-crossnode" class="view">
<div class="section-header">
<span class="section-title">Cross-node RSSI</span>
<span class="section-count" id="crossnode-count"></span>
<label style="margin-left:auto; font-size:10px; color:var(--text-dim); display:flex; align-items:center; gap:6px; cursor:pointer;">
<input type="checkbox" id="crossnode-filter" onchange="renderCrossNodeTable()" style="accent-color:var(--green-hi)">
multi-node only
</label>
</div>
<div class="tbl-wrap">
<table id="crossnode-table">
<thead id="crossnode-thead"></thead>
<tbody id="crossnode-tbody"></tbody>
</table>
<div class="empty" id="crossnode-empty" style="display:none">No data yet.</div>
</div>
</div>
<!-- Network RSSI chart view -->
<div id="view-network-chart" class="view">
<button class="back-btn" onclick="showNetworks()">&#8592; Networks</button>
<div class="section-header">
<span class="section-title" id="chart-title">RSSI History</span>
<span class="section-count" id="chart-subtitle"></span>
<div class="range-btns" style="margin-left:auto">
<button class="range-btn" data-hours="1" onclick="setChartRange(1)">1h</button>
<button class="range-btn active" data-hours="2" onclick="setChartRange(2)">2h</button>
<button class="range-btn" data-hours="6" onclick="setChartRange(6)">6h</button>
<button class="range-btn" data-hours="24" onclick="setChartRange(24)">24h</button>
</div>
</div>
<div id="chart-legend"></div>
<canvas id="rssi-chart" height="240"></canvas>
<div class="empty" id="chart-empty" style="display:none">No data for this time range.</div>
</div>
<!-- Networks view -->
<div id="view-networks" class="view">
<div class="section-header">
<span class="section-title">Networks</span>
<span class="section-count" id="networks-count"></span>
</div>
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th class="sortable" data-col="ssid" data-arrow="">SSID</th>
<th class="sortable" data-col="bssid" data-arrow="">BSSID</th>
<th class="sortable" data-col="best_rssi" data-arrow="">Best RSSI</th>
<th class="sortable" data-col="avg_rssi" data-arrow="">Avg RSSI</th>
<th class="sortable" data-col="channel" data-arrow="">Ch</th>
<th class="sortable" data-col="encryption" data-arrow="">Enc</th>
<th class="sortable sort-active" data-col="times_seen" data-arrow="↓">Times Seen</th>
<th class="sortable" data-col="node_count" data-arrow="">Nodes</th>
<th class="sortable" data-col="last_seen" data-arrow="">Last Seen</th>
<th></th>
</tr>
</thead>
<tbody id="networks-tbody"></tbody>
</table>
<div class="empty" id="networks-empty" style="display:none">No networks seen yet.</div>
</div>
</div>
<!-- Presence view -->
<div id="view-presence" class="view">
<!-- Present now -->
<div class="section-header">
<span class="section-title">Present Now</span>
<span class="section-count" id="present-count"></span>
<span class="presence-sub">real MACs · seen 2+ times in last hour</span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>MAC</th><th>Vendor</th><th>Best RSSI</th><th>Avg RSSI</th>
<th>Times Seen</th><th>Nodes</th><th>Last Seen</th>
</tr></thead>
<tbody id="present-tbody"></tbody>
</table>
<div class="empty" id="present-empty" style="display:none">No devices currently present.</div>
</div>
<!-- New arrivals -->
<div class="section-header" style="margin-top:8px">
<span class="section-title">New Arrivals</span>
<span class="section-count" id="arrivals-count"></span>
<span class="presence-sub">first seen in last 24h</span>
</div>
<div class="presence-split">
<div>
<div class="presence-sub-label">Devices</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>MAC</th><th>Vendor</th><th>First Seen</th>
<th>Best RSSI</th><th>Times Seen</th>
</tr></thead>
<tbody id="arrivals-macs-tbody"></tbody>
</table>
<div class="empty" id="arrivals-macs-empty" style="display:none">No new devices.</div>
</div>
</div>
<div>
<div class="presence-sub-label">Networks</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>SSID</th><th>BSSID</th><th>First Seen</th>
<th>Best RSSI</th><th>Ch</th><th>Enc</th>
</tr></thead>
<tbody id="arrivals-nets-tbody"></tbody>
</table>
<div class="empty" id="arrivals-nets-empty" style="display:none">No new networks.</div>
</div>
</div>
</div>
<!-- Regulars -->
<div class="section-header" style="margin-top:8px">
<span class="section-title">Regulars</span>
<span class="section-count" id="regulars-count"></span>
<span class="presence-sub">seen on 2+ distinct days</span>
</div>
<div class="presence-split">
<div>
<div class="presence-sub-label">Devices</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>MAC</th><th>Vendor</th><th>Days Seen</th>
<th>First Seen</th><th>Last Seen</th><th>Best RSSI</th>
</tr></thead>
<tbody id="regulars-macs-tbody"></tbody>
</table>
<div class="empty" id="regulars-macs-empty" style="display:none">Regulars appear after 2+ days of data.</div>
</div>
</div>
<div>
<div class="presence-sub-label">Networks</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>SSID</th><th>BSSID</th><th>Days Seen</th>
<th>First Seen</th><th>Best RSSI</th><th>Ch</th>
</tr></thead>
<tbody id="regulars-nets-tbody"></tbody>
</table>
<div class="empty" id="regulars-nets-empty" style="display:none">Regulars appear after 2+ days of data.</div>
</div>
</div>
</div>
</div><!-- /#view-presence -->
<!-- Alerts view -->
<div id="view-alerts" class="view">
<!-- Summary strip -->
<div id="alerts-summary" class="alerts-summary"></div>
<!-- Top targets / attackers -->
<div class="presence-split" style="margin-top:8px">
<div>
<div class="section-header">
<span class="section-title">Most Impersonated Networks</span>
<span class="section-count" id="targets-count"></span>
<span class="presence-sub">all time · APs spoofed as deauth source</span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>BSSID</th><th>SSID</th><th>Frames</th>
<th>Dom. Reason</th><th>Unique Srcs</th><th>Nodes</th><th>Last Seen</th>
</tr></thead>
<tbody id="targets-tbody"></tbody>
</table>
<div class="empty" id="targets-empty" style="display:none">No data yet.</div>
</div>
</div>
<div>
<div class="section-header">
<span class="section-title">Most Targeted Devices</span>
<span class="section-count" id="attackers-count"></span>
<span class="presence-sub">all time · actual victim devices by dst MAC</span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>Dst MAC</th><th>Vendor</th><th>Frames</th>
<th>Dom. Reason</th><th>Networks Used</th><th>Nodes</th><th>Last Seen</th>
</tr></thead>
<tbody id="attackers-tbody"></tbody>
</table>
<div class="empty" id="attackers-empty" style="display:none">No data yet.</div>
</div>
</div>
</div>
2026-04-05 11:29:59 +03:00
<!-- Reason code breakdown -->
<div class="section-header" style="margin-top:8px">
<span class="section-title">Reason Code Breakdown</span>
<span class="section-count" id="reasons-count"></span>
<span class="presence-sub">all time · normal / suspicious / attack classification</span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>Code</th><th>Description</th><th>Class</th>
<th>Frames</th><th>Unique BSSIDs</th><th>Unique Targets</th><th>Last Seen</th>
</tr></thead>
<tbody id="reasons-tbody"></tbody>
</table>
<div class="empty" id="reasons-empty" style="display:none">No data yet.</div>
</div>
<!-- Detected bursts -->
<div class="section-header">
<span class="section-title">Detected Bursts</span>
<span class="section-count" id="bursts-count"></span>
<span class="presence-sub">≥10 deauth/disassoc frames for same BSSID in last 5 min</span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>BSSID</th><th>SSID</th><th>Type</th><th>Frames</th>
<th>Unique Srcs</th><th>Nodes</th><th>First</th><th>Last</th>
</tr></thead>
<tbody id="bursts-tbody"></tbody>
</table>
<div class="empty" id="bursts-empty" style="display:none">No bursts detected in last 5 minutes.</div>
</div>
<!-- Raw deauth feed -->
<div class="section-header" style="margin-top:8px">
<span class="section-title">Recent Deauth / Disassoc Events</span>
<span class="section-count" id="deauth-count"></span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>Time</th><th>Node</th><th>Type</th><th>Src</th>
<th>Dst</th><th>BSSID</th><th>Reason</th><th>RSSI</th>
</tr></thead>
<tbody id="deauth-tbody"></tbody>
</table>
<div class="empty" id="deauth-empty" style="display:none">No deauth events yet. Nodes need to be reflashed with deauth detection enabled.</div>
</div>
</div><!-- /#view-alerts -->
2026-04-05 20:59:22 +03:00
<!-- Sessions view -->
<div id="view-sessions" class="view">
<div class="section-header">
<span class="section-title">Attack Sessions</span>
<span class="section-count" id="sessions-count"></span>
<span class="presence-sub">last 7 days · deauth events grouped by source MAC + 2-min gap</span>
</div>
<div class="tbl-wrap">
<table id="sessions-table">
<thead>
<tr>
<th></th>
<th>Source MAC</th>
<th>Vendor</th>
<th class="sortable" data-col="start" data-arrow="">Start</th>
<th class="sortable" data-col="duration_secs" data-arrow="">Duration</th>
<th class="sortable sort-active" data-col="frame_count" data-arrow="↓">Frames</th>
<th class="sortable" data-col="bssids" data-arrow="">BSSIDs</th>
<th class="sortable" data-col="targets" data-arrow="">Targets</th>
<th class="sortable" data-col="node_count" data-arrow="">Nodes</th>
<th class="sortable" data-col="peak_rssi" data-arrow="">Peak RSSI</th>
<th>Dominant Reason</th>
2026-04-05 20:59:22 +03:00
<th class="sortable" data-col="severity" data-arrow="">Severity</th>
</tr>
</thead>
<tbody id="sessions-tbody"></tbody>
</table>
<div class="empty" id="sessions-empty" style="display:none">No sessions in the last 7 days.</div>
</div>
</div>
2026-04-05 11:29:59 +03:00
<!-- Analysis view -->
<div id="view-analysis" class="view">
<!-- Deauth activity heatmap -->
<div class="section-header">
<span class="section-title">Deauth Activity Heatmap</span>
<span class="section-count" id="heatmap-subtitle"></span>
<span class="presence-sub">frame count per day × hour — pattern reveals manual vs automated activity</span>
</div>
<canvas id="deauth-heatmap"></canvas>
<div class="empty" id="heatmap-empty" style="display:none">No deauth data yet.</div>
</div><!-- /#view-analysis -->
<!-- Search view -->
<div id="view-search" class="view">
<div class="search-bar">
<input type="text" id="search-input" class="search-input"
placeholder="MAC address, SSID, BSSID — full or partial..."
onkeydown="if(event.key==='Enter') doSearch()" />
<button class="search-btn" onclick="doSearch()">Search</button>
</div>
<div id="search-results">
<div class="empty" id="search-empty">Enter a value above to search across all collected data.</div>
</div>
</div>
<!-- BLE view -->
<div id="view-ble" class="view">
<!-- Manufacturer breakdown -->
<div class="section-header">
<span class="section-title">Manufacturers</span>
<span class="section-count" id="ble-breakdown-count"></span>
</div>
<div id="ble-breakdown" class="ble-breakdown-row"></div>
<!-- Devices table -->
<div class="section-header" style="margin-top:18px">
<span class="section-title">Devices</span>
<span class="section-count" id="ble-devices-count"></span>
<span class="presence-sub">unique MACs — last 7 days</span>
</div>
<div class="empty" id="ble-devices-empty" style="display:none">No BLE data yet. Flash a node with BLE firmware first.</div>
<table class="data-table" id="ble-devices-table">
<thead>
<tr>
<th>Type</th><th>MAC</th><th>Name</th><th>Vendor</th>
<th>Addr</th><th>Best RSSI</th><th>Seen</th><th>Nodes</th><th>Last seen</th>
</tr>
</thead>
<tbody id="ble-devices-body"></tbody>
</table>
<!-- Live feed -->
<div class="section-header" style="margin-top:24px">
<span class="section-title">Recent Advertisements</span>
<span class="section-count" id="ble-feed-count"></span>
</div>
<table class="data-table" id="ble-feed-table">
<thead>
<tr>
<th>Time</th><th>Node</th><th>Type</th><th>MAC</th>
<th>Name</th><th>RSSI</th><th>Addr</th>
</tr>
</thead>
<tbody id="ble-feed-body"></tbody>
</table>
</div><!-- /#view-ble -->
2026-04-03 14:38:38 +03:00
</div><!-- /#main -->
</div><!-- /#app -->
<script src="/static/dashboard.js"></script>
</body>
</html>