diff --git a/static/css/adsb_dashboard.css b/static/css/adsb_dashboard.css index 63728bb..1b44f56 100644 --- a/static/css/adsb_dashboard.css +++ b/static/css/adsb_dashboard.css @@ -592,101 +592,175 @@ body { background: rgba(74, 158, 255, 0.08); } -.vdl2-message-item.expanded { - background: rgba(74, 158, 255, 0.05); +/* VDL2 Message Modal */ +.vdl2-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + animation: vdl2ModalFadeIn 0.15s ease; } -.vdl2-msg-summary { +@keyframes vdl2ModalFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.vdl2-modal { + background: var(--bg-panel, #1a1a2e); + border: 1px solid var(--accent-cyan, #4a9eff); + border-radius: 8px; + width: 520px; + max-width: 90vw; + max-height: 80vh; + display: flex; + flex-direction: column; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 1px var(--accent-cyan, #4a9eff); + animation: vdl2ModalSlideIn 0.15s ease; +} + +@keyframes vdl2ModalSlideIn { + from { opacity: 0; transform: scale(0.95) translateY(10px); } + to { opacity: 1; transform: scale(1) translateY(0); } +} + +.vdl2-modal-header { display: flex; justify-content: space-between; align-items: center; -} - -.vdl2-msg-summary .vdl2-expand-icon { - color: var(--text-dim); - font-size: 8px; - transition: transform 0.2s; + padding: 14px 18px; + border-bottom: 1px solid var(--border-color); flex-shrink: 0; - margin-left: 6px; } -.vdl2-message-item.expanded .vdl2-expand-icon { - transform: rotate(90deg); +.vdl2-modal-title { + font-size: 14px; + font-weight: 700; + color: var(--accent-cyan, #4a9eff); + letter-spacing: 0.5px; } -.vdl2-msg-details { - display: none; - margin-top: 6px; - padding-top: 6px; - border-top: 1px solid var(--border-color); -} - -.vdl2-message-item.expanded .vdl2-msg-details { - display: block; -} - -.vdl2-detail-row { - display: flex; - justify-content: space-between; - padding: 2px 0; - font-size: 9px; - line-height: 1.4; -} - -.vdl2-detail-label { +.vdl2-modal-time { + font-size: 11px; color: var(--text-muted); - flex-shrink: 0; - margin-right: 8px; } -.vdl2-detail-value { - color: var(--text-primary); - text-align: right; - word-break: break-word; +.vdl2-modal-close { + background: none; + border: 1px solid var(--border-color); + color: var(--text-muted); + width: 28px; + height: 28px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; + margin-left: 12px; } -.vdl2-msg-body { - margin-top: 4px; - padding: 4px 6px; - background: rgba(0, 0, 0, 0.2); - border-radius: 3px; - font-family: var(--font-mono); +.vdl2-modal-close:hover { + background: rgba(239, 68, 68, 0.15); + border-color: var(--accent-red, #ef4444); + color: var(--accent-red, #ef4444); +} + +.vdl2-modal-body { + padding: 16px 18px; + overflow-y: auto; + flex: 1; + min-height: 0; +} + +.vdl2-modal-section { + margin-bottom: 14px; +} + +.vdl2-modal-section:last-child { + margin-bottom: 0; +} + +.vdl2-modal-section-title { font-size: 9px; + font-weight: 700; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1.5px; + margin-bottom: 8px; +} + +.vdl2-modal-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px 16px; +} + +.vdl2-modal-field { + display: flex; + flex-direction: column; + gap: 1px; +} + +.vdl2-modal-field-label { + font-size: 10px; + color: var(--text-dim); +} + +.vdl2-modal-field-value { + font-size: 12px; + color: var(--text-primary); + font-weight: 500; +} + +.vdl2-modal-msg-body { + padding: 10px 12px; + background: rgba(0, 0, 0, 0.25); + border-radius: 4px; + font-family: var(--font-mono); + font-size: 12px; color: var(--text-primary); white-space: pre-wrap; word-break: break-word; - line-height: 1.4; - max-height: 200px; + line-height: 1.5; + max-height: 250px; overflow-y: auto; } -.vdl2-raw-toggle { +.vdl2-modal-raw-toggle { display: inline-block; - margin-top: 4px; - font-size: 8px; - color: var(--accent-cyan); + margin-top: 10px; + font-size: 10px; + color: var(--accent-cyan, #4a9eff); cursor: pointer; opacity: 0.7; + transition: opacity 0.15s; } -.vdl2-raw-toggle:hover { +.vdl2-modal-raw-toggle:hover { opacity: 1; } -.vdl2-raw-json { +.vdl2-modal-raw-json { display: none; - margin-top: 4px; - padding: 4px 6px; + margin-top: 8px; + padding: 10px 12px; background: rgba(0, 0, 0, 0.3); - border-radius: 3px; + border: 1px solid var(--border-color); + border-radius: 4px; font-family: var(--font-mono); - font-size: 8px; + font-size: 11px; color: var(--text-dim); white-space: pre-wrap; word-break: break-all; max-height: 300px; overflow-y: auto; - line-height: 1.3; + line-height: 1.4; } /* Panels */ diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 2a36ea2..078c7e7 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -4378,66 +4378,131 @@ sudo make install return div.innerHTML; } - function buildVdl2Details(data) { + function showVdl2Modal(data, timeStr) { + // Remove any existing modal + const existing = document.querySelector('.vdl2-modal-overlay'); + if (existing) existing.remove(); + const avlc = data.avlc || {}; const src = avlc.src || {}; const dst = avlc.dst || {}; const acars = avlc.acars || {}; const xid = avlc.xid || {}; - const rows = []; + const flight = acars.flight || ''; + const freq = data.freq ? (data.freq / 1000000).toFixed(3) + ' MHz' : ''; + const title = flight || src.addr || 'VDL2 Message'; - // Station & frequency - if (data.station) rows.push(['Station', escapeHtml(String(data.station))]); - if (data.freq) rows.push(['Frequency', (data.freq / 1000000).toFixed(3) + ' MHz']); + // Build connection fields + let connectionHtml = ''; + const connFields = []; + if (data.station) connFields.push(['Station', String(data.station)]); + if (freq) connFields.push(['Frequency', freq]); + if (src.addr) connFields.push(['Source', String(src.addr) + (src.type ? ` (${src.type})` : '')]); + if (dst.addr) connFields.push(['Destination', String(dst.addr) + (dst.type ? ` (${dst.type})` : '')]); + if (avlc.cr) connFields.push(['Cmd/Response', avlc.cr]); + if (avlc.frame_type) connFields.push(['Frame Type', avlc.frame_type]); + if (data.agent_name) connFields.push(['Agent', data.agent_name]); + if (connFields.length) { + connectionHtml = `