feat(solr): add Apache Solr connection support
This commit is contained in:
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
|
||||
|
||||
Reference in new issue
Block a user