Fix jwt-token
This commit is contained in:
282
client/src/components/DebugOverlay.jsx
Normal file
282
client/src/components/DebugOverlay.jsx
Normal file
@@ -0,0 +1,282 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import api from '../services/api';
|
||||
import { format } from 'date-fns';
|
||||
import {
|
||||
XMarkIcon,
|
||||
BugAntIcon,
|
||||
EyeIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
ArrowPathIcon,
|
||||
ExclamationTriangleIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
const DebugOverlay = ({ isOpen, onClose }) => {
|
||||
const [activeTab, setActiveTab] = useState('detections');
|
||||
const [detectionPayloads, setDetectionPayloads] = useState([]);
|
||||
const [heartbeatPayloads, setHeartbeatPayloads] = useState([]);
|
||||
const [debugConfig, setDebugConfig] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [expandedItems, setExpandedItems] = useState(new Set());
|
||||
const [filters, setFilters] = useState({
|
||||
limit: 20,
|
||||
device_id: ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
fetchDebugConfig();
|
||||
fetchPayloads();
|
||||
}
|
||||
}, [isOpen, activeTab, filters]);
|
||||
|
||||
const fetchDebugConfig = async () => {
|
||||
try {
|
||||
const response = await api.get('/debug/config');
|
||||
if (response.data.success) {
|
||||
setDebugConfig(response.data.config);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching debug config:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPayloads = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const endpoint = activeTab === 'detections' ? '/debug/detection-payloads' : '/debug/heartbeat-payloads';
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (filters.limit) params.append('limit', filters.limit);
|
||||
if (filters.device_id) params.append('device_id', filters.device_id);
|
||||
|
||||
const response = await api.get(`${endpoint}?${params}`);
|
||||
|
||||
if (response.data.success) {
|
||||
if (activeTab === 'detections') {
|
||||
setDetectionPayloads(response.data.data);
|
||||
} else {
|
||||
setHeartbeatPayloads(response.data.data);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching payloads:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleExpanded = (id) => {
|
||||
const newExpanded = new Set(expandedItems);
|
||||
if (newExpanded.has(id)) {
|
||||
newExpanded.delete(id);
|
||||
} else {
|
||||
newExpanded.add(id);
|
||||
}
|
||||
setExpandedItems(newExpanded);
|
||||
};
|
||||
|
||||
const formatPayload = (payload) => {
|
||||
return JSON.stringify(payload, null, 2);
|
||||
};
|
||||
|
||||
const currentData = activeTab === 'detections' ? detectionPayloads : heartbeatPayloads;
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 overflow-hidden">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black bg-opacity-50"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Overlay Panel */}
|
||||
<div className="absolute right-0 top-0 h-full w-full max-w-4xl bg-white shadow-xl">
|
||||
<div className="flex h-full flex-col">
|
||||
{/* Header */}
|
||||
<div className="border-b border-gray-200 bg-gray-50 px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<BugAntIcon className="h-6 w-6 text-gray-600" />
|
||||
<h2 className="text-lg font-semibold text-gray-900">Debug Payload Viewer</h2>
|
||||
{debugConfig.STORE_RAW_PAYLOAD && (
|
||||
<span className="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
||||
Active
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
|
||||
>
|
||||
<XMarkIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="mt-4 flex space-x-4">
|
||||
<button
|
||||
onClick={() => setActiveTab('detections')}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-md ${
|
||||
activeTab === 'detections'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Detection Payloads ({detectionPayloads.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('heartbeats')}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-md ${
|
||||
activeTab === 'heartbeats'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Heartbeat Payloads ({heartbeatPayloads.length})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="border-b border-gray-200 bg-gray-50 px-6 py-3">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<label className="text-sm font-medium text-gray-700">Limit:</label>
|
||||
<select
|
||||
value={filters.limit}
|
||||
onChange={(e) => setFilters({...filters, limit: e.target.value})}
|
||||
className="rounded border-gray-300 text-sm"
|
||||
>
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<label className="text-sm font-medium text-gray-700">Device ID:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={filters.device_id}
|
||||
onChange={(e) => setFilters({...filters, device_id: e.target.value})}
|
||||
placeholder="Filter by device ID"
|
||||
className="rounded border-gray-300 text-sm w-40"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={fetchPayloads}
|
||||
disabled={loading}
|
||||
className="flex items-center space-x-1 rounded bg-blue-600 px-3 py-1 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
<ArrowPathIcon className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
|
||||
<span>Refresh</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-auto">
|
||||
{!debugConfig.STORE_RAW_PAYLOAD && (
|
||||
<div className="p-6">
|
||||
<div className="rounded-md bg-yellow-50 p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-400" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-yellow-800">
|
||||
Raw Payload Storage Disabled
|
||||
</h3>
|
||||
<div className="mt-2 text-sm text-yellow-700">
|
||||
<p>Set STORE_RAW_PAYLOAD=true in environment variables to enable payload storage.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<ArrowPathIcon className="h-8 w-8 animate-spin text-gray-400" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && currentData.length === 0 && (
|
||||
<div className="p-6 text-center text-gray-500">
|
||||
No {activeTab} payloads found
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2 p-4">
|
||||
{currentData.map((item) => (
|
||||
<div key={item.id} className="rounded-lg border border-gray-200 bg-white">
|
||||
<div
|
||||
className="flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50"
|
||||
onClick={() => toggleExpanded(item.id)}
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
Device {item.device_id}
|
||||
</span>
|
||||
{activeTab === 'detections' && (
|
||||
<>
|
||||
<span className="text-sm text-gray-500">
|
||||
Drone {item.drone_id} | Type {item.drone_type}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
RSSI: {item.rssi}dBm
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span className="text-sm text-gray-500">
|
||||
{format(new Date(item.server_timestamp || item.received_at), 'MMM dd, HH:mm:ss')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{item.raw_payload && (
|
||||
<span className="inline-flex items-center rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-800">
|
||||
Raw Data
|
||||
</span>
|
||||
)}
|
||||
{expandedItems.has(item.id) ? (
|
||||
<ChevronUpIcon className="h-5 w-5 text-gray-400" />
|
||||
) : (
|
||||
<ChevronDownIcon className="h-5 w-5 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{expandedItems.has(item.id) && (
|
||||
<div className="border-t border-gray-200 p-4">
|
||||
{item.raw_payload ? (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-2">Raw Payload:</h4>
|
||||
<pre className="text-xs bg-gray-50 p-3 rounded overflow-auto max-h-96">
|
||||
{formatPayload(item.raw_payload)}
|
||||
</pre>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-gray-500">
|
||||
No raw payload stored for this item
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugOverlay;
|
||||
34
client/src/components/DebugToggle.jsx
Normal file
34
client/src/components/DebugToggle.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { useState } from 'react';
|
||||
import { BugAntIcon } from '@heroicons/react/24/outline';
|
||||
import DebugOverlay from './DebugOverlay';
|
||||
|
||||
const DebugToggle = () => {
|
||||
const [isDebugOpen, setIsDebugOpen] = useState(false);
|
||||
|
||||
// Only show in development or when debug is enabled
|
||||
const shouldShow = import.meta.env.DEV ||
|
||||
import.meta.env.VITE_DEBUG_ENABLED === 'true';
|
||||
|
||||
if (!shouldShow) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Floating Debug Button */}
|
||||
<button
|
||||
onClick={() => setIsDebugOpen(true)}
|
||||
className="fixed bottom-4 right-4 z-40 flex h-12 w-12 items-center justify-center rounded-full bg-gray-800 text-white shadow-lg hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
|
||||
title="Open Debug Panel"
|
||||
>
|
||||
<BugAntIcon className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
{/* Debug Overlay */}
|
||||
<DebugOverlay
|
||||
isOpen={isDebugOpen}
|
||||
onClose={() => setIsDebugOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugToggle;
|
||||
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
||||
import { Outlet, Link, useLocation } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useSocket } from '../contexts/SocketContext';
|
||||
import DebugToggle from './DebugToggle';
|
||||
import {
|
||||
HomeIcon,
|
||||
MapIcon,
|
||||
@@ -148,6 +149,9 @@ const Layout = () => {
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Debug Toggle (floating button) */}
|
||||
<DebugToggle />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user