mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
feat: improve audit log - translate actions, fix resource filter, show request body
This commit is contained in:
@@ -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": {
|
||||||
|
|||||||
@@ -2629,6 +2629,7 @@
|
|||||||
"timestamp": "زمان",
|
"timestamp": "زمان",
|
||||||
"before": "قبل",
|
"before": "قبل",
|
||||||
"after": "بعد",
|
"after": "بعد",
|
||||||
|
"requestBody": "دادههای درخواست",
|
||||||
"fullDetails": "جزئیات کامل"
|
"fullDetails": "جزئیات کامل"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
|
|||||||
@@ -3396,6 +3396,7 @@
|
|||||||
"timestamp": "Время",
|
"timestamp": "Время",
|
||||||
"before": "До",
|
"before": "До",
|
||||||
"after": "После",
|
"after": "После",
|
||||||
|
"requestBody": "Данные запроса",
|
||||||
"fullDetails": "Полные данные"
|
"fullDetails": "Полные данные"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
|
|||||||
@@ -2628,6 +2628,7 @@
|
|||||||
"timestamp": "时间戳",
|
"timestamp": "时间戳",
|
||||||
"before": "之前",
|
"before": "之前",
|
||||||
"after": "之后",
|
"after": "之后",
|
||||||
|
"requestBody": "请求数据",
|
||||||
"fullDetails": "完整详情"
|
"fullDetails": "完整详情"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user