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 = ``;
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 || '-'} |
-
-
- ${escapeHtml(security)}
- |
- ${network.client_count || 0} |
-
- ${escapeHtml(agentName)}
- |
-
+
+
+
${displayName}
+
+ ${escapeHtml(security)}
+ ${hiddenTag}
+
+
+
+
+
+ ch ${network.channel || '?'}
+ ${network.client_count || 0} ↔
+
+
+
+
`;
}
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 @@
-
+
+
+ Sort:
+
+
+
+
-
-
-
- | SSID |
- BSSID |
- Ch |
- Signal |
- Security |
- Clients |
- Source |
-
-
-
-
- |
- Start scanning to discover networks
- |
-
-
-
+
+
+
No networks detected.
Start a scan to begin.
+
+