Fix jwt-token
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user