From e312c61b56a6a09588620facbf3620f78396f481 Mon Sep 17 00:00:00 2001 From: Alexander Borg Date: Mon, 1 Sep 2025 07:24:36 +0200 Subject: [PATCH] Fix jwt-token --- client/src/App.jsx | 2 + client/src/components/Layout.jsx | 14 +- client/src/pages/Debug.jsx | 354 +++++++++++++++++++++++++++++++ server/routes/detections.js | 108 +++++++++- server/routes/detectors.js | 26 +++ server/services/alertService.js | 6 + 6 files changed, 507 insertions(+), 3 deletions(-) create mode 100644 client/src/pages/Debug.jsx diff --git a/client/src/App.jsx b/client/src/App.jsx index 8fc7a4e..5dde829 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -9,6 +9,7 @@ import MapView from './pages/MapView'; import Devices from './pages/Devices'; import Detections from './pages/Detections'; import Alerts from './pages/Alerts'; +import Debug from './pages/Debug'; import Login from './pages/Login'; import ProtectedRoute from './components/ProtectedRoute'; @@ -68,6 +69,7 @@ function App() { } /> } /> } /> + } /> diff --git a/client/src/components/Layout.jsx b/client/src/components/Layout.jsx index f54c2a6..d8d717e 100644 --- a/client/src/components/Layout.jsx +++ b/client/src/components/Layout.jsx @@ -12,11 +12,12 @@ import { Bars3Icon, XMarkIcon, SignalIcon, - WifiIcon + WifiIcon, + BugAntIcon } from '@heroicons/react/24/outline'; import classNames from 'classnames'; -const navigation = [ +const baseNavigation = [ { name: 'Dashboard', href: '/', icon: HomeIcon }, { name: 'Map View', href: '/map', icon: MapIcon }, { name: 'Devices', href: '/devices', icon: ServerIcon }, @@ -24,12 +25,21 @@ const navigation = [ { name: 'Alerts', href: '/alerts', icon: BellIcon }, ]; +const adminNavigation = [ + { name: 'Debug', href: '/debug', icon: BugAntIcon }, +]; + const Layout = () => { const [sidebarOpen, setSidebarOpen] = useState(false); const { user, logout } = useAuth(); const { connected, recentDetections } = useSocket(); const location = useLocation(); + // Build navigation based on user role + const navigation = user?.role === 'admin' + ? [...baseNavigation, ...adminNavigation] + : baseNavigation; + return (
{/* Mobile sidebar */} diff --git a/client/src/pages/Debug.jsx b/client/src/pages/Debug.jsx new file mode 100644 index 0000000..c13507b --- /dev/null +++ b/client/src/pages/Debug.jsx @@ -0,0 +1,354 @@ +import React, { useState, useEffect } from 'react'; +import api from '../services/api'; +import { format } from 'date-fns'; +import { + BugAntIcon, + ExclamationTriangleIcon, + InformationCircleIcon, + EyeIcon, + TrashIcon +} from '@heroicons/react/24/outline'; + +const Debug = () => { + const [debugData, setDebugData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [pagination, setPagination] = useState({}); + const [debugInfo, setDebugInfo] = useState({}); + const [filters, setFilters] = useState({ + drone_type: '', + device_id: '', + page: 1, + limit: 50 + }); + + useEffect(() => { + fetchDebugData(); + }, [filters]); + + const fetchDebugData = async () => { + try { + setLoading(true); + const params = new URLSearchParams(); + + Object.entries(filters).forEach(([key, value]) => { + if (value) params.append(key, value); + }); + + const response = await api.get(`/detections/debug?${params}`); + + if (response.data.success) { + setDebugData(response.data.data); + setPagination(response.data.pagination); + setDebugInfo(response.data.debug_info); + } else { + setError(response.data.message || 'Failed to fetch debug data'); + } + } catch (err) { + console.error('Error fetching debug data:', err); + setError(err.response?.data?.message || 'Failed to fetch debug data'); + } finally { + setLoading(false); + } + }; + + const handleFilterChange = (key, value) => { + setFilters(prev => ({ + ...prev, + [key]: value, + page: 1 // Reset to first page when filtering + })); + }; + + const handlePageChange = (newPage) => { + setFilters(prev => ({ ...prev, page: newPage })); + }; + + const getDroneTypeColor = (droneType) => { + if (droneType === 0) return 'bg-gray-100 text-gray-800'; + return 'bg-blue-100 text-blue-800'; + }; + + const getThreatLevelColor = (threatLevel) => { + switch (threatLevel?.toLowerCase()) { + case 'critical': + return 'bg-red-100 text-red-800'; + case 'high': + return 'bg-orange-100 text-orange-800'; + case 'medium': + return 'bg-yellow-100 text-yellow-800'; + case 'low': + return 'bg-green-100 text-green-800'; + default: + return 'bg-gray-100 text-gray-800'; + } + }; + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+
+ +
+

Error

+
{error}
+
+
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+ +
+

Debug Console

+

+ Admin-only access to all detection data including drone type 0 (None) +

+
+
+
+ + {/* Debug Info */} + {debugInfo && ( +
+
+ +
+

Debug Information

+
+

{debugInfo.message}

+

Total None detections: {debugInfo.total_none_detections}

+
+
+
+
+ )} + + {/* Filters */} +
+

Filters

+
+
+ + +
+ +
+ + handleFilterChange('device_id', e.target.value)} + className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500" + placeholder="Filter by device ID" + /> +
+ +
+ + +
+
+
+ + {/* Debug Data Table */} +
+
+

+ Debug Detections ({pagination.total || 0}) +

+
+ + {debugData.length === 0 ? ( +
+ +

No debug data

+

+ No detections found matching the current filters. +

+
+ ) : ( + <> +
+ + + + + + + + + + + + + {debugData.map((detection) => ( + + + + + + + + + ))} + +
+ ID / Time + + Device + + Drone Type + + RSSI / Freq + + Threat Level + + Debug +
+
#{detection.id}
+
+ {format(new Date(detection.server_timestamp), 'MMM dd, HH:mm:ss')} +
+
+
+ {detection.device?.name || `Device ${detection.device_id}`} +
+
ID: {detection.device_id}
+
+ + {detection.drone_type_info?.name || `Type ${detection.drone_type}`} + +
+ ID: {detection.drone_type} +
+
+
{detection.rssi} dBm
+
{detection.freq} MHz
+
+ {detection.threat_level ? ( + + {detection.threat_level} + + ) : ( + N/A + )} + + {detection.is_debug_data && ( + + Debug Data + + )} +
+
+ + {/* Pagination */} + {pagination.pages > 1 && ( +
+
+ + +
+
+
+

+ Showing {((pagination.page - 1) * pagination.limit) + 1} to{' '} + + {Math.min(pagination.page * pagination.limit, pagination.total)} + {' '} + of {pagination.total} results +

+
+
+ +
+
+
+ )} + + )} +
+
+ ); +}; + +export default Debug; diff --git a/server/routes/detections.js b/server/routes/detections.js index 4511627..2506c2b 100644 --- a/server/routes/detections.js +++ b/server/routes/detections.js @@ -23,7 +23,10 @@ router.get('/', authenticateToken, async (req, res) => { } = req.query; // Build where clause for filtering - const whereClause = {}; + const whereClause = { + // Exclude drone type 0 (None) from normal detection queries + drone_type: { [Op.ne]: 0 } + }; if (device_id) { whereClause.device_id = device_id; @@ -165,4 +168,107 @@ router.delete('/:id', authenticateToken, async (req, res) => { } }); +/** + * GET /api/detections/debug + * Get all detections including drone type 0 (None) for debugging purposes + * Admin access only + */ +router.get('/debug', authenticateToken, async (req, res) => { + try { + // Check if user is admin + if (req.user.role !== 'admin') { + return res.status(403).json({ + success: false, + error: 'Access denied', + message: 'Admin access required for debug data' + }); + } + + const { + device_id, + drone_id, + drone_type, + start_date, + end_date, + page = 1, + limit = 100, + sort = 'server_timestamp', + order = 'desc' + } = req.query; + + // Build where clause for debugging (includes all drone types) + const whereClause = {}; + + if (device_id) { + whereClause.device_id = device_id; + } + + if (drone_id) { + whereClause.drone_id = drone_id; + } + + if (drone_type !== undefined) { + whereClause.drone_type = parseInt(drone_type); + } + + if (start_date) { + whereClause.server_timestamp = { ...whereClause.server_timestamp, [Op.gte]: new Date(start_date) }; + } + + if (end_date) { + whereClause.server_timestamp = { ...whereClause.server_timestamp, [Op.lte]: new Date(end_date) }; + } + + // Calculate offset for pagination + const offset = (parseInt(page) - 1) * parseInt(limit); + + // Query ALL detections including type 0 for debugging + const detections = await DroneDetection.findAndCountAll({ + where: whereClause, + include: [{ + model: Device, + as: 'device', + attributes: ['id', 'name', 'location', 'geo_lat', 'geo_lon'] + }], + limit: parseInt(limit), + offset: offset, + order: [[sort, order.toUpperCase()]] + }); + + // Add drone type information to each detection + const enhancedDetections = detections.rows.map(detection => { + const droneTypeInfo = getDroneTypeInfo(detection.drone_type); + return { + ...detection.toJSON(), + drone_type_info: droneTypeInfo, + is_debug_data: detection.drone_type === 0 + }; + }); + + res.json({ + success: true, + data: enhancedDetections, + pagination: { + total: detections.count, + page: parseInt(page), + limit: parseInt(limit), + pages: Math.ceil(detections.count / parseInt(limit)) + }, + debug_info: { + includes_none_detections: true, + total_none_detections: await DroneDetection.count({ where: { drone_type: 0 } }), + message: "Debug data includes drone type 0 (None) detections" + } + }); + + } catch (error) { + console.error('Error fetching debug detections:', error); + res.status(500).json({ + success: false, + error: 'Failed to fetch debug detections', + details: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' + }); + } +}); + module.exports = router; diff --git a/server/routes/detectors.js b/server/routes/detectors.js index c72e2c5..0812590 100644 --- a/server/routes/detectors.js +++ b/server/routes/detectors.js @@ -7,6 +7,12 @@ const AlertService = require('../services/alertService'); const DroneTrackingService = require('../services/droneTrackingService'); const { getDroneTypeInfo, getDroneTypeName } = require('../utils/droneTypes'); +// Configuration for debugging and data storage +const DEBUG_CONFIG = { + storeNoneDetections: process.env.STORE_NONE_DETECTIONS === 'true', // Store drone_type 0 for debugging + logAllDetections: process.env.LOG_ALL_DETECTIONS === 'true' // Log all detections including type 0 +}; + // Initialize services const alertService = new AlertService(); const droneTracker = new DroneTrackingService(); @@ -250,6 +256,26 @@ async function handleDetection(req, res) { }); } + // Handle drone type 0 (None) - should not trigger alarms or be stored as detection + if (detectionData.drone_type === 0) { + if (DEBUG_CONFIG.logAllDetections) { + console.log(`🔍 Debug: Drone type 0 (None) received from device ${detectionData.device_id}`); + } + + if (!DEBUG_CONFIG.storeNoneDetections) { + // Don't store in database, just acknowledge receipt + return res.status(200).json({ + success: true, + message: 'Heartbeat received (no detection)', + stored: false, + debug: DEBUG_CONFIG.logAllDetections + }); + } + + // If debugging enabled, store but mark as debug data + console.log(`🐛 Debug mode: Storing drone type 0 detection for debugging`); + } + // Create detection record const detection = await DroneDetection.create({ ...detectionData, diff --git a/server/services/alertService.js b/server/services/alertService.js index 7c16482..e5036e9 100644 --- a/server/services/alertService.js +++ b/server/services/alertService.js @@ -136,6 +136,12 @@ class AlertService { async processAlert(detection) { try { + // Skip alert processing for drone type 0 (None) - no actual detection + if (detection.drone_type === 0) { + console.log(`🔍 Skipping alert processing for drone type 0 (None) - detection ${detection.id}`); + return; + } + console.log(`🔍 Processing alert for detection ${detection.id}`); // Assess threat level based on RSSI and drone type