feat(solr): add Apache Solr connection support

This commit is contained in:
Terry Yao authored and GitHub committed 2026-09-21 18:07:44 +08:00
1 parent 7b407ccbfc
commit 3687a9cbfb
75 files changed
+5596 -82

No files matched your search

@@ -3241,6 +3241,7 @@ const tlsCapableDatabaseTypes = new Set<DatabaseType>([
"elasticsearch",
"easysearch",
"meilisearch",
"solr",
"hbase",
"qdrant",
"milvus",
@@ -4569,12 +4570,12 @@ function connectionConfigForSubmit(id: string, generatedName = "", validatePlugi
if ((config.client_cert_path && !config.client_key_path) || (!config.client_cert_path && config.client_key_path)) {
throw new Error(t("connection.etcdClientCertPairRequired"));
}
} else if (form.value.db_type !== "consul" && config.db_type !== "elasticsearch" && config.db_type !== "easysearch") {
} else if (form.value.db_type !== "consul" && config.db_type !== "elasticsearch" && config.db_type !== "easysearch" && config.db_type !== "solr") {
config.etcd_endpoints = undefined;
config.client_cert_path = undefined;
config.client_key_path = undefined;
}
if (config.db_type === "elasticsearch" || config.db_type === "easysearch") {
if (config.db_type === "elasticsearch" || config.db_type === "easysearch" || config.db_type === "solr") {
config.client_cert_path = config.client_cert_path?.trim() || "";
config.client_key_path = config.client_key_path?.trim() || "";
if ((config.client_cert_path && !config.client_key_path) || (!config.client_cert_path && config.client_key_path)) {
@@ -4591,7 +4592,8 @@ function connectionConfigForSubmit(id: string, generatedName = "", validatePlugi
config.db_type !== "victoriametrics" &&
config.db_type !== "zookeeper" &&
config.db_type !== "elasticsearch" &&
config.db_type !== "easysearch"
config.db_type !== "easysearch" &&
config.db_type !== "solr"
) {
config.ca_cert_path = undefined;
} else {
@@ -8115,7 +8117,7 @@ function openExternalUrl(url: string) {
</div>
</div>
<div v-if="form.db_type !== 'hbase' && form.db_type !== 'meilisearch' && form.db_type !== 'spanner'" class="grid grid-cols-4 items-center gap-4">
<div v-if="form.db_type !== 'hbase' && form.db_type !== 'meilisearch' && form.db_type !== 'solr' && form.db_type !== 'spanner'" class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelClass">{{ databaseLabel }}</Label>
<Input v-model="form.database" class="col-span-3" :placeholder="databasePlaceholder" />
</div>
@@ -114,7 +114,7 @@ let initializingPrefillGeneration = 0;
let componentUnmounted = false;
let shownSessionError = "";
const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "easysearch", "meilisearch", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper", "consul", "mq", "nacos"].includes(connection.db_type)));
const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "easysearch", "meilisearch", "solr", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper", "consul", "mq", "nacos"].includes(connection.db_type)));
const selectedSourceTableNames = computed(() => sourceTables.value.filter((table) => selectedSourceTables.value.has(table)));
const isBatchCompare = computed(() => selectedSourceTableNames.value.length > 1);
// Bridge the shared TableMultiSelect `string[]` v-model with the Set-based selection store.
@@ -84,7 +84,7 @@ const targetSchemas = ref<string[]>([]);
const sourceDbVersion = ref<string | null>(null);
const targetDbVersion = ref<string | null>(null);
const sqlConnections = computed(() => store.connections.filter((c: any) => !["mongodb", "redis", "elasticsearch", "easysearch", "meilisearch", "etcd", "zookeeper", "consul", "mq", "nacos"].includes(c.db_type)));
const sqlConnections = computed(() => store.connections.filter((c: any) => !["mongodb", "redis", "elasticsearch", "easysearch", "meilisearch", "solr", "etcd", "zookeeper", "consul", "mq", "nacos"].includes(c.db_type)));
const sourceConfig = computed(() => store.getConfig(props.sourceConnectionId));
const targetConfig = computed(() => store.getConfig(props.targetConnectionId));
@@ -71,7 +71,7 @@ import {
stringifyDocumentStoreValue,
documentStoreValueForGrid,
} from "@/lib/app/documentJsonValues";
import { applyDocumentStoreIdentityPlan, formatMeilisearchDocumentOperationPreview, insertDocumentStoreDocument as insertDocumentStoreDocumentCore } from "@/lib/app/documentStoreSave";
import { applyDocumentStoreIdentityPlan, formatMeilisearchDocumentOperationPreview, formatSolrDocumentOperationPreview, insertDocumentStoreDocument as insertDocumentStoreDocumentCore } from "@/lib/app/documentStoreSave";
import { restoreDocumentBrowserState, saveDocumentBrowserState, type DocumentBrowserDataSnapshot } from "@/lib/tabs/documentBrowserStateCache";
import RedisJsonEditor from "@/components/redis/RedisJsonEditor.vue";
import { isLosslessJsonNumber, parseJsonPreservingLargeNumbers } from "@/lib/common/safeJsonFormat";
@@ -145,7 +145,7 @@ const DYNAMODB_DEFAULT_EXPORT_ROW_LIMIT = 10_000;
// stores (DynamoDB/Elasticsearch) cannot resume a page without their cursor
// stacks, and infinite scroll always restarts from the first segment.
const restoredDocumentBrowserState = props.stateKey ? restoreDocumentBrowserState(props.stateKey) : undefined;
const skipBasedDocumentStore = documentStoreProviderFor(props.databaseType).kind === "mongodb" || documentStoreProviderFor(props.databaseType).kind === "meilisearch";
const skipBasedDocumentStore = ["mongodb", "meilisearch", "solr"].includes(documentStoreProviderFor(props.databaseType).kind);
const restoresSkipBasedPage = !!restoredDocumentBrowserState && !settingsStore.editorSettings.infiniteScroll && skipBasedDocumentStore;
const documents = ref<JsonRecord[]>([]);
@@ -466,6 +466,12 @@ function elasticsearchGridColumnTypesFor(columns: readonly string[]): string[] {
});
}
const elasticsearchGridColumnTypes = computed(() => elasticsearchGridColumnTypesFor(lastGridColumns.value));
const solrSchemaFields = ref<ColumnInfo[]>([]);
const solrFieldTypes = computed(() => new Map(solrSchemaFields.value.map((field) => [field.name, field.data_type])));
function solrGridColumnTypesFor(columns: readonly string[]): string[] {
return columns.map((column) => (column === "_id" ? "string" : (solrFieldTypes.value.get(column) ?? "")));
}
const solrGridColumnTypes = computed(() => solrGridColumnTypesFor(lastGridColumns.value));
const dynamodbTableDescription = ref<DynamoDbTableDescription | null>(null);
const dynamodbIndexName = ref("__table__");
const dynamodbPageCursors = ref<Array<string | undefined>>([undefined]);
@@ -536,6 +542,9 @@ const deleteDetails = computed(() => {
});
}
const displayId = mongoDocumentIdForGrid(id);
if (props.databaseType === "solr") {
return `Solr core: ${props.collection}\nDocument _id: ${String(displayId)}`;
}
if (props.databaseType === "elasticsearch" || props.databaseType === "easysearch" || props.databaseType === "meilisearch") {
const product = props.databaseType === "easysearch" ? "Easysearch" : props.databaseType === "meilisearch" ? "Meilisearch" : "Elasticsearch";
return `${product} index: ${props.collection}\nDocument _id: ${String(displayId)}`;
@@ -608,7 +617,7 @@ function commitLoadedDocuments(nextDocuments: JsonRecord[], nextCopyDocuments: J
const gridResult = computed<QueryResult>(() => {
const docs = documents.value;
const columnTypes = documentStoreProvider.value.kind === "elasticsearch" ? elasticsearchGridColumnTypes.value : lastGridColumnTypes.value;
const columnTypes = documentStoreProvider.value.kind === "elasticsearch" ? elasticsearchGridColumnTypes.value : documentStoreProvider.value.kind === "solr" ? solrGridColumnTypes.value : lastGridColumnTypes.value;
if (!docs.length) {
return {
columns: lastGridColumns.value,
@@ -637,7 +646,7 @@ const gridResult = computed<QueryResult>(() => {
async function exportAllDocumentStoreDocuments(onProgress?: (info: { rowsExported: number; totalRows: number | null }) => void): Promise<QueryResult | undefined> {
const kind = documentStoreProvider.value.kind;
if (kind !== "mongodb" && kind !== "dynamodb" && kind !== "elasticsearch") return undefined;
if (kind !== "mongodb" && kind !== "dynamodb" && kind !== "elasticsearch" && kind !== "solr") return undefined;
const connectionId = props.connectionId;
const database = props.database;
@@ -673,7 +682,7 @@ async function exportAllDocumentStoreDocuments(onProgress?: (info: { rowsExporte
}
}
if ((kind === "mongodb" || kind === "elasticsearch") && result.total_is_exact !== false) totalRows = Math.min(result.total, rowLimit);
if ((kind === "mongodb" || kind === "elasticsearch" || kind === "solr") && result.total_is_exact !== false) totalRows = Math.min(result.total, rowLimit);
onProgress?.({ rowsExported: exportedDocuments.length, totalRows });
if (kind === "dynamodb" || kind === "elasticsearch") {
@@ -705,7 +714,7 @@ async function exportAllDocumentStoreDocuments(onProgress?: (info: { rowsExporte
const result = mongoDocumentsToQueryResult(exportedDocuments, performance.now() - exportStartedAt, totalRows ?? exportedDocuments.length, exportedCopyDocuments, totalRows !== null);
if (result.columns.length === 0) result.columns = gridResult.value.columns;
result.column_types = kind === "mongodb" ? mongoDocumentGridColumnTypes(exportedDocuments, result.columns) : kind === "elasticsearch" ? elasticsearchGridColumnTypesFor(result.columns) : undefined;
result.column_types = kind === "mongodb" ? mongoDocumentGridColumnTypes(exportedDocuments, result.columns) : kind === "elasticsearch" ? elasticsearchGridColumnTypesFor(result.columns) : kind === "solr" ? solrGridColumnTypesFor(result.columns) : undefined;
result.affected_rows = exportedDocuments.length;
result.truncated = (kind === "dynamodb" || kind === "elasticsearch") && !!cursor && exportedDocuments.length >= rowLimit;
result.has_more = result.truncated;
@@ -717,10 +726,19 @@ const elasticsearchFilterFieldNames = computed(() => {
const names = [...elasticsearchMappingFields.value.map((field) => field.name), ...gridResult.value.columns, "_id", "_routing"];
return [...new Set(names.filter(Boolean))];
});
const solrFilterFieldNames = computed(() => {
const names = [...solrSchemaFields.value.map((field) => field.name), ...gridResult.value.columns, "_id"];
return [...new Set(names.filter(Boolean))];
});
const documentFilterFieldTree = computed<DocumentFieldPathNode[]>(() => {
if (documentStoreProvider.value.kind === "elasticsearch") {
return elasticsearchFieldPathTreeFromFieldNames(elasticsearchFilterFieldNames.value, elasticsearchFieldTypes.value);
}
if (documentStoreProvider.value.kind === "solr") {
// Schema fields give the filter builder a complete field list even before
// any document page has loaded; unknown Solr types stay leaf-selectable.
return elasticsearchFieldPathTreeFromFieldNames(solrFilterFieldNames.value, solrFieldTypes.value);
}
const tree = documentFieldPathTreeFromDocuments(documents.value);
if (tree.length > 0) return tree;
return gridResult.value.columns.map((column) => ({
@@ -1395,12 +1413,16 @@ async function applyDocumentStructuredFilters() {
applyFilter();
}
async function loadElasticsearchMappingFields() {
if (documentStoreProvider.value.kind !== "elasticsearch") return;
async function loadDocumentStoreSchemaFields() {
const kind = documentStoreProvider.value.kind;
if (kind !== "elasticsearch" && kind !== "solr") return;
try {
elasticsearchMappingFields.value = (await api.getColumns(props.connectionId, props.database, "", props.collection)) ?? [];
const fields = (await api.getColumns(props.connectionId, props.database, "", props.collection)) ?? [];
if (kind === "elasticsearch") elasticsearchMappingFields.value = fields;
else solrSchemaFields.value = fields;
} catch {
elasticsearchMappingFields.value = [];
if (kind === "elasticsearch") elasticsearchMappingFields.value = [];
else solrSchemaFields.value = [];
}
}
@@ -1687,9 +1709,9 @@ async function previewDocumentChanges(changes: DocumentGridChanges): Promise<str
const sourceDocument = documents.value[rowIdx];
if (!sourceDocument) continue;
const documentId = sourceDocument._id ?? id;
const updated = buildPathIdentityUpdatedDocument(sourceDocument, dirtyCols, columns, "meilisearch");
const writeDocument = prepareDocumentStoreWriteDocument(updated, { kind: "meilisearch", mode: "update" });
stmts.push(formatMeilisearchDocumentOperationPreview({ action: "update", index: coll, id: documentId, document: writeDocument }));
const updated = buildPathIdentityUpdatedDocument(sourceDocument, dirtyCols, columns, kind === "solr" ? "solr" : "meilisearch");
const writeDocument = prepareDocumentStoreWriteDocument(updated, { kind: kind === "solr" ? "solr" : "meilisearch", mode: "update" });
stmts.push(kind === "solr" ? formatSolrDocumentOperationPreview({ action: "update", core: coll, id: documentId, document: writeDocument }) : formatMeilisearchDocumentOperationPreview({ action: "update", index: coll, id: documentId, document: writeDocument }));
}
} else {
const updateDoc = buildMongoUpdateDocument(dirtyCols, columns, documents.value[rowIdx]);
@@ -1707,7 +1729,7 @@ async function previewDocumentChanges(changes: DocumentGridChanges): Promise<str
continue;
}
if (!isEs) {
stmts.push(formatMeilisearchDocumentOperationPreview({ action: "delete", index: coll, id: documents.value[rowIdx]?._id ?? id }));
stmts.push(kind === "solr" ? formatSolrDocumentOperationPreview({ action: "delete", core: coll, id: documents.value[rowIdx]?._id ?? id }) : formatMeilisearchDocumentOperationPreview({ action: "delete", index: coll, id: documents.value[rowIdx]?._id ?? id }));
continue;
}
const routing = documentRoutingFromGridRow(row, columns);
@@ -1730,8 +1752,8 @@ async function previewDocumentChanges(changes: DocumentGridChanges): Promise<str
}
if (!isEs) {
const idValue = idColIdx >= 0 ? newRow[idColIdx] : null;
const id = idValue === null || idValue === undefined || idValue === "" ? undefined : parseDocumentStoreInputValue(idValue, "meilisearch");
stmts.push(formatMeilisearchDocumentOperationPreview({ action: id === undefined ? "insert" : "upsert", index: coll, id, document: doc }));
const id = idValue === null || idValue === undefined || idValue === "" ? undefined : parseDocumentStoreInputValue(idValue, kind === "solr" ? "solr" : "meilisearch");
stmts.push(kind === "solr" ? formatSolrDocumentOperationPreview({ action: id === undefined ? "insert" : "upsert", core: coll, id, document: doc }) : formatMeilisearchDocumentOperationPreview({ action: id === undefined ? "insert" : "upsert", index: coll, id, document: doc }));
continue;
}
const id = idColIdx >= 0 ? documentIdFromGridValue(newRow[idColIdx]) : null;
@@ -1917,7 +1939,7 @@ async function load(options: { page?: number; append?: boolean; offset?: number;
if (documentLoadExecutionId.value !== executionId) return;
if (connectionId !== props.connectionId || database !== props.database || collection !== props.collection || storeKind !== documentStoreProvider.value.kind) return;
const nextDocuments =
storeKind === "elasticsearch" && result.raw_documents?.length === result.documents.length
(storeKind === "elasticsearch" || storeKind === "solr") && result.raw_documents?.length === result.documents.length
? result.raw_documents.map((raw, index) => {
try {
return asRecord(parseJsonPreservingLargeNumbers(raw));
@@ -1997,7 +2019,7 @@ async function countExactDocumentTotal(): Promise<number | undefined> {
paginationTotal.value = totals.paginationTotal;
return exactCount;
}
const exactCount = request.storeKind === "dynamodb" ? await api.documentCountDocuments(request.connectionId, request.collection, request.filter) : await api.mongoCountDocuments(request.connectionId, request.database, request.collection, request.filter, "accurate");
const exactCount = request.storeKind === "dynamodb" || request.storeKind === "solr" ? await api.documentCountDocuments(request.connectionId, request.collection, request.filter) : await api.mongoCountDocuments(request.connectionId, request.database, request.collection, request.filter, "accurate");
if (!isCurrentDocumentQueryTotalCountRequest(request)) return undefined;
if (!Number.isFinite(exactCount) || exactCount < 0) {
throw new Error("invalid count");
@@ -2633,9 +2655,9 @@ onMounted(async () => {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
await loadDynamoDbTableDescription();
// Mapping metadata enriches the filter builder, but it must not delay the
// first page of documents when the mapping endpoint is slow.
void loadElasticsearchMappingFields();
// Schema metadata enriches the filter builder, but it must not delay the
// first page of documents when the schema endpoint is slow.
void loadDocumentStoreSchemaFields();
// A restored snapshot already holds the rows the last load produced, so a tab
// switch must not re-issue the collection query (#8679). Refresh and every
// mutation path still force a real load.
@@ -270,7 +270,7 @@ function sqlBehaviorDialect(): "mysql" | "postgres" | "sqlserver" | undefined {
function queryEditorSelectionLanguage(): "sql" | "text" {
const databaseType = props.databaseType;
return databaseType === "redis" || databaseType === "mongodb" || databaseType === "elasticsearch" || databaseType === "easysearch" || databaseType === "meilisearch" || databaseType === "victoriametrics" ? "text" : "sql";
return databaseType === "redis" || databaseType === "mongodb" || databaseType === "elasticsearch" || databaseType === "easysearch" || databaseType === "meilisearch" || databaseType === "solr" || databaseType === "victoriametrics" ? "text" : "sql";
}
const COMPLETION_REMOTE_LATENCY_BUDGET_MS = 120;
@@ -3902,7 +3902,7 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo
setSemanticDiagnostics([]);
return;
}
if (props.databaseType === "elasticsearch" || props.databaseType === "easysearch" || props.databaseType === "meilisearch" || props.databaseType === "victoriametrics") {
if (props.databaseType === "elasticsearch" || props.databaseType === "easysearch" || props.databaseType === "meilisearch" || props.databaseType === "solr" || props.databaseType === "victoriametrics") {
setSemanticDiagnostics([]);
return;
}
@@ -4955,7 +4955,7 @@ function localCompletionSchemasForDatabaseDisambiguation(completionContext: Retu
}
function shouldInsertSqlCompletionSpace(): boolean {
return props.databaseType !== "mongodb" && props.databaseType !== "redis" && props.databaseType !== "elasticsearch" && props.databaseType !== "easysearch" && props.databaseType !== "meilisearch" && props.databaseType !== "victoriametrics";
return props.databaseType !== "mongodb" && props.databaseType !== "redis" && props.databaseType !== "elasticsearch" && props.databaseType !== "easysearch" && props.databaseType !== "meilisearch" && props.databaseType !== "solr" && props.databaseType !== "victoriametrics";
}
// Snippet expansion normally follows from the item type; a provider can also
@@ -5196,7 +5196,7 @@ async function provideSqlCompletions(context: CompletionContext) {
if (props.databaseType === "mongodb") {
return provideMongoCompletions(currentState, position, explicit);
}
if (props.databaseType === "meilisearch") return null;
if (props.databaseType === "meilisearch" || props.databaseType === "solr") return null;
if (props.databaseType === "elasticsearch" || props.databaseType === "easysearch") {
if (!isSqlLikeCompletionStatement(fullDoc, position, sqlCompletionDialectOptions())) {
return provideElasticsearchCompletions(currentState, position, explicit);
@@ -5571,7 +5571,7 @@ function shouldStartSqlCompletionAfterInput(insertedText: string, removedText: s
if (props.databaseType === "mongodb") {
return !!(insertedText || removedText) && shouldAutoOpenMongoCompletion(fullDoc, position);
}
if (props.databaseType === "victoriametrics" || props.databaseType === "meilisearch") return false;
if (props.databaseType === "victoriametrics" || props.databaseType === "meilisearch" || props.databaseType === "solr") return false;
if (props.databaseType === "redis" || props.databaseType === "elasticsearch" || props.databaseType === "easysearch") {
// Preserve old character-based checks for non-SQL providers.
if (!insertedText && removedText) {
@@ -115,7 +115,7 @@ const exportElapsedText = computed(() => {
return formatDataTransferDuration((exportFinishedAt.value ?? currentTime.value) - exportStartedAt.value);
});
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "easysearch", "meilisearch", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper", "consul", "mq", "nacos"].includes(c.db_type)));
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "easysearch", "meilisearch", "solr", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper", "consul", "mq", "nacos"].includes(c.db_type)));
const canExport = computed(() => {
const hasContent = includeStructure.value || includeData.value || includeObjects.value;
@@ -31,6 +31,7 @@ const assetIcons: Record<string, string> = {
elasticsearch: "elasticsearch",
easysearch: "easysearch",
meilisearch: "meilisearch",
solr: "solr",
oracle: "oracle",
"oracle-10g": "oracle",
"oracle-legacy": "oracle",
@@ -135,7 +136,7 @@ const assetSrc = computed(() => {
</script>
<template>
<img v-if="assetName" :src="assetSrc" alt="" class="database-logo object-contain" :class="{ 'database-logo-light': useLightIconInDarkMode, 'database-logo-impala': normalizedType === 'impala' }" aria-hidden="true" />
<img v-if="assetName" :src="assetSrc" alt="" class="database-logo object-contain" :class="{ 'database-logo-light': useLightIconInDarkMode, 'database-logo-impala': normalizedType === 'impala', 'database-logo-solr': normalizedType === 'solr' }" aria-hidden="true" />
<Database v-else class="text-blue-400" />
</template>
@@ -152,4 +153,10 @@ const assetSrc = computed(() => {
.database-logo-impala {
transform: scale(1.55);
}
/* solr.svg 的图形撑满整个 viewBox(无内边距),其他 logo 留白约 20-25%,
统一 scale(1.35) 下视觉偏大,单独收敛到与多数 logo 一致的占幅。 */
.database-logo-solr {
transform: scale(1.02);
}
</style>
@@ -129,6 +129,7 @@ const DamengJobAdmin = defineAsyncComponent(() => import("@/components/admin/Dam
const DamengUserAdmin = defineAsyncComponent(() => import("@/components/admin/DamengUserAdmin.vue"));
const DamengRoleAdmin = defineAsyncComponent(() => import("@/components/admin/DamengRoleAdmin.vue"));
const SolrAdmin = defineAsyncComponent(() => import("@/components/solr/SolrAdmin.vue"));
const PluginFilesystemTab = defineAsyncComponent(() => import("@/components/plugins/PluginFilesystemTab.vue"));
const ExplainPlanViewer = defineAsyncComponent(() => import("@/components/explain/ExplainPlanViewer.vue"));
const QueryChart = defineAsyncComponent(() => import("@/components/chart/QueryChart.vue"));
@@ -505,7 +506,7 @@ 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" && activeEffectiveDatabaseType.value !== "easysearch") return undefined;
if (activeEffectiveDatabaseType.value !== "elasticsearch" && activeEffectiveDatabaseType.value !== "easysearch" && activeEffectiveDatabaseType.value !== "solr") return undefined;
return props.activeTab.result?.elasticsearch_raw_body;
});
/** ES `_search?profile=true` body extracted from the active result, when present. */
@@ -2863,6 +2864,12 @@ defineExpose({
</div>
</template>
<template v-else-if="activeTab.mode === 'solr-admin'">
<div class="min-h-0 flex-1">
<SolrAdmin :key="activeTab.id" :connection-id="activeTab.connectionId" />
</div>
</template>
<template v-else-if="activeTab.mode === 'dolt-version-control'">
<div class="min-h-0 flex-1">
<DoltVersionControl :key="activeTab.id" :connection-id="activeTab.connectionId" :database="activeTab.database" :initial-branch="activeTab.workspaceBranch" />
@@ -209,6 +209,7 @@ const supportsExplain = computed(() => {
dbType !== "elasticsearch" &&
dbType !== "easysearch" &&
dbType !== "meilisearch" &&
dbType !== "solr" &&
dbType !== "qdrant" &&
dbType !== "milvus" &&
dbType !== "weaviate" &&
@@ -758,6 +759,7 @@ async function changeCatalog(selectedCatalog: string) {
activeConnection?.db_type !== 'elasticsearch' &&
activeConnection?.db_type !== 'easysearch' &&
activeConnection?.db_type !== 'meilisearch' &&
activeConnection?.db_type !== 'solr' &&
activeConnection?.db_type !== 'qdrant' &&
activeConnection?.db_type !== 'milvus' &&
activeConnection?.db_type !== 'weaviate' &&
@@ -1747,7 +1747,7 @@ async function ensureTreeLoadedForTarget(target: ActiveTabSidebarTarget, opts?:
await store.loadMongoDatabases(connId);
} else if (config.db_type === "dynamodb") {
await store.loadDynamoDbTables(connId);
} else if (config.db_type === "elasticsearch" || config.db_type === "easysearch" || config.db_type === "meilisearch") {
} else if (config.db_type === "elasticsearch" || config.db_type === "easysearch" || config.db_type === "meilisearch" || config.db_type === "solr") {
await store.loadElasticsearchIndices(connId);
} else if (config.db_type === "qdrant" || config.db_type === "milvus" || config.db_type === "weaviate" || config.db_type === "chromadb") {
await store.loadVectorCollections(connId);
@@ -967,8 +967,8 @@ async function toggle(requestId = beginNavigationRequest()) {
await connectionStore.loadMongoDatabases(node.connectionId);
} else if (config?.db_type === "dynamodb") {
await connectionStore.loadDynamoDbTables(node.connectionId);
} else if (config?.db_type === "elasticsearch" || config?.db_type === "easysearch" || config?.db_type === "meilisearch") {
// Expand: list indices (like other db types list databases).
} else if (config?.db_type === "elasticsearch" || config?.db_type === "easysearch" || config?.db_type === "meilisearch" || config?.db_type === "solr") {
// Expand: list indices/cores (like other db types list databases).
await connectionStore.loadElasticsearchIndices(node.connectionId);
} else if (config?.db_type === "milvus") {
await connectionStore.loadMilvusDatabases(node.connectionId);
@@ -1826,6 +1826,8 @@ async function openServerDashboard() {
connectionStore.activeConnectionId = node.connectionId;
if (currentDatabaseType() === "nacos") {
queryStore.openNacosDashboard(node.connectionId);
} else if (currentDatabaseType() === "solr") {
queryStore.openSolrAdmin(node.connectionId);
} else if (connectionSupportsXuguServerDashboard(connectionStore.getConfig(node.connectionId))) {
queryStore.openXuguDashboard(node.connectionId);
} else if (connectionSupportsPgServerDashboard(connectionStore.getConfig(node.connectionId))) {
@@ -4686,7 +4688,7 @@ const canOpenSqlFileExecution = computed(() => {
const canExportAllDatabases = computed(() => {
if (activeNode.value.type !== "connection" || !activeNode.value.connectionId) return false;
const dbType = connectionStore.getConfig(activeNode.value.connectionId)?.db_type;
return !["redis", "mongodb", "dynamodb", "elasticsearch", "easysearch", "meilisearch", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper", "consul", "mq", "nacos", "plugin"].includes(dbType || "");
return !["redis", "mongodb", "dynamodb", "elasticsearch", "easysearch", "solr", "meilisearch", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper", "consul", "mq", "nacos", "plugin"].includes(dbType || "");
});
const canOpenScheduledBackups = computed(() => {
@@ -5597,7 +5599,11 @@ function buildConnectionSidebarMenu(context: SidebarMenuFactoryContext): boolean
}
if (
node.connectionId &&
(currentDatabaseType() === "nacos" || connectionSupportsXuguServerDashboard(connectionStore.getConfig(node.connectionId)) || connectionSupportsServerDashboard(connectionStore.getConfig(node.connectionId)) || connectionSupportsPgServerDashboard(connectionStore.getConfig(node.connectionId)))
(currentDatabaseType() === "nacos" ||
currentDatabaseType() === "solr" ||
connectionSupportsXuguServerDashboard(connectionStore.getConfig(node.connectionId)) ||
connectionSupportsServerDashboard(connectionStore.getConfig(node.connectionId)) ||
connectionSupportsPgServerDashboard(connectionStore.getConfig(node.connectionId)))
) {
items.push({ label: t("contextMenu.serverDashboard"), action: openServerDashboard, icon: Gauge });
}
File diff suppressed because it is too large. Load diff
@@ -167,7 +167,7 @@ function resetPerFileState() {
currentFileName.value = "";
}
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "easysearch", "meilisearch", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper", "consul", "mq", "nacos"].includes(c.db_type)));
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "easysearch", "meilisearch", "solr", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper", "consul", "mq", "nacos"].includes(c.db_type)));
// Mirrors the core executor gate (`relational_constraint_bypass_kind` in
// sql_file_import.rs): MySQL-family types use the session-scoped
// FOREIGN_KEY_CHECKS toggle, PostgreSQL-family types use DISABLE/ENABLE