diff --git a/Cargo.lock b/Cargo.lock index 7bf0316379..c2320b812f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1906,6 +1906,7 @@ dependencies = [ "log", "mongodb", "mysql_async", + "pageant", "percent-encoding", "portpicker", "rayon", diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 36c0c086a4..dc3099066b 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -121,6 +121,8 @@ const newQueryContextSource = ref<"tab" | "sidebar">("tab"); const showSaveSqlDialog = ref(false); const saveSqlName = ref(""); const saveSqlFolderId = ref(""); +const pendingSaveAndCloseTabId = ref(null); +const pendingPrevActiveTabId = ref(null); const ROOT_SAVED_SQL_FOLDER = "__root__"; const activeTab = computed(() => queryStore.tabs.find((t) => t.id === queryStore.activeTabId)); @@ -185,12 +187,15 @@ async function resolveActiveExecutableSql() { : ""; } +const blockDangerousRedisCommands = ref(true); + const { dangerSql, pendingDangerSql, showDangerDialog, suppressDangerConfirm, tryExecute, doExecute, cancelActiveExecution, tryExplain, onDangerConfirm, explainMode } = useSqlExecution({ activeTab, activeConnection, executableSql, resolveExecutableSql: resolveActiveExecutableSql, activeOutputView, + blockDangerousRedisCommands, }); const dialogs = useDialogSources(); @@ -218,7 +223,19 @@ const connectionStats = computed(() => ({ })); const recentConnections = computed(() => connectionStore.connections.slice(0, 5)); const savedSqlHistoryItems = computed(() => { - const folderById = new Map(savedSqlStore.allFolders.map((folder) => [folder.id, folder.name])); + const folderById = new Map(savedSqlStore.allFolders.map((folder) => [folder.id, folder])); + const folderPath = (folderId?: string): string | undefined => { + if (!folderId) return undefined; + const parts: string[] = []; + const seen = new Set(); + let folder = folderById.get(folderId); + while (folder && !seen.has(folder.id)) { + seen.add(folder.id); + parts.unshift(folder.name); + folder = folder.parentFolderId ? folderById.get(folder.parentFolderId) : undefined; + } + return parts.join("/"); + }; return rankSavedSqlHistory(savedSqlStore.allFiles, { limit: 6 }).map((file) => { const connection = connectionStore.getConfig(file.connectionId); return { @@ -226,13 +243,25 @@ const savedSqlHistoryItems = computed(() => { name: file.name, connectionName: connection ? connectionRedactedNameLabel(connection) : t("welcome.unknownConnection"), database: file.database, - folderName: file.folderId ? folderById.get(file.folderId) : undefined, + folderName: folderPath(file.folderId), openCount: file.openCount ?? 0, }; }); }); const saveSqlFolders = computed(() => { - return savedSqlStore.allFolders; + const folderById = new Map(savedSqlStore.allFolders.map((folder) => [folder.id, folder])); + const pathForFolder = (folderId: string) => { + const parts: string[] = []; + const seen = new Set(); + let folder = folderById.get(folderId); + while (folder && !seen.has(folder.id)) { + seen.add(folder.id); + parts.unshift(folder.name); + folder = folder.parentFolderId ? folderById.get(folder.parentFolderId) : undefined; + } + return parts.join(" / "); + }; + return savedSqlStore.allFoldersTreeOrder.map((folder) => ({ ...folder, displayName: pathForFolder(folder.id) || folder.name })); }); async function applyUiScale(scale: number) { @@ -363,6 +392,36 @@ function defaultSavedSqlName(title: string) { return normalized.endsWith(".sql") ? normalized : `${normalized}.sql`; } +async function handleSaveTab(tabId: string) { + const tab = queryStore.tabs.find((t) => t.id === tabId); + if (!tab || !tab.sql.trim()) return; + const existing = tab.savedSqlId ? savedSqlStore.getFile(tab.savedSqlId) : undefined; + if (existing) { + const updated = await savedSqlStore.saveFile({ + id: existing.id, + connectionId: tab.connectionId, + folderId: existing.folderId, + name: existing.name, + database: tab.database, + schema: tab.schema, + sql: tab.sql, + }); + queryStore.linkSavedSql(tab.id, updated.id, updated.name); + queryStore.markTabClean(tab); + toast(t("savedSql.saved"), 2000); + queryStore.closeTab(tabId, { force: true }); + return; + } + // No existing saved SQL — open save dialog, then close after save + const prevActive = queryStore.activeTabId; + queryStore.activeTabId = tabId; + saveSqlName.value = defaultSavedSqlName(tab.title); + saveSqlFolderId.value = ROOT_SAVED_SQL_FOLDER; + pendingSaveAndCloseTabId.value = tabId; + pendingPrevActiveTabId.value = prevActive; + showSaveSqlDialog.value = true; +} + async function openSaveSqlDialog() { const tab = activeTab.value; if (!tab || !tab.sql.trim()) return; @@ -432,7 +491,15 @@ async function confirmSaveSqlToLibrary() { sql: tab.sql, }); queryStore.linkSavedSql(tab.id, saved.id, saved.name); + queryStore.markTabClean(tab); showSaveSqlDialog.value = false; + if (pendingSaveAndCloseTabId.value) { + const closeId = pendingSaveAndCloseTabId.value; + pendingSaveAndCloseTabId.value = null; + if (pendingPrevActiveTabId.value) queryStore.activeTabId = pendingPrevActiveTabId.value; + pendingPrevActiveTabId.value = null; + queryStore.closeTab(closeId, { force: true }); + } toast(t("savedSql.saved"), 2000); } catch (e: any) { toast(t("savedSql.saveFailed", { message: e?.message || String(e) }), 5000); @@ -1029,7 +1096,7 @@ onUnmounted(() => {
- +
{ :active-connection="activeConnection" :executable-sql="executableSql" :explain-mode="explainMode" + :block-dangerous-redis-commands="blockDangerousRedisCommands" @update:explain-mode="(m: 'explain' | 'autotrace') => (explainMode = m)" + @update:block-dangerous-redis-commands="(v: boolean) => (blockDangerousRedisCommands = v)" @execute="tryExecute()" @cancel="cancelActiveExecution()" @explain="tryExplain()" @@ -1190,7 +1259,15 @@ onUnmounted(() => {
- + {{ t("savedSql.saveToLibrary") }} @@ -1209,14 +1286,21 @@ onUnmounted(() => { {{ t("savedSql.rootFolder") }} - {{ folder.name }} + {{ folder.displayName }}
- + diff --git a/apps/desktop/src/components/config/DriverInstallProgressCircle.vue b/apps/desktop/src/components/config/DriverInstallProgressCircle.vue index 8eced5e8c4..96cd0b8e02 100644 --- a/apps/desktop/src/components/config/DriverInstallProgressCircle.vue +++ b/apps/desktop/src/components/config/DriverInstallProgressCircle.vue @@ -12,7 +12,7 @@ const props = defineProps<{ - {{ props.percent ?? "..." }} + {{ props.percent ?? 0 }} diff --git a/apps/desktop/src/components/config/DriverStoreDialog.vue b/apps/desktop/src/components/config/DriverStoreDialog.vue index 388efaafd4..98816a99fa 100644 --- a/apps/desktop/src/components/config/DriverStoreDialog.vue +++ b/apps/desktop/src/components/config/DriverStoreDialog.vue @@ -5,6 +5,7 @@ import { Activity, ExternalLink, Cpu, FolderOpen, MemoryStick, Search, Square, T import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import DriverInstallProgressCircle from "@/components/config/DriverInstallProgressCircle.vue"; @@ -12,7 +13,7 @@ import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; import { useToast } from "@/composables/useToast"; import { isTauriRuntime } from "@/lib/tauriRuntime"; import { countAvailableDriverUpdates } from "@/lib/agentDriverUpdateBadge"; -import type { JdbcDriverInfo, JdbcPluginStatus } from "@/types/database"; +import type { JdbcDriverInfo, JdbcMavenBundleInfo, JdbcPluginStatus } from "@/types/database"; import * as api from "@/lib/api"; import type { AgentDriverInfo, DriverRuntimeInfo, DriverRuntimeSummary, DriverStoreUsage, JavaRuntimeConfig } from "@/lib/api"; import { formatRuntimeBytes, formatRuntimeCpu, formatRuntimeUptime, runtimeHealthClass, runtimeStatusClass, runtimeStatusDotClass } from "@/lib/driverRuntimePresentation"; @@ -63,6 +64,7 @@ const DRIVER_RUNTIME_POLL_MS = 5000; const OFFLINE_DRIVER_DOWNLOAD_URL = "https://dbxio.com/cn/drivers"; let unlisten: (() => void) | null = null; +const lastProgressPercent = ref(null); const installedJres = computed(() => { const jreMap = new Map(); @@ -78,16 +80,28 @@ const progressText = computed(() => { const p = progress.value; if (!p) return ""; if (p.step === "jre-extract") return t("driverStore.progressJreExtract"); - const label = p.step === "jre" ? t("driverStore.progressDownloadJre") : t("driverStore.progressDownloadDriver"); + if (p.step === "jdbc-plugin-extract") return t("driverStore.progressJdbcPluginExtract"); + const label = p.step === "jre" ? t("driverStore.progressDownloadJre") : p.step === "jdbc-plugin" ? t("driverStore.progressDownloadJdbcPlugin") : t("driverStore.progressDownloadDriver"); if (!p.total) return `${label}...`; const pct = Math.round(((p.downloaded ?? 0) / p.total) * 100); const dl = formatSize(p.downloaded ?? 0); const total = formatSize(p.total); - const prefix = upgradingAll.value && upgradingCurrent.value ? `[${upgradingIndex.value}/${upgradingTotal.value}] ${upgradingCurrent.value} — ` : ""; + const prefix = upgradingAll.value && upgradingCurrent.value ? `[${upgradingIndex.value}/${upgradingTotal.value}] ${upgradingCurrent.value} - ` : ""; return `${prefix}${label} ${dl} / ${total} (${pct}%)`; }); -const progressNumber = computed(() => driverInstallProgressPercent(progress.value)); +const progressNumber = computed(() => { + const next = driverInstallProgressPercent(progress.value); + if (next !== null) { + lastProgressPercent.value = next; + } + return next ?? lastProgressPercent.value; +}); + +function resetInstallProgress() { + progress.value = null; + lastProgressPercent.value = null; +} const updatableCount = computed(() => (props.updateNotificationsEnabled ? drivers.value.filter((d) => d.update_available).length : 0)); const usageSummary = computed(() => { @@ -231,7 +245,7 @@ async function installDriver(dbType: string) { async function runDriverInstall(dbType: string) { const label = drivers.value.find((d) => d.db_type === dbType)?.label ?? dbType; installing.value = dbType; - progress.value = null; + resetInstallProgress(); try { const blockers = await api.checkAgentUpdateBlockers([dbType]); if (blockers.length > 0) { @@ -245,7 +259,7 @@ async function runDriverInstall(dbType: string) { toast(t("driverStore.driverInstallFailed", { label, error: e })); } finally { installing.value = null; - progress.value = null; + resetInstallProgress(); } } @@ -263,7 +277,7 @@ async function runQueuedDriverInstalls() { async function upgradeAll() { upgradingAll.value = true; queuedDriverInstalls.value = []; - progress.value = null; + resetInstallProgress(); try { const updatableDbTypes = drivers.value.filter((driver) => driver.update_available).map((driver) => driver.db_type); const blockers = await api.checkAgentUpdateBlockers(updatableDbTypes); @@ -286,7 +300,7 @@ async function upgradeAll() { upgradingCurrent.value = ""; upgradingIndex.value = 0; upgradingTotal.value = 0; - progress.value = null; + resetInstallProgress(); } } @@ -357,7 +371,7 @@ async function importOfflineZip() { } if (!selected) return; importingZip.value = true; - progress.value = null; + resetInstallProgress(); try { const count = await api.importAgentsFromZip(selected); await refreshAgents(); @@ -366,7 +380,7 @@ async function importOfflineZip() { toast(t("driverStore.offlineImportFailed", { error: e })); } finally { importingZip.value = false; - progress.value = null; + resetInstallProgress(); } } @@ -402,7 +416,7 @@ async function importDriverJar(dbType: string) { async function reinstallJre(jreKey: string) { reinstallingJre.value = jreKey; - progress.value = null; + resetInstallProgress(); try { await api.reinstallJre(jreKey); await refreshAgents(); @@ -411,7 +425,7 @@ async function reinstallJre(jreKey: string) { toast(t("driverStore.jreReinstallFailed", { jre: jreKey, error: e })); } finally { reinstallingJre.value = null; - progress.value = null; + resetInstallProgress(); } } @@ -434,12 +448,45 @@ function formatSize(bytes: number): string { // ──────────── JDBC drivers ──────────── const jdbcDrivers = ref([]); +const jdbcMavenBundles = ref([]); const jdbcDriverSearch = ref(""); const isLoadingJdbcDrivers = ref(false); const jdbcPluginStatus = ref(null); const isInstallingJdbcPlugin = ref(false); const isUninstallingJdbcPlugin = ref(false); const jdbcDriverPathInput = ref(""); +const jdbcMavenCoordinateInput = ref(""); +const jdbcMavenRepository = ref("https://repo.maven.apache.org/maven2/"); +const customJdbcMavenRepository = ref(""); +const isInstallingJdbcMavenDriver = ref(false); + +const jdbcMavenRepositoryOptions = [ + { label: "Maven Central", value: "https://repo.maven.apache.org/maven2/" }, + { label: "Aliyun", value: "https://maven.aliyun.com/repository/public" }, + { label: "Huawei Cloud", value: "https://repo.huaweicloud.com/repository/maven" }, + { label: "Tencent Cloud", value: "https://mirrors.cloud.tencent.com/nexus/repository/maven-public" }, + { label: "Custom", value: "custom" }, +]; + +type JdbcDriverListItem = + | { + kind: "manual"; + id: string; + title: string; + subtitle: string; + source: string; + size: number; + driver: JdbcDriverInfo; + } + | { + kind: "maven"; + id: string; + title: string; + subtitle: string; + source: string; + size: number; + bundle: JdbcMavenBundleInfo; + }; const filteredAgentDrivers = computed(() => { const query = agentDriverSearch.value.trim().toLowerCase(); @@ -447,10 +494,34 @@ const filteredAgentDrivers = computed(() => { return drivers.value.filter((driver) => [driver.label, driver.db_type, driver.version, driver.installed_version, driver.jre].filter(Boolean).join(" ").toLowerCase().includes(query)); }); +const jdbcDriverListItems = computed(() => { + const bundleItems = jdbcMavenBundles.value.map((bundle) => ({ + kind: "maven" as const, + id: `maven:${bundle.id}`, + title: bundle.coordinate, + subtitle: `${bundle.artifacts.length} JARs - ${bundle.repositories.join(", ")}`, + source: t("driverStore.jdbcSourceMaven"), + size: bundle.artifacts.reduce((total, artifact) => total + Number(artifact.size || 0), 0), + bundle, + })); + const manualItems = jdbcDrivers.value + .filter((driver) => !driver.bundle_id) + .map((driver) => ({ + kind: "manual" as const, + id: `manual:${driver.path}`, + title: driver.name, + subtitle: driver.path, + source: t("driverStore.jdbcSourceManual"), + size: driver.size, + driver, + })); + return [...bundleItems, ...manualItems].sort((a, b) => a.title.localeCompare(b.title)); +}); + const filteredJdbcDrivers = computed(() => { const query = jdbcDriverSearch.value.trim().toLowerCase(); - if (!query) return jdbcDrivers.value; - return jdbcDrivers.value.filter((driver) => [driver.name, driver.path, String(driver.size)].join(" ").toLowerCase().includes(query)); + if (!query) return jdbcDriverListItems.value; + return jdbcDriverListItems.value.filter((item) => [item.title, item.subtitle, String(item.size)].join(" ").toLowerCase().includes(query)); }); function formatBytes(bytes: number) { @@ -568,7 +639,9 @@ function jreUsageLabel(key: string) { async function loadJdbcDrivers() { isLoadingJdbcDrivers.value = true; try { - jdbcDrivers.value = await api.listJdbcDrivers(); + const [drivers, bundles] = await Promise.all([api.listJdbcDrivers(), api.listJdbcMavenBundles()]); + jdbcDrivers.value = drivers; + jdbcMavenBundles.value = bundles; } catch (e: any) { toast(String(e?.message || e), 5000); } finally { @@ -597,6 +670,7 @@ async function loadJdbcPluginStatus() { async function installJdbcPlugin() { if (isInstallingJdbcPlugin.value) return; isInstallingJdbcPlugin.value = true; + resetInstallProgress(); try { jdbcPluginStatus.value = await api.installJdbcPlugin(); emitDriverUpdateCount(); @@ -606,6 +680,7 @@ async function installJdbcPlugin() { toast(String(e?.message || e), 5000); } finally { isInstallingJdbcPlugin.value = false; + resetInstallProgress(); } } @@ -697,6 +772,24 @@ async function importJdbcDriverPathInput() { await importJdbcDriverPaths(paths); } +async function installJdbcMavenDriver() { + const coordinate = jdbcMavenCoordinateInput.value.trim(); + const repository = jdbcMavenRepository.value === "custom" ? customJdbcMavenRepository.value.trim() : jdbcMavenRepository.value; + if (!coordinate || !repository || isInstallingJdbcMavenDriver.value) return; + isInstallingJdbcMavenDriver.value = true; + try { + jdbcDrivers.value = await api.installJdbcDriverFromMaven(coordinate, [repository]); + jdbcMavenBundles.value = await api.listJdbcMavenBundles(); + jdbcMavenCoordinateInput.value = ""; + void loadDriverStoreUsage(); + toast(t("driverStore.jdbcMavenInstallSuccess")); + } catch (e: any) { + toast(String(e?.message || e), 8000); + } finally { + isInstallingJdbcMavenDriver.value = false; + } +} + async function deleteJdbcDriver(path: string) { try { jdbcDrivers.value = await api.deleteJdbcDriver(path); @@ -707,6 +800,17 @@ async function deleteJdbcDriver(path: string) { } } +async function deleteJdbcMavenBundle(bundleId: string) { + try { + jdbcDrivers.value = await api.deleteJdbcMavenBundle(bundleId); + jdbcMavenBundles.value = await api.listJdbcMavenBundles(); + void loadDriverStoreUsage(); + toast(t("settings.jdbcDeleteSuccess")); + } catch (e: any) { + toast(String(e?.message || e), 5000); + } +} + // ──────────── Lifecycle ──────────── onMounted(async () => { @@ -961,6 +1065,7 @@ watch(driverStoreTab, (tab) => {

+ {{ jdbcPluginStatus.compatible @@ -1007,26 +1112,50 @@ watch(driverStoreTab, (tab) => { {{ t("settings.jdbcImport") }}
+
+ + + +
+
{{ t("common.loading") }}
-
+
{{ t("settings.jdbcNoDrivers") }}
{{ t("driverStore.noMatchingDrivers") }}
-
+
-
{{ driver.name }}
-
{{ driver.path }}
+
+
{{ item.title }}
+ + {{ item.source }} + +
+
{{ item.subtitle }}
-
{{ formatBytes(driver.size) }}
-
diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 9d5feafb17..e95a85ed94 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -4,13 +4,15 @@ import { uuid } from "@/lib/utils"; import { useI18n } from "vue-i18n"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import type { ConnectionConfig, DatabaseType, JdbcDriverInfo, ProxyTunnelConfig, SshTunnelConfig, TransportLayerConfig } from "@/types/database"; +import { Switch } from "@/components/ui/switch"; +import type { ConnectionConfig, DatabaseType, JdbcDriverInfo, JdbcMavenBundleInfo, ProxyTunnelConfig, SshTunnelConfig, TransportLayerConfig } from "@/types/database"; import { useConnectionStore } from "@/stores/connectionStore"; import { useSettingsStore } from "@/stores/settingsStore"; import { useToast } from "@/composables/useToast"; @@ -33,6 +35,11 @@ type DbCategory = { key: string; title: string; options: DbOption[] }; type DialogStep = "select" | "config"; type DbPickerView = "icon" | "list"; type ConfigTab = "connection" | "advanced" | "tls" | "transport"; +type JdbcDriverSelectItem = { + id: string; + label: string; + paths: string[]; +}; type LegacyTransportFields = { ssh_enabled?: boolean; @@ -246,8 +253,10 @@ const customDriverName = ref(""); const mongoUseUrl = ref(false); const jdbcDriverPathsInput = ref(""); const jdbcDrivers = ref([]); +const jdbcMavenBundles = ref([]); const agentDrivers = ref([]); const selectedJdbcDriverPath = ref(""); +const jdbcManualClasspathOpen = ref(false); const connectionUrlInput = ref(""); const oceanbaseSubMode = ref<"mysql" | "oracle">("mysql"); const h2ConnectionMode = ref("file"); @@ -270,6 +279,31 @@ const isPresetColor = (color: string | undefined) => colorOptions.some((c) => c. const customColorInput = ref(""); const customColorOpen = ref(false); +const jdbcDriverSelectItems = computed(() => { + const bundles = jdbcMavenBundles.value.map((bundle) => ({ + id: `maven:${bundle.id}`, + label: bundle.coordinate, + paths: bundle.artifacts.map((artifact) => artifact.path), + })); + const manual = jdbcDrivers.value + .filter((driver) => !driver.bundle_id) + .map((driver) => ({ + id: `manual:${driver.path}`, + label: driver.name, + paths: [driver.path], + })); + return [...bundles, ...manual].sort((left, right) => left.label.localeCompare(right.label)); +}); + +const jdbcDriverSelectItemById = computed(() => new Map(jdbcDriverSelectItems.value.map((item) => [item.id, item]))); +const jdbcManualClasspathCount = computed( + () => + jdbcDriverPathsInput.value + .split(/\r?\n/) + .map((value) => value.trim()) + .filter(Boolean).length, +); + function applyCustomColor(value: string) { form.value.color = value; customColorInput.value = value; @@ -727,7 +761,7 @@ const iconTypeMap: Record = { custom_postgres: "postgres", }; -const dbOptions = [ +const dbOptions: DbOption[] = [ { value: "postgres", label: "PostgreSQL" }, { value: "mysql", label: "MySQL" }, { value: "mongodb", label: "MongoDB" }, @@ -787,12 +821,19 @@ const dbOptions = [ { value: "influxdb", label: "InfluxDB" }, { value: "iris", label: "IRIS" }, { value: "jdbc", label: "JDBC" }, - { value: "custom_mysql", label: "Custom (MySQL)" }, - { value: "custom_postgres", label: "Custom (PostgreSQL)" }, ]; const dbCategories = computed(() => [{ key: "all", title: "", options: dbOptions }]); +function matchesDbOption(option: DbOption, keyword: string, categoryTitle = "") { + const profile = driverProfiles[option.value]; + return [option.label, option.value, profile?.label, profile?.type, categoryTitle].some((value) => + String(value || "") + .toLowerCase() + .includes(keyword), + ); +} + const filteredDbCategories = computed(() => { const keyword = dbSearchQuery.value.trim().toLowerCase(); if (!keyword) return dbCategories.value; @@ -800,14 +841,7 @@ const filteredDbCategories = computed(() => { return dbCategories.value .map((category) => ({ ...category, - options: category.options.filter((option) => { - const profile = driverProfiles[option.value]; - return [option.label, option.value, profile?.label, profile?.type, category.title].some((value) => - String(value || "") - .toLowerCase() - .includes(keyword), - ); - }), + options: category.options.filter((option) => matchesDbOption(option, keyword, category.title)), })) .filter((category) => category.options.length > 0); }); @@ -1939,9 +1973,12 @@ async function browseJdbcDriverPaths() { async function loadJdbcDrivers() { if (!isDesktop) return; try { - jdbcDrivers.value = await api.listJdbcDrivers(); + const [drivers, bundles] = await Promise.all([api.listJdbcDrivers(), api.listJdbcMavenBundles()]); + jdbcDrivers.value = drivers; + jdbcMavenBundles.value = bundles; } catch { jdbcDrivers.value = []; + jdbcMavenBundles.value = []; } } @@ -1962,18 +1999,21 @@ async function loadAgentDrivers() { } } -function addJdbcDriverPath(path: string) { +function addJdbcDriverPaths(paths: string[]) { const existing = jdbcDriverPathsInput.value .split(/\r?\n/) .map((value) => value.trim()) .filter(Boolean); - jdbcDriverPathsInput.value = Array.from(new Set([...existing, path])).join("\n"); + jdbcDriverPathsInput.value = Array.from(new Set([...existing, ...paths])).join("\n"); } -function onJdbcDriverSelect(path: any) { - if (typeof path !== "string" || !path) return; - selectedJdbcDriverPath.value = path; - addJdbcDriverPath(path); +function onJdbcDriverSelect(id: any) { + if (typeof id !== "string" || !id) return; + const item = jdbcDriverSelectItemById.value.get(id); + if (!item) return; + selectedJdbcDriverPath.value = id; + addJdbcDriverPaths(item.paths); + jdbcManualClasspathOpen.value = false; } function openExternalUrl(url: string) { @@ -2014,7 +2054,7 @@ function openExternalUrl(url: string) {
-

{{ category.title }}

+

{{ category.title }}

@@ -2200,24 +2240,29 @@ function openExternalUrl(url: string) {
-
- - -
- - - {{ driver.name }} + + {{ driver.label }} -
+
+
+
{{ t("connection.jdbcManualClasspath") }}
+ + {{ t("connection.jdbcManualClasspathCount", { count: jdbcManualClasspathCount }) }} + +
+ +
+