313 lines
12 KiB
HTML
313 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>ESP32 Recon</title>
|
|
<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=IBM+Plex+Mono:wght@300;400;500&display=swap" rel="stylesheet" />
|
|
<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>
|
|
</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>
|
|
<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()">← 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-card"><div class="label">Status</div><div class="value" id="ds-status">—</div></div>
|
|
<div class="stat-card"><div class="label">Total Events</div><div class="value" id="ds-total">—</div></div>
|
|
<div class="stat-card"><div class="label">Unique SSIDs</div><div class="value" id="ds-ssids">—</div></div>
|
|
<div class="stat-card"><div class="label">Unique BSSIDs</div><div class="value" id="ds-bssids">—</div></div>
|
|
<div class="stat-card"><div class="label">Avg RSSI</div><div class="value" id="ds-rssi-avg">—</div><div class="sub" id="ds-rssi-range"></div></div>
|
|
<div class="stat-card"><div class="label">First Seen</div><div class="value" style="font-size:12px" id="ds-first">—</div></div>
|
|
<div class="stat-card"><div class="label">Last Seen</div><div class="value" style="font-size:12px" id="ds-last">—</div></div>
|
|
<div class="stat-card"><div class="label">Uptime</div><div class="value" id="ds-uptime">—</div></div>
|
|
<div class="stat-card"><div class="label">Free Heap</div><div class="value" id="ds-heap">—</div></div>
|
|
<div class="stat-card"><div class="label">AP Signal</div><div class="value" id="ds-ap-rssi">—</div><div class="sub">node → router</div></div>
|
|
</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>
|
|
<th>Vendor</th>
|
|
<th>Probing for</th>
|
|
<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()">← 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 -->
|
|
|
|
</div><!-- /#main -->
|
|
</div><!-- /#app -->
|
|
|
|
<script src="/static/dashboard.js"></script>
|
|
</body>
|
|
</html>
|