fix(connection): move query timeout into per-connection settings

This commit is contained in:
t8y2 committed 2026-05-28 18:18:45 +08:00
1 parent 9f72c38253
commit 486d9bb3a6
20 files changed
+97 -60

No files matched your search

@@ -40,7 +40,7 @@ type DbOption = { value: string; label: string };
type DbCategory = { key: string; title: string; options: DbOption[] };
type DialogStep = "select" | "config";
type DbPickerView = "icon" | "list";
type ConfigTab = "connection" | "tls" | "ssh" | "proxy";
type ConfigTab = "connection" | "advanced" | "tls" | "ssh" | "proxy";
const { t } = useI18n();
const { toast } = useToast();
@@ -88,6 +88,7 @@ const defaultForm = (): Omit<ConnectionConfig, "id"> => ({
ssh_expose_lan: false,
ssh_connect_timeout_secs: 5,
connect_timeout_secs: 5,
query_timeout_secs: 30,
proxy_enabled: false,
proxy_type: "socks5",
proxy_host: "",
@@ -347,6 +348,7 @@ watch(
ssh_expose_lan: config.ssh_expose_lan || false,
ssh_connect_timeout_secs: config.ssh_connect_timeout_secs || 5,
connect_timeout_secs: config.connect_timeout_secs || 5,
query_timeout_secs: config.query_timeout_secs ?? 30,
proxy_enabled: config.proxy_enabled || false,
proxy_type: config.proxy_type || "socks5",
proxy_host: config.proxy_host || "",
@@ -741,6 +743,8 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
config.ssh_connect_timeout_secs = Number.isFinite(sshTimeout) && sshTimeout > 0 ? sshTimeout : 5;
const connectTimeout = Number(config.connect_timeout_secs);
config.connect_timeout_secs = Number.isFinite(connectTimeout) && connectTimeout > 0 ? connectTimeout : 5;
const queryTimeout = Number(config.query_timeout_secs);
config.query_timeout_secs = Number.isFinite(queryTimeout) && queryTimeout >= 0 ? queryTimeout : 30;
const proxyPort = Number(config.proxy_port);
config.proxy_port = Number.isFinite(proxyPort) && proxyPort > 0 ? proxyPort : 1080;
if (!config.one_time) config.one_time = undefined;
@@ -1440,15 +1444,13 @@ function openExternalUrl(url: string) {
<template v-else>
<div class="space-y-3">
<Tabs v-model="configTab" class="min-h-0">
<div
v-if="supportsTlsToggle || canUseSsh || canUseProxy"
class="flex items-center justify-between border-b pb-2"
>
<div class="flex items-center justify-between border-b pb-2">
<TabsList>
<TabsTrigger value="connection">{{ t("connection.basicTab") }}</TabsTrigger>
<TabsTrigger v-if="supportsTlsToggle" value="tls">{{ t("connection.tlsTab") }}</TabsTrigger>
<TabsTrigger v-if="canUseSsh" value="ssh">{{ t("connection.sshTunnel") }}</TabsTrigger>
<TabsTrigger v-if="canUseProxy" value="proxy">{{ t("connection.proxy") }}</TabsTrigger>
<TabsTrigger value="advanced">{{ t("connection.advancedTab") }}</TabsTrigger>
</TabsList>
</div>
@@ -2223,6 +2225,33 @@ function openExternalUrl(url: string) {
</div>
</TabsContent>
<TabsContent value="advanced" class="m-0">
<div class="grid gap-4 py-4 pr-2 max-h-[65vh] overflow-y-auto">
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">{{ t("connection.connectTimeout") }}</Label>
<Input
v-model.number="form.connect_timeout_secs"
type="number"
min="1"
max="300"
step="1"
class="col-span-3"
/>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">{{ t("connection.queryTimeout") }}</Label>
<Input
v-model.number="form.query_timeout_secs"
type="number"
min="0"
max="300"
step="1"
class="col-span-3"
/>
</div>
</div>
</TabsContent>
<TabsContent v-if="canUseSsh" value="ssh" class="m-0">
<div class="grid gap-4 py-4 pr-2 max-h-[65vh] overflow-y-auto">
<div class="grid grid-cols-4 items-center gap-4">
@@ -2318,17 +2347,6 @@ function openExternalUrl(url: string) {
:disabled="!form.ssh_enabled"
/>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">{{ t("connection.connectTimeout") }}</Label>
<Input
v-model.number="form.connect_timeout_secs"
type="number"
min="1"
max="300"
step="1"
class="col-span-3"
/>
</div>
</div>
</TabsContent>
@@ -96,7 +96,6 @@ const editShowTrayIcon = ref(settingsStore.desktopSettings.show_tray_icon);
const editShowColumnCommentsInHeader = ref(settingsStore.editorSettings.showColumnCommentsInHeader);
const editCompactColumnHeaderActions = ref(settingsStore.editorSettings.compactColumnHeaderActions);
const editRedisScanPageSize = ref(settingsStore.editorSettings.redisScanPageSize);
const editQueryTimeoutSecs = ref(settingsStore.editorSettings.queryTimeoutSecs);
const editShortcuts = ref(normalizeShortcutSettings(settingsStore.editorSettings.shortcuts));
const editSidebarActivation = ref(settingsStore.editorSettings.sidebarActivation);
const editAutoSelectActiveSidebarNode = ref(settingsStore.editorSettings.autoSelectActiveSidebarNode);
@@ -230,7 +229,6 @@ watch(
editShowColumnCommentsInHeader.value = settingsStore.editorSettings.showColumnCommentsInHeader;
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
editRedisScanPageSize.value = settingsStore.editorSettings.redisScanPageSize;
editQueryTimeoutSecs.value = settingsStore.editorSettings.queryTimeoutSecs;
editShortcuts.value = normalizeShortcutSettings(settingsStore.editorSettings.shortcuts);
editSidebarActivation.value = settingsStore.editorSettings.sidebarActivation;
editAutoSelectActiveSidebarNode.value = settingsStore.editorSettings.autoSelectActiveSidebarNode;
@@ -268,7 +266,6 @@ function hasChanges(): boolean {
editShowColumnCommentsInHeader.value !== settingsStore.editorSettings.showColumnCommentsInHeader ||
editCompactColumnHeaderActions.value !== settingsStore.editorSettings.compactColumnHeaderActions ||
editRedisScanPageSize.value !== settingsStore.editorSettings.redisScanPageSize ||
editQueryTimeoutSecs.value !== settingsStore.editorSettings.queryTimeoutSecs ||
JSON.stringify(editShortcuts.value) !== JSON.stringify(settingsStore.editorSettings.shortcuts) ||
editSidebarActivation.value !== settingsStore.editorSettings.sidebarActivation ||
editAutoSelectActiveSidebarNode.value !== settingsStore.editorSettings.autoSelectActiveSidebarNode ||
@@ -292,7 +289,6 @@ async function applySettings() {
showColumnCommentsInHeader: editShowColumnCommentsInHeader.value,
compactColumnHeaderActions: editCompactColumnHeaderActions.value,
redisScanPageSize: editRedisScanPageSize.value,
queryTimeoutSecs: editQueryTimeoutSecs.value,
shortcuts: editShortcuts.value,
sidebarActivation: editSidebarActivation.value,
autoSelectActiveSidebarNode: editAutoSelectActiveSidebarNode.value,
@@ -318,7 +314,6 @@ function resetDefaults() {
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
editRedisScanPageSize.value = DEFAULT_EDITOR_SETTINGS.redisScanPageSize;
editQueryTimeoutSecs.value = DEFAULT_EDITOR_SETTINGS.queryTimeoutSecs;
editShortcuts.value = normalizeShortcutSettings(DEFAULT_EDITOR_SETTINGS.shortcuts);
editSidebarActivation.value = DEFAULT_EDITOR_SETTINGS.sidebarActivation;
editAutoSelectActiveSidebarNode.value = DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode;
@@ -1075,26 +1070,6 @@ watch(
<Separator />
<div class="space-y-2">
<Label for="query-timeout-secs">{{ t("settings.queryTimeoutSecs") }}</Label>
<Input
id="query-timeout-secs"
type="number"
min="0"
:model-value="String(editQueryTimeoutSecs)"
@update:model-value="
(v: any) => {
const n = Number(v);
if (Number.isFinite(n) && n >= 0) editQueryTimeoutSecs = n;
}
"
class="h-9 w-28"
/>
<p class="text-xs text-muted-foreground">{{ t("settings.queryTimeoutSecsDescription") }}</p>
</div>
<Separator />
<!-- Live Preview -->
<div class="space-y-2">
<Label>{{ t("settings.preview") }}</Label>
+2 -2
View File
@@ -207,6 +207,7 @@ export default {
groupDeleted: "Group deleted",
loadFailed: "Failed to load saved connections: {message}",
sshTunnel: "SSH Tunnel",
advancedTab: "Advanced",
sshEnable: "Connect via SSH tunnel",
sshHost: "SSH Host",
sshUser: "SSH User",
@@ -219,6 +220,7 @@ export default {
sshExposeLan: "Expose tunnel to LAN",
sshConnectTimeout: "SSH Timeout (seconds)",
connectTimeout: "Connection Timeout (seconds)",
queryTimeout: "Query Timeout (seconds)",
proxy: "Proxy",
proxyEnable: "Connect database through proxy",
proxyType: "Proxy Type",
@@ -1497,8 +1499,6 @@ export default {
shortcutPressShortcut: "Press shortcut",
shortcutConflict: "This shortcut conflicts with another action in the same scope.",
preview: "Live Preview",
queryTimeoutSecs: "Query Timeout (seconds)",
queryTimeoutSecsDescription: "Maximum execution time per query. 0 = no timeout. Default 30s.",
jdbcPlugin: "DBX JDBC plugin",
jdbcPluginInstall: "Install JDBC plugin",
jdbcPluginInstallSuccess: "JDBC plugin installed",
+2
View File
@@ -199,6 +199,7 @@ export default {
groupDeleted: "Grupo eliminado",
loadFailed: "Error al cargar las conexiones guardadas: {message}",
sshTunnel: "Túnel SSH",
advancedTab: "Avanzado",
sshEnable: "Conectar mediante túnel SSH",
sshHost: "Host SSH",
sshUser: "Usuario SSH",
@@ -211,6 +212,7 @@ export default {
sshExposeLan: "Exponer túnel a la red local",
sshConnectTimeout: "Tiempo de espera SSH (segundos)",
connectTimeout: "Tiempo de espera de conexión (segundos)",
queryTimeout: "Tiempo de espera de consulta (segundos)",
proxy: "Proxy",
proxyEnable: "Conectar la base de datos mediante proxy",
proxyType: "Tipo de proxy",
+2 -2
View File
@@ -203,6 +203,7 @@ export default {
groupDeleted: "分组已删除",
loadFailed: "加载已保存连接失败:{message}",
sshTunnel: "SSH 隧道",
advancedTab: "高级",
sshEnable: "通过 SSH 隧道连接",
sshHost: "SSH 主机",
sshUser: "SSH 用户",
@@ -215,6 +216,7 @@ export default {
sshExposeLan: "允许局域网访问隧道",
sshConnectTimeout: "SSH 超时时间(秒)",
connectTimeout: "连接超时(秒)",
queryTimeout: "查询超时(秒)",
proxy: "代理",
proxyEnable: "通过代理连接数据库",
proxyType: "代理类型",
@@ -1463,8 +1465,6 @@ export default {
shortcutPressShortcut: "按下快捷键",
shortcutConflict: "这个快捷键与同一作用域内的其他操作冲突。",
preview: "实时预览",
queryTimeoutSecs: "查询超时(秒)",
queryTimeoutSecsDescription: "每条查询的最大执行时间。0 = 不限制。默认 30 秒。",
jdbcPlugin: "DBX JDBC 插件",
jdbcPluginInstall: "安装 JDBC 插件",
jdbcPluginInstallSuccess: "JDBC 插件已安装",
+1
View File
@@ -231,6 +231,7 @@ function buildConnection(
ssh_expose_lan: false,
ssh_connect_timeout_secs: 5,
connect_timeout_secs: 5,
query_timeout_secs: 30,
ssl: false,
oracle_connection_type: profile.dbType === "oracle" ? parsedUrl.oracleConnectionType || "service_name" : undefined,
connection_string: profile.dbType === "jdbc" || profile.dbType === "mongodb" ? url || undefined : undefined,
+1
View File
@@ -165,6 +165,7 @@ async function parseConnection(node: ParsedNode): Promise<ConnectionConfig | nul
ssh_expose_lan: false,
ssh_connect_timeout_secs: 5,
connect_timeout_secs: 5,
query_timeout_secs: 30,
ssl: false,
oracle_connection_type: oracleConnectionType,
connection_string: undefined,
@@ -243,6 +243,7 @@ export const useConnectionStore = defineStore("connection", () => {
: [],
ssh_connect_timeout_secs: config.ssh_connect_timeout_secs || 5,
connect_timeout_secs: config.connect_timeout_secs || 5,
query_timeout_secs: config.query_timeout_secs ?? 30,
proxy_type: config.proxy_type || "socks5",
proxy_port: config.proxy_port || 1080,
};
+15 -2
View File
@@ -560,6 +560,12 @@ export const useQueryStore = defineStore("query", () => {
const connStore = useConnectionStore();
const conn = connStore.getConfig(tab.connectionId);
const useAgentCursor = !!conn?.db_type && AGENT_DRIVER_TYPES.has(conn.db_type);
const queryTimeoutSecs =
typeof conn?.query_timeout_secs === "number" &&
Number.isFinite(conn.query_timeout_secs) &&
conn.query_timeout_secs >= 0
? conn.query_timeout_secs
: 30;
const settingsStore = useSettingsStore();
await closeResultSession(tab, options?.pagination?.sessionId);
if (tab.mode === "query") {
@@ -656,7 +662,7 @@ export const useQueryStore = defineStore("query", () => {
: { maxRows: pageLimit, fetchSize: pageLimit }
: {}),
clientSessionId: tab.id,
timeoutSecs: settingsStore.editorSettings.queryTimeoutSecs,
timeoutSecs: queryTimeoutSecs,
};
const results = await api.executeMulti(
tab.connectionId,
@@ -741,6 +747,13 @@ export const useQueryStore = defineStore("query", () => {
async function explainTabSql(id: string, sql: string, databaseType?: DatabaseType) {
const tab = tabs.value.find((t) => t.id === id);
if (!tab) return { ok: false as const, reason: "empty" as const };
const conn = useConnectionStore().getConfig(tab.connectionId);
const queryTimeoutSecs =
typeof conn?.query_timeout_secs === "number" &&
Number.isFinite(conn.query_timeout_secs) &&
conn.query_timeout_secs >= 0
? conn.query_timeout_secs
: 30;
const built = await buildExplainSql(databaseType, sql);
if (!built.ok) {
@@ -758,7 +771,7 @@ export const useQueryStore = defineStore("query", () => {
try {
const result = await api.executeQuery(tab.connectionId, tab.database, built.sql, tab.schema, executionId, {
clientSessionId: tab.id,
timeoutSecs: useSettingsStore().editorSettings.queryTimeoutSecs,
timeoutSecs: queryTimeoutSecs,
});
const current = tabs.value.find((t) => t.id === id);
if (current?.explainExecutionId === executionId) {
-11
View File
@@ -191,8 +191,6 @@ export interface EditorSettings {
columnFormatters: Record<string, ColumnFormatterConfig>;
customColumnFormatters: Record<string, CustomColumnFormatterConfig>;
snippets: SqlSnippet[];
/** Query timeout in seconds. 0 = no timeout. Default 30s. */
queryTimeoutSecs: number;
}
export const EDITOR_THEMES: { value: EditorTheme; label: string; dark: boolean }[] = [
@@ -242,7 +240,6 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
columnFormatters: {},
customColumnFormatters: {},
snippets: DEFAULT_SQL_SNIPPETS,
queryTimeoutSecs: 30,
};
export const STORAGE_KEY = "dbx-editor-settings";
@@ -301,11 +298,6 @@ function normalizeSqlSnippets(value: unknown, existing?: SqlSnippet[]): SqlSnipp
return valid;
}
function normalizeQueryTimeoutSecs(value: unknown): number {
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
return DEFAULT_EDITOR_SETTINGS.queryTimeoutSecs;
}
export function normalizeEditorSettings(settings: Partial<EditorSettings>, existing?: EditorSettings): EditorSettings {
return {
fontFamily: settings.fontFamily ?? DEFAULT_EDITOR_SETTINGS.fontFamily,
@@ -335,7 +327,6 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
columnFormatters: normalizeColumnFormatters(settings.columnFormatters),
customColumnFormatters: normalizeCustomColumnFormatters(settings.customColumnFormatters),
snippets: normalizeSqlSnippets(settings.snippets, existing?.snippets),
queryTimeoutSecs: normalizeQueryTimeoutSecs(settings.queryTimeoutSecs),
};
}
@@ -464,8 +455,6 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.customColumnFormatters !== undefined)
editorSettings.value.customColumnFormatters = partial.customColumnFormatters;
if (partial.snippets !== undefined) editorSettings.value.snippets = normalizeSqlSnippets(partial.snippets);
if (partial.queryTimeoutSecs !== undefined)
editorSettings.value.queryTimeoutSecs = normalizeQueryTimeoutSecs(partial.queryTimeoutSecs);
saveEditorSettings(editorSettings.value);
}
+1
View File
@@ -75,6 +75,7 @@ export interface ConnectionConfig {
ssh_expose_lan?: boolean;
ssh_connect_timeout_secs?: number;
connect_timeout_secs?: number;
query_timeout_secs?: number;
proxy_enabled?: boolean;
proxy_type?: "socks5" | "http";
proxy_host?: string;