Fix jwt-token

This commit is contained in:
2025-09-19 14:26:27 +02:00
parent 52cc013016
commit 395a5b8842
2 changed files with 108 additions and 44 deletions

View File

@@ -63,7 +63,7 @@ const Devices = () => {
}; };
const handleRejectDevice = async (deviceId) => { const handleRejectDevice = async (deviceId) => {
if (window.confirm('Are you sure you want to reject this device?')) { if (window.confirm(t('devices.confirmReject'))) {
try { try {
await api.post(`/devices/${deviceId}/approve`, { approved: false }); await api.post(`/devices/${deviceId}/approve`, { approved: false });
fetchDevices(); fetchDevices();
@@ -73,7 +73,7 @@ const Devices = () => {
alert('Your session has expired. Please log in again.'); alert('Your session has expired. Please log in again.');
return; return;
} }
alert('Error rejecting device: ' + (error.response?.data?.message || error.message)); alert(t('devices.errorRejecting') + ' ' + (error.response?.data?.message || error.message));
} }
} }
}; };
@@ -103,12 +103,12 @@ const Devices = () => {
}; };
const handleDeleteDevice = async (deviceId) => { const handleDeleteDevice = async (deviceId) => {
if (window.confirm('Are you sure you want to deactivate this device?')) { if (window.confirm(t('devices.confirmDelete'))) {
try { try {
await api.delete(`/devices/${deviceId}`); await api.delete(`/devices/${deviceId}`);
fetchDevices(); fetchDevices();
} catch (error) { } catch (error) {
console.error('Error deleting device:', error); console.error(t('devices.errorDeleting'), error);
} }
} }
}; };
@@ -125,13 +125,13 @@ const Devices = () => {
}; };
const getSignalStrength = (lastHeartbeat) => { const getSignalStrength = (lastHeartbeat) => {
if (!lastHeartbeat) return 'Unknown'; if (!lastHeartbeat) return t('devices.unknown');
const timeSince = (new Date() - new Date(lastHeartbeat)) / 1000 / 60; // minutes const timeSince = (new Date() - new Date(lastHeartbeat)) / 1000 / 60; // minutes
if (timeSince < 5) return 'Strong'; if (timeSince < 5) return t('devices.signalStrong');
if (timeSince < 15) return 'Good'; if (timeSince < 15) return t('devices.signalGood');
if (timeSince < 60) return 'Weak'; if (timeSince < 60) return t('devices.signalWeak');
return 'Lost'; return t('devices.signalLost');
}; };
const filteredDevices = devices.filter(device => { const filteredDevices = devices.filter(device => {
@@ -162,7 +162,7 @@ const Devices = () => {
{t('devices.description')} {t('devices.description')}
{pendingCount > 0 && ( {pendingCount > 0 && (
<span className="ml-2 px-2 py-1 text-xs font-medium bg-yellow-200 text-yellow-800 rounded-full"> <span className="ml-2 px-2 py-1 text-xs font-medium bg-yellow-200 text-yellow-800 rounded-full">
{pendingCount} pending approval {pendingCount} {t('devices.pendingApproval')}
</span> </span>
)} )}
</p> </p>
@@ -187,7 +187,7 @@ const Devices = () => {
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`} }`}
> >
All Devices ({devices.length}) {t('devices.allDevices')} ({devices.length})
</button> </button>
<button <button
onClick={() => setFilter('approved')} onClick={() => setFilter('approved')}
@@ -197,7 +197,7 @@ const Devices = () => {
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`} }`}
> >
Approved ({devices.filter(d => d.is_approved).length}) {t('devices.approved')} ({devices.filter(d => d.is_approved).length})
</button> </button>
<button <button
onClick={() => setFilter('pending')} onClick={() => setFilter('pending')}
@@ -207,7 +207,7 @@ const Devices = () => {
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`} }`}
> >
Pending Approval ({pendingCount}) {t('devices.pendingApprovalTab')} ({pendingCount})
{pendingCount > 0 && ( {pendingCount > 0 && (
<span className="absolute -top-1 -right-1 h-2 w-2 bg-yellow-400 rounded-full"></span> <span className="absolute -top-1 -right-1 h-2 w-2 bg-yellow-400 rounded-full"></span>
)} )}
@@ -228,11 +228,11 @@ const Devices = () => {
device.stats?.status === 'online' ? 'bg-green-400' : 'bg-red-400' device.stats?.status === 'online' ? 'bg-green-400' : 'bg-red-400'
}`} /> }`} />
<h4 className="text-lg font-medium text-gray-900"> <h4 className="text-lg font-medium text-gray-900">
{device.name || `Device ${device.id}`} {device.name || `${t('devices.device')} ${device.id}`}
</h4> </h4>
{!device.is_approved && ( {!device.is_approved && (
<span className="px-2 py-1 text-xs font-medium bg-yellow-200 text-yellow-800 rounded-full"> <span className="px-2 py-1 text-xs font-medium bg-yellow-200 text-yellow-800 rounded-full">
Needs Approval {t('devices.needsApproval')}
</span> </span>
)} )}
</div> </div>
@@ -254,25 +254,25 @@ const Devices = () => {
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Status</span> <span className="text-sm text-gray-500">{t('devices.status')}</span>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${ <span className={`px-2 py-1 rounded-full text-xs font-medium ${
getStatusColor(device.stats?.status) getStatusColor(device.stats?.status)
}`}> }`}>
{device.stats?.status || 'Unknown'} {device.stats?.status ? t(`devices.${device.stats.status}`) : t('devices.unknown')}
</span> </span>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Approval</span> <span className="text-sm text-gray-500">{t('devices.approval')}</span>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${ <span className={`px-2 py-1 rounded-full text-xs font-medium ${
device.is_approved ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800' device.is_approved ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
}`}> }`}>
{device.is_approved ? 'Approved' : 'Pending'} {device.is_approved ? t('devices.approved') : t('devices.pending')}
</span> </span>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Device ID</span> <span className="text-sm text-gray-500">{t('devices.deviceId')}</span>
<span className="text-sm font-medium text-gray-900"> <span className="text-sm font-medium text-gray-900">
{device.id} {device.id}
</span> </span>
@@ -280,7 +280,7 @@ const Devices = () => {
{device.location_description && ( {device.location_description && (
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<span className="text-sm text-gray-500">Location</span> <span className="text-sm text-gray-500">{t('devices.location')}</span>
<span className="text-sm text-gray-900 text-right"> <span className="text-sm text-gray-900 text-right">
{device.location_description} {device.location_description}
</span> </span>
@@ -289,7 +289,7 @@ const Devices = () => {
{(device.geo_lat && device.geo_lon) && ( {(device.geo_lat && device.geo_lon) && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Coordinates</span> <span className="text-sm text-gray-500">{t('devices.coordinates')}</span>
<span className="text-sm text-gray-900"> <span className="text-sm text-gray-900">
{device.geo_lat}, {device.geo_lon} {device.geo_lat}, {device.geo_lon}
</span> </span>
@@ -297,7 +297,7 @@ const Devices = () => {
)} )}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Signal</span> <span className="text-sm text-gray-500">{t('devices.signal')}</span>
<span className="text-sm text-gray-900"> <span className="text-sm text-gray-900">
{getSignalStrength(device.last_heartbeat)} {getSignalStrength(device.last_heartbeat)}
</span> </span>
@@ -305,7 +305,7 @@ const Devices = () => {
{device.stats && ( {device.stats && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Detections (24h)</span> <span className="text-sm text-gray-500">{t('devices.detections24h')}</span>
<span className={`text-sm font-medium ${ <span className={`text-sm font-medium ${
device.stats.detections_24h > 0 ? 'text-red-600' : 'text-green-600' device.stats.detections_24h > 0 ? 'text-red-600' : 'text-green-600'
}`}> }`}>
@@ -316,7 +316,7 @@ const Devices = () => {
{device.last_heartbeat && ( {device.last_heartbeat && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Last Seen</span> <span className="text-sm text-gray-500">{t('devices.lastSeen')}</span>
<span className="text-sm text-gray-900"> <span className="text-sm text-gray-900">
{format(new Date(device.last_heartbeat), 'MMM dd, HH:mm')} {format(new Date(device.last_heartbeat), 'MMM dd, HH:mm')}
</span> </span>
@@ -325,7 +325,7 @@ const Devices = () => {
{device.firmware_version && ( {device.firmware_version && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Firmware</span> <span className="text-sm text-gray-500">{t('devices.firmware')}</span>
<span className="text-sm text-gray-900"> <span className="text-sm text-gray-900">
{device.firmware_version} {device.firmware_version}
</span> </span>
@@ -341,13 +341,13 @@ const Devices = () => {
onClick={() => handleApproveDevice(device.id)} onClick={() => handleApproveDevice(device.id)}
className="flex-1 text-xs bg-green-100 text-green-700 py-2 px-3 rounded hover:bg-green-200 transition-colors font-medium" className="flex-1 text-xs bg-green-100 text-green-700 py-2 px-3 rounded hover:bg-green-200 transition-colors font-medium"
> >
Approve Device {t('devices.approveDevice')}
</button> </button>
<button <button
onClick={() => handleRejectDevice(device.id)} onClick={() => handleRejectDevice(device.id)}
className="flex-1 text-xs bg-red-100 text-red-700 py-2 px-3 rounded hover:bg-red-200 transition-colors font-medium" className="flex-1 text-xs bg-red-100 text-red-700 py-2 px-3 rounded hover:bg-red-200 transition-colors font-medium"
> >
Reject {t('devices.reject')}
</button> </button>
</div> </div>
) : null} ) : null}
@@ -356,13 +356,13 @@ const Devices = () => {
onClick={() => handleViewDetails(device)} onClick={() => handleViewDetails(device)}
className="flex-1 text-xs bg-gray-100 text-gray-700 py-2 px-3 rounded hover:bg-gray-200 transition-colors" className="flex-1 text-xs bg-gray-100 text-gray-700 py-2 px-3 rounded hover:bg-gray-200 transition-colors"
> >
View Details {t('devices.viewDetails')}
</button> </button>
<button <button
onClick={() => handleViewOnMap(device)} onClick={() => handleViewOnMap(device)}
className="flex-1 text-xs bg-primary-100 text-primary-700 py-2 px-3 rounded hover:bg-primary-200 transition-colors" className="flex-1 text-xs bg-primary-100 text-primary-700 py-2 px-3 rounded hover:bg-primary-200 transition-colors"
> >
View on Map {t('devices.viewOnMap')}
</button> </button>
</div> </div>
</div> </div>
@@ -375,14 +375,14 @@ const Devices = () => {
<div className="text-center py-12"> <div className="text-center py-12">
<ServerIcon className="mx-auto h-12 w-12 text-gray-400" /> <ServerIcon className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900"> <h3 className="mt-2 text-sm font-medium text-gray-900">
No {filter === 'all' ? '' : filter} devices {filter === 'all' ? t('devices.noDevices') : `${t('devices.noDevicesFiltered').replace('the current filter', filter)}`}
</h3> </h3>
<p className="mt-1 text-sm text-gray-500"> <p className="mt-1 text-sm text-gray-500">
{filter === 'pending' {filter === 'pending'
? 'No devices are currently pending approval.' ? t('devices.noDevicesPending')
: filter === 'approved' : filter === 'approved'
? 'No devices have been approved yet.' ? t('devices.noDevicesApproved')
: 'No devices match the current filter.' : t('devices.noDevicesFiltered')
} }
</p> </p>
</div> </div>
@@ -391,9 +391,9 @@ const Devices = () => {
{devices.length === 0 && ( {devices.length === 0 && (
<div className="text-center py-12"> <div className="text-center py-12">
<ServerIcon className="mx-auto h-12 w-12 text-gray-400" /> <ServerIcon className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">No devices</h3> <h3 className="mt-2 text-sm font-medium text-gray-900">{t('devices.noDevices')}</h3>
<p className="mt-1 text-sm text-gray-500"> <p className="mt-1 text-sm text-gray-500">
Get started by adding your first drone detection device. {t('devices.noDevicesDescription')}
</p> </p>
<div className="mt-6"> <div className="mt-6">
<button <button

View File

@@ -92,9 +92,10 @@ const translations = {
lowConfidence: 'Low Confidence' lowConfidence: 'Low Confidence'
}, },
devices: { devices: {
title: 'Device Management', title: 'All Devices',
description: 'Monitor and manage your detection devices', description: 'Manage your UAM-ILS detection devices',
noDevices: 'No devices found', noDevices: 'No devices',
noDevicesDescription: 'Get started by adding your first drone detection device.',
loading: 'Loading devices...', loading: 'Loading devices...',
name: 'Name', name: 'Name',
status: 'Status', status: 'Status',
@@ -109,7 +110,38 @@ const translations = {
signalStrength: 'Signal Strength', signalStrength: 'Signal Strength',
firmware: 'Firmware', firmware: 'Firmware',
ipAddress: 'IP Address', ipAddress: 'IP Address',
macAddress: 'MAC Address' macAddress: 'MAC Address',
approved: 'Approved',
pending: 'Pending',
pendingApproval: 'pending approval',
allDevices: 'All Devices',
pendingApprovalTab: 'Pending Approval',
needsApproval: 'Needs Approval',
approval: 'Approval',
deviceId: 'Device ID',
coordinates: 'Coordinates',
signal: 'Signal',
detections24h: 'Detections (24h)',
approveDevice: 'Approve Device',
reject: 'Reject',
viewDetails: 'View Details',
viewOnMap: 'View on Map',
noDevicesFiltered: 'No devices match the current filter.',
noDevicesPending: 'No devices are currently pending approval.',
noDevicesApproved: 'No devices have been approved yet.',
device: 'Device',
unknown: 'Unknown',
online: 'Online',
offline: 'Offline',
inactive: 'Inactive',
confirmReject: 'Are you sure you want to reject this device?',
errorRejecting: 'Error rejecting device:',
errorDeleting: 'Error deleting device:',
confirmDelete: 'Are you sure you want to delete this device? This action cannot be undone.',
signalStrong: 'Strong',
signalGood: 'Good',
signalWeak: 'Weak',
signalLost: 'Lost'
}, },
alerts: { alerts: {
title: 'Alert Management', title: 'Alert Management',
@@ -341,9 +373,10 @@ const translations = {
lowConfidence: 'Låg säkerhet' lowConfidence: 'Låg säkerhet'
}, },
devices: { devices: {
title: 'Enhetshantering', title: 'Alla enheter',
description: 'Övervaka och hantera dina detekteringsenheter', description: 'Hantera dina UAM-ILS detekteringsenheter',
noDevices: 'Inga enheter hittades', noDevices: 'Inga enheter',
noDevicesDescription: 'Kom igång genom att lägga till din första drönaredetekteringsenhet.',
loading: 'Laddar enheter...', loading: 'Laddar enheter...',
name: 'Namn', name: 'Namn',
status: 'Status', status: 'Status',
@@ -358,7 +391,38 @@ const translations = {
signalStrength: 'Signalstyrka', signalStrength: 'Signalstyrka',
firmware: 'Firmware', firmware: 'Firmware',
ipAddress: 'IP-adress', ipAddress: 'IP-adress',
macAddress: 'MAC-adress' macAddress: 'MAC-adress',
approved: 'Godkänd',
pending: 'Väntande',
pendingApproval: 'väntande godkännande',
allDevices: 'Alla enheter',
pendingApprovalTab: 'Väntande godkännande',
needsApproval: 'Behöver godkännande',
approval: 'Godkännande',
deviceId: 'Enhets-ID',
coordinates: 'Koordinater',
signal: 'Signal',
detections24h: 'Detekteringar (24h)',
approveDevice: 'Godkänn enhet',
reject: 'Avvisa',
viewDetails: 'Visa detaljer',
viewOnMap: 'Visa på karta',
noDevicesFiltered: 'Inga enheter matchar det aktuella filtret.',
noDevicesPending: 'Inga enheter väntar för närvarande på godkännande.',
noDevicesApproved: 'Inga enheter har godkänts än.',
device: 'Enhet',
unknown: 'Okänd',
online: 'Online',
offline: 'Offline',
inactive: 'Inaktiv',
confirmReject: 'Är du säker på att du vill avvisa denna enhet?',
errorRejecting: 'Fel vid avvisning av enhet:',
errorDeleting: 'Fel vid borttagning av enhet:',
confirmDelete: 'Är du säker på att du vill ta bort denna enhet? Denna åtgärd kan inte ångras.',
signalStrong: 'Stark',
signalGood: 'Bra',
signalWeak: 'Svag',
signalLost: 'Förlorad'
}, },
alerts: { alerts: {
title: 'Larmhantering', title: 'Larmhantering',