mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-29 00:09:52 +00:00
feat: add connection delete confirmation and improve disconnect behavior
This commit is contained in:
-21
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -124,6 +124,8 @@ export default {
|
||||
openConnection: "打开连接",
|
||||
closeConnection: "关闭连接",
|
||||
deleteConnection: "删除连接",
|
||||
confirmDeleteTitle: "删除连接",
|
||||
confirmDeleteMessage: "确定要删除「{name}」吗?此操作不可撤销。",
|
||||
editConnection: "编辑连接",
|
||||
newQuery: "新建查询",
|
||||
viewData: "查看数据",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user