diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index 6dd30a6b518..9ba5f60c876 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -773,6 +773,25 @@ const System = { return newVersion; }, + /** + * Validates a SQL connection string. + * @param {'postgresql'|'mysql'|'sql-server'} engine - the database engine identifier + * @param {string} connectionString - the connection string to validate + * @returns {Promise<{success: boolean, error: string | null}>} + */ + validateSQLConnection: async function (engine, connectionString) { + return fetch(`${API_BASE}/system/validate-sql-connection`, { + method: "POST", + headers: baseHeaders(), + body: JSON.stringify({ engine, connectionString }), + }) + .then((res) => res.json()) + .catch((e) => { + console.error("Failed to validate SQL connection:", e); + return { success: false, error: e.message }; + }); + }, + experimentalFeatures: { liveSync: LiveDocumentSync, agentPlugins: AgentPlugins, diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx index ee33feac1d5..3628a67a3f7 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx @@ -9,18 +9,16 @@ export const DB_LOGOS = { "sql-server": MSSQLLogo, }; -export default function DBConnection({ connection, onRemove, setHasChanges }) { +export default function DBConnection({ connection, onRemove }) { const { database_id, engine } = connection; function removeConfirmation() { if ( !window.confirm( `Delete ${database_id} from the list of available SQL connections? This cannot be undone.` ) - ) { + ) return false; - } onRemove(database_id); - setHasChanges(true); } return ( diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx index e2a8b313298..46c3d999a36 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx @@ -3,6 +3,8 @@ import { createPortal } from "react-dom"; import ModalWrapper from "@/components/ModalWrapper"; import { WarningOctagon, X } from "@phosphor-icons/react"; import { DB_LOGOS } from "./DBConnection"; +import System from "@/models/system"; +import showToast from "@/utils/toast"; function assembleConnectionString({ engine, @@ -37,9 +39,15 @@ const DEFAULT_CONFIG = { encrypt: false, }; -export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { +export default function NewSQLConnection({ + isOpen, + closeModal, + onSubmit, + setHasChanges, +}) { const [engine, setEngine] = useState(DEFAULT_ENGINE); const [config, setConfig] = useState(DEFAULT_CONFIG); + const [isValidating, setIsValidating] = useState(false); if (!isOpen) return null; function handleClose() { @@ -48,8 +56,8 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { closeModal(); } - function onFormChange() { - const form = new FormData(document.getElementById("sql-connection-form")); + function onFormChange(e) { + const form = new FormData(e.target.form); setConfig({ username: form.get("username").trim(), password: form.get("password"), @@ -64,12 +72,41 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { e.preventDefault(); e.stopPropagation(); const form = new FormData(e.target); - onSubmit({ - engine, - database_id: form.get("name"), - connectionString: assembleConnectionString({ engine, ...config }), - }); - handleClose(); + const connectionString = assembleConnectionString({ engine, ...config }); + + setIsValidating(true); + try { + const { success, error } = await System.validateSQLConnection( + engine, + connectionString + ); + if (!success) { + showToast( + error || + "Failed to establish database connection. Please check your connection details.", + "error" + ); + setIsValidating(false); + return; + } + + onSubmit({ + engine, + database_id: form.get("name"), + connectionString, + }); + setHasChanges(true); + handleClose(); + } catch (error) { + console.error("Error validating connection:", error); + showToast( + error?.message || + "Failed to validate connection. Please check your connection details.", + "error" + ); + } finally { + setIsValidating(false); + } return false; } @@ -95,8 +132,8 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
@@ -238,7 +275,6 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { name="encrypt" value="true" className="sr-only peer" - onChange={onFormChange} checked={config.encrypt} />
@@ -265,9 +301,10 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx index 515c3c897c9..428e55299b9 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx @@ -21,6 +21,17 @@ export default function AgentSQLConnectorSelection({ .catch(() => setConnections([])); }, []); + function handleRemoveConnection(databaseId) { + setHasChanges(true); + setConnections((prev) => + prev.map((conn) => { + if (conn.database_id === databaseId) + return { ...conn, action: "remove" }; + return conn; + }) + ); + } + return ( <>
@@ -81,16 +92,7 @@ export default function AgentSQLConnectorSelection({ { - setHasChanges(true); - setConnections((prev) => - prev.map((conn) => { - if (conn.database_id === databaseId) - return { ...conn, action: "remove" }; - return conn; - }) - ); - }} + onRemove={handleRemoveConnection} /> ))}