import { useState } from "react";
import {
useDatabaseInfo,
useRoles,
useDropDatabase,
useDropRole,
} from "@/hooks/use-management";
import { useAppStore } from "@/stores/app-store";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { CreateDatabaseDialog } from "./CreateDatabaseDialog";
import { CreateRoleDialog } from "./CreateRoleDialog";
import { AlterRoleDialog } from "./AlterRoleDialog";
import { toast } from "sonner";
import {
Plus,
Pencil,
Trash2,
ChevronDown,
ChevronRight,
HardDrive,
Users,
Activity,
Loader2,
} from "lucide-react";
import { DockerContainersList } from "@/components/docker/DockerContainersList";
import type { Tab, RoleInfo } from "@/types";
export function AdminPanel() {
const { activeConnectionId, currentDatabase, readOnlyMap, addTab } = useAppStore();
if (!activeConnectionId) {
return (
Connect to a database to manage it.
);
}
const isReadOnly = readOnlyMap[activeConnectionId] ?? true;
return (
{
const tab: Tab = {
id: crypto.randomUUID(),
type: "roles",
title: "Roles & Users",
connectionId: activeConnectionId,
database: currentDatabase ?? undefined,
};
addTab(tab);
}}
/>
{
const tab: Tab = {
id: crypto.randomUUID(),
type: "sessions",
title: "Active Sessions",
connectionId: activeConnectionId,
database: currentDatabase ?? undefined,
};
addTab(tab);
}}
/>
);
}
function DatabasesSection({
connectionId,
currentDatabase,
isReadOnly,
}: {
connectionId: string;
currentDatabase: string | null;
isReadOnly: boolean;
}) {
const [expanded, setExpanded] = useState(true);
const [createOpen, setCreateOpen] = useState(false);
const { data: databases, isLoading } = useDatabaseInfo(connectionId);
const dropMutation = useDropDatabase();
const handleDrop = (name: string) => {
if (name === currentDatabase) {
toast.error("Cannot drop the active database");
return;
}
if (!confirm(`Drop database "${name}"? This will terminate all connections and cannot be undone.`)) {
return;
}
dropMutation.mutate(
{ connectionId, name },
{
onSuccess: () => toast.success(`Database "${name}" dropped`),
onError: (err) => toast.error("Failed to drop database", { description: String(err) }),
}
);
};
return (
setExpanded(!expanded)}
>
{expanded ? (
) : (
)}
Databases
{!isReadOnly && (
)}
{expanded && (
{isLoading && (
Loading...
)}
{databases?.map((db) => (
{db.name}
{db.size}
{db.name === currentDatabase && (
active
)}
{!isReadOnly && db.name !== currentDatabase && (
)}
))}
)}
);
}
function RolesSection({
connectionId,
isReadOnly,
onOpenRoleManager,
}: {
connectionId: string;
isReadOnly: boolean;
onOpenRoleManager: () => void;
}) {
const [expanded, setExpanded] = useState(true);
const [createOpen, setCreateOpen] = useState(false);
const [alterRole, setAlterRole] = useState(null);
const { data: roles, isLoading } = useRoles(connectionId);
const dropMutation = useDropRole();
const handleDrop = (name: string) => {
if (!confirm(`Drop role "${name}"? This cannot be undone.`)) return;
dropMutation.mutate(
{ connectionId, name },
{
onSuccess: () => toast.success(`Role "${name}" dropped`),
onError: (err) => toast.error("Failed to drop role", { description: String(err) }),
}
);
};
return (
setExpanded(!expanded)}
>
{expanded ? (
) : (
)}
Roles
e.stopPropagation()}>
{!isReadOnly && (
)}
{expanded && (
{isLoading && (
Loading...
)}
{roles?.map((role) => (
{role.name}
{role.can_login && (
LOGIN
)}
{role.is_superuser && (
SUPER
)}
{!isReadOnly && (
<>
>
)}
))}
)}
!open && setAlterRole(null)}
connectionId={connectionId}
role={alterRole}
/>
);
}
function SessionsSection({
connectionId,
onOpenSessions,
}: {
connectionId: string;
onOpenSessions: () => void;
}) {
return (
Monitor active database connections and running queries.
{/* The connectionId is used by parent to open the sessions tab */}
{connectionId}
);
}