feat(nacos): enhance configuration management

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-07-26 11:20:05 +08:00
1 parent 406b7acdbb
commit 1ebc3fd12b
52 files changed
+7139 -263

No files matched your search

@@ -0,0 +1,94 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { shallowRef } from "vue";
import type { TreeNode } from "@/types/database";
import { createNacosNamespaceDesc, createNacosNamespaceId, createNacosNamespaceLoading, createNacosNamespaceName, showCreateNacosNamespaceDialog, sidebarFormTarget } from "@/components/sidebar/sidebarTreeDialogState";
const mocks = vi.hoisted(() => ({
toast: vi.fn(),
nacosCreateNamespace: vi.fn(),
notifyNacosNamespacesChanged: vi.fn(),
loadNacosNamespaces: vi.fn(),
}));
vi.mock("vue-i18n", () => ({
useI18n: () => ({
t: (key: string, params?: Record<string, unknown>) => (params ? `${key}:${JSON.stringify(params)}` : key),
}),
}));
vi.mock("@/composables/useToast", () => ({
useToast: () => ({ toast: mocks.toast }),
}));
vi.mock("@/lib/backend/api", () => ({
nacosCreateNamespace: (...args: unknown[]) => mocks.nacosCreateNamespace(...args),
}));
vi.mock("@/lib/nacos/nacosNamespaceCache", () => ({
notifyNacosNamespacesChanged: (...args: unknown[]) => mocks.notifyNacosNamespacesChanged(...args),
}));
import { useSidebarDatabaseSpecificMutationRuntime } from "@/composables/useSidebarDatabaseSpecificMutationRuntime";
function connectionNode(): TreeNode {
return {
id: "conn-1",
label: "Nacos",
type: "connection",
connectionId: "conn-1",
isExpanded: false,
};
}
describe("Nacos namespace creation cache invalidation", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.nacosCreateNamespace.mockResolvedValue(undefined);
mocks.loadNacosNamespaces.mockResolvedValue(undefined);
sidebarFormTarget.value = connectionNode();
createNacosNamespaceId.value = "new-space";
createNacosNamespaceName.value = "New Space";
createNacosNamespaceDesc.value = "Created during sync";
createNacosNamespaceLoading.value = false;
showCreateNacosNamespaceDialog.value = true;
});
it("notifies open views for the same connection immediately after creation succeeds", async () => {
const { confirmCreateNacosNamespace } = useSidebarDatabaseSpecificMutationRuntime({
activeNode: shallowRef(connectionNode()),
connectionStore: {
treeNodes: [],
loadNacosNamespaces: mocks.loadNacosNamespaces,
} as any,
});
await confirmCreateNacosNamespace();
expect(mocks.nacosCreateNamespace).toHaveBeenCalledWith("conn-1", {
namespaceId: "new-space",
namespaceName: "New Space",
namespaceDesc: "Created during sync",
});
expect(mocks.notifyNacosNamespacesChanged).toHaveBeenCalledWith("conn-1");
expect(mocks.notifyNacosNamespacesChanged.mock.invocationCallOrder[0]).toBeLessThan(mocks.loadNacosNamespaces.mock.invocationCallOrder[0]);
expect(showCreateNacosNamespaceDialog.value).toBe(false);
});
it("does not invalidate namespace caches when creation fails", async () => {
mocks.nacosCreateNamespace.mockRejectedValueOnce(new Error("create failed"));
const { confirmCreateNacosNamespace } = useSidebarDatabaseSpecificMutationRuntime({
activeNode: shallowRef(connectionNode()),
connectionStore: {
treeNodes: [],
loadNacosNamespaces: mocks.loadNacosNamespaces,
} as any,
});
await confirmCreateNacosNamespace();
expect(mocks.notifyNacosNamespacesChanged).not.toHaveBeenCalled();
expect(mocks.loadNacosNamespaces).not.toHaveBeenCalled();
expect(showCreateNacosNamespaceDialog.value).toBe(true);
expect(createNacosNamespaceLoading.value).toBe(false);
});
});
@@ -1,9 +1,10 @@
import { computed, ref } from "vue";
import { computed, ref, type Ref } from "vue";
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
export const NACOS_CONFIG_LIST_COLUMN_WIDTHS_STORAGE_KEY = "dbx-nacos-config-list-column-widths";
export const DEFAULT_NACOS_CONFIG_LIST_COLUMN_WIDTHS = [280, 180, 180, 96] as const;
const MIN_NACOS_CONFIG_LIST_COLUMN_WIDTHS = [180, 120, 120, 72] as const;
export const NACOS_CONFIG_LIST_HORIZONTAL_PADDING = 24;
const MIN_NACOS_CONFIG_LIST_COLUMN_WIDTHS = [140, 96, 96, 72] as const;
function minWidthForColumn(index: number) {
return MIN_NACOS_CONFIG_LIST_COLUMN_WIDTHS[index] ?? MIN_NACOS_CONFIG_LIST_COLUMN_WIDTHS[MIN_NACOS_CONFIG_LIST_COLUMN_WIDTHS.length - 1];
@@ -37,29 +38,63 @@ function gridTemplateColumnsForWidths(widths: readonly number[]) {
return widths.map((width) => `${width}px`).join(" ");
}
export function useNacosConfigListColumnResize() {
const columnWidths = ref(loadNacosConfigListColumnWidths());
export function fitNacosConfigListColumnWidths(widths: readonly number[], availableWidth: number): number[] {
const normalized = normalizeNacosConfigListColumnWidths([...widths]) ?? [...DEFAULT_NACOS_CONFIG_LIST_COLUMN_WIDTHS];
const targetWidth = Math.floor(availableWidth) - NACOS_CONFIG_LIST_HORIZONTAL_PADDING;
if (targetWidth <= 0) return normalized;
const minimums = normalized.map((_, index) => minWidthForColumn(index));
const minimumTotal = minimums.reduce((sum, width) => sum + width, 0);
if (targetWidth <= minimumTotal) return minimums;
const preferredExtras = normalized.map((width, index) => Math.max(0, width - minimums[index]!));
const preferredExtraTotal = preferredExtras.reduce((sum, width) => sum + width, 0);
const availableExtra = targetWidth - minimumTotal;
const fitted = minimums.map((minimum, index) => {
const weight = preferredExtraTotal > 0 ? preferredExtras[index]! / preferredExtraTotal : 1 / minimums.length;
return minimum + Math.floor(availableExtra * weight);
});
fitted[fitted.length - 1]! += targetWidth - fitted.reduce((sum, width) => sum + width, 0);
return fitted;
}
export function useNacosConfigListColumnResize(availableWidth?: Readonly<Ref<number>>) {
const preferredColumnWidths = ref(loadNacosConfigListColumnWidths());
const resizingColumnIndex = ref<number | null>(null);
const columnWidths = computed(() => {
const width = availableWidth?.value ?? 0;
return width > 0 ? fitNacosConfigListColumnWidths(preferredColumnWidths.value, width) : preferredColumnWidths.value;
});
const gridTemplateColumns = computed(() => gridTemplateColumnsForWidths(columnWidths.value));
const totalWidth = computed(() => columnWidths.value.reduce((sum, width) => sum + width, 0));
const minWidth = computed(() => `${totalWidth.value}px`);
const minWidth = computed(() => `${totalWidth.value + NACOS_CONFIG_LIST_HORIZONTAL_PADDING}px`);
function onResizeStart(columnIndex: number, event: MouseEvent) {
if (columnIndex < 0 || columnIndex >= columnWidths.value.length) return;
if (columnIndex < 0 || columnIndex >= columnWidths.value.length - 1) return;
event.preventDefault();
const startX = event.clientX;
const startWidth = columnWidths.value[columnIndex] ?? minWidthForColumn(columnIndex);
const startWidths = [...columnWidths.value];
const startWidth = startWidths[columnIndex] ?? minWidthForColumn(columnIndex);
const nextStartWidth = startWidths[columnIndex + 1] ?? minWidthForColumn(columnIndex + 1);
const pairWidth = startWidth + nextStartWidth;
resizingColumnIndex.value = columnIndex;
const onMove = (moveEvent: MouseEvent) => {
const delta = moveEvent.clientX - startX;
columnWidths.value[columnIndex] = Math.max(minWidthForColumn(columnIndex), startWidth + delta);
const minimum = minWidthForColumn(columnIndex);
const nextMinimum = minWidthForColumn(columnIndex + 1);
const width = Math.min(pairWidth - nextMinimum, Math.max(minimum, startWidth + delta));
const nextWidths = [...startWidths];
nextWidths[columnIndex] = width;
nextWidths[columnIndex + 1] = pairWidth - width;
preferredColumnWidths.value = nextWidths;
};
const onUp = (moveEvent: MouseEvent) => {
onMove(moveEvent);
resizingColumnIndex.value = null;
saveNacosConfigListColumnWidths(columnWidths.value);
preferredColumnWidths.value = [...columnWidths.value];
saveNacosConfigListColumnWidths(preferredColumnWidths.value);
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
};
@@ -5,6 +5,7 @@ import { useConnectionStore } from "@/stores/connectionStore";
import type { TreeNode } from "@/types/database";
import * as api from "@/lib/backend/api";
import { translateBackendError } from "@/i18n/backend-errors";
import { notifyNacosNamespacesChanged } from "@/lib/nacos/nacosNamespaceCache";
import { findSidebarActionTarget } from "@/lib/sidebar/sidebarActionTarget";
import { isRenamableMongoCollection, mongoCollectionKindFromNode, mongoDropAllIndexesPreview, mongoDropCollectionPreview, mongoDropDatabasePreview, mongoDropIndexPreview, mongoRenameCollectionPreview } from "@/lib/sidebar/mongoCollectionMutation";
import { runMongoSidebarMutation } from "@/lib/sidebar/runMongoSidebarMutation";
@@ -170,6 +171,7 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba
namespaceName,
namespaceDesc: createNacosNamespaceDesc.value.trim() || namespaceName,
});
notifyNacosNamespacesChanged(node.connectionId);
showCreateNacosNamespaceDialog.value = false;
await connectionStore.loadNacosNamespaces(node.connectionId, { force: true });
const liveNode = findSidebarActionTarget(connectionStore.treeNodes, node);