mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-30 16:21:39 +00:00
feat: support create/drop database and schema from sidebar context menu
Add context menu actions for creating and dropping databases and schemas directly from the sidebar tree. Supports MySQL, PostgreSQL, SQL Server, ClickHouse, Oracle, and other server-based databases.
This commit is contained in:
@@ -73,6 +73,7 @@ import {
|
||||
FIELD_LINEAGE_SUPPORTED_TYPES,
|
||||
TREE_SCHEMA_TYPES,
|
||||
PG_LIKE_STRUCTURE_TYPES,
|
||||
CREATE_DATABASE_SUPPORTED_TYPES,
|
||||
isSchemaAware,
|
||||
usesFetchFirst,
|
||||
} from "@/lib/databaseCapabilities";
|
||||
@@ -378,6 +379,13 @@ const showTruncateTableConfirm = ref(false);
|
||||
const showDuplicateDialog = ref(false);
|
||||
const duplicateTableName = ref("");
|
||||
|
||||
const showCreateDatabaseDialog = ref(false);
|
||||
const createDatabaseName = ref("");
|
||||
const showDropDatabaseConfirm = ref(false);
|
||||
const showCreateSchemaDialog = ref(false);
|
||||
const createSchemaName = ref("");
|
||||
const showDropSchemaConfirm = ref(false);
|
||||
|
||||
const isTableNotView = computed(() => props.node.type === "table");
|
||||
|
||||
const supportsTruncate = computed(() => {
|
||||
@@ -395,6 +403,26 @@ const canCreateTable = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const canCreateDatabase = computed(() => {
|
||||
const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined;
|
||||
return props.node.type === "connection" && !!config && CREATE_DATABASE_SUPPORTED_TYPES.has(config.db_type);
|
||||
});
|
||||
|
||||
const canDropDatabase = computed(() => {
|
||||
const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined;
|
||||
return props.node.type === "database" && !!config && CREATE_DATABASE_SUPPORTED_TYPES.has(config.db_type);
|
||||
});
|
||||
|
||||
const canCreateSchema = computed(() => {
|
||||
const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined;
|
||||
return props.node.type === "database" && !!config && TREE_SCHEMA_TYPES.has(config.db_type);
|
||||
});
|
||||
|
||||
const canDropSchema = computed(() => {
|
||||
const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined;
|
||||
return props.node.type === "schema" && !!config && TREE_SCHEMA_TYPES.has(config.db_type);
|
||||
});
|
||||
|
||||
function buildDropTableSql(): string {
|
||||
return `DROP TABLE ${qualifiedTableName(props.node.label, props.node.schema)};`;
|
||||
}
|
||||
@@ -463,6 +491,93 @@ async function confirmTruncateTable() {
|
||||
}
|
||||
}
|
||||
|
||||
function buildDropDatabaseSql(): string {
|
||||
return `DROP DATABASE ${quoteIdent(props.node.label)};`;
|
||||
}
|
||||
|
||||
function buildDropSchemaSql(): string {
|
||||
const dbType = currentDatabaseType();
|
||||
const name = quoteIdent(props.node.label);
|
||||
if (dbType === "postgres" || dbType === "gaussdb") return `DROP SCHEMA ${name} CASCADE;`;
|
||||
return `DROP SCHEMA ${name};`;
|
||||
}
|
||||
|
||||
function openCreateDatabaseDialog() {
|
||||
createDatabaseName.value = "";
|
||||
showCreateDatabaseDialog.value = true;
|
||||
}
|
||||
|
||||
async function confirmCreateDatabase() {
|
||||
const node = props.node;
|
||||
const name = createDatabaseName.value.trim();
|
||||
if (!name || !node.connectionId) return;
|
||||
showCreateDatabaseDialog.value = false;
|
||||
try {
|
||||
await connectionStore.ensureConnected(node.connectionId);
|
||||
const sql = `CREATE DATABASE ${quoteIdent(name)};`;
|
||||
await api.executeQuery(node.connectionId, "", sql);
|
||||
toast(t("contextMenu.createDatabaseSuccess", { name }), 3000);
|
||||
await connectionStore.loadDatabases(node.connectionId);
|
||||
} catch (e: any) {
|
||||
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function dropDatabase() {
|
||||
showDropDatabaseConfirm.value = true;
|
||||
}
|
||||
|
||||
async function confirmDropDatabase() {
|
||||
const node = props.node;
|
||||
if (!node.connectionId) return;
|
||||
try {
|
||||
await connectionStore.ensureConnected(node.connectionId);
|
||||
await api.executeQuery(node.connectionId, "", buildDropDatabaseSql());
|
||||
toast(t("contextMenu.dropDatabaseSuccess", { name: node.label }), 3000);
|
||||
await connectionStore.loadDatabases(node.connectionId);
|
||||
} catch (e: any) {
|
||||
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateSchemaDialog() {
|
||||
createSchemaName.value = "";
|
||||
showCreateSchemaDialog.value = true;
|
||||
}
|
||||
|
||||
async function confirmCreateSchema() {
|
||||
const node = props.node;
|
||||
const name = createSchemaName.value.trim();
|
||||
if (!name || !node.connectionId || !node.database) return;
|
||||
showCreateSchemaDialog.value = false;
|
||||
try {
|
||||
await connectionStore.ensureConnected(node.connectionId);
|
||||
const sql = `CREATE SCHEMA ${quoteIdent(name)};`;
|
||||
await api.executeQuery(node.connectionId, node.database, sql);
|
||||
toast(t("contextMenu.createSchemaSuccess", { name }), 3000);
|
||||
await connectionStore.loadSchemas(node.connectionId, node.database);
|
||||
} catch (e: any) {
|
||||
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function dropSchema() {
|
||||
showDropSchemaConfirm.value = true;
|
||||
}
|
||||
|
||||
async function confirmDropSchema() {
|
||||
const node = props.node;
|
||||
if (!node.connectionId || !node.database) return;
|
||||
try {
|
||||
await connectionStore.ensureConnected(node.connectionId);
|
||||
await api.executeQuery(node.connectionId, node.database, buildDropSchemaSql());
|
||||
toast(t("contextMenu.dropSchemaSuccess", { name: node.label }), 3000);
|
||||
await connectionStore.loadSchemas(node.connectionId, node.database);
|
||||
} catch (e: any) {
|
||||
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function duplicateStructure() {
|
||||
duplicateTableName.value = `${props.node.label}_copy`;
|
||||
showDuplicateDialog.value = true;
|
||||
@@ -1163,6 +1278,9 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
|
||||
<ContextMenuItem v-if="canOpenSqlFileExecution" @click="openSqlFileExecution">
|
||||
<FileCode class="w-4 h-4" /> {{ t("sqlFile.title") }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem v-if="canCreateDatabase" @click="openCreateDatabaseDialog">
|
||||
<Plus class="w-4 h-4" /> {{ t("contextMenu.createDatabase") }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuSub v-if="availableGroups.length > 0 || currentGroupId">
|
||||
<ContextMenuSubTrigger>
|
||||
@@ -1228,6 +1346,9 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
|
||||
<ContextMenuItem v-if="canCreateTable" @click="createTable">
|
||||
<Plus class="w-4 h-4" /> {{ t("contextMenu.createTable") }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem v-if="canCreateSchema" @click="openCreateSchemaDialog">
|
||||
<Plus class="w-4 h-4" /> {{ t("contextMenu.createSchema") }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem v-if="canOpenSqlFileExecution" @click="openSqlFileExecution">
|
||||
<FileCode class="w-4 h-4" /> {{ t("sqlFile.title") }}
|
||||
</ContextMenuItem>
|
||||
@@ -1252,6 +1373,15 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
|
||||
<Download v-else class="w-4 h-4" />
|
||||
{{ t("contextMenu.exportDatabase") }}
|
||||
</ContextMenuItem>
|
||||
<template v-if="canDropDatabase || canDropSchema">
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem v-if="canDropDatabase" class="text-destructive" @click="dropDatabase">
|
||||
<Trash2 class="w-4 h-4" /> {{ t("contextMenu.dropDatabase") }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem v-if="canDropSchema" class="text-destructive" @click="dropSchema">
|
||||
<Trash2 class="w-4 h-4" /> {{ t("contextMenu.dropSchema") }}
|
||||
</ContextMenuItem>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-if="node.type === 'redis-db' || node.type === 'mongo-db'">
|
||||
@@ -1438,4 +1568,60 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="showCreateDatabaseDialog">
|
||||
<DialogContent class="sm:max-w-[400px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("contextMenu.createDatabase") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
v-model="createDatabaseName"
|
||||
:placeholder="t('contextMenu.createDatabaseNamePlaceholder')"
|
||||
@keydown.enter.prevent="confirmCreateDatabase"
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="showCreateDatabaseDialog = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
<Button :disabled="!createDatabaseName.trim()" @click="confirmCreateDatabase">{{
|
||||
t("dangerDialog.confirm")
|
||||
}}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<DangerConfirmDialog
|
||||
v-model:open="showDropDatabaseConfirm"
|
||||
:title="t('contextMenu.confirmDropDatabaseTitle')"
|
||||
:message="t('contextMenu.confirmDropDatabaseMessage', { name: node.label })"
|
||||
:sql="buildDropDatabaseSql()"
|
||||
:confirm-label="t('contextMenu.dropDatabase')"
|
||||
@confirm="confirmDropDatabase"
|
||||
/>
|
||||
|
||||
<Dialog v-model:open="showCreateSchemaDialog">
|
||||
<DialogContent class="sm:max-w-[400px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("contextMenu.createSchema") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
v-model="createSchemaName"
|
||||
:placeholder="t('contextMenu.createSchemaNamePlaceholder')"
|
||||
@keydown.enter.prevent="confirmCreateSchema"
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="showCreateSchemaDialog = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
<Button :disabled="!createSchemaName.trim()" @click="confirmCreateSchema">{{
|
||||
t("dangerDialog.confirm")
|
||||
}}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<DangerConfirmDialog
|
||||
v-model:open="showDropSchemaConfirm"
|
||||
:title="t('contextMenu.confirmDropSchemaTitle')"
|
||||
:message="t('contextMenu.confirmDropSchemaMessage', { name: node.label })"
|
||||
:sql="buildDropSchemaSql()"
|
||||
:confirm-label="t('contextMenu.dropSchema')"
|
||||
@confirm="confirmDropSchema"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user