diff options
| author | MohamedBassem <me@mbassem.com> | 2024-03-01 23:17:27 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-03-01 23:19:03 +0000 |
| commit | 7ddcfb630d3dec3d9fecbfd6a498ca7c572809ec (patch) | |
| tree | c0339e9181b35d0819bc0bfd1219ccdb262e54d2 /packages/web/app | |
| parent | a5434730ede1272f195d6a4b13207b840a5ac2cf (diff) | |
| download | karakeep-7ddcfb630d3dec3d9fecbfd6a498ca7c572809ec.tar.zst | |
feature: Add an admin page showing server stats and actions
Diffstat (limited to 'packages/web/app')
| -rw-r--r-- | packages/web/app/dashboard/admin/page.tsx | 123 | ||||
| -rw-r--r-- | packages/web/app/dashboard/components/Sidebar.tsx | 8 | ||||
| -rw-r--r-- | packages/web/app/layout.tsx | 6 |
3 files changed, 135 insertions, 2 deletions
diff --git a/packages/web/app/dashboard/admin/page.tsx b/packages/web/app/dashboard/admin/page.tsx new file mode 100644 index 00000000..26b6447b --- /dev/null +++ b/packages/web/app/dashboard/admin/page.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { ActionButton } from "@/components/ui/action-button"; +import LoadingSpinner from "@/components/ui/spinner"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { toast } from "@/components/ui/use-toast"; +import { api } from "@/lib/trpc"; +import { keepPreviousData } from "@tanstack/react-query"; +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, + }); + + const { mutate: recrawlLinks, isPending: isRecrawlPending } = + api.admin.recrawlAllLinks.useMutation({ + onSuccess: () => { + toast({ + description: "Recrawl enqueued", + }); + }, + onError: (e) => { + toast({ + variant: "destructive", + description: e.message, + }); + }, + }); + + const { mutate: reindexBookmarks, isPending: isReindexPending } = + api.admin.reindexAllBookmarks.useMutation({ + onSuccess: () => { + toast({ + description: "Reindex enqueued", + }); + }, + onError: (e) => { + toast({ + variant: "destructive", + description: e.message, + }); + }, + }); + + 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 ? ( + <Table> + <TableHeader> + <TableRow> + <TableHead>Stats</TableHead> + <TableHead>Value</TableHead> + </TableRow> + </TableHeader> + <TableBody> + <TableRow> + <TableCell>Num Users</TableCell> + <TableCell>{data.numUsers}</TableCell> + </TableRow> + <TableRow> + <TableCell>Num Bookmarks</TableCell> + <TableCell>{data.numBookmarks}</TableCell> + </TableRow> + <TableRow> + <TableCell>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 + variant="destructive" + loading={isRecrawlPending} + onClick={() => recrawlLinks()} + > + Recrawl All Links + </ActionButton> + <ActionButton + variant="destructive" + loading={isReindexPending} + onClick={() => reindexBookmarks()} + > + Reindex All Bookmarks + </ActionButton> + </div> + ); +} diff --git a/packages/web/app/dashboard/components/Sidebar.tsx b/packages/web/app/dashboard/components/Sidebar.tsx index 010ee103..29e5baed 100644 --- a/packages/web/app/dashboard/components/Sidebar.tsx +++ b/packages/web/app/dashboard/components/Sidebar.tsx @@ -6,6 +6,7 @@ import { PackageOpen, Settings, Search, + Shield, } from "lucide-react"; import { redirect } from "next/navigation"; import SidebarItem from "./SidebarItem"; @@ -61,6 +62,13 @@ export default async function Sidebar() { name="Settings" path="/dashboard/settings" /> + {session.user.role == "admin" && ( + <SidebarItem + logo={<Shield />} + name="Admin" + path="/dashboard/admin" + /> + )} </ul> </div> <Separator /> diff --git a/packages/web/app/layout.tsx b/packages/web/app/layout.tsx index d597063b..a8423031 100644 --- a/packages/web/app/layout.tsx +++ b/packages/web/app/layout.tsx @@ -5,6 +5,7 @@ import React from "react"; import { Toaster } from "@/components/ui/toaster"; import Providers from "@/lib/providers"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { getServerAuthSession } from "@/server/auth"; const inter = Inter({ subsets: ["latin"] }); @@ -13,15 +14,16 @@ export const metadata: Metadata = { description: "Your AI powered second brain", }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const session = await getServerAuthSession(); return ( <html lang="en"> <body className={inter.className}> - <Providers> + <Providers session={session}> {children} <ReactQueryDevtools initialIsOpen={false} /> </Providers> |
