Skip to content

Commit

Permalink
feat: add proper validation for all endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
invisal committed Mar 29, 2024
1 parent b5ad0cb commit 902fd4e
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 123 deletions.
104 changes: 104 additions & 0 deletions src/app/connect/connection-string-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Textarea } from "@/components/ui/textarea";
import {
DRIVER_DETAIL,
SavedConnectionItemConfigConfig,
SupportedDriver,
} from "./saved-connection-storage";
import { Input } from "@/components/ui/input";

export default function ConnectionStringInput({
value,
onChange,
driver,
showLockedCredential,
autoFocus,
}: Readonly<{
value: SavedConnectionItemConfigConfig;
onChange: (value: SavedConnectionItemConfigConfig) => void;
driver: SupportedDriver;
showLockedCredential?: boolean;
autoFocus?: boolean;
}>) {
const driverDetail = DRIVER_DETAIL[driver];
const authType = driver === "turso" ? "token" : "username";
const endpointError = driverDetail.invalidateEndpoint(value.url);

return (
<>
<div>
<div className="text-xs mb-2 font-semibold">URL (*)</div>
<Input
autoFocus={autoFocus}
placeholder={"URL"}
value={value.url}
onChange={(e) => {
onChange({ ...value, url: e.currentTarget.value });
}}
/>
{endpointError && (
<div className="text-xs mt-2 text-red-400">{endpointError}</div>
)}
<div className="text-xs mt-2">{driverDetail.endpointExample}</div>
</div>

{authType === "token" && (
<div>
<div className="text-xs mb-2 font-semibold">Token</div>
<Textarea
placeholder={
showLockedCredential && !value.token ? "✱✱✱✱✱✱✱✱✱" : "Token"
}
className={
showLockedCredential && !value.token ? "bg-secondary" : ""
}
value={value.token}
onChange={(e) => {
onChange({ ...value, token: e.currentTarget.value });
}}
/>
</div>
)}

{authType === "username" && (
<>
<div>
<div className="text-xs mb-2 font-semibold">Username</div>
<Input
type="username"
placeholder={
showLockedCredential && !value.username
? "✱✱✱✱✱✱✱✱✱"
: "Username"
}
className={
showLockedCredential && !value.username ? "bg-secondary" : ""
}
value={value.username}
onChange={(e) => {
onChange({ ...value, username: e.currentTarget.value });
}}
/>
</div>
<div>
<div className="text-xs mb-2 font-semibold">Password</div>
<Input
type="password"
placeholder={
showLockedCredential && !value.password
? "✱✱✱✱✱✱✱✱✱"
: "Password"
}
className={
showLockedCredential && !value.password ? "bg-secondary" : ""
}
value={value.password}
onChange={(e) => {
onChange({ ...value, password: e.currentTarget.value });
}}
/>
</div>
</>
)}
</>
);
}
2 changes: 1 addition & 1 deletion src/app/connect/driver-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function DriverDropdown({
<div className="flex gap-4 px-2 items-center h-12">
<img src="/rqlite.png" alt="turso" className="w-9 h-9" />
<div>
<div className="font-bold">Rqlite</div>
<div className="font-bold">rqlite</div>
<div className="text-xs opacity-50">
Distributed database built on SQLite
</div>
Expand Down
87 changes: 30 additions & 57 deletions src/app/connect/quick-connect.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import ConnectionDialogContent from "./saved-connection-content";
import { useCallback, useState } from "react";
import useConnect from "@/hooks/use-connect";
import { SupportedDriver } from "./saved-connection-storage";
import {
DRIVER_DETAIL,
SavedConnectionItemConfigConfig,
SupportedDriver,
} from "./saved-connection-storage";
import { RqliteInstruction } from "./saved-connection";
import ConnectionStringInput from "./connection-string-input";

export default function QuickConnect({
driver,
onClose,
}: Readonly<{ onClose: () => void; driver: SupportedDriver }>) {
const [url, setURL] = useState("");
const [token, setToken] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const driverDetail = DRIVER_DETAIL[driver ?? "turso"];
const [connectionConfig, setConnectionConfig] =
useState<SavedConnectionItemConfigConfig>({
url: DRIVER_DETAIL[driver ?? "turso"].prefill,
token: "",
username: "",
password: "",
});

const connect = useConnect();
const valid = !driverDetail.invalidateEndpoint(connectionConfig.url);

const onConnect = useCallback(() => {
connect(driver, {
token,
url,
password,
username,
token: connectionConfig.token,
url: connectionConfig.url,
password: connectionConfig.password,
username: connectionConfig.username,
});
}, [connect, driver, url, token, password, username]);

const authType = driver === "turso" ? "token" : "username";
}, [connect, driver, connectionConfig]);

return (
<ConnectionDialogContent
Expand All @@ -36,51 +43,17 @@ export default function QuickConnect({
>
{driver === "rqlite" && <RqliteInstruction />}

<div>
<div className="text-xs mb-2">URL</div>
<Input
placeholder={"URL"}
value={url}
onChange={(e) => setURL(e.currentTarget.value)}
/>
</div>

{authType === "token" && (
<div>
<div className="text-xs mb-2">Token</div>
<Textarea
placeholder={"Token"}
value={token}
onChange={(e) => setToken(e.currentTarget.value)}
/>
</div>
)}

{authType === "username" && (
<>
<div>
<div className="text-xs mb-2">Username</div>
<Input
type="username"
placeholder={"Username"}
value={username}
onChange={(e) => setUsername(e.currentTarget.value)}
/>
</div>
<div>
<div className="text-xs mb-2">Password</div>
<Input
type="password"
placeholder={"Password"}
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
/>
</div>
</>
)}
<ConnectionStringInput
autoFocus
driver={driver}
onChange={setConnectionConfig}
value={connectionConfig}
/>

<div className="mt-12 flex gap-4">
<Button onClick={onConnect}>Connect</Button>
<Button onClick={onConnect} disabled={!valid}>
Connect
</Button>
</div>
</ConnectionDialogContent>
);
Expand Down
83 changes: 21 additions & 62 deletions src/app/connect/saved-connection-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import { Separator } from "@/components/ui/separator";
import { Textarea } from "@/components/ui/textarea";
import {
CONNECTION_LABEL_COLORS,
DRIVER_DETAIL,
SavedConnectionItemConfig,
SavedConnectionItemConfigConfig,
SavedConnectionLabel,
SupportedDriver,
} from "@/app/connect/saved-connection-storage";
import { cn } from "@/lib/utils";
import { useState } from "react";
import { LucideLoader } from "lucide-react";
import ConnectionStringInput from "./connection-string-input";

interface Props {
onSave: (conn: SavedConnectionItemConfig) => void;
Expand All @@ -21,8 +24,6 @@ interface Props {
showLockedCredential?: boolean;
}

type AuthType = "token" | "username";

function ColorItem({
color,
selected,
Expand All @@ -45,30 +46,35 @@ export default function SavedConnectionConfig({
initialData,
loading,
}: Readonly<Props>) {
const driverDetail = DRIVER_DETAIL[driver ?? "turso"];
const [name, setName] = useState(initialData?.name ?? "");
const [description, setDescription] = useState(
initialData?.description ?? ""
);
const [color, setColor] = useState<SavedConnectionLabel>(
initialData?.label ?? "gray"
);
const [url, setURL] = useState(initialData?.config?.url ?? "");
const [token, setToken] = useState(initialData?.config?.token ?? "");
const [username, setUsername] = useState(initialData?.config?.username ?? "");
const [password, setPassword] = useState(initialData?.config?.password ?? "");

const authType: AuthType = driver === "turso" ? "token" : "username";
const [connectionString, setConnectionString] =
useState<SavedConnectionItemConfigConfig>({
url: initialData?.config?.url ?? driverDetail.prefill,
token: initialData?.config?.token ?? "",
username: initialData?.config?.username ?? "",
password: initialData?.config?.password ?? "",
});

const onSaveClicked = () => {
onSave({
label: color,
name,
description,
driver,
config: { url, token, username, password },
config: connectionString,
});
};

const valid = !driverDetail.invalidateEndpoint(connectionString.url);

return (
<>
<div>
Expand Down Expand Up @@ -107,62 +113,15 @@ export default function SavedConnectionConfig({

<Separator />

<div>
<div className="text-xs mb-2">URL</div>
<Input
placeholder={"URL"}
value={url}
onChange={(e) => setURL(e.currentTarget.value)}
/>
</div>

{authType === "token" && (
<div>
<div className="text-xs mb-2">Token</div>
<Textarea
placeholder={showLockedCredential && !token ? "✱✱✱✱✱✱✱✱✱" : "Token"}
value={token}
className={showLockedCredential && !token ? "bg-secondary" : ""}
onChange={(e) => setToken(e.currentTarget.value)}
/>
</div>
)}

{authType === "username" && (
<>
<div>
<div className="text-xs mb-2">Username</div>
<Input
type="username"
placeholder={
showLockedCredential && !username ? "✱✱✱✱✱✱✱✱✱" : "Username"
}
value={username}
className={
showLockedCredential && !username ? "bg-secondary" : ""
}
onChange={(e) => setUsername(e.currentTarget.value)}
/>
</div>
<div>
<div className="text-xs mb-2">Password</div>
<Input
type="password"
placeholder={
showLockedCredential && !password ? "✱✱✱✱✱✱✱✱✱" : "Password"
}
value={password}
className={
showLockedCredential && !password ? "bg-secondary" : ""
}
onChange={(e) => setPassword(e.currentTarget.value)}
/>
</div>
</>
)}
<ConnectionStringInput
driver={driver}
onChange={setConnectionString}
value={connectionString}
showLockedCredential={showLockedCredential}
/>

<div className="mt-12 flex gap-2">
<Button onClick={onSaveClicked} disabled={loading}>
<Button onClick={onSaveClicked} disabled={loading || !valid}>
{loading && <LucideLoader className="w-4 h-4 mr-2 animate-spin" />}
Save
</Button>
Expand Down
Loading

0 comments on commit 902fd4e

Please sign in to comment.