feat(jdbc): add dedicated connection entry

This commit is contained in:
zipg authored and GitHub committed 2026-07-14 18:03:36 +08:00
1 parent 358a736c6e
commit 0bbaf33d92
12 files changed
+76 -11

No files matched your search

+6 -3
View File
@@ -135,6 +135,7 @@ const settingsInitialSection = ref<string | undefined>(undefined);
const showQueryEditorDdlDialog = ref(false); const showQueryEditorDdlDialog = ref(false);
const driverStoreTabOpen = ref(false); const driverStoreTabOpen = ref(false);
const driverStoreActive = ref(false); const driverStoreActive = ref(false);
const driverStoreActiveTab = ref<"agent" | "jdbc" | "storage" | "runtime">("agent");
const settingsReturnSurface = ref<"query" | "driverStore" | "welcome">("welcome"); const settingsReturnSurface = ref<"query" | "driverStore" | "welcome">("welcome");
const showDriverStore = computed(() => driverStoreTabOpen.value && driverStoreActive.value); const showDriverStore = computed(() => driverStoreTabOpen.value && driverStoreActive.value);
const showSettingsPage = computed(() => settingsPageTabOpen.value && settingsStore.settingsPageActive); const showSettingsPage = computed(() => settingsPageTabOpen.value && settingsStore.settingsPageActive);
@@ -316,7 +317,8 @@ function closeSettingsPage() {
driverStoreActive.value = false; driverStoreActive.value = false;
} }
function openDriverStorePage() { function openDriverStorePage(tab?: "agent" | "jdbc" | "storage" | "runtime") {
if (tab) driverStoreActiveTab.value = tab;
driverStoreTabOpen.value = true; driverStoreTabOpen.value = true;
driverStoreActive.value = true; driverStoreActive.value = true;
settingsStore.settingsPageActive = false; settingsStore.settingsPageActive = false;
@@ -325,6 +327,7 @@ function openDriverStorePage() {
function closeDriverStorePage() { function closeDriverStorePage() {
driverStoreTabOpen.value = false; driverStoreTabOpen.value = false;
driverStoreActive.value = false; driverStoreActive.value = false;
driverStoreActiveTab.value = "agent";
} }
const toolbarAgentDriverUpdateCount = computed(() => (updateNotificationsEnabled.value ? agentDriverUpdateCount.value : 0)); const toolbarAgentDriverUpdateCount = computed(() => (updateNotificationsEnabled.value ? agentDriverUpdateCount.value : 0));
const toolbarHasUpdateAvailable = computed(() => updateNotificationsEnabled.value && hasUpdateAvailable.value); const toolbarHasUpdateAvailable = computed(() => updateNotificationsEnabled.value && hasUpdateAvailable.value);
@@ -1898,7 +1901,7 @@ onUnmounted(() => {
@discard-all-tab-close="handleDiscardAllPendingTabClose" @discard-all-tab-close="handleDiscardAllPendingTabClose"
@cancel-tab-close="cancelPendingAppClose" @cancel-tab-close="cancelPendingAppClose"
/> />
<DriverStorePage v-if="driverStoreTabOpen" v-show="driverStoreActive" class="flex-1 min-h-0" :update-notifications-enabled="updateNotificationsEnabled" @update-count-change="updateAgentDriverUpdateCount" /> <DriverStorePage v-if="driverStoreTabOpen" v-show="driverStoreActive" v-model:active-tab="driverStoreActiveTab" class="flex-1 min-h-0" :update-notifications-enabled="updateNotificationsEnabled" @update-count-change="updateAgentDriverUpdateCount" />
<EditorSettingsPage <EditorSettingsPage
v-if="settingsPageTabOpen" v-if="settingsPageTabOpen"
v-show="settingsStore.settingsPageActive" v-show="settingsStore.settingsPageActive"
@@ -2104,7 +2107,7 @@ onUnmounted(() => {
" "
@open-driver-store=" @open-driver-store="
setConnectionDialogOpen(false); setConnectionDialogOpen(false);
openDriverStorePage(); openDriverStorePage($event);
" "
@open-tunnel-profile-settings=" @open-tunnel-profile-settings="
setConnectionDialogOpen(false); setConnectionDialogOpen(false);
@@ -28,17 +28,23 @@ const isWeb = !isTauriRuntime();
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
updateNotificationsEnabled?: boolean; updateNotificationsEnabled?: boolean;
activeTab?: "agent" | "jdbc" | "storage" | "runtime";
}>(), }>(),
{ {
updateNotificationsEnabled: true, updateNotificationsEnabled: true,
activeTab: "agent",
}, },
); );
const emit = defineEmits<{ const emit = defineEmits<{
"update-count-change": [count: number]; "update-count-change": [count: number];
"update:activeTab": [tab: "agent" | "jdbc" | "storage" | "runtime"];
}>(); }>();
const driverStoreTab = ref("agent"); const driverStoreTab = computed({
get: () => props.activeTab,
set: (tab: "agent" | "jdbc" | "storage" | "runtime") => emit("update:activeTab", tab),
});
// ──────────── Driver store path ──────────── // ──────────── Driver store path ────────────
@@ -117,7 +117,7 @@ const emit = defineEmits<{
connectStarted: [name: string]; connectStarted: [name: string];
connectSucceeded: [name: string]; connectSucceeded: [name: string];
connectFailed: [message: string]; connectFailed: [message: string];
openDriverStore: []; openDriverStore: [tab?: "jdbc"];
openTunnelProfileSettings: []; openTunnelProfileSettings: [];
}>(); }>();
@@ -1820,7 +1820,6 @@ const dbOptions: DbOption[] = [
{ value: "nacos", label: "Nacos" }, { value: "nacos", label: "Nacos" },
{ value: "influxdb", label: "InfluxDB" }, { value: "influxdb", label: "InfluxDB" },
{ value: "iris", label: "IRIS" }, { value: "iris", label: "IRIS" },
{ value: "jdbc", label: "JDBC" },
{ value: "manticoresearch", label: "Manticore Search" }, { value: "manticoresearch", label: "Manticore Search" },
{ value: "custom_mysql", label: "Custom (MySQL)" }, { value: "custom_mysql", label: "Custom (MySQL)" },
{ value: "custom_postgres", label: "Custom (PostgreSQL)" }, { value: "custom_postgres", label: "Custom (PostgreSQL)" },
@@ -3833,7 +3832,7 @@ function openExternalUrl(url: string) {
<template v-if="dialogStep === 'select'"> <template v-if="dialogStep === 'select'">
<div class="space-y-4"> <div class="space-y-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-end"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="flex shrink-0 rounded-lg border bg-muted/40 p-0.5"> <div class="flex shrink-0 rounded-lg border bg-muted/40 p-0.5">
<Button type="button" size="icon-sm" :variant="dbPickerView === 'icon' ? 'secondary' : 'ghost'" :title="t('connection.iconView')" :aria-label="t('connection.iconView')" @click="dbPickerView = 'icon'"> <Button type="button" size="icon-sm" :variant="dbPickerView === 'icon' ? 'secondary' : 'ghost'" :title="t('connection.iconView')" :aria-label="t('connection.iconView')" @click="dbPickerView = 'icon'">
@@ -3848,6 +3847,10 @@ function openExternalUrl(url: string) {
<Input v-model="dbSearchQuery" class="h-9 pl-8" :placeholder="t('connection.searchDatabasePlaceholder')" /> <Input v-model="dbSearchQuery" class="h-9 pl-8" :placeholder="t('connection.searchDatabasePlaceholder')" />
</div> </div>
</div> </div>
<Button data-jdbc-connection-entry type="button" variant="outline" class="h-9 shrink-0 gap-2" @click="goToConnectionStep('jdbc')">
<DatabaseIcon db-type="jdbc" class="h-4 w-4" />
{{ t("connection.jdbcConnection") }}
</Button>
</div> </div>
<div class="max-h-[58vh] space-y-5 overflow-y-auto pr-2"> <div class="max-h-[58vh] space-y-5 overflow-y-auto pr-2">
@@ -4132,7 +4135,7 @@ function openExternalUrl(url: string) {
{{ t("connection.jdbcPluginHint") }} {{ t("connection.jdbcPluginHint") }}
</p> </p>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<Button type="button" variant="outline" size="sm" @click="emit('openDriverStore')"> <Button type="button" variant="outline" size="sm" @click="emit('openDriverStore', 'jdbc')">
<FolderOpen class="h-3.5 w-3.5" /> <FolderOpen class="h-3.5 w-3.5" />
{{ t("toolbar.driverManager") }} {{ t("toolbar.driverManager") }}
</Button> </Button>
@@ -5057,7 +5060,7 @@ function openExternalUrl(url: string) {
{{ t("connection.jdbcPluginHint") }} {{ t("connection.jdbcPluginHint") }}
</p> </p>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<Button type="button" variant="outline" size="sm" @click="emit('openDriverStore')"> <Button type="button" variant="outline" size="sm" @click="emit('openDriverStore', 'jdbc')">
<FolderOpen class="h-3.5 w-3.5" /> <FolderOpen class="h-3.5 w-3.5" />
{{ t("toolbar.driverManager") }} {{ t("toolbar.driverManager") }}
</Button> </Button>
@@ -49,7 +49,7 @@ const emit = defineEmits<{
connectStarted: [name: string]; connectStarted: [name: string];
connectSucceeded: [name: string]; connectSucceeded: [name: string];
connectFailed: [message: string]; connectFailed: [message: string];
openDriverStore: []; openDriverStore: [tab?: "jdbc"];
openTunnelProfileSettings: []; openTunnelProfileSettings: [];
openLineageTarget: [ openLineageTarget: [
target: { target: {
@@ -136,7 +136,7 @@ watch(
@connect-started="emit('connectStarted', $event)" @connect-started="emit('connectStarted', $event)"
@connect-succeeded="emit('connectSucceeded', $event)" @connect-succeeded="emit('connectSucceeded', $event)"
@connect-failed="emit('connectFailed', $event)" @connect-failed="emit('connectFailed', $event)"
@open-driver-store="emit('openDriverStore')" @open-driver-store="emit('openDriverStore', $event)"
@open-tunnel-profile-settings="emit('openTunnelProfileSettings')" @open-tunnel-profile-settings="emit('openTunnelProfileSettings')"
/> />
<DangerConfirmDialog <DangerConfirmDialog
+1
View File
@@ -322,6 +322,7 @@ export default {
kafkaKerberosPathHint: "The keytab and krb5.conf paths are read by DBX Agent and must exist on the machine running DBX Agent; files are not uploaded from this browser.", kafkaKerberosPathHint: "The keytab and krb5.conf paths are read by DBX Agent and must exist on the machine running DBX Agent; files are not uploaded from this browser.",
kafkaKerberosAuthHint: "Uses GSSAPI + keytab login. If the server requires encrypted transport, set Security to SASL_SSL; otherwise Auto or SASL_PLAINTEXT can be used.", kafkaKerberosAuthHint: "Uses GSSAPI + keytab login. If the server requires encrypted transport, set Security to SASL_SSL; otherwise Auto or SASL_PLAINTEXT can be used.",
searchDatabasePlaceholder: "Search database types", searchDatabasePlaceholder: "Search database types",
jdbcConnection: "JDBC connection",
iconView: "Icon view", iconView: "Icon view",
listView: "List view", listView: "List view",
selectedDatabase: "Selected", selectedDatabase: "Selected",
+1
View File
@@ -302,6 +302,7 @@ export default withEnglishFallback({
nacosTlsSkipVerify: "Omitir verificación de certificado", nacosTlsSkipVerify: "Omitir verificación de certificado",
nacosPageSize: "Tamaño de página", nacosPageSize: "Tamaño de página",
searchDatabasePlaceholder: "Buscar tipos de bases de datos", searchDatabasePlaceholder: "Buscar tipos de bases de datos",
jdbcConnection: "Conexión JDBC",
iconView: "Vista de íconos", iconView: "Vista de íconos",
listView: "Vista de lista", listView: "Vista de lista",
selectedDatabase: "Seleccionada", selectedDatabase: "Seleccionada",
+1
View File
@@ -301,6 +301,7 @@ export default withEnglishFallback({
nacosTlsSkipVerify: "Salta verifica certificato", nacosTlsSkipVerify: "Salta verifica certificato",
nacosPageSize: "Dimensione pagina", nacosPageSize: "Dimensione pagina",
searchDatabasePlaceholder: "Cerca tipi di database", searchDatabasePlaceholder: "Cerca tipi di database",
jdbcConnection: "Connessione JDBC",
iconView: "Vista icone", iconView: "Vista icone",
listView: "Vista elenco", listView: "Vista elenco",
selectedDatabase: "Selezionato", selectedDatabase: "Selezionato",
+1
View File
@@ -295,6 +295,7 @@ export default withEnglishFallback({
nacosTlsSkipVerify: "証明書検証をスキップ", nacosTlsSkipVerify: "証明書検証をスキップ",
nacosPageSize: "ページサイズ", nacosPageSize: "ページサイズ",
searchDatabasePlaceholder: "データベースタイプを検索", searchDatabasePlaceholder: "データベースタイプを検索",
jdbcConnection: "JDBC 接続",
iconView: "アイコン表示", iconView: "アイコン表示",
listView: "リスト表示", listView: "リスト表示",
selectedDatabase: "選択中", selectedDatabase: "選択中",
+1
View File
@@ -302,6 +302,7 @@ export default withEnglishFallback({
nacosTlsSkipVerify: "Ignorar verificação do certificado", nacosTlsSkipVerify: "Ignorar verificação do certificado",
nacosPageSize: "Tamanho da página", nacosPageSize: "Tamanho da página",
searchDatabasePlaceholder: "Pesquisar tipos de banco de dados", searchDatabasePlaceholder: "Pesquisar tipos de banco de dados",
jdbcConnection: "Conexão JDBC",
iconView: "Visualização em ícones", iconView: "Visualização em ícones",
listView: "Visualização em lista", listView: "Visualização em lista",
selectedDatabase: "Selecionado", selectedDatabase: "Selecionado",
+1
View File
@@ -324,6 +324,7 @@ export default withEnglishFallback({
kafkaKerberosPathHint: "keytab 和 krb5.conf 路径由 DBX Agent 读取,必须存在于运行 DBX Agent 的机器上;不会从当前浏览器上传文件。", kafkaKerberosPathHint: "keytab 和 krb5.conf 路径由 DBX Agent 读取,必须存在于运行 DBX Agent 的机器上;不会从当前浏览器上传文件。",
kafkaKerberosAuthHint: "使用 GSSAPI + keytab 登录。若服务端要求加密传输,请将 Security 设为 SASL_SSL;否则可使用 Auto 或 SASL_PLAINTEXT。", kafkaKerberosAuthHint: "使用 GSSAPI + keytab 登录。若服务端要求加密传输,请将 Security 设为 SASL_SSL;否则可使用 Auto 或 SASL_PLAINTEXT。",
searchDatabasePlaceholder: "搜索数据库类型", searchDatabasePlaceholder: "搜索数据库类型",
jdbcConnection: "JDBC 连接",
iconView: "图标视图", iconView: "图标视图",
listView: "列表视图", listView: "列表视图",
selectedDatabase: "已选择", selectedDatabase: "已选择",
+1
View File
@@ -302,6 +302,7 @@ export default withEnglishFallback({
nacosTlsSkipVerify: "跳過憑證驗證", nacosTlsSkipVerify: "跳過憑證驗證",
nacosPageSize: "分頁大小", nacosPageSize: "分頁大小",
searchDatabasePlaceholder: "搜尋資料庫類型", searchDatabasePlaceholder: "搜尋資料庫類型",
jdbcConnection: "JDBC 連線",
iconView: "圖示檢視", iconView: "圖示檢視",
listView: "清單檢視", listView: "清單檢視",
selectedDatabase: "已選擇", selectedDatabase: "已選擇",
+46
View File
@@ -0,0 +1,46 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
import { test } from "vitest";
function source(relativePath: string): string {
return readFileSync(path.resolve(relativePath), "utf8");
}
test("keeps JDBC outside the database picker and exposes a dedicated entry", () => {
const content = source("apps/desktop/src/components/connection/ConnectionDialog.vue");
const optionsStart = content.indexOf("const dbOptions: DbOption[] = [");
const optionsEnd = content.indexOf("const dbCategories", optionsStart);
const pickerToolbar = content.indexOf("sm:justify-between");
const searchInput = content.indexOf('v-model="dbSearchQuery"', pickerToolbar);
const jdbcEntry = content.indexOf("data-jdbc-connection-entry", pickerToolbar);
assert.notEqual(optionsStart, -1);
assert.notEqual(optionsEnd, -1);
assert.equal(content.slice(optionsStart, optionsEnd).includes('{ value: "jdbc"'), false);
assert.ok(pickerToolbar < searchInput && searchInput < jdbcEntry);
assert.match(content.slice(jdbcEntry, jdbcEntry + 400), /goToConnectionStep\('jdbc'\)/);
});
test("opens driver management on the JDBC tab from JDBC connection settings", () => {
const connectionDialog = source("apps/desktop/src/components/connection/ConnectionDialog.vue");
const appDialogs = source("apps/desktop/src/components/layout/AppDialogs.vue");
const app = source("apps/desktop/src/App.vue");
const driverStore = source("apps/desktop/src/components/config/DriverStoreDialog.vue");
assert.equal(connectionDialog.match(/emit\('openDriverStore', 'jdbc'\)/g)?.length, 2);
assert.match(appDialogs, /@open-driver-store="emit\('openDriverStore', \$event\)"/);
assert.match(app, /openDriverStorePage\(\$event\)/);
assert.match(app, /v-model:active-tab="driverStoreActiveTab"/);
assert.match(driverStore, /"update:activeTab": \[tab: "agent" \| "jdbc" \| "storage" \| "runtime"\]/);
});
test("resets the driver management tab after the page is closed", () => {
const app = source("apps/desktop/src/App.vue");
const closeStart = app.indexOf("function closeDriverStorePage() {");
const closeEnd = app.indexOf("\n}", closeStart);
assert.notEqual(closeStart, -1);
assert.notEqual(closeEnd, -1);
assert.match(app.slice(closeStart, closeEnd), /driverStoreActiveTab\.value = "agent"/);
});