diff --git a/packages/api/package.json b/packages/api/package.json index 41d6b6746..0ac1c406f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -49,6 +49,7 @@ "jsonwebtoken": "^8.5.1", "line-reader": "^0.4.0", "lodash": "^4.17.21", + "mkdirp": "^3.0.1", "moment": "^2.24.0", "ncp": "^2.0.0", "node-cron": "^2.0.3", diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index 9c470b030..7ce356d37 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -32,6 +32,7 @@ const { MissingCredentialsError } = require('../utility/exceptions'); const pipeForkLogs = require('../utility/pipeForkLogs'); const crypto = require('crypto'); const loadModelTransform = require('../utility/loadModelTransform'); +const exportDbModelSql = require('../utility/exportDbModelSql'); const logger = getLogger('databaseConnections'); @@ -398,7 +399,7 @@ module.exports = { }, structure_meta: true, - async structure({ conid, database, modelTransFile }, req) { + async structure({ conid, database, modelTransFile = null }, req) { testConnectionPermission(conid, req); if (conid == '__model') { const model = await importDbModel(database); @@ -439,14 +440,33 @@ module.exports = { }, exportModel_meta: true, - async exportModel({ conid, database }, req) { + async exportModel({ conid, database, outputFolder }, req) { testConnectionPermission(conid, req); - const archiveFolder = await archive.getNewArchiveFolder({ database }); - await fs.mkdir(path.join(archivedir(), archiveFolder)); + + const realFolder = outputFolder.startsWith('archive:') + ? resolveArchiveFolder(outputFolder.substring('archive:'.length)) + : outputFolder; + const model = await this.structure({ conid, database }); - await exportDbModel(model, path.join(archivedir(), archiveFolder)); - socket.emitChanged(`archive-folders-changed`); - return { archiveFolder }; + await exportDbModel(extendDatabaseInfo(model), realFolder); + + if (outputFolder.startsWith('archive:')) { + socket.emitChanged(`archive-files-changed`, { folder: outputFolder.substring('archive:'.length) }); + } + return { status: 'ok' }; + }, + + exportModelSql_meta: true, + async exportModelSql({ conid, database, outputFolder, outputFile }, req) { + testConnectionPermission(conid, req); + + const connection = await connections.getCore({ conid }); + const driver = requireEngineDriver(connection); + + const model = await this.structure({ conid, database }); + await exportDbModelSql(extendDatabaseInfo(model), driver, outputFolder, outputFile); + + return { status: 'ok' }; }, generateDeploySql_meta: true, diff --git a/packages/web/src/appobj/DatabaseAppObject.svelte b/packages/web/src/appobj/DatabaseAppObject.svelte index 15f59ef9e..f2f4b3be3 100644 --- a/packages/web/src/appobj/DatabaseAppObject.svelte +++ b/packages/web/src/appobj/DatabaseAppObject.svelte @@ -170,14 +170,18 @@ }; const handleExportModel = async () => { - const resp = await apiCall('database-connections/export-model', { + showModal(ExportDbModelModal, { conid: connection._id, database: name, }); - currentArchive.set(resp.archiveFolder); - selectedWidget.set('archive'); - visibleWidgetSideBar.set(true); - showSnackbarSuccess(`Saved to archive ${resp.archiveFolder}`); + // 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 handleCompareWithCurrentDb = () => { @@ -198,13 +202,13 @@ ); }; - const handleOpenJsonModel = async () => { - const db = await getDatabaseInfo({ - conid: connection._id, - database: name, - }); - openJsonDocument(db, 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', { @@ -325,11 +329,12 @@ hasPermission(`dbops/sql-generator`) && { onClick: handleSqlGenerator, text: 'SQL Generator' }, driver?.supportsDatabaseProfiler && hasPermission(`dbops/profiler`) && { onClick: handleDatabaseProfiler, text: 'Database profiler' }, + // isSqlOrDoc && + // isSqlOrDoc && + // hasPermission(`dbops/model/view`) && { onClick: handleOpenJsonModel, text: 'Open model as JSON' }, isSqlOrDoc && - isSqlOrDoc && - hasPermission(`dbops/model/view`) && { onClick: handleOpenJsonModel, text: 'Open model as JSON' }, - isSqlOrDoc && - hasPermission(`dbops/model/view`) && { onClick: handleExportModel, text: 'Export DB model - experimental' }, + isProApp() && + hasPermission(`dbops/model/view`) && { onClick: handleExportModel, text: 'Export DB model' }, isSqlOrDoc && _.get($currentDatabase, 'connection._id') && hasPermission('dbops/model/compare') && @@ -408,6 +413,7 @@ import newTable from '../tableeditor/newTable'; import { loadSchemaList, switchCurrentDatabase } from '../utility/common'; import { isProApp } from '../utility/proTools'; + import ExportDbModelModal from '../modals/ExportDbModelModal.svelte'; export let data; export let passProps; diff --git a/packages/web/src/forms/FormArchiveFolderSelect.svelte b/packages/web/src/forms/FormArchiveFolderSelect.svelte index 1ee8d958a..887862e84 100644 --- a/packages/web/src/forms/FormArchiveFolderSelect.svelte +++ b/packages/web/src/forms/FormArchiveFolderSelect.svelte @@ -22,6 +22,7 @@ label: folder.name, })), ...additionalFolders + .filter(x => x != '@create') .filter(x => !($folders || []).find(y => y.name == x)) .map(folder => ({ value: folder, diff --git a/packages/web/src/forms/FormElectronFileSelectorRaw.svelte b/packages/web/src/forms/FormElectronFileSelectorRaw.svelte index e59fb65f8..b524a85e7 100644 --- a/packages/web/src/forms/FormElectronFileSelectorRaw.svelte +++ b/packages/web/src/forms/FormElectronFileSelectorRaw.svelte @@ -9,19 +9,32 @@ export let name; export let disabled = false; export let defaultFileName = ''; + export let dialogProperties = undefined; + export let isSaveDialog = false; + export let dialogFilters = [{ name: 'All Files', extensions: ['*'] }]; const { values, setFieldValue } = getFormContext(); async function handleBrowse() { const electron = getElectron(); if (!electron) return; - const filePaths = await electron.showOpenDialog({ - defaultPath: values[name], - properties: ['showHiddenFiles', 'openFile'], - filters: [{ name: 'All Files', extensions: ['*'] }], - }); - const filePath = filePaths && filePaths[0]; - if (filePath) setFieldValue(name, filePath); + + if (isSaveDialog) { + const filePath = await electron.showSaveDialog({ + defaultPath: values[name], + properties: dialogProperties ?? ['showHiddenFiles', 'showOverwriteConfirmation'], + filters: dialogFilters, + }); + if (filePath) setFieldValue(name, filePath); + } else { + const filePaths = await electron.showOpenDialog({ + defaultPath: values[name], + properties: dialogProperties ?? ['showHiddenFiles', 'openFile'], + filters: dialogFilters, + }); + const filePath = filePaths && filePaths[0]; + if (filePath) setFieldValue(name, filePath); + } } diff --git a/packages/web/src/modals/ExportDbModelModal.svelte b/packages/web/src/modals/ExportDbModelModal.svelte new file mode 100644 index 000000000..d79cee84b --- /dev/null +++ b/packages/web/src/modals/ExportDbModelModal.svelte @@ -0,0 +1,5 @@ + + + diff --git a/yarn.lock b/yarn.lock index 527c1ffa6..3c5b61754 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8390,6 +8390,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + moment@^2.24.0, moment@^2.29.2: version "2.30.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"