feat(mongodb): improve sidebar index management

This commit is contained in:
onenewcode authored and GitHub committed 2026-08-02 22:45:33 +08:00
1 parent 22055e081f
commit 8e7b74d0ee
56 files changed
+3558 -246

No files matched your search

+87 -33
View File
@@ -221,6 +221,10 @@ import { supportsTableStructureEditing } from "@/lib/database/databaseCapabiliti
import { rememberDataGridConditionHistory } from "@/lib/dataGrid/dataGridConditionHistory";
import { restoreDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { mongoCollectionSupportsIndexes, supportsMongoIndexMutations } from "@/lib/mongo/mongoCapabilities";
import { refreshLoadedMongoIndexes } from "@/lib/mongo/mongoIndexMetadata";
import { isProtectedMongoIndex, mongoDropAllIndexesPreview, mongoDropIndexFailureCount, mongoDropIndexPreview } from "@/lib/sidebar/mongoCollectionMutation";
import { runMongoMutation } from "@/lib/sidebar/runMongoSidebarMutation";
import { dataGridConditionColumnOptions, dataGridConditionIdentifierQuote } from "@/lib/dataGrid/dataGridConditionCompletion";
import { isMacOS } from "@/lib/backend/platform";
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
@@ -7336,7 +7340,8 @@ function toggleCellDetailPanelLayout() {
const tableMetadataCapabilities = computed(() => getTableMetadataCapabilities(props.databaseType));
const canOpenTableStructureEditor = computed(() => !!props.connectionId && !!props.database && !!props.tableMeta?.tableName && supportsTableStructureEditing(resolvedDatabaseType.value));
const mongoConnectionConfig = resolvedConnectionConfig;
const canManageMongoIndexes = computed(() => resolvedDatabaseType.value === "mongodb" && !!props.connectionId && !!props.database && !!props.tableMeta?.tableName && mongoConnectionConfig.value?.db_type === "mongodb" && mongoConnectionConfig.value?.driver_profile !== "mongodb-legacy");
const canManageMongoIndexes = computed(() => resolvedDatabaseType.value === "mongodb" && !!props.connectionId && !!props.database && !!props.tableMeta?.tableName && supportsMongoIndexMutations(mongoConnectionConfig.value, props.tableMeta?.tableType));
const canShowTableIndexes = computed(() => tableMetadataCapabilities.value.indexes && (resolvedDatabaseType.value !== "mongodb" || mongoCollectionSupportsIndexes(props.tableMeta?.tableType)));
const tableInfoTabs = computed(() => {
const tabs: TableInfoTabItem[] = [];
if (tableMetadataCapabilities.value.ddl) {
@@ -7350,7 +7355,7 @@ const tableInfoTabs = computed(() => {
count: props.tableMeta?.columns.length,
});
}
if (tableMetadataCapabilities.value.indexes) {
if (canShowTableIndexes.value) {
tabs.push({ id: "indexes", label: t("grid.tableInfoIndexes"), icon: KeyRound, count: indexes.value.length });
}
if (tableMetadataCapabilities.value.foreignKeys) {
@@ -7435,6 +7440,19 @@ async function reloadIndexes() {
await fetchIndexes();
}
async function refreshMongoIndexMetadataAfterMutation() {
await reloadIndexes();
const connectionId = props.connectionId;
const database = props.database;
const collection = props.tableMeta?.tableName;
if (!connectionId || !database || !collection) return;
try {
await refreshLoadedMongoIndexes(connectionStore, { connectionId, database, collection });
} catch (e: any) {
toast(t("contextMenu.mongoIndexRefreshFailed", { message: String(e?.message || e) }), 5000);
}
}
async function fetchForeignKeys() {
const requestIdentity = currentForeignKeyTableIdentity.value;
if (!props.connectionId || !props.tableMeta || !requestIdentity || foreignKeysLoaded.value || foreignKeysLoading.value) return;
@@ -7775,7 +7793,8 @@ const filteredIndexes = computed(() => {
return indexes.value.filter((i) => i.name.toLowerCase().includes(q) || i.columns.some((c) => c.toLowerCase().includes(q)));
});
const droppableMongoIndexes = computed(() => indexes.value.filter((index) => !index.is_primary));
const droppableMongoIndexes = computed(() => indexes.value.filter((index) => !isProtectedMongoIndex(index)));
const dropMongoIndexConfirmMessage = computed(() =>
pendingDropMongoIndex.value
? t("contextMenu.confirmDropMongoIndexMessage", {
@@ -7784,51 +7803,86 @@ const dropMongoIndexConfirmMessage = computed(() =>
})
: "",
);
const dropMongoIndexPreview = computed(() => (pendingDropMongoIndex.value ? mongoDropIndexPreview(props.database || "", props.tableMeta?.tableName || "", pendingDropMongoIndex.value.name) : ""));
const dropAllMongoIndexesConfirmMessage = computed(() => t("contextMenu.confirmDropMongoAllIndexesMessage", { name: props.tableMeta?.tableName || "" }));
const dropAllMongoIndexesConfirmDetails = computed(() => t("contextMenu.confirmDropMongoAllIndexesDetails"));
const dropMongoIndexPreview = computed(() => (pendingDropMongoIndex.value ? `db.getCollection(${JSON.stringify(props.tableMeta?.tableName || "")}).dropIndex(${JSON.stringify(pendingDropMongoIndex.value.name)})` : ""));
const dropAllMongoIndexesPreview = computed(() => `db.getCollection(${JSON.stringify(props.tableMeta?.tableName || "")}).dropIndexes()`);
const dropAllMongoIndexesPreview = computed(() => mongoDropAllIndexesPreview(props.database || "", props.tableMeta?.tableName || ""));
function requestDropMongoIndex(index: IndexInfo) {
if (!canManageMongoIndexes.value || isProtectedMongoIndex(index)) return;
pendingDropMongoIndex.value = index;
showDropMongoIndexConfirm.value = true;
}
function requestDropAllMongoIndexes() {
if (!canManageMongoIndexes.value || droppableMongoIndexes.value.length === 0) return;
showDropAllMongoIndexesConfirm.value = true;
}
async function confirmDropMongoIndex() {
if (!props.connectionId || !props.database || !props.tableMeta?.tableName || !pendingDropMongoIndex.value || dropMongoIndexLoading.value) return;
dropMongoIndexLoading.value = true;
try {
await connectionStore.ensureConnected(props.connectionId);
await api.mongoDropIndexes(props.connectionId, props.database, props.tableMeta.tableName, JSON.stringify(pendingDropMongoIndex.value.name), true);
toast(t("contextMenu.dropTableChildObjectSuccess", { name: pendingDropMongoIndex.value.name }), 3000);
showDropMongoIndexConfirm.value = false;
pendingDropMongoIndex.value = null;
await reloadIndexes();
} catch (e: any) {
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
} finally {
dropMongoIndexLoading.value = false;
}
const index = pendingDropMongoIndex.value;
const connectionId = props.connectionId;
const database = props.database;
const tableName = props.tableMeta?.tableName;
if (!connectionId || !database || !tableName || !index || !canManageMongoIndexes.value || isProtectedMongoIndex(index) || dropMongoIndexLoading.value) return;
await runMongoMutation({
connection: connectionStore.getConfig(connectionId),
database,
reviewText: dropMongoIndexPreview.value,
source: t("production.sourceDataGrid"),
loading: dropMongoIndexLoading,
beforeExecute: () => connectionStore.ensureConnected(connectionId),
execute: async () => {
try {
return await api.mongoDropIndexes(connectionId, database, tableName, JSON.stringify(index.name), true);
} finally {
await refreshMongoIndexMetadataAfterMutation();
}
},
onSuccess: (result) => {
const failed = mongoDropIndexFailureCount(result);
if (failed > 0) {
toast(t("contextMenu.dropIndexesPartialFailure", { success: result.dropped_names.length, failed }), 5000);
} else {
toast(t("contextMenu.dropTableChildObjectSuccess", { name: index.name }), 3000);
}
showDropMongoIndexConfirm.value = false;
pendingDropMongoIndex.value = null;
},
onError: (e: any) => toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000),
});
}
async function confirmDropAllMongoIndexes() {
if (!props.connectionId || !props.database || !props.tableMeta?.tableName || dropAllMongoIndexesLoading.value) return;
dropAllMongoIndexesLoading.value = true;
try {
await connectionStore.ensureConnected(props.connectionId);
const result = await api.mongoDropIndexes(props.connectionId, props.database, props.tableMeta.tableName, undefined, false);
toast(t("contextMenu.dropAllIndexesSuccess", { count: result.dropped_names.length, name: props.tableMeta.tableName }), 3000);
showDropAllMongoIndexesConfirm.value = false;
await reloadIndexes();
} catch (e: any) {
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
} finally {
dropAllMongoIndexesLoading.value = false;
}
const connectionId = props.connectionId;
const database = props.database;
const tableName = props.tableMeta?.tableName;
if (!connectionId || !database || !tableName || !canManageMongoIndexes.value || dropAllMongoIndexesLoading.value) return;
await runMongoMutation({
connection: connectionStore.getConfig(connectionId),
database,
reviewText: dropAllMongoIndexesPreview.value,
source: t("production.sourceDataGrid"),
loading: dropAllMongoIndexesLoading,
beforeExecute: () => connectionStore.ensureConnected(connectionId),
execute: async () => {
try {
return await api.mongoDropIndexes(connectionId, database, tableName, undefined, false);
} finally {
await refreshMongoIndexMetadataAfterMutation();
}
},
onSuccess: (result) => {
const failed = mongoDropIndexFailureCount(result);
if (failed > 0) {
toast(t("contextMenu.dropIndexesPartialFailure", { success: result.dropped_names.length, failed }), 5000);
} else {
toast(t("contextMenu.dropAllIndexesSuccess", { count: result.dropped_names.length, name: tableName }), 3000);
}
showDropAllMongoIndexesConfirm.value = false;
},
onError: (e: any) => toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000),
});
}
const filteredForeignKeys = computed(() => {
@@ -9373,7 +9427,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
{{ index.columns.join(", ") }}
</div>
</div>
<Button v-if="canManageMongoIndexes && !index.is_primary" variant="ghost" size="sm" class="h-7 shrink-0 px-2 text-[11px] text-destructive hover:text-destructive" @click="requestDropMongoIndex(index)">
<Button v-if="canManageMongoIndexes && !isProtectedMongoIndex(index)" variant="ghost" size="sm" class="h-7 shrink-0 px-2 text-[11px] text-destructive hover:text-destructive" @click="requestDropMongoIndex(index)">
<Trash2 class="mr-1 h-3 w-3" />
{{ t("contextMenu.dropIndex") }}
</Button>