Fix jwt-token

This commit is contained in:
2025-08-18 06:21:59 +02:00
parent 136549f9a0
commit c3773f2b88
2 changed files with 429 additions and 0 deletions

View File

@@ -0,0 +1,407 @@
import React, { useState, useEffect } from 'react';
import api from '../services/api';
import { format } from 'date-fns';
import { XMarkIcon } from '@heroicons/react/24/outline';
// Edit Alert Rule Modal
export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
const [formData, setFormData] = useState({
name: '',
description: '',
priority: 'medium',
min_detections: 1,
time_window: 300,
cooldown_period: 600,
alert_channels: ['sms'],
min_threat_level: '',
sms_phone_number: '',
webhook_url: ''
});
const [saving, setSaving] = useState(false);
useEffect(() => {
if (rule) {
setFormData({
name: rule.name || '',
description: rule.description || '',
priority: rule.priority || 'medium',
min_detections: rule.min_detections || 1,
time_window: rule.time_window || 300,
cooldown_period: rule.cooldown_period || 600,
alert_channels: rule.alert_channels || ['sms'],
min_threat_level: rule.min_threat_level || '',
sms_phone_number: rule.sms_phone_number || '',
webhook_url: rule.webhook_url || ''
});
}
}, [rule]);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleChannelChange = (channel, checked) => {
setFormData(prev => ({
...prev,
alert_channels: checked
? [...prev.alert_channels, channel]
: prev.alert_channels.filter(c => c !== channel)
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
try {
await api.put(`/alerts/rules/${rule.id}`, formData);
onSuccess();
onClose();
} catch (error) {
console.error('Error updating alert rule:', error);
alert('Failed to update alert rule');
} finally {
setSaving(false);
}
};
return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div className="flex items-center justify-between pb-3">
<h3 className="text-lg font-medium">Edit Alert Rule</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<XMarkIcon className="h-6 w-6" />
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Name *
</label>
<input
type="text"
name="name"
required
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
value={formData.name}
onChange={handleChange}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Description
</label>
<textarea
name="description"
rows="2"
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
value={formData.description}
onChange={handleChange}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Priority
</label>
<select
name="priority"
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
value={formData.priority}
onChange={handleChange}
>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Min Threat Level
</label>
<select
name="min_threat_level"
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
value={formData.min_threat_level}
onChange={handleChange}
>
<option value="">Any Level</option>
<option value="monitoring">Monitoring</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Min Detections
</label>
<input
type="number"
name="min_detections"
min="1"
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
value={formData.min_detections}
onChange={handleChange}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Time Window (seconds)
</label>
<input
type="number"
name="time_window"
min="60"
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
value={formData.time_window}
onChange={handleChange}
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Cooldown Period (seconds)
</label>
<input
type="number"
name="cooldown_period"
min="0"
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
value={formData.cooldown_period}
onChange={handleChange}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Alert Channels
</label>
<div className="space-y-2">
{['sms', 'email', 'webhook'].map(channel => (
<label key={channel} className="flex items-center">
<input
type="checkbox"
checked={formData.alert_channels.includes(channel)}
onChange={(e) => handleChannelChange(channel, e.target.checked)}
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
/>
<span className="ml-2 text-sm text-gray-700 capitalize">
{channel}
</span>
</label>
))}
</div>
</div>
{formData.alert_channels.includes('sms') && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
SMS Phone Number
</label>
<input
type="tel"
name="sms_phone_number"
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
value={formData.sms_phone_number}
onChange={handleChange}
placeholder="+1234567890"
/>
</div>
)}
{formData.alert_channels.includes('webhook') && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Webhook URL
</label>
<input
type="url"
name="webhook_url"
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
value={formData.webhook_url}
onChange={handleChange}
placeholder="https://example.com/webhook"
/>
</div>
)}
<div className="flex space-x-3 pt-4">
<button
type="submit"
disabled={saving}
className="flex-1 bg-primary-600 text-white px-4 py-2 rounded-md hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 disabled:opacity-50"
>
{saving ? 'Updating...' : 'Update Rule'}
</button>
<button
type="button"
onClick={onClose}
className="flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500"
>
Cancel
</button>
</div>
</form>
</div>
</div>
);
};
// Detection Details Modal
export const DetectionDetailsModal = ({ detection, onClose }) => {
if (!detection) return null;
const getPriorityColor = (level) => {
switch (level?.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';
}
};
return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div className="relative top-20 mx-auto p-5 border w-2/3 max-w-4xl shadow-lg rounded-md bg-white">
<div className="flex items-center justify-between pb-3">
<h3 className="text-lg font-medium">Detection Details</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<XMarkIcon className="h-6 w-6" />
</button>
</div>
<div className="space-y-6">
{/* Basic Information */}
<div className="grid grid-cols-2 gap-6">
<div className="space-y-3">
<h4 className="font-medium text-gray-900">Basic Information</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-500">ID:</span>
<span className="font-mono text-xs">{detection.id}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Detected At:</span>
<span>{format(new Date(detection.detected_at), 'MMM dd, yyyy HH:mm:ss')}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Device:</span>
<span>{detection.device?.name || `Device ${detection.device_id}`}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Location:</span>
<span>{detection.device?.location || 'Unknown'}</span>
</div>
</div>
</div>
<div className="space-y-3">
<h4 className="font-medium text-gray-900">Signal Information</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-500">RSSI:</span>
<span className="font-medium">{detection.rssi} dBm</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Frequency:</span>
<span>{detection.frequency ? `${detection.frequency} MHz` : 'N/A'}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Drone Type:</span>
<span>{detection.drone_type || 'Unknown'}</span>
</div>
{detection.estimated_distance && (
<div className="flex justify-between">
<span className="text-gray-500">Est. Distance:</span>
<span>{detection.estimated_distance}m</span>
</div>
)}
</div>
</div>
</div>
{/* Threat Assessment */}
{detection.threat_assessment && (
<div className="space-y-3">
<h4 className="font-medium text-gray-900">Threat Assessment</h4>
<div className="bg-gray-50 p-4 rounded-md">
<div className="flex items-center justify-between mb-3">
<span className="text-sm text-gray-500">Threat Level:</span>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getPriorityColor(detection.threat_assessment.level)}`}>
{detection.threat_assessment.level?.toUpperCase()}
</span>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-500">Priority:</span>
<span>{detection.threat_assessment.priority}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Immediate Action:</span>
<span className={detection.threat_assessment.requiresImmediateAction ? 'text-red-600 font-medium' : 'text-gray-600'}>
{detection.threat_assessment.requiresImmediateAction ? 'Required' : 'Not Required'}
</span>
</div>
{detection.threat_assessment.description && (
<div className="mt-3">
<span className="text-gray-500 block mb-1">Description:</span>
<p className="text-gray-700">{detection.threat_assessment.description}</p>
</div>
)}
</div>
</div>
</div>
)}
{/* Raw Data */}
{detection.raw_data && (
<div className="space-y-3">
<h4 className="font-medium text-gray-900">Raw Data</h4>
<div className="bg-gray-900 text-green-400 p-4 rounded-md font-mono text-xs overflow-x-auto">
<pre>{JSON.stringify(detection.raw_data, null, 2)}</pre>
</div>
</div>
)}
</div>
<div className="flex justify-end pt-4">
<button
onClick={onClose}
className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500"
>
Close
</button>
</div>
</div>
</div>
);
};

View File

@@ -8,6 +8,7 @@ import {
XCircleIcon, XCircleIcon,
ExclamationTriangleIcon ExclamationTriangleIcon
} from '@heroicons/react/24/outline'; } from '@heroicons/react/24/outline';
import { EditAlertModal, DetectionDetailsModal } from '../components/AlertModals';
const Alerts = () => { const Alerts = () => {
const [alertRules, setAlertRules] = useState([]); const [alertRules, setAlertRules] = useState([]);
@@ -388,6 +389,27 @@ const Alerts = () => {
}} }}
/> />
)} )}
{showEditModal && editingRule && (
<EditAlertModal
rule={editingRule}
onClose={() => {
setShowEditModal(false);
setEditingRule(null);
}}
onSuccess={fetchAlertData}
/>
)}
{showDetectionModal && selectedDetection && (
<DetectionDetailsModal
detection={selectedDetection}
onClose={() => {
setShowDetectionModal(false);
setSelectedDetection(null);
}}
/>
)}
</div> </div>
); );
}; };