diff --git a/static/css/adsb_dashboard.css b/static/css/adsb_dashboard.css
index 1b44f56..1168e1a 100644
--- a/static/css/adsb_dashboard.css
+++ b/static/css/adsb_dashboard.css
@@ -1128,7 +1128,7 @@ body {
gap: 8px;
padding: 8px 15px;
background: var(--bg-panel);
- border-top: 1px solid rgba(74, 158, 255, 0.3);
+ border-top: none;
font-size: 11px;
overflow-x: auto;
overflow-y: hidden;
diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html
index fbd52f9..dfe6878 100644
--- a/templates/adsb_dashboard.html
+++ b/templates/adsb_dashboard.html
@@ -198,6 +198,7 @@
VDL2 MESSAGES
@@ -4113,6 +4114,7 @@ sudo make install
let vdl2CurrentAgent = null;
let vdl2PollTimer = null;
let vdl2MessageCount = 0;
+ let vdl2CollectedData = [];
let vdl2SidebarCollapsed = localStorage.getItem('vdl2SidebarCollapsed') !== 'false';
let vdl2Frequencies = {
'na': ['136975000', '136100000', '136650000', '136700000', '136800000'],
@@ -4226,6 +4228,7 @@ sudo make install
if (vdl2Result.status === 'started' || vdl2Result.status === 'success') {
isVdl2Running = true;
vdl2MessageCount = 0;
+ vdl2CollectedData = [];
document.getElementById('vdl2ToggleBtn').innerHTML = '■ STOP VDL2';
document.getElementById('vdl2ToggleBtn').classList.add('active');
document.getElementById('vdl2PanelIndicator').classList.add('active');
@@ -4550,6 +4553,27 @@ sudo make install
const msgText = acars.msg_text || '';
const time = new Date().toLocaleTimeString();
const freq = inner.freq ? (inner.freq / 1000000).toFixed(3) : '';
+
+ // Store for CSV export
+ vdl2CollectedData.push({
+ timestamp: new Date().toISOString(),
+ frequency: freq ? freq + ' MHz' : '',
+ source: avlc.src?.addr || '',
+ source_type: avlc.src?.type || '',
+ destination: avlc.dst?.addr || '',
+ destination_type: avlc.dst?.type || '',
+ frame_type: avlc.frame_type || '',
+ flight: flight,
+ registration: acars.reg || '',
+ label: acars.label || '',
+ sublabel: acars.sublabel || '',
+ block_id: acars.blk_id || '',
+ msg_number: acars.msg_num || '',
+ message: msgText.replace(/\n/g, ' '),
+ signal_level: inner.sig_level != null ? inner.sig_level.toFixed(1) : '',
+ noise_level: inner.noise_level != null ? inner.noise_level.toFixed(1) : '',
+ agent: data.agent_name || ''
+ });
const label = flight || src || (avlc.frame_type || 'VDL2');
msg.innerHTML = `
@@ -4571,6 +4595,28 @@ sudo make install
}
}
+ function exportVdl2Csv() {
+ if (vdl2CollectedData.length === 0) {
+ alert('No VDL2 messages to export.');
+ return;
+ }
+ const headers = Object.keys(vdl2CollectedData[0]);
+ const csvRows = [headers.join(',')];
+ for (const row of vdl2CollectedData) {
+ csvRows.push(headers.map(h => {
+ const val = String(row[h] || '');
+ return '"' + val.replace(/"/g, '""') + '"';
+ }).join(','));
+ }
+ const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `vdl2_${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')}.csv`;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+
// Populate VDL2 device selector
document.addEventListener('DOMContentLoaded', () => {
fetch('/devices')
diff --git a/templates/index.html b/templates/index.html
index 5ea8f11..883e7a3 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -51,7 +51,6 @@
-
@@ -572,7 +571,6 @@
{% include 'partials/modes/acars.html' %}
- {% include 'partials/modes/vdl2.html' %}