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"; import { getAppSettings, type AppSettings } from "@/lib/settings"; import { updateInstanceSettings, updateSecuritySettings } from "@/lib/settings-actions"; import { banUser, createInstanceInvite, disableUser, enableUser, expireStaleInvites, grantAdminRole, removeUserFriendships, revokeAdminRole, revokeInvite } from "@/lib/admin-actions"; import { deleteRoom } from "@/lib/room-actions"; import { isInviteExpired } from "@/lib/invites"; 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; saved?: string }> }) { await requireInitialSetup(); const user = await requireCurrentUser(); if (!userIsAdmin(user)) { redirect("/dashboard"); } const { tab = "Users", q = "", saved } = await searchParams; const activeTab = adminTabs.includes(tab) ? tab : "Users"; const query = q.trim().toLowerCase(); const shell = await getShellContext(user); const [users, rooms, roles, pendingRequests, appSettings, invites, auditEvents] = await Promise.all([ prisma.user.findMany({ include: { roles: { include: { role: true } }, _count: { select: { ownedRooms: true, roomMembers: true, sentFriends: true, gotFriends: 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" } }), getAppSettings(), prisma.invite.findMany({ include: { room: true, creator: true }, orderBy: { createdAt: "desc" }, take: 50 }), prisma.auditEvent.findMany({ include: { actor: true, room: true }, orderBy: { createdAt: "desc" }, take: 20 }) ]); 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={ <> Invite Audit } />
} > ({ 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, currentUserId }: { users: Array<{ id: string; username: string; displayName: string | null; avatarUrl: string | null; disabledAt: Date | null; createdAt: Date; roles: Array<{ roleId: string; role: { name: string } }>; _count: { ownedRooms: number; roomMembers: number; sentFriends: number; gotFriends: number }; }>; currentUserId: string; }) { return ( {users.map((account) => ( ))}
User Roles Rooms Friends 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} {account._count.sentFriends + account._count.gotFriends} {account.disabledAt ? disabled : formatDate(account.createdAt)}
{account.roles.some((role) => role.role.name === "admin") ? (
) : (
)} {account.disabledAt ? (
) : (
)}
); } function RoomsTable({ rooms }: { rooms: Array<{ id: string; slug: string; name: string; visibility: string; isPersonal: boolean; 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 {!room.isPersonal ? (
) : ( personal )}
); } 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, invites, rooms }: { pendingRequests: number; invites: Array<{ id: string; code: string; status: string; expiresAt: Date | null; createdAt: Date; room: { id: string; name: string; slug: string } | null; creator: { username: string; displayName: string | null } | null; }>; rooms: Array<{ id: string; name: string; slug: string }>; }) { const expiredInviteCount = invites.filter((invite) => invite.status === "ACTIVE" && isInviteExpired(invite.expiresAt)).length; return (
{expiredInviteCount > 0 ? (
) : null} {invites.length === 0 ? ( ) : (
{invites.map((invite) => { const expired = isInviteExpired(invite.expiresAt); const label = expired && invite.status === "ACTIVE" ? "expired" : invite.status.toLowerCase(); return (
{invite.code} {invite.room ? `${invite.room.name} /${invite.room.slug}` : "Instance invite"} - created {formatDate(invite.createdAt)} {invite.expiresAt ? ` - expires ${formatDate(invite.expiresAt)}` : " - no expiry"} {label} {invite.status === "ACTIVE" && !expired ? (
) : null}
); })}
)}
{pendingRequests} pending friend requests Handled by each receiving user in People.
); } function InstancePanel({ userCount, roomCount, settings, saved }: { userCount: number; roomCount: number; settings: AppSettings; saved?: string }) { return (
{saved ?

Instance settings saved.

: null}