fix(user): align admin controls with permissions
This commit is contained in:
@@ -46,7 +46,7 @@ export const PERM = {
|
||||
PERMISSION_READ: 'auth:permission:read',
|
||||
PERMISSION_MANAGE: 'auth:permission:manage',
|
||||
USER_READ: 'admin:user:read',
|
||||
USER_MANAGE: 'admin:user:manage',
|
||||
USER_UPDATE: 'admin:user:update',
|
||||
} as const
|
||||
|
||||
export type Permission = (typeof PERM)[keyof typeof PERM]
|
||||
|
||||
@@ -107,7 +107,7 @@ export const NAV_GROUPS: NavGroup[] = [
|
||||
{
|
||||
label: '用户与系统',
|
||||
items: [
|
||||
{ label: '用户列表', to: '/users', icon: Users, permission: 'auth:role:assign' },
|
||||
{ label: '用户列表', to: '/users', icon: Users, permission: 'admin:user:read' },
|
||||
{ label: '角色管理', to: '/roles', icon: Shield, permission: 'auth:role:read' },
|
||||
{ label: '用户角色', to: '/admin-users', icon: Users, permission: 'auth:role:assign' },
|
||||
{ label: 'SID 管理', to: '/sid', icon: Hash, permission: 'admin:sid:manage' },
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { ColumnDef } from '@tanstack/react-table'
|
||||
import type { PlatformUser } from '@/api/types'
|
||||
import { fetchUsers, updateUserStatus } from '@/api/modules/users'
|
||||
import { fetchRoles, syncUserRoles } from '@/api/modules/roles'
|
||||
import { useAuthStore } from '@/auth/store'
|
||||
import { useCan, useAuthStore } from '@/auth/store'
|
||||
import { handleApiError } from '@/lib/errors'
|
||||
import { DataTable } from '@/components/data-table/data-table'
|
||||
import { PageHeader } from '@/components/page-header'
|
||||
@@ -45,13 +45,18 @@ import { Switch } from '@/components/ui/switch'
|
||||
import { UserDetailSheet } from '@/features/users/user-detail-sheet'
|
||||
|
||||
export const Route = createFileRoute('/_authed/users/')({
|
||||
beforeLoad: () => requirePermission(PERM.ROLE_ASSIGN),
|
||||
beforeLoad: () => requirePermission(PERM.USER_READ),
|
||||
component: UsersPage,
|
||||
})
|
||||
|
||||
function UsersPage() {
|
||||
const queryClient = useQueryClient()
|
||||
const myUid = useAuthStore((s) => s.user?.uid)
|
||||
const isSuperAdmin = useAuthStore((s) => s.roles.includes('super-admin'))
|
||||
const canUpdateUsers = useCan(PERM.USER_UPDATE)
|
||||
const canReadRoles = useCan(PERM.ROLE_READ)
|
||||
const canAssignRoles = useCan(PERM.ROLE_ASSIGN)
|
||||
const canManageRoles = canReadRoles && canAssignRoles
|
||||
|
||||
// Filter state
|
||||
const [searchInput, setSearchInput] = useState('')
|
||||
@@ -68,7 +73,11 @@ function UsersPage() {
|
||||
const [togglingUser, setTogglingUser] = useState<{ user: PlatformUser; targetStatus: boolean } | null>(null)
|
||||
|
||||
// Available roles query for filter dropdown
|
||||
const rolesQuery = useQuery({ queryKey: ['roles'], queryFn: fetchRoles })
|
||||
const rolesQuery = useQuery({
|
||||
queryKey: ['roles'],
|
||||
queryFn: fetchRoles,
|
||||
enabled: canReadRoles,
|
||||
})
|
||||
|
||||
// Users listing query
|
||||
const usersQuery = useQuery({
|
||||
@@ -172,11 +181,12 @@ function UsersPage() {
|
||||
cell: ({ row }) => {
|
||||
const u = row.original
|
||||
const isSelf = u.uid === myUid
|
||||
const isProtectedSuperAdmin = u.roles.includes('super-admin') && !isSuperAdmin
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={u.status}
|
||||
disabled={isSelf}
|
||||
disabled={!canUpdateUsers || isSelf || isProtectedSuperAdmin}
|
||||
onCheckedChange={(checked) => {
|
||||
if (!checked) {
|
||||
setTogglingUser({ user: u, targetStatus: false })
|
||||
@@ -215,15 +225,17 @@ function UsersPage() {
|
||||
<Eye className="size-3.5 mr-1" />
|
||||
详情
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setAssigningUser(u)}
|
||||
title="分配角色"
|
||||
>
|
||||
<Shield className="size-3.5 mr-1" />
|
||||
角色
|
||||
</Button>
|
||||
{canManageRoles && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setAssigningUser(u)}
|
||||
title="分配角色"
|
||||
>
|
||||
<Shield className="size-3.5 mr-1" />
|
||||
角色
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@@ -296,25 +308,27 @@ function UsersPage() {
|
||||
</Select>
|
||||
|
||||
{/* Role Filter */}
|
||||
<Select
|
||||
value={roleFilter}
|
||||
onValueChange={(val) => {
|
||||
setRoleFilter(val)
|
||||
setPage(1)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="sm" className="w-[130px]">
|
||||
<SelectValue placeholder="筛选角色" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部角色</SelectItem>
|
||||
{(rolesQuery.data?.data ?? []).map((r) => (
|
||||
<SelectItem key={r.name} value={r.name}>
|
||||
{r.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{canReadRoles && (
|
||||
<Select
|
||||
value={roleFilter}
|
||||
onValueChange={(val) => {
|
||||
setRoleFilter(val)
|
||||
setPage(1)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="sm" className="w-[130px]">
|
||||
<SelectValue placeholder="筛选角色" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部角色</SelectItem>
|
||||
{(rolesQuery.data?.data ?? []).map((r) => (
|
||||
<SelectItem key={r.name} value={r.name}>
|
||||
{r.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
@@ -382,12 +396,16 @@ function UsersPage() {
|
||||
<UserDetailSheet
|
||||
uid={detailUid}
|
||||
onClose={() => setDetailUid(null)}
|
||||
onAssignRoles={() => {
|
||||
const u = usersQuery.data?.data?.find((x) => x.uid === detailUid)
|
||||
if (u) {
|
||||
setAssigningUser(u)
|
||||
}
|
||||
}}
|
||||
onAssignRoles={
|
||||
canManageRoles
|
||||
? () => {
|
||||
const u = usersQuery.data?.data?.find((x) => x.uid === detailUid)
|
||||
if (u) {
|
||||
setAssigningUser(u)
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Assign Roles Modal */}
|
||||
|
||||
Reference in New Issue
Block a user