Fix jwt-token
This commit is contained in:
@@ -2,10 +2,12 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import api from '../services/api';
|
import api from '../services/api';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { formatFrequency } from '../utils/formatFrequency';
|
import { formatFrequency } from '../utils/formatFrequency';
|
||||||
|
import { useTranslation } from '../utils/tempTranslations';
|
||||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
// Edit Alert Rule Modal
|
// Edit Alert Rule Modal
|
||||||
export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
|
export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
@@ -104,7 +106,7 @@ export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
|
|||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating alert rule:', error);
|
console.error('Error updating alert rule:', error);
|
||||||
alert('Failed to update alert rule');
|
alert(t('alerts.form.updateFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
@@ -114,7 +116,7 @@ export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
|
|||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<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="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">
|
<div className="flex items-center justify-between pb-3">
|
||||||
<h3 className="text-lg font-medium">Edit Alert Rule</h3>
|
<h3 className="text-lg font-medium">{t('alerts.form.editAlertRule')}</h3>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="text-gray-400 hover:text-gray-600"
|
className="text-gray-400 hover:text-gray-600"
|
||||||
@@ -126,7 +128,7 @@ export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
|
|||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Name *
|
{t('alerts.name')} *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -140,7 +142,7 @@ export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Description
|
{t('alerts.description')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="description"
|
name="description"
|
||||||
@@ -154,7 +156,7 @@ export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
|
|||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Priority
|
{t('alerts.priority')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="priority"
|
name="priority"
|
||||||
@@ -162,16 +164,16 @@ export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
|
|||||||
value={formData.priority}
|
value={formData.priority}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<option value="low">Low</option>
|
<option value="low">{t('alerts.priorities.low')}</option>
|
||||||
<option value="medium">Medium</option>
|
<option value="medium">{t('alerts.priorities.medium')}</option>
|
||||||
<option value="high">High</option>
|
<option value="high">{t('alerts.priorities.high')}</option>
|
||||||
<option value="critical">Critical</option>
|
<option value="critical">{t('alerts.priorities.critical')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Min Threat Level
|
{t('alerts.minThreatLevel')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="min_threat_level"
|
name="min_threat_level"
|
||||||
@@ -179,23 +181,23 @@ export const EditAlertModal = ({ rule, onClose, onSuccess }) => {
|
|||||||
value={formData.min_threat_level}
|
value={formData.min_threat_level}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<option value="">Any Level</option>
|
<option value="">{t('alerts.form.anyLevel')}</option>
|
||||||
<option value="monitoring">Monitoring</option>
|
<option value="monitoring">{t('alerts.form.monitoring')}</option>
|
||||||
<option value="low">Low</option>
|
<option value="low">{t('alerts.threatLevels.low')}</option>
|
||||||
<option value="medium">Medium</option>
|
<option value="medium">{t('alerts.threatLevels.medium')}</option>
|
||||||
<option value="high">High</option>
|
<option value="high">{t('alerts.threatLevels.high')}</option>
|
||||||
<option value="critical">Critical</option>
|
<option value="critical">{t('alerts.threatLevels.critical')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Drone Types Filter
|
{t('alerts.form.droneTypesFilter')}
|
||||||
</label>
|
</label>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-xs text-gray-500 mb-2">
|
<div className="text-xs text-gray-500 mb-2">
|
||||||
Leave empty to monitor all drone types
|
{t('alerts.form.leaveEmptyAllTypes')}
|
||||||
</div>
|
</div>
|
||||||
{droneTypes.map(droneType => (
|
{droneTypes.map(droneType => (
|
||||||
<label key={droneType.id} className="flex items-center">
|
<label key={droneType.id} className="flex items-center">
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ const MovementAlertsPanel = () => {
|
|||||||
{filteredAlerts.length === 0 ? (
|
{filteredAlerts.length === 0 ? (
|
||||||
<div className="px-6 py-8 text-center text-gray-500">
|
<div className="px-6 py-8 text-center text-gray-500">
|
||||||
<EyeIcon className="h-12 w-12 mx-auto mb-4 text-gray-400" />
|
<EyeIcon className="h-12 w-12 mx-auto mb-4 text-gray-400" />
|
||||||
<p>No movement alerts</p>
|
<p>{t('movementAlerts.noAlerts')}</p>
|
||||||
<p className="text-sm">Drone movement patterns will appear here</p>
|
<p className="text-sm">{t('movementAlerts.noAlertsDescription')}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
filteredAlerts.map((alert, index) => (
|
filteredAlerts.map((alert, index) => (
|
||||||
@@ -124,7 +124,7 @@ const MovementAlertsPanel = () => {
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<h4 className="text-sm font-medium text-gray-900">
|
<h4 className="text-sm font-medium text-gray-900">
|
||||||
Drone {alert.droneId} • Device {alert.deviceId}
|
{t('movementAlerts.droneDevice', { droneId: alert.droneId, deviceId: alert.deviceId })}
|
||||||
</h4>
|
</h4>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
{format(new Date(alert.timestamp), 'HH:mm:ss')}
|
{format(new Date(alert.timestamp), 'HH:mm:ss')}
|
||||||
@@ -140,7 +140,7 @@ const MovementAlertsPanel = () => {
|
|||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
{getMovementIcon(alert.analysis.rssiTrend.trend)}
|
{getMovementIcon(alert.analysis.rssiTrend.trend)}
|
||||||
<span className="text-gray-600">
|
<span className="text-gray-600">
|
||||||
{alert.analysis.rssiTrend.trend.toLowerCase()}
|
{t(`movementAlerts.${alert.analysis.rssiTrend.trend.toLowerCase()}`)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -177,26 +177,26 @@ const MovementAlertsPanel = () => {
|
|||||||
<div className="mt-4 pl-8 space-y-3">
|
<div className="mt-4 pl-8 space-y-3">
|
||||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-gray-700">Drone Type:</span>
|
<span className="font-medium text-gray-700">{t('movementAlerts.droneType')}</span>
|
||||||
<div className="text-gray-900">
|
<div className="text-gray-900">
|
||||||
{droneTypes[alert.detection.drone_type] || 'Unknown'}
|
{droneTypes[alert.detection.drone_type] || t('movementAlerts.droneTypes.unknown')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-gray-700">Frequency:</span>
|
<span className="font-medium text-gray-700">{t('movementAlerts.frequency')}</span>
|
||||||
<div className="text-gray-900">{formatFrequency(alert.detection.freq)}</div>
|
<div className="text-gray-900">{formatFrequency(alert.detection.freq)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-gray-700">Confidence:</span>
|
<span className="font-medium text-gray-700">{t('movementAlerts.confidence')}</span>
|
||||||
<div className="text-gray-900">
|
<div className="text-gray-900">
|
||||||
{(alert.detection.confidence_level * 100).toFixed(0)}%
|
{(alert.detection.confidence_level * 100).toFixed(0)}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-gray-700">Signal Duration:</span>
|
<span className="font-medium text-gray-700">{t('movementAlerts.signalDuration')}</span>
|
||||||
<div className="text-gray-900">
|
<div className="text-gray-900">
|
||||||
{(alert.detection.signal_duration / 1000).toFixed(1)}s
|
{(alert.detection.signal_duration / 1000).toFixed(1)}s
|
||||||
</div>
|
</div>
|
||||||
@@ -205,14 +205,14 @@ const MovementAlertsPanel = () => {
|
|||||||
|
|
||||||
{alert.analysis.movement && (
|
{alert.analysis.movement && (
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-gray-700 block mb-1">Movement Pattern:</span>
|
<span className="font-medium text-gray-700 block mb-1">{t('movementAlerts.movementPattern')}</span>
|
||||||
<div className="text-sm space-y-1">
|
<div className="text-sm space-y-1">
|
||||||
<div>Pattern: <span className="font-mono">{alert.analysis.movement.pattern}</span></div>
|
<div>{t('movementAlerts.pattern')} <span className="font-mono">{alert.analysis.movement.pattern}</span></div>
|
||||||
{alert.analysis.movement.speed > 0 && (
|
{alert.analysis.movement.speed > 0 && (
|
||||||
<div>Speed: <span className="font-mono">{alert.analysis.movement.speed.toFixed(1)} m/s</span></div>
|
<div>{t('movementAlerts.speed')} <span className="font-mono">{alert.analysis.movement.speed.toFixed(1)} m/s</span></div>
|
||||||
)}
|
)}
|
||||||
{alert.analysis.movement.totalDistance > 0 && (
|
{alert.analysis.movement.totalDistance > 0 && (
|
||||||
<div>Distance: <span className="font-mono">{(alert.analysis.movement.totalDistance * 1000).toFixed(0)}m</span></div>
|
<div>{t('movementAlerts.distance')} <span className="font-mono">{(alert.analysis.movement.totalDistance * 1000).toFixed(0)}m</span></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -220,16 +220,19 @@ const MovementAlertsPanel = () => {
|
|||||||
|
|
||||||
{alert.analysis.detectionCount && (
|
{alert.analysis.detectionCount && (
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-gray-700">Tracking Stats:</span>
|
<span className="font-medium text-gray-700">{t('movementAlerts.trackingStats')}</span>
|
||||||
<div className="text-sm mt-1">
|
<div className="text-sm mt-1">
|
||||||
<div>{alert.analysis.detectionCount} detections over {(alert.analysis.timeTracked / 60).toFixed(1)} minutes</div>
|
<div>{t('movementAlerts.detectionsOverTime', {
|
||||||
|
count: alert.analysis.detectionCount,
|
||||||
|
time: (alert.analysis.timeTracked / 60).toFixed(1)
|
||||||
|
})}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{alert.history && alert.history.length > 0 && (
|
{alert.history && alert.history.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-gray-700 block mb-2">Recent RSSI History:</span>
|
<span className="font-medium text-gray-700 block mb-2">{t('movementAlerts.recentRssiHistory')}</span>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
{alert.history.slice(-5).map((point, i) => (
|
{alert.history.slice(-5).map((point, i) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import api from '../services/api';
|
import api from '../services/api';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { t } from '../utils/tempTranslations';
|
import { useTranslation } from '../utils/tempTranslations';
|
||||||
import {
|
import {
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
BellIcon,
|
BellIcon,
|
||||||
@@ -16,6 +16,7 @@ import { EditAlertModal, DetectionDetailsModal } from '../components/AlertModals
|
|||||||
import { useDroneTypes } from '../hooks/useDroneTypes';
|
import { useDroneTypes } from '../hooks/useDroneTypes';
|
||||||
|
|
||||||
const Alerts = () => {
|
const Alerts = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
// Drone types hook for dynamic drone type data
|
// Drone types hook for dynamic drone type data
|
||||||
const { getDroneTypeInfo: getDroneTypeInfoFromAPI, loading: droneTypesLoading } = useDroneTypes();
|
const { getDroneTypeInfo: getDroneTypeInfoFromAPI, loading: droneTypesLoading } = useDroneTypes();
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ const Alerts = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteRule = async (ruleId) => {
|
const handleDeleteRule = async (ruleId) => {
|
||||||
if (window.confirm('Are you sure you want to delete this alert rule?')) {
|
if (window.confirm(t('alerts.deleteRule') + '?')) {
|
||||||
try {
|
try {
|
||||||
await api.delete(`/alerts/rules/${ruleId}`);
|
await api.delete(`/alerts/rules/${ruleId}`);
|
||||||
fetchAlertData();
|
fetchAlertData();
|
||||||
@@ -233,7 +234,7 @@ const Alerts = () => {
|
|||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary-600"></div>
|
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary-600"></div>
|
||||||
<span className="ml-4 text-gray-600">
|
<span className="ml-4 text-gray-600">
|
||||||
{droneTypesLoading ? 'Loading drone types...' : t('alerts.loading')}
|
{droneTypesLoading ? t('common.loading') : t('alerts.loading')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import api from '../services/api';
|
import api from '../services/api';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { formatFrequency } from '../utils/formatFrequency';
|
import { formatFrequency } from '../utils/formatFrequency';
|
||||||
|
import { useTranslation } from '../utils/tempTranslations';
|
||||||
import {
|
import {
|
||||||
BugAntIcon,
|
BugAntIcon,
|
||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
const Debug = () => {
|
const Debug = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [debugData, setDebugData] = useState([]);
|
const [debugData, setDebugData] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
@@ -48,11 +50,11 @@ const Debug = () => {
|
|||||||
setPagination(response.data.pagination);
|
setPagination(response.data.pagination);
|
||||||
setDebugInfo(response.data.debug_info);
|
setDebugInfo(response.data.debug_info);
|
||||||
} else {
|
} else {
|
||||||
setError(response.data.message || 'Failed to fetch debug data');
|
setError(response.data.message || t('debug.noDetectionsFound'));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching debug data:', err);
|
console.error('Error fetching debug data:', err);
|
||||||
setError(err.response?.data?.message || 'Failed to fetch debug data');
|
setError(err.response?.data?.message || t('debug.noDetectionsFound'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -97,11 +99,11 @@ const Debug = () => {
|
|||||||
setShowPayloadModal(true);
|
setShowPayloadModal(true);
|
||||||
} else {
|
} else {
|
||||||
console.error('No payload data found for detection:', detectionId);
|
console.error('No payload data found for detection:', detectionId);
|
||||||
alert('No raw payload data found for this detection');
|
alert(t('debug.payloadViewer.noPayloadData'));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching payload:', err);
|
console.error('Error fetching payload:', err);
|
||||||
alert('Failed to fetch payload data');
|
alert(t('debug.payloadViewer.failedToFetch'));
|
||||||
} finally {
|
} finally {
|
||||||
setPayloadLoading(false);
|
setPayloadLoading(false);
|
||||||
}
|
}
|
||||||
@@ -146,7 +148,7 @@ const Debug = () => {
|
|||||||
<div className="flex">
|
<div className="flex">
|
||||||
<ExclamationTriangleIcon className="h-5 w-5 text-red-400" />
|
<ExclamationTriangleIcon className="h-5 w-5 text-red-400" />
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<h3 className="text-sm font-medium text-red-800">Error</h3>
|
<h3 className="text-sm font-medium text-red-800">{t('common.error')}</h3>
|
||||||
<div className="mt-2 text-sm text-red-700">{error}</div>
|
<div className="mt-2 text-sm text-red-700">{error}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,9 +163,9 @@ const Debug = () => {
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<BugAntIcon className="h-8 w-8 text-orange-500 mr-3" />
|
<BugAntIcon className="h-8 w-8 text-orange-500 mr-3" />
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">Debug Console</h1>
|
<h1 className="text-2xl font-bold text-gray-900">{t('debug.title')}</h1>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Admin-only access to all detection data including drone type 0 (None)
|
{t('debug.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -175,10 +177,10 @@ const Debug = () => {
|
|||||||
<div className="flex">
|
<div className="flex">
|
||||||
<InformationCircleIcon className="h-5 w-5 text-yellow-400" />
|
<InformationCircleIcon className="h-5 w-5 text-yellow-400" />
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<h3 className="text-sm font-medium text-yellow-800">Debug Information</h3>
|
<h3 className="text-sm font-medium text-yellow-800">{t('debug.debugInformation')}</h3>
|
||||||
<div className="mt-2 text-sm text-yellow-700">
|
<div className="mt-2 text-sm text-yellow-700">
|
||||||
<p>{debugInfo.message}</p>
|
<p>{debugInfo.message}</p>
|
||||||
<p className="mt-1">Total None detections: <strong>{debugInfo.total_none_detections}</strong></p>
|
<p className="mt-1">{t('debug.totalNoneDetections', { count: debugInfo.total_none_detections })}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -187,56 +189,56 @@ const Debug = () => {
|
|||||||
|
|
||||||
{/* Filters */}
|
{/* Filters */}
|
||||||
<div className="bg-white shadow rounded-lg p-6">
|
<div className="bg-white shadow rounded-lg p-6">
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-4">Filters</h3>
|
<h3 className="text-lg font-medium text-gray-900 mb-4">{t('debug.filters')}</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Drone Type
|
{t('debug.droneType')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={filters.drone_type}
|
value={filters.drone_type}
|
||||||
onChange={(e) => handleFilterChange('drone_type', e.target.value)}
|
onChange={(e) => handleFilterChange('drone_type', e.target.value)}
|
||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
|
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
|
||||||
>
|
>
|
||||||
<option value="">All Types</option>
|
<option value="">{t('debug.allTypes')}</option>
|
||||||
<option value="0">0 - None (Debug)</option>
|
<option value="0">{t('debug.droneTypeOptions.none')}</option>
|
||||||
<option value="1">1 - Unknown</option>
|
<option value="1">{t('debug.droneTypeOptions.unknown')}</option>
|
||||||
<option value="2">2 - Orlan</option>
|
<option value="2">{t('debug.droneTypeOptions.orlan')}</option>
|
||||||
<option value="3">3 - Zala</option>
|
<option value="3">{t('debug.droneTypeOptions.zala')}</option>
|
||||||
<option value="4">4 - Eleron</option>
|
<option value="4">{t('debug.droneTypeOptions.eleron')}</option>
|
||||||
<option value="5">5 - Zala Lancet</option>
|
<option value="5">{t('debug.droneTypeOptions.zalaLancet')}</option>
|
||||||
<option value="6">6 - Lancet</option>
|
<option value="6">{t('debug.droneTypeOptions.lancet')}</option>
|
||||||
<option value="7">7 - FPV CrossFire</option>
|
<option value="7">{t('debug.droneTypeOptions.fpvCrossFire')}</option>
|
||||||
<option value="8">8 - FPV ELRS</option>
|
<option value="8">{t('debug.droneTypeOptions.fpvElrs')}</option>
|
||||||
<option value="9">9 - Maybe Orlan</option>
|
<option value="9">{t('debug.droneTypeOptions.maybeOrlan')}</option>
|
||||||
<option value="10">10 - Maybe Zala</option>
|
<option value="10">{t('debug.droneTypeOptions.maybeZala')}</option>
|
||||||
<option value="11">11 - Maybe Lancet</option>
|
<option value="11">{t('debug.droneTypeOptions.maybeLancet')}</option>
|
||||||
<option value="12">12 - Maybe Eleron</option>
|
<option value="12">{t('debug.droneTypeOptions.maybeEleron')}</option>
|
||||||
<option value="13">13 - DJI</option>
|
<option value="13">{t('debug.droneTypeOptions.dji')}</option>
|
||||||
<option value="14">14 - Supercam</option>
|
<option value="14">{t('debug.droneTypeOptions.supercam')}</option>
|
||||||
<option value="15">15 - Maybe Supercam</option>
|
<option value="15">{t('debug.droneTypeOptions.maybeSupercam')}</option>
|
||||||
<option value="16">16 - REB</option>
|
<option value="16">{t('debug.droneTypeOptions.reb')}</option>
|
||||||
<option value="17">17 - Crypto Orlan</option>
|
<option value="17">{t('debug.droneTypeOptions.cryptoOrlan')}</option>
|
||||||
<option value="18">18 - DJI Enterprise</option>
|
<option value="18">{t('debug.droneTypeOptions.djiEnterprise')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Device ID
|
{t('debug.deviceId')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={filters.device_id}
|
value={filters.device_id}
|
||||||
onChange={(e) => handleFilterChange('device_id', e.target.value)}
|
onChange={(e) => handleFilterChange('device_id', e.target.value)}
|
||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
|
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-primary-500 focus:border-primary-500"
|
||||||
placeholder="Filter by device ID"
|
placeholder={t('debug.filterByDeviceId')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Results per page
|
{t('debug.resultsPerPage')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={filters.limit}
|
value={filters.limit}
|
||||||
@@ -255,16 +257,16 @@ const Debug = () => {
|
|||||||
<div className="bg-white shadow rounded-lg overflow-hidden">
|
<div className="bg-white shadow rounded-lg overflow-hidden">
|
||||||
<div className="px-6 py-4 border-b border-gray-200">
|
<div className="px-6 py-4 border-b border-gray-200">
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
<h3 className="text-lg font-medium text-gray-900">
|
||||||
Debug Detections ({pagination.total || 0})
|
{t('debug.debugDetections', { count: pagination.total || 0 })}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{debugData.length === 0 ? (
|
{debugData.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<BugAntIcon className="mx-auto h-12 w-12 text-gray-400" />
|
<BugAntIcon className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">No debug data</h3>
|
<h3 className="mt-2 text-sm font-medium text-gray-900">{t('debug.noDebugData')}</h3>
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
No detections found matching the current filters.
|
{t('debug.noDetectionsFound')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -274,25 +276,25 @@ const Debug = () => {
|
|||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
ID / Time
|
{t('debug.idTime')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Device
|
{t('debug.device')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Drone Type
|
{t('debug.droneType')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
RSSI / Freq
|
{t('debug.rssiFreq')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Threat Level
|
{t('debug.threatLevel')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Debug
|
{t('debug.debug')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Actions
|
{t('debug.actions')}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -346,7 +348,7 @@ const Debug = () => {
|
|||||||
className="inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-xs font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50"
|
className="inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-xs font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<DocumentTextIcon className="h-4 w-4 mr-1" />
|
<DocumentTextIcon className="h-4 w-4 mr-1" />
|
||||||
{payloadLoading ? 'Loading...' : 'View Payload'}
|
{payloadLoading ? t('common.loading') : t('debug.viewPayload')}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -419,7 +421,7 @@ const Debug = () => {
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<DocumentTextIcon className="h-6 w-6 text-blue-500 mr-2" />
|
<DocumentTextIcon className="h-6 w-6 text-blue-500 mr-2" />
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
<h3 className="text-lg font-medium text-gray-900">
|
||||||
Raw Payload Data
|
{t('debug.payloadViewer.title')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -432,24 +434,24 @@ const Debug = () => {
|
|||||||
|
|
||||||
{/* Detection Info */}
|
{/* Detection Info */}
|
||||||
<div className="mt-4 bg-gray-50 rounded-lg p-4">
|
<div className="mt-4 bg-gray-50 rounded-lg p-4">
|
||||||
<h4 className="font-medium text-gray-900 mb-2">Detection Information</h4>
|
<h4 className="font-medium text-gray-900 mb-2">{t('alerts.detectionDetails')}</h4>
|
||||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-600">Detection ID:</span>
|
<span className="text-gray-600">{t('debug.payloadViewer.detectionId')}</span>
|
||||||
<span className="ml-2 font-mono">{selectedPayload.id}</span>
|
<span className="ml-2 font-mono">{selectedPayload.id}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-600">Device ID:</span>
|
<span className="text-gray-600">{t('debug.payloadViewer.deviceId')}</span>
|
||||||
<span className="ml-2 font-mono">{selectedPayload.deviceId}</span>
|
<span className="ml-2 font-mono">{selectedPayload.deviceId}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-600">Server Timestamp:</span>
|
<span className="text-gray-600">{t('debug.payloadViewer.timestamp')}</span>
|
||||||
<span className="ml-2 font-mono">
|
<span className="ml-2 font-mono">
|
||||||
{format(new Date(selectedPayload.timestamp), 'yyyy-MM-dd HH:mm:ss')}
|
{format(new Date(selectedPayload.timestamp), 'yyyy-MM-dd HH:mm:ss')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-600">Drone Type:</span>
|
<span className="text-gray-600">{t('debug.droneType')}</span>
|
||||||
<span className="ml-2 font-mono">{selectedPayload.processedData.drone_type}</span>
|
<span className="ml-2 font-mono">{selectedPayload.processedData.drone_type}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -457,7 +459,7 @@ const Debug = () => {
|
|||||||
|
|
||||||
{/* Processed Data */}
|
{/* Processed Data */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h4 className="font-medium text-gray-900 mb-2">Processed Data</h4>
|
<h4 className="font-medium text-gray-900 mb-2">{t('debug.payloadViewer.processedData')}</h4>
|
||||||
<div className="bg-gray-100 rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
<div className="bg-gray-100 rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
||||||
<pre className="whitespace-pre-wrap">
|
<pre className="whitespace-pre-wrap">
|
||||||
{JSON.stringify(selectedPayload.processedData, null, 2)}
|
{JSON.stringify(selectedPayload.processedData, null, 2)}
|
||||||
@@ -467,7 +469,7 @@ const Debug = () => {
|
|||||||
|
|
||||||
{/* Raw Payload */}
|
{/* Raw Payload */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h4 className="font-medium text-gray-900 mb-2">Raw Payload from Detector</h4>
|
<h4 className="font-medium text-gray-900 mb-2">{t('debug.payloadViewer.rawPayload')}</h4>
|
||||||
{selectedPayload.rawPayload ? (
|
{selectedPayload.rawPayload ? (
|
||||||
<div className="bg-black text-green-400 rounded-lg p-4 font-mono text-sm overflow-x-auto max-h-96 overflow-y-auto">
|
<div className="bg-black text-green-400 rounded-lg p-4 font-mono text-sm overflow-x-auto max-h-96 overflow-y-auto">
|
||||||
<pre className="whitespace-pre-wrap">
|
<pre className="whitespace-pre-wrap">
|
||||||
@@ -494,7 +496,7 @@ const Debug = () => {
|
|||||||
onClick={closePayloadModal}
|
onClick={closePayloadModal}
|
||||||
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500"
|
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500"
|
||||||
>
|
>
|
||||||
Close
|
{t('common.close')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -503,7 +505,7 @@ const Debug = () => {
|
|||||||
}}
|
}}
|
||||||
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
>
|
>
|
||||||
Copy to Clipboard
|
{t('common.copy')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ const Settings = () => {
|
|||||||
setTenantConfig(response.data.data);
|
setTenantConfig(response.data.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch tenant config:', error);
|
console.error('Failed to fetch tenant config:', error);
|
||||||
toast.error('Failed to load tenant settings');
|
toast.error(t('settings.failedToLoad'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ const Settings = () => {
|
|||||||
<ShieldCheckIcon className="mx-auto h-12 w-12 text-gray-400" />
|
<ShieldCheckIcon className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">{t('settings.accessDenied')}</h3>
|
<h3 className="mt-2 text-sm font-medium text-gray-900">{t('settings.accessDenied')}</h3>
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
{t('settings.accessDeniedMessage')}
|
{t('settings.noPermission')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -376,6 +376,23 @@ const translations = {
|
|||||||
highPriority: 'High Priority',
|
highPriority: 'High Priority',
|
||||||
mediumPriority: 'Medium Priority',
|
mediumPriority: 'Medium Priority',
|
||||||
clearAll: 'Clear All',
|
clearAll: 'Clear All',
|
||||||
|
noAlerts: 'No movement alerts',
|
||||||
|
noAlertsDescription: 'Drone movement patterns will appear here',
|
||||||
|
droneDevice: 'Drone {droneId} • Device {deviceId}',
|
||||||
|
droneType: 'Drone Type:',
|
||||||
|
frequency: 'Frequency:',
|
||||||
|
confidence: 'Confidence:',
|
||||||
|
signalDuration: 'Signal Duration:',
|
||||||
|
movementPattern: 'Movement Pattern:',
|
||||||
|
pattern: 'Pattern:',
|
||||||
|
speed: 'Speed:',
|
||||||
|
distance: 'Distance:',
|
||||||
|
trackingStats: 'Tracking Stats:',
|
||||||
|
detectionsOverTime: '{count} detections over {time} minutes',
|
||||||
|
recentRssiHistory: 'Recent RSSI History:',
|
||||||
|
strengthening: 'strengthening',
|
||||||
|
weakening: 'weakening',
|
||||||
|
stable: 'stable',
|
||||||
droneTypes: {
|
droneTypes: {
|
||||||
djiMavic: 'DJI Mavic',
|
djiMavic: 'DJI Mavic',
|
||||||
racingDrone: 'Racing Drone',
|
racingDrone: 'Racing Drone',
|
||||||
@@ -385,6 +402,184 @@ const translations = {
|
|||||||
unknown: 'Unknown'
|
unknown: 'Unknown'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
debug: {
|
||||||
|
title: 'Debug Console',
|
||||||
|
subtitle: 'Admin-only access to all detection data including drone type 0 (None)',
|
||||||
|
debugInformation: 'Debug Information',
|
||||||
|
totalNoneDetections: 'Total None detections: {count}',
|
||||||
|
filters: 'Filters',
|
||||||
|
droneType: 'Drone Type',
|
||||||
|
deviceId: 'Device ID',
|
||||||
|
resultsPerPage: 'Results per page',
|
||||||
|
filterByDeviceId: 'Filter by device ID',
|
||||||
|
allTypes: 'All Types',
|
||||||
|
debugDetections: 'Debug Detections ({count})',
|
||||||
|
noDebugData: 'No debug data',
|
||||||
|
noDetectionsFound: 'No detections found matching the current filters.',
|
||||||
|
idTime: 'ID / Time',
|
||||||
|
device: 'Device',
|
||||||
|
rssiFreq: 'RSSI / Freq',
|
||||||
|
threatLevel: 'Threat Level',
|
||||||
|
debug: 'Debug',
|
||||||
|
actions: 'Actions',
|
||||||
|
viewPayload: 'View Payload',
|
||||||
|
deleteDetection: 'Delete Detection',
|
||||||
|
droneTypeOptions: {
|
||||||
|
none: '0 - None (Debug)',
|
||||||
|
unknown: '1 - Unknown',
|
||||||
|
orlan: '2 - Orlan',
|
||||||
|
zala: '3 - Zala',
|
||||||
|
eleron: '4 - Eleron',
|
||||||
|
zalaLancet: '5 - Zala Lancet',
|
||||||
|
lancet: '6 - Lancet',
|
||||||
|
fpvCrossFire: '7 - FPV CrossFire',
|
||||||
|
fpvElrs: '8 - FPV ELRS',
|
||||||
|
maybeOrlan: '9 - Maybe Orlan',
|
||||||
|
maybeZala: '10 - Maybe Zala',
|
||||||
|
maybeLancet: '11 - Maybe Lancet',
|
||||||
|
maybeEleron: '12 - Maybe Eleron',
|
||||||
|
dji: '13 - DJI',
|
||||||
|
supercam: '14 - Supercam',
|
||||||
|
maybeSupercam: '15 - Maybe Supercam',
|
||||||
|
reb: '16 - REB',
|
||||||
|
cryptoOrlan: '17 - Crypto Orlan',
|
||||||
|
djiEnterprise: '18 - DJI Enterprise'
|
||||||
|
},
|
||||||
|
payloadViewer: {
|
||||||
|
title: 'Debug Payload Viewer',
|
||||||
|
detectionId: 'Detection ID:',
|
||||||
|
timestamp: 'Timestamp:',
|
||||||
|
deviceId: 'Device ID:',
|
||||||
|
rawPayload: 'Raw Payload:',
|
||||||
|
processedData: 'Processed Data:',
|
||||||
|
noPayloadData: 'No raw payload data found for this detection',
|
||||||
|
failedToFetch: 'Failed to fetch payload data'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
title: 'Settings',
|
||||||
|
loading: 'Loading settings...',
|
||||||
|
accessDenied: 'Access Denied',
|
||||||
|
noPermission: 'You do not have permission to access settings.',
|
||||||
|
general: 'General',
|
||||||
|
branding: 'Branding',
|
||||||
|
security: 'Security',
|
||||||
|
authentication: 'Authentication',
|
||||||
|
users: 'Users',
|
||||||
|
saveChanges: 'Save Changes',
|
||||||
|
saving: 'Saving...',
|
||||||
|
saved: 'Settings saved successfully',
|
||||||
|
failedToSave: 'Failed to save settings',
|
||||||
|
failedToLoad: 'Failed to load tenant settings'
|
||||||
|
},
|
||||||
|
alerts: {
|
||||||
|
title: 'Alert Management',
|
||||||
|
description: 'Manage alert rules and view alert activity',
|
||||||
|
loading: 'Loading alerts...',
|
||||||
|
rules: 'Rules',
|
||||||
|
logs: 'Logs',
|
||||||
|
stats: 'Stats',
|
||||||
|
createRule: 'Create Rule',
|
||||||
|
createAlert: 'Create Alert',
|
||||||
|
createAlertRule: 'Create Alert Rule',
|
||||||
|
editRule: 'Edit Rule',
|
||||||
|
deleteRule: 'Delete Rule',
|
||||||
|
ruleDetails: 'Rule Details',
|
||||||
|
noRules: 'No alert rules configured',
|
||||||
|
noAlertRules: 'No alert rules',
|
||||||
|
noRulesDescription: 'Create your first alert rule to start monitoring drone activity.',
|
||||||
|
noAlertRulesDescription: 'Create your first alert rule to start monitoring drone activity.',
|
||||||
|
noLogs: 'No alert logs',
|
||||||
|
noLogsDescription: 'Alert activity will appear here once rules are triggered.',
|
||||||
|
name: 'Name',
|
||||||
|
description: 'Description',
|
||||||
|
priority: 'Priority',
|
||||||
|
minDetections: 'Minimum Detections',
|
||||||
|
timeWindow: 'Time Window (seconds)',
|
||||||
|
cooldownPeriod: 'Cooldown Period (seconds)',
|
||||||
|
alertChannels: 'Alert Channels',
|
||||||
|
minThreatLevel: 'Minimum Threat Level',
|
||||||
|
droneTypes: 'Drone Types',
|
||||||
|
deviceIds: 'Device IDs',
|
||||||
|
smsPhoneNumber: 'SMS Phone Number',
|
||||||
|
webhookUrl: 'Webhook URL',
|
||||||
|
enabled: 'Enabled',
|
||||||
|
disabled: 'Disabled',
|
||||||
|
active: 'Active',
|
||||||
|
inactive: 'Inactive',
|
||||||
|
triggered: 'Triggered',
|
||||||
|
sentAt: 'Sent At',
|
||||||
|
channel: 'Channel',
|
||||||
|
channels: 'Channels',
|
||||||
|
status: 'Status',
|
||||||
|
message: 'Message',
|
||||||
|
detectionDetails: 'Detection Details',
|
||||||
|
viewDetails: 'View Details',
|
||||||
|
alertEvent: 'Alert Event',
|
||||||
|
relatedAlerts: 'Related Alerts',
|
||||||
|
showMore: 'Show {count} more alerts',
|
||||||
|
showLess: 'Show less',
|
||||||
|
alertRules: 'Alert Rules',
|
||||||
|
alertLogs: 'Alert Logs',
|
||||||
|
conditions: 'Conditions',
|
||||||
|
actions: 'Actions',
|
||||||
|
created: 'Created',
|
||||||
|
cooldown: 'Cooldown',
|
||||||
|
minThreat: 'Min Threat',
|
||||||
|
totalAlerts24h: 'Total Alerts (24h)',
|
||||||
|
sentSuccessfully: 'Sent Successfully',
|
||||||
|
failed: 'Failed',
|
||||||
|
pending: 'Pending',
|
||||||
|
low: 'Low',
|
||||||
|
medium: 'Medium',
|
||||||
|
high: 'High',
|
||||||
|
critical: 'Critical',
|
||||||
|
priorities: {
|
||||||
|
low: 'Low',
|
||||||
|
medium: 'Medium',
|
||||||
|
high: 'High',
|
||||||
|
critical: 'Critical'
|
||||||
|
},
|
||||||
|
channels: {
|
||||||
|
sms: 'SMS',
|
||||||
|
webhook: 'Webhook',
|
||||||
|
email: 'Email'
|
||||||
|
},
|
||||||
|
threatLevels: {
|
||||||
|
low: 'Low',
|
||||||
|
medium: 'Medium',
|
||||||
|
high: 'High',
|
||||||
|
critical: 'Critical'
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
nameRequired: 'Rule name is required',
|
||||||
|
nameHelp: 'A descriptive name for this alert rule',
|
||||||
|
descriptionHelp: 'Optional description of what this rule monitors',
|
||||||
|
priorityHelp: 'Priority level for alerts generated by this rule',
|
||||||
|
minDetectionsHelp: 'Minimum number of detections required to trigger alert',
|
||||||
|
timeWindowHelp: 'Time window in seconds to count detections',
|
||||||
|
cooldownHelp: 'Minimum time between alerts for the same rule',
|
||||||
|
channelsRequired: 'At least one alert channel is required',
|
||||||
|
channelsHelp: 'How alerts from this rule will be delivered',
|
||||||
|
threatLevelHelp: 'Only trigger for detections with this threat level or higher',
|
||||||
|
droneTypesHelp: 'Leave empty to monitor all drone types',
|
||||||
|
deviceIdsHelp: 'Leave empty to monitor all devices',
|
||||||
|
smsHelp: 'Phone number for SMS alerts (required if SMS channel selected)',
|
||||||
|
webhookHelp: 'URL for webhook alerts (required if webhook channel selected)',
|
||||||
|
updateFailed: 'Failed to update alert rule',
|
||||||
|
editAlertRule: 'Edit Alert Rule',
|
||||||
|
anyLevel: 'Any Level',
|
||||||
|
monitoring: 'Monitoring',
|
||||||
|
droneTypesFilter: 'Drone Types Filter',
|
||||||
|
leaveEmptyAllTypes: 'Leave empty to monitor all drone types',
|
||||||
|
alertChannelsLabel: 'Alert Channels',
|
||||||
|
selectChannels: 'Select how alerts will be sent',
|
||||||
|
smsPhoneLabel: 'SMS Phone Number',
|
||||||
|
smsRequired: 'Required when SMS is selected',
|
||||||
|
webhookUrlLabel: 'Webhook URL',
|
||||||
|
webhookRequired: 'Required when webhook is selected'
|
||||||
|
}
|
||||||
|
},
|
||||||
register: {
|
register: {
|
||||||
registrationDisabled: 'Registration is not enabled for this tenant',
|
registrationDisabled: 'Registration is not enabled for this tenant',
|
||||||
configLoadFailed: 'Failed to load authentication configuration',
|
configLoadFailed: 'Failed to load authentication configuration',
|
||||||
@@ -414,6 +609,7 @@ const translations = {
|
|||||||
info: 'Information',
|
info: 'Information',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
|
copy: 'Copy to Clipboard',
|
||||||
delete: 'Delete',
|
delete: 'Delete',
|
||||||
edit: 'Edit',
|
edit: 'Edit',
|
||||||
view: 'View',
|
view: 'View',
|
||||||
@@ -860,6 +1056,23 @@ const translations = {
|
|||||||
highPriority: 'Hög prioritet',
|
highPriority: 'Hög prioritet',
|
||||||
mediumPriority: 'Medel prioritet',
|
mediumPriority: 'Medel prioritet',
|
||||||
clearAll: 'Rensa alla',
|
clearAll: 'Rensa alla',
|
||||||
|
noAlerts: 'Inga rörelselarm',
|
||||||
|
noAlertsDescription: 'Drönarrörelsemönster kommer att visas här',
|
||||||
|
droneDevice: 'Drönare {droneId} • Enhet {deviceId}',
|
||||||
|
droneType: 'Drönartyp:',
|
||||||
|
frequency: 'Frekvens:',
|
||||||
|
confidence: 'Säkerhet:',
|
||||||
|
signalDuration: 'Signallängd:',
|
||||||
|
movementPattern: 'Rörelsemönster:',
|
||||||
|
pattern: 'Mönster:',
|
||||||
|
speed: 'Hastighet:',
|
||||||
|
distance: 'Avstånd:',
|
||||||
|
trackingStats: 'Spårningsstatistik:',
|
||||||
|
detectionsOverTime: '{count} detekteringar över {time} minuter',
|
||||||
|
recentRssiHistory: 'Senaste RSSI-historik:',
|
||||||
|
strengthening: 'förstärks',
|
||||||
|
weakening: 'försvagas',
|
||||||
|
stable: 'stabil',
|
||||||
droneTypes: {
|
droneTypes: {
|
||||||
djiMavic: 'DJI Mavic',
|
djiMavic: 'DJI Mavic',
|
||||||
racingDrone: 'Racingdrönare',
|
racingDrone: 'Racingdrönare',
|
||||||
@@ -869,6 +1082,184 @@ const translations = {
|
|||||||
unknown: 'Okänd'
|
unknown: 'Okänd'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
debug: {
|
||||||
|
title: 'Felsökningskonsol',
|
||||||
|
subtitle: 'Endast admin-åtkomst till all detekteringsdata inklusive drönartyp 0 (Ingen)',
|
||||||
|
debugInformation: 'Felsökningsinformation',
|
||||||
|
totalNoneDetections: 'Totalt antal Ingen-detekteringar: {count}',
|
||||||
|
filters: 'Filter',
|
||||||
|
droneType: 'Drönartyp',
|
||||||
|
deviceId: 'Enhets-ID',
|
||||||
|
resultsPerPage: 'Resultat per sida',
|
||||||
|
filterByDeviceId: 'Filtrera efter enhets-ID',
|
||||||
|
allTypes: 'Alla typer',
|
||||||
|
debugDetections: 'Felsökningsdetekteringar ({count})',
|
||||||
|
noDebugData: 'Ingen felsökningsdata',
|
||||||
|
noDetectionsFound: 'Inga detekteringar hittades som matchar nuvarande filter.',
|
||||||
|
idTime: 'ID / Tid',
|
||||||
|
device: 'Enhet',
|
||||||
|
rssiFreq: 'RSSI / Frekv',
|
||||||
|
threatLevel: 'Hotnivå',
|
||||||
|
debug: 'Felsökning',
|
||||||
|
actions: 'Åtgärder',
|
||||||
|
viewPayload: 'Visa nyttolast',
|
||||||
|
deleteDetection: 'Ta bort detektion',
|
||||||
|
droneTypeOptions: {
|
||||||
|
none: '0 - Ingen (Felsökning)',
|
||||||
|
unknown: '1 - Okänd',
|
||||||
|
orlan: '2 - Orlan',
|
||||||
|
zala: '3 - Zala',
|
||||||
|
eleron: '4 - Eleron',
|
||||||
|
zalaLancet: '5 - Zala Lancet',
|
||||||
|
lancet: '6 - Lancet',
|
||||||
|
fpvCrossFire: '7 - FPV CrossFire',
|
||||||
|
fpvElrs: '8 - FPV ELRS',
|
||||||
|
maybeOrlan: '9 - Kanske Orlan',
|
||||||
|
maybeZala: '10 - Kanske Zala',
|
||||||
|
maybeLancet: '11 - Kanske Lancet',
|
||||||
|
maybeEleron: '12 - Kanske Eleron',
|
||||||
|
dji: '13 - DJI',
|
||||||
|
supercam: '14 - Supercam',
|
||||||
|
maybeSupercam: '15 - Kanske Supercam',
|
||||||
|
reb: '16 - REB',
|
||||||
|
cryptoOrlan: '17 - Krypto Orlan',
|
||||||
|
djiEnterprise: '18 - DJI Enterprise'
|
||||||
|
},
|
||||||
|
payloadViewer: {
|
||||||
|
title: 'Felsökningsnyttolastvisare',
|
||||||
|
detectionId: 'Detektions-ID:',
|
||||||
|
timestamp: 'Tidsstämpel:',
|
||||||
|
deviceId: 'Enhets-ID:',
|
||||||
|
rawPayload: 'Rå nyttolast:',
|
||||||
|
processedData: 'Bearbetad data:',
|
||||||
|
noPayloadData: 'Ingen rå nyttolastdata hittades för denna detektion',
|
||||||
|
failedToFetch: 'Misslyckades med att hämta nyttolastdata'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
title: 'Inställningar',
|
||||||
|
loading: 'Laddar inställningar...',
|
||||||
|
accessDenied: 'Åtkomst nekad',
|
||||||
|
noPermission: 'Du har inte behörighet att komma åt inställningar.',
|
||||||
|
general: 'Allmänt',
|
||||||
|
branding: 'Varumärke',
|
||||||
|
security: 'Säkerhet',
|
||||||
|
authentication: 'Autentisering',
|
||||||
|
users: 'Användare',
|
||||||
|
saveChanges: 'Spara ändringar',
|
||||||
|
saving: 'Sparar...',
|
||||||
|
saved: 'Inställningar sparade framgångsrikt',
|
||||||
|
failedToSave: 'Misslyckades med att spara inställningar',
|
||||||
|
failedToLoad: 'Misslyckades med att ladda klientinställningar'
|
||||||
|
},
|
||||||
|
alerts: {
|
||||||
|
title: 'Larmhantering',
|
||||||
|
description: 'Hantera larmregler och visa larmaktivitet',
|
||||||
|
loading: 'Laddar larm...',
|
||||||
|
rules: 'Regler',
|
||||||
|
logs: 'Loggar',
|
||||||
|
stats: 'Statistik',
|
||||||
|
createRule: 'Skapa regel',
|
||||||
|
createAlert: 'Skapa larm',
|
||||||
|
createAlertRule: 'Skapa larmregel',
|
||||||
|
editRule: 'Redigera regel',
|
||||||
|
deleteRule: 'Ta bort regel',
|
||||||
|
ruleDetails: 'Regeldetaljer',
|
||||||
|
noRules: 'Inga larmregler konfigurerade',
|
||||||
|
noAlertRules: 'Inga larmregler',
|
||||||
|
noRulesDescription: 'Skapa din första larmregel för att börja övervaka drönaraktivitet.',
|
||||||
|
noAlertRulesDescription: 'Skapa din första larmregel för att börja övervaka drönaraktivitet.',
|
||||||
|
noLogs: 'Inga larmloggar',
|
||||||
|
noLogsDescription: 'Larmaktivitet kommer att visas här när regler utlöses.',
|
||||||
|
name: 'Namn',
|
||||||
|
description: 'Beskrivning',
|
||||||
|
priority: 'Prioritet',
|
||||||
|
minDetections: 'Minsta antal detekteringar',
|
||||||
|
timeWindow: 'Tidsfönster (sekunder)',
|
||||||
|
cooldownPeriod: 'Nedkylningsperiod (sekunder)',
|
||||||
|
alertChannels: 'Larmkanaler',
|
||||||
|
minThreatLevel: 'Lägsta hotnivå',
|
||||||
|
droneTypes: 'Drönartyper',
|
||||||
|
deviceIds: 'Enhets-ID:n',
|
||||||
|
smsPhoneNumber: 'SMS-telefonnummer',
|
||||||
|
webhookUrl: 'Webhook-URL',
|
||||||
|
enabled: 'Aktiverad',
|
||||||
|
disabled: 'Inaktiverad',
|
||||||
|
active: 'Aktiv',
|
||||||
|
inactive: 'Inaktiv',
|
||||||
|
triggered: 'Utlöst',
|
||||||
|
sentAt: 'Skickat vid',
|
||||||
|
channel: 'Kanal',
|
||||||
|
channels: 'Kanaler',
|
||||||
|
status: 'Status',
|
||||||
|
message: 'Meddelande',
|
||||||
|
detectionDetails: 'Detekteringsdetaljer',
|
||||||
|
viewDetails: 'Visa detaljer',
|
||||||
|
alertEvent: 'Larmhändelse',
|
||||||
|
relatedAlerts: 'Relaterade larm',
|
||||||
|
showMore: 'Visa {count} fler larm',
|
||||||
|
showLess: 'Visa mindre',
|
||||||
|
alertRules: 'Larmregler',
|
||||||
|
alertLogs: 'Larmloggar',
|
||||||
|
conditions: 'Villkor',
|
||||||
|
actions: 'Åtgärder',
|
||||||
|
created: 'Skapad',
|
||||||
|
cooldown: 'Nedkylning',
|
||||||
|
minThreat: 'Min hot',
|
||||||
|
totalAlerts24h: 'Totala larm (24h)',
|
||||||
|
sentSuccessfully: 'Skickade framgångsrikt',
|
||||||
|
failed: 'Misslyckades',
|
||||||
|
pending: 'Väntande',
|
||||||
|
low: 'Låg',
|
||||||
|
medium: 'Medel',
|
||||||
|
high: 'Hög',
|
||||||
|
critical: 'Kritisk',
|
||||||
|
priorities: {
|
||||||
|
low: 'Låg',
|
||||||
|
medium: 'Medel',
|
||||||
|
high: 'Hög',
|
||||||
|
critical: 'Kritisk'
|
||||||
|
},
|
||||||
|
channels: {
|
||||||
|
sms: 'SMS',
|
||||||
|
webhook: 'Webhook',
|
||||||
|
email: 'E-post'
|
||||||
|
},
|
||||||
|
threatLevels: {
|
||||||
|
low: 'Låg',
|
||||||
|
medium: 'Medel',
|
||||||
|
high: 'Hög',
|
||||||
|
critical: 'Kritisk'
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
nameRequired: 'Regelnamn krävs',
|
||||||
|
nameHelp: 'Ett beskrivande namn för denna larmregel',
|
||||||
|
descriptionHelp: 'Valfri beskrivning av vad denna regel övervakar',
|
||||||
|
priorityHelp: 'Prioritetsnivå för larm genererade av denna regel',
|
||||||
|
minDetectionsHelp: 'Minsta antal detekteringar som krävs för att utlösa larm',
|
||||||
|
timeWindowHelp: 'Tidsfönster i sekunder för att räkna detekteringar',
|
||||||
|
cooldownHelp: 'Minimitid mellan larm för samma regel',
|
||||||
|
channelsRequired: 'Minst en larmkanal krävs',
|
||||||
|
channelsHelp: 'Hur larm från denna regel kommer att levereras',
|
||||||
|
threatLevelHelp: 'Utlös endast för detekteringar med denna hotnivå eller högre',
|
||||||
|
droneTypesHelp: 'Lämna tomt för att övervaka alla drönartyper',
|
||||||
|
deviceIdsHelp: 'Lämna tomt för att övervaka alla enheter',
|
||||||
|
smsHelp: 'Telefonnummer för SMS-larm (krävs om SMS-kanal är vald)',
|
||||||
|
webhookHelp: 'URL för webhook-larm (krävs om webhook-kanal är vald)',
|
||||||
|
updateFailed: 'Misslyckades med att uppdatera larmregel',
|
||||||
|
editAlertRule: 'Redigera larmregel',
|
||||||
|
anyLevel: 'Alla nivåer',
|
||||||
|
monitoring: 'Övervakning',
|
||||||
|
droneTypesFilter: 'Drönartypsfilter',
|
||||||
|
leaveEmptyAllTypes: 'Lämna tomt för att övervaka alla drönartyper',
|
||||||
|
alertChannelsLabel: 'Larmkanaler',
|
||||||
|
selectChannels: 'Välj hur larm kommer att skickas',
|
||||||
|
smsPhoneLabel: 'SMS-telefonnummer',
|
||||||
|
smsRequired: 'Krävs när SMS är valt',
|
||||||
|
webhookUrlLabel: 'Webhook-URL',
|
||||||
|
webhookRequired: 'Krävs när webhook är valt'
|
||||||
|
}
|
||||||
|
},
|
||||||
register: {
|
register: {
|
||||||
registrationDisabled: 'Registrering är inte aktiverad för denna klient',
|
registrationDisabled: 'Registrering är inte aktiverad för denna klient',
|
||||||
configLoadFailed: 'Misslyckades med att ladda autentiseringskonfiguration',
|
configLoadFailed: 'Misslyckades med att ladda autentiseringskonfiguration',
|
||||||
@@ -898,6 +1289,7 @@ const translations = {
|
|||||||
info: 'Information',
|
info: 'Information',
|
||||||
cancel: 'Avbryt',
|
cancel: 'Avbryt',
|
||||||
save: 'Spara',
|
save: 'Spara',
|
||||||
|
copy: 'Kopiera till urklipp',
|
||||||
delete: 'Ta bort',
|
delete: 'Ta bort',
|
||||||
edit: 'Redigera',
|
edit: 'Redigera',
|
||||||
view: 'Visa',
|
view: 'Visa',
|
||||||
|
|||||||
Reference in New Issue
Block a user