import { useEffect, useMemo, useState } from "react"; import { useDbViewerStore } from "../../../stores/dbViewerStore"; import * as cmd from "../../../lib/commands"; import type { RoleInfo } from "../../../lib/types"; import { FormRow, FormSectionHeader, inputClass, controlClass } from "./formRow"; interface RoleMember { name: string; admin: boolean; } interface RoleAction { op: "create" | "edit" | "drop"; login: boolean; superuser: boolean; createdb: boolean; createrole: boolean; inherit: boolean; replication: boolean; bypassrls: boolean; connection_limit: number; valid_until: string; password: string; members: RoleMember[]; } interface RoleParams { schema: string; name: string; action: RoleAction; } interface Props { connectionId: string; mode?: "create" | "edit"; params: Record; onChange: (p: Record) => void; } const OPTIONS: { key: keyof Omit; label: string }[] = [ { key: "login", label: "LOGIN" }, { key: "superuser", label: "SUPERUSER" }, { key: "createdb", label: "CREATEDB" }, { key: "createrole", label: "CREATEROLE" }, { key: "inherit", label: "INHERIT" }, { key: "replication", label: "REPLICATION" }, { key: "bypassrls", label: "BYPASSRLS" }, ]; function patchAction(params: RoleParams, patch: Partial): RoleParams { return { ...params, action: { ...params.action, ...patch } }; } export function RoleForm({ connectionId, mode = "create", params, onChange }: Props) { const typed = params as unknown as RoleParams; const action = typed.action; const [view, setView] = useState<"visual" | "sql">("visual"); const [preview, setPreview] = useState(""); const [error, setError] = useState(null); const [staging, setStaging] = useState(false); const [roles, setRoles] = useState([]); const setParams = (next: RoleParams) => onChange(next as unknown as Record); useEffect(() => { let active = true; cmd .getRoles(connectionId) .then((list) => { if (active) setRoles(list); }) .catch(() => { if (active) setRoles([]); }); return () => { active = false; }; }, [connectionId]); useEffect(() => { let active = true; setError(null); cmd .buildObjectDdl(connectionId, "role", params) .then((sqls) => { if (active) setPreview(Array.isArray(sqls) ? sqls.join("\n;\n") : (sqls as string)); }) .catch((e: unknown) => { if (active) { setPreview(""); setError(e instanceof Error ? e.message : String(e)); } }); return () => { active = false; }; }, [connectionId, params]); const availableRoles = useMemo( () => roles.filter((r) => r.name !== typed.name), [roles, typed.name], ); const toggleMember = (name: string, checked: boolean) => { const next = checked ? [...action.members, { name, admin: false }] : action.members.filter((m) => m.name !== name); setParams(patchAction(typed, { members: next })); }; const setMemberAdmin = (name: string, admin: boolean) => { const next = action.members.map((m) => (m.name === name ? { ...m, admin } : m)); setParams(patchAction(typed, { members: next })); }; const stage = async () => { setStaging(true); setError(null); try { const sqls = await cmd.buildObjectDdl(connectionId, "role", params); const list = Array.isArray(sqls) ? sqls : [sqls]; list.forEach((sql) => useDbViewerStore.getState().addChange({ type: "ddl", sql, description: `${mode === "edit" ? "Edit" : "Create"} role ${typed.name}`, }), ); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setStaging(false); } }; return (
{mode} role
{view === "visual" ? ( <> setParams({ ...typed, name: e.target.value })} className={inputClass} />
{OPTIONS.map(({ key, label }) => ( ))}
setParams( patchAction(typed, { connection_limit: e.target.value === "" ? -1 : Number(e.target.value), }), ) } className={inputClass} /> setParams(patchAction(typed, { valid_until: e.target.value }))} className={controlClass} /> setParams(patchAction(typed, { password: e.target.value }))} className={inputClass} />
{availableRoles.length === 0 && (

No other roles available.

)}
{availableRoles.map((r) => { const member = action.members.find((m) => m.name === r.name); return (
{member && ( )}
); })}
) : (
{preview}
)} {error &&

{error}

}
); }