fix(tabs): close connection tabs on disconnect

This commit is contained in:
t8y2 2026-06-02 18:19:28 +08:00
parent 53239d91fb
commit 2ff4a3fd97
4 changed files with 109 additions and 15 deletions

View File

@ -2151,12 +2151,16 @@ function editConnection() {
}
}
function disconnectConnection() {
async function disconnectConnection() {
if (props.node.connectionId) {
connectionStore.disconnect(props.node.connectionId);
props.node.isExpanded = false;
props.node.children = [];
toast(t("connection.disconnected"), 2000);
try {
await connectionStore.disconnect(props.node.connectionId);
props.node.isExpanded = false;
props.node.children = [];
toast(t("connection.disconnected"), 2000);
} catch (e: any) {
toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000);
}
}
}
@ -2165,7 +2169,6 @@ async function closeDatabaseConnection() {
if (node.type !== "database" || !node.connectionId || node.database == null) return;
try {
await connectionStore.closeDatabaseConnection(node.connectionId, node.database);
queryStore.closeDatabaseTabs(node.connectionId, node.database);
toast(t("connection.databaseConnectionClosed", { name: node.label }), 2000);
} catch (e: any) {
toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000);

View File

@ -675,6 +675,8 @@ export const useConnectionStore = defineStore("connection", () => {
async function disconnect(connectionId: string) {
const shouldRemoveOneTimeConnection = getConfig(connectionId)?.one_time === true;
await api.disconnectDb(connectionId);
const { useQueryStore } = await import("@/stores/queryStore");
useQueryStore().closeConnectionTabs(connectionId);
connectedIds.value.delete(connectionId);
const node = findNode(treeNodes.value, connectionId);
if (node) {
@ -693,6 +695,8 @@ export const useConnectionStore = defineStore("connection", () => {
async function closeDatabaseConnection(connectionId: string, database: string) {
await api.closeDatabaseConnection(connectionId, database);
const { useQueryStore } = await import("@/stores/queryStore");
useQueryStore().closeDatabaseTabs(connectionId, database);
const node = findDatabaseTreeNode(treeNodes.value, connectionId, database);
if (node) {
node.isExpanded = false;

View File

@ -313,13 +313,8 @@ export const useQueryStore = defineStore("query", () => {
activeTabId.value = next.activeTabId;
}
function closeDatabaseTabs(connectionId: string, database: string) {
const closingModes = new Set<QueryTab["mode"]>(["data", "objects", "structure", "mongo"]);
const closingIds = new Set(
tabs.value
.filter((tab) => tab.connectionId === connectionId && tab.database === database && closingModes.has(tab.mode))
.map((tab) => tab.id),
);
function closeTabsWhere(predicate: (tab: QueryTab) => boolean) {
const closingIds = new Set(tabs.value.filter((tab) => predicate(tab)).map((tab) => tab.id));
if (closingIds.size === 0) return;
tabs.value
@ -339,6 +334,14 @@ export const useQueryStore = defineStore("query", () => {
}
}
function closeConnectionTabs(connectionId: string) {
closeTabsWhere((tab) => tab.connectionId === connectionId);
}
function closeDatabaseTabs(connectionId: string, database: string) {
closeTabsWhere((tab) => tab.connectionId === connectionId && tab.database === database);
}
function updateSql(id: string, sql: string) {
const tab = tabs.value.find((t) => t.id === id);
if (tab) {
@ -1257,6 +1260,7 @@ export const useQueryStore = defineStore("query", () => {
closeTab,
closeOtherTabs,
closeAllTabs,
closeConnectionTabs,
closeDatabaseTabs,
updateSql,
renameTab,

View File

@ -345,11 +345,11 @@ test("closing database tabs removes browser tabs for that database only", async
assert.deepEqual(
store.tabs.map((tab) => tab.id),
[queryId, otherDbId, otherConnectionId],
[otherDbId, otherConnectionId],
);
assert.equal(store.activeTabId, otherConnectionId);
assert.equal(
store.tabs.some((tab) => [dataId, objectsId, structureId, mongoId].includes(tab.id)),
store.tabs.some((tab) => [dataId, objectsId, structureId, mongoId, queryId].includes(tab.id)),
false,
);
assert.equal(structureTab.result, undefined);
@ -360,6 +360,89 @@ test("closing database tabs removes browser tabs for that database only", async
}
});
test("closing connection tabs removes every tab for that connection only", async () => {
const restoreStorage = installMemoryStorage();
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () => {
return new Response(JSON.stringify(true), { status: 200, headers: { "Content-Type": "application/json" } });
}) as typeof fetch;
try {
setActivePinia(createPinia());
const store = useQueryStore();
const queryId = store.createTab("conn-1", "db", "draft query", "query");
const dataId = store.createTab("conn-1", "db", "users", "data", "public");
const objectsId = store.openObjectBrowser("conn-1", "db", "public");
const otherConnectionId = store.createTab("conn-2", "db", "users", "data", "public");
const queryTab = store.tabs.find((item) => item.id === queryId);
assert.ok(queryTab);
queryTab.result = {
columns: ["payload"],
rows: [["query"]],
affected_rows: 0,
execution_time_ms: 1,
session_id: "session-query",
};
queryTab.resultSessionId = "session-query";
store.activeTabId = queryId;
store.closeConnectionTabs("conn-1");
await new Promise((resolve) => setTimeout(resolve, 0));
assert.deepEqual(
store.tabs.map((tab) => tab.id),
[otherConnectionId],
);
assert.equal(store.activeTabId, otherConnectionId);
assert.equal(
store.tabs.some((tab) => [queryId, dataId, objectsId].includes(tab.id)),
false,
);
assert.equal(queryTab.result, undefined);
assert.equal(queryTab.resultSessionId, undefined);
} finally {
globalThis.fetch = originalFetch;
restoreStorage();
}
});
test("disconnecting a connection closes every tab for that connection", async () => {
const restoreStorage = installMemoryStorage();
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () => {
return new Response(JSON.stringify(true), { status: 200, headers: { "Content-Type": "application/json" } });
}) as typeof fetch;
try {
setActivePinia(createPinia());
const connectionStore = useConnectionStore();
const queryStore = useQueryStore();
connectionStore.addEphemeralConnection(conn("conn-1"));
connectionStore.addEphemeralConnection(conn("conn-2"));
const queryId = queryStore.createTab("conn-1", "db", "draft query", "query");
const dataId = queryStore.createTab("conn-1", "db", "users", "data", "public");
const otherConnectionId = queryStore.createTab("conn-2", "db", "users", "data", "public");
queryStore.activeTabId = dataId;
await connectionStore.disconnect("conn-1");
await new Promise((resolve) => setTimeout(resolve, 0));
assert.deepEqual(
queryStore.tabs.map((tab) => tab.id),
[otherConnectionId],
);
assert.equal(queryStore.activeTabId, otherConnectionId);
assert.equal(
queryStore.tabs.some((tab) => [queryId, dataId].includes(tab.id)),
false,
);
} finally {
globalThis.fetch = originalFetch;
restoreStorage();
}
});
test("starting a new query clears the previous result payload immediately", async () => {
const restoreStorage = installMemoryStorage();
setActivePinia(createPinia());