mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-29 08:11:06 +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>
|
||||
|
||||
@@ -451,6 +451,22 @@ export default {
|
||||
tableOperationFailed: "Operation failed: {message}",
|
||||
duplicateNameTitle: "Duplicate Structure",
|
||||
duplicateNamePlaceholder: "New table name",
|
||||
createDatabase: "Create Database",
|
||||
dropDatabase: "Drop Database",
|
||||
confirmDropDatabaseTitle: "Drop Database",
|
||||
confirmDropDatabaseMessage:
|
||||
'Are you sure you want to drop database "{name}"? This will permanently delete the database and all its data.',
|
||||
createDatabaseSuccess: 'Database "{name}" created',
|
||||
dropDatabaseSuccess: 'Database "{name}" dropped',
|
||||
createDatabaseNamePlaceholder: "Database name",
|
||||
createSchema: "Create Schema",
|
||||
dropSchema: "Drop Schema",
|
||||
confirmDropSchemaTitle: "Drop Schema",
|
||||
confirmDropSchemaMessage:
|
||||
'Are you sure you want to drop schema "{name}"? This will permanently delete the schema and all its objects.',
|
||||
createSchemaSuccess: 'Schema "{name}" created',
|
||||
dropSchemaSuccess: 'Schema "{name}" dropped',
|
||||
createSchemaNamePlaceholder: "Schema name",
|
||||
},
|
||||
tree: {
|
||||
columns: "Columns",
|
||||
|
||||
@@ -442,6 +442,20 @@ export default {
|
||||
tableOperationFailed: "操作失败:{message}",
|
||||
duplicateNameTitle: "复制表结构",
|
||||
duplicateNamePlaceholder: "新表名",
|
||||
createDatabase: "新建数据库",
|
||||
dropDatabase: "删除数据库",
|
||||
confirmDropDatabaseTitle: "删除数据库",
|
||||
confirmDropDatabaseMessage: "确定要删除数据库「{name}」吗?这将永久删除该数据库及其所有数据。",
|
||||
createDatabaseSuccess: "数据库「{name}」已创建",
|
||||
dropDatabaseSuccess: "数据库「{name}」已删除",
|
||||
createDatabaseNamePlaceholder: "数据库名称",
|
||||
createSchema: "新建 Schema",
|
||||
dropSchema: "删除 Schema",
|
||||
confirmDropSchemaTitle: "删除 Schema",
|
||||
confirmDropSchemaMessage: "确定要删除 Schema「{name}」吗?这将永久删除该 Schema 及其所有对象。",
|
||||
createSchemaSuccess: "Schema「{name}」已创建",
|
||||
dropSchemaSuccess: "Schema「{name}」已删除",
|
||||
createSchemaNamePlaceholder: "Schema 名称",
|
||||
},
|
||||
tree: {
|
||||
columns: "字段",
|
||||
|
||||
@@ -52,6 +52,19 @@ export const TABLE_IMPORT_SUPPORTED_TYPES = new Set<DatabaseType>([
|
||||
|
||||
export const TABLE_STRUCTURE_SUPPORTED_TYPES = new Set<DatabaseType>(["mysql", "postgres", "sqlite", "sqlserver"]);
|
||||
|
||||
export const CREATE_DATABASE_SUPPORTED_TYPES = new Set<DatabaseType>([
|
||||
"mysql",
|
||||
"postgres",
|
||||
"sqlserver",
|
||||
"clickhouse",
|
||||
"oracle",
|
||||
"dameng",
|
||||
"gaussdb",
|
||||
"doris",
|
||||
"starrocks",
|
||||
"redshift",
|
||||
]);
|
||||
|
||||
export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set<DatabaseType>([
|
||||
"mysql",
|
||||
"postgres",
|
||||
|
||||
Reference in New Issue
Block a user