Fix jwt-token
This commit is contained in:
@@ -137,19 +137,19 @@ const Alerts = () => {
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
<div className="bg-white p-4 rounded-lg shadow border">
|
<div className="bg-white p-4 rounded-lg shadow border">
|
||||||
<div className="text-2xl font-bold text-gray-900">{alertStats.total_alerts}</div>
|
<div className="text-2xl font-bold text-gray-900">{alertStats.total_alerts}</div>
|
||||||
<div className="text-sm text-gray-500">Total Alerts (24h)</div>
|
<div className="text-sm text-gray-500">{t('alerts.totalAlerts24h')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-4 rounded-lg shadow border">
|
<div className="bg-white p-4 rounded-lg shadow border">
|
||||||
<div className="text-2xl font-bold text-green-600">{alertStats.sent_alerts}</div>
|
<div className="text-2xl font-bold text-green-600">{alertStats.sent_alerts}</div>
|
||||||
<div className="text-sm text-gray-500">Sent Successfully</div>
|
<div className="text-sm text-gray-500">{t('alerts.sentSuccessfully')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-4 rounded-lg shadow border">
|
<div className="bg-white p-4 rounded-lg shadow border">
|
||||||
<div className="text-2xl font-bold text-red-600">{alertStats.failed_alerts}</div>
|
<div className="text-2xl font-bold text-red-600">{alertStats.failed_alerts}</div>
|
||||||
<div className="text-sm text-gray-500">Failed</div>
|
<div className="text-sm text-gray-500">{t('alerts.failed')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-4 rounded-lg shadow border">
|
<div className="bg-white p-4 rounded-lg shadow border">
|
||||||
<div className="text-2xl font-bold text-yellow-600">{alertStats.pending_alerts}</div>
|
<div className="text-2xl font-bold text-yellow-600">{alertStats.pending_alerts}</div>
|
||||||
<div className="text-sm text-gray-500">Pending</div>
|
<div className="text-sm text-gray-500">{t('alerts.pending')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -165,7 +165,7 @@ const Alerts = () => {
|
|||||||
: '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'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Alert Rules ({alertRules.length})
|
{t('alerts.alertRules')} ({alertRules.length})
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('logs')}
|
onClick={() => setActiveTab('logs')}
|
||||||
@@ -175,7 +175,7 @@ const Alerts = () => {
|
|||||||
: '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'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Alert Logs ({alertLogs?.length || 0})
|
{t('alerts.alertLogs')} ({alertLogs?.length || 0})
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,9 +186,9 @@ const Alerts = () => {
|
|||||||
{(alertRules?.length || 0) === 0 ? (
|
{(alertRules?.length || 0) === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<BellIcon className="mx-auto h-12 w-12 text-gray-400" />
|
<BellIcon className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">No alert rules</h3>
|
<h3 className="mt-2 text-sm font-medium text-gray-900">{t('alerts.noAlertRules')}</h3>
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
Get started by creating your first alert rule.
|
{t('alerts.noAlertRulesDescription')}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<button
|
<button
|
||||||
@@ -196,7 +196,7 @@ const Alerts = () => {
|
|||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-4 w-4 mr-2" />
|
<PlusIcon className="h-4 w-4 mr-2" />
|
||||||
Create Alert Rule
|
{t('alerts.createAlertRule')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -205,13 +205,13 @@ const Alerts = () => {
|
|||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>{t('alerts.name')}</th>
|
||||||
<th>Priority</th>
|
<th>{t('alerts.priority')}</th>
|
||||||
<th>Channels</th>
|
<th>{t('alerts.channels')}</th>
|
||||||
<th>Conditions</th>
|
<th>{t('alerts.conditions')}</th>
|
||||||
<th>Status</th>
|
<th>{t('alerts.status')}</th>
|
||||||
<th>Created</th>
|
<th>{t('alerts.created')}</th>
|
||||||
<th>Actions</th>
|
<th>{t('alerts.actions')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -233,7 +233,7 @@ const Alerts = () => {
|
|||||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
|
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
|
||||||
getPriorityColor(rule.priority)
|
getPriorityColor(rule.priority)
|
||||||
}`}>
|
}`}>
|
||||||
{rule.priority}
|
{t(`alerts.${rule.priority}`)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -251,28 +251,28 @@ const Alerts = () => {
|
|||||||
<td>
|
<td>
|
||||||
<div className="text-sm text-gray-900">
|
<div className="text-sm text-gray-900">
|
||||||
{rule.min_detections > 1 && (
|
{rule.min_detections > 1 && (
|
||||||
<div>Min detections: {rule.min_detections}</div>
|
<div>{t('alerts.minDetections')}: {rule.min_detections}</div>
|
||||||
)}
|
)}
|
||||||
{rule.time_window && (
|
{rule.time_window && (
|
||||||
<div>Time window: {rule.time_window}s</div>
|
<div>{t('alerts.timeWindow')}: {rule.time_window}s</div>
|
||||||
)}
|
)}
|
||||||
{rule.cooldown_period && (
|
{rule.cooldown_period && (
|
||||||
<div>Cooldown: {rule.cooldown_period}s</div>
|
<div>{t('alerts.cooldown')}: {rule.cooldown_period}s</div>
|
||||||
)}
|
)}
|
||||||
{rule.min_threat_level && (
|
{rule.min_threat_level && (
|
||||||
<div>Min threat: {rule.min_threat_level}</div>
|
<div>{t('alerts.minThreat')}: {rule.min_threat_level}</div>
|
||||||
)}
|
)}
|
||||||
{rule.drone_types && rule.drone_types.length > 0 && (
|
{rule.drone_types && rule.drone_types.length > 0 && (
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<div className="text-xs text-gray-500">Drone types:</div>
|
<div className="text-xs text-gray-500">{t('alerts.droneTypes')}:</div>
|
||||||
<div className="flex flex-wrap gap-1 mt-1">
|
<div className="flex flex-wrap gap-1 mt-1">
|
||||||
{rule.drone_types.map((typeId, index) => {
|
{rule.drone_types.map((typeId, index) => {
|
||||||
const droneTypes = {
|
const droneTypeKeys = {
|
||||||
0: 'Consumer',
|
0: 'consumer',
|
||||||
1: 'Orlan',
|
1: 'orlan',
|
||||||
2: 'Professional',
|
2: 'professional',
|
||||||
3: 'Racing',
|
3: 'racing',
|
||||||
4: 'Unknown'
|
4: 'unknown'
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
@@ -283,7 +283,7 @@ const Alerts = () => {
|
|||||||
: 'bg-gray-100 text-gray-800'
|
: 'bg-gray-100 text-gray-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{droneTypes[typeId] || 'Unknown'}
|
{t(`alerts.${droneTypeKeys[typeId] || 'unknown'}`)}
|
||||||
{typeId === 1 && '⚠️'}
|
{typeId === 1 && '⚠️'}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -299,7 +299,7 @@ const Alerts = () => {
|
|||||||
? 'bg-green-100 text-green-800'
|
? 'bg-green-100 text-green-800'
|
||||||
: 'bg-gray-100 text-gray-800'
|
: 'bg-gray-100 text-gray-800'
|
||||||
}`}>
|
}`}>
|
||||||
{rule.is_active ? 'Active' : 'Inactive'}
|
{rule.is_active ? t('alerts.active') : t('alerts.inactive')}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -313,13 +313,13 @@ const Alerts = () => {
|
|||||||
onClick={() => handleEditRule(rule)}
|
onClick={() => handleEditRule(rule)}
|
||||||
className="text-primary-600 hover:text-primary-900 text-sm"
|
className="text-primary-600 hover:text-primary-900 text-sm"
|
||||||
>
|
>
|
||||||
Edit
|
{t('alerts.edit')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteRule(rule.id)}
|
onClick={() => handleDeleteRule(rule.id)}
|
||||||
className="text-red-600 hover:text-red-900 text-sm"
|
className="text-red-600 hover:text-red-900 text-sm"
|
||||||
>
|
>
|
||||||
Delete
|
{t('alerts.delete')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -338,9 +338,9 @@ const Alerts = () => {
|
|||||||
{(alertLogs?.length || 0) === 0 ? (
|
{(alertLogs?.length || 0) === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<BellIcon className="mx-auto h-12 w-12 text-gray-400" />
|
<BellIcon className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">No alert logs</h3>
|
<h3 className="mt-2 text-sm font-medium text-gray-900">{t('alerts.noAlertLogs')}</h3>
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
Alert logs will appear here when alerts are triggered.
|
{t('alerts.noAlertLogsDescription')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -348,13 +348,13 @@ const Alerts = () => {
|
|||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Status</th>
|
<th>{t('alerts.status')}</th>
|
||||||
<th>Type</th>
|
<th>{t('alerts.type')}</th>
|
||||||
<th>Recipient</th>
|
<th>{t('alerts.recipient')}</th>
|
||||||
<th>Rule</th>
|
<th>{t('alerts.rule')}</th>
|
||||||
<th>Detection</th>
|
<th>{t('alerts.detection')}</th>
|
||||||
<th>Message</th>
|
<th>{t('alerts.message')}</th>
|
||||||
<th>Sent At</th>
|
<th>{t('alerts.sentAt')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -380,7 +380,7 @@ const Alerts = () => {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="text-sm text-gray-900">
|
<div className="text-sm text-gray-900">
|
||||||
{log.rule?.name || 'Unknown Rule'}
|
{log.rule?.name || t('alerts.unknownRule')}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -389,10 +389,10 @@ const Alerts = () => {
|
|||||||
onClick={() => handleViewDetection(log.detection_id)}
|
onClick={() => handleViewDetection(log.detection_id)}
|
||||||
className="text-primary-600 hover:text-primary-900 text-sm font-medium"
|
className="text-primary-600 hover:text-primary-900 text-sm font-medium"
|
||||||
>
|
>
|
||||||
View Details
|
{t('alerts.viewDetails')}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-gray-400 text-sm">N/A</span>
|
<span className="text-gray-400 text-sm">{t('alerts.na')}</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -53,10 +53,45 @@ const ALL_TABS = [
|
|||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [tenantConfig, setTenantConfig] = useState(null);
|
const [tenantConfig, setTenantConfig] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
|
// Define tabs with translations inside component
|
||||||
|
const ALL_TABS = [
|
||||||
|
{
|
||||||
|
id: 'general',
|
||||||
|
name: t('settings.general'),
|
||||||
|
icon: CogIcon,
|
||||||
|
permission: 'tenant.view'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'branding',
|
||||||
|
name: t('settings.branding'),
|
||||||
|
icon: PaintBrushIcon,
|
||||||
|
permission: 'branding.view'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'security',
|
||||||
|
name: t('settings.security'),
|
||||||
|
icon: ShieldCheckIcon,
|
||||||
|
permission: 'security.view'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'authentication',
|
||||||
|
name: t('settings.authentication'),
|
||||||
|
icon: KeyIcon,
|
||||||
|
permission: 'auth.view'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'users',
|
||||||
|
name: t('settings.users'),
|
||||||
|
icon: UserGroupIcon,
|
||||||
|
permission: 'users.view'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
// Calculate available tabs
|
// Calculate available tabs
|
||||||
const availableTabs = user?.role
|
const availableTabs = user?.role
|
||||||
? ALL_TABS.filter(tab => hasPermission(user.role, tab.permission))
|
? ALL_TABS.filter(tab => hasPermission(user.role, tab.permission))
|
||||||
@@ -101,9 +136,9 @@ const Settings = () => {
|
|||||||
<div className="min-h-screen flex items-center justify-center">
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<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">Access Denied</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">
|
||||||
You don't have permission to access tenant settings.
|
{t('settings.accessDeniedMessage')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,27 +202,30 @@ const Settings = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// General Settings Component
|
// General Settings Component
|
||||||
const GeneralSettings = ({ tenantConfig }) => (
|
const GeneralSettings = ({ tenantConfig }) => {
|
||||||
<div className="bg-white shadow rounded-lg">
|
const { t } = useTranslation();
|
||||||
<div className="px-4 py-5 sm:p-6">
|
return (
|
||||||
<h3 className="text-lg leading-6 font-medium text-gray-900">General Information</h3>
|
<div className="bg-white shadow rounded-lg">
|
||||||
<div className="mt-5 space-y-4">
|
<div className="px-4 py-5 sm:p-6">
|
||||||
<div>
|
<h3 className="text-lg leading-6 font-medium text-gray-900">{t('settings.generalInformation')}</h3>
|
||||||
<label className="block text-sm font-medium text-gray-700">Tenant Name</label>
|
<div className="mt-5 space-y-4">
|
||||||
<p className="mt-1 text-sm text-gray-900">{tenantConfig?.name}</p>
|
<div>
|
||||||
</div>
|
<label className="block text-sm font-medium text-gray-700">{t('settings.tenantName')}</label>
|
||||||
<div>
|
<p className="mt-1 text-sm text-gray-900">{tenantConfig?.name}</p>
|
||||||
<label className="block text-sm font-medium text-gray-700">Tenant ID</label>
|
</div>
|
||||||
<p className="mt-1 text-sm text-gray-500 font-mono">{tenantConfig?.slug}</p>
|
<div>
|
||||||
</div>
|
<label className="block text-sm font-medium text-gray-700">{t('settings.tenantId')}</label>
|
||||||
<div>
|
<p className="mt-1 text-sm text-gray-500 font-mono">{tenantConfig?.slug}</p>
|
||||||
<label className="block text-sm font-medium text-gray-700">Authentication Provider</label>
|
</div>
|
||||||
<p className="mt-1 text-sm text-gray-900 uppercase">{tenantConfig?.auth_provider}</p>
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700">{t('settings.authenticationProvider')}</label>
|
||||||
|
<p className="mt-1 text-sm text-gray-900 uppercase">{tenantConfig?.auth_provider}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
// Branding Settings Component
|
// Branding Settings Component
|
||||||
const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
||||||
@@ -214,10 +252,10 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await api.put('/tenant/branding', branding);
|
await api.put('/tenant/branding', branding);
|
||||||
toast.success('Branding updated successfully');
|
toast.success(t('settings.brandingUpdated'));
|
||||||
if (onRefresh) onRefresh();
|
if (onRefresh) onRefresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to update branding');
|
toast.error(t('settings.brandingUpdateFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
@@ -279,7 +317,7 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
<h3 className="text-lg leading-6 font-medium text-gray-900">Branding & Appearance</h3>
|
<h3 className="text-lg leading-6 font-medium text-gray-900">Branding & Appearance</h3>
|
||||||
<div className="mt-5 space-y-6">
|
<div className="mt-5 space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">Company Name</label>
|
<label className="block text-sm font-medium text-gray-700">{t('settings.companyName')}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={branding.company_name}
|
value={branding.company_name}
|
||||||
@@ -345,7 +383,7 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
|
|
||||||
{/* Manual URL input as fallback */}
|
{/* Manual URL input as fallback */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<label className="block text-sm font-medium text-gray-700">Or enter logo URL manually</label>
|
<label className="block text-sm font-medium text-gray-700">{t('settings.logoUrl')}</label>
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
value={branding.logo_url}
|
value={branding.logo_url}
|
||||||
@@ -359,7 +397,7 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
|
|
||||||
<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">Primary Color</label>
|
<label className="block text-sm font-medium text-gray-700">{t('settings.primaryColor')}</label>
|
||||||
<div className="mt-1 flex">
|
<div className="mt-1 flex">
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
@@ -379,7 +417,7 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">Secondary Color</label>
|
<label className="block text-sm font-medium text-gray-700">{t('settings.secondaryColor')}</label>
|
||||||
<div className="mt-1 flex">
|
<div className="mt-1 flex">
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
@@ -406,7 +444,7 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="bg-primary-600 text-white px-4 py-2 rounded-md hover:bg-primary-700 disabled:opacity-50"
|
className="bg-primary-600 text-white px-4 py-2 rounded-md hover:bg-primary-700 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saving ? 'Saving...' : 'Save Branding'}
|
{saving ? t('settings.saving') : t('settings.saveBranding')}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-sm text-gray-500 py-2">
|
<div className="text-sm text-gray-500 py-2">
|
||||||
@@ -491,11 +529,11 @@ const SecuritySettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
try {
|
try {
|
||||||
console.log('🔒 Sending security settings:', securitySettings);
|
console.log('🔒 Sending security settings:', securitySettings);
|
||||||
await api.put('/tenant/security', securitySettings);
|
await api.put('/tenant/security', securitySettings);
|
||||||
toast.success('Security settings updated successfully');
|
toast.success(t('settings.securityUpdated'));
|
||||||
if (onRefresh) onRefresh();
|
if (onRefresh) onRefresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update security settings:', error);
|
console.error('Failed to update security settings:', error);
|
||||||
toast.error('Failed to update security settings');
|
toast.error(t('settings.securityUpdateFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
@@ -516,7 +554,7 @@ const SecuritySettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
disabled={saving || !canEdit}
|
disabled={saving || !canEdit}
|
||||||
className="bg-primary-600 text-white px-4 py-2 rounded-md hover:bg-primary-700 disabled:opacity-50"
|
className="bg-primary-600 text-white px-4 py-2 rounded-md hover:bg-primary-700 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saving ? 'Saving...' : 'Save Changes'}
|
{saving ? t('settings.saving') : t('settings.saveChanges')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -168,7 +168,44 @@ const translations = {
|
|||||||
lowPriority: 'Low Priority',
|
lowPriority: 'Low Priority',
|
||||||
active: 'Active',
|
active: 'Active',
|
||||||
inactive: 'Inactive',
|
inactive: 'Inactive',
|
||||||
triggered: 'Triggered'
|
totalAlerts24h: 'Total Alerts (24h)',
|
||||||
|
sentSuccessfully: 'Sent Successfully',
|
||||||
|
failed: 'Failed',
|
||||||
|
pending: 'Pending',
|
||||||
|
alertRules: 'Alert Rules',
|
||||||
|
alertLogs: 'Alert Logs',
|
||||||
|
noAlertRules: 'No alert rules',
|
||||||
|
noAlertRulesDescription: 'Get started by creating your first alert rule.',
|
||||||
|
createAlertRule: 'Create Alert Rule',
|
||||||
|
noAlertLogs: 'No alert logs',
|
||||||
|
noAlertLogsDescription: 'Alert logs will appear here when alerts are triggered.',
|
||||||
|
name: 'Name',
|
||||||
|
channels: 'Channels',
|
||||||
|
conditions: 'Conditions',
|
||||||
|
edit: 'Edit',
|
||||||
|
delete: 'Delete',
|
||||||
|
recipient: 'Recipient',
|
||||||
|
rule: 'Rule',
|
||||||
|
detection: 'Detection',
|
||||||
|
message: 'Message',
|
||||||
|
sentAt: 'Sent At',
|
||||||
|
viewDetails: 'View Details',
|
||||||
|
unknownRule: 'Unknown Rule',
|
||||||
|
na: 'N/A',
|
||||||
|
minDetections: 'Min detections',
|
||||||
|
timeWindow: 'Time window',
|
||||||
|
cooldown: 'Cooldown',
|
||||||
|
minThreat: 'Min threat',
|
||||||
|
droneTypes: 'Drone types',
|
||||||
|
consumer: 'Consumer',
|
||||||
|
orlan: 'Orlan',
|
||||||
|
professional: 'Professional',
|
||||||
|
racing: 'Racing',
|
||||||
|
unknown: 'Unknown',
|
||||||
|
critical: 'Critical',
|
||||||
|
high: 'High',
|
||||||
|
medium: 'Medium',
|
||||||
|
low: 'Low'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
@@ -187,7 +224,30 @@ const translations = {
|
|||||||
apiKeys: 'API Keys',
|
apiKeys: 'API Keys',
|
||||||
dataRetention: 'Data Retention',
|
dataRetention: 'Data Retention',
|
||||||
exportData: 'Export Data',
|
exportData: 'Export Data',
|
||||||
deleteAccount: 'Delete Account'
|
deleteAccount: 'Delete Account',
|
||||||
|
loading: 'Loading settings...',
|
||||||
|
accessDenied: 'Access Denied',
|
||||||
|
accessDeniedMessage: "You don't have permission to access tenant settings.",
|
||||||
|
general: 'General',
|
||||||
|
branding: 'Branding',
|
||||||
|
authentication: 'Authentication',
|
||||||
|
users: 'Users',
|
||||||
|
generalInformation: 'General Information',
|
||||||
|
tenantName: 'Tenant Name',
|
||||||
|
tenantId: 'Tenant ID',
|
||||||
|
authenticationProvider: 'Authentication Provider',
|
||||||
|
companyName: 'Company Name',
|
||||||
|
logoUrl: 'Or enter logo URL manually',
|
||||||
|
primaryColor: 'Primary Color',
|
||||||
|
secondaryColor: 'Secondary Color',
|
||||||
|
save: 'Save',
|
||||||
|
saving: 'Saving...',
|
||||||
|
saveBranding: 'Save Branding',
|
||||||
|
saveChanges: 'Save Changes',
|
||||||
|
brandingUpdated: 'Branding updated successfully',
|
||||||
|
brandingUpdateFailed: 'Failed to update branding',
|
||||||
|
securityUpdated: 'Security settings updated successfully',
|
||||||
|
securityUpdateFailed: 'Failed to update security settings'
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
login: 'Login',
|
login: 'Login',
|
||||||
@@ -449,7 +509,44 @@ const translations = {
|
|||||||
lowPriority: 'Låg prioritet',
|
lowPriority: 'Låg prioritet',
|
||||||
active: 'Aktiv',
|
active: 'Aktiv',
|
||||||
inactive: 'Inaktiv',
|
inactive: 'Inaktiv',
|
||||||
triggered: 'Utlöst'
|
totalAlerts24h: 'Totalt larm (24h)',
|
||||||
|
sentSuccessfully: 'Skickade framgångsrikt',
|
||||||
|
failed: 'Misslyckades',
|
||||||
|
pending: 'Väntande',
|
||||||
|
alertRules: 'Larmregler',
|
||||||
|
alertLogs: 'Larmloggar',
|
||||||
|
noAlertRules: 'Inga larmregler',
|
||||||
|
noAlertRulesDescription: 'Kom igång genom att skapa din första larmregel.',
|
||||||
|
createAlertRule: 'Skapa larmregel',
|
||||||
|
noAlertLogs: 'Inga larmloggar',
|
||||||
|
noAlertLogsDescription: 'Larmloggar kommer att visas här när larm utlöses.',
|
||||||
|
name: 'Namn',
|
||||||
|
channels: 'Kanaler',
|
||||||
|
conditions: 'Villkor',
|
||||||
|
edit: 'Redigera',
|
||||||
|
delete: 'Ta bort',
|
||||||
|
recipient: 'Mottagare',
|
||||||
|
rule: 'Regel',
|
||||||
|
detection: 'Detektion',
|
||||||
|
message: 'Meddelande',
|
||||||
|
sentAt: 'Skickat vid',
|
||||||
|
viewDetails: 'Visa detaljer',
|
||||||
|
unknownRule: 'Okänd regel',
|
||||||
|
na: 'Ej tillämpligt',
|
||||||
|
minDetections: 'Min detekteringar',
|
||||||
|
timeWindow: 'Tidsfönster',
|
||||||
|
cooldown: 'Nedkylning',
|
||||||
|
minThreat: 'Min hot',
|
||||||
|
droneTypes: 'Drönartyper',
|
||||||
|
consumer: 'Konsument',
|
||||||
|
orlan: 'Orlan',
|
||||||
|
professional: 'Professionell',
|
||||||
|
racing: 'Racing',
|
||||||
|
unknown: 'Okänd',
|
||||||
|
critical: 'Kritisk',
|
||||||
|
high: 'Hög',
|
||||||
|
medium: 'Medel',
|
||||||
|
low: 'Låg'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
title: 'Inställningar',
|
title: 'Inställningar',
|
||||||
@@ -468,7 +565,30 @@ const translations = {
|
|||||||
apiKeys: 'API-nycklar',
|
apiKeys: 'API-nycklar',
|
||||||
dataRetention: 'Datalagring',
|
dataRetention: 'Datalagring',
|
||||||
exportData: 'Exportera data',
|
exportData: 'Exportera data',
|
||||||
deleteAccount: 'Ta bort konto'
|
deleteAccount: 'Ta bort konto',
|
||||||
|
loading: 'Laddar inställningar...',
|
||||||
|
accessDenied: 'Åtkomst nekad',
|
||||||
|
accessDeniedMessage: 'Du har inte behörighet att komma åt tenant-inställningar.',
|
||||||
|
general: 'Allmänt',
|
||||||
|
branding: 'Varumärke',
|
||||||
|
authentication: 'Autentisering',
|
||||||
|
users: 'Användare',
|
||||||
|
generalInformation: 'Allmän information',
|
||||||
|
tenantName: 'Tenant-namn',
|
||||||
|
tenantId: 'Tenant-ID',
|
||||||
|
authenticationProvider: 'Autentiseringsleverantör',
|
||||||
|
companyName: 'Företagsnamn',
|
||||||
|
logoUrl: 'Eller ange logotyp-URL manuellt',
|
||||||
|
primaryColor: 'Primärfärg',
|
||||||
|
secondaryColor: 'Sekundär färg',
|
||||||
|
save: 'Spara',
|
||||||
|
saving: 'Sparar...',
|
||||||
|
saveBranding: 'Spara varumärke',
|
||||||
|
saveChanges: 'Spara ändringar',
|
||||||
|
brandingUpdated: 'Varumärke uppdaterat framgångsrikt',
|
||||||
|
brandingUpdateFailed: 'Misslyckades med att uppdatera varumärke',
|
||||||
|
securityUpdated: 'Säkerhetsinställningar uppdaterade framgångsrikt',
|
||||||
|
securityUpdateFailed: 'Misslyckades med att uppdatera säkerhetsinställningar'
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
login: 'Logga in',
|
login: 'Logga in',
|
||||||
|
|||||||
Reference in New Issue
Block a user