Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7,033 changes: 7,033 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,10 @@ pub async fn update_connection<R: Runtime>(

persistence::save_connections_file(&path, &conn_file)?;

// Invalidate the cached pool so reconnecting picks up any changed settings
// (e.g. max_connections). Only affects this specific connection.
crate::pool_manager::close_pool_with_id(&params, Some(&id)).await;

let mut returned_conn = updated;
returned_conn.params = params;
Ok(returned_conn)
Expand Down
7 changes: 7 additions & 0 deletions src-tauri/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ pub fn save_config(app: AppHandle, config: AppConfig) -> Result<(), String> {
existing_config.plugins = config.plugins;
}

if config.plugins.is_some() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Duplicate code - this if config.plugins.is_some() block is identical to the one on lines 141-143. This appears to be an accidental duplication during the merge. Remove lines 145-147.

existing_config.plugins = config.plugins;
}

let content = serde_json::to_string_pretty(&existing_config).map_err(|e| e.to_string())?;
fs::write(config_path, content).map_err(|e| e.to_string())?;
Ok(())
Expand Down Expand Up @@ -225,6 +229,9 @@ pub fn delete_ai_key(provider: String) -> Result<(), String> {
keychain_utils::delete_ai_key(&provider)
}

pub const DEFAULT_MAX_CONNECTIONS: u32 = 10;


/// Get the configured maximum BLOB size in bytes, or DEFAULT_MAX_BLOB_SIZE if not set
pub fn get_max_blob_size<R: tauri::Runtime>(app: &AppHandle<R>) -> u64 {
let config = load_config_internal(app);
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ pub struct ConnectionParams {
// Connection ID for stable pooling (not persisted, set at runtime)
#[serde(skip_serializing_if = "Option::is_none")]
pub connection_id: Option<String>,
// Per-connection pool size
#[serde(skip_serializing_if = "Option::is_none")]
pub max_connections: Option<u32>,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
Expand Down
12 changes: 6 additions & 6 deletions src-tauri/src/pool_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ static POSTGRES_POOLS: Lazy<PoolMap<Postgres>> =
Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
static SQLITE_POOLS: Lazy<PoolMap<Sqlite>> = Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));

const SQLITE_MAX_CONNECTIONS_CAP: u32 = 5;

/// Build a stable connection key that works with SSH tunnels.
/// If connection_id is provided (from saved connections), use it for stable pooling.
/// Otherwise fall back to host:port:database (for ad-hoc connections).
Expand Down Expand Up @@ -76,7 +78,6 @@ pub async fn get_mysql_pool_with_id(
connection_id: Option<&str>,
) -> Result<Pool<MySql>, String> {
let key = build_connection_key(params, connection_id);

// Try to get existing pool
{
let pools = MYSQL_POOLS.read().await;
Expand All @@ -99,14 +100,13 @@ pub async fn get_mysql_pool_with_id(
);
let url = build_mysql_url(params);
let pool = sqlx::mysql::MySqlPoolOptions::new()
.max_connections(10)
.max_connections(params.max_connections.unwrap_or(crate::config::DEFAULT_MAX_CONNECTIONS))
.connect(&url)
.await
.map_err(|e| {
log::error!("Failed to create MySQL connection pool: {}", e);
e.to_string()
})?;

log::info!(
"MySQL connection pool created successfully for: {} (key: {})",
params.database,
Expand Down Expand Up @@ -155,14 +155,14 @@ pub async fn get_postgres_pool_with_id(
);
let copts = build_postgres_connectoptions(params);
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(10)
.max_connections(params.max_connections.unwrap_or(crate::config::DEFAULT_MAX_CONNECTIONS))
.connect_with(copts)
.await
.map_err(|e| {
log::error!("Failed to create PostgreSQL connection pool: {}", e);
e.to_string()
})?;

log::info!("Max connections: {}", params.max_connections.unwrap_or(crate::config::DEFAULT_MAX_CONNECTIONS));
log::info!(
"PostgreSQL connection pool created successfully for: {} (key: {})",
params.database,
Expand Down Expand Up @@ -210,7 +210,7 @@ pub async fn get_sqlite_pool_with_id(
);
let options = build_sqlite_connectoptions(params);
let pool = sqlx::sqlite::SqlitePoolOptions::new()
.max_connections(5) // SQLite has lower concurrency needs
.max_connections(SQLITE_MAX_CONNECTIONS_CAP)
.connect_with(options)
.await
.map_err(|e| {
Expand Down
51 changes: 50 additions & 1 deletion src/components/ui/NewConnectionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import {
X,
Expand Down Expand Up @@ -51,6 +51,7 @@ interface ConnectionParams {
ssh_key_file?: string;
ssh_key_passphrase?: string;
save_in_keychain?: boolean;
max_connections?: number;
}

interface SavedConnection {
Expand Down Expand Up @@ -124,6 +125,10 @@ export const NewConnectionModal = ({
ssh_enabled: false,
ssh_port: 22,
});
// Stores form data per driver so switching tabs preserves per-driver values
const driverFormDataRef = useRef<Record<string, Partial<ConnectionParams>>>(
{},
);
const [selectedDatabasesState, setSelectedDatabasesState] = useState<
string[]
>([]);
Expand Down Expand Up @@ -314,7 +319,25 @@ export const NewConnectionModal = ({
}, [isOpen, initialConnection]);

const handleDriverChange = (newDriver: string) => {
// Save current driver's form data before switching
driverFormDataRef.current[driver] = formData;

const saved = driverFormDataRef.current[newDriver];
if (saved) {
setFormData(saved);
} else {
setFormData((prev) => ({
...prev,
driver: newDriver,
port:
drivers.find((d) => d.id === newDriver)?.default_port ?? undefined,
username:
drivers.find((d) => d.id === newDriver)?.default_username ?? "",
max_connections: undefined,
}));
}
setDriver(newDriver);

setFormData({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Logic bug - this setFormData call unconditionally overwrites all the form data that was carefully preserved/restored in lines 327-337. This makes the driver-specific form data preservation feature completely ineffective.

The code should either:

  1. Remove this hardcoded reset entirely, OR
  2. Move it before the preservation logic (lines 325-338), OR
  3. Merge the two setFormData calls so that preserved values are not lost

driver: newDriver,
host: "",
Expand Down Expand Up @@ -761,6 +784,32 @@ export const NewConnectionModal = ({
{t("newConnection.saveKeychain")}
</span>
</label>

{/* Max connections */}
<div className="flex flex-col gap-1">
<label className="text-[10px] uppercase font-semibold tracking-wider text-muted">
{t("newConnection.maxConnections")}
</label>
<p className="text-xs text-muted">
{t("newConnection.maxConnectionsDesc")}
</p>
<input
type="number"
value={formData.max_connections ?? ""}
onChange={(e) => {
const val = parseInt(e.target.value);
updateField("max_connections", isNaN(val) ? undefined : val);
}}
min="1"
max="100"
placeholder="10"
autoCorrect="off"
autoCapitalize="off"
autoComplete="off"
spellCheck={false}
className="w-32 px-2.5 py-1.5 bg-base border border-strong rounded-md text-sm text-primary placeholder:text-muted focus:border-blue-500 focus:outline-none transition-colors"
/>
</div>
</>
)}
</div>
Expand Down
26 changes: 17 additions & 9 deletions src/i18n/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";

import en from './locales/en.json';
import it from './locales/it.json';
import es from './locales/es.json';
import en from "./locales/en.json";
import it from "./locales/it.json";
import es from "./locales/es.json";

i18n
.use(LanguageDetector)
Expand All @@ -15,13 +15,21 @@ i18n
it: { translation: it },
es: { translation: es },
},
fallbackLng: 'en',
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
detection: {
order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag', 'path', 'subdomain'],
caches: ['localStorage', 'cookie'],
order: [
"querystring",
"cookie",
"localStorage",
"navigator",
"htmlTag",
"path",
"subdomain",
],
caches: ["localStorage", "cookie"],
},
});

Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
"pageSize": "Result Page Size (Limit)",
"pageSizeDesc": "Limits the number of rows fetched per query to prevent performance issues. Set to 0 to disable (not recommended).",
"rows": "rows",
"connections": "connections",
"appearance": "Appearance",
"localization": "Localization",
"themeSelection": "Theme Selection",
Expand Down Expand Up @@ -445,6 +446,8 @@
"sshKeyPassphrase": "SSH Key Passphrase (Optional)",
"sshKeyPassphrasePlaceholder": "Enter key passphrase if encrypted",
"saveKeychain": "Save passwords in Keychain",
"maxConnections": "Connection Pool Size",
"maxConnectionsDesc": "Maximum number of concurrent connections for this pool. Leave empty to use the default (10).",
"testConnection": "Test Connection",
"save": "Save",
"failSave": "Failed to save connection",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
"pageSize": "Tamaño de Página de Resultados (Límite)",
"pageSizeDesc": "Limita el número de filas obtenidas por consulta para prevenir problemas de rendimiento. Establece en 0 para desactivar (no recomendado).",
"rows": "filas",
"connections": "conexiones",
"appearance": "Apariencia",
"localization": "Localización",
"themeSelection": "Selección de Tema",
Expand Down Expand Up @@ -445,6 +446,8 @@
"sshKeyPassphrase": "Frase de Paso de Clave SSH (Opcional)",
"sshKeyPassphrasePlaceholder": "Ingresa la frase de paso si la clave está cifrada",
"saveKeychain": "Guardar contraseñas en el Llavero",
"maxConnections": "Tamaño del Pool de Conexiones",
"maxConnectionsDesc": "Número máximo de conexiones simultáneas para este pool. Dejar vacío para usar el valor predeterminado (10).",
"testConnection": "Probar Conexión",
"save": "Guardar",
"failSave": "Error al guardar la conexión",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
"pageSize": "Dimensione Pagina Risultati (Limite)",
"pageSizeDesc": "Limita il numero di righe caricate per query per prevenire problemi di performance. Imposta a 0 per disabilitare (non consigliato).",
"rows": "righe",
"connections": "connessioni",
"appearance": "Aspetto",
"localization": "Localizzazione",
"themeSelection": "Selezione Tema",
Expand Down Expand Up @@ -445,6 +446,8 @@
"sshKeyPassphrase": "Passphrase Chiave SSH (Opzionale)",
"sshKeyPassphrasePlaceholder": "Inserisci passphrase se la chiave è cifrata",
"saveKeychain": "Salva password nel Portachiavi",
"maxConnections": "Dimensione Pool di Connessioni",
"maxConnectionsDesc": "Numero massimo di connessioni simultanee per questo pool. Lascia vuoto per usare il valore predefinito (10).",
"testConnection": "Testa Connessione",
"save": "Salva",
"failSave": "Salvataggio connessione fallito",
Expand Down
Empty file added src/i18n/locales/pt-br.json
Empty file.
1 change: 1 addition & 0 deletions src/pages/Connections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,7 @@ export const Connections = () => {
</div>

<NewConnectionModal
key={editingConnection?.id ?? "new"}
isOpen={isModalOpen}
onClose={() => { setIsModalOpen(false); setEditingConnection(null); }}
onSave={handleSave}
Expand Down
1 change: 1 addition & 0 deletions src/pages/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,7 @@ export const Settings = () => {
</span>
</div>
</div>

</div>
</div>

Expand Down
1 change: 1 addition & 0 deletions src/utils/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface ConnectionParams {
ssh_password?: string;
ssh_key_file?: string;
ssh_key_passphrase?: string;
max_connections?: number;
}

/**
Expand Down