| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- import { redirect } from 'next/navigation';
- import { db } from '@/db';
- import { groups, permissions, rolePermissions, roles, users } from '@/db/schema/auth';
- import { aclRules, resources } from '@/db/schema/resource';
- import { requireAdmin } from '@/lib/auth/admin';
- import { Badge } from '@/components/ui/badge';
- import { Button } from '@/components/ui/button';
- import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
- import { Dialog } from '@/components/ui/dialog';
- import { Input } from '@/components/ui/input';
- import { Label } from '@/components/ui/label';
- import { Select } from '@/components/ui/select';
- import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
- import { Tabs } from '@/components/ui/tabs';
- import {
- assignPermissionToRoleAction,
- createAclRuleAction,
- createPermissionAction,
- createResourceAction,
- deleteAclRuleAction,
- removePermissionFromRoleAction,
- } from './actions';
- export default async function PermissionsAdminPage() {
- try { await requireAdmin(); } catch { redirect('/login'); }
- const [roleRows, permissionRows, rolePermissionRows, resourceRows, aclRows, userRows, groupRows] = await Promise.all([
- db.select().from(roles).orderBy(roles.name),
- db.select().from(permissions).orderBy(permissions.resourceType, permissions.action),
- db.select().from(rolePermissions),
- db.select().from(resources).orderBy(resources.path),
- db.select().from(aclRules).orderBy(aclRules.createdAt),
- db.select().from(users).orderBy(users.email),
- db.select().from(groups).orderBy(groups.name),
- ]);
- const permissionById = new Map(permissionRows.map((permission) => [permission.id, permission]));
- const resourceById = new Map(resourceRows.map((resource) => [resource.id, resource]));
- const userById = new Map(userRows.map((user) => [user.id, user.email]));
- const groupById = new Map(groupRows.map((group) => [group.id, group.name]));
- const actions = (
- <div className="flex flex-wrap gap-2">
- <Dialog title="创建权限" trigger="创建权限">
- <form className="grid gap-4" action={createPermissionAction}>
- <Label>动作<Input name="action" placeholder="read" required /></Label>
- <Label>资源类型<Input name="resourceType" placeholder="video" required /></Label>
- <Button type="submit">创建</Button>
- </form>
- </Dialog>
- <Dialog title="角色授权" trigger="角色授权">
- <form className="grid gap-4" action={assignPermissionToRoleAction}>
- <Label>角色<Select name="roleId">{roleRows.map((role) => <option key={role.id} value={role.id}>{role.name}</option>)}</Select></Label>
- <Label>权限<Select name="permissionId">{permissionRows.map((permission) => <option key={permission.id} value={permission.id}>{permission.resourceType}:{permission.action}</option>)}</Select></Label>
- <Button type="submit">授权</Button>
- </form>
- </Dialog>
- <Dialog title="登记资源" trigger="登记资源">
- <form className="grid gap-4" action={createResourceAction}>
- <Label>名称<Input name="name" required /></Label>
- <Label>路径<Input name="path" placeholder="/media/example.mp4" required /></Label>
- <Label>类型<Input name="type" placeholder="file" required /></Label>
- <Button type="submit">登记</Button>
- </form>
- </Dialog>
- <Dialog title="添加 ACL" trigger="添加 ACL">
- <form className="grid gap-4" action={createAclRuleAction}>
- <Label>资源<Select name="resourceId">{resourceRows.map((resource) => <option key={resource.id} value={resource.id}>{resource.path}</option>)}</Select></Label>
- <Label>主体类型<Select name="subjectType"><option value="user">user</option><option value="group">group</option></Select></Label>
- <Label>主体<Select name="subjectId">{userRows.map((user) => <option key={user.id} value={user.id}>user:{user.email}</option>)}{groupRows.map((group) => <option key={group.id} value={group.id}>group:{group.name}</option>)}</Select></Label>
- <Label>权限类型<Select name="permissionType"><option value="allow">allow</option><option value="deny">deny</option></Select></Label>
- <Label>动作<Input name="action" placeholder="read" required /></Label>
- <Button type="submit">添加</Button>
- </form>
- </Dialog>
- </div>
- );
- const rolePermissionsContent = (
- <Card>
- <CardHeader><CardTitle>角色权限</CardTitle></CardHeader>
- <CardContent>
- <Table>
- <TableHeader>
- <TableRow><TableHead>角色</TableHead><TableHead>权限</TableHead></TableRow>
- </TableHeader>
- <TableBody>
- {roleRows.map((role) => (
- <TableRow key={role.id}>
- <TableCell className="font-medium">{role.name}</TableCell>
- <TableCell>
- <div className="flex flex-wrap gap-2">
- {rolePermissionRows.filter((row) => row.roleId === role.id).map((row) => {
- const permission = row.permissionId ? permissionById.get(row.permissionId) : null;
- if (!permission) return null;
- return (
- <form action={removePermissionFromRoleAction} key={permission.id}>
- <input type="hidden" name="roleId" value={role.id} />
- <input type="hidden" name="permissionId" value={permission.id} />
- <button className="rounded-full border px-3 py-1 text-xs" type="submit">{permission.resourceType}:{permission.action} x</button>
- </form>
- );
- })}
- </div>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </CardContent>
- </Card>
- );
- const aclContent = (
- <Card>
- <CardHeader><CardTitle>ACL 规则</CardTitle></CardHeader>
- <CardContent>
- <Table>
- <TableHeader>
- <TableRow><TableHead>资源</TableHead><TableHead>主体</TableHead><TableHead>规则</TableHead><TableHead>操作</TableHead></TableRow>
- </TableHeader>
- <TableBody>
- {aclRows.map((rule) => (
- <TableRow key={rule.id}>
- <TableCell className="[overflow-wrap:anywhere]">{resourceById.get(rule.resourceId)?.path || rule.resourceId}</TableCell>
- <TableCell className="[overflow-wrap:anywhere]">{rule.subjectType}:{rule.subjectType === 'user' ? userById.get(rule.subjectId) : groupById.get(rule.subjectId)}</TableCell>
- <TableCell><Badge>{rule.permissionType}:{rule.action}</Badge></TableCell>
- <TableCell>
- <form action={deleteAclRuleAction}>
- <input type="hidden" name="aclRuleId" value={rule.id} />
- <Button variant="outline" size="sm" type="submit">删除</Button>
- </form>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </CardContent>
- </Card>
- );
- return (
- <main className="min-h-screen p-8 max-md:p-5">
- <section className="mx-auto max-w-6xl">
- <div className="mb-6 flex items-end justify-between gap-5 max-md:items-start">
- <div>
- <p className="mb-2 text-xs uppercase text-[hsl(var(--muted-foreground))]">EKB Admin</p>
- <h1 className="text-4xl font-semibold max-md:text-3xl">权限管理</h1>
- </div>
- {actions}
- </div>
- <Tabs
- defaultValue="roles"
- items={[
- { value: 'roles', label: '角色权限', content: rolePermissionsContent },
- { value: 'acl', label: 'ACL 规则', content: aclContent },
- ]}
- />
- </section>
- </main>
- );
- }
|