feat: improve audit log - translate actions, fix resource filter, show request body

This commit is contained in:
Fringg
2026-02-25 05:11:57 +03:00
parent a1a8dc2203
commit 5d0e3539e2
5 changed files with 66 additions and 9 deletions

View File

@@ -2844,6 +2844,7 @@
"timestamp": "Timestamp", "timestamp": "Timestamp",
"before": "Before", "before": "Before",
"after": "After", "after": "After",
"requestBody": "Request Body",
"fullDetails": "Full Details" "fullDetails": "Full Details"
}, },
"time": { "time": {

View File

@@ -2629,6 +2629,7 @@
"timestamp": "زمان", "timestamp": "زمان",
"before": "قبل", "before": "قبل",
"after": "بعد", "after": "بعد",
"requestBody": "داده‌های درخواست",
"fullDetails": "جزئیات کامل" "fullDetails": "جزئیات کامل"
}, },
"time": { "time": {

View File

@@ -3396,6 +3396,7 @@
"timestamp": "Время", "timestamp": "Время",
"before": "До", "before": "До",
"after": "После", "after": "После",
"requestBody": "Данные запроса",
"fullDetails": "Полные данные" "fullDetails": "Полные данные"
}, },
"time": { "time": {

View File

@@ -2628,6 +2628,7 @@
"timestamp": "时间戳", "timestamp": "时间戳",
"before": "之前", "before": "之前",
"after": "之后", "after": "之后",
"requestBody": "请求数据",
"fullDetails": "完整详情" "fullDetails": "完整详情"
}, },
"time": { "time": {

View File

@@ -83,15 +83,30 @@ const SearchIcon = () => (
const RESOURCE_TYPES = [ const RESOURCE_TYPES = [
'users', 'users',
'tickets', 'tickets',
'roles', 'stats',
'policies',
'settings',
'broadcasts', 'broadcasts',
'tariffs', 'tariffs',
'servers',
'payments',
'campaigns',
'promocodes', 'promocodes',
'promo_groups',
'promo_offers',
'campaigns',
'partners',
'withdrawals',
'payments',
'payment_methods',
'servers',
'remnawave',
'traffic',
'settings',
'roles',
'audit_log',
'channels',
'ban_system',
'wheel',
'apps',
'email_templates',
'pinned_messages',
'updates',
] as const; ] as const;
const STATUS_OPTIONS = ['success', 'denied', 'error'] as const; const STATUS_OPTIONS = ['success', 'denied', 'error'] as const;
@@ -118,6 +133,24 @@ const INITIAL_FILTERS: FiltersState = {
// === Utility functions === // === Utility functions ===
function translateAction(
action: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
t: any,
): string {
return action
.split(',')
.map((perm: string) => {
const trimmed = perm.trim();
const [section, act] = trimmed.split(':', 2);
if (!section || !act) return trimmed;
const sectionLabel = t(`admin.roles.form.permissionSections.${section}`, section) as string;
const actionLabel = t(`admin.roles.form.permissionActions.${act}`, act) as string;
return `${sectionLabel}: ${actionLabel}`;
})
.join(', ');
}
function formatRelativeTime( function formatRelativeTime(
dateString: string, dateString: string,
t: (key: string, opts?: Record<string, unknown>) => string, t: (key: string, opts?: Record<string, unknown>) => string,
@@ -228,12 +261,18 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {
status={status} status={status}
label={t(`admin.auditLog.status.${status}`, { defaultValue: status })} label={t(`admin.auditLog.status.${status}`, { defaultValue: status })}
/> />
<span className="truncate text-sm font-medium text-dark-100">{entry.action}</span> <span className="truncate text-sm font-medium text-dark-100">
{translateAction(entry.action, t)}
</span>
</div> </div>
{/* Resource */} {/* Resource */}
<div className="flex shrink-0 items-center gap-2 text-sm text-dark-400"> <div className="flex shrink-0 items-center gap-2 text-sm text-dark-400">
<span>{entry.resource_type}</span> <span>
{entry.resource_type
? t(`admin.roles.form.permissionSections.${entry.resource_type}`, entry.resource_type)
: null}
</span>
{entry.resource_id && ( {entry.resource_id && (
<span className="rounded bg-dark-700 px-1.5 py-0.5 font-mono text-xs text-dark-300"> <span className="rounded bg-dark-700 px-1.5 py-0.5 font-mono text-xs text-dark-300">
#{entry.resource_id} #{entry.resource_id}
@@ -323,6 +362,20 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {
</pre> </pre>
</div> </div>
)} )}
{/* Request body */}
{entry.details &&
'request_body' in entry.details &&
entry.details.request_body != null && (
<div className="sm:col-span-2">
<p className="mb-1 text-xs font-medium uppercase text-dark-500">
{t('admin.auditLog.details.requestBody')}
</p>
<pre className="max-h-60 overflow-auto rounded-lg bg-dark-900 p-2 text-xs text-dark-300">
{JSON.stringify(entry.details.request_body, null, 2)}
</pre>
</div>
)}
</div> </div>
{/* Full details JSON */} {/* Full details JSON */}
@@ -622,7 +675,7 @@ export default function AdminAuditLog() {
<option value="">{t('admin.auditLog.filters.allResources')}</option> <option value="">{t('admin.auditLog.filters.allResources')}</option>
{RESOURCE_TYPES.map((type) => ( {RESOURCE_TYPES.map((type) => (
<option key={type} value={type}> <option key={type} value={type}>
{t(`admin.auditLog.resourceTypes.${type}`, { defaultValue: type })} {t(`admin.roles.form.permissionSections.${type}`, type)}
</option> </option>
))} ))}
</select> </select>