diff --git a/apps/desktop/public/icons/database/zookeeper.svg b/apps/desktop/public/icons/database/zookeeper.svg new file mode 100644 index 0000000000..7d0da946df --- /dev/null +++ b/apps/desktop/public/icons/database/zookeeper.svg @@ -0,0 +1,19 @@ + + \ No newline at end of file diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 7f4f6e4bb1..610e1b607c 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -1036,6 +1036,8 @@ async function handleQuickOpenSelect(item: any) { await connectionStore.loadRedisDatabases(item.connectionId); } else if (config?.db_type === "etcd") { await connectionStore.loadEtcdRoot(item.connectionId); + } else if (config?.db_type === "zookeeper") { + await connectionStore.loadZooKeeperRoot(item.connectionId); } else if (config?.db_type === "mongodb") { await connectionStore.loadMongoDatabases(item.connectionId); } else if (config?.db_type === "elasticsearch") { @@ -1059,6 +1061,8 @@ async function handleQuickOpenSelect(item: any) { await connectionStore.loadRedisDatabases(item.connectionId); } else if (config?.db_type === "etcd") { await connectionStore.loadEtcdRoot(item.connectionId); + } else if (config?.db_type === "zookeeper") { + await connectionStore.loadZooKeeperRoot(item.connectionId); } else if (config?.db_type === "mongodb") { await connectionStore.loadMongoDatabases(item.connectionId); } else if (config?.db_type === "elasticsearch") { diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 1700e7f137..8430c0f8c9 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -26,6 +26,7 @@ import { applyParsedConnectionUrl, normalizeMongoConnectionString, parseConnecti import type { ConnectionDeepLinkDraft } from "@/lib/connectionDeepLink"; import { connectionUrlPlaceholder as getUrlPlaceholder } from "@/lib/connectionPresentation"; import { h2ConnectionModeForConfig, h2FileJdbcUrl, h2FilePathFromJdbcUrl, type H2ConnectionMode } from "@/lib/h2Connection"; +import { firstZooKeeperEndpoint, normalizeZooKeeperConnectString } from "@/lib/zookeeperConnection"; import { isLocalFileTypeDb } from "@/lib/connectionFile"; import { MQ_PINNED_VERSION_OPTIONS, pinnedVersionToSelection, selectionToPinnedVersion } from "@/lib/mqPinnedVersionOptions"; import { mongodbAuthFailureHint, mongoUrlParam, setMongoUrlParam } from "@/lib/mongoConnectionOptions"; @@ -523,6 +524,7 @@ const driverProfiles: Record< xugu: { type: "xugu", port: 5138, user: "", label: "虚谷 XuguDB", icon: "xugu" }, iotdb: { type: "iotdb", port: 6667, user: "root", label: "Apache IoTDB", icon: "iotdb" }, etcd: { type: "etcd", port: 2379, user: "", label: "etcd", icon: "etcd" }, + zookeeper: { type: "zookeeper", port: 2181, user: "", label: "Apache ZooKeeper", icon: "zookeeper" }, mq: { type: "mq", port: 8080, user: "", label: "Apache Pulsar", icon: "pulsar", host: "127.0.0.1" }, nacos: { type: "nacos", port: 8848, user: "nacos", label: "Nacos", icon: "nacos", host: "127.0.0.1" }, iris: { type: "iris", port: 1972, user: "_SYSTEM", label: "IRIS", icon: "iris" }, @@ -799,6 +801,14 @@ function applyProfile(val: string, preserveConnectionFields = false) { form.value.database = undefined; form.value.connection_string = undefined; } + if (profile.type === "zookeeper") { + form.value.database = undefined; + form.value.connection_string = ""; + form.value.ssl = false; + form.value.ca_cert_path = ""; + form.value.client_cert_path = ""; + form.value.client_key_path = ""; + } if (profile.type === "nacos") { resetNacosFields(); form.value.database = undefined; @@ -1042,6 +1052,7 @@ const iconTypeMap: Record = { xugu: "xugu", iotdb: "iotdb", etcd: "etcd", + zookeeper: "zookeeper", mq: "mq", nacos: "nacos", dm: "dm", @@ -1126,6 +1137,7 @@ const dbOptions: DbOption[] = [ { value: "xugu", label: "虚谷 XuguDB" }, { value: "iotdb", label: "Apache IoTDB" }, { value: "etcd", label: "etcd" }, + { value: "zookeeper", label: "Apache ZooKeeper" }, { value: "mq", label: "Apache Pulsar" }, { value: "nacos", label: "Nacos" }, { value: "influxdb", label: "InfluxDB" }, @@ -1251,6 +1263,12 @@ const etcdEndpointsLines = computed({ form.value.etcd_endpoints = normalizeEndpointLines(value); }, }); +const zookeeperConnectString = computed({ + get: () => form.value.connection_string || "", + set: (value: string) => { + form.value.connection_string = normalizeZooKeeperConnectString(value); + }, +}); const canUseTransportLayers = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "access" && !isH2FileMode.value); const shouldShowAgentDriverInstallHint = computed(() => showAgentDriverInstallHint(form.value.db_type, agentDrivers.value, form.value.driver_profile)); const h2DriverMissing = computed(() => form.value.db_type === "h2" && isH2FileMode.value && agentDrivers.value.find((d) => d.db_type === "h2")?.installed !== true); @@ -1284,6 +1302,7 @@ const testResultMessage = computed(() => { }); const hasRequiredConnectionTarget = computed(() => { if (form.value.db_type === "mq") return !!mqAdminUrl.value.trim(); + if (form.value.db_type === "zookeeper") return !!(form.value.host || form.value.connection_string || connectionUrlInput.value.trim()); if (form.value.db_type === "nacos") return !!nacosServerAddr.value.trim(); if (isH2FileMode.value) return !!(form.value.host.trim() || h2FilePathFromJdbcUrl(form.value.connection_string)); return !!(form.value.host || (mongoUseUrl.value && form.value.connection_string) || (form.value.db_type === "jdbc" && form.value.connection_string) || connectionUrlInput.value.trim()); @@ -1500,6 +1519,17 @@ function connectionConfigForSubmit(id: string): ConnectionConfig { if (config.db_type === "redis") { config.redis_key_separator = config.redis_key_separator?.trim() ?? ":"; } + if (config.db_type === "zookeeper") { + const normalizedConnectString = normalizeZooKeeperConnectString(config.connection_string || ""); + config.connection_string = normalizedConnectString || undefined; + const firstEndpoint = firstZooKeeperEndpoint(normalizedConnectString || (config.host ? `${config.host}:${config.port || 2181}` : "")); + if (firstEndpoint) { + config.host = firstEndpoint.host; + config.port = firstEndpoint.port; + } + config.database = undefined; + config.ssl = false; + } if (config.db_type === "etcd") { config.etcd_endpoints = normalizeEndpointLines(config.etcd_endpoints || ""); const firstEndpoint = firstEtcdEndpoint(config.etcd_endpoints); @@ -3083,6 +3113,37 @@ function openExternalUrl(url: string) { + +