diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 11e6db8ad..e3a1fa5ee 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -1691,7 +1691,7 @@ async function handleQuickOpenSelect(item: any) { } else if (config?.db_type === "mongodb") { await connectionStore.loadMongoDatabases(item.connectionId); } else if (config?.db_type === "elasticsearch") { - await connectionStore.loadElasticsearchIndices(item.connectionId); + await connectionStore.openElasticsearchConnectionTree(item.connectionId); } else if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate" || config?.db_type === "chromadb") { await connectionStore.loadVectorCollections(item.connectionId); } else if (config?.db_type === "mq") { @@ -1716,7 +1716,7 @@ async function handleQuickOpenSelect(item: any) { } else if (config?.db_type === "mongodb") { await connectionStore.loadMongoDatabases(item.connectionId); } else if (config?.db_type === "elasticsearch") { - await connectionStore.loadElasticsearchIndices(item.connectionId); + await connectionStore.openElasticsearchConnectionTree(item.connectionId); } else if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate" || config?.db_type === "chromadb") { await connectionStore.loadVectorCollections(item.connectionId); } else if (config?.db_type === "mq") { diff --git a/apps/desktop/src/components/common/ElasticsearchJsonResponsePanel.vue b/apps/desktop/src/components/common/ElasticsearchJsonResponsePanel.vue index 4e54f73af..457866561 100644 --- a/apps/desktop/src/components/common/ElasticsearchJsonResponsePanel.vue +++ b/apps/desktop/src/components/common/ElasticsearchJsonResponsePanel.vue @@ -13,6 +13,12 @@ import JsonTree from "./JsonTree.vue"; const props = defineProps<{ status: number; body: string; + /** When true, render a "Table" button to switch back to the grid view. */ + canShowTable?: boolean; +}>(); + +const emit = defineEmits<{ + showTable: []; }>(); const { t } = useI18n(); @@ -101,6 +107,9 @@ onMounted(() => { > {{ t("redis.jsonView") }} + diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 23231b3ca..2379dd4ad 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -96,7 +96,7 @@ import { oceanbaseModeConnectionPatch, oceanbaseSubModeFromConfig } from "@/lib/ import { translateBackendError } from "@/i18n/backend-errors"; import { applyHiveKerberosSubmitConfig, hiveKerberosFormConfig, type HiveKerberosAuthMode } from "@/lib/database/hiveKerberosOptions"; import { hasCloudflareD1Credentials, isCloudflareD1Connection, normalizeCloudflareD1Connection } from "@/lib/connection/cloudflareD1"; -import { buildElasticsearchExternalConfig, elasticsearchConnectionModeFromConfig, elasticsearchKibanaBasePathFromConfig, type ElasticsearchConnectionMode } from "@/lib/connection/elasticsearchKibanaProxy"; +import { buildElasticsearchExternalConfig, elasticsearchConnectionModeFromConfig, elasticsearchConnectivityCheckPathFromConfig, elasticsearchKibanaBasePathFromConfig, type ElasticsearchConnectionMode } from "@/lib/connection/elasticsearchKibanaProxy"; type DbOption = { value: string; label: string }; type DbCategoryKey = "sql" | "analytics" | "domestic" | "lightweight" | "document" | "graph_ai" | "timeseries" | "mq" | "registry_config"; @@ -261,6 +261,7 @@ const defaultForm = (): ConnectionForm => ({ const elasticsearchConnectionMode = ref("direct"); const elasticsearchKibanaBasePath = ref(""); +const elasticsearchConnectivityCheckPath = ref(""); const elasticsearchConnectionPorts = ref>({ direct: 9200, kibana: 5601, @@ -270,6 +271,7 @@ function resetElasticsearchProxyFields(externalConfig?: unknown) { const mode = elasticsearchConnectionModeFromConfig(externalConfig); elasticsearchConnectionMode.value = mode; elasticsearchKibanaBasePath.value = elasticsearchKibanaBasePathFromConfig(externalConfig); + elasticsearchConnectivityCheckPath.value = elasticsearchConnectivityCheckPathFromConfig(externalConfig); elasticsearchConnectionPorts.value = { direct: mode === "direct" ? form.value.port : 9200, kibana: mode === "kibana" ? form.value.port : 5601, @@ -3191,7 +3193,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo config.database = config.database?.trim() || undefined; } } else if (config.db_type === "elasticsearch") { - config.external_config = buildElasticsearchExternalConfig(elasticsearchConnectionMode.value, elasticsearchKibanaBasePath.value); + config.external_config = buildElasticsearchExternalConfig(elasticsearchConnectionMode.value, elasticsearchKibanaBasePath.value, elasticsearchConnectivityCheckPath.value); } else if (config.db_type === "sqlserver") { config.external_config = sqlServerPortExplicitFromConfig(config) ? { portExplicit: true } : undefined; } else { @@ -5874,6 +5876,11 @@ function openExternalUrl(url: string) { +
+ + +
+
diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 409d30741..5d624e394 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -368,6 +368,19 @@ const activeStatementExecutionMarkers = computed(() => ), ); const activeElasticsearchJsonResponse = computed(() => elasticsearchJsonResponseForResult(activeEffectiveDatabaseType.value, activeResultSql.value, props.activeTab.result)); +/** Whether the active result is an Elasticsearch _source table that also has a raw JSON toggle. */ +const activeElasticsearchRawBody = computed(() => { + if (activeEffectiveDatabaseType.value !== "elasticsearch") return undefined; + return props.activeTab.result?.elasticsearch_raw_body; +}); +/** Toggle between the _source table and the raw JSON panel for Elasticsearch REST results. */ +const showElasticsearchRawJson = ref(false); +watch( + () => props.activeTab.result?.elasticsearch_raw_body, + () => { + showElasticsearchRawJson.value = false; + }, +); const resultArchiveExporting = ref(false); const canExportResultArchive = computed(() => props.activeTab.mode === "query" && (!!props.activeTab.result || !!props.activeTab.results?.length || !!props.activeTab.resultRuns?.length)); const resultAutoSave = computed(() => props.activeTab.resultAutoSave === true); @@ -1301,6 +1314,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand