From 7d704c9d42c717f341ae434d9f5b83b438c6f2df Mon Sep 17 00:00:00 2001 From: mitchross Date: Wed, 25 Mar 2026 01:36:32 -0400 Subject: [PATCH] Fix ACARS message display and add missing decoded data types - Use global InterceptTime for all ACARS timestamps (respects Eastern/12h) - Add weather message rendering (wind, temperature, turbulence) - Add CPDLC controller-pilot message rendering (purple highlight) - Add squawk code change rendering (red highlight) - Fix engine_data crash when parsed value isn't an object - Show tail/registration alongside flight number on all cards - Increase message text truncation to 200 chars - Add FL prefix to flight level in position reports - Applied consistently across ADS-B dashboard, sidebar feed, and standalone ACARS mode Co-Authored-By: Claude Opus 4.6 (1M context) --- templates/adsb_dashboard.html | 34 +++++++++++++++++++++-------- templates/partials/modes/acars.html | 28 +++++++++++++++++++----- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 244d665..860c08a 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -3525,15 +3525,17 @@ sudo make install return '' + lbl + ''; } - // TODO: Similar to renderAcarsMainCard in partials/modes/acars.html — consider unifying function renderAcarsCard(msg) { const type = msg.message_type || 'other'; const badge = getAcarsTypeBadge(type); const desc = escapeHtml(msg.label_description || ('Label ' + (msg.label || '?'))); const text = msg.text || msg.msg || ''; - const truncText = escapeHtml(text.length > 120 ? text.substring(0, 120) + '...' : text); - const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : ''; + const truncText = escapeHtml(text.length > 200 ? text.substring(0, 200) + '...' : text); + const time = msg.timestamp && typeof InterceptTime !== 'undefined' + ? InterceptTime.shortTime(msg.timestamp) + InterceptTime.tzSuffix() + : (msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : ''); const flight = escapeHtml(msg.flight || ''); + const tail = escapeHtml(msg.tail || msg.reg || ''); let parsedHtml = ''; if (msg.parsed) { @@ -3541,23 +3543,35 @@ sudo make install if (type === 'position' && p.lat !== undefined) { parsedHtml = '
' + p.lat.toFixed(4) + ', ' + p.lon.toFixed(4) + - (p.flight_level ? ' • ' + escapeHtml(String(p.flight_level)) : '') + - (p.destination ? ' → ' + escapeHtml(String(p.destination)) : '') + '
'; + (p.flight_level ? ' • FL' + escapeHtml(String(p.flight_level)) : '') + + (p.destination ? ' → ' + escapeHtml(String(p.destination)) : '') + ''; } else if (type === 'engine_data') { const parts = []; Object.keys(p).forEach(k => { - parts.push(escapeHtml(k) + ': ' + escapeHtml(String(p[k].value))); + const val = typeof p[k] === 'object' ? p[k].value : p[k]; + parts.push(escapeHtml(k) + ': ' + escapeHtml(String(val))); }); if (parts.length) { parsedHtml = '
' + parts.slice(0, 4).join(' | ') + '
'; } } else if (type === 'oooi' && p.origin) { parsedHtml = '
' + - escapeHtml(String(p.origin)) + ' → ' + escapeHtml(String(p.destination)) + + escapeHtml(String(p.origin)) + ' → ' + escapeHtml(String(p.destination)) + (p.out ? ' | OUT ' + escapeHtml(String(p.out)) : '') + (p.off ? ' OFF ' + escapeHtml(String(p.off)) : '') + (p.on ? ' ON ' + escapeHtml(String(p.on)) : '') + (p['in'] ? ' IN ' + escapeHtml(String(p['in'])) : '') + '
'; + } else if (type === 'weather' && (p.wind_speed || p.temperature)) { + const wx = []; + if (p.wind_speed) wx.push('Wind ' + escapeHtml(String(p.wind_speed)) + (p.wind_dir ? '/' + escapeHtml(String(p.wind_dir)) : '')); + if (p.temperature) wx.push(escapeHtml(String(p.temperature)) + '°C'); + if (p.turbulence) wx.push('Turb: ' + escapeHtml(String(p.turbulence))); + if (wx.length) parsedHtml = '
' + wx.join(' | ') + '
'; + } else if (type === 'cpdlc') { + const cpdlcText = p.message || p.text || ''; + if (cpdlcText) parsedHtml = '
' + escapeHtml(String(cpdlcText)) + '
'; + } else if (type === 'squawk' && p.squawk) { + parsedHtml = '
Squawk: ' + escapeHtml(String(p.squawk)) + '
'; } } @@ -3565,7 +3579,7 @@ sudo make install '
' + '' + badge + ' ' + desc + '' + '' + time + '
' + - (flight ? '
' + flight + '
' : '') + + (flight || tail ? '
' + flight + (tail ? ' (' + tail + ')' : '') + '
' : '') + parsedHtml + (truncText && type !== 'link_test' && type !== 'handshake' ? '
' + truncText + '
' : '') + @@ -4398,7 +4412,9 @@ sudo make install const labelDesc = data.label_description || ''; const msgType = data.message_type || 'other'; const text = data.text || data.msg || ''; - const time = new Date().toLocaleTimeString(); + const time = typeof InterceptTime !== 'undefined' + ? InterceptTime.shortTime(new Date()) + InterceptTime.tzSuffix() + : new Date().toLocaleTimeString(); // Escape user-controlled strings for safe innerHTML insertion const eFlight = escapeHtml(flight); diff --git a/templates/partials/modes/acars.html b/templates/partials/modes/acars.html index ef722e9..a6dc3ad 100644 --- a/templates/partials/modes/acars.html +++ b/templates/partials/modes/acars.html @@ -188,33 +188,49 @@ return `${lbl}`; } - // TODO: Similar to renderAcarsCard in templates/adsb_dashboard.html — consider unifying function renderAcarsMainCard(data) { const flight = escapeHtml(data.flight || 'UNKNOWN'); + const tail = escapeHtml(data.tail || data.reg || ''); const type = data.message_type || 'other'; const badge = acarsMainTypeBadge(type); const desc = escapeHtml(data.label_description || (data.label ? 'Label: ' + data.label : '')); const text = data.text || data.msg || ''; - const truncText = escapeHtml(text.length > 150 ? text.substring(0, 150) + '...' : text); - const time = new Date().toLocaleTimeString(); + const truncText = escapeHtml(text.length > 200 ? text.substring(0, 200) + '...' : text); + const time = typeof InterceptTime !== 'undefined' + ? InterceptTime.shortTime(new Date()) + InterceptTime.tzSuffix() + : new Date().toLocaleTimeString(); let parsedHtml = ''; if (data.parsed) { const p = data.parsed; if (type === 'position' && p.lat !== undefined) { - parsedHtml = `
${p.lat.toFixed(4)}, ${p.lon.toFixed(4)}${p.flight_level ? ' • ' + escapeHtml(String(p.flight_level)) : ''}${p.destination ? ' → ' + escapeHtml(String(p.destination)) : ''}
`; + parsedHtml = `
${p.lat.toFixed(4)}, ${p.lon.toFixed(4)}${p.flight_level ? ' • FL' + escapeHtml(String(p.flight_level)) : ''}${p.destination ? ' → ' + escapeHtml(String(p.destination)) : ''}
`; } else if (type === 'engine_data') { const parts = []; - Object.keys(p).forEach(k => parts.push(escapeHtml(k) + ': ' + escapeHtml(String(p[k].value)))); + Object.keys(p).forEach(k => { + const val = typeof p[k] === 'object' ? p[k].value : p[k]; + parts.push(escapeHtml(k) + ': ' + escapeHtml(String(val))); + }); if (parts.length) parsedHtml = `
${parts.slice(0, 4).join(' | ')}
`; } else if (type === 'oooi' && p.origin) { parsedHtml = `
${escapeHtml(String(p.origin))} → ${escapeHtml(String(p.destination))}${p.out ? ' | OUT ' + escapeHtml(String(p.out)) : ''}${p.off ? ' OFF ' + escapeHtml(String(p.off)) : ''}${p.on ? ' ON ' + escapeHtml(String(p.on)) : ''}${p['in'] ? ' IN ' + escapeHtml(String(p['in'])) : ''}
`; + } else if (type === 'weather' && (p.wind_speed || p.temperature)) { + const wx = []; + if (p.wind_speed) wx.push('Wind ' + escapeHtml(String(p.wind_speed)) + (p.wind_dir ? '/' + escapeHtml(String(p.wind_dir)) : '')); + if (p.temperature) wx.push(escapeHtml(String(p.temperature)) + '°C'); + if (p.turbulence) wx.push('Turb: ' + escapeHtml(String(p.turbulence))); + if (wx.length) parsedHtml = `
${wx.join(' | ')}
`; + } else if (type === 'cpdlc') { + const cpdlcText = p.message || p.text || ''; + if (cpdlcText) parsedHtml = `
${escapeHtml(String(cpdlcText))}
`; + } else if (type === 'squawk' && p.squawk) { + parsedHtml = `
Squawk: ${escapeHtml(String(p.squawk))}
`; } } return `
- ${flight} + ${flight}${tail ? ' (' + tail + ')' : ''} ${time}
${badge} ${desc}