Fix jwt-token

This commit is contained in:
2025-08-17 09:29:19 +02:00
parent c7cef98aeb
commit b4b9472cf8

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet';
import { MapContainer, TileLayer, Marker, Popup, Circle } from 'react-leaflet';
import { Icon } from 'leaflet';
import { useSocket } from '../contexts/SocketContext';
import api from '../services/api';
@@ -45,20 +45,85 @@ const createDeviceIcon = (status, hasDetections) => {
});
};
// Drone detection icon
const createDroneIcon = (rssi, droneType) => {
// Color based on signal strength
let color = '#ff6b6b'; // Default red
if (rssi > -50) color = '#ff4757'; // Very strong - bright red
else if (rssi > -60) color = '#ff6b6b'; // Strong - red
else if (rssi > -70) color = '#ffa726'; // Medium - orange
else if (rssi > -80) color = '#ffca28'; // Weak - yellow
else color = '#66bb6a'; // Very weak - green
return new Icon({
iconUrl: `data:image/svg+xml;base64,${btoa(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="28" height="28">
<circle cx="12" cy="12" r="11" fill="${color}" stroke="#fff" stroke-width="2" opacity="0.8"/>
<path d="M12 6l-2 4h4l-2-4zm0 6l-3 3h6l-3-3z" fill="#fff"/>
</svg>
`)}`,
iconSize: [28, 28],
iconAnchor: [14, 14],
popupAnchor: [0, -14],
});
};
const MapView = () => {
const [devices, setDevices] = useState([]);
const [selectedDevice, setSelectedDevice] = useState(null);
const [loading, setLoading] = useState(true);
const [mapCenter, setMapCenter] = useState([59.3293, 18.0686]); // Stockholm default
const [mapZoom, setMapZoom] = useState(10);
const [showDroneDetections, setShowDroneDetections] = useState(true);
const [droneDetectionHistory, setDroneDetectionHistory] = useState([]);
const { recentDetections, deviceStatus } = useSocket();
// Drone types mapping
const droneTypes = {
1: "DJI Mavic",
2: "Racing Drone",
3: "DJI Phantom",
4: "Fixed Wing",
5: "Surveillance",
0: "Unknown"
};
useEffect(() => {
fetchDevices();
const interval = setInterval(fetchDevices, 30000); // Refresh every 30 seconds
return () => clearInterval(interval);
}, []);
// Update drone detection history when new detections arrive
useEffect(() => {
if (recentDetections.length > 0) {
const latestDetection = recentDetections[0];
// Add to history with timestamp for fade-out
setDroneDetectionHistory(prev => [
{
...latestDetection,
timestamp: Date.now(),
id: `${latestDetection.device_id}-${latestDetection.drone_id}-${latestDetection.device_timestamp}`
},
...prev.slice(0, 19) // Keep last 20 detections
]);
}
}, [recentDetections]);
// Clean up old detections (fade them out after 5 minutes)
useEffect(() => {
const cleanup = setInterval(() => {
const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
setDroneDetectionHistory(prev =>
prev.filter(detection => detection.timestamp > fiveMinutesAgo)
);
}, 30000); // Clean up every 30 seconds
return () => clearInterval(cleanup);
}, []);
const fetchDevices = async () => {
try {
const response = await api.get('/devices/map');
@@ -90,6 +155,20 @@ const MapView = () => {
return recentDetections.filter(d => d.device_id === deviceId);
};
const getDetectionAge = (detection) => {
const ageMs = Date.now() - detection.timestamp;
const ageMinutes = ageMs / (1000 * 60);
return ageMinutes;
};
const getDetectionOpacity = (detection) => {
const age = getDetectionAge(detection);
if (age < 1) return 1.0; // Full opacity for first minute
if (age < 3) return 0.8; // 80% for 1-3 minutes
if (age < 5) return 0.5; // 50% for 3-5 minutes
return 0.2; // 20% for older detections
};
if (loading) {
return (
<div className="flex items-center justify-center h-96">
@@ -100,18 +179,38 @@ const MapView = () => {
return (
<div className="space-y-6">
<div>
<h3 className="text-lg leading-6 font-medium text-gray-900">
Device Map
</h3>
<p className="mt-1 text-sm text-gray-500">
Real-time view of all devices and their detection status
</p>
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg leading-6 font-medium text-gray-900">
Device Map
</h3>
<p className="mt-1 text-sm text-gray-500">
Real-time view of all devices and drone detections
</p>
</div>
<div className="flex items-center space-x-4">
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={showDroneDetections}
onChange={(e) => setShowDroneDetections(e.target.checked)}
className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-gray-700">Show Drone Detections</span>
</label>
{droneDetectionHistory.length > 0 && (
<div className="text-sm text-gray-500">
{droneDetectionHistory.length} recent detection{droneDetectionHistory.length > 1 ? 's' : ''}
</div>
)}
</div>
</div>
{/* Map */}
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
<div className="h-96 lg:h-[600px]">
<div className="h-96 lg:h-[600px] relative">
<MapContainer
center={mapCenter}
zoom={mapZoom}
@@ -156,7 +255,82 @@ const MapView = () => {
</Marker>
);
})}
{/* Drone Detection Markers */}
{showDroneDetections && droneDetectionHistory
.filter(detection => detection.geo_lat && detection.geo_lon)
.map(detection => {
const opacity = getDetectionOpacity(detection);
const age = getDetectionAge(detection);
return (
<React.Fragment key={detection.id}>
<Marker
position={[detection.geo_lat, detection.geo_lon]}
icon={createDroneIcon(detection.rssi, detection.drone_type)}
opacity={opacity}
>
<Popup>
<DroneDetectionPopup detection={detection} age={age} droneTypes={droneTypes} />
</Popup>
</Marker>
{/* Detection range circle for recent detections */}
{age < 2 && (
<Circle
center={[detection.geo_lat, detection.geo_lon]}
radius={200} // 200m detection radius
pathOptions={{
color: detection.rssi > -60 ? '#ff4757' : '#ffa726',
fillColor: detection.rssi > -60 ? '#ff4757' : '#ffa726',
fillOpacity: 0.1 * opacity,
weight: 2,
opacity: opacity * 0.5
}}
/>
)}
</React.Fragment>
);
})}
</MapContainer>
{/* Map Legend */}
<div className="absolute bottom-4 left-4 bg-white bg-opacity-90 backdrop-blur-sm rounded-lg p-3 shadow-lg text-xs">
<div className="font-semibold mb-2">Legend</div>
<div className="space-y-1">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
<span>Device Online</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
<span>Device Detecting</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-gray-500 rounded-full"></div>
<span>Device Offline</span>
</div>
{showDroneDetections && (
<>
<div className="border-t border-gray-200 mt-2 pt-1">
<div className="font-medium">Drone Detections:</div>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-red-500 rounded-full opacity-100"></div>
<span>Strong Signal (&gt;-60dBm)</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-orange-500 rounded-full opacity-80"></div>
<span>Medium Signal (-60 to -70dBm)</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-green-500 rounded-full opacity-60"></div>
<span>Weak Signal (&lt;-70dBm)</span>
</div>
</>
)}
</div>
</div>
</div>
</div>
@@ -238,6 +412,73 @@ const DevicePopup = ({ device, status, detections }) => (
</div>
);
const DroneDetectionPopup = ({ detection, age, droneTypes }) => (
<div className="p-2 min-w-[250px]">
<div className="flex items-center justify-between mb-2">
<h4 className="font-semibold text-red-700 flex items-center space-x-1">
<span>🚨</span>
<span>Drone Detection</span>
</h4>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
age < 1 ? 'bg-red-100 text-red-800' :
age < 3 ? 'bg-orange-100 text-orange-800' :
'bg-gray-100 text-gray-800'
}`}>
{age < 1 ? 'LIVE' : `${Math.round(age)}m ago`}
</span>
</div>
<div className="space-y-2 text-sm">
<div className="grid grid-cols-2 gap-2">
<div>
<span className="font-medium text-gray-700">Drone ID:</span>
<div className="text-gray-900">{detection.drone_id}</div>
</div>
<div>
<span className="font-medium text-gray-700">Type:</span>
<div className="text-gray-900">{droneTypes[detection.drone_type] || 'Unknown'}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<span className="font-medium text-gray-700">RSSI:</span>
<div className={`font-mono ${
detection.rssi > -50 ? 'text-red-600' :
detection.rssi > -70 ? 'text-orange-600' :
'text-green-600'
}`}>
{detection.rssi}dBm
</div>
</div>
<div>
<span className="font-medium text-gray-700">Frequency:</span>
<div className="text-gray-900">{detection.freq}MHz</div>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<span className="font-medium text-gray-700">Confidence:</span>
<div className="text-gray-900">{(detection.confidence_level * 100).toFixed(0)}%</div>
</div>
<div>
<span className="font-medium text-gray-700">Duration:</span>
<div className="text-gray-900">{(detection.signal_duration / 1000).toFixed(1)}s</div>
</div>
</div>
<div className="pt-2 border-t border-gray-200">
<div className="text-xs text-gray-500">
<div>Detected by: Device {detection.device_id}</div>
<div>Location: {detection.geo_lat?.toFixed(4)}, {detection.geo_lon?.toFixed(4)}</div>
<div>Time: {format(new Date(detection.device_timestamp), 'MMM dd, HH:mm:ss')}</div>
</div>
</div>
</div>
</div>
);
const DeviceListItem = ({ device, status, detections, onClick }) => (
<div
className="px-6 py-4 hover:bg-gray-50 cursor-pointer transition-colors"