import Link from "next/link"; import { AlertTriangle, Database, LockKeyhole, Plus, Search, Shield, UsersRound } from "lucide-react"; import { redirect } from "next/navigation"; import { AppShell } from "@/components/app-shell"; import { Avatar } from "@/components/avatar"; import { StatusBadge } from "@/components/status-badge"; import { SYSTEM_PERMISSIONS } from "@/lib/access"; import { prisma } from "@/lib/prisma"; import { getShellContext } from "@/lib/shell"; import { requireCurrentUser, userIsAdmin } from "@/lib/session"; import { requireInitialSetup } from "@/lib/setup"; import { DataTable, EmptyState, MetricTile, PageHeader, Panel, StatusDot, Tabs } from "@/components/ui"; export const dynamic = "force-dynamic"; const adminTabs = ["Users", "Rooms", "Roles", "Invites", "Instance", "Security"]; export default async function AdminPage({ searchParams }: { searchParams: Promise<{ tab?: string; q?: string }> }) { await requireInitialSetup(); const user = await requireCurrentUser(); if (!userIsAdmin(user)) { redirect("/dashboard"); } const { tab = "Users", q = "" } = await searchParams; const activeTab = adminTabs.includes(tab) ? tab : "Users"; const query = q.trim().toLowerCase(); const shell = await getShellContext(user); const [users, rooms, roles, pendingRequests] = await Promise.all([ prisma.user.findMany({ include: { roles: { include: { role: true } }, _count: { select: { ownedRooms: true, roomMembers: true } } }, orderBy: { createdAt: "asc" } }), prisma.room.findMany({ include: { owner: true, _count: { select: { members: true, mediaSources: true } } }, orderBy: { updatedAt: "desc" } }), prisma.role.findMany({ include: { _count: { select: { users: true, permissions: true } } }, orderBy: { name: "asc" } }), prisma.friendship.count({ where: { status: "PENDING" } }) ]); const filteredUsers = query ? users.filter((account) => [account.username, account.displayName || "", ...account.roles.map((role) => role.role.name)].join(" ").toLowerCase().includes(query) ) : users; const filteredRooms = query ? rooms.filter((room) => [room.name, room.slug, room.visibility, room.owner?.username || "", room.owner?.displayName || ""].join(" ").toLowerCase().includes(query) ) : rooms; const filteredRoles = query ? roles.filter((role) => [role.name, role.description || "", role.scope].join(" ").toLowerCase().includes(query)) : roles; return ( 0 ? "warn" : "neutral"} label={`${pendingRequests} pending requests`} /> } actions={ <> } />
} > ({ label: item, href: `/admin?tab=${encodeURIComponent(item)}` }))} /> {activeTab === "Users" ? : null} {activeTab === "Rooms" ? : null} {activeTab === "Roles" ? : null} {activeTab === "Invites" ? : null} {activeTab === "Instance" ? : null} {activeTab === "Security" ? : null}
); } function UsersTable({ users }: { users: Array<{ id: string; username: string; displayName: string | null; createdAt: Date; roles: Array<{ roleId: string; role: { name: string } }>; _count: { ownedRooms: number; roomMembers: number }; }>; }) { return ( {users.map((account) => ( ))}
User Roles Rooms Created Actions
{account.displayName || account.username} @{account.username}
{account.roles.length === 0 ? user : null} {account.roles.map((userRole) => ( {userRole.role.name} ))}
{account._count.ownedRooms + account._count.roomMembers} {formatDate(account.createdAt)}
); } function RoomsTable({ rooms }: { rooms: Array<{ id: string; slug: string; name: string; visibility: string; updatedAt: Date; owner: { username: string; displayName: string | null } | null; _count: { members: number; mediaSources: number }; }>; }) { return ( {rooms.map((room) => ( ))}
Room Owner Access State Updated
{room.name} /{room.slug}
{room.owner?.displayName || room.owner?.username || "Unassigned"} {room.visibility.toLowerCase().replace("_", " ")} {room._count.members + 1} users / {room._count.mediaSources} media {formatDate(room.updatedAt)} Open
); } function RolesTable({ roles }: { roles: Array<{ id: string; name: string; description: string | null; scope: string; _count: { users: number; permissions: number }; }>; }) { return roles.length === 0 ? ( ) : (
{roles.map((role) => (
{role.name} {role.description || `${role.scope.toLowerCase()} role`}
{role._count.users} users / {role._count.permissions} permissions
))}
{SYSTEM_PERMISSIONS.map((permission) => (
{permission} Assignable to system roles
Enabled
))}
); } function InvitesPanel({ pendingRequests }: { pendingRequests: number }) { return (
{pendingRequests} pending friend requests Handled by each receiving user in People.
); } function InstancePanel({ userCount, roomCount }: { userCount: number; roomCount: number }) { return (
} label="Database" value="Postgres via DATABASE_URL" /> } label="Sessions" value="Signed HTTP-only cookie" /> } label="Accounts" value={`${userCount} users`} /> } label="Rooms" value={`${roomCount} rooms`} />
); } function SecurityPanel() { return (
} label="Password storage" value="bcrypt hash" /> } label="Admin check" value="Server-side role lookup" /> } label="Registration mode" value="Open registration" />
); } function Setting({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) { return (
{icon} {label} {value}
); } function formatDate(date: Date) { return new Intl.DateTimeFormat("en", { month: "short", day: "numeric", year: "numeric" }).format(date); }