feat(connection): unify Ignite version selection

This commit is contained in:
SCSTC authored and GitHub committed 2026-08-20 21:01:23 +08:00
1 parent 8031a9e651
commit e8e980c25c
11 files changed
+145 -8

No files matched your search

@@ -194,6 +194,18 @@ const NACOS_CONNECTION_PROFILES: ReadonlyArray<{ value: NacosConnectionProfile;
{ value: "rnacos", title: "r-nacos" },
];
type IgniteConnectionProfile = "ignite" | "ignite3";
const IGNITE_CONNECTION_PROFILES: ReadonlyArray<{ value: IgniteConnectionProfile; title: string }> = [
{ value: "ignite", title: "Ignite 2.x" },
{ value: "ignite3", title: "Ignite 3.x" },
];
// The picker merges the Ignite 2.x/3.x cards into a single "Apache Ignite"
// entry; the version is picked inside the connection form instead.
const MERGED_PICKER_OPTION_FOR_TYPE: Record<string, string> = { ignite3: "ignite" };
const PICKER_SEARCH_ALIASES: Record<string, string[]> = { ignite: ["ignite3", "ignite 3"] };
type LegacyTransportFields = {
ssh_enabled?: boolean;
ssh_host?: string;
@@ -2883,6 +2895,16 @@ function onDbTypeChange(val: string) {
resetVisibleSchemasState();
}
function selectIgniteConnectionProfile(profile: IgniteConnectionProfile) {
if (form.value.db_type === profile) return;
const category = dbCategoryForOption(profile);
if (category) selectedDbCategory.value = category;
customDriverName.value = "";
applyProfile(profile, true);
resetTestState();
resetVisibleSchemasState();
}
function switchH2ConnectionMode(mode: H2ConnectionMode) {
h2ConnectionMode.value = mode;
if (mode === "file") {
@@ -3177,17 +3199,19 @@ assertCompleteDatabaseCategories(
dbCategoryDefinitions.map((category) => category.optionValues),
);
const hiddenPickerOptionTypes = new Set(Object.keys(MERGED_PICKER_OPTION_FOR_TYPE));
const dbCategories = computed<DbCategory[]>(() => {
return dbCategoryDefinitions.map((category) => ({
key: category.key,
title: t(category.titleKey),
options: dbOptions.filter((option) => category.optionValues.includes(option.value)),
options: dbOptions.filter((option) => category.optionValues.includes(option.value) && !hiddenPickerOptionTypes.has(option.value)),
}));
});
function matchesDbOption(option: DbOption, keyword: string, categoryTitle = "") {
const profile = driverProfiles[option.value];
return [option.label, option.value, profile?.label, profile?.type, categoryTitle].some((value) =>
return [option.label, option.value, profile?.label, profile?.type, categoryTitle, ...(PICKER_SEARCH_ALIASES[option.value] ?? [])].some((value) =>
String(value || "")
.toLowerCase()
.includes(keyword),
@@ -3213,7 +3237,11 @@ const visibleDbCategories = computed<DbCategory[]>(() => {
return filteredDbCategories.value.filter((category) => category.key === selectedDbCategory.value);
});
const hasDbPickerResults = computed(() => visibleDbCategories.value.some((category) => category.options.length > 0));
const selectedDbOptionIsVisible = computed(() => visibleDbCategories.value.some((category) => category.options.some((option) => option.value === selectedType.value)));
function isPickerOptionSelected(optionValue: string): boolean {
return selectedType.value === optionValue || MERGED_PICKER_OPTION_FOR_TYPE[selectedType.value] === optionValue;
}
const selectedDbOptionIsVisible = computed(() => visibleDbCategories.value.some((category) => category.options.some((option) => isPickerOptionSelected(option.value))));
function selectDbCategory(category: DbCategoryKey) {
selectedDbCategory.value = category;
@@ -3229,7 +3257,8 @@ function selectDbPickerView(view: DbPickerView) {
}
function dbCategoryForOption(value: string): DbCategoryKey | undefined {
return dbCategories.value.find((category) => category.options.some((option) => option.value === value))?.key;
const pickerValue = MERGED_PICKER_OPTION_FOR_TYPE[value] ?? value;
return dbCategories.value.find((category) => category.options.some((option) => option.value === pickerValue))?.key;
}
const selectedDbIcon = computed(() => iconTypeMap[selectedType.value] || selectedProfile().icon || selectedType.value);
@@ -5881,8 +5910,8 @@ function openExternalUrl(url: string) {
type="button"
:title="opt.label"
class="connection-db-picker-option group flex min-h-24 flex-col items-center justify-center gap-2 rounded-[4px] border bg-background/70 p-3 text-center transition hover:border-primary/40 hover:bg-muted/40 hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
:class="selectedType === opt.value ? 'dbx-tile-selected shadow-sm' : 'border-border'"
:aria-pressed="selectedType === opt.value"
:class="isPickerOptionSelected(opt.value) ? 'dbx-tile-selected shadow-sm' : 'border-border'"
:aria-pressed="isPickerOptionSelected(opt.value)"
@click="onDbTypeChange(opt.value)"
@dblclick="goToConnectionStep(opt.value)"
>
@@ -5901,8 +5930,8 @@ function openExternalUrl(url: string) {
:key="opt.value"
type="button"
class="connection-db-picker-option flex items-center gap-3 rounded-[4px] border bg-background px-3 py-2 text-left transition hover:border-primary/40 hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
:class="selectedType === opt.value ? 'dbx-tile-selected' : 'border-border'"
:aria-pressed="selectedType === opt.value"
:class="isPickerOptionSelected(opt.value) ? 'dbx-tile-selected' : 'border-border'"
:aria-pressed="isPickerOptionSelected(opt.value)"
@click="onDbTypeChange(opt.value)"
@dblclick="goToConnectionStep(opt.value)"
>
@@ -7270,6 +7299,23 @@ function openExternalUrl(url: string) {
<!-- MySQL / PostgreSQL: host, port, user, password, database -->
<template v-else>
<div v-if="form.db_type === 'ignite' || form.db_type === 'ignite3'" class="grid grid-cols-4 items-start gap-4">
<Label :class="connectionLabelSmallClass">{{ t("connection.igniteVersion") }}</Label>
<div class="col-span-3 grid grid-cols-2 gap-2">
<button
v-for="profile in IGNITE_CONNECTION_PROFILES"
:key="profile.value"
type="button"
class="min-w-0 rounded-md border px-3 py-2.5 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
:class="form.db_type === profile.value ? 'border-primary bg-primary/5 shadow-sm' : 'border-border bg-background'"
:aria-pressed="form.db_type === profile.value"
@click="selectIgniteConnectionProfile(profile.value)"
>
<span class="block truncate text-sm font-medium">{{ profile.title }}</span>
</button>
</div>
</div>
<div v-if="form.db_type === 'elasticsearch'" class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelSmallClass">{{ t("connection.mode") }}</Label>
<div class="col-span-3 grid h-8 grid-cols-2 overflow-hidden rounded-md border border-input bg-muted/30 p-0.5">
+1
View File
@@ -489,6 +489,7 @@ export default {
oracleTnsAliasRequired: "Oracle TNS mode requires a network alias.",
oracleTnsAdminRequired: "Oracle TNS mode requires the TNS_ADMIN directory containing tnsnames.ora.",
oracleTnsTransportUnsupported: "Oracle TNS connections cannot use SSH, proxy, or HTTP tunnel layers. Remove the transport layer or use Service Name/SID mode.",
igniteVersion: "Ignite version",
elasticsearchDirectMode: "Direct",
elasticsearchKibanaProxyMode: "Kibana Proxy",
elasticsearchKibanaHost: "Kibana Host",
+1
View File
@@ -1016,6 +1016,7 @@ export default withEnglishFallback({
mixed: "Mixto",
},
},
igniteVersion: "Versión de Ignite",
elasticsearchDirectMode: "Conexión directa",
elasticsearchKibanaProxyMode: "proxy de Kibana",
elasticsearchKibanaHost: "host de Kibana",
+1
View File
@@ -1014,6 +1014,7 @@ export default withEnglishFallback({
mixed: "Misto",
},
},
igniteVersion: "Versione di Ignite",
elasticsearchDirectMode: "Connessione diretta",
elasticsearchKibanaProxyMode: "Proxy Kibana",
elasticsearchKibanaHost: "Host Kibana",
+1
View File
@@ -1014,6 +1014,7 @@ export default withEnglishFallback({
mixed: "大文字小文字混在",
},
},
igniteVersion: "Ignite バージョン",
elasticsearchDirectMode: "直接接続",
elasticsearchKibanaProxyMode: "Kibana プロキシ",
elasticsearchKibanaHost: "Kibana ホスト",
+1
View File
@@ -483,6 +483,7 @@ export default withEnglishFallback({
oracleTnsAliasRequired: "Oracle TNS 모드에는 네트워크 별칭이 필요합니다.",
oracleTnsAdminRequired: "Oracle TNS 모드에는 tnsnames.ora가 포함된 TNS_ADMIN 디렉터리가 필요합니다.",
oracleTnsTransportUnsupported: "Oracle TNS 연결은 SSH, 프록시 또는 HTTP 터널 계층을 사용할 수 없습니다. 전송 계층을 제거하거나 서비스 이름/SID 모드를 사용하세요.",
igniteVersion: "Ignite 버전",
elasticsearchDirectMode: "직접",
elasticsearchKibanaProxyMode: "Kibana 프록시",
elasticsearchKibanaHost: "Kibana 호스트",
+1
View File
@@ -1015,6 +1015,7 @@ export default withEnglishFallback({
mixed: "Maiúsculas e minúsculas",
},
},
igniteVersion: "Versão do Ignite",
elasticsearchDirectMode: "Conexão Direta",
elasticsearchKibanaProxyMode: "Proxy do Kibana",
elasticsearchKibanaHost: "Host do Kibana",
+1
View File
@@ -414,6 +414,7 @@ export default withEnglishFallback({
oracleTnsAliasRequired: "Oracle TNS 模式需要填写网络别名。",
oracleTnsAdminRequired: "Oracle TNS 模式需要填写包含 tnsnames.ora 的 TNS_ADMIN 目录。",
oracleTnsTransportUnsupported: "Oracle TNS 连接不能与 SSH、代理或 HTTP 隧道组合使用。请移除传输层,或改用服务名/SID 模式。",
igniteVersion: "Ignite 版本",
elasticsearchDirectMode: "直连",
elasticsearchKibanaProxyMode: "Kibana 代理",
elasticsearchKibanaHost: "Kibana 主机",
+1
View File
@@ -1014,6 +1014,7 @@ export default withEnglishFallback({
mixed: "混合大小寫",
},
},
igniteVersion: "Ignite 版本",
elasticsearchDirectMode: "直接連線",
elasticsearchKibanaProxyMode: "Kibana 代理",
elasticsearchKibanaHost: "Kibana 主機",
@@ -75,6 +75,63 @@ function profileSwitchHarness(selectedProfile: string, editing = false) {
return { customDriverName, events, form, selectProfile, selectedType };
}
function igniteProfileSwitchHarness(selectedProfile: "ignite" | "ignite3") {
const form = {
value: {
db_type: selectedProfile,
driver_profile: selectedProfile,
driver_label: selectedProfile === "ignite" ? "Apache Ignite 2.x" : "Apache Ignite 3.x",
host: "ignite.example.com",
port: 10800,
username: "draft-user",
password: "draft-password",
database: "draft-database",
ssl: true,
url_params: "sslMode=require",
connection_string: "jdbc:ignite:thin://ignite.example.com:10800/draft-database",
agent_java_options: ["-Xms256m", "-Xmx1g"],
},
};
const selectedType = { value: selectedProfile };
const selectedDbCategory = { value: "sql" };
const customDriverName = { value: "" };
const events: string[] = [];
function applyProfile(profile: "ignite" | "ignite3", preserveConnectionFields: boolean) {
events.push(`apply:${profile}:${preserveConnectionFields}`);
selectedType.value = profile;
form.value.db_type = profile;
form.value.driver_profile = profile;
form.value.driver_label = profile === "ignite" ? "Apache Ignite 2.x" : "Apache Ignite 3.x";
if (!preserveConnectionFields) {
form.value.host = "127.0.0.1";
form.value.username = "";
form.value.password = "";
form.value.database = "";
form.value.ssl = false;
form.value.url_params = "";
form.value.connection_string = "";
form.value.agent_java_options = [];
}
}
const javascript = ts.transpileModule(functionSource("selectIgniteConnectionProfile"), {
compilerOptions: { module: ts.ModuleKind.None, target: ts.ScriptTarget.ES2022 },
}).outputText;
const selectProfile = new Function("form", "selectedType", "dbCategoryForOption", "selectedDbCategory", "customDriverName", "applyProfile", "resetTestState", "resetVisibleSchemasState", `${javascript}\nreturn selectIgniteConnectionProfile;`)(
form,
selectedType,
() => "sql",
selectedDbCategory,
customDriverName,
applyProfile,
() => events.push("reset-test"),
() => events.push("reset-schemas"),
) as (profile: "ignite" | "ignite3") => void;
return { events, form, selectProfile, selectedType };
}
describe("ConnectionDialog database profile switching", () => {
it.each([
["mysql", ""],
@@ -116,4 +173,30 @@ describe("ConnectionDialog database profile switching", () => {
expect(harness.customDriverName.value).toBe("");
expect(harness.events).toEqual(["apply:custom_mysql:true", "reset-test", "reset-schemas"]);
});
it.each([
["ignite", "ignite3"],
["ignite3", "ignite"],
] as const)("preserves an Ignite %s draft when switching to %s", (from, to) => {
const harness = igniteProfileSwitchHarness(from);
const connectionFields = {
host: harness.form.value.host,
port: harness.form.value.port,
username: harness.form.value.username,
password: harness.form.value.password,
database: harness.form.value.database,
ssl: harness.form.value.ssl,
url_params: harness.form.value.url_params,
connection_string: harness.form.value.connection_string,
agent_java_options: [...harness.form.value.agent_java_options],
};
harness.selectProfile(to);
expect(harness.form.value).toMatchObject(connectionFields);
expect(harness.form.value.db_type).toBe(to);
expect(harness.form.value.driver_profile).toBe(to);
expect(harness.selectedType.value).toBe(to);
expect(harness.events).toEqual([`apply:${to}:true`, "reset-test", "reset-schemas"]);
});
});