Skip to main content
Glama

MCPDemo - Visual SQL Chat Platform

by Ayi456
index.tsx11.9 kB
import React, { useState, useEffect } from 'react'; import { Connection, Engine } from '../../types/sql-api.types'; interface ConnectionModalProps { isOpen: boolean; onClose: () => void; onSave: (connection: Connection) => void; connection?: Connection; // 编辑模式时传入 } export function ConnectionModal({ isOpen, onClose, onSave, connection }: ConnectionModalProps) { const [formData, setFormData] = useState<Partial<Connection>>({ title: '', engineType: Engine.MySQL, host: 'localhost', port: 3306, database: '', username: '', password: '', }); const [errors, setErrors] = useState<Record<string, string>>({}); const [isTesting, setIsTesting] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; message: string; } | null>(null); useEffect(() => { if (connection) { setFormData(connection); } else { setFormData({ title: '', engineType: Engine.MySQL, host: 'localhost', port: 3306, database: '', username: '', password: '', }); } setErrors({}); setTestResult(null); }, [connection, isOpen]); const validateForm = (): boolean => { const newErrors: Record<string, string> = {}; if (!formData.title?.trim()) { newErrors.title = '连接名称不能为空'; } if (!formData.host?.trim()) { newErrors.host = '主机地址不能为空'; } if (!formData.port || formData.port <= 0) { newErrors.port = '端口号必须大于 0'; } // 数据库名改为可选,连接后可查看所有数据库 if (!formData.username?.trim()) { newErrors.username = '用户名不能为空'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleTestConnection = async () => { if (!validateForm()) { setTestResult({ success: false, message: '请先填写完整的连接信息', }); return; } setIsTesting(true); setTestResult(null); try { // 导入 API 服务 const { sqlApiService } = await import('../../services/sqlApiService'); // 创建临时连接对象用于测试 const testConnection: Connection = { id: connection?.id || `temp-${Date.now()}`, title: formData.title!, engineType: formData.engineType!, host: formData.host!, port: formData.port!, database: formData.database || undefined, username: formData.username!, password: formData.password, }; const success = await sqlApiService.testConnection(testConnection); setTestResult({ success, message: success ? '✅ 连接测试成功!' : '❌ 连接测试失败,请检查配置', }); } catch (error: any) { setTestResult({ success: false, message: `❌ 测试失败: ${error.message}`, }); } finally { setIsTesting(false); } }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { return; } const connectionData: Connection = { id: connection?.id || `conn-${Date.now()}`, title: formData.title!, engineType: formData.engineType!, host: formData.host!, port: formData.port!, database: formData.database || undefined, username: formData.username!, password: formData.password, }; onSave(connectionData); onClose(); }; if (!isOpen) { return null; } return ( <div className="fixed inset-0 z-50 overflow-y-auto"> <div className="flex items-center justify-center min-h-screen px-4"> {/* 背景遮罩 */} <div className="fixed inset-0 bg-black opacity-50" onClick={onClose} ></div> {/* 模态框内容 */} <div className="relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full p-6"> <h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white"> {connection ? '编辑数据库连接' : '添加数据库连接'} </h2> <form onSubmit={handleSubmit} className="space-y-4"> {/* 连接名称 */} <div> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 连接名称 <span className="text-red-500">*</span> </label> <input type="text" value={formData.title || ''} onChange={(e) => setFormData({ ...formData, title: e.target.value })} className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ errors.title ? 'border-red-500' : 'border-gray-300 dark:border-gray-600' } dark:bg-gray-700 dark:text-white`} placeholder="例如: 生产数据库" /> {errors.title && ( <p className="text-red-500 text-sm mt-1">{errors.title}</p> )} </div> {/* 数据库类型 */} <div> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 数据库类型 </label> <select value={formData.engineType || Engine.MySQL} onChange={(e) => { const engineType = e.target.value as Engine; setFormData({ ...formData, engineType, port: engineType === Engine.MySQL ? 3306 : 5432 }); }} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white" > <option value={Engine.MySQL}>MySQL</option> <option value={Engine.PostgreSQL}>PostgreSQL</option> </select> </div> {/* 主机和端口 */} <div className="grid grid-cols-2 gap-4"> <div> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 主机地址 <span className="text-red-500">*</span> </label> <input type="text" value={formData.host || ''} onChange={(e) => setFormData({ ...formData, host: e.target.value })} className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ errors.host ? 'border-red-500' : 'border-gray-300 dark:border-gray-600' } dark:bg-gray-700 dark:text-white`} placeholder="localhost" /> {errors.host && ( <p className="text-red-500 text-sm mt-1">{errors.host}</p> )} </div> <div> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 端口 <span className="text-red-500">*</span> </label> <input type="number" value={formData.port || ''} onChange={(e) => setFormData({ ...formData, port: parseInt(e.target.value) })} className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ errors.port ? 'border-red-500' : 'border-gray-300 dark:border-gray-600' } dark:bg-gray-700 dark:text-white`} placeholder="3306" /> {errors.port && ( <p className="text-red-500 text-sm mt-1">{errors.port}</p> )} </div> </div> {/* 数据库名 */} <div> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 数据库名 <span className="text-gray-400 text-xs">(可选)</span> </label> <input type="text" value={formData.database || ''} onChange={(e) => setFormData({ ...formData, database: e.target.value })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white" placeholder="留空则连接后可查看所有数据库" /> <p className="text-gray-500 text-xs mt-1"> 💡 MySQL 可留空,PostgreSQL 不填则连接到 postgres 数据库 </p> </div> {/* 用户名 */} <div> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 用户名 <span className="text-red-500">*</span> </label> <input type="text" value={formData.username || ''} onChange={(e) => setFormData({ ...formData, username: e.target.value })} className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ errors.username ? 'border-red-500' : 'border-gray-300 dark:border-gray-600' } dark:bg-gray-700 dark:text-white`} placeholder="root" /> {errors.username && ( <p className="text-red-500 text-sm mt-1">{errors.username}</p> )} </div> {/* 密码 */} <div> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 密码 </label> <input type="password" value={formData.password || ''} onChange={(e) => setFormData({ ...formData, password: e.target.value })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white" placeholder="••••••••" /> </div> {/* 测试结果 */} {testResult && ( <div className={`p-3 rounded ${ testResult.success ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300' }`}> {testResult.message} </div> )} {/* 操作按钮 */} <div className="flex justify-end gap-3 pt-4"> <button type="button" onClick={handleTestConnection} disabled={isTesting} className="px-4 py-2 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-gray-700 rounded disabled:opacity-50" > {isTesting ? '测试中...' : '测试连接'} </button> <button type="button" onClick={onClose} className="px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" > 取消 </button> <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" > {connection ? '保存修改' : '添加连接'} </button> </div> </form> </div> </div> </div> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ayi456/visual-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server