mirror of
https://github.com/smittix/intercept.git
synced 2026-07-26 17:48:15 -07:00
feat: ship platform UX and reliability upgrades
This commit is contained in:
@@ -599,7 +599,7 @@
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed.lat && parsed.lon) return parsed;
|
||||
if (parsed.lat !== undefined && parsed.lat !== null && parsed.lon !== undefined && parsed.lon !== null) return parsed;
|
||||
} catch (e) {}
|
||||
}
|
||||
return { lat: 51.5074, lon: -0.1278 };
|
||||
@@ -985,7 +985,7 @@
|
||||
}
|
||||
|
||||
// Distance calculation
|
||||
if (ac.lat && ac.lon) {
|
||||
if (ac.lat !== undefined && ac.lat !== null && ac.lon !== undefined && ac.lon !== null) {
|
||||
const distance = calculateDistanceNm(
|
||||
observerLocation.lat, observerLocation.lon,
|
||||
ac.lat, ac.lon
|
||||
@@ -1037,7 +1037,7 @@
|
||||
fastest = ac.speed;
|
||||
fastestIcao = icao;
|
||||
}
|
||||
if (ac.lat && ac.lon) {
|
||||
if (ac.lat !== undefined && ac.lat !== null && ac.lon !== undefined && ac.lon !== null) {
|
||||
const dist = calculateDistanceNm(
|
||||
observerLocation.lat, observerLocation.lon,
|
||||
ac.lat, ac.lon
|
||||
@@ -1555,7 +1555,7 @@ ACARS: ${r.statistics.acarsMessages} messages`;
|
||||
gpsEventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'position' && data.latitude && data.longitude) {
|
||||
if (data.type === 'position' && data.latitude !== undefined && data.latitude !== null && data.longitude !== undefined && data.longitude !== null) {
|
||||
updateLocationFromGps(data);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -1627,7 +1627,7 @@ ACARS: ${r.statistics.acarsMessages} messages`;
|
||||
Object.keys(markerState).forEach(icao => delete markerState[icao]);
|
||||
pendingMarkerUpdates.clear();
|
||||
Object.keys(aircraft).forEach(icao => {
|
||||
if (aircraft[icao].lat && aircraft[icao].lon) {
|
||||
if (aircraft[icao].lat !== undefined && aircraft[icao].lat !== null && aircraft[icao].lon !== undefined && aircraft[icao].lon !== null) {
|
||||
pendingMarkerUpdates.add(icao);
|
||||
}
|
||||
});
|
||||
@@ -2556,7 +2556,7 @@ sudo make install</code>
|
||||
updateStatistics(icao, aircraft[icao]);
|
||||
|
||||
// Record trail point
|
||||
if (data.lat && data.lon) {
|
||||
if (data.lat !== undefined && data.lat !== null && data.lon !== undefined && data.lon !== null) {
|
||||
recordTrailPoint(icao, data.lat, data.lon, data.altitude);
|
||||
if (showTrails) {
|
||||
updateTrailLine(icao);
|
||||
@@ -2571,7 +2571,7 @@ sudo make install</code>
|
||||
|
||||
function updateMarkerImmediate(icao) {
|
||||
const ac = aircraft[icao];
|
||||
if (!ac || !ac.lat || !ac.lon) return;
|
||||
if (!ac || ac.lat === undefined || ac.lat === null || ac.lon === undefined || ac.lon === null) return;
|
||||
|
||||
if (!passesFilter(icao, ac)) {
|
||||
if (markers[icao]) {
|
||||
@@ -2808,7 +2808,7 @@ sudo make install</code>
|
||||
updateFlightLookupBtn();
|
||||
|
||||
const ac = aircraft[icao];
|
||||
if (ac && ac.lat && ac.lon) {
|
||||
if (ac && ac.lat !== undefined && ac.lat !== null && ac.lon !== undefined && ac.lon !== null) {
|
||||
radarMap.setView([ac.lat, ac.lon], 10);
|
||||
}
|
||||
}
|
||||
@@ -5124,7 +5124,7 @@ sudo make install</code>
|
||||
const gps = typeof data.agent.gps_coords === 'string'
|
||||
? JSON.parse(data.agent.gps_coords)
|
||||
: data.agent.gps_coords;
|
||||
if (gps.lat && gps.lon) {
|
||||
if (gps.lat !== undefined && gps.lat !== null && gps.lon !== undefined && gps.lon !== null) {
|
||||
document.getElementById('obsLat').value = gps.lat.toFixed(4);
|
||||
document.getElementById('obsLon').value = gps.lon.toFixed(4);
|
||||
updateObserverLoc();
|
||||
|
||||
@@ -750,7 +750,7 @@
|
||||
stats.fastestSpeed = data.speed;
|
||||
}
|
||||
|
||||
if (data.lat && data.lon) {
|
||||
if (data.lat !== undefined && data.lat !== null && data.lon !== undefined && data.lon !== null) {
|
||||
const dist = calculateDistance(observerLocation.lat, observerLocation.lon, data.lat, data.lon);
|
||||
if (dist > stats.maxRange) stats.maxRange = dist;
|
||||
if (dist < stats.closestDistance) stats.closestDistance = dist;
|
||||
@@ -1019,7 +1019,7 @@
|
||||
gpsEventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'position' && data.latitude && data.longitude) {
|
||||
if (data.type === 'position' && data.latitude !== undefined && data.latitude !== null && data.longitude !== undefined && data.longitude !== null) {
|
||||
updateLocationFromGps(data);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -1345,7 +1345,7 @@
|
||||
}
|
||||
|
||||
// Add position marker if coordinates present
|
||||
if (data.latitude && data.longitude) {
|
||||
if (data.latitude !== undefined && data.latitude !== null && data.longitude !== undefined && data.longitude !== null) {
|
||||
addDscPositionMarker(data);
|
||||
}
|
||||
|
||||
@@ -1600,7 +1600,7 @@
|
||||
const gps = typeof data.agent.gps_coords === 'string'
|
||||
? JSON.parse(data.agent.gps_coords)
|
||||
: data.agent.gps_coords;
|
||||
if (gps.lat && gps.lon) {
|
||||
if (gps.lat !== undefined && gps.lat !== null && gps.lon !== undefined && gps.lon !== null) {
|
||||
document.getElementById('obsLat').value = gps.lat.toFixed(4);
|
||||
document.getElementById('obsLon').value = gps.lon.toFixed(4);
|
||||
updateObserverLoc();
|
||||
|
||||
+92
-830
File diff suppressed because it is too large
Load Diff
@@ -787,7 +787,7 @@
|
||||
|
||||
entry.rssiByAgent.forEach((info, agentName) => {
|
||||
const gps = agentGPS.get(agentName);
|
||||
if (gps && gps.lat && gps.lon) {
|
||||
if (gps && gps.lat !== undefined && gps.lat !== null && gps.lon !== undefined && gps.lon !== null) {
|
||||
observations.push({
|
||||
agent_name: agentName,
|
||||
agent_lat: gps.lat,
|
||||
@@ -1073,7 +1073,7 @@
|
||||
const coords = agent.gps_coords;
|
||||
const lat = coords.lat || coords.latitude;
|
||||
const lon = coords.lon || coords.longitude;
|
||||
if (lat && lon) {
|
||||
if (lat !== undefined && lat !== null && lon !== undefined && lon !== null) {
|
||||
agentGPS.set(agent.name, { lat, lon });
|
||||
addLogEntry('gps', `Agent "${agent.name}" GPS: ${lat.toFixed(4)}, ${lon.toFixed(4)}`);
|
||||
}
|
||||
|
||||
@@ -135,23 +135,60 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-header collapsible" onclick="toggleSection(this)">
|
||||
<span>Geofences</span>
|
||||
<span class="collapse-icon">▼</span>
|
||||
<div class="section">
|
||||
<h3 class="section-header collapsible" onclick="toggleSection(this)">
|
||||
<span>Geofences</span>
|
||||
<span class="collapse-icon">▼</span>
|
||||
</h3>
|
||||
<div class="section-content">
|
||||
<div id="analyticsGeofenceList"></div>
|
||||
<button class="btn btn-sm" onclick="Analytics.addGeofence()" style="margin-top:8px; font-size:10px; padding:4px 10px; background:var(--accent-cyan); color:#fff; border:none; border-radius:4px; cursor:pointer;">
|
||||
+ Add Zone
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-header collapsible" onclick="toggleSection(this)">
|
||||
<span>Export Data</span>
|
||||
<span class="collapse-icon">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-header collapsible" onclick="toggleSection(this)">
|
||||
<span>Target View</span>
|
||||
<span class="collapse-icon">▼</span>
|
||||
</h3>
|
||||
<div class="section-content">
|
||||
<div class="analytics-target-toolbar">
|
||||
<input id="analyticsTargetQuery" type="text" placeholder="Search callsign, ICAO, MMSI, MAC, SSID, node..." onkeydown="if(event.key==='Enter'){Analytics.searchTarget();}">
|
||||
<button onclick="Analytics.searchTarget()">Search</button>
|
||||
</div>
|
||||
<div id="analyticsTargetSummary" class="analytics-target-summary">Search to correlate entities across modes</div>
|
||||
<div id="analyticsTargetResults">
|
||||
<div class="analytics-empty">No target selected</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-header collapsible" onclick="toggleSection(this)">
|
||||
<span>Session Replay</span>
|
||||
<span class="collapse-icon">▼</span>
|
||||
</h3>
|
||||
<div class="section-content">
|
||||
<div class="analytics-replay-toolbar">
|
||||
<select id="analyticsReplaySelect"></select>
|
||||
<button onclick="Analytics.loadReplay()">Load</button>
|
||||
<button onclick="Analytics.playReplay()">Play</button>
|
||||
<button onclick="Analytics.pauseReplay()">Pause</button>
|
||||
<button onclick="Analytics.stepReplay()">Step</button>
|
||||
</div>
|
||||
<div id="analyticsReplayMeta" class="analytics-target-summary">No replay loaded</div>
|
||||
<div id="analyticsReplayTimeline">
|
||||
<div class="analytics-empty">Select a recording to replay key events</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="section-header collapsible" onclick="toggleSection(this)">
|
||||
<span>Export Data</span>
|
||||
<span class="collapse-icon">▼</span>
|
||||
</h3>
|
||||
<div class="section-content">
|
||||
<div class="export-controls">
|
||||
|
||||
@@ -291,6 +291,72 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="settings-group-title">Rule Builder</div>
|
||||
<div class="settings-row" style="border-bottom: none; padding-top: 0;">
|
||||
<div class="settings-label">
|
||||
<span class="settings-label-text">Rule Name</span>
|
||||
<span class="settings-label-desc">Human-friendly title for this alert</span>
|
||||
</div>
|
||||
<input type="text" id="alertsRuleName" class="settings-input" placeholder="New alert rule" style="width: 220px;">
|
||||
</div>
|
||||
<div class="settings-row" style="border-bottom: none;">
|
||||
<div class="settings-label">
|
||||
<span class="settings-label-text">Mode</span>
|
||||
<span class="settings-label-desc">Filter to a specific mode or all</span>
|
||||
</div>
|
||||
<select id="alertsRuleMode" class="settings-select" style="width: 220px;">
|
||||
<option value="">All modes</option>
|
||||
<option value="pager">Pager</option>
|
||||
<option value="sensor">433 Sensors</option>
|
||||
<option value="wifi">WiFi</option>
|
||||
<option value="bluetooth">Bluetooth</option>
|
||||
<option value="adsb">ADS-B</option>
|
||||
<option value="ais">AIS</option>
|
||||
<option value="acars">ACARS</option>
|
||||
<option value="vdl2">VDL2</option>
|
||||
<option value="aprs">APRS</option>
|
||||
<option value="dsc">DSC</option>
|
||||
<option value="meshtastic">Meshtastic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-row" style="border-bottom: none;">
|
||||
<div class="settings-label">
|
||||
<span class="settings-label-text">Event Type</span>
|
||||
<span class="settings-label-desc">Optional event type (for example <code>device_update</code>)</span>
|
||||
</div>
|
||||
<input type="text" id="alertsRuleEventType" class="settings-input" placeholder="Optional" style="width: 220px;">
|
||||
</div>
|
||||
<div class="settings-row" style="border-bottom: none;">
|
||||
<div class="settings-label">
|
||||
<span class="settings-label-text">Match Filter</span>
|
||||
<span class="settings-label-desc">Optional key/value exact match (for example <code>address</code> + MAC)</span>
|
||||
</div>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<input type="text" id="alertsRuleMatchKey" class="settings-input" placeholder="key" style="width: 100px;">
|
||||
<input type="text" id="alertsRuleMatchValue" class="settings-input" placeholder="value" style="width: 112px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row" style="border-bottom: none;">
|
||||
<div class="settings-label">
|
||||
<span class="settings-label-text">Severity</span>
|
||||
<span class="settings-label-desc">Controls priority coloring and notifications</span>
|
||||
</div>
|
||||
<select id="alertsRuleSeverity" class="settings-select" style="width: 220px;">
|
||||
<option value="low">Low</option>
|
||||
<option value="medium" selected>Medium</option>
|
||||
<option value="high">High</option>
|
||||
<option value="critical">Critical</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-top: 8px;">
|
||||
<button class="check-assets-btn" onclick="AlertCenter.saveRule()">Save Rule</button>
|
||||
<button class="check-assets-btn" onclick="AlertCenter.clearRuleForm()">Clear</button>
|
||||
<button class="check-assets-btn" onclick="AlertCenter.loadRules()">Refresh Rules</button>
|
||||
</div>
|
||||
<input type="hidden" id="alertsRuleEditingId" value="">
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="settings-group-title">Quick Rules</div>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
@@ -301,6 +367,13 @@
|
||||
Use Bluetooth device details to add specific device watchlist alerts.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="settings-group-title">Active Rules</div>
|
||||
<div id="alertsRulesList" class="settings-feed">
|
||||
<div class="settings-feed-empty">No rules yet</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recording Section -->
|
||||
|
||||
Reference in New Issue
Block a user