diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index dc37a1c87..9e890b006 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -592,6 +592,8 @@ watch(customDriverName, (value) => { }); async function testConnection() { + if (!ensureConnectionHostResolvedFromUrl()) return; + const runId = ++testRunId; isTesting.value = true; testResult.value = null; @@ -610,6 +612,31 @@ async function testConnection() { } } +function applyConnectionUrlToForm(input: string): boolean { + try { + const parsed = parseConnectionUrl(input, selectedType.value); + form.value = applyParsedConnectionUrl(form.value, parsed); + selectedType.value = parsed.driverProfile; + customDriverName.value = isCustomCompatibleProfile() ? parsed.driverLabel : ""; + mongoUseUrl.value = !!parsed.useMongoUrl; + if (!form.value.name.trim()) { + form.value.name = parsed.database || parsed.host || parsed.driverLabel; + } + resetTestState(); + return true; + } catch (e: any) { + toast(t("connection.parseConnectionUrlFailed", { message: e?.message || String(e) }), 5000); + return false; + } +} + +function ensureConnectionHostResolvedFromUrl(): boolean { + if (form.value.host.trim()) return true; + const url = connectionUrlInput.value.trim(); + if (!url) return true; + return applyConnectionUrlToForm(url); +} + function generateConnectionName(): string { const label = selectedProfile().label; const rand = Math.random().toString(36).slice(2, 6); @@ -654,19 +681,8 @@ function resetTestState() { } function applyConnectionUrl() { - try { - const parsed = parseConnectionUrl(connectionUrlInput.value, selectedType.value); - form.value = applyParsedConnectionUrl(form.value, parsed); - selectedType.value = parsed.driverProfile; - customDriverName.value = isCustomCompatibleProfile() ? parsed.driverLabel : ""; - mongoUseUrl.value = !!parsed.useMongoUrl; - if (!form.value.name.trim()) { - form.value.name = parsed.database || parsed.host || parsed.driverLabel; - } - resetTestState(); + if (applyConnectionUrlToForm(connectionUrlInput.value)) { toast(t("connection.parseConnectionUrlApplied"), 2000); - } catch (e: any) { - toast(t("connection.parseConnectionUrlFailed", { message: e?.message || String(e) }), 5000); } } @@ -794,6 +810,7 @@ watch(canUseProxy, (value) => { }); async function save() { + if (!ensureConnectionHostResolvedFromUrl()) return; if (isSaving.value) return; isSaving.value = true; resetTestState(); @@ -1723,7 +1740,8 @@ function openExternalUrl(url: string) { isSaving || (!form.host && !(mongoUseUrl && form.connection_string) && - !(form.db_type === 'jdbc' && form.connection_string)) + !(form.db_type === 'jdbc' && form.connection_string) && + !connectionUrlInput.trim()) " > {{ diff --git a/packages/app-tests/connectionDialogUrlAutofill.test.ts b/packages/app-tests/connectionDialogUrlAutofill.test.ts new file mode 100644 index 000000000..26b967563 --- /dev/null +++ b/packages/app-tests/connectionDialogUrlAutofill.test.ts @@ -0,0 +1,15 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; + +const source = readFileSync("apps/desktop/src/components/connection/ConnectionDialog.vue", "utf8"); + +test("connection dialog auto-resolves host from URL before test/save", () => { + assert.match(source, /function ensureConnectionHostResolvedFromUrl\(\): boolean/); + assert.match(source, /if \(!ensureConnectionHostResolvedFromUrl\(\)\) return;/); + assert.match(source, /return applyConnectionUrlToForm\(url\);/); +}); + +test("save button allows URL-only submissions when host is empty", () => { + assert.match(source, /&&\s*!connectionUrlInput\.trim\(\)\)/); +});