feat(gaussdb): support multi-host connections
This commit is contained in:
1 parent
c0a6d6f2f9
commit
336fbe094b
16 files changed
+358
-25
No files matched your search
@@ -34,6 +34,7 @@ import { applyParsedConnectionUrl, normalizeMongoConnectionString, parseConnecti
|
||||
import { buildOracleTnsConnectionString, normalizeOracleTnsAdminPath, parseOracleTnsConnectionString } from "@/lib/connection/oracleTnsConnection";
|
||||
import { parseConnectionDeepLink, type ConnectionDeepLinkDraft } from "@/lib/connection/connectionDeepLink";
|
||||
import { connectionUrlPlaceholder as getUrlPlaceholder } from "@/lib/connection/connectionPresentation";
|
||||
import { parseGaussdbHosts, serializeGaussdbHosts, type GaussdbHostEntry } from "@/lib/connection/gaussdbHosts";
|
||||
import { h2ConnectionModeForConfig, h2FileJdbcUrlWithPath, h2FilePathFromJdbcUrl, isH2SplitJdbcUrl, type H2ConnectionMode } from "@/lib/database/h2Connection";
|
||||
import { firstZooKeeperEndpoint, normalizeZooKeeperConnectString } from "@/lib/zookeeper/zookeeperConnection";
|
||||
import { setZooKeeperAuthScheme, zooKeeperAuthScheme as resolveZooKeeperAuthScheme, type ZooKeeperAuthScheme } from "@/lib/zookeeper/zookeeperConnectionOptions";
|
||||
@@ -476,6 +477,36 @@ const gaussdbQuoteStyle = computed<GaussdbIdentifierQuoteStyle>({
|
||||
},
|
||||
});
|
||||
|
||||
const gaussdbHostEntries = ref<GaussdbHostEntry[]>(parseGaussdbHosts(form.value.host, form.value.port));
|
||||
|
||||
watch(
|
||||
() => form.value.db_type,
|
||||
(dbType) => {
|
||||
if (dbType === "gaussdb") {
|
||||
gaussdbHostEntries.value = parseGaussdbHosts(form.value.host, form.value.port);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [form.value.host, form.value.port] as const,
|
||||
([host, port]) => {
|
||||
if (form.value.db_type === "gaussdb") {
|
||||
gaussdbHostEntries.value = parseGaussdbHosts(host, port);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function addGaussdbHostEntry() {
|
||||
const lastPort = gaussdbHostEntries.value.length > 0 ? gaussdbHostEntries.value[gaussdbHostEntries.value.length - 1].port : 5432;
|
||||
gaussdbHostEntries.value.push({ host: "", port: lastPort });
|
||||
}
|
||||
|
||||
function removeGaussdbHostEntry(idx: number) {
|
||||
if (gaussdbHostEntries.value.length <= 1) return;
|
||||
gaussdbHostEntries.value.splice(idx, 1);
|
||||
}
|
||||
|
||||
function resizeNoteTextarea() {
|
||||
const textarea = noteTextareaRef.value;
|
||||
if (!textarea) return;
|
||||
@@ -3227,6 +3258,11 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo
|
||||
throw new Error(t("connection.kingbaseDatabaseRequired"));
|
||||
}
|
||||
}
|
||||
if (config.db_type === "gaussdb") {
|
||||
const serialized = serializeGaussdbHosts(gaussdbHostEntries.value);
|
||||
config.host = serialized.host;
|
||||
config.port = serialized.port;
|
||||
}
|
||||
if (isCloudflareD1Connection(config)) {
|
||||
normalizeCloudflareD1Connection(config);
|
||||
if (!hasCloudflareD1Credentials(config)) {
|
||||
@@ -6157,7 +6193,26 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="form.db_type !== 'oracle' || form.oracle_connection_type !== 'tns'" class="grid grid-cols-4 items-center gap-4">
|
||||
<!-- GaussDB: multi-host dynamic list -->
|
||||
<template v-if="form.db_type === 'gaussdb'">
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<Label :class="connectionLabelTopClass">{{ t("connection.host") }}</Label>
|
||||
<div class="col-span-3 space-y-2">
|
||||
<div v-for="(entry, idx) in gaussdbHostEntries" :key="idx" class="flex items-start gap-2">
|
||||
<Input v-model="entry.host" class="flex-1 min-w-0 break-all" placeholder="127.0.0.1" />
|
||||
<Input v-model.number="entry.port" type="number" class="w-24 shrink-0" />
|
||||
<Button type="button" variant="outline" size="icon" class="h-9 w-9 shrink-0 mt-0.5" :disabled="gaussdbHostEntries.length <= 1" @click="removeGaussdbHostEntry(idx)">
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" class="mt-1" @click="addGaussdbHostEntry">
|
||||
<Plus class="mr-1 h-3.5 w-3.5" />
|
||||
{{ t("connection.addHost") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="form.db_type !== 'oracle' || form.oracle_connection_type !== 'tns'" class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelClass">{{ form.db_type === "elasticsearch" && elasticsearchConnectionMode === "kibana" ? t("connection.elasticsearchKibanaHost") : t("connection.host") }}</Label>
|
||||
<Input v-model="form.host" class="col-span-2" />
|
||||
<Input v-model.number="form.port" type="number" class="col-span-1" @input="markSqlServerPortExplicit" />
|
||||
|
||||
@@ -361,6 +361,8 @@ type DetailTooltipRow = {
|
||||
label: string;
|
||||
value: string;
|
||||
multiline?: boolean;
|
||||
/** When set, renders each value on its own line (e.g. one host per line) */
|
||||
values?: string[];
|
||||
};
|
||||
|
||||
function cleanTooltipValue(value: string | number | null | undefined): string {
|
||||
@@ -376,7 +378,7 @@ function redactedConnectionString(value: string): string {
|
||||
}
|
||||
|
||||
function hostForDisplay(host: string): string {
|
||||
if (!host.includes(":") || host.startsWith("[") || host.includes("://")) return host;
|
||||
if (!host.includes(":") || host.startsWith("[") || host.includes("://") || host.includes(",")) return host;
|
||||
return `[${host}]`;
|
||||
}
|
||||
|
||||
@@ -410,10 +412,17 @@ const detailTooltip = computed(() => {
|
||||
const config = connectionStore.getConfig(node.connectionId);
|
||||
if (!config) return null;
|
||||
const hostLabel = isLocalFileConnection(config) ? t("connection.filePath") : t("connection.host");
|
||||
const hostValue = cleanTooltipValue(config.host);
|
||||
const hostValues = hostValue.includes(",")
|
||||
? hostValue
|
||||
.split(",")
|
||||
.map((h) => h.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const rows: DetailTooltipRow[] = [
|
||||
{ label: t("connection.name"), value: cleanTooltipValue(config.name) },
|
||||
{ label: "URL", value: connectionTooltipUrl(config), multiline: true },
|
||||
{ label: hostLabel, value: cleanTooltipValue(config.host), multiline: isLocalFileConnection(config) },
|
||||
...(hostValues.length > 0 ? [{ label: hostLabel, value: hostValues[0], values: hostValues } as DetailTooltipRow] : [{ label: hostLabel, value: hostValue, multiline: isLocalFileConnection(config) } as DetailTooltipRow]),
|
||||
{ label: "Port", value: Number(config.port) > 0 ? String(config.port) : "" },
|
||||
{ label: t("connection.database"), value: cleanTooltipValue(config.database) },
|
||||
{ label: t("connection.user"), value: cleanTooltipValue(config.username) },
|
||||
@@ -1220,8 +1229,13 @@ function onKeydown(event: KeyboardEvent) {
|
||||
<div class="w-max min-w-40 max-w-[min(28rem,calc(100vw-24px))] rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-lg">
|
||||
<div class="space-y-1">
|
||||
<div v-for="row in detailTooltip.rows" :key="row.label" class="grid grid-cols-[max-content_minmax(0,1fr)] gap-2 text-xs leading-5">
|
||||
<span class="text-muted-foreground">{{ row.label }}</span>
|
||||
<span v-if="row.multiline" class="max-h-20 overflow-hidden whitespace-pre-wrap break-words text-foreground/90">
|
||||
<span class="text-muted-foreground shrink-0">{{ row.label }}</span>
|
||||
<template v-if="row.values">
|
||||
<div class="flex flex-col gap-0.5 font-mono text-foreground/90">
|
||||
<span v-for="(v, vi) in row.values" :key="vi" class="break-all">{{ v }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else-if="row.multiline" class="max-h-20 overflow-hidden whitespace-pre-wrap break-words text-foreground/90">
|
||||
{{ row.value }}
|
||||
</span>
|
||||
<span v-else class="truncate font-mono text-foreground/90" :title="row.value">{{ row.value }}</span>
|
||||
|
||||
Reference in new issue
Block a user