Fix jwt-token
This commit is contained in:
@@ -9,7 +9,8 @@ import {
|
|||||||
XCircleIcon,
|
XCircleIcon,
|
||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronRightIcon
|
ChevronRightIcon,
|
||||||
|
XMarkIcon
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { EditAlertModal, DetectionDetailsModal } from '../components/AlertModals';
|
import { EditAlertModal, DetectionDetailsModal } from '../components/AlertModals';
|
||||||
import { useDroneTypes } from '../hooks/useDroneTypes';
|
import { useDroneTypes } from '../hooks/useDroneTypes';
|
||||||
@@ -28,6 +29,8 @@ const Alerts = () => {
|
|||||||
const [editingRule, setEditingRule] = useState(null);
|
const [editingRule, setEditingRule] = useState(null);
|
||||||
const [showDetectionModal, setShowDetectionModal] = useState(false);
|
const [showDetectionModal, setShowDetectionModal] = useState(false);
|
||||||
const [selectedDetection, setSelectedDetection] = useState(null);
|
const [selectedDetection, setSelectedDetection] = useState(null);
|
||||||
|
const [showAlertDetailsModal, setShowAlertDetailsModal] = useState(false);
|
||||||
|
const [selectedAlertForDetails, setSelectedAlertForDetails] = useState(null);
|
||||||
const [expandedGroups, setExpandedGroups] = useState(new Set());
|
const [expandedGroups, setExpandedGroups] = useState(new Set());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -132,6 +135,71 @@ const Alerts = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Parse error message JSON to extract meaningful information
|
||||||
|
const parseErrorMessage = (errorMessage) => {
|
||||||
|
if (!errorMessage) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if it's a success message
|
||||||
|
if (errorMessage.startsWith('Success:')) {
|
||||||
|
const jsonPart = errorMessage.substring(8).trim();
|
||||||
|
const successData = JSON.parse(jsonPart);
|
||||||
|
return {
|
||||||
|
type: 'success',
|
||||||
|
data: successData
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a webhook network error
|
||||||
|
if (errorMessage.startsWith('Webhook network error:')) {
|
||||||
|
const jsonPart = errorMessage.substring(22).trim();
|
||||||
|
const errorData = JSON.parse(jsonPart);
|
||||||
|
return {
|
||||||
|
type: 'webhook_error',
|
||||||
|
data: errorData
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an SMS error
|
||||||
|
if (errorMessage.startsWith('SMS error:')) {
|
||||||
|
const jsonPart = errorMessage.substring(10).trim();
|
||||||
|
const errorData = JSON.parse(jsonPart);
|
||||||
|
return {
|
||||||
|
type: 'sms_error',
|
||||||
|
data: errorData
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an email error
|
||||||
|
if (errorMessage.startsWith('Email error:')) {
|
||||||
|
const jsonPart = errorMessage.substring(12).trim();
|
||||||
|
const errorData = JSON.parse(jsonPart);
|
||||||
|
return {
|
||||||
|
type: 'email_error',
|
||||||
|
data: errorData
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse as direct JSON
|
||||||
|
const parsed = JSON.parse(errorMessage);
|
||||||
|
return {
|
||||||
|
type: 'parsed_json',
|
||||||
|
data: parsed
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// If parsing fails, return the raw message
|
||||||
|
return {
|
||||||
|
type: 'raw',
|
||||||
|
data: { message: errorMessage }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleViewAlertDetails = (alert) => {
|
||||||
|
setSelectedAlertForDetails(alert);
|
||||||
|
setShowAlertDetailsModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
const getStatusIcon = (status) => {
|
const getStatusIcon = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'sent':
|
case 'sent':
|
||||||
@@ -417,6 +485,7 @@ const Alerts = () => {
|
|||||||
<th>Drone Type</th>
|
<th>Drone Type</th>
|
||||||
<th>{t('alerts.droneId')}</th>
|
<th>{t('alerts.droneId')}</th>
|
||||||
<th>{t('alerts.detection')}</th>
|
<th>{t('alerts.detection')}</th>
|
||||||
|
<th>Alert Details</th>
|
||||||
<th>{t('alerts.message')}</th>
|
<th>{t('alerts.message')}</th>
|
||||||
<th>{t('alerts.sentAt')}</th>
|
<th>{t('alerts.sentAt')}</th>
|
||||||
<th>Event ID</th>
|
<th>Event ID</th>
|
||||||
@@ -525,6 +594,14 @@ const Alerts = () => {
|
|||||||
<span className="text-gray-400 text-sm">{t('alerts.na')}</span>
|
<span className="text-gray-400 text-sm">{t('alerts.na')}</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
onClick={() => handleViewAlertDetails(primaryAlert)}
|
||||||
|
className="text-purple-600 hover:text-purple-800 text-sm underline"
|
||||||
|
>
|
||||||
|
Visa detaljer
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="text-sm text-gray-900 max-w-xs overflow-hidden">
|
<div className="text-sm text-gray-900 max-w-xs overflow-hidden">
|
||||||
<div className="truncate" title={primaryAlert.message}>
|
<div className="truncate" title={primaryAlert.message}>
|
||||||
@@ -614,6 +691,14 @@ const Alerts = () => {
|
|||||||
<span className="text-gray-400 text-sm">{t('alerts.na')}</span>
|
<span className="text-gray-400 text-sm">{t('alerts.na')}</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
onClick={() => handleViewAlertDetails(alert)}
|
||||||
|
className="text-purple-500 hover:text-purple-700 text-sm underline"
|
||||||
|
>
|
||||||
|
Visa detaljer
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="text-sm text-gray-700 max-w-xs overflow-hidden">
|
<div className="text-sm text-gray-700 max-w-xs overflow-hidden">
|
||||||
<div className="truncate" title={alert.message}>
|
<div className="truncate" title={alert.message}>
|
||||||
@@ -681,6 +766,17 @@ const Alerts = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{showAlertDetailsModal && selectedAlertForDetails && (
|
||||||
|
<AlertDetailsModal
|
||||||
|
alert={selectedAlertForDetails}
|
||||||
|
parseErrorMessage={parseErrorMessage}
|
||||||
|
onClose={() => {
|
||||||
|
setShowAlertDetailsModal(false);
|
||||||
|
setSelectedAlertForDetails(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1041,4 +1137,285 @@ const CreateAlertRuleModal = ({ onClose, onSave }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Alert Details Modal Component
|
||||||
|
const AlertDetailsModal = ({ alert, parseErrorMessage, onClose }) => {
|
||||||
|
if (!alert) return null;
|
||||||
|
|
||||||
|
const errorInfo = parseErrorMessage(alert.error_message);
|
||||||
|
|
||||||
|
const getStatusBadge = (status) => {
|
||||||
|
const statusClasses = {
|
||||||
|
sent: 'bg-green-100 text-green-800',
|
||||||
|
failed: 'bg-red-100 text-red-800',
|
||||||
|
pending: 'bg-yellow-100 text-yellow-800'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={`px-3 py-1 rounded-full text-sm font-medium ${statusClasses[status] || 'bg-gray-100 text-gray-800'}`}>
|
||||||
|
{status.charAt(0).toUpperCase() + status.slice(1)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSuccessDetails = (data) => (
|
||||||
|
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||||
|
<h4 className="text-lg font-medium text-green-800 mb-3">✅ Success Details</h4>
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
{data.recipient && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-green-700">Recipient:</span>
|
||||||
|
<div className="text-green-600">{data.recipient}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.messageLength && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-green-700">Message Length:</span>
|
||||||
|
<div className="text-green-600">{data.messageLength} characters</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.timestamp && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-green-700">Processed At:</span>
|
||||||
|
<div className="text-green-600">{format(new Date(data.timestamp), 'MMM dd, yyyy HH:mm:ss')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.simulatedDelivery !== undefined && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-green-700">Delivery Mode:</span>
|
||||||
|
<div className="text-green-600">{data.simulatedDelivery ? 'Simulated' : 'Real'}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.ruleId && (
|
||||||
|
<div className="col-span-2">
|
||||||
|
<span className="font-medium text-green-700">Rule ID:</span>
|
||||||
|
<div className="text-green-600 font-mono text-xs">{data.ruleId}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.detectionId && (
|
||||||
|
<div className="col-span-2">
|
||||||
|
<span className="font-medium text-green-700">Detection ID:</span>
|
||||||
|
<div className="text-green-600 font-mono text-xs">{data.detectionId}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderWebhookError = (data) => {
|
||||||
|
let webhookDetails = {};
|
||||||
|
|
||||||
|
// Parse the nested webhook error details
|
||||||
|
if (data.errorMessage && data.errorMessage.includes('Webhook HTTP')) {
|
||||||
|
try {
|
||||||
|
const match = data.errorMessage.match(/Webhook HTTP \d+: (.+)/);
|
||||||
|
if (match) {
|
||||||
|
webhookDetails = JSON.parse(match[1]);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing webhook details:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
|
<h4 className="text-lg font-medium text-red-800 mb-3">❌ Webhook Error Details</h4>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{webhookDetails.httpStatus && (
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-red-700">HTTP Status:</span>
|
||||||
|
<div className="text-red-600 font-mono">{webhookDetails.httpStatus} {webhookDetails.httpStatusText}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-red-700">Response Time:</span>
|
||||||
|
<div className="text-red-600">{webhookDetails.responseTime}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-red-700">URL:</span>
|
||||||
|
<div className="text-red-600 break-all">{webhookDetails.url}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-red-700">Payload Size:</span>
|
||||||
|
<div className="text-red-600">{webhookDetails.requestPayload}</div>
|
||||||
|
</div>
|
||||||
|
{webhookDetails.timestamp && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-red-700">Failed At:</span>
|
||||||
|
<div className="text-red-600">{format(new Date(webhookDetails.timestamp), 'MMM dd, yyyy HH:mm:ss')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{webhookDetails.responseBody && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-red-700">Response Body:</span>
|
||||||
|
<div className="mt-1 p-2 bg-red-100 rounded text-xs text-red-600 font-mono max-h-32 overflow-y-auto">
|
||||||
|
{webhookDetails.responseBody}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{webhookDetails.responseHeaders && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-red-700">Response Headers:</span>
|
||||||
|
<div className="mt-1 p-2 bg-red-100 rounded text-xs text-red-600 font-mono max-h-32 overflow-y-auto">
|
||||||
|
{JSON.stringify(webhookDetails.responseHeaders, null, 2)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderErrorDetails = (data, type) => {
|
||||||
|
if (type === 'webhook_error') {
|
||||||
|
return renderWebhookError(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic error display for SMS and email errors
|
||||||
|
const errorTypeNames = {
|
||||||
|
sms_error: '📱 SMS Error Details',
|
||||||
|
email_error: '📧 Email Error Details'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
|
<h4 className="text-lg font-medium text-red-800 mb-3">
|
||||||
|
{errorTypeNames[type] || '❌ Error Details'}
|
||||||
|
</h4>
|
||||||
|
<div className="text-sm">
|
||||||
|
<pre className="whitespace-pre-wrap text-red-600 font-mono text-xs max-h-64 overflow-y-auto">
|
||||||
|
{JSON.stringify(data, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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-4/5 max-w-6xl shadow-lg rounded-md bg-white">
|
||||||
|
<div className="flex items-center justify-between pb-3 border-b">
|
||||||
|
<h3 className="text-xl font-medium">Alert 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 mt-6">
|
||||||
|
{/* Basic Alert Information */}
|
||||||
|
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4">
|
||||||
|
<h4 className="text-lg font-medium text-gray-800 mb-3">📋 Basic Information</h4>
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-gray-700">Alert ID:</span>
|
||||||
|
<div className="text-gray-600 font-mono text-xs">{alert.id}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-gray-700">Status:</span>
|
||||||
|
<div className="mt-1">{getStatusBadge(alert.status)}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-gray-700">Type:</span>
|
||||||
|
<div className="text-gray-600 capitalize">{alert.alert_type}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-gray-700">Priority:</span>
|
||||||
|
<div className="text-gray-600 capitalize">{alert.priority}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-gray-700">Recipient:</span>
|
||||||
|
<div className="text-gray-600 break-all">{alert.recipient}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-gray-700">Retry Count:</span>
|
||||||
|
<div className="text-gray-600">{alert.retry_count}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-gray-700">Created At:</span>
|
||||||
|
<div className="text-gray-600">{format(new Date(alert.created_at), 'MMM dd, yyyy HH:mm:ss')}</div>
|
||||||
|
</div>
|
||||||
|
{alert.sent_at && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-gray-700">Sent At:</span>
|
||||||
|
<div className="text-gray-600">{format(new Date(alert.sent_at), 'MMM dd, yyyy HH:mm:ss')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{alert.alert_event_id && (
|
||||||
|
<div className="col-span-2">
|
||||||
|
<span className="font-medium text-gray-700">Event ID:</span>
|
||||||
|
<div className="text-gray-600 font-mono text-xs">{alert.alert_event_id}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Alert Message */}
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||||
|
<h4 className="text-lg font-medium text-blue-800 mb-3">💬 Alert Message</h4>
|
||||||
|
<div className="text-sm text-blue-700 whitespace-pre-wrap max-h-32 overflow-y-auto">
|
||||||
|
{alert.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error/Success Details */}
|
||||||
|
{errorInfo && (
|
||||||
|
<>
|
||||||
|
{errorInfo.type === 'success' && renderSuccessDetails(errorInfo.data)}
|
||||||
|
{(errorInfo.type === 'webhook_error' || errorInfo.type === 'sms_error' || errorInfo.type === 'email_error') &&
|
||||||
|
renderErrorDetails(errorInfo.data, errorInfo.type)}
|
||||||
|
{errorInfo.type === 'raw' && (
|
||||||
|
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4">
|
||||||
|
<h4 className="text-lg font-medium text-gray-800 mb-3">📝 Raw Error Message</h4>
|
||||||
|
<div className="text-sm text-gray-600 font-mono whitespace-pre-wrap max-h-32 overflow-y-auto">
|
||||||
|
{errorInfo.data.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Rule and Detection Info */}
|
||||||
|
{(alert.rule || alert.detection) && (
|
||||||
|
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4">
|
||||||
|
<h4 className="text-lg font-medium text-purple-800 mb-3">🔗 Related Information</h4>
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
{alert.rule && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-purple-700">Alert Rule:</span>
|
||||||
|
<div className="text-purple-600">{alert.rule.name}</div>
|
||||||
|
<div className="text-purple-600 font-mono text-xs">ID: {alert.rule.id}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{alert.detection && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-purple-700">Detection:</span>
|
||||||
|
<div className="text-purple-600">Drone ID: {alert.detection.drone_id}</div>
|
||||||
|
<div className="text-purple-600">Type: {alert.detection.drone_type}</div>
|
||||||
|
<div className="text-purple-600">RSSI: {alert.detection.rssi} dBm</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-6 pt-4 border-t">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 transition-colors"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Alerts;
|
export default Alerts;
|
||||||
|
|||||||
Reference in New Issue
Block a user