Release v2.13.0 - WiFi client display in AP detail drawer

Features:
- Display connected clients for access points in detail drawer
- Real-time client updates via SSE streaming
- Client cards show MAC, vendor, RSSI, probed SSIDs, and last seen
- Count badge in Connected Clients header

Other changes:
- Updated aircraft database
- CSS and template refinements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-04 17:44:23 +00:00
parent bf7026cc9f
commit 340b300aa4
27 changed files with 365 additions and 132 deletions
+5 -3
View File
@@ -18,6 +18,8 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
{% endif %}
<!-- Core CSS variables -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/adsb_dashboard.css') }}">
@@ -1387,7 +1389,7 @@ ACARS: ${r.statistics.acarsMessages} messages`;
// Range label
const rangeNm = Math.round(maxRange * ratio);
ctx.fillStyle = 'rgba(0, 255, 255, 0.5)';
ctx.font = '10px JetBrains Mono';
ctx.font = '10px Space Mono';
ctx.fillText(`${rangeNm}`, this.centerX + r + 5, this.centerY + 4);
});
}
@@ -1528,7 +1530,7 @@ ACARS: ${r.statistics.acarsMessages} messages`;
// Label
if (blip.callsign || blip.selected) {
ctx.fillStyle = '#00ffff';
ctx.font = '9px JetBrains Mono';
ctx.font = '9px Space Mono';
ctx.textAlign = 'left';
ctx.fillText(blip.callsign || blip.icao, blip.x + 8, blip.y - 5);
if (blip.altitude) {
@@ -1646,7 +1648,7 @@ ACARS: ${r.statistics.acarsMessages} messages`;
// Range label
const rangeNm = Math.round(maxRange * ratio);
ctx.fillStyle = 'rgba(0, 255, 255, 0.4)';
ctx.font = '10px JetBrains Mono';
ctx.font = '10px Space Mono';
ctx.fillText(`${rangeNm}nm`, centerX + r + 5, centerY);
});
+3 -3
View File
@@ -470,7 +470,7 @@
if (!points.length) {
ctx.fillStyle = 'rgba(156, 163, 175, 0.6)';
ctx.font = '12px "JetBrains Mono", monospace';
ctx.font = '12px "Space Mono", monospace';
ctx.fillText(`No ${label.toLowerCase()} data`, 12, height / 2);
return;
}
@@ -478,7 +478,7 @@
const series = points.map(p => p[field]).filter(v => v !== null && v !== undefined);
if (!series.length) {
ctx.fillStyle = 'rgba(156, 163, 175, 0.6)';
ctx.font = '12px "JetBrains Mono", monospace';
ctx.font = '12px "Space Mono", monospace';
ctx.fillText(`No ${label.toLowerCase()} data`, 12, height / 2);
return;
}
@@ -519,7 +519,7 @@
}
ctx.fillStyle = 'rgba(226, 232, 240, 0.8)';
ctx.font = '11px "JetBrains Mono", monospace';
ctx.font = '11px "Space Mono", monospace';
ctx.fillText(`${maxVal} ${unit}`, 12, padding);
ctx.fillText(`${minVal} ${unit}`, 12, height - padding);
}
+2
View File
@@ -18,6 +18,8 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
{% endif %}
<!-- Core CSS variables -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/ais_dashboard.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
+11 -21
View File
@@ -707,7 +707,7 @@
</div>
</div>
<div class="wifi-detail-clients" id="wifiDetailClientList" style="display: none;">
<h6>Connected Clients</h6>
<h6>Connected Clients <span class="wifi-client-count-badge" id="wifiClientCountBadge"></span></h6>
<div class="wifi-client-list"></div>
</div>
</div>
@@ -4259,7 +4259,7 @@
const infoEl = document.createElement('div');
infoEl.className = 'info-msg';
infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid #00d4ff; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #888; word-break: break-all;';
infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid #00d4ff; font-family: "Space Mono", monospace; font-size: 11px; color: #888; word-break: break-all;';
infoEl.textContent = text;
output.insertBefore(infoEl, output.firstChild);
}
@@ -4275,7 +4275,7 @@
const errorEl = document.createElement('div');
errorEl.className = 'error-msg';
errorEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #1a0a0a; border: 1px solid #2a1a1a; border-left: 2px solid #ff3366; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #ff6688; word-break: break-all;';
errorEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #1a0a0a; border: 1px solid #2a1a1a; border-left: 2px solid #ff3366; font-family: "Space Mono", monospace; font-size: 11px; color: #ff6688; word-break: break-all;';
errorEl.textContent = '⚠ ' + text;
output.insertBefore(errorEl, output.firstChild);
}
@@ -6891,7 +6891,7 @@
// Draw total in center
ctx.fillStyle = '#fff';
ctx.font = 'bold 16px JetBrains Mono';
ctx.font = 'bold 16px Space Mono';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(total, cx, cy);
@@ -8661,7 +8661,7 @@
// Label
if (el > 0) {
ctx.fillStyle = '#444';
ctx.font = '10px JetBrains Mono';
ctx.font = '10px Space Mono';
ctx.textAlign = 'center';
ctx.fillText(el + '°', cx, cy - r + 12);
}
@@ -8734,7 +8734,7 @@
// Label
ctx.fillStyle = '#fff';
ctx.font = '11px JetBrains Mono';
ctx.font = '11px Space Mono';
ctx.fillText(pass.satellite, maxX + 10, maxY - 5);
}
}
@@ -9319,14 +9319,10 @@
// Theme toggle functions
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const currentTheme = html.getAttribute('data-theme') || 'dark';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
if (newTheme === 'dark') {
html.removeAttribute('data-theme');
} else {
html.setAttribute('data-theme', newTheme);
}
html.setAttribute('data-theme', newTheme);
// Save to localStorage for instant load on next visit
localStorage.setItem('intercept-theme', newTheme);
@@ -9358,10 +9354,8 @@
// Load saved theme and animations on page load
(function () {
// First apply localStorage theme for instant load (no flash)
const localTheme = localStorage.getItem('intercept-theme');
if (localTheme === 'light') {
document.documentElement.setAttribute('data-theme', 'light');
}
const localTheme = localStorage.getItem('intercept-theme') || 'dark';
document.documentElement.setAttribute('data-theme', localTheme);
// Apply animations preference
const localAnimations = localStorage.getItem('intercept-animations');
@@ -9377,11 +9371,7 @@
const serverTheme = data.value;
if (serverTheme !== localTheme) {
// Server has different theme, apply it
if (serverTheme === 'light') {
document.documentElement.setAttribute('data-theme', 'light');
} else {
document.documentElement.removeAttribute('data-theme');
}
document.documentElement.setAttribute('data-theme', serverTheme);
localStorage.setItem('intercept-theme', serverTheme);
}
}
+7 -7
View File
@@ -6,12 +6,12 @@
<title>{% block title %}iNTERCEPT{% endblock %} // iNTERCEPT</title>
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
{# Fonts - Conditional CDN/Local loading #}
{% if offline_settings and offline_settings.fonts_source == 'local' %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}">
{% else %}
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
{% endif %}
{# Fonts - Conditional CDN/Local loading #}
{% if offline_settings and offline_settings.fonts_source == 'local' %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}">
{% else %}
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
{% endif %}
{# Core CSS (Design System) #}
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
@@ -137,7 +137,7 @@
}
// Apply saved theme
const savedTheme = localStorage.getItem('theme');
const savedTheme = localStorage.getItem('intercept-theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
}
+2 -2
View File
@@ -16,8 +16,8 @@
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--font-sans: 'JetBrains Mono', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
--font-sans: 'Space Mono', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'Space Mono', 'Fira Code', 'Consolas', monospace;
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--bg-tertiary: #1a1a2e;
+3 -3
View File
@@ -229,7 +229,7 @@
const current = html.getAttribute('data-theme') || 'dark';
const next = current === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
localStorage.setItem('intercept-theme', next);
};
}
@@ -257,12 +257,12 @@
// Apply saved preferences and start clock
(function() {
const savedTheme = localStorage.getItem('theme');
const savedTheme = localStorage.getItem('intercept-theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
}
const savedAnimations = localStorage.getItem('animations');
const savedAnimations = localStorage.getItem('intercept-animations');
if (savedAnimations) {
document.documentElement.setAttribute('data-animations', savedAnimations);
}
+2 -2
View File
@@ -52,7 +52,7 @@
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Web Fonts</span>
<span class="settings-label-desc">JetBrains Mono</span>
<span class="settings-label-desc">Space Mono</span>
</div>
<select id="fontsSource" class="settings-select" onchange="Settings.setFontsSource(this.value)">
<option value="cdn">Google Fonts (Online)</option>
@@ -105,7 +105,7 @@
<span class="asset-badge checking" id="statusInter">Checking...</span>
</div>
<div class="asset-status-row">
<span class="asset-name">JetBrains Mono</span>
<span class="asset-name">Space Mono</span>
<span class="asset-badge checking" id="statusJetbrains">Checking...</span>
</div>
</div>
+6 -4
View File
@@ -18,6 +18,8 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
{% endif %}
<!-- Core CSS variables -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/core/variables.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/global-nav.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/satellite_dashboard.css') }}">
@@ -593,7 +595,7 @@
ctx.stroke();
ctx.fillStyle = 'rgba(0, 212, 255, 0.4)';
ctx.font = '10px JetBrains Mono';
ctx.font = '10px Space Mono';
ctx.fillText(el + '°', cx + 5, cy - r + 12);
}
@@ -961,7 +963,7 @@
ctx.stroke();
ctx.fillStyle = 'rgba(0, 212, 255, 0.4)';
ctx.font = '10px JetBrains Mono';
ctx.font = '10px Space Mono';
ctx.fillText(elRing + '°', cx + 5, cy - r + 12);
}
@@ -1028,7 +1030,7 @@
ctx.textAlign = 'center';
ctx.fillText(satellites[selectedSatellite]?.name || 'SAT', x, y - 20);
ctx.font = '10px JetBrains Mono';
ctx.font = '10px Space Mono';
ctx.fillStyle = el > 0 ? '#00ff88' : '#ff4444';
ctx.fillText(el.toFixed(1) + '°', x, y + 25);
} else {
@@ -1077,7 +1079,7 @@
ctx.textAlign = 'center';
ctx.fillText(satellites[selectedSatellite]?.name || 'SAT', x, y - 20);
ctx.font = '10px JetBrains Mono';
ctx.font = '10px Space Mono';
ctx.fillStyle = el > 0 ? '#00ff88' : '#ff4444';
ctx.fillText(el.toFixed(1) + '°', x, y + 25);
}