fix(user): align admin controls with permissions

This commit is contained in:
2026-09-08 07:30:04 +08:00
parent d39a88c445
commit 6dcc31a093
3 changed files with 58 additions and 40 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ export const PERM = {
PERMISSION_READ: 'auth:permission:read', PERMISSION_READ: 'auth:permission:read',
PERMISSION_MANAGE: 'auth:permission:manage', PERMISSION_MANAGE: 'auth:permission:manage',
USER_READ: 'admin:user:read', USER_READ: 'admin:user:read',
USER_MANAGE: 'admin:user:manage', USER_UPDATE: 'admin:user:update',
} as const } as const
export type Permission = (typeof PERM)[keyof typeof PERM] export type Permission = (typeof PERM)[keyof typeof PERM]
+1 -1
View File
@@ -107,7 +107,7 @@ export const NAV_GROUPS: NavGroup[] = [
{ {
label: '用户与系统', label: '用户与系统',
items: [ 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: '/roles', icon: Shield, permission: 'auth:role:read' },
{ label: '用户角色', to: '/admin-users', icon: Users, permission: 'auth:role:assign' }, { label: '用户角色', to: '/admin-users', icon: Users, permission: 'auth:role:assign' },
{ label: 'SID 管理', to: '/sid', icon: Hash, permission: 'admin:sid:manage' }, { label: 'SID 管理', to: '/sid', icon: Hash, permission: 'admin:sid:manage' },
+24 -6
View File
@@ -8,7 +8,7 @@ import type { ColumnDef } from '@tanstack/react-table'
import type { PlatformUser } from '@/api/types' import type { PlatformUser } from '@/api/types'
import { fetchUsers, updateUserStatus } from '@/api/modules/users' import { fetchUsers, updateUserStatus } from '@/api/modules/users'
import { fetchRoles, syncUserRoles } from '@/api/modules/roles' import { fetchRoles, syncUserRoles } from '@/api/modules/roles'
import { useAuthStore } from '@/auth/store' import { useCan, useAuthStore } from '@/auth/store'
import { handleApiError } from '@/lib/errors' import { handleApiError } from '@/lib/errors'
import { DataTable } from '@/components/data-table/data-table' import { DataTable } from '@/components/data-table/data-table'
import { PageHeader } from '@/components/page-header' import { PageHeader } from '@/components/page-header'
@@ -45,13 +45,18 @@ import { Switch } from '@/components/ui/switch'
import { UserDetailSheet } from '@/features/users/user-detail-sheet' import { UserDetailSheet } from '@/features/users/user-detail-sheet'
export const Route = createFileRoute('/_authed/users/')({ export const Route = createFileRoute('/_authed/users/')({
beforeLoad: () => requirePermission(PERM.ROLE_ASSIGN), beforeLoad: () => requirePermission(PERM.USER_READ),
component: UsersPage, component: UsersPage,
}) })
function UsersPage() { function UsersPage() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const myUid = useAuthStore((s) => s.user?.uid) 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 // Filter state
const [searchInput, setSearchInput] = useState('') const [searchInput, setSearchInput] = useState('')
@@ -68,7 +73,11 @@ function UsersPage() {
const [togglingUser, setTogglingUser] = useState<{ user: PlatformUser; targetStatus: boolean } | null>(null) const [togglingUser, setTogglingUser] = useState<{ user: PlatformUser; targetStatus: boolean } | null>(null)
// Available roles query for filter dropdown // 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 // Users listing query
const usersQuery = useQuery({ const usersQuery = useQuery({
@@ -172,11 +181,12 @@ function UsersPage() {
cell: ({ row }) => { cell: ({ row }) => {
const u = row.original const u = row.original
const isSelf = u.uid === myUid const isSelf = u.uid === myUid
const isProtectedSuperAdmin = u.roles.includes('super-admin') && !isSuperAdmin
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Switch <Switch
checked={u.status} checked={u.status}
disabled={isSelf} disabled={!canUpdateUsers || isSelf || isProtectedSuperAdmin}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
if (!checked) { if (!checked) {
setTogglingUser({ user: u, targetStatus: false }) setTogglingUser({ user: u, targetStatus: false })
@@ -215,6 +225,7 @@ function UsersPage() {
<Eye className="size-3.5 mr-1" /> <Eye className="size-3.5 mr-1" />
</Button> </Button>
{canManageRoles && (
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
@@ -224,6 +235,7 @@ function UsersPage() {
<Shield className="size-3.5 mr-1" /> <Shield className="size-3.5 mr-1" />
</Button> </Button>
)}
</div> </div>
) )
}, },
@@ -296,6 +308,7 @@ function UsersPage() {
</Select> </Select>
{/* Role Filter */} {/* Role Filter */}
{canReadRoles && (
<Select <Select
value={roleFilter} value={roleFilter}
onValueChange={(val) => { onValueChange={(val) => {
@@ -315,6 +328,7 @@ function UsersPage() {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
)}
</div> </div>
{/* Table */} {/* Table */}
@@ -382,12 +396,16 @@ function UsersPage() {
<UserDetailSheet <UserDetailSheet
uid={detailUid} uid={detailUid}
onClose={() => setDetailUid(null)} onClose={() => setDetailUid(null)}
onAssignRoles={() => { onAssignRoles={
canManageRoles
? () => {
const u = usersQuery.data?.data?.find((x) => x.uid === detailUid) const u = usersQuery.data?.data?.find((x) => x.uid === detailUid)
if (u) { if (u) {
setAssigningUser(u) setAssigningUser(u)
} }
}} }
: undefined
}
/> />
{/* Assign Roles Modal */} {/* Assign Roles Modal */}