feat(zookeeper): add agent client browsing and node management

This commit is contained in:
miracle authored and GitHub committed 2026-06-25 00:40:48 +08:00
1 parent a0df43c391
commit 0effda91b3
65 files changed
+2037 -420

No files matched your search

@@ -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<string, string> = {
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) {
</div>
</template>
<!-- ZooKeeper: host, connect string, user, password -->
<template v-else-if="form.db_type === 'zookeeper'">
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t("connection.host") }}</Label>
<Input v-model="form.host" class="col-span-2" placeholder="127.0.0.1" />
<Input v-model.number="form.port" type="number" class="col-span-1" />
</div>
<div class="grid grid-cols-4 items-start gap-4">
<Label class="text-right mt-2">{{ t("connection.zookeeperConnectString") }}</Label>
<div class="col-span-3 space-y-1">
<textarea
v-model="zookeeperConnectString"
class="flex min-h-[76px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
placeholder="127.0.0.1:2181&#10;zk-2:2181"
spellcheck="false"
/>
<p class="text-xs text-muted-foreground">
{{ t("connection.zookeeperConnectStringHint") }}
</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t("connection.user") }}</Label>
<Input v-model="form.username" class="col-span-3" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t("connection.password") }}</Label>
<PasswordInput v-model="form.password" class="col-span-3" />
</div>
</template>
<!-- MongoDB: URL or form -->
<template v-else-if="form.db_type === 'mongodb'">
<div class="grid grid-cols-4 items-center gap-4">