feat(elasticsearch): model indices as first-class nodes
This commit is contained in:
parent
6f253abe55
commit
26d714909e
|
|
@ -5272,15 +5272,18 @@ function toggleCellDetailPanelLayout() {
|
|||
|
||||
const tableMetadataCapabilities = computed(() => getTableMetadataCapabilities(props.databaseType));
|
||||
const tableInfoTabs = computed(() => {
|
||||
const tabs: TableInfoTabItem[] = [
|
||||
{
|
||||
const tabs: TableInfoTabItem[] = [];
|
||||
if (tableMetadataCapabilities.value.columns) {
|
||||
tabs.push({
|
||||
id: "columns",
|
||||
label: t("grid.tableInfoColumns"),
|
||||
icon: ListTree,
|
||||
count: props.tableMeta?.columns.length,
|
||||
},
|
||||
{ id: "indexes", label: t("grid.tableInfoIndexes"), icon: KeyRound, count: indexes.value.length },
|
||||
];
|
||||
});
|
||||
}
|
||||
if (tableMetadataCapabilities.value.indexes) {
|
||||
tabs.push({ id: "indexes", label: t("grid.tableInfoIndexes"), icon: KeyRound, count: indexes.value.length });
|
||||
}
|
||||
if (tableMetadataCapabilities.value.foreignKeys) {
|
||||
tabs.push({
|
||||
id: "foreignKeys",
|
||||
|
|
@ -5292,7 +5295,9 @@ const tableInfoTabs = computed(() => {
|
|||
if (tableMetadataCapabilities.value.triggers) {
|
||||
tabs.push({ id: "triggers", label: t("grid.tableInfoTriggers"), icon: RotateCcw, count: triggers.value.length });
|
||||
}
|
||||
tabs.push({ id: "ddl", label: "DDL", icon: Code2 });
|
||||
if (tableMetadataCapabilities.value.ddl) {
|
||||
tabs.push({ id: "ddl", label: "DDL", icon: Code2 });
|
||||
}
|
||||
return tabs;
|
||||
});
|
||||
const tableInfoTabListStyle = computed(() => ({
|
||||
|
|
@ -5309,7 +5314,8 @@ async function toggleTableInfo(tab: TableInfoTab = activeTableInfoTab.value) {
|
|||
}
|
||||
|
||||
async function selectTableInfoTab(tab: TableInfoTab) {
|
||||
const nextTab = tableInfoTabs.value.some((item) => item.id === tab) ? tab : "columns";
|
||||
const nextTab = tableInfoTabs.value.some((item) => item.id === tab) ? tab : tableInfoTabs.value[0]?.id;
|
||||
if (!nextTab) return;
|
||||
activeTableInfoTab.value = nextTab;
|
||||
if (nextTab === "ddl") await fetchDdl();
|
||||
else if (nextTab === "indexes") await fetchIndexes();
|
||||
|
|
|
|||
|
|
@ -820,10 +820,10 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<!-- MongoDB mode: document browser -->
|
||||
<!-- Document mode: MongoDB collections and Elasticsearch indices -->
|
||||
<template v-else-if="activeTab.mode === 'mongo'">
|
||||
<div class="flex-1 min-h-0">
|
||||
<MongoDocBrowser :key="activeTab.id" :connection-id="activeTab.connectionId" :database="activeTab.database" :collection="activeTab.sql" />
|
||||
<MongoDocBrowser :key="activeTab.id" :connection-id="activeTab.connectionId" :database="activeTab.database" :collection="activeTab.sql" :database-type="activeEffectiveDatabaseType" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { normalizeResultPageSize } from "@/lib/paginationPageSize";
|
|||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import JsonEditNode from "./JsonEditNode.vue";
|
||||
import type { EditNode } from "@/types/editor";
|
||||
import type { QueryResult } from "@/types/database";
|
||||
import type { DatabaseType, QueryResult } from "@/types/database";
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
import "splitpanes/dist/splitpanes.css";
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ const props = defineProps<{
|
|||
connectionId: string;
|
||||
database: string;
|
||||
collection: string;
|
||||
databaseType?: DatabaseType;
|
||||
}>();
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
|
@ -64,6 +65,17 @@ const tableFindPaneStyle = computed(() => {
|
|||
if (tableFindPaneWidth.value == null) return {};
|
||||
return { flex: `0 0 ${tableFindPaneWidth.value}px` };
|
||||
});
|
||||
const documentStoreLabels = computed(() =>
|
||||
props.databaseType === "elasticsearch"
|
||||
? {
|
||||
documentsLabel: "Documents",
|
||||
queryPreview: `${props.collection}/_search`,
|
||||
}
|
||||
: {
|
||||
documentsLabel: t("mongo.documents", { count: total.value }),
|
||||
queryPreview: mongoQueryPreview.value,
|
||||
},
|
||||
);
|
||||
|
||||
type PendingDelete = { kind: "document"; index: number } | { kind: "field"; index: number; name: string };
|
||||
type LocalFilterSummary = {
|
||||
|
|
@ -114,6 +126,7 @@ const deleteDetails = computed(() => {
|
|||
if (!pending) return "";
|
||||
if (pending.kind === "document") {
|
||||
const id = documents.value[pending.index]?._id ?? "";
|
||||
if (props.databaseType === "elasticsearch") return `Elasticsearch index: ${props.collection}\nDocument _id: ${String(id)}`;
|
||||
return t("dangerDialog.mongoDocumentDetails", { collection: props.collection, id: String(id) });
|
||||
}
|
||||
return t("dangerDialog.mongoFieldDetails", { field: pending.name || t("mongo.field") });
|
||||
|
|
@ -675,7 +688,7 @@ function resetTableSearchSplitWidth() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<span class="shrink-0 ml-1">{{ t("mongo.documents", { count: total }) }}</span>
|
||||
<span class="shrink-0 ml-1">{{ documentStoreLabels.documentsLabel }}</span>
|
||||
|
||||
<Button v-if="viewMode === 'document'" variant="ghost" size="icon" class="h-5 w-5" @click="startNew"><Plus class="h-3 w-3" /></Button>
|
||||
<Button v-if="viewMode === 'document'" variant="ghost" size="icon" class="h-5 w-5" @click="load"><RefreshCw class="h-3 w-3" :class="{ 'animate-spin': loading }" /></Button>
|
||||
|
|
@ -767,7 +780,7 @@ function resetTableSearchSplitWidth() {
|
|||
editable
|
||||
:custom-save="gridSave"
|
||||
:loading="loading"
|
||||
:sql="mongoQueryPreview"
|
||||
:sql="documentStoreLabels.queryPreview"
|
||||
:page-offset="page * pageSize"
|
||||
:page-limit="pageSize"
|
||||
:total-row-count="total"
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const SEARCH_SCOPE_TO_NODE_TYPES: Record<SearchScope, TreeNodeType[]> = {
|
|||
connection: ["connection"],
|
||||
database: ["database", "redis-db", "mongo-db"],
|
||||
schema: ["schema"],
|
||||
table: ["table", "mongo-collection"],
|
||||
table: ["table", "mongo-collection", "elasticsearch-index"],
|
||||
view: ["view"],
|
||||
};
|
||||
|
||||
|
|
@ -275,8 +275,10 @@ async function ensureTreeLoadedForTab(tab: QueryTab, opts?: { force?: boolean })
|
|||
try {
|
||||
if (config.db_type === "redis") {
|
||||
await store.loadRedisDatabases(connId);
|
||||
} else if (config.db_type === "mongodb" || config.db_type === "elasticsearch") {
|
||||
} else if (config.db_type === "mongodb") {
|
||||
await store.loadMongoDatabases(connId);
|
||||
} else if (config.db_type === "elasticsearch") {
|
||||
await store.loadElasticsearchIndices(connId);
|
||||
} else {
|
||||
await store.loadDatabases(connId, loadOptions);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,6 +219,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
|
|||
return { icon: Database, colorClass: "text-yellow-500" };
|
||||
case "mongo-collection":
|
||||
return { icon: Table, colorClass: "text-green-400" };
|
||||
case "elasticsearch-index":
|
||||
return { icon: Table, colorClass: "text-emerald-400" };
|
||||
case "procedure":
|
||||
return { icon: ScrollText, colorClass: "text-blue-500" };
|
||||
case "function":
|
||||
|
|
@ -249,7 +251,7 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
|
|||
}
|
||||
|
||||
const groupTypes: Set<TreeNodeType> = new Set(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-tables", "group-views", "group-procedures", "group-functions", "group-sequences", "group-packages", "group-partitions"]);
|
||||
const pinnableTypes: Set<TreeNodeType> = new Set(["connection-group", "database", "schema", "table", "view", "redis-db", "mongo-db", "mongo-collection"]);
|
||||
const pinnableTypes: Set<TreeNodeType> = new Set(["connection-group", "database", "schema", "table", "view", "redis-db", "mongo-db", "mongo-collection", "elasticsearch-index"]);
|
||||
|
||||
function isGroupLabel(node: TreeNode): boolean {
|
||||
return groupTypes.has(node.type);
|
||||
|
|
@ -263,7 +265,7 @@ function displayLabel(node: TreeNode): string {
|
|||
}
|
||||
|
||||
function visibleLabel(node: TreeNode): string {
|
||||
if (node.type === "table" || node.type === "view" || node.type === "mongo-collection") {
|
||||
if (node.type === "table" || node.type === "view" || node.type === "mongo-collection" || node.type === "elasticsearch-index") {
|
||||
return sidebarDisplayTableName(node.label, settingsStore.editorSettings.sidebarHiddenTablePrefixes);
|
||||
}
|
||||
return displayLabel(node);
|
||||
|
|
@ -312,8 +314,10 @@ async function toggle() {
|
|||
await connectionStore.loadRedisDatabases(node.connectionId);
|
||||
} else if (config?.db_type === "etcd") {
|
||||
await connectionStore.loadEtcdRoot(node.connectionId);
|
||||
} else if (config?.db_type === "mongodb" || config?.db_type === "elasticsearch") {
|
||||
} else if (config?.db_type === "mongodb") {
|
||||
await connectionStore.loadMongoDatabases(node.connectionId);
|
||||
} else if (config?.db_type === "elasticsearch") {
|
||||
await connectionStore.loadElasticsearchIndices(node.connectionId);
|
||||
} else {
|
||||
await connectionStore.loadDatabases(node.connectionId);
|
||||
}
|
||||
|
|
@ -331,6 +335,9 @@ async function toggle() {
|
|||
const tabTitle = `${node.database}.${node.label}`;
|
||||
const tab = queryStore.createTab(node.connectionId, node.database, tabTitle, "mongo");
|
||||
queryStore.updateSql(tab, node.label);
|
||||
} else if (node.type === "elasticsearch-index" && node.connectionId) {
|
||||
const tab = queryStore.createTab(node.connectionId, node.database || "default", node.label, "mongo");
|
||||
queryStore.updateSql(tab, node.label);
|
||||
} else if (node.type === "database" && node.connectionId && hasTreeNodeDatabaseContext(node)) {
|
||||
const config = connectionStore.getConfig(node.connectionId);
|
||||
if (config?.db_type === "sqlserver") {
|
||||
|
|
@ -2237,7 +2244,7 @@ const hasTypeMenu = computed(() => {
|
|||
return t === "connection" || t === "database" || t === "schema" || t === "table" || t === "view" || t === "column" || t === "procedure" || t === "function" || t === "package" || t === "package-body" || isGroupLabel(props.node);
|
||||
});
|
||||
const columnComment = computed(() => (props.node.type === "column" && props.node.meta && "comment" in props.node.meta ? (props.node.meta as any).comment : null));
|
||||
const tableComment = computed(() => ((props.node.type === "table" || props.node.type === "view" || props.node.type === "mongo-collection") && props.node.comment ? props.node.comment : null));
|
||||
const tableComment = computed(() => ((props.node.type === "table" || props.node.type === "view" || props.node.type === "mongo-collection" || props.node.type === "elasticsearch-index") && props.node.comment ? props.node.comment : null));
|
||||
const paddingLeft = computed(() => treeItemPaddingLeft(props.depth));
|
||||
const isConnected = computed(() => props.node.type === "connection" && !!props.node.connectionId && connectionStore.connectedIds.has(props.node.connectionId));
|
||||
const isConnectionReadonly = computed(() => props.node.type === "connection" && !!props.node.connectionId && (connectionStore.getConfig(props.node.connectionId)?.read_only ?? false));
|
||||
|
|
@ -2788,6 +2795,14 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
return items;
|
||||
}
|
||||
|
||||
if (node.type === "elasticsearch-index") {
|
||||
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
|
||||
items.push({ label: "", separator: true });
|
||||
items.push({ label: t("contextMenu.viewData"), action: toggle, icon: TableProperties });
|
||||
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
|
||||
return items;
|
||||
}
|
||||
|
||||
// 6. Table / View
|
||||
if (node.type === "table" || node.type === "view") {
|
||||
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const ddlLoading = ref(false);
|
|||
const ddlFetched = ref(false);
|
||||
|
||||
async function fetchDdl() {
|
||||
if (!props.connectionId || !props.database || !props.tableName || ddlFetched.value) return;
|
||||
if (!props.connectionId || !props.database || !props.tableName || ddlFetched.value || !tableMetadataCapabilities.value.ddl) return;
|
||||
ddlLoading.value = true;
|
||||
try {
|
||||
ddlContent.value = await api.getTableDdl(props.connectionId, props.database, metadataSchema.value, props.tableName);
|
||||
|
|
@ -373,7 +373,7 @@ async function loadStructure(silent = false) {
|
|||
await store.ensureConnected(props.connectionId);
|
||||
const nextColumns = await api.getColumns(props.connectionId, props.database, metadataSchema.value, props.tableName);
|
||||
const [nextIndexes, nextForeignKeys, nextTriggers] = await Promise.all([
|
||||
api.listIndexes(props.connectionId, props.database, metadataSchema.value, props.tableName).catch(() => []),
|
||||
tableMetadataCapabilities.value.indexes ? api.listIndexes(props.connectionId, props.database, metadataSchema.value, props.tableName).catch(() => []) : Promise.resolve([]),
|
||||
tableMetadataCapabilities.value.foreignKeys ? api.listForeignKeys(props.connectionId, props.database, metadataSchema.value, props.tableName).catch(() => []) : Promise.resolve([]),
|
||||
tableMetadataCapabilities.value.triggers ? api.listTriggers(props.connectionId, props.database, metadataSchema.value, props.tableName).catch(() => []) : Promise.resolve([]),
|
||||
]);
|
||||
|
|
@ -672,9 +672,23 @@ onBeforeUnmount(() => {
|
|||
unregisterStructureEditorShortcuts();
|
||||
});
|
||||
|
||||
function firstStructureMetadataTab(capabilities = tableMetadataCapabilities.value) {
|
||||
if (capabilities.columns) return "columns";
|
||||
if (capabilities.indexes) return "indexes";
|
||||
if (capabilities.foreignKeys) return "foreignKeys";
|
||||
if (capabilities.triggers) return "triggers";
|
||||
if (capabilities.ddl && !isCreateMode.value) return "ddl";
|
||||
return "columns";
|
||||
}
|
||||
|
||||
watch(tableMetadataCapabilities, (capabilities) => {
|
||||
if (activeTab.value === "foreignKeys" && !capabilities.foreignKeys) activeTab.value = "columns";
|
||||
if (activeTab.value === "triggers" && !capabilities.triggers) activeTab.value = "columns";
|
||||
const supported =
|
||||
(activeTab.value === "columns" && capabilities.columns) ||
|
||||
(activeTab.value === "indexes" && capabilities.indexes) ||
|
||||
(activeTab.value === "foreignKeys" && capabilities.foreignKeys) ||
|
||||
(activeTab.value === "triggers" && capabilities.triggers) ||
|
||||
(activeTab.value === "ddl" && capabilities.ddl && !isCreateMode.value);
|
||||
if (!supported) activeTab.value = firstStructureMetadataTab(capabilities);
|
||||
});
|
||||
|
||||
watch(
|
||||
|
|
@ -735,11 +749,11 @@ watch(activeTab, (tab) => {
|
|||
<Tabs v-model="activeTab" class="flex h-full min-h-0 flex-col">
|
||||
<div class="flex shrink-0 items-center justify-between gap-2 border-b px-2 py-[var(--structure-header-py)]">
|
||||
<TabsList>
|
||||
<TabsTrigger value="columns">{{ t("structureEditor.columns") }}</TabsTrigger>
|
||||
<TabsTrigger value="indexes">{{ t("structureEditor.indexes") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.columns" value="columns">{{ t("structureEditor.columns") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.indexes" value="indexes">{{ t("structureEditor.indexes") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.foreignKeys" value="foreignKeys">{{ t("structureEditor.foreignKeys") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.triggers" value="triggers">{{ t("structureEditor.triggers") }}</TabsTrigger>
|
||||
<TabsTrigger value="ddl" v-if="!isCreateMode">DDL</TabsTrigger>
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.ddl && !isCreateMode" value="ddl">DDL</TabsTrigger>
|
||||
</TabsList>
|
||||
<div class="flex shrink-0 items-center gap-1.5">
|
||||
<div class="flex items-center gap-1.5">
|
||||
|
|
@ -766,7 +780,7 @@ watch(activeTab, (tab) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<TabsContent value="columns" class="m-0 min-h-0 flex-1 overflow-auto p-0">
|
||||
<TabsContent v-if="tableMetadataCapabilities.columns" value="columns" class="m-0 min-h-0 flex-1 overflow-auto p-0">
|
||||
<table class="border-separate border-spacing-0 text-[length:var(--structure-font-size)] leading-[var(--structure-line-height)]" :style="{ minWidth: visibleColWidths.reduce((a, w) => a + w, 0) + 'px' }">
|
||||
<thead class="sticky top-0 z-10 bg-background">
|
||||
<tr>
|
||||
|
|
@ -984,7 +998,7 @@ watch(activeTab, (tab) => {
|
|||
</table>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="indexes" class="m-0 min-h-0 flex-1 overflow-auto p-0">
|
||||
<TabsContent v-if="tableMetadataCapabilities.indexes" value="indexes" class="m-0 min-h-0 flex-1 overflow-auto p-0">
|
||||
<table class="border-separate border-spacing-0 text-[length:var(--structure-font-size)] leading-[var(--structure-line-height)]" :style="{ minWidth: indexColWidths.reduce((a, w) => a + w, 0) + 'px' }">
|
||||
<thead class="sticky top-0 z-10 bg-background">
|
||||
<tr>
|
||||
|
|
@ -1108,7 +1122,7 @@ watch(activeTab, (tab) => {
|
|||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="ddl" class="m-0 min-h-0 flex-1 overflow-auto p-[var(--structure-cell-px)]">
|
||||
<TabsContent v-if="tableMetadataCapabilities.ddl" value="ddl" class="m-0 min-h-0 flex-1 overflow-auto p-[var(--structure-cell-px)]">
|
||||
<div v-if="ddlLoading" class="flex items-center justify-center gap-2 py-10 text-muted-foreground">
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{{ t("common.loading") }}
|
||||
|
|
|
|||
|
|
@ -266,6 +266,9 @@ export const mongoUpdateDocuments = forward("mongoUpdateDocuments");
|
|||
export const mongoDeleteDocument = forward("mongoDeleteDocument");
|
||||
export const mongoDeleteDocuments = forward("mongoDeleteDocuments");
|
||||
|
||||
// Elasticsearch
|
||||
export const elasticsearchListIndices = forward("elasticsearchListIndices");
|
||||
|
||||
// History
|
||||
export const saveHistory = forward("saveHistory");
|
||||
export const loadHistory = forward("loadHistory");
|
||||
|
|
|
|||
|
|
@ -1251,6 +1251,10 @@ export async function mongoListCollections(connectionId: string, database: strin
|
|||
return post("/api/mongo/list-collections", { connectionId, database });
|
||||
}
|
||||
|
||||
export async function elasticsearchListIndices(connectionId: string): Promise<string[]> {
|
||||
return mongoListCollections(connectionId, "default");
|
||||
}
|
||||
|
||||
export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string): Promise<MongoDocumentResult> {
|
||||
return post("/api/mongo/find-documents", { connectionId, database, collection, skip, limit, filter, sort });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,9 @@ function schemaMatches(node: TreeNode, schema: string | undefined): boolean {
|
|||
|
||||
export function matchesTarget(node: TreeNode, target: ActiveTabSidebarTarget): boolean {
|
||||
if (target.type === "mongo-collection") {
|
||||
if (node.type === "elasticsearch-index") {
|
||||
return node.connectionId === target.connectionId && node.label === target.collectionName;
|
||||
}
|
||||
return node.type === "mongo-collection" && node.connectionId === target.connectionId && node.database === target.database && node.label === target.collectionName;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { TreeNodeType } from "@/types/database";
|
||||
|
||||
const leafTypes: Set<TreeNodeType> = new Set(["column", "index", "fkey", "trigger", "procedure", "function", "package", "package-body", "object-browser", "redis-db", "mongo-collection", "user-admin", "saved-sql-file"]);
|
||||
const leafTypes: Set<TreeNodeType> = new Set(["column", "index", "fkey", "trigger", "procedure", "function", "package", "package-body", "object-browser", "redis-db", "mongo-collection", "elasticsearch-index", "user-admin", "saved-sql-file"]);
|
||||
|
||||
const fullWidthLabelTypes: Set<TreeNodeType> = new Set(["table", "view", "mongo-collection"]);
|
||||
const fullWidthLabelTypes: Set<TreeNodeType> = new Set(["table", "view", "mongo-collection", "elasticsearch-index"]);
|
||||
|
||||
const emptyContainerTypes: Set<TreeNodeType> = new Set(["saved-sql-root", "saved-sql-folder"]);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ const capabilityByType: Partial<Record<DatabaseType, Partial<TableMetadataCapabi
|
|||
foreignKeys: false,
|
||||
triggers: false,
|
||||
},
|
||||
elasticsearch: {
|
||||
indexes: false,
|
||||
foreignKeys: false,
|
||||
triggers: false,
|
||||
ddl: false,
|
||||
},
|
||||
influxdb: {
|
||||
indexes: false,
|
||||
foreignKeys: false,
|
||||
|
|
|
|||
|
|
@ -1150,6 +1150,10 @@ export async function mongoListCollections(connectionId: string, database: strin
|
|||
return invoke("mongo_list_collections", { connectionId, database });
|
||||
}
|
||||
|
||||
export async function elasticsearchListIndices(connectionId: string): Promise<string[]> {
|
||||
return mongoListCollections(connectionId, "default");
|
||||
}
|
||||
|
||||
export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string): Promise<MongoDocumentResult> {
|
||||
return invoke("mongo_find_documents", { connectionId, database, collection, skip, limit, filter, sort });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export type SidebarSelectionCopyAction = "copy-name" | "none";
|
|||
export type SidebarActivation = "single" | "double";
|
||||
|
||||
const dataNodeTypes = new Set<TreeNodeType>(["table", "view"]);
|
||||
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mongo-collection", "user-admin"]);
|
||||
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mongo-collection", "elasticsearch-index", "user-admin"]);
|
||||
const objectBrowserNodeTypes = new Set<TreeNodeType>(["database", "schema", "object-browser"]);
|
||||
const sourceNodeTypes = new Set<TreeNodeType>(["procedure", "function", "sequence", "package", "package-body"]);
|
||||
const savedSqlNodeTypes = new Set<TreeNodeType>(["saved-sql-file"]);
|
||||
|
|
|
|||
|
|
@ -522,16 +522,16 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
for (const key of Object.keys(elasticsearchCompletionIndicesCache.value)) {
|
||||
if (key === exactCacheKey || key.startsWith(cachePrefix)) delete elasticsearchCompletionIndicesCache.value[key];
|
||||
}
|
||||
for (const key of [...completionTableIndex.keys()]) {
|
||||
for (const key of completionTableIndex.keys()) {
|
||||
if (key.startsWith(cachePrefix)) completionTableIndex.delete(key);
|
||||
}
|
||||
for (const key of [...completionObjectIndex.keys()]) {
|
||||
for (const key of completionObjectIndex.keys()) {
|
||||
if (key.startsWith(cachePrefix)) completionObjectIndex.delete(key);
|
||||
}
|
||||
for (const key of [...completionColumnIndex.keys()]) {
|
||||
for (const key of completionColumnIndex.keys()) {
|
||||
if (key.startsWith(cachePrefix)) completionColumnIndex.delete(key);
|
||||
}
|
||||
for (const key of [...completionInFlight.keys()]) {
|
||||
for (const key of completionInFlight.keys()) {
|
||||
if (key.startsWith(cachePrefix)) completionInFlight.delete(key);
|
||||
}
|
||||
}
|
||||
|
|
@ -645,6 +645,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
await loadEtcdRoot(connectionId);
|
||||
} else if (config.db_type === "mongodb") {
|
||||
await loadMongoDatabases(connectionId);
|
||||
} else if (config.db_type === "elasticsearch") {
|
||||
await loadElasticsearchIndices(connectionId);
|
||||
} else {
|
||||
await loadDatabases(connectionId, { force: true });
|
||||
}
|
||||
|
|
@ -975,6 +977,38 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadElasticsearchIndices(connectionId: string) {
|
||||
const node = findNode(treeNodes.value, connectionId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
await ensureConnected(connectionId);
|
||||
const indices = await api.elasticsearchListIndices(connectionId);
|
||||
setChildren(
|
||||
node,
|
||||
withSavedSqlRoot(
|
||||
connectionId,
|
||||
sortSidebarNames(indices).map((index) => ({
|
||||
id: `${connectionId}:__es_index:${index}`,
|
||||
label: index,
|
||||
type: "elasticsearch-index" as const,
|
||||
connectionId,
|
||||
database: "default",
|
||||
isExpanded: false,
|
||||
})),
|
||||
node,
|
||||
),
|
||||
);
|
||||
node.isExpanded = true;
|
||||
} catch (e) {
|
||||
recordMetadataLoadError(connectionId, e);
|
||||
throw e;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMongoCollections(connectionId: string, database: string) {
|
||||
const nodeId = `${connectionId}:${database}`;
|
||||
const node = findNode(treeNodes.value, nodeId);
|
||||
|
|
@ -1408,8 +1442,10 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
await loadRedisDatabases(node.connectionId);
|
||||
} else if (config?.db_type === "etcd") {
|
||||
await loadEtcdRoot(node.connectionId);
|
||||
} else if (config?.db_type === "mongodb" || config?.db_type === "elasticsearch") {
|
||||
} else if (config?.db_type === "mongodb") {
|
||||
await loadMongoDatabases(node.connectionId);
|
||||
} else if (config?.db_type === "elasticsearch") {
|
||||
await loadElasticsearchIndices(node.connectionId);
|
||||
} else {
|
||||
await loadDatabases(node.connectionId, options);
|
||||
}
|
||||
|
|
@ -1700,7 +1736,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
return elasticsearchCompletionIndicesCache.value[cacheKey];
|
||||
}
|
||||
await ensureConnected(connectionId);
|
||||
const indices = await api.mongoListCollections(connectionId, database);
|
||||
const indices = await api.elasticsearchListIndices(connectionId);
|
||||
elasticsearchCompletionIndicesCache.value[cacheKey] = indices;
|
||||
evictOldestCacheEntries(elasticsearchCompletionIndicesCache.value, COMPLETION_CACHE_MAX);
|
||||
return elasticsearchCompletionIndicesCache.value[cacheKey];
|
||||
|
|
@ -2435,6 +2471,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
loadEtcdRoot,
|
||||
updateRedisDbKeyStats,
|
||||
loadMongoDatabases,
|
||||
loadElasticsearchIndices,
|
||||
loadMongoCollections,
|
||||
loadSchemas,
|
||||
loadSqlServerDatabaseObjects,
|
||||
|
|
|
|||
|
|
@ -325,7 +325,8 @@ export type TreeNodeType =
|
|||
| "redis-db"
|
||||
| "etcd-root"
|
||||
| "mongo-db"
|
||||
| "mongo-collection";
|
||||
| "mongo-collection"
|
||||
| "elasticsearch-index";
|
||||
|
||||
export interface ConnectionGroup {
|
||||
id: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue