Fix jwt-token
This commit is contained in:
263
client/src/components/MovementAlertsPanel.jsx
Normal file
263
client/src/components/MovementAlertsPanel.jsx
Normal 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;
|
||||
Reference in New Issue
Block a user