Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | 1x 1x 1x 1x | /**
* WebSDRMarkers Component
*
* Displays WebSDR receiver locations on the map with status-based colors
*/
import { useEffect, useRef } from 'react';
import mapboxgl from 'mapbox-gl';
import type { WebSDRConfig, WebSDRHealthStatus } from '@/services/api/types';
export interface WebSDRMarkersProps {
map: mapboxgl.Map;
websdrs: WebSDRConfig[];
healthStatus: Record<number, WebSDRHealthStatus>;
}
/**
* Get marker color based on WebSDR health status
*/
function getStatusColor(status?: 'online' | 'offline' | 'unknown'): string {
switch (status) {
case 'online':
return '#10b981'; // Green
case 'offline':
return '#ef4444'; // Red
case 'unknown':
default:
return '#f59e0b'; // Yellow
}
}
/**
* Create popup HTML for WebSDR receiver
*/
function createPopupHTML(websdr: WebSDRConfig, health?: WebSDRHealthStatus): string {
const status = health?.status || 'unknown';
const statusBadge =
status === 'online'
? '<span class="badge bg-success">Online</span>'
: status === 'offline'
? '<span class="badge bg-danger">Offline</span>'
: '<span class="badge bg-warning">Unknown</span>';
return `
<div style="min-width: 200px;">
<h6 class="mb-2">${websdr.name}</h6>
<div class="mb-2">${statusBadge}</div>
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr>
<td class="text-muted">Location:</td>
<td>${websdr.location_name}</td>
</tr>
<tr>
<td class="text-muted">Coordinates:</td>
<td>${websdr.latitude.toFixed(4)}, ${websdr.longitude.toFixed(4)}</td>
</tr>
${
health?.response_time_ms
? `<tr>
<td class="text-muted">Response:</td>
<td>${health.response_time_ms}ms</td>
</tr>`
: ''
}
${
health?.avg_snr !== undefined && health?.avg_snr !== null
? `<tr>
<td class="text-muted">Avg SNR:</td>
<td>${health.avg_snr.toFixed(1)} dB</td>
</tr>`
: ''
}
</tbody>
</table>
</div>
`;
}
const WebSDRMarkers: React.FC<WebSDRMarkersProps> = ({ map, websdrs, healthStatus }) => {
const markersRef = useRef<mapboxgl.Marker[]>([]);
useEffect(() => {
// Clear existing markers
markersRef.current.forEach((marker) => marker.remove());
markersRef.current = [];
// Add markers for each WebSDR
websdrs.forEach((websdr) => {
const health = healthStatus[websdr.id];
const color = getStatusColor(health?.status);
// Create custom marker element
const el = document.createElement('div');
el.className = 'websdr-marker';
el.style.width = '24px';
el.style.height = '24px';
el.style.borderRadius = '50%';
el.style.backgroundColor = color;
el.style.border = '2px solid white';
el.style.boxShadow = '0 2px 4px rgba(0,0,0,0.3)';
el.style.cursor = 'pointer';
el.title = websdr.name;
// Add pulsing animation for online receivers
if (health?.status === 'online') {
el.style.animation = 'pulse 2s infinite';
}
// Create popup
const popup = new mapboxgl.Popup({
offset: 25,
closeButton: true,
closeOnClick: false,
}).setHTML(createPopupHTML(websdr, health));
// Create marker
const marker = new mapboxgl.Marker(el)
.setLngLat([websdr.longitude, websdr.latitude])
.setPopup(popup)
.addTo(map);
markersRef.current.push(marker);
});
// Cleanup function
return () => {
markersRef.current.forEach((marker) => marker.remove());
markersRef.current = [];
};
}, [map, websdrs, healthStatus]);
return null; // This component doesn't render anything directly
};
export default WebSDRMarkers;
|