sing-ui/components/connection-dialog.tsx

211 lines
6.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState } from "react";
import { toast } from "sonner";
import { api, type Connection } from "@/components/api";
import { keyValueLinesFromJson, parseKeyValueLines } from "@/components/kv";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
interface ConnectionDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
/** 传入则为编辑,否则为新建 */
initial?: Connection | null;
onSaved: () => void;
}
const EMPTY = {
name: "",
type: "mysql",
host: "127.0.0.1",
port: "3306",
user: "root",
password: "",
database: "",
};
export function ConnectionDialog({
open,
onOpenChange,
initial,
onSaved,
}: ConnectionDialogProps) {
const [form, setForm] = useState(() =>
initial
? {
name: initial.name,
type: initial.type,
host: initial.host,
port: String(initial.port),
user: initial.user,
password: initial.password,
database: initial.database,
}
: EMPTY
);
const [saving, setSaving] = useState(false);
const [paramsText, setParamsText] = useState(() =>
initial ? keyValueLinesFromJson(initial.params) : ""
);
const set = (k: keyof typeof EMPTY) => (e: React.ChangeEvent<HTMLInputElement>) =>
setForm((f) => ({ ...f, [k]: e.target.value }));
async function submit() {
if (!form.name.trim() || !form.host.trim() || !form.database.trim()) {
toast.error("请填写名称、host 和 database");
return;
}
const port = Number(form.port) || 3306;
setSaving(true);
try {
const body = JSON.stringify({
name: form.name.trim(),
type: form.type,
host: form.host.trim(),
port,
user: form.user.trim(),
password: form.password,
database: form.database.trim(),
params: parseKeyValueLines(paramsText),
});
if (initial) {
await api(`/api/connections/${initial.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body,
});
toast.success("连接已更新");
} else {
await api("/api/connections", {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
});
toast.success("连接已创建");
}
onOpenChange(false);
onSaved();
} catch (e) {
toast.error(e instanceof Error ? e.message : "保存失败");
} finally {
setSaving(false);
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{initial ? "编辑连接" : "新建连接"}</DialogTitle>
</DialogHeader>
<div className="grid gap-3">
<div className="grid gap-1.5">
<Label htmlFor="conn-name"></Label>
<Input
id="conn-name"
value={form.name}
onChange={set("name")}
placeholder="例如:测试环境 MySQL"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-1.5">
<Label></Label>
<Select
value={form.type}
onValueChange={(v) => setForm((f) => ({ ...f, type: String(v) }))}
>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="mysql">mysql</SelectItem>
<SelectItem value="mariadb">mariadb</SelectItem>
<SelectItem value="tidb">tidb</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid gap-1.5">
<Label htmlFor="conn-port"></Label>
<Input
id="conn-port"
type="number"
value={form.port}
onChange={set("port")}
/>
</div>
</div>
<div className="grid gap-1.5">
<Label htmlFor="conn-host">Host</Label>
<Input id="conn-host" value={form.host} onChange={set("host")} />
</div>
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-1.5">
<Label htmlFor="conn-user"></Label>
<Input id="conn-user" value={form.user} onChange={set("user")} />
</div>
<div className="grid gap-1.5">
<Label htmlFor="conn-password"></Label>
<Input
id="conn-password"
type="password"
value={form.password}
onChange={set("password")}
/>
</div>
</div>
<div className="grid gap-1.5">
<Label htmlFor="conn-db"></Label>
<Input
id="conn-db"
value={form.database}
onChange={set("database")}
/>
</div>
<div className="grid gap-1.5">
<Label htmlFor="conn-params"></Label>
<Textarea
id="conn-params"
className="font-mono text-xs"
rows={3}
value={paramsText}
onChange={(e) => setParamsText(e.target.value)}
placeholder={"tidb_skip_isolation_level_check=1"}
/>
<p className="text-xs text-muted-foreground">
key=value URL sling / atlas TiDB
tidb_skip_isolation_level_check=1
</p>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={saving}>
</Button>
<Button onClick={submit} disabled={saving}>
{saving ? "保存中…" : "保存"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}