Fix jwt-token
This commit is contained in:
407
client/src/components/AlertModals.jsx
Normal file
407
client/src/components/AlertModals.jsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
XCircleIcon,
|
||||
ExclamationTriangleIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { EditAlertModal, DetectionDetailsModal } from '../components/AlertModals';
|
||||
|
||||
const Alerts = () => {
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user