import { useState, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Loader2, Check, AlertTriangle, Upload } from "lucide-react"; import { api } from "@/lib/api"; export interface DataSourceConfig { id?: number; name: string; type: string; config: Record; } interface DataSourceFormProps { initialData?: DataSourceConfig | null; onSubmit: (data: Omit) => Promise; onTest: (type: string, config: Record) => Promise; onCancel: () => void; } export function DataSourceForm({ initialData, onSubmit, onTest, onCancel }: DataSourceFormProps) { const [name, setName] = useState(initialData?.name || ""); const [type, setType] = useState(initialData?.type || "postgres"); const [config, setConfig] = useState>(initialData?.config || {}); const [isTesting, setIsTesting] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null); const [isSaving, setIsSaving] = useState(false); const [isUploading, setIsUploading] = useState(false); const fileInputRef = useRef(null); const handleConfigChange = (key: string, value: any) => { setConfig(prev => ({ ...prev, [key]: value })); }; const handleFileSelect = () => { fileInputRef.current?.click(); }; const handleFileUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setIsUploading(true); const formData = new FormData(); formData.append("file", file); try { // @ts-ignore const res = await api.post("/api/v1/upload/file", formData); if (res && (res as any).url) { handleConfigChange("file_path", (res as any).url); } } catch (error) { console.error("Upload failed", error); alert("上传失败"); } finally { setIsUploading(false); // Clear input value so same file can be selected again if (fileInputRef.current) { fileInputRef.current.value = ""; } } }; const handleTest = async () => { setIsTesting(true); setTestResult(null); try { const success = await onTest(type, config); setTestResult({ success, message: success ? "连接成功" : "连接失败", }); } catch (e: any) { setTestResult({ success: false, message: e.message || "连接失败", }); } finally { setIsTesting(false); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSaving(true); try { await onSubmit({ name, type, config }); } finally { setIsSaving(false); } }; const renderConfigFields = () => { switch (type) { case "postgres": case "supabase": return (
handleConfigChange("host", e.target.value)} placeholder="localhost" />
handleConfigChange("port", parseInt(e.target.value))} placeholder="5432" />
handleConfigChange("database", e.target.value)} placeholder="postgres" />
handleConfigChange("user", e.target.value)} placeholder="postgres" />
handleConfigChange("password", e.target.value)} placeholder="••••••" />
或者使用连接字符串 (覆盖上述设置):
handleConfigChange("connection_string", e.target.value)} placeholder="postgresql://user:pass@host:5432/db" />
); case "clickhouse": return (
handleConfigChange("host", e.target.value)} placeholder="localhost" />
handleConfigChange("port", parseInt(e.target.value))} placeholder="9000" />
handleConfigChange("database", e.target.value)} placeholder="default" />
handleConfigChange("user", e.target.value)} placeholder="default" />
handleConfigChange("password", e.target.value)} placeholder="••••••" />
); case "sqlite": return (
handleConfigChange("file_path", e.target.value)} placeholder="/path/to/database.db" />
); case "parquet": return (
handleConfigChange("file_path", e.target.value)} placeholder="/path/to/data.parquet" />
); default: return null; } }; return (
setName(e.target.value)} placeholder="我的数据源" required />
{renderConfigFields()}
{testResult && (
{testResult.success ? : } {testResult.message}
)}
); }