import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'umi'; import { createColumnHelper, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { LucideClipboardList, LucideDot, LucideTrash2, LucideUserLock, LucideUserPlus, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { rsaPsw } from '@/utils'; import { TableEmpty } from '@/components/table-skeleton'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { LoadingButton } from '@/components/ui/loading-button'; import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Routes } from '@/routes'; import { LucideFilter, LucideSearch } from 'lucide-react'; import useChangePasswordForm from './forms/change-password-form'; import useCreateUserForm from './forms/user-form'; import { createUser, deleteUser, listRoles, listUsers, updateUserPassword, updateUserRole, updateUserStatus, } from '@/services/admin-service'; import { createColumnFilterFn, createFuzzySearchFn, EMPTY_DATA, IS_ENTERPRISE, parseBooleanish, } from './utils'; import EnterpriseFeature from './components/enterprise-feature'; const columnHelper = createColumnHelper(); const globalFilterFn = createFuzzySearchFn([ 'email', 'nickname', ]); const STATUS_FILTER_OPTIONS = [ { value: '', label: 'admin.all' }, { value: 'active', label: 'admin.active' }, { value: 'inactive', label: 'admin.inactive' }, ]; function AdminUserManagement() { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [passwordModalOpen, setPasswordModalOpen] = useState(false); const [createUserModalOpen, setCreateUserModalOpen] = useState(false); const [userToMakeAction, setUserToMakeAction] = useState(null); const changePasswordForm = useChangePasswordForm(); const createUserForm = useCreateUserForm(); const { data: roleList } = useQuery({ queryKey: ['admin/listRoles'], queryFn: async () => (await listRoles()).data.data.roles, enabled: IS_ENTERPRISE, retry: false, }); const { data: usersList } = useQuery({ queryKey: ['admin/listUsers'], queryFn: async () => (await listUsers()).data.data, retry: false, }); // Delete user mutation const deleteUserMutation = useMutation({ mutationFn: deleteUser, onSuccess: () => { // message.success(t('admin.userDeletedSuccessfully')); queryClient.invalidateQueries({ queryKey: ['admin/listUsers'] }); setDeleteModalOpen(false); setUserToMakeAction(null); }, retry: false, }); // Change password mutation const changePasswordMutation = useMutation({ mutationFn: ({ email, password }: { email: string; password: string }) => updateUserPassword(email, rsaPsw(password) as string), onSuccess: () => { // message.success(t('admin.passwordChangedSuccessfully')); setPasswordModalOpen(false); setUserToMakeAction(null); }, retry: false, }); // Update user role mutation const updateUserRoleMutation = useMutation({ mutationFn: ({ email, role }: { email: string; role: string }) => updateUserRole(email, role), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin/listUsers'] }); }, retry: false, }); // Create user mutation const createUserMutation = useMutation({ mutationFn: async ({ email, password, role, }: { email: string; password: string; role?: string; }) => { await createUser(email, rsaPsw(password) as string); if (IS_ENTERPRISE && role) { await updateUserRoleMutation.mutateAsync({ email, role }); } }, onSuccess: () => { // message.success(t('admin.userCreatedSuccessfully')); queryClient.invalidateQueries({ queryKey: ['admin/listUsers'] }); setCreateUserModalOpen(false); createUserForm.form.reset(); }, retry: false, }); // Update user status mutation const updateUserStatusMutation = useMutation({ mutationFn: (data: { email: string; isActive: boolean }) => updateUserStatus(data.email, data.isActive ? 'on' : 'off'), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin/listUsers'] }); }, retry: false, }); const columnDefs = useMemo( () => [ columnHelper.accessor('email', { header: t('admin.email'), }), columnHelper.accessor('nickname', { header: t('admin.nickname'), }), ...(IS_ENTERPRISE ? [ columnHelper.accessor('role', { header: t('admin.role'), cell: ({ row, cell }) => ( ), filterFn: createColumnFilterFn( (row, id, filterValue) => row.getValue(id) === filterValue, { autoRemove: (v) => !v, }, ), }), ] : []), columnHelper.display({ id: 'enable', header: t('admin.enable'), cell: ({ row }) => ( { updateUserStatusMutation.mutate({ email: row.original.email, isActive: checked, }); }} disabled={updateUserStatusMutation.isPending} /> ), }), columnHelper.accessor('is_active', { header: t('admin.status'), cell: ({ cell }) => ( {t( parseBooleanish(cell.getValue()) ? 'admin.active' : 'admin.inactive', )} ), filterFn: createColumnFilterFn( (row, id, filterValue) => row.getValue(id) === filterValue, { autoRemove: (v) => !v, resolveFilterValue: (v) => v ? (v === 'active' ? '1' : '0') : null, }, ), }), columnHelper.display({ id: 'actions', header: t('admin.actions'), cell: ({ row }) => (
), }), ], [t, updateUserRoleMutation, roleList, updateUserStatusMutation, navigate], ); const table = useReactTable({ data: usersList ?? EMPTY_DATA, columns: columnDefs, globalFilterFn, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), }); return ( <> {t('admin.userManagement')}
{() => (
{t('admin.role')}
table.getColumn('role')?.setFilterValue(value) } > {roleList?.map(({ id, role_name }) => ( ))}
)}
{t('admin.status')}
table.getColumn('is_active')?.setFilterValue(value) } > {STATUS_FILTER_OPTIONS.map(({ label, value }) => ( ))}
table.setGlobalFilter(e.target.value)} />
{() => } {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} ))} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ))} )) ) : ( )}
{ table.setPagination({ pageIndex: page - 1, pageSize, }); }} />
{/* Delete Confirmation Modal */} {t('admin.deleteUser')}
{t('admin.deleteUserConfirmation')}
{userToMakeAction?.email}
userToMakeAction && deleteUserMutation.mutate(userToMakeAction?.email) } disabled={deleteUserMutation.isPending} loading={deleteUserMutation.isPending} > {t('admin.delete')}
{/* Change Password Modal */} { if (!passwordModalOpen) { changePasswordForm.form.reset(); } }} > {t('admin.changePassword')}
{ if (userToMakeAction) { changePasswordMutation.mutate({ email: userToMakeAction.email, password: newPassword, }); } }} />
{t('admin.changePassword')}
{/* Create User Modal */} { setCreateUserModalOpen(false); createUserForm.form.reset(); }} > {t('admin.createNewUser')}
{t('admin.confirm')}
); } export default AdminUserManagement;