mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
SSE EventSource connections for AIS, ACARS, VDL2, and radiosonde were not closed when switching modes, causing fd exhaustion after repeated switches. Also fixes socket leaks on exception paths in AIS/ADS-B stream parsers, closes subprocess pipes in safe_terminate/cleanup, and caches skyfield timescale at module level to avoid per-request fd churn. Closes #169 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
191 lines
9.8 KiB
HTML
191 lines
9.8 KiB
HTML
<!-- AIS VESSEL TRACKING MODE -->
|
|
<div id="aisMode" class="mode-content" style="display: none;">
|
|
<div class="section">
|
|
<h3>AIS Vessel Tracking</h3>
|
|
<div class="info-text" style="margin-bottom: 15px;">
|
|
Track ships and vessels via AIS (Automatic Identification System) on 161.975 / 162.025 MHz.
|
|
</div>
|
|
<a href="/ais/dashboard" target="_blank" class="run-btn" style="display: inline-block; text-decoration: none; text-align: center; margin-bottom: 15px;">
|
|
Open AIS Dashboard
|
|
</a>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Settings</h3>
|
|
<div class="form-group">
|
|
<label>Gain (dB, 0 = auto)</label>
|
|
<input type="number" id="aisGainInput" value="40" min="0" max="50" placeholder="0-50">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Status</h3>
|
|
<div id="aisStatusDisplay" class="info-text">
|
|
<p>Status: <span id="aisStatusText" style="color: var(--accent-yellow);">Standby</span></p>
|
|
<p>Vessels: <span id="aisVesselCount">0</span></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Antenna Guide -->
|
|
<div class="section">
|
|
<h3>Antenna Guide</h3>
|
|
<div style="font-size: 11px; color: var(--text-dim); line-height: 1.5;">
|
|
<p style="margin-bottom: 8px; color: var(--accent-cyan); font-weight: 600;">
|
|
Marine VHF band (162 MHz) — stock SDR antenna will NOT work well
|
|
</p>
|
|
|
|
<div style="background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px; margin-bottom: 10px;">
|
|
<strong style="color: var(--accent-cyan); font-size: 12px;">Simple Dipole (Cheapest)</strong>
|
|
<ul style="margin: 6px 0 0 14px; padding: 0;">
|
|
<li><strong style="color: var(--text-primary);">Element length:</strong> ~46 cm each (quarter-wave at 162 MHz)</li>
|
|
<li><strong style="color: var(--text-primary);">Material:</strong> Wire, coat hanger, or copper rod</li>
|
|
<li><strong style="color: var(--text-primary);">Orientation:</strong> Vertical (AIS is vertically polarized)</li>
|
|
<li><strong style="color: var(--text-primary);">Placement:</strong> As high as possible with clear view of the water/harbor</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div style="background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px; margin-bottom: 10px;">
|
|
<strong style="color: var(--accent-cyan); font-size: 12px;">Commercial Options</strong>
|
|
<ul style="margin: 6px 0 0 14px; padding: 0;">
|
|
<li><strong style="color: var(--text-primary);">Marine VHF whip:</strong> ~$20–50, designed for 156–163 MHz band</li>
|
|
<li><strong style="color: var(--text-primary);">Discone:</strong> ~$30–50, wideband coverage including marine VHF</li>
|
|
<li><strong style="color: var(--text-primary);">Collinear:</strong> Higher gain (~6 dBi), best for coastal monitoring</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div style="background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px; margin-bottom: 10px;">
|
|
<strong style="color: var(--accent-cyan); font-size: 12px;">Placement Tips</strong>
|
|
<ul style="margin: 6px 0 0 14px; padding: 0;">
|
|
<li><strong style="color: var(--text-primary);">Height is critical:</strong> AIS is line-of-sight. Roof or mast mount is ideal</li>
|
|
<li><strong style="color: var(--text-primary);">Range:</strong> At 10m height, expect ~25 NM (46 km) range over water</li>
|
|
<li><strong style="color: var(--text-primary);">LNA:</strong> Nooelec Lana or similar broadband LNA, mount at antenna</li>
|
|
<li><strong style="color: var(--text-primary);">Coax:</strong> Keep cable short. RG-58 loses ~4 dB per 10m at 162 MHz</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div style="background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px;">
|
|
<strong style="color: var(--accent-cyan); font-size: 12px;">Quick Reference</strong>
|
|
<table style="width: 100%; margin-top: 6px; font-size: 10px; border-collapse: collapse;">
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">AIS Channel A</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">161.975 MHz</td>
|
|
</tr>
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">AIS Channel B</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">162.025 MHz</td>
|
|
</tr>
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">Quarter-wave length</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">46 cm</td>
|
|
</tr>
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">Modulation</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">GMSK 9600 baud</td>
|
|
</tr>
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">Bandwidth</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">25 kHz</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">Polarization</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">Vertical</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="run-btn" id="startAisBtn" onclick="startAisTracking()">
|
|
Start AIS Tracking
|
|
</button>
|
|
<button class="stop-btn" id="stopAisBtn" onclick="stopAisTracking()" style="display: none;">
|
|
Stop AIS Tracking
|
|
</button>
|
|
</div>
|
|
|
|
<script>
|
|
let aisEventSource = null;
|
|
let aisVessels = {};
|
|
|
|
function startAisTracking() {
|
|
const gain = document.getElementById('aisGainInput').value || '40';
|
|
const device = document.getElementById('deviceSelect')?.value || '0';
|
|
|
|
fetch('/ais/start', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ device, gain, bias_t: typeof getBiasTEnabled === 'function' ? getBiasTEnabled() : false })
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.status === 'started' || data.status === 'already_running') {
|
|
document.getElementById('startAisBtn').style.display = 'none';
|
|
document.getElementById('stopAisBtn').style.display = 'block';
|
|
document.getElementById('aisStatusText').textContent = 'Tracking';
|
|
document.getElementById('aisStatusText').style.color = 'var(--accent-green)';
|
|
startAisSSE();
|
|
} else {
|
|
alert(data.message || 'Failed to start AIS tracking');
|
|
}
|
|
})
|
|
.catch(err => alert('Error: ' + err.message));
|
|
}
|
|
|
|
function stopAisTracking() {
|
|
fetch('/ais/stop', { method: 'POST' })
|
|
.then(r => r.json())
|
|
.then(() => {
|
|
document.getElementById('startAisBtn').style.display = 'block';
|
|
document.getElementById('stopAisBtn').style.display = 'none';
|
|
document.getElementById('aisStatusText').textContent = 'Standby';
|
|
document.getElementById('aisStatusText').style.color = 'var(--accent-yellow)';
|
|
document.getElementById('aisVesselCount').textContent = '0';
|
|
if (aisEventSource) {
|
|
aisEventSource.close();
|
|
aisEventSource = null;
|
|
}
|
|
aisVessels = {};
|
|
});
|
|
}
|
|
|
|
function startAisSSE() {
|
|
if (aisEventSource) aisEventSource.close();
|
|
|
|
aisEventSource = new EventSource('/ais/stream');
|
|
aisEventSource.onmessage = function(e) {
|
|
try {
|
|
const data = JSON.parse(e.data);
|
|
if (data.type === 'vessel') {
|
|
aisVessels[data.mmsi] = data;
|
|
document.getElementById('aisVesselCount').textContent = Object.keys(aisVessels).length;
|
|
}
|
|
} catch (err) {}
|
|
};
|
|
|
|
aisEventSource.onerror = function() {
|
|
setTimeout(() => {
|
|
const panel = document.getElementById('aisMode');
|
|
if (panel && panel.classList.contains('active') &&
|
|
document.getElementById('stopAisBtn').style.display === 'block') {
|
|
startAisSSE();
|
|
}
|
|
}, 2000);
|
|
};
|
|
}
|
|
|
|
// Check initial status
|
|
fetch('/ais/status')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.tracking_active) {
|
|
document.getElementById('startAisBtn').style.display = 'none';
|
|
document.getElementById('stopAisBtn').style.display = 'block';
|
|
document.getElementById('aisStatusText').textContent = 'Tracking';
|
|
document.getElementById('aisStatusText').style.color = 'var(--accent-green)';
|
|
document.getElementById('aisVesselCount').textContent = data.vessel_count || 0;
|
|
startAisSSE();
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
</script>
|