190 lines
5.4 KiB
TypeScript
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>
|
|
);
|
|
}
|