fix: add TLS connection settings

This commit is contained in:
t8y2
2026-05-26 17:32:05 +08:00
parent 94487384e1
commit f9b58993b4
11 changed files with 324 additions and 87 deletions
@@ -38,7 +38,7 @@ type DbOption = { value: string; label: string };
type DbCategory = { key: string; title: string; options: DbOption[] };
type DialogStep = "select" | "config";
type DbPickerView = "icon" | "list";
type ConfigTab = "connection" | "ssh" | "proxy";
type ConfigTab = "connection" | "tls" | "ssh" | "proxy";
const { t } = useI18n();
const { toast } = useToast();
@@ -550,6 +550,18 @@ const filePathPlaceholder = computed(() => {
return "/path/to/database.db or :memory:";
});
const supportsMemoryDatabasePath = computed(() => form.value.db_type === "sqlite" || form.value.db_type === "duckdb");
const tlsCapableDatabaseTypes = new Set<DatabaseType>([
"mysql",
"postgres",
"redshift",
"gaussdb",
"opengauss",
"redis",
"clickhouse",
"elasticsearch",
]);
const supportsTlsToggle = computed(() => tlsCapableDatabaseTypes.has(form.value.db_type));
const supportsCaCertificatePath = computed(() => form.value.db_type === "mysql" || form.value.db_type === "clickhouse");
const canUseSsh = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "access");
const canUseProxy = computed(
() => form.value.db_type !== "sqlite" && form.value.db_type !== "duckdb" && form.value.db_type !== "access",
@@ -821,6 +833,12 @@ watch(canUseProxy, (value) => {
}
});
watch(supportsTlsToggle, (value) => {
if (!value && configTab.value === "tls") {
configTab.value = "connection";
}
});
async function save() {
if (!ensureConnectionHostResolvedFromUrl()) return;
if (isSaving.value) return;
@@ -1117,9 +1135,13 @@ function openExternalUrl(url: string) {
<template v-else>
<div class="space-y-3">
<Tabs v-model="configTab" class="min-h-0">
<div v-if="canUseSsh || canUseProxy" class="flex items-center justify-between border-b pb-2">
<div
v-if="supportsTlsToggle || canUseSsh || canUseProxy"
class="flex items-center justify-between border-b pb-2"
>
<TabsList>
<TabsTrigger value="connection">{{ t("connection.basicTab") }}</TabsTrigger>
<TabsTrigger v-if="supportsTlsToggle" value="tls">{{ t("connection.tlsTab") }}</TabsTrigger>
<TabsTrigger v-if="canUseSsh" value="ssh">{{ t("connection.sshTunnel") }}</TabsTrigger>
<TabsTrigger v-if="canUseProxy" value="proxy">{{ t("connection.proxy") }}</TabsTrigger>
</TabsList>
@@ -1339,13 +1361,6 @@ function openExternalUrl(url: string) {
:placeholder="t('connection.databasePlaceholder')"
/>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">SSL/TLS</Label>
<div class="col-span-3">
<input type="checkbox" v-model="form.ssl" class="mr-2" />
<span class="text-xs text-muted-foreground">{{ t("connection.sshEnable") }}</span>
</div>
</div>
</template>
<!-- MongoDB: URL or form -->
@@ -1454,33 +1469,6 @@ function openExternalUrl(url: string) {
<Input v-model="form.database" class="col-span-3" :placeholder="databasePlaceholder" />
</div>
<div v-if="form.db_type === 'clickhouse'" class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">SSL/TLS</Label>
<label class="col-span-3 flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="form.ssl" class="mr-0" />
<span class="text-xs text-muted-foreground">{{ t("connection.sslEnable") }}</span>
</label>
</div>
<div v-if="form.db_type === 'clickhouse'" class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">{{ t("connection.caCertPath") }}</Label>
<div class="col-span-3 flex items-center gap-1">
<Input
v-model="form.ca_cert_path"
class="flex-1"
:placeholder="t('connection.caCertPathPlaceholder')"
/>
<Tooltip v-if="isDesktop">
<TooltipTrigger as-child>
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="browseCaCertPath">
<FolderOpen class="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("connection.caCertPathBrowse") }}</TooltipContent>
</Tooltip>
</div>
</div>
<div v-if="form.db_type === 'oracle'" class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">连接方式</Label>
<div
@@ -1590,6 +1578,44 @@ function openExternalUrl(url: string) {
</div>
</TabsContent>
<TabsContent v-if="supportsTlsToggle" value="tls" class="m-0">
<div class="grid gap-4 py-4 pr-2 max-h-[65vh] overflow-y-auto">
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">SSL/TLS</Label>
<label class="col-span-3 flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="form.ssl" class="mr-0" />
<span class="text-xs text-muted-foreground">{{ t("connection.sslEnable") }}</span>
</label>
</div>
<div v-if="supportsCaCertificatePath" class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">{{ t("connection.caCertPath") }}</Label>
<div class="col-span-3 flex items-center gap-1">
<Input
v-model="form.ca_cert_path"
class="flex-1"
:placeholder="t('connection.caCertPathPlaceholder')"
:disabled="!form.ssl"
/>
<Tooltip v-if="isDesktop">
<TooltipTrigger as-child>
<Button
variant="outline"
size="icon"
class="h-9 w-9 shrink-0"
:disabled="!form.ssl"
@click="browseCaCertPath"
>
<FolderOpen class="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("connection.caCertPathBrowse") }}</TooltipContent>
</Tooltip>
</div>
</div>
</div>
</TabsContent>
<TabsContent v-if="canUseSsh" value="ssh" class="m-0">
<div class="grid gap-4 py-4 pr-2 max-h-[65vh] overflow-y-auto">
<div class="grid grid-cols-4 items-center gap-4">
+1
View File
@@ -137,6 +137,7 @@ export default {
next: "Next",
back: "Previous",
basicTab: "Connection",
tlsTab: "TLS/SSL",
test: "Test",
testing: "Testing...",
copyTestResult: "Copy test result",
+1
View File
@@ -127,6 +127,7 @@ export default {
next: "Siguiente",
back: "Anterior",
basicTab: "Conexión",
tlsTab: "TLS/SSL",
test: "Probar",
testing: "Probando...",
copyTestResult: "Copiar resultado de prueba",
+1
View File
@@ -136,6 +136,7 @@ export default {
next: "下一步",
back: "上一步",
basicTab: "连接信息",
tlsTab: "TLS/SSL",
test: "测试",
testing: "测试中...",
copyTestResult: "复制测试结果",
+30 -1
View File
@@ -66,6 +66,35 @@ function databaseFromPath(pathname: string): string | undefined {
return decodeUrlPart(value.split("/")[0]);
}
function queryParamValue(params: string, key: string): string | undefined {
for (const part of params.split(/[&;]/)) {
if (!part) continue;
const [rawKey, ...rest] = part.split("=");
if (decodeUrlPart(rawKey).toLowerCase() === key.toLowerCase()) {
return decodeUrlPart(rest.join("=")).trim();
}
}
return undefined;
}
function urlParamsRequireTls(dbType: DatabaseType, params: string): boolean {
if (dbType === "mysql") {
const requireSsl = queryParamValue(params, "require_ssl")?.toLowerCase();
if (requireSsl === "true" || requireSsl === "1" || requireSsl === "yes") return true;
const sslMode = (queryParamValue(params, "ssl-mode") || queryParamValue(params, "sslmode") || "")
.toLowerCase()
.replace("-", "_");
return sslMode === "required" || sslMode === "require" || sslMode === "verify_ca" || sslMode === "verify_identity";
}
if (dbType === "postgres" || dbType === "redshift") {
const sslMode = (queryParamValue(params, "sslmode") || "").toLowerCase();
return sslMode === "require" || sslMode === "verify-ca" || sslMode === "verify-full";
}
return false;
}
export function connectionProfileForScheme(scheme: string, preferredProfile?: string): ConnectionProfile | undefined {
if ((scheme === "http" || scheme === "https") && preferredProfile) {
return HTTP_SELECTED_PROFILES[preferredProfile];
@@ -226,7 +255,7 @@ export function parseConnectionUrl(value: string, preferredProfile?: string): Pa
password: decodeUrlPart(parsed.password),
database: databaseFromPath(parsed.pathname),
urlParams,
ssl: scheme === "rediss" || scheme === "https",
ssl: scheme === "rediss" || scheme === "https" || urlParamsRequireTls(profile.type, urlParams),
};
}