sing-ui/components/connection-dialog.tsx

190 lines
5.4 KiB
TypeScript

"use client";
import { useState } from "react";
import { toast } from "sonner";
import { api, type Connection } from "@/components/api";
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";
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 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(),
});
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>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={saving}>
</Button>
<Button onClick={submit} disabled={saving}>
{saving ? "保存中…" : "保存"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}