From 6b28ae8d30a5eb6db75e69db6dd2312b11370473 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Mon, 14 Jul 2025 17:02:53 -0700 Subject: [PATCH 1/5] wip sql connection string validation --- server/models/systemSettings.js | 50 +++++++++++-------- .../plugins/sql-agent/SQLConnectors/MSSQL.js | 9 ++++ .../plugins/sql-agent/SQLConnectors/MySQL.js | 9 ++++ .../sql-agent/SQLConnectors/Postgresql.js | 9 ++++ .../plugins/sql-agent/SQLConnectors/index.js | 16 ++++++ 5 files changed, 71 insertions(+), 22 deletions(-) diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index bb7311fb857..d7ad4057d1b 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -9,6 +9,9 @@ const { v4 } = require("uuid"); const { MetaGenerator } = require("../utils/boot/MetaGenerator"); const { PGVector } = require("../utils/vectorDbProviders/pgvector"); const { getBaseLLMProviderModel } = require("../utils/helpers"); +const { + validateConnection, +} = require("../utils/agents/aibitat/plugins/sql-agent/SQLConnectors"); function isNullOrNaN(value) { if (value === null) return true; @@ -144,7 +147,7 @@ const SystemSettings = { [] ); try { - const updatedConnections = mergeConnections( + const updatedConnections = await mergeConnections( existingConnections, safeJsonParse(updates, []) ); @@ -629,7 +632,7 @@ const SystemSettings = { }, }; -function mergeConnections(existingConnections = [], updates = []) { +async function mergeConnections(existingConnections = [], updates = []) { let updatedConnections = [...existingConnections]; const existingDbIds = existingConnections.map((conn) => conn.database_id); @@ -641,28 +644,31 @@ function mergeConnections(existingConnections = [], updates = []) { (conn) => !toRemove.includes(conn.database_id) ); - // Next add all 'action:add' candidates into the updatedConnections; We DO NOT validate the connection strings. - // but we do validate their database_id is unique. - updates - .filter((conn) => conn.action === "add") - .forEach((update) => { - if (!update.connectionString) return; // invalid connection string - - // Remap name to be unique to entire set. - if (existingDbIds.includes(update.database_id)) { - update.database_id = slugify( - `${update.database_id}-${v4().slice(0, 4)}` - ); - } else { - update.database_id = slugify(update.database_id); - } + // Next add all 'action:add' candidates into the updatedConnections + for (const update of updates.filter((conn) => conn.action === "add")) { + if (!update.connectionString) continue; // invalid connection string - updatedConnections.push({ - engine: update.engine, - database_id: update.database_id, - connectionString: update.connectionString, - }); + // Validate the connection before adding it + const { success, error } = await validateConnection(update.engine, { + connectionString: update.connectionString, + }); + if (!success) { + throw new Error(`Failed to validate connection: ${error}`); + } + + // Remap name to be unique to entire set. + if (existingDbIds.includes(update.database_id)) { + update.database_id = slugify(`${update.database_id}-${v4().slice(0, 4)}`); + } else { + update.database_id = slugify(update.database_id); + } + + updatedConnections.push({ + engine: update.engine, + database_id: update.database_id, + connectionString: update.connectionString, }); + } return updatedConnections; } diff --git a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MSSQL.js b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MSSQL.js index 153c8c77015..2282f3f6fd4 100644 --- a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MSSQL.js +++ b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MSSQL.js @@ -77,6 +77,15 @@ class MSSQLConnector { return result; } + async validateConnection() { + try { + const result = await this.runQuery("SELECT 1"); + return { success: !result.error, error: result.error }; + } catch (error) { + return { success: false, error: error.message }; + } + } + getTablesSql() { return `SELECT name FROM sysobjects WHERE xtype='U';`; } diff --git a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MySQL.js b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MySQL.js index 5434e7d68a2..213ebb22faf 100644 --- a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MySQL.js +++ b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MySQL.js @@ -48,6 +48,15 @@ class MySQLConnector { return result; } + async validateConnection() { + try { + const result = await this.runQuery("SELECT 1"); + return { success: !result.error, error: result.error }; + } catch (error) { + return { success: false, error: error.message }; + } + } + getTablesSql() { return `SELECT table_name FROM information_schema.tables WHERE table_schema = '${this.database_id}'`; } diff --git a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/Postgresql.js b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/Postgresql.js index 463fea51018..b432247a55c 100644 --- a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/Postgresql.js +++ b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/Postgresql.js @@ -41,6 +41,15 @@ class PostgresSQLConnector { return result; } + async validateConnection() { + try { + const result = await this.runQuery("SELECT 1"); + return { success: !result.error, error: result.error }; + } catch (error) { + return { success: false, error: error.message }; + } + } + getTablesSql() { return `SELECT * FROM pg_catalog.pg_tables WHERE schemaname = 'public'`; } diff --git a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/index.js b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/index.js index 9cf1e1ff4d7..ad68794615e 100644 --- a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/index.js +++ b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/index.js @@ -54,7 +54,23 @@ async function listSQLConnections() { ); } +/** + * Validates a SQL connection by attempting to connect and run a simple query + * @param {SQLEngine} identifier - The SQL engine type + * @param {object} connectionConfig - The connection configuration + * @returns {Promise<{success: boolean, error: string|null}>} + */ +async function validateConnection(identifier = "", connectionConfig = {}) { + try { + const client = getDBClient(identifier, connectionConfig); + return await client.validateConnection(); + } catch (error) { + return { success: false, error: error.message }; + } +} + module.exports = { getDBClient, listSQLConnections, + validateConnection, }; From 2a26b1976e6659582c2311cbc45cf7b4e191c327 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Tue, 15 Jul 2025 12:59:24 -0700 Subject: [PATCH 2/5] handle failed sql connections in frontend --- frontend/src/models/admin.js | 10 +++- .../Agents/SQLConnectorSelection/index.jsx | 19 ++++++++ frontend/src/pages/Admin/Agents/index.jsx | 36 ++++++++++++--- server/endpoints/admin.js | 9 ++-- server/models/systemSettings.js | 46 +++++++++++-------- 5 files changed, 88 insertions(+), 32 deletions(-) diff --git a/frontend/src/models/admin.js b/frontend/src/models/admin.js index 336e98789b4..1798c30ed2a 100644 --- a/frontend/src/models/admin.js +++ b/frontend/src/models/admin.js @@ -195,9 +195,15 @@ const Admin = { headers: baseHeaders(), body: JSON.stringify(updates), }) - .then((res) => res.json()) + .then(async (res) => { + const data = await res.json(); + if (!res.ok || !data.success) { + throw new Error(data.error || "Failed to update system preferences"); + } + return data; + }) .catch((e) => { - console.error(e); + console.error("Failed to update system preferences:", e); return { success: false, error: e.message }; }); }, diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx index 515c3c897c9..fdc8d6252ef 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx @@ -21,6 +21,25 @@ export default function AgentSQLConnectorSelection({ .catch(() => setConnections([])); }, []); + // Update connections state when validation fails + useEffect(() => { + const handleValidationError = (event) => { + if ( + event.detail?.error + ?.toLowerCase() + ?.includes("failed to connect to database") + ) { + // Remove any connections marked as "add" since they failed validation + setConnections((prev) => prev.filter((conn) => conn.action !== "add")); + setHasChanges(false); + } + }; + + window.addEventListener("sql_validation_error", handleValidationError); + return () => + window.removeEventListener("sql_validation_error", handleValidationError); + }, []); + return ( <>
diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index a322be5abf0..cb8699fdea7 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -148,16 +148,22 @@ export default function AdminAgents() { data.workspace[key] = castToType(key, value); } - const { success } = await Admin.updateSystemPreferences(data.system); - await System.updateSystem(data.env); + try { + const { success, error } = await Admin.updateSystemPreferences( + data.system + ); + if (!success) { + throw new Error(error); + } - if (success) { + await System.updateSystem(data.env); const _settings = await System.keys(); const _preferences = await Admin.systemPreferencesByFields([ "disabled_agent_skills", "default_agent_skills", "imported_agent_skills", ]); + setSettings({ ..._settings, preferences: _preferences.settings } ?? {}); setAgentSkills(_preferences.settings?.default_agent_skills ?? []); setDisabledAgentSkills( @@ -167,11 +173,27 @@ export default function AdminAgents() { showToast(`Agent preferences saved successfully.`, "success", { clear: true, }); - } else { - showToast(`Agent preferences failed to save.`, "error", { clear: true }); + setHasChanges(false); + } catch (error) { + console.error("Failed to save preferences:", error); + const isDbError = error.message + ?.toLowerCase() + .includes("failed to connect to database"); + + // Send event to SQL Agent component to show error message + if (isDbError) { + window.dispatchEvent( + new CustomEvent("sql_validation_error", { + detail: { error: error.message }, + }) + ); + } + const errorMessage = isDbError + ? "Failed to save: Invalid database connection. Please check your connection details and try again." + : error.message || "Failed to save agent preferences."; + showToast(errorMessage, "error", { clear: true }); + setHasChanges(true); } - - setHasChanges(false); }; let SelectedSkillComponent = null; diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index 4964c35a133..cbee23347f3 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -484,11 +484,14 @@ function adminEndpoints(app) { async (request, response) => { try { const updates = reqBody(request); - await SystemSettings.updateSettings(updates); + const { success, error } = await SystemSettings.updateSettings(updates); + if (!success) { + return response.status(400).json({ success: false, error }); + } response.status(200).json({ success: true, error: null }); } catch (e) { - console.error(e); - response.sendStatus(500).end(); + console.error("FAILED TO UPDATE SYSTEM SETTINGS", e.message); + response.status(500).json({ success: false, error: e.message }); } } ); diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index d7ad4057d1b..cae83afe6fc 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -153,8 +153,8 @@ const SystemSettings = { ); return JSON.stringify(updatedConnections); } catch (e) { - console.error(`Failed to merge connections`); - return JSON.stringify(existingConnections ?? []); + console.error(`Failed to merge connections`, e); + throw new Error(e.message); } }, experimental_live_file_sync: (update) => { @@ -648,26 +648,32 @@ async function mergeConnections(existingConnections = [], updates = []) { for (const update of updates.filter((conn) => conn.action === "add")) { if (!update.connectionString) continue; // invalid connection string - // Validate the connection before adding it - const { success, error } = await validateConnection(update.engine, { - connectionString: update.connectionString, - }); - if (!success) { - throw new Error(`Failed to validate connection: ${error}`); - } + try { + // Validate the connection before adding it + const { success, error } = await validateConnection(update.engine, { + connectionString: update.connectionString, + }); + if (!success) { + throw new Error(`Failed to connect to database: ${error}`); + } - // Remap name to be unique to entire set. - if (existingDbIds.includes(update.database_id)) { - update.database_id = slugify(`${update.database_id}-${v4().slice(0, 4)}`); - } else { - update.database_id = slugify(update.database_id); - } + // Remap name to be unique to entire set. + if (existingDbIds.includes(update.database_id)) { + update.database_id = slugify( + `${update.database_id}-${v4().slice(0, 4)}` + ); + } else { + update.database_id = slugify(update.database_id); + } - updatedConnections.push({ - engine: update.engine, - database_id: update.database_id, - connectionString: update.connectionString, - }); + updatedConnections.push({ + engine: update.engine, + database_id: update.database_id, + connectionString: update.connectionString, + }); + } catch (error) { + throw new Error(`Failed to validate connection: ${error.message}`); + } } return updatedConnections; From 64e124a7a56abc168902ba3b4f3993f5ac131059 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Tue, 15 Jul 2025 18:40:06 -0700 Subject: [PATCH 3/5] sql preflight connection validation on modal save --- frontend/src/models/system.js | 13 ++++ .../SQLConnectorSelection/DBConnection.jsx | 3 +- .../NewConnectionModal.jsx | 68 +++++++++++++++---- .../Agents/SQLConnectorSelection/index.jsx | 7 +- server/endpoints/system.js | 36 ++++++++++ .../plugins/sql-agent/SQLConnectors/MSSQL.js | 7 +- .../plugins/sql-agent/SQLConnectors/MySQL.js | 7 +- .../sql-agent/SQLConnectors/Postgresql.js | 7 +- .../plugins/sql-agent/SQLConnectors/index.js | 6 +- 9 files changed, 128 insertions(+), 26 deletions(-) diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index 6dd30a6b518..7ac364b727b 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -773,6 +773,19 @@ const System = { return newVersion; }, + 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..bb591cadd56 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx @@ -9,7 +9,7 @@ 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 ( @@ -20,7 +20,6 @@ export default function DBConnection({ connection, onRemove, setHasChanges }) { 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 1d781894ce7..640f32309fc 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, @@ -35,9 +37,15 @@ const DEFAULT_CONFIG = { database: null, }; -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() { @@ -61,12 +69,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; } @@ -90,11 +127,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
-
+

@@ -147,6 +180,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { required={true} autoComplete="off" spellCheck={false} + onChange={onFormChange} />

@@ -163,6 +197,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { required={true} autoComplete="off" spellCheck={false} + onChange={onFormChange} />
@@ -177,6 +212,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { required={true} autoComplete="off" spellCheck={false} + onChange={onFormChange} />
@@ -194,6 +230,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { required={true} autoComplete="off" spellCheck={false} + onChange={onFormChange} />
@@ -208,6 +245,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { required={false} autoComplete="off" spellCheck={false} + onChange={onFormChange} />
@@ -224,6 +262,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { required={true} autoComplete="off" spellCheck={false} + onChange={onFormChange} />

@@ -242,9 +281,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..d9c325e96df 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx @@ -120,9 +120,10 @@ export default function AgentSQLConnectorSelection({ - setConnections((prev) => [...prev, { action: "add", ...newDb }]) - } + onSubmit={(newDb) => { + setConnections((prev) => [...prev, { action: "add", ...newDb }]); + }} + setHasChanges={setHasChanges} /> ); diff --git a/server/endpoints/system.js b/server/endpoints/system.js index ee3778be01c..a44dc893f5c 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -1383,6 +1383,42 @@ function systemEndpoints(app) { } } ); + + app.post( + "/system/validate-sql-connection", + [validatedRequest], + async (request, response) => { + try { + const { engine, connectionString } = reqBody(request); + if (!engine || !connectionString) { + return response.status(400).json({ + success: false, + error: "Both engine and connection details are required.", + }); + } + + const { + validateConnection, + } = require("../utils/agents/aibitat/plugins/sql-agent/SQLConnectors"); + const result = await validateConnection(engine, { connectionString }); + + if (!result.success) { + return response.status(200).json({ + success: false, + error: `Unable to connect to ${engine}. Please verify your connection details.`, + }); + } + + response.status(200).json(result); + } catch (error) { + console.error("SQL validation error:", error); + response.status(500).json({ + success: false, + error: `Unable to connect to ${engine}. Please verify your connection details.`, + }); + } + } + ); } module.exports = { systemEndpoints }; diff --git a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MSSQL.js b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MSSQL.js index 2282f3f6fd4..65cc50f1179 100644 --- a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MSSQL.js +++ b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MSSQL.js @@ -71,8 +71,11 @@ class MSSQLConnector { console.log(this.constructor.name, err); result.error = err.message; } finally { - await this._client.close(); - this.#connected = false; + // Check client is connected before closing since we use this for validation + if (this._client) { + await this._client.close(); + this.#connected = false; + } } return result; } diff --git a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MySQL.js b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MySQL.js index 213ebb22faf..f99f77adc34 100644 --- a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MySQL.js +++ b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/MySQL.js @@ -42,8 +42,11 @@ class MySQLConnector { console.log(this.constructor.name, err); result.error = err.message; } finally { - await this._client.end(); - this.#connected = false; + // Check client is connected before closing since we use this for validation + if (this._client) { + await this._client.end(); + this.#connected = false; + } } return result; } diff --git a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/Postgresql.js b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/Postgresql.js index b432247a55c..72d479f0612 100644 --- a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/Postgresql.js +++ b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/Postgresql.js @@ -35,8 +35,11 @@ class PostgresSQLConnector { console.log(this.constructor.name, err); result.error = err.message; } finally { - await this._client.end(); - this.#connected = false; + // Check client is connected before closing since we use this for validation + if (this._client) { + await this._client.end(); + this.#connected = false; + } } return result; } diff --git a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/index.js b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/index.js index ad68794615e..82353683e99 100644 --- a/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/index.js +++ b/server/utils/agents/aibitat/plugins/sql-agent/SQLConnectors/index.js @@ -65,7 +65,11 @@ async function validateConnection(identifier = "", connectionConfig = {}) { const client = getDBClient(identifier, connectionConfig); return await client.validateConnection(); } catch (error) { - return { success: false, error: error.message }; + console.log(`Failed to connect to ${identifier} database.`); + return { + success: false, + error: `Unable to connect to ${identifier}. Please verify your connection details.`, + }; } } From 3174fdac9a7eb035d9dd6ce19e2b4e272a12c844 Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Tue, 15 Jul 2025 18:53:38 -0700 Subject: [PATCH 4/5] revert unneeded be/fe changes --- frontend/src/models/admin.js | 10 ++---- .../Agents/SQLConnectorSelection/index.jsx | 19 ---------- frontend/src/pages/Admin/Agents/index.jsx | 36 ++++--------------- server/endpoints/admin.js | 9 ++--- server/models/systemSettings.js | 34 ++++++------------ 5 files changed, 23 insertions(+), 85 deletions(-) diff --git a/frontend/src/models/admin.js b/frontend/src/models/admin.js index 1798c30ed2a..336e98789b4 100644 --- a/frontend/src/models/admin.js +++ b/frontend/src/models/admin.js @@ -195,15 +195,9 @@ const Admin = { headers: baseHeaders(), body: JSON.stringify(updates), }) - .then(async (res) => { - const data = await res.json(); - if (!res.ok || !data.success) { - throw new Error(data.error || "Failed to update system preferences"); - } - return data; - }) + .then((res) => res.json()) .catch((e) => { - console.error("Failed to update system preferences:", e); + console.error(e); return { success: false, error: e.message }; }); }, diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx index 5d0ab9e7eaf..d9c325e96df 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx @@ -21,25 +21,6 @@ export default function AgentSQLConnectorSelection({ .catch(() => setConnections([])); }, []); - // Update connections state when validation fails - useEffect(() => { - const handleValidationError = (event) => { - if ( - event.detail?.error - ?.toLowerCase() - ?.includes("failed to connect to database") - ) { - // Remove any connections marked as "add" since they failed validation - setConnections((prev) => prev.filter((conn) => conn.action !== "add")); - setHasChanges(false); - } - }; - - window.addEventListener("sql_validation_error", handleValidationError); - return () => - window.removeEventListener("sql_validation_error", handleValidationError); - }, []); - return ( <>
diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index cb8699fdea7..a322be5abf0 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -148,22 +148,16 @@ export default function AdminAgents() { data.workspace[key] = castToType(key, value); } - try { - const { success, error } = await Admin.updateSystemPreferences( - data.system - ); - if (!success) { - throw new Error(error); - } + const { success } = await Admin.updateSystemPreferences(data.system); + await System.updateSystem(data.env); - await System.updateSystem(data.env); + if (success) { const _settings = await System.keys(); const _preferences = await Admin.systemPreferencesByFields([ "disabled_agent_skills", "default_agent_skills", "imported_agent_skills", ]); - setSettings({ ..._settings, preferences: _preferences.settings } ?? {}); setAgentSkills(_preferences.settings?.default_agent_skills ?? []); setDisabledAgentSkills( @@ -173,27 +167,11 @@ export default function AdminAgents() { showToast(`Agent preferences saved successfully.`, "success", { clear: true, }); - setHasChanges(false); - } catch (error) { - console.error("Failed to save preferences:", error); - const isDbError = error.message - ?.toLowerCase() - .includes("failed to connect to database"); - - // Send event to SQL Agent component to show error message - if (isDbError) { - window.dispatchEvent( - new CustomEvent("sql_validation_error", { - detail: { error: error.message }, - }) - ); - } - const errorMessage = isDbError - ? "Failed to save: Invalid database connection. Please check your connection details and try again." - : error.message || "Failed to save agent preferences."; - showToast(errorMessage, "error", { clear: true }); - setHasChanges(true); + } else { + showToast(`Agent preferences failed to save.`, "error", { clear: true }); } + + setHasChanges(false); }; let SelectedSkillComponent = null; diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index cbee23347f3..4964c35a133 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -484,14 +484,11 @@ function adminEndpoints(app) { async (request, response) => { try { const updates = reqBody(request); - const { success, error } = await SystemSettings.updateSettings(updates); - if (!success) { - return response.status(400).json({ success: false, error }); - } + await SystemSettings.updateSettings(updates); response.status(200).json({ success: true, error: null }); } catch (e) { - console.error("FAILED TO UPDATE SYSTEM SETTINGS", e.message); - response.status(500).json({ success: false, error: e.message }); + console.error(e); + response.sendStatus(500).end(); } } ); diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index cae83afe6fc..bb7311fb857 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -9,9 +9,6 @@ const { v4 } = require("uuid"); const { MetaGenerator } = require("../utils/boot/MetaGenerator"); const { PGVector } = require("../utils/vectorDbProviders/pgvector"); const { getBaseLLMProviderModel } = require("../utils/helpers"); -const { - validateConnection, -} = require("../utils/agents/aibitat/plugins/sql-agent/SQLConnectors"); function isNullOrNaN(value) { if (value === null) return true; @@ -147,14 +144,14 @@ const SystemSettings = { [] ); try { - const updatedConnections = await mergeConnections( + const updatedConnections = mergeConnections( existingConnections, safeJsonParse(updates, []) ); return JSON.stringify(updatedConnections); } catch (e) { - console.error(`Failed to merge connections`, e); - throw new Error(e.message); + console.error(`Failed to merge connections`); + return JSON.stringify(existingConnections ?? []); } }, experimental_live_file_sync: (update) => { @@ -632,7 +629,7 @@ const SystemSettings = { }, }; -async function mergeConnections(existingConnections = [], updates = []) { +function mergeConnections(existingConnections = [], updates = []) { let updatedConnections = [...existingConnections]; const existingDbIds = existingConnections.map((conn) => conn.database_id); @@ -644,18 +641,12 @@ async function mergeConnections(existingConnections = [], updates = []) { (conn) => !toRemove.includes(conn.database_id) ); - // Next add all 'action:add' candidates into the updatedConnections - for (const update of updates.filter((conn) => conn.action === "add")) { - if (!update.connectionString) continue; // invalid connection string - - try { - // Validate the connection before adding it - const { success, error } = await validateConnection(update.engine, { - connectionString: update.connectionString, - }); - if (!success) { - throw new Error(`Failed to connect to database: ${error}`); - } + // Next add all 'action:add' candidates into the updatedConnections; We DO NOT validate the connection strings. + // but we do validate their database_id is unique. + updates + .filter((conn) => conn.action === "add") + .forEach((update) => { + if (!update.connectionString) return; // invalid connection string // Remap name to be unique to entire set. if (existingDbIds.includes(update.database_id)) { @@ -671,10 +662,7 @@ async function mergeConnections(existingConnections = [], updates = []) { database_id: update.database_id, connectionString: update.connectionString, }); - } catch (error) { - throw new Error(`Failed to validate connection: ${error.message}`); - } - } + }); return updatedConnections; } From 6e6ede99fee58634ae288df3d6d6482ba1ae46ea Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Wed, 16 Jul 2025 09:01:56 -0700 Subject: [PATCH 5/5] linting, form updates --- frontend/src/models/system.js | 6 ++++ .../SQLConnectorSelection/DBConnection.jsx | 3 +- .../NewConnectionModal.jsx | 17 +++++------ .../Agents/SQLConnectorSelection/index.jsx | 28 ++++++++++--------- server/endpoints/system.js | 2 +- .../plugins/sql-agent/SQLConnectors/MSSQL.js | 2 +- .../plugins/sql-agent/SQLConnectors/MySQL.js | 2 +- .../sql-agent/SQLConnectors/Postgresql.js | 2 +- 8 files changed, 33 insertions(+), 29 deletions(-) diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index 7ac364b727b..9ba5f60c876 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -773,6 +773,12 @@ 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", diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx index bb591cadd56..3628a67a3f7 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/DBConnection.jsx @@ -16,9 +16,8 @@ export default function DBConnection({ connection, onRemove }) { !window.confirm( `Delete ${database_id} from the list of available SQL connections? This cannot be undone.` ) - ) { + ) return false; - } onRemove(database_id); } diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx index 163e3094235..46c3d999a36 100644 --- a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx +++ b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx @@ -56,8 +56,8 @@ export default function NewSQLConnection({ 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"), @@ -130,7 +130,11 @@ export default function NewSQLConnection({
-
+

@@ -183,7 +187,6 @@ export default function NewSQLConnection({ required={true} autoComplete="off" spellCheck={false} - onChange={onFormChange} />

@@ -200,7 +203,6 @@ export default function NewSQLConnection({ required={true} autoComplete="off" spellCheck={false} - onChange={onFormChange} />
@@ -215,7 +217,6 @@ export default function NewSQLConnection({ required={true} autoComplete="off" spellCheck={false} - onChange={onFormChange} />
@@ -233,7 +234,6 @@ export default function NewSQLConnection({ required={true} autoComplete="off" spellCheck={false} - onChange={onFormChange} />
@@ -248,7 +248,6 @@ export default function NewSQLConnection({ required={false} autoComplete="off" spellCheck={false} - onChange={onFormChange} />
@@ -265,7 +264,6 @@ export default function NewSQLConnection({ required={true} autoComplete="off" spellCheck={false} - onChange={onFormChange} /> @@ -277,7 +275,6 @@ export default function NewSQLConnection({ name="encrypt" value="true" className="sr-only peer" - onChange={onFormChange} checked={config.encrypt} />
diff --git a/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx b/frontend/src/pages/Admin/Agents/SQLConnectorSelection/index.jsx index d9c325e96df..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} /> ))}