diff options
| author | MohamedBassem <me@mbassem.com> | 2024-03-02 12:48:26 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-03-02 12:48:26 +0000 |
| commit | 64f75a0fb010d4a12086b839cc9d80ed011aa2b3 (patch) | |
| tree | 00282d3081f87407b67fd53135b75f2cdb2a40ba /packages/web/app | |
| parent | e70a2211e9c85d49c131fba2dbd7a4db61ad47e7 (diff) | |
| download | karakeep-64f75a0fb010d4a12086b839cc9d80ed011aa2b3.tar.zst | |
feature: Show user list in admin page
Diffstat (limited to 'packages/web/app')
| -rw-r--r-- | packages/web/app/dashboard/admin/page.tsx | 193 |
1 files changed, 134 insertions, 59 deletions
diff --git a/packages/web/app/dashboard/admin/page.tsx b/packages/web/app/dashboard/admin/page.tsx index b2f8e9ac..6babdd79 100644 --- a/packages/web/app/dashboard/admin/page.tsx +++ b/packages/web/app/dashboard/admin/page.tsx @@ -13,27 +13,11 @@ import { import { toast } from "@/components/ui/use-toast"; import { api } from "@/lib/trpc"; import { keepPreviousData } from "@tanstack/react-query"; +import { Trash } from "lucide-react"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; -export default function AdminPage() { - const router = useRouter(); - const { data: session, status } = useSession(); - - if (status == "loading") { - return <LoadingSpinner />; - } - - if (!session || session.user.role != "admin") { - router.push("/"); - return; - } - - const { data } = api.admin.stats.useQuery(undefined, { - refetchInterval: 1000, - placeholderData: keepPreviousData, - }); - +function ActionsSection() { const { mutate: recrawlLinks, isPending: isRecrawlPending } = api.admin.recrawlAllLinks.useMutation({ onSuccess: () => { @@ -65,47 +49,7 @@ export default function AdminPage() { }); return ( - <div className="m-4 flex flex-col gap-5 rounded-md border bg-white p-4"> - <p className="text-2xl">Admin</p> - <hr /> - {data ? ( - <> - <p className="text-xl">Server Stats</p> - <Table className="w-1/2"> - <TableBody> - <TableRow> - <TableCell className="w-2/3">Num Users</TableCell> - <TableCell>{data.numUsers}</TableCell> - </TableRow> - <TableRow> - <TableCell>Num Bookmarks</TableCell> - <TableCell>{data.numBookmarks}</TableCell> - </TableRow> - </TableBody> - </Table> - <hr /> - <p className="text-xl">Background Jobs</p> - <Table className="w-1/2"> - <TableBody> - <TableRow> - <TableCell className="w-2/3">Pending Crawling Jobs</TableCell> - <TableCell>{data.pendingCrawls}</TableCell> - </TableRow> - <TableRow> - <TableCell>Pending Indexing Jobs</TableCell> - <TableCell>{data.pendingIndexing}</TableCell> - </TableRow> - <TableRow> - <TableCell>Pending OpenAI Jobs</TableCell> - <TableCell>{data.pendingOpenai}</TableCell> - </TableRow> - </TableBody> - </Table> - </> - ) : ( - <LoadingSpinner /> - )} - <hr /> + <> <p className="text-xl">Actions</p> <ActionButton className="w-1/2" @@ -123,6 +67,137 @@ export default function AdminPage() { > Reindex All Bookmarks </ActionButton> + </> + ); +} + +function ServerStatsSection() { + const { data: serverStats } = api.admin.stats.useQuery(undefined, { + refetchInterval: 1000, + placeholderData: keepPreviousData, + }); + + if (!serverStats) { + return <LoadingSpinner />; + } + + return ( + <> + <p className="text-xl">Server Stats</p> + <Table className="w-1/2"> + <TableBody> + <TableRow> + <TableCell className="w-2/3">Num Users</TableCell> + <TableCell>{serverStats.numUsers}</TableCell> + </TableRow> + <TableRow> + <TableCell>Num Bookmarks</TableCell> + <TableCell>{serverStats.numBookmarks}</TableCell> + </TableRow> + </TableBody> + </Table> + <hr /> + <p className="text-xl">Background Jobs</p> + <Table className="w-1/2"> + <TableBody> + <TableRow> + <TableCell className="w-2/3">Pending Crawling Jobs</TableCell> + <TableCell>{serverStats.pendingCrawls}</TableCell> + </TableRow> + <TableRow> + <TableCell>Pending Indexing Jobs</TableCell> + <TableCell>{serverStats.pendingIndexing}</TableCell> + </TableRow> + <TableRow> + <TableCell>Pending OpenAI Jobs</TableCell> + <TableCell>{serverStats.pendingOpenai}</TableCell> + </TableRow> + </TableBody> + </Table> + </> + ); +} + +function UsersSection() { + const { data: session } = useSession(); + const invalidateUserList = api.useUtils().users.list.invalidate; + const { data: users } = api.users.list.useQuery(); + const { mutate: deleteUser, isPending: isDeletionPending } = + api.users.delete.useMutation({ + onSuccess: () => { + toast({ + description: "User deleted", + }); + invalidateUserList(); + }, + onError: (e) => { + toast({ + variant: "destructive", + description: `Something went wrong: ${e.message}`, + }); + }, + }); + + if (!users) { + return <LoadingSpinner />; + } + + return ( + <> + <p className="text-xl">Users</p> + <Table> + <TableHeader> + <TableHead>Name</TableHead> + <TableHead>Email</TableHead> + <TableHead>Role</TableHead> + <TableHead>Action</TableHead> + </TableHeader> + <TableBody> + {users.users.map((u) => ( + <TableRow key={u.id}> + <TableCell>{u.name}</TableCell> + <TableCell>{u.email}</TableCell> + <TableCell>{u.role}</TableCell> + <TableCell> + <ActionButton + variant="destructive" + onClick={() => deleteUser({ userId: u.id })} + loading={isDeletionPending} + disabled={session!.user.id == u.id} + > + <Trash /> + </ActionButton> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </> + ); +} + +export default function AdminPage() { + const router = useRouter(); + const { data: session, status } = useSession(); + + if (status == "loading") { + return <LoadingSpinner />; + } + + if (!session || session.user.role != "admin") { + router.push("/"); + return; + } + + return ( + <div className="m-4 flex flex-col gap-5 rounded-md border bg-white p-4"> + <p className="text-2xl">Admin</p> + <hr /> + <ServerStatsSection /> + <hr /> + <UsersSection /> + <hr /> + <ActionsSection /> </div> ); } |
