merlyn/frontend/src/pages/Admin/Agents/SQLConnectorSelection/NewConnectionModal.jsx
Timothy Carambat cd597a361e
OBDC Support (#1933)
* add possibility to connect to SQL Base by ODBC

---------

Co-authored-by: suchaudn <nicolas.suchaud@legrand.fr>
Co-authored-by: nicho2 <nicho2@laposte.net>
2024-07-23 12:42:53 -07:00

301 lines
12 KiB
JavaScript

import { useState } from "react";
import { createPortal } from "react-dom";
import ModalWrapper from "@/components/ModalWrapper";
import { WarningOctagon, X } from "@phosphor-icons/react";
import { DB_LOGOS } from "./DBConnection";
function assembleConnectionString({
engine,
username = "",
password = "",
host = "",
port = "",
database = "",
driver = "",
}) {
if ([username, password, host, database].every((i) => !!i) === false)
return `Please fill out all the fields above.`;
switch (engine) {
case "postgresql":
return `postgres://${username}:${password}@${host}:${port}/${database}`;
case "mysql":
return `mysql://${username}:${password}@${host}:${port}/${database}`;
case "sql-server":
return `mssql://${username}:${password}@${host}:${port}/${database}`;
case "odbc":
if (!driver) return `Please fill out the driver field.`;
return `Driver={${driver}};Server=${host};Port=${port};Database=${database};UID=${username};PWD=${password}`;
default:
return null;
}
}
const DEFAULT_ENGINE = "postgresql";
const DEFAULT_CONFIG = {
username: null,
password: null,
host: null,
port: null,
database: null,
driver: null,
};
export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
const [engine, setEngine] = useState(DEFAULT_ENGINE);
const [config, setConfig] = useState(DEFAULT_CONFIG);
if (!isOpen) return null;
function handleClose() {
setEngine(DEFAULT_ENGINE);
setConfig(DEFAULT_CONFIG);
closeModal();
}
function onFormChange() {
const form = new FormData(document.getElementById("sql-connection-form"));
setConfig({
username: form.get("username").trim(),
password: form.get("password"),
host: form.get("host").trim(),
port: form.get("port").trim(),
database: form.get("database").trim(),
driver: form.get("driver")?.trim(),
});
}
async function handleUpdate(e) {
e.preventDefault();
e.stopPropagation();
const form = new FormData(e.target);
onSubmit({
engine,
database_id: form.get("name"),
connectionString: assembleConnectionString({ engine, ...config }),
});
handleClose();
return false;
}
// Cannot do nested forms, it will cause all sorts of issues, so we portal this out
// to the parent container form so we don't have nested forms.
return createPortal(
<ModalWrapper isOpen={isOpen}>
<div className="relative w-full md:w-fit max-w-2xl max-h-full md:mt-8">
<div className="relative bg-main-gradient rounded-xl shadow-[0_4px_14px_rgba(0,0,0,0.25)] max-h-[85vh] overflow-y-scroll no-scroll">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
<h3 className="text-xl font-semibold text-white">
New SQL Connection
</h3>
<button
onClick={handleClose}
type="button"
className="border-none transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
>
<X className="text-gray-300 text-lg" />
</button>
</div>
<form
id="sql-connection-form"
onSubmit={handleUpdate}
onChange={onFormChange}
>
<div className="py-[17px] px-[20px] flex flex-col gap-y-6">
<p className="text-sm text-white">
Add the connection information for your database below and it
will be available for future SQL agent calls.
</p>
<div className="flex flex-col w-full">
<div className="border border-red-800 bg-zinc-800 p-4 rounded-lg flex items-center gap-x-2 text-sm text-red-400">
<WarningOctagon size={28} className="shrink-0" />
<p>
<b>WARNING:</b> The SQL agent has been <i>instructed</i> to
only perform non-modifying queries. This <b>does not</b>{" "}
prevent a hallucination from still deleting data. Only
connect with a user who has <b>READ_ONLY</b> permissions.
</p>
</div>
<label className="text-white text-sm font-semibold block my-4">
Select your SQL engine
</label>
<div className="flex flex-wrap gap-x-4 gap-y-4">
<DBEngine
provider="postgresql"
active={engine === "postgresql"}
onClick={() => setEngine("postgresql")}
/>
<DBEngine
provider="mysql"
active={engine === "mysql"}
onClick={() => setEngine("mysql")}
/>
<DBEngine
provider="sql-server"
active={engine === "sql-server"}
onClick={() => setEngine("sql-server")}
/>
<DBEngine
provider="odbc"
active={engine === "odbc"}
onClick={() => setEngine("odbc")}
/>
</div>
</div>
<div className="flex flex-col w-full">
<label className="text-white text-sm font-semibold block mb-3">
Connection name
</label>
<input
type="text"
name="name"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="a unique name to identify this SQL connection"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Database user
</label>
<input
type="text"
name="username"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="root"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Database user password
</label>
<input
type="text"
name="password"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="password123"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div className="sm:col-span-2">
<label className="text-white text-sm font-semibold block mb-3">
Server endpoint
</label>
<input
type="text"
name="host"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="the hostname or endpoint for your database"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="text-white text-sm font-semibold block mb-3">
Port
</label>
<input
type="text"
name="port"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="3306"
required={false}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Database
</label>
<input
type="text"
name="database"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="the database the agent will interact with"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
{engine === "odbc" && (
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Driver
</label>
<input
type="text"
name="driver"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="the driver to use eg: MongoDB ODBC 1.2.0 ANSI Driver"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
)}
<p className="text-white/40 text-sm">
{assembleConnectionString({ engine, ...config })}
</p>
</div>
<div className="flex w-full justify-between items-center p-3 space-x-2 border-t rounded-b border-gray-500/50">
<button
type="button"
onClick={handleClose}
className="border-none text-xs px-2 py-1 font-semibold rounded-lg bg-white hover:bg-transparent border-2 border-transparent hover:border-white hover:text-white h-[32px] w-fit -mr-8 whitespace-nowrap shadow-[0_4px_14px_rgba(0,0,0,0.25)]"
>
Cancel
</button>
<button
type="submit"
form="sql-connection-form"
className="border-none text-xs px-2 py-1 font-semibold rounded-lg bg-primary-button hover:bg-secondary border-2 border-transparent hover:border-primary-button hover:text-white h-[32px] w-fit -mr-8 whitespace-nowrap shadow-[0_4px_14px_rgba(0,0,0,0.25)]"
>
Save connection
</button>
</div>
</form>
</div>
</div>
</ModalWrapper>,
document.getElementById("workspace-agent-settings-container")
);
}
function DBEngine({ provider, active, onClick }) {
return (
<button
type="button"
onClick={onClick}
className={`flex flex-col p-4 border border-white/40 bg-zinc-800 rounded-lg w-fit hover:bg-zinc-700 ${
active ? "!bg-blue-500/50" : ""
}`}
>
<img
src={DB_LOGOS[provider]}
className="h-[100px] rounded-md"
alt={provider}
/>
</button>
);
}