diff --git a/static/css/index.css b/static/css/index.css index 8c9c72f..b0ac50d 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -3650,112 +3650,137 @@ header h1 .tagline { overscroll-behavior: contain; } -.wifi-networks-table { - width: 100%; - border-collapse: collapse; - font-size: 11px; +/* WiFi Network List */ +.wifi-network-list { + display: flex; + flex-direction: column; } -.wifi-networks-table thead { - position: sticky; - top: 0; - background: var(--bg-tertiary); - z-index: 1; -} - -.wifi-networks-table th { - padding: 8px 10px; - text-align: left; +.wifi-network-placeholder { + padding: 32px 16px; + text-align: center; color: var(--text-dim); - font-weight: 500; - border-bottom: 1px solid var(--border-color); - white-space: nowrap; - cursor: pointer; + font-size: 11px; + line-height: 1.6; } -.wifi-networks-table th:hover { - color: var(--accent-cyan); -} - -.wifi-networks-table th.sortable::after { - content: ' \2195'; - opacity: 0.3; -} - -.wifi-networks-table td { - padding: 8px 10px; - border-bottom: 1px solid var(--border-color); - vertical-align: middle; -} - -.wifi-network-row { +/* Network rows */ +.network-row { + padding: 9px 14px; + border-bottom: 1px solid var(--bg-secondary); + border-left: 3px solid transparent; cursor: pointer; transition: background 0.15s; } -.wifi-network-row:hover { - background: rgba(0, 255, 255, 0.05); +.network-row:hover { background: var(--bg-tertiary); } + +.network-row.selected { + background: rgba(74, 163, 255, 0.07); + border-left-color: var(--accent-cyan) !important; } -.wifi-network-row.selected { - background: rgba(0, 255, 255, 0.1); - border-left: 2px solid var(--accent-cyan); +.network-row.threat-open { border-left-color: var(--accent-red); } +.network-row.threat-safe { border-left-color: var(--accent-green); } +.network-row.threat-hidden { border-left-color: var(--border-color); } + +.row-top { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 5px; } -.wifi-network-row .essid { +.row-ssid { + font-size: 12px; font-weight: 500; color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 55%; } -.wifi-network-row .badge { - display: inline-block; - padding: 2px 5px; +.row-ssid.hidden-net { + color: var(--text-dim); + font-style: italic; +} + +.row-badges { display: flex; gap: 4px; align-items: center; flex-shrink: 0; } + +.badge { font-size: 9px; + padding: 2px 5px; border-radius: 3px; - margin-left: 6px; -} - -.wifi-network-row .badge-hidden { - background: rgba(255, 165, 0, 0.2); - color: var(--accent-orange); -} - -.wifi-network-row .badge-new { - background: rgba(0, 255, 0, 0.2); - color: var(--accent-green); -} - -.wifi-network-row .rssi-value { - font-family: monospace; font-weight: 600; + letter-spacing: 0.5px; + border: 1px solid transparent; } -.wifi-network-row .rssi-value.signal-strong { color: var(--accent-green); } -.wifi-network-row .rssi-value.signal-medium { color: var(--accent-yellow); } -.wifi-network-row .rssi-value.signal-weak { color: var(--accent-orange); } -.wifi-network-row .rssi-value.signal-very-weak { color: var(--accent-red); } +.badge.open { color: var(--accent-red); background: var(--accent-red-dim); border-color: var(--accent-red); } +.badge.wpa2 { color: var(--accent-green); background: var(--accent-green-dim); border-color: var(--accent-green); } +.badge.wpa3 { color: var(--accent-cyan); background: var(--accent-cyan-dim); border-color: var(--accent-cyan); } +.badge.wep { color: var(--accent-orange); background: var(--accent-amber-dim); border-color: var(--accent-orange); } +.badge.hidden-tag { color: var(--text-dim); background: transparent; border-color: var(--border-color); font-size: 8px; } -.wifi-network-row .security-badge { - display: inline-block; +.row-bottom { + display: flex; + align-items: center; + gap: 8px; +} + +.signal-bar-wrap { flex: 1; max-width: 130px; } + +.signal-track { + height: 4px; + background: var(--bg-elevated); + border-radius: 2px; + overflow: hidden; +} + +.signal-fill { height: 100%; border-radius: 2px; transition: width 0.3s; } +.signal-fill.strong { background: linear-gradient(90deg, var(--accent-green), #88d49b); } +.signal-fill.medium { background: linear-gradient(90deg, var(--accent-green), var(--accent-orange)); } +.signal-fill.weak { background: linear-gradient(90deg, var(--accent-orange), var(--accent-red)); } + +.row-meta { + display: flex; + gap: 10px; + margin-left: auto; + color: var(--text-dim); + font-size: 10px; +} + +.row-rssi { color: var(--text-secondary); } + +/* Sort controls */ +.wifi-sort-controls { + display: flex; + align-items: center; + gap: 4px; +} + +.wifi-sort-label { + font-size: 9px; + color: var(--text-dim); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.wifi-sort-btn { padding: 2px 6px; font-size: 9px; - border-radius: 3px; -} - -.wifi-network-row .security-badge.security-wpa3 { background: rgba(0, 255, 0, 0.15); color: var(--accent-green); } -.wifi-network-row .security-badge.security-wpa { background: rgba(0, 255, 255, 0.15); color: var(--accent-cyan); } -.wifi-network-row .security-badge.security-wep { background: rgba(255, 165, 0, 0.15); color: var(--accent-orange); } -.wifi-network-row .security-badge.security-open { background: rgba(255, 0, 0, 0.15); color: var(--accent-red); } - -.wifi-network-placeholder td { - text-align: center; - padding: 40px 20px; -} - -.wifi-network-placeholder .placeholder-text { + font-family: inherit; + background: none; + border: none; color: var(--text-dim); + cursor: pointer; + transition: color 0.15s; } +.wifi-sort-btn:hover { color: var(--text-primary); } +.wifi-sort-btn.active { color: var(--accent-cyan); } + .app-collection-state-row td { text-align: center; padding: 0; diff --git a/static/js/modes/wifi.js b/static/js/modes/wifi.js index dbd15f4..c9489d3 100644 --- a/static/js/modes/wifi.js +++ b/static/js/modes/wifi.js @@ -121,7 +121,7 @@ const WiFiMode = (function() { let recommendations = []; // UI state - let selectedNetwork = null; + let selectedBssid = null; let currentFilter = 'all'; let currentSort = { field: 'rssi', order: 'desc' }; let renderFramePending = false; @@ -196,9 +196,8 @@ const WiFiMode = (function() { clientCount: document.getElementById('wifiClientCount'), hiddenCount: document.getElementById('wifiHiddenCount'), - // Network table - networkTable: document.getElementById('wifiNetworkTable'), - networkTableBody: document.getElementById('wifiNetworkTableBody'), + // Network list + networkList: document.getElementById('wifiNetworkList'), networkFilters: document.getElementById('wifiNetworkFilters'), // Visualizations @@ -972,7 +971,7 @@ const WiFiMode = (function() { stats: true, radar: true, chart: true, - detail: selectedNetwork === network.bssid, + detail: selectedBssid === network.bssid, }); if (onNetworkUpdate) onNetworkUpdate(network); @@ -1004,7 +1003,7 @@ const WiFiMode = (function() { network.display_name = `${revealedSsid} (revealed)`; scheduleRender({ table: true, - detail: selectedNetwork === bssid, + detail: selectedBssid === bssid, }); // Show notification @@ -1039,34 +1038,27 @@ const WiFiMode = (function() { }); } - updateNetworkTable(); + renderNetworks(); } function initSortControls() { if (listenersBound.sort) return; - if (!elements.networkTable) return; - elements.networkTable.addEventListener('click', (e) => { - const th = e.target.closest('th[data-sort]'); - if (th) { - const field = th.dataset.sort; + document.querySelectorAll('.wifi-sort-btn').forEach(btn => { + btn.addEventListener('click', () => { + const field = btn.dataset.sort; if (currentSort.field === field) { currentSort.order = currentSort.order === 'desc' ? 'asc' : 'desc'; } else { currentSort.field = field; currentSort.order = 'desc'; } - updateNetworkTable(); - } + document.querySelectorAll('.wifi-sort-btn').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + scheduleRender({ table: true }); + }); }); - if (elements.networkTableBody) { - elements.networkTableBody.addEventListener('click', (e) => { - const row = e.target.closest('tr[data-bssid]'); - if (!row) return; - selectNetwork(row.dataset.bssid); - }); - } listenersBound.sort = true; } @@ -1083,12 +1075,12 @@ const WiFiMode = (function() { requestAnimationFrame(() => { renderFramePending = false; - if (pendingRender.table) updateNetworkTable(); + if (pendingRender.table) renderNetworks(); if (pendingRender.stats) updateStats(); if (pendingRender.radar) updateProximityRadar(); if (pendingRender.chart) updateChannelChart(); - if (pendingRender.detail && selectedNetwork) { - updateDetailPanel(selectedNetwork, { refreshClients: false }); + if (pendingRender.detail && selectedBssid) { + updateDetailPanel(selectedBssid, { refreshClients: false }); } pendingRender.table = false; @@ -1099,8 +1091,8 @@ const WiFiMode = (function() { }); } - function updateNetworkTable() { - if (!elements.networkTableBody) return; + function renderNetworks() { + if (!elements.networkList) return; // Filter networks let filtered = Array.from(networks.values()); @@ -1157,87 +1149,89 @@ const WiFiMode = (function() { }); if (filtered.length === 0) { - let message = 'Start scanning to discover networks'; - let type = 'empty'; - if (isScanning) { - message = 'Scanning for networks...'; - type = 'loading'; - } else if (networks.size > 0) { - message = 'No networks match current filters'; - } - if (typeof renderCollectionState === 'function') { - renderCollectionState(elements.networkTableBody, { - type, - message, - columns: 7, - }); - } else { - elements.networkTableBody.innerHTML = `
${escapeHtml(message)}
`; - } + let message = networks.size > 0 + ? 'No networks match current filters' + : (isScanning ? 'Scanning for networks...' : 'Start scanning to discover networks'); + elements.networkList.innerHTML = `

${escapeHtml(message)}

`; return; } - // Render table - elements.networkTableBody.innerHTML = filtered.map(n => createNetworkRow(n)).join(''); + // Render list + elements.networkList.innerHTML = filtered.map(n => createNetworkRow(n)).join(''); + + // Re-apply selected state after re-render + if (selectedBssid) { + const sel = elements.networkList.querySelector(`[data-bssid="${CSS.escape(selectedBssid)}"]`); + if (sel) sel.classList.add('selected'); + } } function createNetworkRow(network) { const rssi = network.rssi_current; const security = network.security || 'Unknown'; - const signalClass = rssi >= -50 ? 'signal-strong' : - rssi >= -70 ? 'signal-medium' : - rssi >= -85 ? 'signal-weak' : 'signal-very-weak'; - const securityClass = security === 'Open' ? 'security-open' : - security === 'WEP' ? 'security-wep' : - security.includes('WPA3') ? 'security-wpa3' : 'security-wpa'; + // Badge class + const sec = security.toLowerCase(); + const badgeClass = sec === 'open' || sec === '' ? 'open' + : sec.includes('wpa3') ? 'wpa3' + : sec.includes('wpa') ? 'wpa2' + : sec.includes('wep') ? 'wep' + : 'wpa2'; - const hiddenBadge = network.is_hidden ? 'Hidden' : ''; - const newBadge = network.is_new ? 'New' : ''; + // Threat class (left border) + const threatClass = badgeClass === 'open' ? 'threat-open' + : badgeClass === 'wpa2' || badgeClass === 'wpa3' ? 'threat-safe' + : 'threat-hidden'; - // Agent source badge - const agentName = network._agent || 'Local'; - const agentClass = agentName === 'Local' ? 'agent-local' : 'agent-remote'; + // Signal bar width + class + const pct = rssi != null ? Math.max(0, Math.min(100, (rssi + 100) / 80 * 100)) : 0; + const fillClass = rssi > -55 ? 'strong' : rssi > -70 ? 'medium' : 'weak'; + + const displayName = escapeHtml(network.display_name || network.essid || '[Hidden]'); + const isHidden = network.is_hidden; + const hiddenTag = isHidden ? 'HIDDEN' : ''; return ` - - - ${escapeHtml(network.display_name || network.essid || '[Hidden]')} - ${hiddenBadge}${newBadge} - - ${escapeHtml(network.bssid)} - ${network.channel || '-'} - - ${rssi != null ? rssi : '-'} - - - ${escapeHtml(security)} - - ${network.client_count || 0} - - ${escapeHtml(agentName)} - - +
+
+ ${displayName} +
+ ${escapeHtml(security)} + ${hiddenTag} +
+
+
+
+
+
+
+
+
+ ch ${network.channel || '?'} + ${network.client_count || 0} ↔ + ${rssi != null ? rssi : '?'} +
+
+
`; } function updateNetworkRow(network) { scheduleRender({ table: true, - detail: selectedNetwork === network.bssid, + detail: selectedBssid === network.bssid, }); } function selectNetwork(bssid) { - selectedNetwork = bssid; + selectedBssid = bssid; // Update row selection - elements.networkTableBody?.querySelectorAll('.wifi-network-row').forEach(row => { + elements.networkList?.querySelectorAll('.network-row').forEach(row => { row.classList.toggle('selected', row.dataset.bssid === bssid); }); @@ -1308,11 +1302,11 @@ const WiFiMode = (function() { } function closeDetail() { - selectedNetwork = null; + selectedBssid = null; if (elements.detailDrawer) { elements.detailDrawer.classList.remove('open'); } - elements.networkTableBody?.querySelectorAll('.wifi-network-row').forEach(row => { + elements.networkList?.querySelectorAll('.network-row').forEach(row => { row.classList.remove('selected'); }); } @@ -1431,7 +1425,7 @@ const WiFiMode = (function() { function updateClientInList(client) { // Check if this client belongs to the currently selected network - if (!selectedNetwork || client.associated_bssid !== selectedNetwork) { + if (!selectedBssid || client.associated_bssid !== selectedBssid) { return; } @@ -1459,7 +1453,7 @@ const WiFiMode = (function() { } } else { // New client for this network - re-fetch the full list - fetchClientsForNetwork(selectedNetwork); + fetchClientsForNetwork(selectedBssid); } } @@ -1738,7 +1732,7 @@ const WiFiMode = (function() { probeRequests = []; channelStats = []; recommendations = []; - if (selectedNetwork) { + if (selectedBssid) { closeDetail(); } scheduleRender({ table: true, stats: true, radar: true, chart: true }); @@ -1788,7 +1782,7 @@ const WiFiMode = (function() { } }); clientsToRemove.forEach(mac => clients.delete(mac)); - if (selectedNetwork && !networks.has(selectedNetwork)) { + if (selectedBssid && !networks.has(selectedBssid)) { closeDetail(); } scheduleRender({ table: true, stats: true, radar: true, chart: true }); diff --git a/templates/index.html b/templates/index.html index 660312f..8a89130 100644 --- a/templates/index.html +++ b/templates/index.html @@ -846,7 +846,7 @@
- +
Discovered Networks
@@ -857,28 +857,19 @@
+
+ Sort: + + + +
- - - - - - - - - - - - - - - - - -
SSIDBSSIDChSignalSecurityClientsSource
-
Start scanning to discover networks
-
+
+
+

No networks detected.
Start a scan to begin.

+
+