diff --git a/webserver/templates/realtime.html b/webserver/templates/realtime.html index 790cfa1..dcccbd7 100644 --- a/webserver/templates/realtime.html +++ b/webserver/templates/realtime.html @@ -5,136 +5,89 @@ {% block extra_css %} {% endblock %} {% block content %} -
- -
-

- Real-Time Data Visualization -

-

Live CML network time series measurements

-
- - -
-
-
-

- CML Network Map -

- -
- - -
- -

Click on a CML line to select it.

- -
- - CML Selected: - - -
- -
- -
- Debug: - stats=N/A -  |  - mapData=N/A -  |  - lastError=none -
-
- -
-

- Time Series Data -

-

Click on a CML line in the map to select it and view its received signal - level (RSL) measurements. The time series panel updates automatically.

- -
-

Use the time picker in the Grafana panel below to navigate to - available data.

- -
- -
- -
-
-
-
- - -
-

- About This View -

-
-
-

- Click on any CML line in the network map to select it and view its received signal level (RSL) - measurements. - The time series panel updates automatically. The dashboard refreshes every 10 seconds. -

-
-
-
+
+
+
+
+
+ +
{% endblock %} @@ -184,6 +137,36 @@

return '#0066cc'; } + function buildPopupText(cmlId, stats) { + var popupText = 'CML ' + cmlId + '
'; + if (stats) { + popupText += 'Completeness: ' + stats.completeness_percent + '%
'; + popupText += 'Valid records: ' + stats.valid_records + '/' + stats.total_records + '
'; + if (stats.last_rsl !== null) { + popupText += 'Last RSL: ' + stats.last_rsl + ' dBm
'; + } + if (stats.stddev_last_60min !== null) { + popupText += 'Std Dev (60min): ' + stats.stddev_last_60min + '
'; + } + } + return popupText; + } + + function applyStatsToLine(cmlId, stats) { + var polyline = cmlLayers[cmlId]; + if (!polyline) return; + + polyline._stats = stats; + var color = getColor(stats, currentColoringOption); + polyline.setStyle({ color: color }); + + if (polyline.getPopup()) { + polyline.setPopupContent(buildPopupText(cmlId, stats)); + } else { + polyline.bindPopup(buildPopupText(cmlId, stats)); + } + } + // Initialize map function initializeMap() { console.log('Initializing map...'); @@ -197,7 +180,42 @@

}).addTo(map); console.log('Map created'); - // Fetch stats first + // Add custom control for CML coloring + var ColorControl = L.Control.extend({ + options: { + position: 'topright' + }, + onAdd: function (map) { + var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom'); + container.style.backgroundColor = 'white'; + container.style.padding = '10px'; + container.style.fontSize = '14px'; + + var label = L.DomUtil.create('div', '', container); + label.innerHTML = 'Color by:'; + label.style.marginBottom = '5px'; + + var select = L.DomUtil.create('select', '', container); + select.id = 'coloringOption'; + select.innerHTML = ` + + + + `; + + // Prevent map interactions when using the select + L.DomEvent.disableClickPropagation(container); + L.DomEvent.disableScrollPropagation(container); + + return container; + } + }); + map.addControl(new ColorControl()); + + // Start map rendering immediately + loadCmlData(); + + // Fetch stats in parallel and update colors/popups when ready console.log('Fetching CML stats...'); try { fetch('/api/cml-stats') @@ -211,10 +229,10 @@

console.log('Received stats for ' + stats.length + ' CMLs'); stats.forEach(function (stat) { cmlStats[stat.cml_id] = stat; + applyStatsToLine(stat.cml_id, stat); }); var el2 = document.getElementById('debugStats'); if (el2) el2.textContent = 'stats=ok:' + stats.length; - loadCmlData(); }) .catch(function (error) { console.error('Error loading stats:', error); @@ -222,13 +240,11 @@

if (errEl) errEl.textContent = 'lastError=stats:' + (error && error.message ? error.message : String(error)); var el3 = document.getElementById('debugStats'); if (el3) el3.textContent = 'stats=error'; - loadCmlData(); // Try loading without stats }); } catch (e) { console.error('Exception when fetching stats:', e); var errEl = document.getElementById('debugError'); if (errEl) errEl.textContent = 'lastError=stats-ex:' + String(e); - loadCmlData(); } } catch (e) { console.error('Error in initializeMap:', e); @@ -301,19 +317,8 @@

polyline._cmlId = cmlId; polyline._stats = stats; - // Create popup with stats - var popupText = 'CML ' + cmlId + '
'; - if (stats) { - popupText += 'Completeness: ' + stats.completeness_percent + '%
'; - popupText += 'Valid records: ' + stats.valid_records + '/' + stats.total_records + '
'; - if (stats.last_rsl !== null) { - popupText += 'Last RSL: ' + stats.last_rsl + ' dBm
'; - } - if (stats.stddev_last_60min !== null) { - popupText += 'Std Dev (60min): ' + stats.stddev_last_60min + '
'; - } - } - polyline.bindPopup(popupText); + // Create popup with stats (or placeholder until stats arrive) + polyline.bindPopup(buildPopupText(cmlId, stats)); // Add click handler polyline.on('click', function () { @@ -349,10 +354,6 @@

console.log('Selected CML: ' + cmlId); selectedCmlId = cmlId; - // Update badge - document.getElementById('selectedCmlBadge').textContent = cmlId; - document.getElementById('mapInfo').classList.add('active'); - // Update Grafana iframe URL with new CML ID var grafanaPanel = document.getElementById('grafana-panel'); var base = 'http://localhost:3000/d/cml-realtime/cml-real-time-data'; @@ -361,7 +362,7 @@

'var-cml_id=' + encodeURIComponent(cmlId), 'refresh=10s', 'theme=light', - 'from=now-1h', + 'from=now-7d', 'to=now', 'viewPanel=2', 'kiosk' @@ -372,71 +373,6 @@

// Initialize on page load document.addEventListener('DOMContentLoaded', function () { initializeMap(); - - // Add event listener for Jump to Latest Data button - var jumpBtn = document.getElementById('jumpToDataBtn'); - if (jumpBtn) { - jumpBtn.addEventListener('click', function () { - // Fetch the actual data time range from the database - fetch('/api/data-time-range') - .then(function (response) { return response.json(); }) - .then(function (data) { - if (data.earliest && data.latest) { - var grafanaPanel = document.getElementById('grafana-panel'); - var base = 'http://localhost:3000/d/cml-realtime/cml-real-time-data'; - // Convert ISO strings to Unix timestamps in milliseconds - var fromTimestamp = new Date(data.earliest).getTime(); - var toTimestamp = new Date(data.latest).getTime(); - var params = [ - 'orgId=1', - 'var-cml_id=' + encodeURIComponent(selectedCmlId), - 'refresh=10s', - 'theme=light', - 'from=' + fromTimestamp, - 'to=' + toTimestamp, - 'viewPanel=2', - 'kiosk' - ]; - grafanaPanel.src = base + '?' + params.join('&'); - } else { - console.warn('No data time range available'); - } - }) - .catch(function (error) { - console.error('Error fetching data time range:', error); - }); - }); - } - - // Sync iframe height to the square map element so both columns match - function syncIframeHeight() { - try { - var mapEl = document.getElementById('cml-map'); - var iframe = document.getElementById('grafana-panel'); - if (mapEl && iframe) { - // offsetHeight works after layout; give a small delay for map tiles - setTimeout(function () { - iframe.style.height = mapEl.offsetHeight + 'px'; - }, 150); - } - } catch (e) { - console.warn('syncIframeHeight error', e); - } - } - - // Call after initial draw and when window resizes - window.addEventListener('resize', syncIframeHeight); - - // Also call when map data has been loaded (after drawing) - var origLoadCmlData = loadCmlData; - loadCmlData = function () { - var p = origLoadCmlData(); - syncIframeHeight(); - return p; - }; - - // Ensure height synced after initial load - setTimeout(syncIframeHeight, 500); }); {% endblock %} \ No newline at end of file