Fix jwt-token
This commit is contained in:
@@ -1,2 +1,5 @@
|
|||||||
# Development environment - runs on root path
|
# Development environment - runs on root path
|
||||||
VITE_BASE_PATH=
|
VITE_BASE_PATH=
|
||||||
|
|
||||||
|
# Debug configuration
|
||||||
|
VITE_DEBUG_ENABLED=true
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
# Production environment - deployed under /uggla/ path
|
# Production environment - deployed under /uggla/ path
|
||||||
VITE_BASE_PATH=/uggla/
|
VITE_BASE_PATH=/uggla/
|
||||||
|
|
||||||
|
# Debug configuration
|
||||||
|
VITE_DEBUG_ENABLED=true
|
||||||
|
|||||||
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 { Outlet, Link, useLocation } from 'react-router-dom';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { useSocket } from '../contexts/SocketContext';
|
import { useSocket } from '../contexts/SocketContext';
|
||||||
|
import DebugToggle from './DebugToggle';
|
||||||
import {
|
import {
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
MapIcon,
|
MapIcon,
|
||||||
@@ -148,6 +149,9 @@ const Layout = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* Debug Toggle (floating button) */}
|
||||||
|
<DebugToggle />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user