Merge branch 'refs/heads/maybe-rollback-to-remove-primeng'

# Conflicts:
#	frontend/bun.lockb
This commit is contained in:
2024-07-14 12:55:00 +02:00
24 changed files with 663 additions and 417 deletions

View File

@@ -1,27 +1,11 @@
import { SessionSelector } from "@/components/session-selector";
import { SettingsDialog } from "@/components/settings-dialog";
import {
Button,
ModeToggle,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
buttonVariants,
} from "@/components/ui";
import { Sidebar } from "@/components/sidebar/sidebar";
import { Button, ModeToggle } from "@/components/ui";
import { cn } from "@/lib/utils";
import { useDatabasesListQuery, useTablesListQuery } from "@/services/db";
import { useUiStore } from "@/state";
import {
Link,
Outlet,
createRootRoute,
useNavigate,
useParams,
} from "@tanstack/react-router";
import { Link, Outlet, createRootRoute } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
import { Database, PanelLeft, PanelLeftClose, Rows3 } from "lucide-react";
import { PanelLeft, PanelLeftClose } from "lucide-react";
export const Route = createRootRoute({
component: Root,
@@ -31,16 +15,6 @@ function Root() {
const showSidebar = useUiStore.use.showSidebar();
const toggleSidebar = useUiStore.use.toggleSidebar();
const { data } = useDatabasesListQuery();
const params = useParams({ strict: false });
const dbName = params.dbName ?? "";
const navigate = useNavigate({ from: Route.fullPath });
const handleSelectedDb = (dbName: string) => {
void navigate({ to: "/db/$dbName/tables", params: { dbName } });
};
const { data: tables } = useTablesListQuery({ dbName });
return (
<>
<div
@@ -70,86 +44,7 @@ function Root() {
<SettingsDialog />
</div>
</header>
<aside className={"p-3"}>
{showSidebar && (
<>
<SessionSelector />
<Select value={dbName} onValueChange={handleSelectedDb}>
<SelectTrigger className="w-full mt-4">
<SelectValue placeholder="Select a Database" />
</SelectTrigger>
<SelectContent>
{data?.map((db) => {
return (
<SelectItem value={db} key={db}>
{db}
</SelectItem>
);
})}
</SelectContent>
</Select>
<nav className="flex flex-col gap-1 mt-4">
{dbName && (
<Link
to={"/db/$dbName/tables"}
params={{ dbName }}
activeOptions={{ exact: true }}
title={dbName}
className={cn(
"flex items-center gap-2 rounded py-1.5 pl-1.5",
"hover:bg-muted",
"[&.active]:bg-muted [&.active]:font-semibold",
)}
>
<Database className={"size-4"} />
<span className={"max-w-full inline-block truncate"}>
{dbName}
</span>
</Link>
)}
{tables?.map((table) => {
return (
<div
key={table.table_name}
className={cn(
"flex items-center gap-2 px-2.5 rounded py-1.5 justify-between w-full",
)}
>
<Link
className={cn(
"max-w-full inline-block truncate",
"hover:underline",
"[&.active]:font-medium",
)}
title={table.table_name}
to={"/db/$dbName/tables/$tableName"}
params={{ tableName: table.table_name, dbName: dbName }}
>
{table.table_name}
</Link>
<Link
className={cn(
"shrink-0",
"hover:underline",
buttonVariants({ variant: "ghost", size: "iconSm" }),
"[&.active]:bg-muted",
)}
title={"Explore Data"}
aria-label={"Explore Data"}
to={"/db/$dbName/tables/$tableName/data"}
params={{ tableName: table.table_name, dbName: dbName }}
search={{ pageIndex: 0, pageSize: 10 }}
>
<Rows3 className={"size-4 shrink-0"} />
</Link>
</div>
);
})}
</nav>
</>
)}
</aside>
<Sidebar />
<Outlet />
</div>
<TanStackRouterDevtools />

View File

@@ -6,9 +6,9 @@ import {
CardFooter,
CardHeader,
CardTitle,
Input,
FormInput,
FormSelect,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
@@ -16,30 +16,122 @@ import {
ToggleGroup,
ToggleGroupItem,
} from "@/components/ui";
import { useLoginMutation } from "@/services/db";
import { type LoginArgs, useLoginMutation } from "@/services/db";
import { useSessionStore } from "@/state/db-session-store";
import { zodResolver } from "@hookform/resolvers/zod";
import { createFileRoute } from "@tanstack/react-router";
import { type FormEventHandler, useState } from "react";
import { toast } from "sonner";
import { useState } from "react";
import { type Control, useForm } from "react-hook-form";
import { z } from "zod";
export const Route = createFileRoute("/auth/login")({
component: LoginForm,
});
function DatabaseTypeSelector() {
const loginWithConnectionStringSchema = z.object({
type: z.enum(["mysql", "postgres"]),
connectionString: z.string().trim().min(1, "Connection string is required"),
});
type LoginWithConnectionStringFields = z.infer<
typeof loginWithConnectionStringSchema
>;
function ConnectionStringForm({
onSubmit,
}: {
onSubmit: (values: LoginWithConnectionStringFields) => void;
}) {
const { control, handleSubmit } = useForm<LoginWithConnectionStringFields>({
resolver: zodResolver(loginWithConnectionStringSchema),
defaultValues: {
type: "postgres",
connectionString: "",
},
});
return (
<div className="grid gap-2">
<Label htmlFor="dbType">Database type</Label>
<Select defaultValue={"postgres"} name={"type"}>
<SelectTrigger className="w-full" id={"dbType"}>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="postgres">Postgres</SelectItem>
<SelectItem value="mysql">MySQL</SelectItem>
</SelectContent>
</Select>
</div>
<form
className="grid gap-2"
onSubmit={handleSubmit(onSubmit)}
id={"login-form"}
>
<DatabaseTypeSelector control={control} />
<FormInput
label={"Connection string"}
name={"connectionString"}
control={control}
placeholder={"postgres://postgres:postgres@localhost:5432/postgres"}
/>
</form>
);
}
const loginWithConnectionFieldsSchema = z.object({
type: z.enum(["mysql", "postgres"]),
username: z.string().min(1, "Username is required"),
password: z.string().min(1, "Password is required"),
host: z.string().min(1, "Host is required"),
port: z.string().min(1, "Port is required"),
database: z.string().min(1, "Database is required"),
ssl: z.enum(["false", "true", "require", "allow", "prefer", "verify-full"]),
});
type LoginWithConnectionFields = z.infer<
typeof loginWithConnectionFieldsSchema
>;
function ConnectionFieldsForm({
onSubmit,
}: {
onSubmit: (values: LoginWithConnectionFields) => void;
}) {
const { control, handleSubmit } = useForm<LoginWithConnectionFields>({
resolver: zodResolver(loginWithConnectionFieldsSchema),
defaultValues: {
type: "postgres",
host: "",
port: "",
username: "",
password: "",
ssl: "prefer",
database: "",
},
});
return (
<form
className="grid gap-3"
onSubmit={handleSubmit(onSubmit)}
id={"login-form"}
>
<DatabaseTypeSelector control={control} />
<FormInput
name={"host"}
control={control}
label={"Host"}
placeholder={"127.0.0.1"}
/>
<FormInput name={"port"} control={control} label={"Port"} />
<FormInput name={"username"} control={control} label={"User"} />
<FormInput name={"password"} control={control} label={"Password"} />
<FormInput name={"database"} control={control} label={"Database"} />
<div className="grid gap-2">
<Label htmlFor="ssl">SSL mode</Label>
<FormSelect control={control} name={"ssl"}>
<SelectTrigger className="w-full" id={"ssl"}>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="false">false</SelectItem>
<SelectItem value="true">true</SelectItem>
<SelectItem value="require">require</SelectItem>
<SelectItem value="allow">allow</SelectItem>
<SelectItem value="prefer">prefer</SelectItem>
<SelectItem value="verify-full">verify-full</SelectItem>
</SelectContent>
</FormSelect>
</div>
</form>
);
}
@@ -49,78 +141,9 @@ function LoginForm() {
const { mutateAsync } = useLoginMutation();
const addSession = useSessionStore.use.addSession();
const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const connectionString = formData.get("connectionString");
const type = formData.get("type");
if (connectionMethod === "connectionString") {
if (
connectionString != null &&
typeof connectionString === "string" &&
type != null &&
typeof type === "string"
) {
try {
await mutateAsync({ connectionString, type });
addSession({ connectionString, type });
} catch (error) {
console.log(error);
toast.error("Invalid connection string");
return;
}
} else {
toast.error("Please fill all fields");
}
return;
}
const username = formData.get("username");
const password = formData.get("password");
const host = formData.get("host");
const port = formData.get("port");
const database = formData.get("database");
const ssl = formData.get("ssl");
if (
database == null ||
host == null ||
password == null ||
port == null ||
ssl == null ||
type == null ||
username == null
) {
toast.error("Please fill all fields");
return;
}
if (
typeof database !== "string" ||
typeof host !== "string" ||
typeof password !== "string" ||
typeof port !== "string" ||
typeof ssl !== "string" ||
typeof type !== "string" ||
typeof username !== "string"
) {
return;
}
try {
await mutateAsync({
username,
password,
host,
type,
port,
database,
ssl,
});
addSession({ username, password, host, type, port, database, ssl });
} catch (error) {
console.log(error);
toast.error("Invalid connection string");
return;
}
const onSubmit = async (args: LoginArgs) => {
await mutateAsync(args);
addSession(args);
};
return (
@@ -133,11 +156,7 @@ function LoginForm() {
</CardDescription>
</CardHeader>
<CardContent>
<form
className="grid gap-4"
id={"login-form"}
onSubmit={handleSubmit}
>
<div className="grid gap-4">
<ToggleGroup
type="single"
className="w-full border gap-0.5 rounded-md"
@@ -158,85 +177,11 @@ function LoginForm() {
</ToggleGroupItem>
</ToggleGroup>
{connectionMethod === "fields" ? (
<>
<DatabaseTypeSelector />
<div className="grid gap-2">
<Label htmlFor="host">Host</Label>
<Input
id="host"
name="host"
type="text"
required
placeholder={"127.0.0.1"}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="port">Port</Label>
<Input
id="port"
name="port"
type="text"
required
defaultValue={"5432"}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="username">User</Label>
<Input id="username" name="username" type="text" required />
</div>
<div className="grid gap-2">
<Label htmlFor="password">Password</Label>
<Input
name="password"
id="password"
type="password"
required
placeholder={"********"}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="database">Database</Label>
<Input
name="database"
id="database"
type="text"
defaultValue={"postgres"}
placeholder={"postgres"}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="ssl">SSL mode</Label>
<Select defaultValue={"false"} name={"ssl"}>
<SelectTrigger className="w-full" id={"ssl"}>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="false">false</SelectItem>
<SelectItem value="true">true</SelectItem>
<SelectItem value="require">require</SelectItem>
<SelectItem value="allow">allow</SelectItem>
<SelectItem value="prefer">prefer</SelectItem>
<SelectItem value="verify-full">verify-full</SelectItem>
</SelectContent>
</Select>
</div>
</>
<ConnectionFieldsForm onSubmit={onSubmit} />
) : (
<div className="grid gap-2">
<DatabaseTypeSelector />
<Label htmlFor="connectionString">Connection string</Label>
<Input
name="connectionString"
id="connectionString"
type="text"
required
placeholder={
"postgres://postgres:postgres@localhost:5432/postgres"
}
/>
</div>
<ConnectionStringForm onSubmit={onSubmit} />
)}
</form>
</div>
</CardContent>
<CardFooter>
<Button className="w-full" form={"login-form"}>
@@ -247,3 +192,24 @@ function LoginForm() {
</div>
);
}
function DatabaseTypeSelector({
control,
}: {
control: Control<any>;
}) {
return (
<div className="grid gap-2">
<Label htmlFor="dbType">Database type</Label>
<FormSelect control={control} name={"type"}>
<SelectTrigger className="w-full" id={"dbType"}>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="postgres">Postgres</SelectItem>
<SelectItem value="mysql">MySQL</SelectItem>
</SelectContent>
</FormSelect>
</div>
);
}