fix: dashboard instant boot, pipeline map layer, theater data bugs

- Add /api/static endpoint for instant geospatial data on boot (158 items)
- Add per-coroutine 45s timeout to prevent SSE first-frame blocking
- Dismiss loading overlay after static fetch instead of waiting for SSE
- Render 24 oil/gas/hydrogen pipelines as colored polylines on map
- Fix theaters dict-vs-list bug in posture.py and fleet.py
- Add exposure detail modal and color/label entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Marc Shade
2026-02-24 10:09:14 -05:00
co-authored by Claude Opus 4.6
parent 2d1c95389a
commit a72fdc50f7
5 changed files with 185 additions and 15 deletions
+51 -2
View File
@@ -391,6 +391,8 @@ a { color: var(--accent); text-decoration: none; }
}
.mk-nuke-fac:hover { transform: scale(1.4); }
.mk-nuke-fac.hot { animation: glow-gold 2s ease-in-out infinite; }
.pipeline-line { cursor: pointer; transition: opacity 0.15s; }
.pipeline-line:hover { opacity: 1 !important; stroke-width: 3 !important; }
@keyframes glow-gold {
0%,100% { box-shadow: 0 0 4px rgba(240,184,64,0.3); }
50% { box-shadow: 0 0 16px rgba(240,184,64,0.7); }
@@ -595,8 +597,8 @@ $('#drawerToggle').addEventListener('click', function() {
});
// ════════════ DETAIL MODAL ════════════
var COLORS = { earthquake: 'var(--red)', military: 'var(--blue)', conflict: 'var(--amber)', fire: 'var(--gold)', convergence: 'var(--purple)', nuclear: 'var(--green)', military_base: 'var(--teal)', port: 'var(--blue)', nuclear_facility: 'var(--gold)', pipeline: 'var(--purple)' };
var LABELS = { earthquake: 'EARTHQUAKE', military: 'MILITARY AIRCRAFT', conflict: 'CONFLICT EVENT', fire: 'WILDFIRE CLUSTER', convergence: 'SIGNAL CONVERGENCE', nuclear: 'NUCLEAR MONITOR', military_base: 'MILITARY BASE', port: 'STRATEGIC PORT', nuclear_facility: 'NUCLEAR FACILITY', pipeline: 'PIPELINE' };
var COLORS = { earthquake: 'var(--red)', military: 'var(--blue)', conflict: 'var(--amber)', fire: 'var(--gold)', convergence: 'var(--purple)', nuclear: 'var(--green)', military_base: 'var(--teal)', port: 'var(--blue)', nuclear_facility: 'var(--gold)', pipeline: 'var(--purple)', exposure: '#e040fb' };
var LABELS = { earthquake: 'EARTHQUAKE', military: 'MILITARY AIRCRAFT', conflict: 'CONFLICT EVENT', fire: 'WILDFIRE CLUSTER', convergence: 'SIGNAL CONVERGENCE', nuclear: 'NUCLEAR MONITOR', military_base: 'MILITARY BASE', port: 'STRATEGIC PORT', nuclear_facility: 'NUCLEAR FACILITY', pipeline: 'PIPELINE', exposure: 'POPULATION EXPOSURE' };
function showDetail(type, d) {
var title = '', subtitle = '', fields = [], link = '';
@@ -720,6 +722,17 @@ function showDetail(type, d) {
['Notes', d.notes || '\u2014'],
['Coordinates', d.latitude != null ? d.latitude.toFixed(3) + ', ' + d.longitude.toFixed(3) : '\u2014']
];
} else if (type === 'exposure') {
title = d.city || 'Exposed City';
subtitle = (d.country || '') + ' \u2014 ' + (d.population || '');
fields = [
['City', d.city || '\u2014'],
['Country', d.country || '\u2014'],
['Population', d.population || '\u2014'],
['Nearest Event', d.nearest_event || '\u2014', d.nearest_event === 'conflict' ? 'crit' : 'high'],
['Event Detail', d.event_detail || '\u2014'],
['Distance', d.distance_km != null ? d.distance_km + ' km' : '\u2014']
];
}
var hdr = '<div class="detail-cat"><span class="detail-cat-dot" style="background:' + c + ';--cat-color:' + c + '"></span><span class="detail-cat-label">' + esc(LABELS[type] || type) + '</span></div>' +
@@ -1120,6 +1133,28 @@ function updateMapInfra(data) {
});
mk.addTo(mapLayers.infra);
});
// Pipelines (polylines: lat_start/lon_start → lat_end/lon_end)
var pipeColors = { oil: '#ff9100', gas: '#4da8ff', hydrogen: '#34d399', lng: '#2dd4bf' };
var pipeStatusOpacity = { active: 0.55, destroyed: 0.25, terminated: 0.25, cancelled: 0.2, intermittent: 0.45, reduced: 0.4, stalled: 0.3, construction: 0.35, proposed: 0.2 };
var pipes = (data.pipelines && data.pipelines.pipelines) || [];
pipes.forEach(function(p) {
if (p.lat_start == null || p.lon_start == null || p.lat_end == null || p.lon_end == null) return;
total++;
var color = pipeColors[p.type] || '#a78bfa';
var opacity = pipeStatusOpacity[p.status] || 0.4;
var dash = (p.status === 'proposed' || p.status === 'construction') ? '6 4' : (p.status === 'destroyed' || p.status === 'terminated' || p.status === 'cancelled') ? '3 6' : null;
var opts = { color: color, weight: 2, opacity: opacity, className: 'pipeline-line' };
if (dash) opts.dashArray = dash;
var line = L.polyline([[p.lat_start, p.lon_start], [p.lat_end, p.lon_end]], opts);
line.bindTooltip(esc(p.name) + '<br><span style="opacity:0.7">' + esc(p.type) + ' \u2022 ' + esc(p.status) + '</span>', { className: 'mk-tip', sticky: true });
line.on('click', function() {
showDetail('pipeline', {
name: p.name, route: p.route, type: p.type,
capacity: p.capacity, status: p.status, notes: p.notes
});
});
line.addTo(mapLayers.infra);
});
$('#cntInfra').textContent = total;
}
@@ -1954,6 +1989,20 @@ function connectSSE() {
// ════════════ BOOT ════════════
initMap();
// Load static geospatial data immediately (bases, ports, nuclear facilities).
// This populates the infrastructure layer without waiting for the full SSE
// gather, which can take 30-60s when external APIs are slow.
fetch('/api/static')
.then(function(r) { return r.json(); })
.then(function(data) {
try { updateMapInfra(data); } catch(e) { console.warn('static infra failed:', e); }
// Dismiss loading overlay once static infra is rendered — the map is
// usable with 158 items while live feeds continue loading via SSE.
document.getElementById('loading').classList.add('gone');
})
.catch(function(e) { console.warn('static fetch failed:', e); });
connectSSE();
</script>
</body>