Fix jwt-token

This commit is contained in:
2025-08-17 09:36:12 +02:00
parent b4b9472cf8
commit fb6c105591
6 changed files with 807 additions and 5 deletions

View File

@@ -0,0 +1,263 @@
import React, { useState } from 'react';
import { format } from 'date-fns';
import { useSocket } from '../contexts/SocketContext';
import {
ExclamationTriangleIcon,
InformationCircleIcon,
ChevronDownIcon,
ChevronUpIcon,
SignalIcon,
ArrowTrendingUpIcon,
ArrowTrendingDownIcon,
EyeIcon
} from '@heroicons/react/24/outline';
const MovementAlertsPanel = () => {
const { movementAlerts, clearMovementAlerts } = useSocket();
const [expandedAlert, setExpandedAlert] = useState(null);
const [filter, setFilter] = useState('all'); // all, critical, high, medium
const getAlertIcon = (alertLevel) => {
if (alertLevel >= 3) return <ExclamationTriangleIcon className="h-5 w-5 text-red-600" />;
if (alertLevel >= 2) return <ExclamationTriangleIcon className="h-5 w-5 text-orange-600" />;
return <InformationCircleIcon className="h-5 w-5 text-blue-600" />;
};
const getAlertColor = (alertLevel) => {
if (alertLevel >= 3) return 'border-red-200 bg-red-50';
if (alertLevel >= 2) return 'border-orange-200 bg-orange-50';
return 'border-blue-200 bg-blue-50';
};
const getProximityColor = (level) => {
switch (level) {
case 'VERY_CLOSE': return 'text-red-700 bg-red-100';
case 'CLOSE': return 'text-orange-700 bg-orange-100';
case 'MEDIUM': return 'text-yellow-700 bg-yellow-100';
case 'FAR': return 'text-green-700 bg-green-100';
case 'VERY_FAR': return 'text-gray-700 bg-gray-100';
default: return 'text-gray-700 bg-gray-100';
}
};
const getMovementIcon = (trend) => {
if (trend === 'STRENGTHENING') return <ArrowTrendingUpIcon className="h-4 w-4 text-red-600" />;
if (trend === 'WEAKENING') return <ArrowTrendingDownIcon className="h-4 w-4 text-green-600" />;
return <SignalIcon className="h-4 w-4 text-gray-600" />;
};
const filteredAlerts = movementAlerts.filter(alert => {
if (filter === 'all') return true;
if (filter === 'critical') return alert.analysis.alertLevel >= 3;
if (filter === 'high') return alert.analysis.alertLevel === 2;
if (filter === 'medium') return alert.analysis.alertLevel === 1;
return true;
});
const droneTypes = {
1: "DJI Mavic",
2: "Racing Drone",
3: "DJI Phantom",
4: "Fixed Wing",
5: "Surveillance",
0: "Unknown"
};
return (
<div className="bg-white rounded-lg shadow-lg">
<div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<h3 className="text-lg font-medium text-gray-900">Movement Alerts</h3>
{movementAlerts.length > 0 && (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
{movementAlerts.length}
</span>
)}
</div>
<div className="flex items-center space-x-2">
<select
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="text-sm border border-gray-300 rounded px-2 py-1"
>
<option value="all">All Alerts</option>
<option value="critical">Critical</option>
<option value="high">High Priority</option>
<option value="medium">Medium Priority</option>
</select>
{movementAlerts.length > 0 && (
<button
onClick={clearMovementAlerts}
className="text-sm text-gray-600 hover:text-gray-800"
>
Clear All
</button>
)}
</div>
</div>
</div>
<div className="divide-y divide-gray-200 max-h-96 overflow-y-auto">
{filteredAlerts.length === 0 ? (
<div className="px-6 py-8 text-center text-gray-500">
<EyeIcon className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No movement alerts</p>
<p className="text-sm">Drone movement patterns will appear here</p>
</div>
) : (
filteredAlerts.map((alert, index) => (
<div
key={index}
className={`border-l-4 ${getAlertColor(alert.analysis.alertLevel)}`}
>
<div className="px-6 py-4">
<div className="flex items-start justify-between">
<div className="flex items-start space-x-3 flex-1">
{getAlertIcon(alert.analysis.alertLevel)}
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2">
<h4 className="text-sm font-medium text-gray-900">
Drone {alert.droneId} Device {alert.deviceId}
</h4>
<span className="text-xs text-gray-500">
{format(new Date(alert.timestamp), 'HH:mm:ss')}
</span>
</div>
<p className="text-sm text-gray-700 mt-1">
{alert.analysis.description}
</p>
{alert.analysis.rssiTrend && (
<div className="flex items-center space-x-4 mt-2 text-xs">
<div className="flex items-center space-x-1">
{getMovementIcon(alert.analysis.rssiTrend.trend)}
<span className="text-gray-600">
{alert.analysis.rssiTrend.trend.toLowerCase()}
</span>
</div>
<div className="flex items-center space-x-1">
<span className="text-gray-500">RSSI:</span>
<span className="font-mono text-gray-900">
{alert.detection.rssi}dBm
</span>
</div>
{alert.analysis.proximityLevel && (
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getProximityColor(alert.analysis.proximityLevel)}`}>
{alert.analysis.proximityLevel.replace('_', ' ')}
</span>
)}
</div>
)}
</div>
</div>
<button
onClick={() => setExpandedAlert(expandedAlert === index ? null : index)}
className="ml-2 text-gray-400 hover:text-gray-600"
>
{expandedAlert === index ? (
<ChevronUpIcon className="h-5 w-5" />
) : (
<ChevronDownIcon className="h-5 w-5" />
)}
</button>
</div>
{expandedAlert === index && (
<div className="mt-4 pl-8 space-y-3">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="font-medium text-gray-700">Drone Type:</span>
<div className="text-gray-900">
{droneTypes[alert.detection.drone_type] || 'Unknown'}
</div>
</div>
<div>
<span className="font-medium text-gray-700">Frequency:</span>
<div className="text-gray-900">{alert.detection.freq}MHz</div>
</div>
<div>
<span className="font-medium text-gray-700">Confidence:</span>
<div className="text-gray-900">
{(alert.detection.confidence_level * 100).toFixed(0)}%
</div>
</div>
<div>
<span className="font-medium text-gray-700">Signal Duration:</span>
<div className="text-gray-900">
{(alert.detection.signal_duration / 1000).toFixed(1)}s
</div>
</div>
</div>
{alert.analysis.movement && (
<div>
<span className="font-medium text-gray-700 block mb-1">Movement Pattern:</span>
<div className="text-sm space-y-1">
<div>Pattern: <span className="font-mono">{alert.analysis.movement.pattern}</span></div>
{alert.analysis.movement.speed > 0 && (
<div>Speed: <span className="font-mono">{alert.analysis.movement.speed.toFixed(1)} m/s</span></div>
)}
{alert.analysis.movement.totalDistance > 0 && (
<div>Distance: <span className="font-mono">{(alert.analysis.movement.totalDistance * 1000).toFixed(0)}m</span></div>
)}
</div>
</div>
)}
{alert.analysis.detectionCount && (
<div>
<span className="font-medium text-gray-700">Tracking Stats:</span>
<div className="text-sm mt-1">
<div>{alert.analysis.detectionCount} detections over {(alert.analysis.timeTracked / 60).toFixed(1)} minutes</div>
</div>
</div>
)}
{alert.history && alert.history.length > 0 && (
<div>
<span className="font-medium text-gray-700 block mb-2">Recent RSSI History:</span>
<div className="flex items-center space-x-1">
{alert.history.slice(-5).map((point, i) => (
<div
key={i}
className="flex flex-col items-center text-xs"
>
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-white text-xs font-mono ${
point.rssi > -50 ? 'bg-red-500' :
point.rssi > -60 ? 'bg-orange-500' :
point.rssi > -70 ? 'bg-yellow-500' :
'bg-green-500'
}`}>
{point.rssi}
</div>
<div className="text-gray-500 mt-1">
{format(new Date(point.timestamp), 'HH:mm')}
</div>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
</div>
))
)}
</div>
</div>
);
};
export default MovementAlertsPanel;

View File

@@ -10,6 +10,8 @@ export const SocketProvider = ({ children }) => {
const [connected, setConnected] = useState(false);
const [recentDetections, setRecentDetections] = useState([]);
const [deviceStatus, setDeviceStatus] = useState({});
const [movementAlerts, setMovementAlerts] = useState([]);
const [droneTracking, setDroneTracking] = useState(new Map());
const { isAuthenticated } = useAuth();
useEffect(() => {
@@ -49,9 +51,25 @@ export const SocketProvider = ({ children }) => {
setRecentDetections(prev => [detection, ...prev.slice(0, 49)]); // Keep last 50
// Update drone tracking
if (detection.movement_analysis) {
const trackingKey = `${detection.drone_id}_${detection.device_id}`;
setDroneTracking(prev => {
const newTracking = new Map(prev);
newTracking.set(trackingKey, {
droneId: detection.drone_id,
deviceId: detection.device_id,
lastDetection: detection,
analysis: detection.movement_analysis,
timestamp: Date.now()
});
return newTracking;
});
}
// Show toast notification
toast.error(
`Drone detected by ${detection.device.name || `Device ${detection.device_id}`}`,
`Drone ${detection.drone_id} detected by ${detection.device?.name || `Device ${detection.device_id}`}`,
{
duration: 5000,
icon: '🚨',
@@ -59,6 +77,32 @@ export const SocketProvider = ({ children }) => {
);
});
// Listen for drone movement alerts
newSocket.on('drone_movement_alert', (alertData) => {
console.log('Drone movement alert:', alertData);
setMovementAlerts(prev => [alertData, ...prev.slice(0, 19)]); // Keep last 20 alerts
// Show priority-based notifications
const alertIcon = alertData.analysis.alertLevel >= 3 ? '🚨' :
alertData.analysis.alertLevel >= 2 ? '⚠️' : '📍';
const alertColor = alertData.analysis.alertLevel >= 3 ? 'error' :
alertData.analysis.alertLevel >= 2 ? 'warning' : 'info';
toast[alertColor === 'error' ? 'error' : alertColor === 'warning' ? 'error' : 'info'](
alertData.analysis.description,
{
duration: alertData.analysis.alertLevel >= 2 ? 10000 : 6000,
icon: alertIcon,
style: {
background: alertData.analysis.alertLevel >= 3 ? '#fee2e2' :
alertData.analysis.alertLevel >= 2 ? '#fef3c7' : '#e0f2fe'
}
}
);
});
// Listen for device heartbeats
newSocket.on('device_heartbeat', (heartbeat) => {
console.log('Device heartbeat:', heartbeat);
@@ -109,14 +153,27 @@ export const SocketProvider = ({ children }) => {
setRecentDetections([]);
};
const clearMovementAlerts = () => {
setMovementAlerts([]);
};
const getDroneTracking = (droneId, deviceId) => {
const trackingKey = `${droneId}_${deviceId}`;
return droneTracking.get(trackingKey);
};
const value = {
socket,
connected,
recentDetections,
deviceStatus,
movementAlerts,
droneTracking,
joinDeviceRoom,
leaveDeviceRoom,
clearRecentDetections
clearRecentDetections,
clearMovementAlerts,
getDroneTracking
};
return (

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useSocket } from '../contexts/SocketContext';
import MovementAlertsPanel from '../components/MovementAlertsPanel';
import api from '../services/api';
import {
ServerIcon,
@@ -30,7 +31,8 @@ const Dashboard = () => {
const [deviceActivity, setDeviceActivity] = useState([]);
const [recentActivity, setRecentActivity] = useState([]);
const [loading, setLoading] = useState(true);
const { recentDetections, deviceStatus, connected } = useSocket();
const [showMovementAlerts, setShowMovementAlerts] = useState(true);
const { recentDetections, deviceStatus, connected, movementAlerts } = useSocket();
useEffect(() => {
fetchDashboardData();
@@ -318,6 +320,70 @@ const Dashboard = () => {
</div>
</div>
</div>
{/* Movement Alerts Panel */}
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
<div className="xl:col-span-2">
<MovementAlertsPanel />
</div>
{/* Movement Summary Stats */}
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-medium text-gray-900 mb-4">
Movement Tracking
</h3>
<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-red-50 rounded-lg">
<div>
<div className="font-medium text-red-900">Critical Alerts</div>
<div className="text-sm text-red-700">Very close approaches</div>
</div>
<div className="text-2xl font-bold text-red-600">
{movementAlerts.filter(a => a.analysis.alertLevel >= 3).length}
</div>
</div>
<div className="flex items-center justify-between p-3 bg-orange-50 rounded-lg">
<div>
<div className="font-medium text-orange-900">High Priority</div>
<div className="text-sm text-orange-700">Approaching drones</div>
</div>
<div className="text-2xl font-bold text-orange-600">
{movementAlerts.filter(a => a.analysis.alertLevel === 2).length}
</div>
</div>
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
<div>
<div className="font-medium text-blue-900">Medium Priority</div>
<div className="text-sm text-blue-700">Movement changes</div>
</div>
<div className="text-2xl font-bold text-blue-600">
{movementAlerts.filter(a => a.analysis.alertLevel === 1).length}
</div>
</div>
<div className="pt-4 border-t border-gray-200">
<div className="text-sm text-gray-600">
<div className="flex justify-between">
<span>Total Tracked:</span>
<span className="font-medium">{movementAlerts.length} events</span>
</div>
<div className="flex justify-between mt-1">
<span>Last Alert:</span>
<span className="font-medium">
{movementAlerts.length > 0
? format(new Date(movementAlerts[0].timestamp), 'HH:mm:ss')
: 'None'
}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};

View File

@@ -468,6 +468,55 @@ const DroneDetectionPopup = ({ detection, age, droneTypes }) => (
</div>
</div>
{/* Movement Analysis */}
{detection.movement_analysis && (
<div className="pt-2 border-t border-gray-200">
<span className="font-medium text-gray-700 block mb-1">Movement Analysis:</span>
<div className="text-xs space-y-1">
<div className={`px-2 py-1 rounded ${
detection.movement_analysis.alertLevel >= 3 ? 'bg-red-100 text-red-800' :
detection.movement_analysis.alertLevel >= 2 ? 'bg-orange-100 text-orange-800' :
detection.movement_analysis.alertLevel >= 1 ? 'bg-blue-100 text-blue-800' :
'bg-gray-100 text-gray-800'
}`}>
{detection.movement_analysis.description}
</div>
{detection.movement_analysis.rssiTrend && (
<div className="flex items-center space-x-2 mt-1">
<span className="text-gray-600">Trend:</span>
<span className={`font-medium ${
detection.movement_analysis.rssiTrend.trend === 'STRENGTHENING' ? 'text-red-600' :
detection.movement_analysis.rssiTrend.trend === 'WEAKENING' ? 'text-green-600' :
'text-gray-600'
}`}>
{detection.movement_analysis.rssiTrend.trend}
{detection.movement_analysis.rssiTrend.change !== 0 && (
<span className="ml-1">
({detection.movement_analysis.rssiTrend.change > 0 ? '+' : ''}{detection.movement_analysis.rssiTrend.change.toFixed(1)}dB)
</span>
)}
</span>
</div>
)}
{detection.movement_analysis.proximityLevel && (
<div className="flex items-center space-x-2">
<span className="text-gray-600">Proximity:</span>
<span className={`px-1 py-0.5 rounded text-xs font-medium ${
detection.movement_analysis.proximityLevel === 'VERY_CLOSE' ? 'bg-red-100 text-red-700' :
detection.movement_analysis.proximityLevel === 'CLOSE' ? 'bg-orange-100 text-orange-700' :
detection.movement_analysis.proximityLevel === 'MEDIUM' ? 'bg-yellow-100 text-yellow-700' :
'bg-green-100 text-green-700'
}`}>
{detection.movement_analysis.proximityLevel.replace('_', ' ')}
</span>
</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>