mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(connection): allow configuring connection idle timeout
This commit is contained in:
@@ -74,7 +74,7 @@ import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { isWindows } from "@/lib/backend/platform";
|
||||
import { applyMeilisearchBasePathToExternalConfig, applyParsedConnectionUrl, normalizeMongoConnectionString, parseConnectionUrl } from "@/lib/connection/connectionUrl";
|
||||
import { hasXuguConnectionDatabase } from "@/lib/connection/xuguDatabase";
|
||||
import { DEFAULT_QUERY_TIMEOUT_SECS, MAX_CONNECT_TIMEOUT_SECS, MAX_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits";
|
||||
import { DEFAULT_QUERY_TIMEOUT_SECS, MAX_CONNECT_TIMEOUT_SECS, MAX_IDLE_TIMEOUT_SECS, MAX_QUERY_TIMEOUT_SECS, supportsIdleTimeout } from "@/lib/connection/timeoutLimits";
|
||||
import { buildOracleTnsConnectionString, normalizeOracleTnsAdminPath, parseOracleTnsConnectionString } from "@/lib/connection/oracleTnsConnection";
|
||||
import { applyConnectionDeepLinkUpdate, resolveConnectionDeepLinkUpdate } from "@/lib/connection/connectionDeepLinkUpdate";
|
||||
import { connectionDeepLinkServiceHydrationValue, parseConnectionDeepLink, parseConnectionDeepLinkUpdate, parseServiceConnectionUrl, type ConnectionDeepLinkDraft, type ConnectionDeepLinkUpdate } from "@/lib/connection/connectionDeepLink";
|
||||
@@ -3848,6 +3848,7 @@ const showGenericUrlParamsHint = computed(() => form.value.db_type === "mysql" |
|
||||
const bareMysqlProfiles = new Set(["doris", "selectdb", "oceanbase"]);
|
||||
const supportsMysqlTlsOptions = computed(() => mysqlTlsOptionsSupported(form.value.db_type, selectedType.value));
|
||||
const supportsMysqlCleartextPasswordAuth = computed(() => form.value.db_type === "mysql" && !bareMysqlProfiles.has(selectedType.value));
|
||||
const supportsIdleTimeoutSetting = computed(() => supportsIdleTimeout(form.value.db_type));
|
||||
const supportsDoltSystemTables = computed(() => isDoltDriverProfile(form.value.driver_profile));
|
||||
const showDoltSystemTables = computed({
|
||||
get: () => doltSystemTablesVisible(form.value),
|
||||
@@ -6590,6 +6591,15 @@ function clampConnectTimeoutInput(event: Event, target: "global" | "connection")
|
||||
else form.value.connect_timeout_secs = MAX_CONNECT_TIMEOUT_SECS;
|
||||
}
|
||||
|
||||
function clampIdleTimeoutInput(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (input.value === "") return;
|
||||
const value = Number(input.value);
|
||||
if (!Number.isFinite(value) || value <= MAX_IDLE_TIMEOUT_SECS) return;
|
||||
input.value = String(MAX_IDLE_TIMEOUT_SECS);
|
||||
form.value.idle_timeout_secs = MAX_IDLE_TIMEOUT_SECS;
|
||||
}
|
||||
|
||||
async function persistGlobalTimeoutDrafts() {
|
||||
const nextConnect = normalizeGlobalConnectTimeoutSecs(editGlobalConnectTimeoutSecs.value);
|
||||
const nextQuery = normalizeGlobalQueryTimeoutSecs(editGlobalQueryTimeoutSecs.value);
|
||||
@@ -10279,9 +10289,12 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="form.db_type === 'mongodb'" class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelSmallClass">{{ t("connection.idleTimeout") }}</Label>
|
||||
<Input v-model.number="form.idle_timeout_secs" type="number" min="0" max="600" step="1" class="col-span-3" />
|
||||
<div v-show="supportsIdleTimeoutSetting" class="grid grid-cols-4 items-start gap-4">
|
||||
<Label :class="connectionLabelSmallPaddedClass">{{ t("connection.idleTimeout") }}</Label>
|
||||
<div class="col-span-3 space-y-1">
|
||||
<Input v-model.number="form.idle_timeout_secs" type="number" min="0" :max="MAX_IDLE_TIMEOUT_SECS" step="1" @input="clampIdleTimeoutInput($event)" />
|
||||
<p class="text-xs leading-5 text-muted-foreground">{{ t("connection.idleTimeoutHint") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelSmallClass">{{ t("connection.keepaliveInterval") }}</Label>
|
||||
|
||||
@@ -1286,6 +1286,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "Aralıq: 0–3600 saniyə. Vaxt limitini ləğv etmək üçün 0 təyin edin.",
|
||||
useConnectionQueryTimeout: "Əlaqə",
|
||||
idleTimeout: "Boşdayanma vaxt limiti (saniyə)",
|
||||
idleTimeoutHint: "Bağlantı hovuzundakı boşda qalan bağlantıların geri qaytarılmasına tətbiq edilir.",
|
||||
keepaliveInterval: "Əlaqənin canlı saxlanma intervalı (saniyə)",
|
||||
readOnly: "Yalnız oxuma",
|
||||
readOnlyHint: "Bütün yazma əməliyyatlarını blokla (INSERT, UPDATE, DELETE və s.)",
|
||||
|
||||
@@ -1631,6 +1631,7 @@ export default {
|
||||
globalQueryTimeoutHint: "Range: 0 to 3600 seconds. Set to 0 for no timeout.",
|
||||
useConnectionQueryTimeout: "Connection",
|
||||
idleTimeout: "Idle Timeout (seconds)",
|
||||
idleTimeoutHint: "Applies to idle connection reclamation within the connection pool.",
|
||||
keepaliveInterval: "Keepalive Interval (seconds)",
|
||||
readOnly: "Read Only",
|
||||
readOnlyHint: "Block all write operations (INSERT, UPDATE, DELETE, etc.)",
|
||||
|
||||
@@ -1375,6 +1375,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "Rango: de 0 a 3600 segundos. Establece 0 para no aplicar límite.",
|
||||
useConnectionQueryTimeout: "Conexión",
|
||||
idleTimeout: "Tiempo de espera inactivo (segundos)",
|
||||
idleTimeoutHint: "Se aplica a la recuperación de conexiones inactivas dentro del grupo de conexiones.",
|
||||
keepaliveInterval: "Intervalo de keepalive (segundos)",
|
||||
readOnly: "Solo lectura",
|
||||
readOnlyHint: "Bloquear todas las operaciones de escritura (INSERT, UPDATE, DELETE, etc.)",
|
||||
|
||||
@@ -1523,6 +1523,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "Rentang: 0 hingga 3600 detik. Tetapkan ke 0 untuk tanpa timeout.",
|
||||
useConnectionQueryTimeout: "Koneksi",
|
||||
idleTimeout: "Timeout Idle (detik)",
|
||||
idleTimeoutHint: "Berlaku untuk pemulihan koneksi idle dalam connection pool.",
|
||||
keepaliveInterval: "Interval Keepalive (detik)",
|
||||
readOnly: "Read Only",
|
||||
readOnlyHint: "Blokir semua operasi tulis (INSERT, UPDATE, DELETE, dll.)",
|
||||
|
||||
@@ -1297,6 +1297,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "Intervallo: da 0 a 3600 secondi. Imposta 0 per nessun limite.",
|
||||
useConnectionQueryTimeout: "Connessione",
|
||||
idleTimeout: "Timeout Inattività (secondi)",
|
||||
idleTimeoutHint: "Si applica al recupero delle connessioni inattive all'interno del pool di connessioni.",
|
||||
keepaliveInterval: "Intervallo keepalive (secondi)",
|
||||
readOnly: "Sola lettura",
|
||||
readOnlyHint: "Blocca tutte le operazioni di scrittura (INSERT, UPDATE, DELETE, ecc.)",
|
||||
|
||||
@@ -1267,6 +1267,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "0~3600 秒の範囲で指定します。0 はタイムアウトなしです。",
|
||||
useConnectionQueryTimeout: "接続",
|
||||
idleTimeout: "アイドルタイムアウト(秒)",
|
||||
idleTimeoutHint: "コネクションプール内のアイドル接続の回収・解放に適用されます。",
|
||||
keepaliveInterval: "Keepalive 間隔(秒)",
|
||||
readOnly: "読み取り専用",
|
||||
readOnlyHint: "すべての書き込み操作(INSERT, UPDATE, DELETEなど)をブロックします",
|
||||
|
||||
@@ -1452,6 +1452,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "범위: 0~3600초. 0으로 설정하면 제한이 없습니다.",
|
||||
useConnectionQueryTimeout: "연결",
|
||||
idleTimeout: "유휴 제한 시간 (초)",
|
||||
idleTimeoutHint: "연결 풀 내 유휴 연결의 회수 및 해제에 적용됩니다.",
|
||||
keepaliveInterval: "킵얼라이브 간격 (초)",
|
||||
readOnly: "읽기 전용",
|
||||
readOnlyHint: "모든 쓰기 작업 차단 (INSERT, UPDATE, DELETE 등)",
|
||||
|
||||
@@ -1298,6 +1298,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "Intervalo: de 0 a 3600 segundos. Defina 0 para não haver limite.",
|
||||
useConnectionQueryTimeout: "Conexão",
|
||||
idleTimeout: "Timeout de Inatividade (segundos)",
|
||||
idleTimeoutHint: "Aplica-se à recuperação de conexões inativas no pool de conexões.",
|
||||
keepaliveInterval: "Intervalo de keepalive (segundos)",
|
||||
readOnly: "Somente leitura",
|
||||
readOnlyHint: "Bloquear todas as operações de escrita (INSERT, UPDATE, DELETE, etc.)",
|
||||
|
||||
@@ -1542,6 +1542,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "Диапазон: от 0 до 3600 секунд. Установите 0, чтобы отключить тайм-аут.",
|
||||
useConnectionQueryTimeout: "Подключение",
|
||||
idleTimeout: "Тайм-аут простоя (секунды)",
|
||||
idleTimeoutHint: "Применяется для освобождения простаивающих соединений в пуле соединений.",
|
||||
keepaliveInterval: "Интервал keepalive (секунды)",
|
||||
readOnly: "Только чтение",
|
||||
readOnlyHint: "Блокировать все операции записи (INSERT, UPDATE, DELETE и т. д.)",
|
||||
|
||||
@@ -1286,6 +1286,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "Aralık: 0 ile 3600 saniye. Zaman aşımı istemiyorsanız 0 yapın.",
|
||||
useConnectionQueryTimeout: "Bağlantı",
|
||||
idleTimeout: "Boşta Kalma Zaman Aşımı (saniye)",
|
||||
idleTimeoutHint: "Bağlantı havuzundaki boşta kalan bağlantıların geri kazanılması için geçerlidir.",
|
||||
keepaliveInterval: "Keepalive Aralığı (saniye)",
|
||||
readOnly: "Salt Okunur",
|
||||
readOnlyHint: "Tüm yazma işlemlerini engelle (INSERT, UPDATE, DELETE vb.)",
|
||||
|
||||
@@ -1570,6 +1570,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "范围为0~3600秒。设为0表示不限制",
|
||||
useConnectionQueryTimeout: "当前连接",
|
||||
idleTimeout: "空闲超时(秒)",
|
||||
idleTimeoutHint: "作用于连接池内空闲连接的回收释放。",
|
||||
keepaliveInterval: "保持连接间隔(秒)",
|
||||
readOnly: "只读模式",
|
||||
readOnlyHint: "阻止所有写操作(INSERT、UPDATE、DELETE 等)",
|
||||
|
||||
@@ -1309,6 +1309,7 @@ export default withEnglishFallback({
|
||||
globalQueryTimeoutHint: "範圍為0~3600秒。設為0表示不限制",
|
||||
useConnectionQueryTimeout: "目前連線",
|
||||
idleTimeout: "閒置逾時(秒)",
|
||||
idleTimeoutHint: "作用於連線池內閒置連線的回收釋放。",
|
||||
keepaliveInterval: "保持連線間隔(秒)",
|
||||
readOnly: "唯讀模式",
|
||||
readOnlyHint: "阻止所有寫入操作(INSERT、UPDATE、DELETE 等)",
|
||||
|
||||
+35
-1
@@ -1,8 +1,42 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_CONNECT_TIMEOUT_SECS, normalizeConnectTimeoutSecs } from "@/lib/connection/timeoutLimits";
|
||||
import { DEFAULT_CONNECT_TIMEOUT_SECS, DEFAULT_IDLE_TIMEOUT_SECS, MAX_IDLE_TIMEOUT_SECS, normalizeConnectTimeoutSecs, normalizeIdleTimeoutSecs, supportsIdleTimeout } from "@/lib/connection/timeoutLimits";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("ConnectionDialog timeout controls", () => {
|
||||
it("restores the default when the connection timeout input is cleared", () => {
|
||||
expect(normalizeConnectTimeoutSecs("")).toBe(DEFAULT_CONNECT_TIMEOUT_SECS);
|
||||
});
|
||||
|
||||
it("normalizes and bounds idle timeout values", () => {
|
||||
expect(normalizeIdleTimeoutSecs("")).toBe(DEFAULT_IDLE_TIMEOUT_SECS);
|
||||
expect(normalizeIdleTimeoutSecs(Number.NaN)).toBe(DEFAULT_IDLE_TIMEOUT_SECS);
|
||||
expect(normalizeIdleTimeoutSecs(-1)).toBe(DEFAULT_IDLE_TIMEOUT_SECS);
|
||||
expect(normalizeIdleTimeoutSecs(0)).toBe(0);
|
||||
expect(normalizeIdleTimeoutSecs(60)).toBe(60);
|
||||
expect(normalizeIdleTimeoutSecs(120.4)).toBe(120);
|
||||
expect(normalizeIdleTimeoutSecs(5000)).toBe(MAX_IDLE_TIMEOUT_SECS);
|
||||
});
|
||||
|
||||
it("identifies database dialects that support idle connection timeouts", () => {
|
||||
expect(supportsIdleTimeout("mongodb")).toBe(true);
|
||||
expect(supportsIdleTimeout("mysql")).toBe(true);
|
||||
expect(supportsIdleTimeout("doris")).toBe(true);
|
||||
expect(supportsIdleTimeout("starrocks")).toBe(true);
|
||||
expect(supportsIdleTimeout("manticoresearch")).toBe(true);
|
||||
|
||||
expect(supportsIdleTimeout("postgres")).toBe(false);
|
||||
expect(supportsIdleTimeout("sqlite")).toBe(false);
|
||||
expect(supportsIdleTimeout("redis")).toBe(false);
|
||||
expect(supportsIdleTimeout("sqlserver")).toBe(false);
|
||||
expect(supportsIdleTimeout("oracle")).toBe(false);
|
||||
expect(supportsIdleTimeout(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("binds idle timeout setting and hint in ConnectionDialog", () => {
|
||||
expect(dialogSource).toContain('v-show="supportsIdleTimeoutSetting"');
|
||||
expect(dialogSource).toContain("clampIdleTimeoutInput($event)");
|
||||
expect(dialogSource).toContain('t("connection.idleTimeoutHint")');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeIdleTimeoutSecs } from "@/lib/connection/timeoutLimits";
|
||||
import { isSingleDatabase } from "@/lib/database/databaseFeatureSupport";
|
||||
import { normalizeGlobalConnectTimeoutSecs, normalizeGlobalQueryTimeoutSecs } from "@/stores/settingsStore";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
@@ -6,8 +7,7 @@ export function normalizeConnectionTimeouts(config: ConnectionConfig, connectTim
|
||||
config.connect_timeout_secs = config.connect_timeout_inherit === true ? normalizeGlobalConnectTimeoutSecs(connectTimeoutSecs) : normalizeGlobalConnectTimeoutSecs(config.connect_timeout_secs);
|
||||
const queryTimeout = Number(config.query_timeout_secs);
|
||||
config.query_timeout_secs = config.query_timeout_inherit === true ? normalizeGlobalQueryTimeoutSecs(queryTimeoutSecs) : normalizeGlobalQueryTimeoutSecs(queryTimeout);
|
||||
const idleTimeout = Number(config.idle_timeout_secs);
|
||||
config.idle_timeout_secs = Number.isFinite(idleTimeout) && idleTimeout >= 0 ? idleTimeout : 60;
|
||||
config.idle_timeout_secs = normalizeIdleTimeoutSecs(config.idle_timeout_secs);
|
||||
const keepaliveInterval = Number(config.keepalive_interval_secs);
|
||||
config.keepalive_interval_secs = Number.isFinite(keepaliveInterval) && keepaliveInterval >= 0 ? keepaliveInterval : 30;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
export const DEFAULT_CONNECT_TIMEOUT_SECS = 10;
|
||||
export const MAX_CONNECT_TIMEOUT_SECS = 300;
|
||||
export const DEFAULT_QUERY_TIMEOUT_SECS = 60;
|
||||
export const MAX_QUERY_TIMEOUT_SECS = 3600;
|
||||
export const DEFAULT_IDLE_TIMEOUT_SECS = 60;
|
||||
export const MAX_IDLE_TIMEOUT_SECS = 3600;
|
||||
|
||||
export function normalizeConnectTimeoutSecs(value: unknown): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_CONNECT_TIMEOUT_SECS;
|
||||
@@ -12,3 +16,12 @@ export function normalizeQueryTimeoutSecs(value: unknown): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_QUERY_TIMEOUT_SECS;
|
||||
return Math.min(MAX_QUERY_TIMEOUT_SECS, Math.max(0, Math.round(value)));
|
||||
}
|
||||
|
||||
export function normalizeIdleTimeoutSecs(value: unknown): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return DEFAULT_IDLE_TIMEOUT_SECS;
|
||||
return Math.min(MAX_IDLE_TIMEOUT_SECS, Math.round(value));
|
||||
}
|
||||
|
||||
export function supportsIdleTimeout(dbType?: DatabaseType): boolean {
|
||||
return dbType === "mongodb" || dbType === "mysql" || dbType === "doris" || dbType === "starrocks" || dbType === "manticoresearch";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user