feat(connection): add compatible driver profiles

This commit is contained in:
t8y2
2026-04-30 14:00:59 +08:00
parent 68430d16c1
commit 4ef13e68c4
8 changed files with 202 additions and 51 deletions
+16 -5
View File
@@ -78,7 +78,7 @@ const activeTabContext = computed(() => {
const items = [
connection.name,
connection.db_type.toUpperCase(),
connectionDriverLabel(connection),
];
if (tab.tableMeta?.tableName) {
@@ -100,7 +100,7 @@ const activeConnectionValue = computed(() => activeConnection.value?.id || "");
const connectionStats = computed(() => {
const total = connectionStore.connections.length;
const connected = connectionStore.connectedIds.size;
const types = new Set(connectionStore.connections.map((connection) => connection.db_type)).size;
const types = new Set(connectionStore.connections.map((connection) => connection.driver_profile || connection.db_type)).size;
return { total, connected, types };
});
@@ -110,6 +110,14 @@ function connectionDisplayName(connectionId: string): string {
return connectionStore.getConfig(connectionId)?.name || connectionId;
}
function connectionDriverLabel(connection?: ConnectionConfig): string {
return connection?.driver_label || connection?.db_type.toUpperCase() || "";
}
function connectionIconType(connection?: ConnectionConfig): string {
return connection?.driver_profile || connection?.db_type || "postgres";
}
function connectionColor(connectionId: string): string {
return connectionStore.getConfig(connectionId)?.color || "";
}
@@ -424,6 +432,9 @@ async function setupFileDrop() {
id: crypto.randomUUID(),
name,
db_type: dbType,
driver_profile: dbType,
driver_label: dbType === "duckdb" ? "DuckDB" : "SQLite",
url_params: "",
host: path,
port: 0,
username: "",
@@ -599,7 +610,7 @@ async function setupFileDrop() {
</SelectContent>
</Select>
<span class="text-muted-foreground/50">/</span>
<span class="shrink-0">{{ activeConnection?.db_type.toUpperCase() }}</span>
<span class="shrink-0">{{ connectionDriverLabel(activeConnection) }}</span>
<span class="text-muted-foreground/50">/</span>
<Select
:model-value="activeDatabaseValue"
@@ -748,12 +759,12 @@ async function setupFileDrop() {
class="flex w-full items-center gap-3 px-4 py-3 text-left hover:bg-muted/40"
@click="openConnectionQuery(connection.id)"
>
<DatabaseIcon :db-type="connection.db_type" class="h-4 w-4" />
<DatabaseIcon :db-type="connectionIconType(connection)" class="h-4 w-4" />
<span v-if="connection.color" class="h-5 w-1 rounded-full shrink-0" :style="{ backgroundColor: connection.color }" />
<div class="min-w-0 flex-1">
<div class="truncate text-sm font-medium">{{ connection.name }}</div>
<div class="truncate text-xs text-muted-foreground">
{{ connection.db_type.toUpperCase() }} · {{ connection.host || connection.database || 'local' }}{{ connection.port ? ':' + connection.port : '' }}
{{ connectionDriverLabel(connection) }} · {{ connection.host || connection.database || 'local' }}{{ connection.port ? ':' + connection.port : '' }}
</div>
</div>
<FilePlus2 class="h-4 w-4 text-muted-foreground" />
+87 -31
View File
@@ -31,6 +31,9 @@ const editingId = ref<string | null>(null);
const defaultForm = (): Omit<ConnectionConfig, "id"> => ({
name: "",
db_type: "mysql",
driver_profile: "mysql",
driver_label: "MySQL",
url_params: "",
host: "127.0.0.1",
port: 3306,
username: "root",
@@ -48,6 +51,7 @@ const defaultForm = (): Omit<ConnectionConfig, "id"> => ({
const form = ref(defaultForm());
const selectedType = ref("mysql");
const customDriverName = ref("");
const colorOptions = [
{ value: "", class: "bg-transparent border-dashed", labelKey: "connection.colorNone" },
@@ -59,12 +63,69 @@ const colorOptions = [
{ value: "#a855f7", class: "bg-purple-500", labelKey: "connection.colorPurple" },
];
const driverProfiles: Record<string, { type: DatabaseType; port: number; user: string; label: string; icon: string; urlParams?: string }> = {
mysql: { type: "mysql", port: 3306, user: "root", label: "MySQL", icon: "mysql", urlParams: "" },
postgres: { type: "postgres", port: 5432, user: "postgres", label: "PostgreSQL", icon: "postgres", urlParams: "" },
redis: { type: "redis", port: 6379, user: "", label: "Redis", icon: "redis" },
sqlite: { type: "sqlite", port: 0, user: "", label: "SQLite", icon: "sqlite" },
duckdb: { type: "duckdb", port: 0, user: "", label: "DuckDB", icon: "duckdb" },
mongodb: { type: "mongodb", port: 27017, user: "", label: "MongoDB", icon: "mongodb" },
clickhouse: { type: "clickhouse", port: 8123, user: "default", label: "ClickHouse", icon: "clickhouse" },
sqlserver: { type: "sqlserver", port: 1433, user: "sa", label: "SQL Server", icon: "sqlserver" },
oracle: { type: "oracle", port: 1521, user: "system", label: "Oracle", icon: "oracle" },
mariadb: { type: "mysql", port: 3306, user: "root", label: "MariaDB", icon: "mariadb" },
tidb: { type: "mysql", port: 4000, user: "root", label: "TiDB", icon: "tidb" },
oceanbase: { type: "mysql", port: 2881, user: "root", label: "OceanBase", icon: "oceanbase" },
goldendb: { type: "mysql", port: 3306, user: "root", label: "GoldenDB", icon: "goldendb" },
opengauss: { type: "postgres", port: 5432, user: "gaussdb", label: "openGauss", icon: "opengauss", urlParams: "sslmode=disable" },
gaussdb: { type: "postgres", port: 5432, user: "gaussdb", label: "GaussDB", icon: "gaussdb" },
kingbase: { type: "postgres", port: 54321, user: "system", label: "KingBase", icon: "kingbase" },
vastbase: { type: "postgres", port: 5432, user: "vastbase", label: "Vastbase", icon: "vastbase" },
custom_mysql: { type: "mysql", port: 3306, user: "root", label: "Custom MySQL-compatible", icon: "mysql", urlParams: "" },
custom_postgres: { type: "postgres", port: 5432, user: "postgres", label: "Custom PostgreSQL-compatible", icon: "postgres", urlParams: "" },
};
function profileForConfig(config: ConnectionConfig) {
if (config.driver_profile && driverProfiles[config.driver_profile]) return config.driver_profile;
return config.db_type;
}
function selectedProfile() {
return driverProfiles[selectedType.value] ?? driverProfiles.mysql;
}
function isCustomCompatibleProfile() {
return selectedType.value === "custom_mysql" || selectedType.value === "custom_postgres";
}
function applyProfile(val: string, preserveConnectionFields = false) {
const profile = driverProfiles[val];
if (!profile) return;
selectedType.value = val;
form.value.db_type = profile.type;
form.value.driver_profile = val;
form.value.driver_label = isCustomCompatibleProfile()
? (customDriverName.value.trim() || profile.label)
: profile.label;
if (!preserveConnectionFields) {
form.value.port = profile.port;
form.value.username = profile.user;
form.value.url_params = profile.urlParams || "";
}
}
watch(() => props.editConfig, (config) => {
if (config) {
const profile = profileForConfig(config);
editingId.value = config.id;
form.value = {
name: config.name,
db_type: config.db_type,
driver_profile: profile,
driver_label: config.driver_label || driverProfiles[profile]?.label || config.db_type,
url_params: config.url_params || "",
host: config.host,
port: config.port,
username: config.username,
@@ -79,11 +140,13 @@ watch(() => props.editConfig, (config) => {
ssh_key_path: config.ssh_key_path || "",
ssl: config.ssl || false,
};
selectedType.value = config.db_type;
selectedType.value = profile;
customDriverName.value = isCustomCompatibleProfile() ? (config.driver_label || "") : "";
} else {
editingId.value = null;
form.value = defaultForm();
selectedType.value = "mysql";
customDriverName.value = "";
}
testResult.value = null;
});
@@ -92,42 +155,17 @@ const isEditing = ref(false);
watch(() => editingId.value, (v) => { isEditing.value = !!v; });
function onDbTypeChange(val: string) {
const aliasMap: Record<string, { type: DatabaseType; port: number; user: string }> = {
mysql: { type: "mysql", port: 3306, user: "root" },
postgres: { type: "postgres", port: 5432, user: "postgres" },
redis: { type: "redis", port: 6379, user: "" },
sqlite: { type: "sqlite", port: 0, user: "" },
duckdb: { type: "duckdb", port: 0, user: "" },
mongodb: { type: "mongodb", port: 27017, user: "" },
clickhouse: { type: "clickhouse", port: 8123, user: "default" },
sqlserver: { type: "sqlserver", port: 1433, user: "sa" },
oracle: { type: "oracle", port: 1521, user: "system" },
tidb: { type: "mysql", port: 4000, user: "root" },
oceanbase: { type: "mysql", port: 2881, user: "root" },
goldendb: { type: "mysql", port: 3306, user: "root" },
mariadb: { type: "mysql", port: 3306, user: "root" },
opengauss: { type: "postgres", port: 5432, user: "gaussdb" },
gaussdb: { type: "postgres", port: 5432, user: "gaussdb" },
kingbase: { type: "postgres", port: 54321, user: "system" },
vastbase: { type: "postgres", port: 5432, user: "vastbase" },
};
const alias = aliasMap[val];
if (alias) {
selectedType.value = val;
form.value.db_type = alias.type;
if (!editingId.value) {
form.value.port = alias.port;
form.value.username = alias.user;
}
}
customDriverName.value = "";
applyProfile(val, !!editingId.value);
}
const iconTypeMap: Record<string, string> = {
mysql: "mysql", postgres: "postgres", sqlite: "sqlite", redis: "redis",
mongodb: "mongodb", duckdb: "duckdb", clickhouse: "clickhouse", sqlserver: "sqlserver",
oracle: "oracle",
mariadb: "mariadb", tidb: "tidb", oceanbase: "mysql", goldendb: "mysql",
opengauss: "postgres", gaussdb: "postgres", kingbase: "postgres", vastbase: "postgres",
mariadb: "mariadb", tidb: "tidb", oceanbase: "oceanbase", goldendb: "goldendb",
opengauss: "opengauss", gaussdb: "gaussdb", kingbase: "kingbase", vastbase: "vastbase",
custom_mysql: "mysql", custom_postgres: "postgres",
};
const dbOptions = [
@@ -147,6 +185,7 @@ const mysqlCompat = [
{ value: "tidb", label: "TiDB" },
{ value: "oceanbase", label: "OceanBase" },
{ value: "goldendb", label: "GoldenDB" },
{ value: "custom_mysql", label: "Custom MySQL-compatible" },
];
const pgCompat = [
@@ -154,8 +193,15 @@ const pgCompat = [
{ value: "gaussdb", label: "GaussDB" },
{ value: "kingbase", label: "KingBase" },
{ value: "vastbase", label: "Vastbase" },
{ value: "custom_postgres", label: "Custom PostgreSQL-compatible" },
];
watch(customDriverName, (value) => {
if (isCustomCompatibleProfile()) {
form.value.driver_label = value.trim() || selectedProfile().label;
}
});
async function testConnection() {
isTesting.value = true;
testResult.value = null;
@@ -257,6 +303,11 @@ watch([() => editingId.value, () => open.value], () => {
</Select>
</div>
<div v-if="isCustomCompatibleProfile()" class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t('connection.driverName') }}</Label>
<Input v-model="customDriverName" class="col-span-3" :placeholder="t('connection.driverNamePlaceholder')" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t('connection.color') }}</Label>
<div class="col-span-3 flex items-center gap-1.5">
@@ -326,6 +377,11 @@ watch([() => editingId.value, () => open.value], () => {
<Label class="text-right">{{ t('connection.database') }}</Label>
<Input v-model="form.database" class="col-span-3" :placeholder="t('connection.databasePlaceholder')" />
</div>
<div v-if="form.db_type === 'mysql' || form.db_type === 'postgres'" class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t('connection.urlParams') }}</Label>
<Input v-model="form.url_params" class="col-span-3" :placeholder="form.db_type === 'postgres' ? 'sslmode=disable' : 'charset=utf8mb4'" />
</div>
</template>
<!-- SSH Tunnel (not for SQLite) -->
+6 -1
View File
@@ -223,6 +223,11 @@ const isConnected = computed(() =>
props.node.type === "connection" && !!props.node.connectionId && connectionStore.connectedIds.has(props.node.connectionId)
);
function connectionIconType(connectionId?: string) {
const config = connectionId ? connectionStore.getConfig(connectionId) : undefined;
return config?.driver_profile || config?.db_type || "postgres";
}
const connectionColor = computed(() => {
const connectionId = props.node.connectionId;
return connectionId ? connectionStore.getConfig(connectionId)?.color || "" : "";
@@ -272,7 +277,7 @@ async function showMore() {
<ChevronRight v-else class="w-3.5 h-3.5 shrink-0 text-muted-foreground" />
</template>
<span v-else class="w-3.5 h-3.5 shrink-0" />
<DatabaseIcon v-if="node.type === 'connection'" :db-type="connectionStore.getConfig(node.connectionId || '')?.db_type || 'postgres'" class="w-3.5 h-3.5 shrink-0" />
<DatabaseIcon v-if="node.type === 'connection'" :db-type="connectionIconType(node.connectionId)" class="w-3.5 h-3.5 shrink-0" />
<component v-else :is="getIconInfo(node)?.icon || Database" class="w-3.5 h-3.5 shrink-0" :class="getIconInfo(node)?.colorClass" />
<span v-if="node.type === 'connection' && connectionColor" class="h-3 w-1.5 rounded-full shrink-0" :style="{ backgroundColor: connectionColor }" />
<span class="truncate">{{ isGroupLabel(node) ? t(node.label) : node.label }}</span>
+3
View File
@@ -27,6 +27,9 @@ export default {
password: "Password",
database: "Database",
databasePlaceholder: "Optional",
driverName: "Driver Name",
driverNamePlaceholder: "Vendor or environment name",
urlParams: "URL Params",
test: "Test",
testing: "Testing...",
saveAndConnect: "Save & Connect",
+3
View File
@@ -27,6 +27,9 @@ export default {
password: "密码",
database: "数据库",
databasePlaceholder: "可选",
driverName: "驱动名称",
driverNamePlaceholder: "厂商或环境名称",
urlParams: "URL 参数",
test: "测试",
testing: "测试中...",
saveAndConnect: "保存并连接",
+29 -6
View File
@@ -22,8 +22,28 @@ export const useConnectionStore = defineStore("connection", () => {
return connections.value.find((c) => c.id === connectionId);
}
function normalizeConnection(config: ConnectionConfig): ConnectionConfig {
const labelMap: Record<string, string> = {
mysql: "MySQL",
postgres: "PostgreSQL",
sqlite: "SQLite",
redis: "Redis",
duckdb: "DuckDB",
clickhouse: "ClickHouse",
sqlserver: "SQL Server",
mongodb: "MongoDB",
oracle: "Oracle",
};
return {
...config,
driver_profile: config.driver_profile || config.db_type,
driver_label: config.driver_label || labelMap[config.driver_profile || config.db_type] || config.db_type,
url_params: config.url_params || "",
};
}
function addConnection(config: ConnectionConfig) {
connections.value.push(config);
connections.value.push(normalizeConnection(config));
persistConnections();
}
@@ -37,6 +57,7 @@ export const useConnectionStore = defineStore("connection", () => {
}
function updateConnection(config: ConnectionConfig) {
config = normalizeConnection(config);
const idx = connections.value.findIndex((c) => c.id === config.id);
if (idx >= 0) connections.value[idx] = config;
const node = findNode(treeNodes.value, config.id);
@@ -50,6 +71,7 @@ export const useConnectionStore = defineStore("connection", () => {
}
async function connect(config: ConnectionConfig) {
config = normalizeConnection(config);
const id = await api.connectDb(config);
activeConnectionId.value = id;
connectedIds.value.add(id);
@@ -439,12 +461,13 @@ export const useConnectionStore = defineStore("connection", () => {
for (const config of imported) {
if (!connections.value.find((c) => c.id === config.id)) {
config.id = crypto.randomUUID();
addConnection(config);
const normalized = normalizeConnection(config);
addConnection(normalized);
treeNodes.value.push({
id: config.id,
label: config.name,
id: normalized.id,
label: normalized.name,
type: "connection" as const,
connectionId: config.id,
connectionId: normalized.id,
isExpanded: false,
children: [],
});
@@ -454,7 +477,7 @@ export const useConnectionStore = defineStore("connection", () => {
async function initFromDisk() {
const saved = await api.loadConnections();
connections.value = saved;
connections.value = saved.map(normalizeConnection);
treeNodes.value = saved.map((config) => ({
id: config.id,
label: config.name,
+3
View File
@@ -4,6 +4,9 @@ export interface ConnectionConfig {
id: string;
name: string;
db_type: DatabaseType;
driver_profile?: string;
driver_label?: string;
url_params?: string;
host: string;
port: number;
username: string;