mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
refactor: load RBAC users from API for audit log user filter
Instead of extracting users from log entries, fetch all users with RBAC roles via dedicated endpoint for complete user list.
This commit is contained in:
@@ -49,6 +49,16 @@ export interface UserRoleAssignment {
|
|||||||
user_telegram_id: number | null;
|
user_telegram_id: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AdminWithRoles {
|
||||||
|
user_id: number;
|
||||||
|
telegram_id: number | null;
|
||||||
|
username: string | null;
|
||||||
|
first_name: string | null;
|
||||||
|
last_name: string | null;
|
||||||
|
email: string | null;
|
||||||
|
role_names: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface AssignRolePayload {
|
export interface AssignRolePayload {
|
||||||
user_id: number;
|
user_id: number;
|
||||||
role_id: number;
|
role_id: number;
|
||||||
@@ -167,6 +177,13 @@ export const rbacApi = {
|
|||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// --- RBAC Users ---
|
||||||
|
|
||||||
|
getRbacUsers: async (): Promise<AdminWithRoles[]> => {
|
||||||
|
const response = await apiClient.get<AdminWithRoles[]>(`${BASE}/users`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
// --- Role Users ---
|
// --- Role Users ---
|
||||||
|
|
||||||
getRoleUsers: async (roleId: number): Promise<UserRoleAssignment[]> => {
|
getRoleUsers: async (roleId: number): Promise<UserRoleAssignment[]> => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, useMemo, useEffect } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -415,11 +415,6 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {
|
|||||||
|
|
||||||
// === Main Page ===
|
// === Main Page ===
|
||||||
|
|
||||||
interface KnownUser {
|
|
||||||
name: string;
|
|
||||||
email: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AdminAuditLog() {
|
export default function AdminAuditLog() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -440,9 +435,6 @@ export default function AdminAuditLog() {
|
|||||||
const [exporting, setExporting] = useState(false);
|
const [exporting, setExporting] = useState(false);
|
||||||
const [exportError, setExportError] = useState<string | null>(null);
|
const [exportError, setExportError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Known users accumulated from audit log entries
|
|
||||||
const [knownUsers, setKnownUsers] = useState<Map<number, KnownUser>>(new Map());
|
|
||||||
|
|
||||||
// Build query params
|
// Build query params
|
||||||
const queryParams = useMemo((): AuditLogFilters => {
|
const queryParams = useMemo((): AuditLogFilters => {
|
||||||
const params: AuditLogFilters = {
|
const params: AuditLogFilters = {
|
||||||
@@ -480,23 +472,12 @@ export default function AdminAuditLog() {
|
|||||||
refetchInterval: autoRefresh ? AUTO_REFRESH_INTERVAL : false,
|
refetchInterval: autoRefresh ? AUTO_REFRESH_INTERVAL : false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Accumulate unique users from audit log entries
|
// RBAC users for filter chips
|
||||||
useEffect(() => {
|
const { data: rbacUsers } = useQuery({
|
||||||
if (!data?.items?.length) return;
|
queryKey: ['rbac-users'],
|
||||||
setKnownUsers((prev) => {
|
queryFn: () => rbacApi.getRbacUsers(),
|
||||||
const next = new Map(prev);
|
staleTime: 5 * 60 * 1000,
|
||||||
let changed = false;
|
});
|
||||||
for (const entry of data.items) {
|
|
||||||
const name = entry.user_first_name || entry.user_email || `#${entry.user_id}`;
|
|
||||||
const existing = next.get(entry.user_id);
|
|
||||||
if (!existing || existing.name !== name || existing.email !== entry.user_email) {
|
|
||||||
next.set(entry.user_id, { name, email: entry.user_email });
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed ? next : prev;
|
|
||||||
});
|
|
||||||
}, [data?.items]);
|
|
||||||
|
|
||||||
const entries = data?.items ?? [];
|
const entries = data?.items ?? [];
|
||||||
const total = data?.total ?? 0;
|
const total = data?.total ?? 0;
|
||||||
@@ -669,23 +650,25 @@ export default function AdminAuditLog() {
|
|||||||
<div className="border-t border-dark-700 p-4">
|
<div className="border-t border-dark-700 p-4">
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{/* User filter */}
|
{/* User filter */}
|
||||||
{knownUsers.size > 0 && (
|
{rbacUsers && rbacUsers.length > 0 && (
|
||||||
<div className="sm:col-span-2 lg:col-span-3">
|
<div className="sm:col-span-2 lg:col-span-3">
|
||||||
<label className="mb-1 block text-sm font-medium text-dark-300">
|
<label className="mb-1 block text-sm font-medium text-dark-300">
|
||||||
{t('admin.auditLog.filters.user')}
|
{t('admin.auditLog.filters.user')}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{Array.from(knownUsers.entries()).map(([uid, user]) => {
|
{rbacUsers.map((ru) => {
|
||||||
const isSelected = filters.userId === String(uid);
|
const isSelected = filters.userId === String(ru.user_id);
|
||||||
|
const displayName =
|
||||||
|
ru.first_name || ru.email || ru.username || `#${ru.user_id}`;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={uid}
|
key={ru.user_id}
|
||||||
type="button"
|
type="button"
|
||||||
aria-pressed={isSelected}
|
aria-pressed={isSelected}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setFilters((prev) => ({
|
setFilters((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
userId: isSelected ? '' : String(uid),
|
userId: isSelected ? '' : String(ru.user_id),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
className={`flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-500 focus-visible:ring-offset-1 focus-visible:ring-offset-dark-900 ${
|
className={`flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-500 focus-visible:ring-offset-1 focus-visible:ring-offset-dark-900 ${
|
||||||
@@ -703,7 +686,7 @@ export default function AdminAuditLog() {
|
|||||||
>
|
>
|
||||||
{isSelected && '✓'}
|
{isSelected && '✓'}
|
||||||
</span>
|
</span>
|
||||||
<span>{user.name}</span>
|
<span>{displayName}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user