From 5e307f56ccee3123064b697e74d38640295111cd Mon Sep 17 00:00:00 2001 From: whoops <35476126+wzlove@users.noreply.github.com> Date: Tue, 25 Aug 2026 22:34:04 +0800 Subject: [PATCH] feat(redis): add configurable key search templates --- .../connection/ConnectionDialog.vue | 25 ++++ .../editor/EditorSettingsDialog.vue | 27 ++++ .../redis/RedisKeyBrowser.expiry.spec.ts | 35 +++-- .../src/components/redis/RedisKeyBrowser.vue | 140 +++++++++++++++++- apps/desktop/src/i18n/locales/en.ts | 8 + apps/desktop/src/i18n/locales/es.ts | 8 + apps/desktop/src/i18n/locales/it.ts | 8 + apps/desktop/src/i18n/locales/ja.ts | 8 + apps/desktop/src/i18n/locales/ko.ts | 8 + apps/desktop/src/i18n/locales/pt-BR.ts | 8 + apps/desktop/src/i18n/locales/zh-CN.ts | 8 + apps/desktop/src/i18n/locales/zh-TW.ts | 8 + .../__tests__/redis/redisKeyTemplates.spec.ts | 35 +++++ .../src/lib/redis/redisKeyTemplates.ts | 35 +++++ .../src/lib/settings/editorSettingsDraft.ts | 3 + .../src/lib/settings/settingsSearch.ts | 1 + apps/desktop/src/stores/connectionStore.ts | 5 + apps/desktop/src/stores/settingsStore.ts | 6 + apps/desktop/src/types/database.ts | 2 + .../examples/table_import_live_bench.rs | 1 + crates/dbx-core/src/agent_connection.rs | 1 + crates/dbx-core/src/agent_tools.rs | 1 + crates/dbx-core/src/cloud_sync.rs | 3 + crates/dbx-core/src/connection.rs | 1 + crates/dbx-core/src/connection_secrets.rs | 1 + crates/dbx-core/src/db/redis_driver.rs | 1 + crates/dbx-core/src/models/connection.rs | 8 + crates/dbx-core/src/mq/config.rs | 1 + crates/dbx-core/src/mq/service.rs | 1 + crates/dbx-core/src/nacos/config.rs | 1 + crates/dbx-core/src/nacos/service.rs | 2 + crates/dbx-core/src/production_safety.rs | 1 + crates/dbx-core/src/query.rs | 2 + crates/dbx-core/src/schema.rs | 1 + crates/dbx-core/src/storage.rs | 2 + crates/dbx-core/src/transfer.rs | 1 + crates/dbx-core/tests/dump_docs_fixture.rs | 1 + .../tests/live_postgres_docs_annotations.rs | 1 + .../tests/live_postgres_docs_snapshot.rs | 1 + .../live_postgres_query_result_export.rs | 1 + .../dbx-core/tests/live_postgres_transfer.rs | 1 + .../tests/live_sqlserver_completion.rs | 1 + .../live_sqlserver_query_result_export.rs | 1 + crates/dbx-core/tests/support/mod.rs | 1 + crates/dbx-web/src/routes/connection.rs | 1 + crates/dbx-web/src/routes/transfer.rs | 1 + src-tauri/src/commands/connection.rs | 1 + 47 files changed, 407 insertions(+), 11 deletions(-) create mode 100644 apps/desktop/src/lib/__tests__/redis/redisKeyTemplates.spec.ts create mode 100644 apps/desktop/src/lib/redis/redisKeyTemplates.ts diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 37489c2514..317bc47906 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -29,6 +29,7 @@ import { applySshConfigHostAliasPrefill as prefillSshConfigHostAlias } from "@/l import { canPersistConnectionTestResult, connectionEditDraftSyncAction } from "./connectionEditDraftSync"; import { createConnectionNoteVisibilityDraft, persistConnectionNoteVisibilityDraft as persistConnectionNoteVisibilityDraftState, resetConnectionNoteVisibilityDraft, setConnectionNoteVisibilityDraft, syncConnectionNoteVisibilityDraft } from "./connectionNoteVisibilityDraft"; import { REDIS_SCAN_PAGE_SIZE_DEFAULT, REDIS_SCAN_PAGE_SIZE_MIN, REDIS_SCAN_PAGE_SIZE_MAX, REDIS_SCAN_PAGE_SIZE_OPTIONS } from "@/lib/redis/redisKeyPattern"; +import { normalizeRedisKeyTemplates, redisKeyTemplatesToTextarea } from "@/lib/redis/redisKeyTemplates"; import { normalizeGlobalConnectTimeoutSecs, normalizeGlobalQueryTimeoutSecs, useSettingsStore } from "@/stores/settingsStore"; import { useToast } from "@/composables/useToast"; import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; @@ -363,6 +364,7 @@ const defaultForm = (): ConnectionForm => ({ redis_cluster_nodes: "", redis_key_separator: ":", redis_scan_page_size: REDIS_SCAN_PAGE_SIZE_DEFAULT, + redis_key_templates: [], etcd_endpoints: "", gbase_server: "", informix_server: "", @@ -556,6 +558,7 @@ function sshLayersForConfig(config: LegacyConnectionConfig): SshTunnelConfig[] { } const form = ref(defaultForm()); +const redisKeyTemplatesText = ref(""); const noteTextareaRef = ref(null); const showGaussdbConnectionMode = computed(() => form.value.db_type === "gaussdb"); const gaussdbDriverMode = computed({ @@ -2484,6 +2487,7 @@ watch( redis_cluster_nodes: config.redis_cluster_nodes || "", redis_key_separator: config.redis_key_separator ?? ":", redis_scan_page_size: config.redis_scan_page_size ?? REDIS_SCAN_PAGE_SIZE_DEFAULT, + redis_key_templates: normalizeRedisKeyTemplates(config.redis_key_templates), etcd_endpoints: config.etcd_endpoints || "", gbase_server: config.gbase_server || "", informix_server: config.informix_server || "", @@ -2499,6 +2503,7 @@ watch( visible_schemas: config.visible_schemas, save_password: config.save_password !== false, }; + redisKeyTemplatesText.value = redisKeyTemplatesToTextarea(form.value.redis_key_templates); oracleTnsAdminPath.value = parseOracleTnsConnectionString(config.connection_string)?.tnsAdmin || ""; productionProtectionEnabled.value = !!config.is_production || (config.production_databases?.length ?? 0) > 0; connectionUrlInput.value = config.db_type === "h2" && config.connection_string ? config.connection_string : ""; @@ -2570,6 +2575,7 @@ watch( clearSavedDatabaseInfo(); editingId.value = null; form.value = defaultForm(); + redisKeyTemplatesText.value = ""; productionProtectionEnabled.value = false; selectedTransportLayerId.value = null; selectedType.value = "mysql"; @@ -3859,6 +3865,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo config.redis_key_separator = undefined; config.redis_scan_page_size = undefined; config.redis_database_aliases = undefined; + config.redis_key_templates = undefined; } else if (config.redis_connection_mode === "sentinel") { config.redis_sentinel_master = config.redis_sentinel_master?.trim() || ""; config.redis_sentinel_nodes = normalizeRedisSentinelNodes(config.redis_sentinel_nodes || ""); @@ -3894,6 +3901,11 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo config.redis_key_separator = config.redis_key_separator?.trim() ?? ":"; const scanSize = Number(config.redis_scan_page_size); config.redis_scan_page_size = Number.isFinite(scanSize) && scanSize >= REDIS_SCAN_PAGE_SIZE_MIN && scanSize <= REDIS_SCAN_PAGE_SIZE_MAX ? Math.round(scanSize) : REDIS_SCAN_PAGE_SIZE_DEFAULT; + { + const templates = normalizeRedisKeyTemplates(redisKeyTemplatesText.value); + config.redis_key_templates = templates.length > 0 ? templates : undefined; + form.value.redis_key_templates = templates; + } } if (config.db_type === "zookeeper") { const normalizedConnectString = normalizeZooKeeperConnectString(config.connection_string || ""); @@ -4760,6 +4772,7 @@ function openJdbcDriverManagerFromError() { function resetForm(options: { preservePickerState?: boolean } = {}) { editingId.value = null; form.value = defaultForm(); + redisKeyTemplatesText.value = ""; resetConnectionNoteVisibilityDraft(connectionNoteVisibilityDraft, settingsStore.editorSettings.sidebarShowConnectionNotes); editGlobalConnectTimeoutSecs.value = settingsStore.editorSettings.globalConnectTimeoutSecs; editGlobalQueryTimeoutSecs.value = settingsStore.editorSettings.globalQueryTimeoutSecs; @@ -6514,6 +6527,18 @@ function openExternalUrl(url: string) { +
+ +
+