Files
dbgate/packages/web/src/appobj/DatabaseAppObject.svelte
Stela Augustinova 9ee0b32cac translation-db object
2025-11-10 14:42:20 +01:00

768 lines
24 KiB
Svelte

<script lang="ts" context="module">
import { copyTextToClipboard } from '../utility/clipboard';
export const extractKey = props => props.name;
export const createMatcher = filter => props => {
const { name, displayName, server } = props;
return filterName(filter, name, displayName, server);
};
export function disconnectDatabaseConnection(conid, database, showConfirmation = true) {
const closeCondition = x =>
x.props?.conid == conid &&
x.props?.database == database &&
x.tabComponent != 'ConnectionTab' &&
x.closedTime == null;
if (showConfirmation) {
const count = getOpenedTabs().filter(closeCondition).length;
if (count > 0) {
showModal(ConfirmModal, {
message: _t('database.closeConfirm', {
defaultMessage: 'Closing connection will close {count} opened tabs, continue?',
values: { count },
}),
onConfirm: () => disconnectDatabaseConnection(conid, database, false),
});
return;
}
}
const electron = getElectron();
if (electron) {
apiCall('database-connections/disconnect', { conid, database });
}
if (getCurrentDatabase()?.connection?._id == conid && getCurrentDatabase()?.name == database) {
switchCurrentDatabase(null);
}
openedSingleDatabaseConnections.update(list => list.filter(x => x != conid));
closeMultipleTabs(closeCondition);
}
export function getDatabaseMenuItems(
connection,
name,
$extensions,
$currentDatabase,
$apps,
$openedSingleDatabaseConnections,
databasePermissionRole
) {
const apps = filterAppsForDatabase(connection, name, $apps);
const handleNewQuery = () => {
const tooltip = `${getConnectionLabel(connection)}\n${name}`;
openNewTab({
title: _t('database.newQuery', { defaultMessage: 'Query #' }),
icon: 'img sql-file',
tooltip,
tabComponent: 'QueryTab',
focused: true,
props: {
conid: connection._id,
database: name,
},
});
};
const handleNewTable = () => {
newTable(connection, name);
};
const handleDropDatabase = () => {
showModal(ConfirmModal, {
message: _t('database.dropConfirm', {
defaultMessage:
'Really drop database {name}? All opened sessions with this database will be forcefully closed.',
values: { name },
}),
onConfirm: () =>
apiCall('server-connections/drop-database', {
conid: connection._id,
name,
}),
});
};
const handleNewCollection = () => {
showModal(NewCollectionModal, {
driver,
dbid: { conid: connection._id, database: name },
});
};
const handleImport = () => {
openImportExportTab({
sourceStorageType: getDefaultFileFormat($extensions).storageType,
targetStorageType: 'database',
targetConnectionId: connection._id,
targetDatabaseName: name,
});
// showModal(ImportExportModal, {
// initialValues: {
// sourceStorageType: getDefaultFileFormat($extensions).storageType,
// targetStorageType: 'database',
// targetConnectionId: connection._id,
// targetDatabaseName: name,
// },
// });
};
const handleExport = () => {
openImportExportTab({
targetStorageType: getDefaultFileFormat($extensions).storageType,
sourceStorageType: 'database',
sourceConnectionId: connection._id,
sourceDatabaseName: name,
});
// showModal(ImportExportModal, {
// initialValues: {
// targetStorageType: getDefaultFileFormat($extensions).storageType,
// sourceStorageType: 'database',
// sourceConnectionId: connection._id,
// sourceDatabaseName: name,
// },
// });
};
const handleSqlGenerator = () => {
showModal(SqlGeneratorModal, {
conid: connection._id,
database: name,
});
};
const handleBackupDatabase = () => {
openNewTab({
title: _t('database.backup', { defaultMessage: 'Backup #' }),
icon: 'img db-backup',
tabComponent: 'BackupDatabaseTab',
props: {
conid: connection._id,
database: name,
},
});
};
const handleRestoreDatabase = () => {
openNewTab({
title: _t('database.restore', { defaultMessage: 'Restore #' }),
icon: 'img db-restore',
tabComponent: 'RestoreDatabaseTab',
props: {
conid: connection._id,
database: name,
},
});
};
const handleShowDiagram = async () => {
const db = await getDatabaseInfo({
conid: connection._id,
database: name,
});
openNewTab(
{
title: _t('database.diagram', { defaultMessage: 'Diagram #' }),
icon: 'img diagram',
tabComponent: 'DiagramTab',
props: {
conid: connection._id,
database: name,
},
},
{
editor: {
tables: db.tables.map(table => ({
...table,
designerId: `${table.pureName}-${uuidv1()}`,
})),
references: [],
autoLayout: true,
},
}
);
};
const handleCopyName = async () => {
copyTextToClipboard(name);
};
const handleDisconnect = () => {
disconnectDatabaseConnection(connection._id, name);
};
const handleExportModel = async () => {
showModal(ExportDbModelModal, {
conid: connection._id,
database: name,
});
// const resp = await apiCall('database-connections/export-model', {
// conid: connection._id,
// database: name,
// });
// currentArchive.set(resp.archiveFolder);
// selectedWidget.set('archive');
// visibleWidgetSideBar.set(true);
// showSnackbarSuccess(`Saved to archive ${resp.archiveFolder}`);
};
const handleDatabaseChat = () => {
openNewTab({
title: 'Chat',
icon: 'img ai',
tabComponent: 'DatabaseChatTab',
props: {
conid: connection._id,
database: name,
},
});
};
const handleCompareWithCurrentDb = () => {
openNewTab(
{
title: _t('database.compare', { defaultMessage: 'Compare' }),
icon: 'img compare',
tabComponent: 'CompareModelTab',
props: {
conid: $currentDatabase?.connection?._id,
database: $currentDatabase?.name,
},
},
{
editor: {
sourceConid: connection?._id,
sourceDatabase: name,
targetConid: $currentDatabase?.connection?._id,
targetDatabase: $currentDatabase?.name,
},
}
);
};
// const handleOpenJsonModel = async () => {
// const db = await getDatabaseInfo({
// conid: connection._id,
// database: name,
// });
// openJsonDocument(db, name);
// };
const handleGenerateScript = async () => {
const data = await apiCall('database-connections/export-keys', {
conid: connection?._id,
database: name,
options: {
keyPrefix: '',
},
});
if (data.errorMessage) {
showSnackbarError(data.errorMessage);
return;
}
newQuery({
title: _t('database.export', { defaultMessage: 'Export #' }),
initialData: data,
});
};
const handleQueryDesigner = () => {
openNewTab({
title: _t('database.queryDesigner', { defaultMessage: 'Query #' }),
icon: 'img query-design',
tabComponent: 'QueryDesignTab',
focused: true,
props: {
conid: connection?._id,
database: name,
},
});
};
const handleNewPerspective = () => {
openNewTab({
title: _t('database.perspective', { defaultMessage: 'Perspective #' }),
icon: 'img perspective',
tabComponent: 'PerspectiveTab',
props: {
conid: connection?._id,
database: name,
},
});
};
const handleDatabaseProfiler = () => {
openNewTab({
title: _t('database.profiler', { defaultMessage: 'Profiler' }),
icon: 'img profiler',
tabComponent: 'ProfilerTab',
props: {
conid: connection?._id,
database: name,
},
});
};
const handleRefreshSchemas = () => {
const conid = connection?._id;
const database = name;
apiCall('database-connections/dispatch-database-changed-event', {
event: 'schema-list-changed',
conid,
database,
});
loadSchemaList(conid, database);
};
async function handleConfirmSql(sql) {
saveScriptToDatabase({ conid: connection?._id, database: name }, sql, false);
}
const handleGenerateDropAllObjectsScript = () => {
showModal(ConfirmModal, {
message: _t('database.dropAllObjectsConfirm', {
defaultMessage:
'This will generate script, after executing this script all objects in {name} will be dropped. Continue?',
values: { name },
}),
onConfirm: () => {
openNewTab(
{
title: _t('database.shellTitle', { defaultMessage: 'Shell #' }),
icon: 'img shell',
tabComponent: 'ShellTab',
},
{
editor: `// @require ${extractPackageName(connection.engine)}
await dbgateApi.dropAllDbObjects(${JSON.stringify(
{
connection: extractShellConnection(connection, name),
},
undefined,
2
)})`,
}
);
},
});
};
const handleGenerateRunScript = () => {
openNewTab(
{
title: _t('database.shellTitle', { defaultMessage: 'Shell #' }),
icon: 'img shell',
tabComponent: 'ShellTab',
},
{
editor: `// @require ${extractPackageName(connection.engine)}
await dbgateApi.executeQuery(${JSON.stringify(
{
connection: extractShellConnection(connection, name),
sql: 'your script here',
},
undefined,
2
)});
`,
}
);
};
const handleShowDataDeployer = () => {
showModal(ChooseArchiveFolderModal, {
message: _t('database.chooseArchiveFolderForDataDeployer', {
defaultMessage: 'Choose archive folder for data deployer',
}),
onConfirm: archiveFolder => {
openNewTab(
{
title: archiveFolder,
icon: 'img replicator',
tabComponent: 'DataDeployTab',
props: {
conid: connection?._id,
database: name,
},
},
{
editor: {
archiveFolder,
conid: connection?._id,
database: name,
},
}
);
},
});
};
const handleCreateNewApp = () => {
showModal(InputTextModal, {
header: 'New application',
label: 'Application name',
value: _.startCase(name),
onConfirm: async appName => {
const newAppId = await apiCall('apps/create-app-from-db', {
appName,
server: connection?.server,
database: name,
});
openApplicationEditor(newAppId);
},
});
};
const driver = findEngineDriver(connection, getExtensions());
const commands = _.flatten((apps || []).map(x => Object.values(x.files || {}).filter(x => x.type == 'command')));
const isSqlOrDoc =
driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document');
return [
hasPermission(`dbops/query`) &&
isAllowedDatabaseRunScript(databasePermissionRole) && {
onClick: handleNewQuery,
text: _t('database.newQuery', { defaultMessage: 'New query' }),
isNewQuery: true,
},
hasPermission(`dbops/model/edit`) &&
!connection.isReadOnly &&
driver?.databaseEngineTypes?.includes('sql') && {
onClick: handleNewTable,
text: _t('database.newTable', { defaultMessage: 'New table' }),
},
!connection.isReadOnly &&
hasPermission(`dbops/model/edit`) &&
driver?.databaseEngineTypes?.includes('document') && {
onClick: handleNewCollection,
text: _t('database.newCollection', {
defaultMessage: 'New collection/container'
}),
},
hasPermission(`dbops/query`) &&
driver?.databaseEngineTypes?.includes('sql') &&
isProApp() && {
onClick: handleQueryDesigner,
text: _t('database.designQuery', { defaultMessage: 'Design query' }),
},
driver?.databaseEngineTypes?.includes('sql') &&
isProApp() && {
onClick: handleNewPerspective,
text: _t('database.designPerspectiveQuery', { defaultMessage: 'Design perspective query' }),
},
connection.useSeparateSchemas && {
onClick: handleRefreshSchemas,
text: _t('database.refreshSchemas', { defaultMessage: 'Refresh schemas' }),
},
{ divider: true },
isSqlOrDoc &&
isProApp() &&
!connection.isReadOnly &&
hasPermission(`dbops/import`) && {
onClick: handleImport,
text: _t('database.import', { defaultMessage: 'Import' }),
},
isSqlOrDoc &&
isProApp() &&
hasPermission(`dbops/export`) && {
onClick: handleExport,
text: _t('database.export', { defaultMessage: 'Export' }),
},
driver?.supportsDatabaseRestore &&
isProApp() &&
hasPermission(`dbops/sql-dump/import`) &&
!connection.isReadOnly && {
onClick: handleRestoreDatabase,
text: _t('database.restoreDatabaseBackup', { defaultMessage: 'Restore database backup' }),
},
driver?.supportsDatabaseBackup &&
isProApp() &&
hasPermission(`dbops/sql-dump/export`) && {
onClick: handleBackupDatabase,
text: _t('database.createDatabaseBackup', { defaultMessage: 'Create database backup' }),
},
isSqlOrDoc &&
!connection.isReadOnly &&
!connection.singleDatabase &&
isSqlOrDoc &&
hasPermission(`dbops/dropdb`) && {
onClick: handleDropDatabase,
text: _t('database.dropDatabase', { defaultMessage: 'Drop database' }),
},
{ divider: true },
driver?.databaseEngineTypes?.includes('sql') && {
onClick: handleCopyName,
text: _t('database.copyDatabaseName', { defaultMessage: 'Copy database name' }),
},
driver?.databaseEngineTypes?.includes('sql') && {
onClick: handleShowDiagram,
text: _t('database.showDiagram', { defaultMessage: 'Show diagram' }),
},
driver?.databaseEngineTypes?.includes('sql') &&
hasPermission(`dbops/sql-generator`) && {
onClick: handleSqlGenerator,
text: _t('database.sqlGenerator', { defaultMessage: 'SQL Generator' }),
},
driver?.supportsDatabaseProfiler &&
isProApp() &&
hasPermission(`dbops/profiler`) && {
onClick: handleDatabaseProfiler,
text: _t('database.databaseProfiler', { defaultMessage: 'Database profiler' }),
},
// isSqlOrDoc &&
// isSqlOrDoc &&
// hasPermission(`dbops/model/view`) && { onClick: handleOpenJsonModel, text: 'Open model as JSON' },
isSqlOrDoc &&
isProApp() &&
hasPermission(`dbops/model/view`) && {
onClick: handleExportModel,
text: _t('database.exportDbModel', { defaultMessage: 'Export DB model' }),
},
isProApp() &&
driver?.databaseEngineTypes?.includes('sql') &&
hasPermission('dbops/chat') && {
onClick: handleDatabaseChat,
text: _t('database.databaseChat', { defaultMessage: 'Database chat' }),
},
isSqlOrDoc &&
_.get($currentDatabase, 'connection._id') &&
hasPermission('dbops/model/compare') &&
isProApp() &&
(_.get($currentDatabase, 'connection._id') != _.get(connection, '_id') ||
(_.get($currentDatabase, 'connection._id') == _.get(connection, '_id') &&
_.get($currentDatabase, 'name') != _.get(connection, 'name'))) && {
onClick: handleCompareWithCurrentDb,
text: _t('database.compareWithCurrentDb', {
defaultMessage: 'Compare with {name}',
values: { name: _.get($currentDatabase, 'name') },
}),
},
driver?.databaseEngineTypes?.includes('keyvalue') && {
onClick: handleGenerateScript,
text: _t('database.generateScript', { defaultMessage: 'Generate script' }),
},
($openedSingleDatabaseConnections.includes(connection._id) ||
(_.get($currentDatabase, 'connection._id') == _.get(connection, '_id') &&
_.get($currentDatabase, 'name') == name)) && {
onClick: handleDisconnect,
text: _t('database.disconnect', { defaultMessage: 'Disconnect' }),
},
{ divider: true },
driver?.databaseEngineTypes?.includes('sql') &&
hasPermission(`run-shell-script`) &&
hasPermission(`dbops/dropdb`) && {
onClick: handleGenerateDropAllObjectsScript,
text: _t('database.shellDropAllObjects', { defaultMessage: 'Shell: Drop all objects' }),
},
hasPermission(`run-shell-script`) && {
onClick: handleGenerateRunScript,
text: _t('database.shellRunScript', { defaultMessage: 'Shell: Run script' }),
},
driver?.databaseEngineTypes?.includes('sql') &&
hasPermission(`dbops/import`) && {
onClick: handleShowDataDeployer,
text: _t('database.dataDeployer', { defaultMessage: 'Data deployer' }),
},
isProApp() &&
hasPermission(`files/apps/write`) && {
onClick: handleCreateNewApp,
text: _t('database.createNewApplication', { defaultMessage: 'Create new application' }),
},
isProApp() &&
apps?.length > 0 && {
text: _t('database.editApplications', { defaultMessage: 'Edit application' }),
submenu: apps.map((app: any) => ({
text: app.applicationName,
onClick: () => openApplicationEditor(app.appid),
})),
},
{ divider: true },
commands.length > 0 && [
commands.map((cmd: any) => ({
text: cmd.label,
onClick: () => {
showModal(ConfirmSqlModal, {
sql: cmd.sql,
onConfirm: () => handleConfirmSql(cmd.sql),
engine: driver.engine,
});
},
})),
],
];
}
</script>
<script lang="ts">
import uuidv1 from 'uuid/v1';
import _, { find } from 'lodash';
import { showModal } from '../modals/modalTools';
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
import { getDefaultFileFormat } from '../plugins/fileformats';
import {
currentArchive,
currentDatabase,
extensions,
focusedConnectionOrDatabase,
getCurrentDatabase,
getExtensions,
getOpenedTabs,
loadingSchemaLists,
lockedDatabaseMode,
openedConnections,
openedSingleDatabaseConnections,
openedTabs,
pinnedDatabases,
selectedWidget,
visibleWidgetSideBar,
} from '../stores';
import getElectron from '../utility/getElectron';
import openNewTab from '../utility/openNewTab';
import AppObjectCore from './AppObjectCore.svelte';
import { showSnackbarError, showSnackbarSuccess } from '../utility/snackbar';
import {
extractDbNameFromComposite,
extractPackageName,
filterName,
findEngineDriver,
getConnectionLabel,
} from 'dbgate-tools';
import InputTextModal from '../modals/InputTextModal.svelte';
import { getDatabaseInfo, useAllApps, useDatabaseInfoPeek } from '../utility/metadataLoaders';
import { openJsonDocument } from '../tabs/JsonTab.svelte';
import { apiCall } from '../utility/api';
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
import ConfirmSqlModal, { runOperationOnDatabase, saveScriptToDatabase } from '../modals/ConfirmSqlModal.svelte';
import { filterAppsForDatabase, openApplicationEditor } from '../utility/appTools';
import newQuery from '../query/newQuery';
import ConfirmModal from '../modals/ConfirmModal.svelte';
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
import NewCollectionModal from '../modals/NewCollectionModal.svelte';
import hasPermission, { isAllowedDatabaseRunScript } from '../utility/hasPermission';
import { openImportExportTab } from '../utility/importExportTools';
import newTable from '../tableeditor/newTable';
import { loadSchemaList, switchCurrentDatabase } from '../utility/common';
import { isProApp } from '../utility/proTools';
import ExportDbModelModal from '../modals/ExportDbModelModal.svelte';
import ChooseArchiveFolderModal from '../modals/ChooseArchiveFolderModal.svelte';
import { extractShellConnection } from '../impexp/createImpExpScript';
import { getNumberIcon } from '../icons/FontIcon.svelte';
import { getDatabaseClickActionSetting } from '../settings/settingsTools';
import { _t } from '../translations';
import { tick } from 'svelte';
export let data;
export let passProps;
function createMenu() {
return getDatabaseMenuItems(
data.connection,
data.name,
$extensions,
$currentDatabase,
$apps,
$openedSingleDatabaseConnections,
data.databasePermissionRole
);
}
$: isPinned = !!$pinnedDatabases.find(x => x?.name == data.name && x?.connection?._id == data.connection?._id);
$: apps = useAllApps();
$: isLoadingSchemas = $loadingSchemaLists[`${data?.connection?._id}::${data?.name}`];
$: dbInfo = useDatabaseInfoPeek({ conid: data?.connection?._id, database: data?.name });
$: appsForDb = filterAppsForDatabase(data?.connection, data?.name, $apps, $dbInfo);
// $: console.log('AppsForDB:', data?.name, appsForDb);
</script>
<AppObjectCore
{...$$restProps}
{data}
title={data.name}
extInfo={data.extInfo}
icon="img database"
colorMark={passProps?.connectionColorFactory &&
passProps?.connectionColorFactory({ conid: data?.connection?._id, database: data.name }, null, null, false)}
isBold={$currentDatabase?.connection?._id == data?.connection?._id &&
extractDbNameFromComposite($currentDatabase?.name) == data.name}
on:dblclick={() => {
switchCurrentDatabase(data);
// passProps?.onFocusSqlObjectList?.();
}}
on:click={() => {
// switchCurrentDatabase(data);
if (getDatabaseClickActionSetting() == 'switch') {
switchCurrentDatabase(data);
}
}}
additionalIcons={appsForDb?.length > 0
? appsForDb.map(ic => ({
icon: ic.applicationIcon || 'img app',
title: ic.applicationName,
colorClass: ic.applicationColor ? `color-icon-${ic.applicationColor}` : undefined,
}))
: null}
on:mousedown={() => {
$focusedConnectionOrDatabase = { conid: data.connection?._id, database: data.name, connection: data.connection };
}}
on:dragstart
on:dragenter
on:dragend
on:drop
on:middleclick={() => {
createMenu()
.find(x => x.isNewQuery)
.onClick();
}}
statusIcon={isLoadingSchemas
? 'icon loading'
: $lockedDatabaseMode
? getNumberIcon(
$openedTabs.filter(
x => !x.closedTime && x.props?.conid == data?.connection?._id && x.props?.database == data?.name
).length
)
: ''}
statusIconBefore={data.databasePermissionRole == 'read_content' || data.databasePermissionRole == 'view'
? 'icon lock'
: null}
menu={createMenu}
showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin}
onPin={isPinned ? null : () => pinnedDatabases.update(list => [...list, data])}
onUnpin={isPinned
? () =>
pinnedDatabases.update(list =>
list.filter(x => x?.name != data?.name || x?.connection?._id != data?.connection?._id)
)
: null}
isChoosed={data.connection?._id == $focusedConnectionOrDatabase?.conid &&
data.name == $focusedConnectionOrDatabase?.database}
disableBoldScroll={!!$focusedConnectionOrDatabase}
divProps={{
'data-testid': `DatabaseAppObject_${data.name}`,
}}
/>