feat(elasticsearch): add custom checks and tabular REST results

This commit is contained in:
weiyong authored and GitHub committed 2026-07-27 18:55:37 +08:00
1 parent e2b35944ce
commit a49137ae8b
44 files changed
+917 -74

No files matched your search

@@ -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") }}
</button>
<button v-if="canShowTable" type="button" class="h-6 rounded-[4px] px-2 text-xs transition-colors bg-background font-medium text-foreground shadow-sm" @click="emit('showTable')">
{{ t("tabs.tableData") }}
</button>
</div>
</div>
<span class="shrink-0 rounded-full border px-2 py-0.5 font-mono text-[11px] font-medium tabular-nums" :class="statusClass" role="status" :aria-label="statusLabel">
@@ -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<ElasticsearchConnectionMode>("direct");
const elasticsearchKibanaBasePath = ref("");
const elasticsearchConnectivityCheckPath = ref("");
const elasticsearchConnectionPorts = ref<Record<ElasticsearchConnectionMode, number>>({
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) {
<Input v-model="elasticsearchKibanaBasePath" class="col-span-3" placeholder="/kibana/s/default" @input="resetTestState" />
</div>
<div v-if="form.db_type === 'elasticsearch'" class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelSmallClass">{{ t("connection.elasticsearchConnectivityCheckPath") }}</Label>
<Input v-model="elasticsearchConnectivityCheckPath" class="col-span-3" :placeholder="t('connection.elasticsearchConnectivityCheckPathPlaceholder')" @input="resetTestState" />
</div>
<div v-if="form.driver_profile === 'gbase8s'" class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelSmallClass">{{ t("connection.gbaseServer") }}</Label>
<Input v-model="form.gbase_server" class="col-span-3" placeholder="gbase01" />
@@ -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
<template v-else>
<ElasticsearchJsonResponsePanel v-if="activeElasticsearchJsonResponse" class="flex-1 min-h-0" :status="activeElasticsearchJsonResponse.status" :body="activeElasticsearchJsonResponse.body" />
<ElasticsearchJsonResponsePanel v-else-if="showElasticsearchRawJson && activeElasticsearchRawBody" class="flex-1 min-h-0" :status="200" :body="activeElasticsearchRawBody" can-show-table @show-table="showElasticsearchRawJson = false" />
<DataGrid
v-else-if="activeTab.result && hasTabularResult"
ref="dataGridRef"
@@ -1350,6 +1364,18 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
>
<template #result-toolbar-leading="{ compact }">
<QueryResultViewSwitcher :active-view="activeOutputView" :can-show-result="canShowResultOutput" :can-show-summary="hasExecutionSummary" :can-show-chart="hasNumericData && !activeElasticsearchJsonResponse" :compact="compact" @select-view="emit('update:activeOutputView', $event)" />
<template v-if="activeElasticsearchRawBody">
<div class="mx-1 h-4 w-px bg-border" />
<button
type="button"
class="inline-flex h-5 shrink-0 items-center rounded-sm border border-transparent px-2 text-xs leading-none transition-colors"
:class="showElasticsearchRawJson ? 'bg-secondary text-secondary-foreground' : 'text-muted-foreground hover:text-foreground'"
:aria-pressed="showElasticsearchRawJson"
@click="showElasticsearchRawJson = !showElasticsearchRawJson"
>
{{ showElasticsearchRawJson ? t("tabs.tableData") : t("redis.jsonView") }}
</button>
</template>
</template>
<template #result-toolbar-actions="{ compact }">
<QueryResultToolbarActions
@@ -554,6 +554,7 @@ async function toggle() {
} else if (config?.db_type === "mongodb") {
await connectionStore.loadMongoDatabases(node.connectionId);
} else if (config?.db_type === "elasticsearch") {
// Expand: list indices (like other db types list databases).
await connectionStore.loadElasticsearchIndices(node.connectionId);
} else if (config?.db_type === "milvus") {
await connectionStore.loadMilvusDatabases(node.connectionId);