diff --git a/public/index.html b/public/index.html index 21791bc..64d2355 100644 --- a/public/index.html +++ b/public/index.html @@ -7,8 +7,8 @@ - - + + @@ -434,10 +438,25 @@ a{color:var(--blue);text-decoration:none;transition:color .2s}a:hover{color:var(
@@ -1240,18 +1259,127 @@ function renderDashboard(d) { // Note: innerHTML usage above is the existing pattern in this codebase; all user-facing // strings are escaped via escHtml/escAttr before insertion. +// ============================================================ +// Global Infrastructure Map (MapLibre GL) +// Layers: ASN PoPs | Submarine Cables | Global Datacenters +// ============================================================ var _pcMap = null; +var _pcMapData = null; // current ASN data +var _mapLayers = { pops: true, cables: false, globalFacs: false }; +var _cablesLoaded = false; +var _globalFacsLoaded = false; + +function toggleMapLayer(layer, btn) { + _mapLayers[layer] = !_mapLayers[layer]; + var active = _mapLayers[layer]; + btn.style.border = active ? '1px solid ' + _layerColor(layer) : '1px solid #4a5568'; + btn.style.color = active ? _layerColor(layer) : 'var(--muted)'; + btn.style.background = active ? 'rgba(' + _layerRgb(layer) + ',.12)' : 'transparent'; + + if (!_pcMap) return; + if (layer === 'pops') { + ['pops-fac', 'pops-ix'].forEach(function(id) { + if (_pcMap.getLayer(id)) _pcMap.setLayoutProperty(id, 'visibility', active ? 'visible' : 'none'); + }); + } else if (layer === 'cables') { + if (active && !_cablesLoaded) { _loadCables(); return; } + ['cables-line', 'cables-glow'].forEach(function(id) { + if (_pcMap.getLayer(id)) _pcMap.setLayoutProperty(id, 'visibility', active ? 'visible' : 'none'); + }); + } else if (layer === 'globalFacs') { + if (active && !_globalFacsLoaded) { _loadGlobalFacs(); return; } + if (_pcMap.getLayer('global-facs')) _pcMap.setLayoutProperty('global-facs', 'visibility', active ? 'visible' : 'none'); + } +} + +function _layerColor(l) { + return l === 'pops' ? '#7dcfff' : l === 'cables' ? '#9ece6a' : '#bb9af7'; +} +function _layerRgb(l) { + return l === 'pops' ? '125,207,255' : l === 'cables' ? '158,206,106' : '187,154,247'; +} + +function _showMapLoader(show) { + var el = document.getElementById('mapLoadingIndicator'); + if (el) el.style.display = show ? 'inline' : 'none'; +} + +function _loadCables() { + _showMapLoader(true); + fetch('/api/submarine-cables').then(function(r) { return r.json(); }).then(function(geo) { + if (!_pcMap) return; + _cablesLoaded = true; + _showMapLoader(false); + if (_pcMap.getSource('cables')) return; + _pcMap.addSource('cables', { type: 'geojson', data: geo }); + _pcMap.addLayer({ + id: 'cables-glow', type: 'line', source: 'cables', + layout: { 'line-cap': 'round', visibility: 'visible' }, + paint: { 'line-color': '#9ece6a', 'line-width': 4, 'line-opacity': 0.08 } + }, 'pops-fac'); + _pcMap.addLayer({ + id: 'cables-line', type: 'line', source: 'cables', + layout: { 'line-cap': 'round', visibility: 'visible' }, + paint: { 'line-color': '#9ece6a', 'line-width': 1.2, 'line-opacity': 0.55 } + }, 'pops-fac'); + _pcMap.on('click', 'cables-line', function(e) { + var props = e.features[0].properties; + new maplibregl.Popup({ closeButton: false, className: 'pc-popup' }) + .setLngLat(e.lngLat) + .setHTML('' + (props.name || 'Submarine Cable') + '' + + (props.color ? '