feat: add connection delete confirmation and improve disconnect behavior

This commit is contained in:
t8y2
2026-04-29 22:01:54 +08:00
parent e05075bb24
commit 51225d06e2
5 changed files with 52 additions and 25 deletions
-21
View File
@@ -616,27 +616,6 @@ onUnmounted(() => {
<!-- Empty State -->
<div v-else class="flex-1 overflow-auto bg-background">
<div class="mx-auto flex min-h-full w-full max-w-5xl flex-col justify-center gap-6 px-8 py-10">
<div class="flex items-start justify-between gap-6">
<div class="space-y-2">
<div class="flex items-center gap-2 text-sm font-medium text-muted-foreground">
<DatabaseZap class="h-4 w-4" />
{{ t('app.name') }}
</div>
<h2 class="text-2xl font-semibold tracking-normal">{{ t('welcome.title') }}</h2>
<p class="max-w-xl text-sm leading-6 text-muted-foreground">
{{ t('welcome.subtitle') }}
</p>
</div>
<div class="flex shrink-0 gap-2">
<Button variant="outline" @click="showConnectionDialog = true">
<Plus class="h-4 w-4" /> {{ t('toolbar.newConnection') }}
</Button>
<Button :disabled="!connectionStore.activeConnectionId" @click="newQuery">
<FilePlus2 class="h-4 w-4" /> {{ t('toolbar.newQuery') }}
</Button>
</div>
</div>
<div class="grid grid-cols-3 gap-3">
<div class="rounded-lg border bg-muted/20 px-4 py-3">
<div class="flex items-center gap-2 text-xs text-muted-foreground">
+40 -2
View File
@@ -4,7 +4,7 @@ import { useI18n } from "vue-i18n";
import {
Database, Table, Columns3, Eye, ChevronRight, ChevronDown,
Loader2, FolderOpen, Trash2, TerminalSquare, RefreshCw,
Copy, TableProperties, Key, Link, Zap, ListTree, Pencil,
Copy, TableProperties, Key, Link, Zap, ListTree, Pencil, Plug, Unplug,
} from "lucide-vue-next";
import {
ContextMenu, ContextMenuContent, ContextMenuItem,
@@ -15,6 +15,10 @@ import { useQueryStore } from "@/stores/queryStore";
import type { TreeNode, TreeNodeType } from "@/types/database";
import * as api from "@/lib/tauri";
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
const { t } = useI18n();
const connectionStore = useConnectionStore();
@@ -175,7 +179,13 @@ async function refresh() {
await toggle();
}
const showDeleteConfirm = ref(false);
function deleteConnection() {
showDeleteConfirm.value = true;
}
function confirmDelete() {
const node = props.node;
if (node.connectionId) {
connectionStore.disconnect(node.connectionId);
@@ -193,8 +203,19 @@ function editConnection() {
}
}
function disconnectConnection() {
if (props.node.connectionId) {
connectionStore.disconnect(props.node.connectionId);
props.node.isExpanded = false;
props.node.children = [];
}
}
const canExpand = !leafTypes.has(props.node.type);
const paddingLeft = `${props.depth * 16 + 8}px`;
const isConnected = computed(() =>
props.node.type === "connection" && !!props.node.connectionId && connectionStore.connectedIds.has(props.node.connectionId)
);
const CHILDREN_PAGE_SIZE = 100;
const displayLimit = ref(CHILDREN_PAGE_SIZE);
@@ -235,6 +256,7 @@ function showMore() {
<DatabaseIcon v-if="node.type === 'connection'" :db-type="connectionStore.getConfig(node.connectionId || '')?.db_type || 'postgres'" class="w-3.5 h-3.5 shrink-0" />
<component v-else :is="getIconInfo(node)?.icon || Database" class="w-3.5 h-3.5 shrink-0" :class="getIconInfo(node)?.colorClass" />
<span class="truncate">{{ isGroupLabel(node) ? t(node.label) : node.label }}</span>
<span v-if="node.type === 'connection' && node.connectionId && connectionStore.connectedIds.has(node.connectionId)" class="w-1.5 h-1.5 rounded-full bg-green-500 shrink-0" />
</div>
<template v-if="node.isExpanded && node.children">
<TreeItem v-for="child in visibleChildren" :key="child.id" :node="child" :depth="depth + 1" />
@@ -252,9 +274,12 @@ function showMore() {
<ContextMenuContent class="w-48">
<template v-if="node.type === 'connection'">
<ContextMenuItem @click="toggle">
<ContextMenuItem v-if="!isConnected" @click="toggle">
<Plug class="w-3.5 h-3.5 mr-2" /> {{ t('contextMenu.openConnection') }}
</ContextMenuItem>
<ContextMenuItem v-else @click="disconnectConnection">
<Unplug class="w-3.5 h-3.5 mr-2" /> {{ t('contextMenu.closeConnection') }}
</ContextMenuItem>
<ContextMenuItem @click="newQuery">
<TerminalSquare class="w-3.5 h-3.5 mr-2" /> {{ t('contextMenu.newQuery') }}
</ContextMenuItem>
@@ -305,4 +330,17 @@ function showMore() {
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<Dialog v-model:open="showDeleteConfirm">
<DialogContent class="sm:max-w-[400px]">
<DialogHeader>
<DialogTitle>{{ t('contextMenu.confirmDeleteTitle') }}</DialogTitle>
</DialogHeader>
<p class="text-sm text-muted-foreground">{{ t('contextMenu.confirmDeleteMessage', { name: node.label }) }}</p>
<DialogFooter>
<Button variant="outline" @click="showDeleteConfirm = false">{{ t('dangerDialog.cancel') }}</Button>
<Button variant="destructive" @click="showDeleteConfirm = false; confirmDelete()">{{ t('contextMenu.deleteConnection') }}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>
+5 -1
View File
@@ -72,7 +72,7 @@ export default {
},
welcome: {
title: "Database Workspace",
subtitle: "Choose a connection on the left to browse schema, or open a query tab directly. DBX supports multiple databases, query history, grid editing, and an AI SQL assistant.",
subtitle: "Choose a connection on the left to browse schema, or open a query tab directly.",
connections: "Connections",
connected: "Connected",
databaseTypes: "Database Types",
@@ -81,6 +81,8 @@ export default {
shortcuts: "Shortcuts",
shortcutsHint: "Start the next action from here.",
tip: "You can also expand connections on the left and right-click databases or tables for more actions.",
tipSidebar: "Click a table on the left to view data",
tipExecute: "to execute query",
},
common: {
language: "Language",
@@ -124,6 +126,8 @@ export default {
openConnection: "Open Connection",
closeConnection: "Close Connection",
deleteConnection: "Delete Connection",
confirmDeleteTitle: "Delete Connection",
confirmDeleteMessage: "Are you sure you want to delete \"{name}\"? This cannot be undone.",
editConnection: "Edit Connection",
newQuery: "New Query",
viewData: "View Data",
+2
View File
@@ -124,6 +124,8 @@ export default {
openConnection: "打开连接",
closeConnection: "关闭连接",
deleteConnection: "删除连接",
confirmDeleteTitle: "删除连接",
confirmDeleteMessage: "确定要删除「{name}」吗?此操作不可撤销。",
editConnection: "编辑连接",
newQuery: "新建查询",
viewData: "查看数据",
+5 -1
View File
@@ -74,7 +74,11 @@ export const useConnectionStore = defineStore("connection", () => {
async function disconnect(connectionId: string) {
await api.disconnectDb(connectionId);
connectedIds.value.delete(connectionId);
treeNodes.value = treeNodes.value.filter((n) => n.id !== connectionId);
const node = treeNodes.value.find((n) => n.connectionId === connectionId);
if (node) {
node.isExpanded = false;
node.children = [];
}
if (activeConnectionId.value === connectionId) {
activeConnectionId.value = null;
}