Implement role and user management enhancements
- Added MaxIpLimit to roles, allowing for the configuration of simultaneous IP limits for users. - Updated role creation and update commands to include MaxIpLimit, ensuring proper handling in the application logic. - Enhanced user management by introducing a DELETE endpoint for user accounts, with appropriate checks to prevent self-deletion. - Updated documentation to reflect changes in role and user management, clarifying the new IP limit functionality and user deletion process. - Adjusted related tests to cover new functionality and ensure robust validation of role and user management features.
This commit is contained in:
@@ -23,6 +23,7 @@ export function RoleFormDialog({
|
||||
const queryClient = useQueryClient()
|
||||
const [name, setName] = useState(role?.name ?? '')
|
||||
const [maxConfigs, setMaxConfigs] = useState(String(role?.maxConfigs ?? 3))
|
||||
const [maxIpLimit, setMaxIpLimit] = useState(String(role?.maxIpLimit ?? 2))
|
||||
const [internalOpen, setInternalOpen] = useState(false)
|
||||
|
||||
const isControlled = open !== undefined
|
||||
@@ -30,13 +31,17 @@ export function RoleFormDialog({
|
||||
const setDialogOpen = isControlled ? onOpenChange! : setInternalOpen
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () => (role ? updateRole(role.id, Number(maxConfigs)) : createRole(name.trim(), Number(maxConfigs))),
|
||||
mutationFn: () =>
|
||||
role
|
||||
? updateRole(role.id, Number(maxConfigs), Number(maxIpLimit))
|
||||
: createRole(name.trim(), Number(maxConfigs), Number(maxIpLimit)),
|
||||
onSuccess: async () => {
|
||||
toast.success(role ? t('admin.roles.updated') : t('admin.roles.created'))
|
||||
await queryClient.invalidateQueries({ queryKey: ['admin-roles'] })
|
||||
setDialogOpen(false)
|
||||
setName('')
|
||||
setMaxConfigs('3')
|
||||
setMaxIpLimit('2')
|
||||
},
|
||||
onError: () => toast.error(t('auth.genericError')),
|
||||
})
|
||||
@@ -70,6 +75,11 @@ export function RoleFormDialog({
|
||||
<Input id="maxConfigs" type="number" value={maxConfigs} onChange={(e) => setMaxConfigs(e.target.value)} />
|
||||
<p className="text-xs text-muted-foreground">{t('admin.roles.maxConfigsHint')}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label htmlFor="maxIpLimit">{t('admin.roles.maxIpLimit')}</Label>
|
||||
<Input id="maxIpLimit" type="number" value={maxIpLimit} onChange={(e) => setMaxIpLimit(e.target.value)} />
|
||||
<p className="text-xs text-muted-foreground">{t('admin.roles.maxIpLimitHint')}</p>
|
||||
</div>
|
||||
<Button type="submit" disabled={mutation.isPending || (!role && !name.trim())}>
|
||||
{role ? t('admin.roles.save') : t('admin.roles.create')}
|
||||
</Button>
|
||||
|
||||
@@ -5,12 +5,12 @@ export function listRoles() {
|
||||
return apiRequest<RoleDto[]>('/admin/roles')
|
||||
}
|
||||
|
||||
export function createRole(name: string, maxConfigs: number) {
|
||||
return apiRequest<RoleDto>('/admin/roles', { method: 'POST', body: { name, maxConfigs } })
|
||||
export function createRole(name: string, maxConfigs: number, maxIpLimit: number) {
|
||||
return apiRequest<RoleDto>('/admin/roles', { method: 'POST', body: { name, maxConfigs, maxIpLimit } })
|
||||
}
|
||||
|
||||
export function updateRole(id: string, maxConfigs: number) {
|
||||
return apiRequest<RoleDto>(`/admin/roles/${id}`, { method: 'PUT', body: { maxConfigs } })
|
||||
export function updateRole(id: string, maxConfigs: number, maxIpLimit: number) {
|
||||
return apiRequest<RoleDto>(`/admin/roles/${id}`, { method: 'PUT', body: { maxConfigs, maxIpLimit } })
|
||||
}
|
||||
|
||||
export function deleteRole(id: string) {
|
||||
|
||||
@@ -9,10 +9,12 @@ import { Label } from '@/shared/ui/label'
|
||||
import { Badge } from '@/shared/ui/badge'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import { listRoles } from '@/features/admin/roles/api'
|
||||
import { useAuthStore } from '@/features/auth/store'
|
||||
import type { UserSummaryDto } from '@/shared/api/types'
|
||||
import {
|
||||
blockUser,
|
||||
changeUserRole,
|
||||
deleteUser,
|
||||
forceRevokeConfig,
|
||||
getUserConfigs,
|
||||
resetUserPassword,
|
||||
@@ -23,6 +25,7 @@ export function UserManageDialog({ user, open, onOpenChange }: { user: UserSumma
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const [newPassword, setNewPassword] = useState('')
|
||||
const currentUserId = useAuthStore((state) => state.user?.id)
|
||||
|
||||
const rolesQuery = useQuery({ queryKey: ['admin-roles'], queryFn: listRoles, enabled: open })
|
||||
const configsQuery = useQuery({ queryKey: ['admin-user-configs', user.id], queryFn: () => getUserConfigs(user.id), enabled: open })
|
||||
@@ -65,6 +68,16 @@ export function UserManageDialog({ user, open, onOpenChange }: { user: UserSumma
|
||||
onError: () => toast.error(t('auth.genericError')),
|
||||
})
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: () => deleteUser(user.id),
|
||||
onSuccess: async () => {
|
||||
toast.success(t('admin.users.deleted'))
|
||||
onOpenChange(false)
|
||||
await invalidateUsers()
|
||||
},
|
||||
onError: () => toast.error(t('auth.genericError')),
|
||||
})
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-lg">
|
||||
@@ -147,6 +160,18 @@ export function UserManageDialog({ user, open, onOpenChange }: { user: UserSumma
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{user.id !== currentUserId && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={deleteMutation.isPending}
|
||||
onClick={() => {
|
||||
if (confirm(t('admin.users.confirmDelete'))) deleteMutation.mutate()
|
||||
}}
|
||||
>
|
||||
{t('admin.users.delete')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -30,3 +30,7 @@ export function forceRevokeConfig(id: string) {
|
||||
export function changeUserRole(id: string, roleId: string) {
|
||||
return apiRequest<void>(`/admin/users/${id}/role`, { method: 'PATCH', body: { roleId } })
|
||||
}
|
||||
|
||||
export function deleteUser(id: string) {
|
||||
return apiRequest<void>(`/admin/users/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user