Fix jwt-token
This commit is contained in:
181
client/src/hooks/useDroneTypes.js
Normal file
181
client/src/hooks/useDroneTypes.js
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for fetching and managing drone types from the API
|
||||||
|
* Provides caching, error handling, and convenient access to drone type data
|
||||||
|
*/
|
||||||
|
export const useDroneTypes = () => {
|
||||||
|
const [droneTypes, setDroneTypes] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
// Fetch drone types from API
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDroneTypes = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const response = await fetch('/api/drone-types');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch drone types: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success && result.data) {
|
||||||
|
setDroneTypes(result.data);
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid response format from drone types API');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching drone types:', err);
|
||||||
|
setError(err.message);
|
||||||
|
// Set fallback data on error
|
||||||
|
setDroneTypes(getFallbackDroneTypes());
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchDroneTypes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Create a mapping object for quick lookups by ID
|
||||||
|
const droneTypeMap = useMemo(() => {
|
||||||
|
const map = {};
|
||||||
|
droneTypes.forEach(type => {
|
||||||
|
map[type.id] = type;
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [droneTypes]);
|
||||||
|
|
||||||
|
// Get drone type info by ID with fallback
|
||||||
|
const getDroneTypeInfo = (droneTypeId) => {
|
||||||
|
const typeInfo = droneTypeMap[droneTypeId];
|
||||||
|
|
||||||
|
if (typeInfo) {
|
||||||
|
return {
|
||||||
|
...typeInfo,
|
||||||
|
// Add visual styling based on threat level
|
||||||
|
...getVisualStyling(typeInfo)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for unknown types
|
||||||
|
return {
|
||||||
|
id: droneTypeId,
|
||||||
|
name: 'Unknown',
|
||||||
|
category: 'Unknown',
|
||||||
|
threat_level: 'medium',
|
||||||
|
description: `Unknown drone type ${droneTypeId}`,
|
||||||
|
...getVisualStyling({ threat_level: 'medium' })
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get drone types by category
|
||||||
|
const getDroneTypesByCategory = (category) => {
|
||||||
|
return droneTypes.filter(type => type.category === category);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get drone types by threat level
|
||||||
|
const getDroneTypesByThreatLevel = (threatLevel) => {
|
||||||
|
return droneTypes.filter(type => type.threat_level === threatLevel);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all categories
|
||||||
|
const getCategories = () => {
|
||||||
|
const categories = new Set(droneTypes.map(type => type.category));
|
||||||
|
return Array.from(categories);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all threat levels
|
||||||
|
const getThreatLevels = () => {
|
||||||
|
const levels = new Set(droneTypes.map(type => type.threat_level));
|
||||||
|
return Array.from(levels);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
droneTypes,
|
||||||
|
droneTypeMap,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
getDroneTypeInfo,
|
||||||
|
getDroneTypesByCategory,
|
||||||
|
getDroneTypesByThreatLevel,
|
||||||
|
getCategories,
|
||||||
|
getThreatLevels
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get visual styling based on threat level and category
|
||||||
|
*/
|
||||||
|
const getVisualStyling = (typeInfo) => {
|
||||||
|
const isMilitary = typeInfo.category?.includes('Military') ||
|
||||||
|
typeInfo.threat_level === 'critical';
|
||||||
|
|
||||||
|
const isWarning = isMilitary || typeInfo.threat_level === 'high';
|
||||||
|
|
||||||
|
// Base styling
|
||||||
|
let styling = {
|
||||||
|
warning: isWarning,
|
||||||
|
icon: isMilitary ? '⚠️' : '🛩️'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Color scheme based on threat level
|
||||||
|
switch (typeInfo.threat_level) {
|
||||||
|
case 'critical':
|
||||||
|
styling = {
|
||||||
|
...styling,
|
||||||
|
color: 'red',
|
||||||
|
bgColor: 'bg-red-100',
|
||||||
|
textColor: 'text-red-800',
|
||||||
|
borderColor: 'border-red-300'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'high':
|
||||||
|
styling = {
|
||||||
|
...styling,
|
||||||
|
color: 'orange',
|
||||||
|
bgColor: 'bg-orange-100',
|
||||||
|
textColor: 'text-orange-800',
|
||||||
|
borderColor: 'border-orange-300'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'medium':
|
||||||
|
styling = {
|
||||||
|
...styling,
|
||||||
|
color: 'yellow',
|
||||||
|
bgColor: 'bg-yellow-100',
|
||||||
|
textColor: 'text-yellow-800',
|
||||||
|
borderColor: 'border-yellow-300'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'low':
|
||||||
|
default:
|
||||||
|
styling = {
|
||||||
|
...styling,
|
||||||
|
color: 'gray',
|
||||||
|
bgColor: 'bg-gray-100',
|
||||||
|
textColor: 'text-gray-800',
|
||||||
|
borderColor: 'border-gray-300'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return styling;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback drone types for when API is unavailable
|
||||||
|
* This ensures the app continues working even if the API is down
|
||||||
|
*/
|
||||||
|
const getFallbackDroneTypes = () => {
|
||||||
|
return [
|
||||||
|
{ id: 0, name: 'None', category: 'Unknown', threat_level: 'low' },
|
||||||
|
{ id: 1, name: 'Unknown', category: 'Unknown', threat_level: 'medium' },
|
||||||
|
{ id: 2, name: 'Orlan', category: 'Military/Reconnaissance', threat_level: 'critical' },
|
||||||
|
{ id: 3, name: 'Zala', category: 'Military/Surveillance', threat_level: 'critical' },
|
||||||
|
{ id: 13, name: 'DJI', category: 'Commercial/Professional', threat_level: 'low' }
|
||||||
|
];
|
||||||
|
};
|
||||||
@@ -12,8 +12,12 @@ import {
|
|||||||
ChevronRightIcon
|
ChevronRightIcon
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { EditAlertModal, DetectionDetailsModal } from '../components/AlertModals';
|
import { EditAlertModal, DetectionDetailsModal } from '../components/AlertModals';
|
||||||
|
import { useDroneTypes } from '../hooks/useDroneTypes';
|
||||||
|
|
||||||
const Alerts = () => {
|
const Alerts = () => {
|
||||||
|
// Drone types hook for dynamic drone type data
|
||||||
|
const { getDroneTypeInfo: getDroneTypeInfoFromAPI, loading: droneTypesLoading } = useDroneTypes();
|
||||||
|
|
||||||
const [alertRules, setAlertRules] = useState([]);
|
const [alertRules, setAlertRules] = useState([]);
|
||||||
const [alertLogs, setAlertLogs] = useState([]);
|
const [alertLogs, setAlertLogs] = useState([]);
|
||||||
const [alertStats, setAlertStats] = useState(null);
|
const [alertStats, setAlertStats] = useState(null);
|
||||||
@@ -52,6 +56,9 @@ const Alerts = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Show loading if either alerts or drone types are loading
|
||||||
|
const isLoading = loading || droneTypesLoading;
|
||||||
|
|
||||||
// Group alerts by alert_event_id to show related alerts together
|
// Group alerts by alert_event_id to show related alerts together
|
||||||
const groupAlertsByEvent = (logs) => {
|
const groupAlertsByEvent = (logs) => {
|
||||||
const grouped = {};
|
const grouped = {};
|
||||||
@@ -89,35 +96,14 @@ const Alerts = () => {
|
|||||||
setExpandedGroups(newExpanded);
|
setExpandedGroups(newExpanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get drone type information with visual styling
|
// Get drone type information with visual styling (now using dynamic API data)
|
||||||
const getDroneTypeInfo = (detection) => {
|
const getDroneTypeInfo = (detection) => {
|
||||||
if (!detection || detection.drone_type === undefined) {
|
if (!detection || detection.drone_type === undefined) {
|
||||||
return { name: 'Unknown', color: 'gray', bgColor: 'bg-gray-100', textColor: 'text-gray-600', icon: '🔍' };
|
return { name: 'Unknown', color: 'gray', bgColor: 'bg-gray-100', textColor: 'text-gray-600', icon: '🔍' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map drone types based on the backend droneTypes utility
|
// Use the dynamic API data from the hook
|
||||||
const droneTypeMap = {
|
return getDroneTypeInfoFromAPI(detection.drone_type);
|
||||||
0: { name: 'Unknown', color: 'gray', bgColor: 'bg-gray-100', textColor: 'text-gray-600', icon: '🔍' },
|
|
||||||
1: { name: 'Generic', color: 'blue', bgColor: 'bg-blue-100', textColor: 'text-blue-800', icon: '🚁' },
|
|
||||||
2: { name: 'Orlan', color: 'red', bgColor: 'bg-red-100', textColor: 'text-red-800', icon: '⚠️', warning: '⚠️ MILITARY' },
|
|
||||||
3: { name: 'Zala', color: 'orange', bgColor: 'bg-orange-100', textColor: 'text-orange-800', icon: '🔶', warning: '⚠️ MILITARY' },
|
|
||||||
4: { name: 'Forpost', color: 'purple', bgColor: 'bg-purple-100', textColor: 'text-purple-800', icon: '🟣', warning: '⚠️ MILITARY' },
|
|
||||||
5: { name: 'Inokhodets', color: 'indigo', bgColor: 'bg-indigo-100', textColor: 'text-indigo-800', icon: '🔷', warning: '⚠️ MILITARY' },
|
|
||||||
6: { name: 'Lancet', color: 'red', bgColor: 'bg-red-200', textColor: 'text-red-900', icon: '💥', warning: '⚠️ MILITARY' },
|
|
||||||
7: { name: 'Shahed', color: 'yellow', bgColor: 'bg-yellow-100', textColor: 'text-yellow-800', icon: '⚡', warning: '⚠️ MILITARY' },
|
|
||||||
8: { name: 'Geran', color: 'amber', bgColor: 'bg-amber-100', textColor: 'text-amber-800', icon: '🟨', warning: '⚠️ MILITARY' },
|
|
||||||
9: { name: 'Kub', color: 'green', bgColor: 'bg-green-100', textColor: 'text-green-800', icon: '🟢', warning: '⚠️ MILITARY' },
|
|
||||||
10: { name: 'X-UAV', color: 'teal', bgColor: 'bg-teal-100', textColor: 'text-teal-800', icon: '🔷' },
|
|
||||||
11: { name: 'SuperCam', color: 'cyan', bgColor: 'bg-cyan-100', textColor: 'text-cyan-800', icon: '📷' },
|
|
||||||
12: { name: 'Eleron', color: 'lime', bgColor: 'bg-lime-100', textColor: 'text-lime-800', icon: '🟩' },
|
|
||||||
13: { name: 'DJI', color: 'blue', bgColor: 'bg-blue-200', textColor: 'text-blue-900', icon: '📱' },
|
|
||||||
14: { name: 'Autel', color: 'violet', bgColor: 'bg-violet-100', textColor: 'text-violet-800', icon: '🟪' },
|
|
||||||
15: { name: 'Parrot', color: 'emerald', bgColor: 'bg-emerald-100', textColor: 'text-emerald-800', icon: '🦜' },
|
|
||||||
16: { name: 'Skydio', color: 'sky', bgColor: 'bg-sky-100', textColor: 'text-sky-800', icon: '☁️' },
|
|
||||||
17: { name: 'CryptoOrlan', color: 'red', bgColor: 'bg-red-300', textColor: 'text-red-900', icon: '🔴', warning: '⚠️ MILITARY' }
|
|
||||||
};
|
|
||||||
|
|
||||||
return droneTypeMap[detection.drone_type] || droneTypeMap[0];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteRule = async (ruleId) => {
|
const handleDeleteRule = async (ruleId) => {
|
||||||
@@ -174,11 +160,13 @@ const Alerts = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary-600"></div>
|
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary-600"></div>
|
||||||
<span className="ml-4 text-gray-600">{t('alerts.loading')}</span>
|
<span className="ml-4 text-gray-600">
|
||||||
|
{droneTypesLoading ? 'Loading drone types...' : t('alerts.loading')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -338,29 +326,8 @@ const Alerts = () => {
|
|||||||
<div className="text-xs text-gray-500">{t('alerts.droneTypes')}:</div>
|
<div className="text-xs text-gray-500">{t('alerts.droneTypes')}:</div>
|
||||||
<div className="flex flex-wrap gap-1 mt-1">
|
<div className="flex flex-wrap gap-1 mt-1">
|
||||||
{rule.drone_types.map((typeId, index) => {
|
{rule.drone_types.map((typeId, index) => {
|
||||||
// Use the same comprehensive drone type mapping as getDroneTypeInfo
|
// Use the dynamic API data instead of hardcoded mapping
|
||||||
const droneTypeMap = {
|
const droneInfo = getDroneTypeInfoFromAPI(typeId);
|
||||||
0: { name: 'Unknown', color: 'gray' },
|
|
||||||
1: { name: 'Generic', color: 'blue' },
|
|
||||||
2: { name: 'Orlan', color: 'red', warning: true },
|
|
||||||
3: { name: 'Zala', color: 'orange', warning: true },
|
|
||||||
4: { name: 'Forpost', color: 'purple', warning: true },
|
|
||||||
5: { name: 'Inokhodets', color: 'indigo', warning: true },
|
|
||||||
6: { name: 'Lancet', color: 'red', warning: true },
|
|
||||||
7: { name: 'Shahed', color: 'yellow', warning: true },
|
|
||||||
8: { name: 'Geran', color: 'amber', warning: true },
|
|
||||||
9: { name: 'Kub', color: 'green', warning: true },
|
|
||||||
10: { name: 'X-UAV', color: 'teal' },
|
|
||||||
11: { name: 'SuperCam', color: 'cyan' },
|
|
||||||
12: { name: 'Eleron', color: 'lime' },
|
|
||||||
13: { name: 'DJI', color: 'blue' },
|
|
||||||
14: { name: 'Autel', color: 'violet' },
|
|
||||||
15: { name: 'Parrot', color: 'emerald' },
|
|
||||||
16: { name: 'Skydio', color: 'sky' },
|
|
||||||
17: { name: 'CryptoOrlan', color: 'red', warning: true }
|
|
||||||
};
|
|
||||||
|
|
||||||
const droneInfo = droneTypeMap[typeId] || droneTypeMap[0];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
|
|||||||
Reference in New Issue
Block a user