mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
fix: RBAC frontend type mismatches and translations
- Move RBAC translations from banSystem to admin namespace in all 4 locales - Fix AccessPolicy type: action (string) -> actions (string[]) - Fix AuditLogEntry type to match backend: add status, request_method, request_path, resource_type, user info - Fix AuditLogFilters: resource -> resource_type, add status filter - Handle null details safely in audit log expanded view - Fix policy form submission and display to use actions array
This commit is contained in:
@@ -118,27 +118,6 @@ const INITIAL_FILTERS: FiltersState = {
|
||||
|
||||
// === Utility functions ===
|
||||
|
||||
function getStatusFromEntry(entry: AuditLogEntry): string {
|
||||
const details = entry.details;
|
||||
if (typeof details.status === 'string') return details.status;
|
||||
if (typeof details.error === 'string') return 'error';
|
||||
if (details.denied === true) return 'denied';
|
||||
return 'success';
|
||||
}
|
||||
|
||||
function getMethodFromEntry(entry: AuditLogEntry): string | null {
|
||||
const details = entry.details;
|
||||
if (typeof details.method === 'string') return details.method.toUpperCase();
|
||||
return null;
|
||||
}
|
||||
|
||||
function getRequestPathFromEntry(entry: AuditLogEntry): string | null {
|
||||
const details = entry.details;
|
||||
if (typeof details.path === 'string') return details.path;
|
||||
if (typeof details.request_path === 'string') return details.request_path;
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatRelativeTime(
|
||||
dateString: string,
|
||||
t: (key: string, opts?: Record<string, unknown>) => string,
|
||||
@@ -216,9 +195,9 @@ interface LogEntryCardProps {
|
||||
|
||||
function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {
|
||||
const { t } = useTranslation();
|
||||
const status = getStatusFromEntry(entry);
|
||||
const method = getMethodFromEntry(entry);
|
||||
const requestPath = getRequestPathFromEntry(entry);
|
||||
const status = entry.status;
|
||||
const method = entry.request_method?.toUpperCase() ?? null;
|
||||
const requestPath = entry.request_path;
|
||||
const userName = entry.user_first_name || entry.user_email || t('admin.auditLog.unknownUser');
|
||||
|
||||
return (
|
||||
@@ -254,7 +233,7 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {
|
||||
|
||||
{/* Resource */}
|
||||
<div className="flex shrink-0 items-center gap-2 text-sm text-dark-400">
|
||||
<span>{entry.resource}</span>
|
||||
<span>{entry.resource_type}</span>
|
||||
{entry.resource_id && (
|
||||
<span className="rounded bg-dark-700 px-1.5 py-0.5 font-mono text-xs text-dark-300">
|
||||
#{entry.resource_id}
|
||||
@@ -323,7 +302,7 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {
|
||||
</div>
|
||||
|
||||
{/* Before/after diff */}
|
||||
{'before' in entry.details && entry.details.before != null && (
|
||||
{entry.details && 'before' in entry.details && entry.details.before != null && (
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-medium uppercase text-dark-500">
|
||||
{t('admin.auditLog.details.before')}
|
||||
@@ -334,7 +313,7 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{'after' in entry.details && entry.details.after != null && (
|
||||
{entry.details && 'after' in entry.details && entry.details.after != null && (
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-medium uppercase text-dark-500">
|
||||
{t('admin.auditLog.details.after')}
|
||||
@@ -347,14 +326,16 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {
|
||||
</div>
|
||||
|
||||
{/* Full details JSON */}
|
||||
<div className="mt-4">
|
||||
<p className="mb-1 text-xs font-medium uppercase text-dark-500">
|
||||
{t('admin.auditLog.details.fullDetails')}
|
||||
</p>
|
||||
<pre className="max-h-60 overflow-auto rounded-lg bg-dark-900 p-3 text-xs text-dark-300">
|
||||
{JSON.stringify(entry.details, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
{entry.details && (
|
||||
<div className="mt-4">
|
||||
<p className="mb-1 text-xs font-medium uppercase text-dark-500">
|
||||
{t('admin.auditLog.details.fullDetails')}
|
||||
</p>
|
||||
<pre className="max-h-60 overflow-auto rounded-lg bg-dark-900 p-3 text-xs text-dark-300">
|
||||
{JSON.stringify(entry.details, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -397,7 +378,10 @@ export default function AdminAuditLog() {
|
||||
params.action = appliedFilters.action.trim();
|
||||
}
|
||||
if (appliedFilters.resource) {
|
||||
params.resource = appliedFilters.resource;
|
||||
params.resource_type = appliedFilters.resource;
|
||||
}
|
||||
if (appliedFilters.status) {
|
||||
params.status = appliedFilters.status;
|
||||
}
|
||||
if (appliedFilters.dateFrom) {
|
||||
params.date_from = appliedFilters.dateFrom;
|
||||
@@ -467,7 +451,7 @@ export default function AdminAuditLog() {
|
||||
try {
|
||||
const exportParams: AuditLogFilters = {};
|
||||
if (appliedFilters.action.trim()) exportParams.action = appliedFilters.action.trim();
|
||||
if (appliedFilters.resource) exportParams.resource = appliedFilters.resource;
|
||||
if (appliedFilters.resource) exportParams.resource_type = appliedFilters.resource;
|
||||
if (appliedFilters.dateFrom) exportParams.date_from = appliedFilters.dateFrom;
|
||||
if (appliedFilters.dateTo) exportParams.date_to = appliedFilters.dateTo;
|
||||
|
||||
@@ -495,7 +479,7 @@ export default function AdminAuditLog() {
|
||||
// Filter status entries client-side (status is derived from details, not a backend field)
|
||||
const filteredEntries = useMemo(() => {
|
||||
if (!appliedFilters.status) return entries;
|
||||
return entries.filter((entry) => getStatusFromEntry(entry) === appliedFilters.status);
|
||||
return entries.filter((entry) => entry.status === appliedFilters.status);
|
||||
}, [entries, appliedFilters.status]);
|
||||
|
||||
const hasActiveFilters = useMemo(() => {
|
||||
|
||||
@@ -433,14 +433,7 @@ export default function AdminPolicies() {
|
||||
// Determine the role_id from conditions if present
|
||||
const roleId = typeof policy.conditions.role_id === 'number' ? policy.conditions.role_id : null;
|
||||
|
||||
// Parse actions — the API stores a single `action` string which may be comma-separated or
|
||||
// a single action. Normalize to an array.
|
||||
const actions = policy.action
|
||||
? policy.action
|
||||
.split(',')
|
||||
.map((a) => a.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const actions = Array.isArray(policy.actions) ? policy.actions : [];
|
||||
|
||||
setEditingPolicy(policy);
|
||||
setFormData({
|
||||
@@ -511,17 +504,16 @@ export default function AdminPolicies() {
|
||||
conditionsPayload.role_id = formData.role_id;
|
||||
}
|
||||
|
||||
const actionString = formData.actions.join(',');
|
||||
|
||||
if (editingPolicy) {
|
||||
const payload: UpdatePolicyPayload = {
|
||||
name: formData.name.trim(),
|
||||
description: formData.description.trim() || null,
|
||||
effect: formData.effect,
|
||||
resource: formData.resource,
|
||||
action: actionString,
|
||||
actions: formData.actions,
|
||||
conditions: conditionsPayload,
|
||||
priority: formData.priority,
|
||||
role_id: formData.role_id,
|
||||
};
|
||||
updateMutation.mutate({ id: editingPolicy.id, payload });
|
||||
} else {
|
||||
@@ -530,9 +522,10 @@ export default function AdminPolicies() {
|
||||
description: formData.description.trim() || null,
|
||||
effect: formData.effect,
|
||||
resource: formData.resource,
|
||||
action: actionString,
|
||||
actions: formData.actions,
|
||||
conditions: conditionsPayload,
|
||||
priority: formData.priority,
|
||||
role_id: formData.role_id,
|
||||
};
|
||||
createMutation.mutate(payload);
|
||||
}
|
||||
@@ -705,7 +698,9 @@ export default function AdminPolicies() {
|
||||
{policy.resource}
|
||||
</span>
|
||||
<span className="text-dark-500">:</span>
|
||||
<span className="font-mono text-xs text-dark-300">{policy.action}</span>
|
||||
<span className="font-mono text-xs text-dark-300">
|
||||
{(policy.actions ?? []).join(', ')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Info row */}
|
||||
|
||||
Reference in New Issue
Block a user