mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
feat(wifi): animated SVG proximity radar with sweep rotation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3855,6 +3855,16 @@ header h1 .tagline {
|
||||
.wifi-zone.mid .wifi-zone-count { color: var(--accent-yellow); }
|
||||
.wifi-zone.far .wifi-zone-count { color: var(--accent-red); }
|
||||
|
||||
.wifi-radar-sweep {
|
||||
transform-origin: 105px 105px;
|
||||
animation: wifi-radar-rotate 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes wifi-radar-rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* WiFi Analysis Panel (RIGHT) */
|
||||
.wifi-analysis-panel {
|
||||
display: flex;
|
||||
|
||||
@@ -167,7 +167,6 @@ const WiFiMode = (function() {
|
||||
initScanModeTabs();
|
||||
initNetworkFilters();
|
||||
initSortControls();
|
||||
initProximityRadar();
|
||||
initChannelChart();
|
||||
scheduleRender({ table: true, stats: true, radar: true, chart: true });
|
||||
|
||||
@@ -201,7 +200,6 @@ const WiFiMode = (function() {
|
||||
networkFilters: document.getElementById('wifiNetworkFilters'),
|
||||
|
||||
// Visualizations
|
||||
proximityRadar: document.getElementById('wifiProximityRadar'),
|
||||
channelChart: document.getElementById('wifiChannelChart'),
|
||||
channelBandTabs: document.getElementById('wifiChannelBandTabs'),
|
||||
|
||||
@@ -1077,7 +1075,7 @@ const WiFiMode = (function() {
|
||||
|
||||
if (pendingRender.table) renderNetworks();
|
||||
if (pendingRender.stats) updateStats();
|
||||
if (pendingRender.radar) updateProximityRadar();
|
||||
if (pendingRender.radar) renderRadar(Array.from(networks.values()));
|
||||
if (pendingRender.chart) updateChannelChart();
|
||||
if (pendingRender.detail && selectedBssid) {
|
||||
updateDetailPanel(selectedBssid, { refreshClients: false });
|
||||
@@ -1506,36 +1504,58 @@ const WiFiMode = (function() {
|
||||
// Proximity Radar
|
||||
// ==========================================================================
|
||||
|
||||
function initProximityRadar() {
|
||||
if (!elements.proximityRadar) return;
|
||||
// Simple hash of BSSID string → stable angle in radians
|
||||
function bssidToAngle(bssid) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < bssid.length; i++) {
|
||||
hash = (hash * 31 + bssid.charCodeAt(i)) & 0xffffffff;
|
||||
}
|
||||
return (hash >>> 0) / 0xffffffff * 2 * Math.PI;
|
||||
}
|
||||
|
||||
// Initialize radar component
|
||||
if (typeof ProximityRadar !== 'undefined') {
|
||||
ProximityRadar.init('wifiProximityRadar', {
|
||||
mode: 'wifi',
|
||||
size: 280,
|
||||
onDeviceClick: (bssid) => selectNetwork(bssid),
|
||||
function renderRadar(networksList) {
|
||||
const dotsGroup = document.getElementById('wifiRadarDots');
|
||||
if (!dotsGroup) return;
|
||||
|
||||
const dots = [];
|
||||
const zoneCounts = { immediate: 0, near: 0, far: 0 };
|
||||
|
||||
networksList.forEach(network => {
|
||||
const rssi = network.rssi_current ?? -100;
|
||||
const strength = Math.max(0, Math.min(1, (rssi + 100) / 80));
|
||||
const dotR = 5 + (1 - strength) * 90; // stronger = closer to centre
|
||||
const angle = bssidToAngle(network.bssid);
|
||||
const cx = 105 + dotR * Math.cos(angle);
|
||||
const cy = 105 + dotR * Math.sin(angle);
|
||||
|
||||
// Zone counts
|
||||
if (dotR < 35) zoneCounts.immediate++;
|
||||
else if (dotR < 70) zoneCounts.near++;
|
||||
else zoneCounts.far++;
|
||||
|
||||
// Visual radius by zone
|
||||
const vr = dotR < 35 ? 6 : dotR < 70 ? 4.5 : 3;
|
||||
|
||||
// Colour by security
|
||||
const sec = (network.security || '').toLowerCase();
|
||||
const colour = sec === 'open' || sec === '' ? '#e25d5d'
|
||||
: sec.includes('wpa') ? '#38c180'
|
||||
: sec.includes('wep') ? '#d6a85e'
|
||||
: '#484f58';
|
||||
|
||||
dots.push(`
|
||||
<circle cx="${cx.toFixed(1)}" cy="${cy.toFixed(1)}" r="${vr * 1.5}"
|
||||
fill="${colour}" opacity="0.12"/>
|
||||
<circle cx="${cx.toFixed(1)}" cy="${cy.toFixed(1)}" r="${vr}"
|
||||
fill="${colour}" opacity="0.9" filter="url(#wifi-glow-sm)"/>
|
||||
`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateProximityRadar() {
|
||||
if (typeof ProximityRadar === 'undefined') return;
|
||||
dotsGroup.innerHTML = dots.join('');
|
||||
|
||||
// Convert networks to radar-compatible format
|
||||
const devices = Array.from(networks.values()).map(n => ({
|
||||
device_key: n.bssid,
|
||||
device_id: n.bssid,
|
||||
name: n.essid || '[Hidden]',
|
||||
rssi_current: n.rssi_current,
|
||||
rssi_ema: n.rssi_ema,
|
||||
proximity_band: n.proximity_band,
|
||||
estimated_distance_m: n.estimated_distance_m,
|
||||
is_new: n.is_new,
|
||||
heuristic_flags: n.heuristic_flags || [],
|
||||
}));
|
||||
|
||||
ProximityRadar.updateDevices(devices);
|
||||
if (elements.zoneImmediate) elements.zoneImmediate.textContent = zoneCounts.immediate;
|
||||
if (elements.zoneNear) elements.zoneNear.textContent = zoneCounts.near;
|
||||
if (elements.zoneFar) elements.zoneFar.textContent = zoneCounts.far;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
|
||||
@@ -876,7 +876,50 @@
|
||||
<!-- CENTER: Proximity Radar -->
|
||||
<div class="wifi-radar-panel">
|
||||
<h5>Proximity Radar</h5>
|
||||
<div id="wifiProximityRadar" class="wifi-radar-container"></div>
|
||||
<div id="wifiProximityRadar" class="wifi-radar-container">
|
||||
<svg width="100%" viewBox="0 0 210 210" id="wifiRadarSvg">
|
||||
<defs>
|
||||
<clipPath id="wifi-radar-clip">
|
||||
<circle cx="105" cy="105" r="100"/>
|
||||
</clipPath>
|
||||
<filter id="wifi-glow-sm">
|
||||
<feGaussianBlur stdDeviation="2.5" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
<filter id="wifi-glow-md">
|
||||
<feGaussianBlur stdDeviation="4" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Background rings (static) -->
|
||||
<circle cx="105" cy="105" r="100" fill="none" stroke="#00b4d8" stroke-width="0.5" opacity="0.12"/>
|
||||
<circle cx="105" cy="105" r="70" fill="none" stroke="#00b4d8" stroke-width="0.5" opacity="0.18"/>
|
||||
<circle cx="105" cy="105" r="40" fill="none" stroke="#00b4d8" stroke-width="0.5" opacity="0.25"/>
|
||||
<circle cx="105" cy="105" r="15" fill="none" stroke="#00b4d8" stroke-width="0.5" opacity="0.35"/>
|
||||
|
||||
<!-- Crosshairs -->
|
||||
<line x1="5" y1="105" x2="205" y2="105" stroke="#00b4d8" stroke-width="0.3" opacity="0.1"/>
|
||||
<line x1="105" y1="5" x2="105" y2="205" stroke="#00b4d8" stroke-width="0.3" opacity="0.1"/>
|
||||
|
||||
<!-- Rotating sweep group -->
|
||||
<g class="wifi-radar-sweep" clip-path="url(#wifi-radar-clip)">
|
||||
<!-- Primary trailing arc: 60° -->
|
||||
<path d="M105,105 L105,5 A100,100 0 0,1 191.6,155 Z" fill="#00b4d8" opacity="0.08"/>
|
||||
<!-- Secondary trailing arc: 90° -->
|
||||
<path d="M105,105 L105,5 A100,100 0 0,1 205,105 Z" fill="#00b4d8" opacity="0.04"/>
|
||||
<!-- Sweep line -->
|
||||
<line x1="105" y1="105" x2="105" y2="5" stroke="#00b4d8" stroke-width="1.5" opacity="0.7"
|
||||
filter="url(#wifi-glow-sm)"/>
|
||||
</g>
|
||||
|
||||
<!-- Centre dot -->
|
||||
<circle cx="105" cy="105" r="3" fill="#00b4d8" opacity="0.8"/>
|
||||
|
||||
<!-- Network dots (managed by renderRadar()) -->
|
||||
<g id="wifiRadarDots"></g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="wifi-zone-summary">
|
||||
<div class="wifi-zone near">
|
||||
<span class="wifi-zone-count" id="wifiZoneImmediate">0</span>
|
||||
|
||||
Reference in New Issue
Block a user