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 (
+
+ );
+ }
+
+ 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.
+
+
+ ) : (
+ <>
+
+
+
+
+ |
+ ID / Time
+ |
+
+ Device
+ |
+
+ Drone Type
+ |
+
+ RSSI / Freq
+ |
+
+ Threat Level
+ |
+
+ Debug
+ |
+
+
+
+ {debugData.map((detection) => (
+
+ |
+ #{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