diff --git a/packages/api/src/controllers/apps.js b/packages/api/src/controllers/apps.js index 655f13b51..462d3d4d6 100644 --- a/packages/api/src/controllers/apps.js +++ b/packages/api/src/controllers/apps.js @@ -1,7 +1,9 @@ const fs = require('fs-extra'); +const _ = require('lodash'); const path = require('path'); const { appdir } = require('../utility/directories'); const socket = require('../utility/socket'); +const connections = require('./connections'); module.exports = { folders_meta: true, @@ -50,6 +52,16 @@ module.exports = { return [...refsType(), ...fileType('.command.sql', 'command.sql'), ...fileType('.query.sql', 'query.sql')]; }, + async emitChangedDbApp(folder) { + for (const conn of await connections.list()) { + for (const db of conn.databases || []) { + if (db[`useApp:${folder}`]) { + socket.emitChanged(`db-apps-changed-${conn._id}-${db.name}`); + } + } + } + }, + refreshFiles_meta: true, async refreshFiles({ folder }) { socket.emitChanged(`app-files-changed-${folder}`); @@ -64,6 +76,7 @@ module.exports = { async deleteFile({ folder, file, fileType }) { await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`)); socket.emitChanged(`app-files-changed-${folder}`); + this.emitChangedDbApp(folder); }, renameFile_meta: true, @@ -73,6 +86,7 @@ module.exports = { path.join(path.join(appdir(), folder), `${newFile}.${fileType}`) ); socket.emitChanged(`app-files-changed-${folder}`); + this.emitChangedDbApp(folder); }, renameFolder_meta: true, @@ -97,4 +111,54 @@ module.exports = { } return `${name}${index}`; }, + + getAppsForDb_meta: true, + async getAppsForDb({ conid, database }) { + const connection = await connections.get({ conid }); + if (!connection) return []; + const db = (connection.databases || []).find(x => x.name == database); + const apps = []; + const res = []; + if (db) { + for (const key of _.keys(db || {})) { + if (key.startsWith('useApp:') && db[key]) { + apps.push(key.substring('useApp:'.length)); + } + } + } + console.log('APPS', apps); + for (const folder of apps) { + res.push(await this.loadApp({ folder })); + } + return res; + }, + + loadApp_meta: true, + async loadApp({ folder }) { + const res = { + queries: [], + commands: [], + name: folder, + }; + const dir = path.join(appdir(), folder); + if (await fs.exists(dir)) { + const files = await fs.readdir(dir); + + async function processType(ext, field) { + for (const file of files) { + if (file.endsWith(ext)) { + res[field].push({ + name: file.slice(0, -ext.length), + sql: await fs.readFile(path.join(dir, file), { encoding: 'utf-8' }), + }); + } + } + } + + await processType('.command.sql', 'commands'); + await processType('.query.sql', 'queries'); + } + + return res; + }, }; diff --git a/packages/api/src/controllers/connections.js b/packages/api/src/controllers/connections.js index 5ec9873d2..f5588db59 100644 --- a/packages/api/src/controllers/connections.js +++ b/packages/api/src/controllers/connections.js @@ -178,6 +178,9 @@ module.exports = { res = await this.datastore.insert(encrypted); } socket.emitChanged('connection-list-changed'); + for (const db of connection.databases || []) { + socket.emitChanged(`db-apps-changed-${connection._id}-${db.name}`); + } return res; }, @@ -201,6 +204,7 @@ module.exports = { } const res = await this.datastore.update({ _id: conid }, { $set: { databases } }); socket.emitChanged('connection-list-changed'); + socket.emitChanged(`db-apps-changed-${conid}-${database}`); return res; }, diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js index 8c8319a7d..14ea526a5 100644 --- a/packages/api/src/controllers/files.js +++ b/packages/api/src/controllers/files.js @@ -7,6 +7,7 @@ const hasPermission = require('../utility/hasPermission'); const socket = require('../utility/socket'); const scheduler = require('./scheduler'); const getDiagramExport = require('../utility/getDiagramExport'); +const apps = require('./apps'); function serialize(format, data) { if (format == 'text') return data; @@ -94,8 +95,10 @@ module.exports = { socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`); return true; } else if (folder.startsWith('app:')) { - await fs.writeFile(path.join(appdir(), folder.substring('app:'.length), file), serialize(format, data)); - socket.emitChanged(`app-files-changed-${folder.substring('app:'.length)}`); + const app = folder.substring('app:'.length); + await fs.writeFile(path.join(appdir(), app, file), serialize(format, data)); + socket.emitChanged(`app-files-changed-${app}`); + apps.emitChangedDbApp(folder); return true; } else { if (!hasPermission(`files/${folder}/write`)) return false; diff --git a/packages/types/appdefs.d.ts b/packages/types/appdefs.d.ts new file mode 100644 index 000000000..ff63dda42 --- /dev/null +++ b/packages/types/appdefs.d.ts @@ -0,0 +1,16 @@ +interface ApplicationCommand { + name: string; + sql: string; +} + +interface ApplicationQuery { + name: string; + sql: string; +} + +interface ApplicationDefinition { + name: string; + + queries: ApplicationQuery[]; + commands: ApplicationCommand[]; +} diff --git a/packages/web/src/appobj/AppFileAppObject.svelte b/packages/web/src/appobj/AppFileAppObject.svelte index e15251038..7a92be33d 100644 --- a/packages/web/src/appobj/AppFileAppObject.svelte +++ b/packages/web/src/appobj/AppFileAppObject.svelte @@ -34,7 +34,7 @@ 'query.sql': 'img app-query', }; - function getAppIcon( data) { + function getAppIcon(data) { return APP_ICONS[data.fileType]; } @@ -42,24 +42,14 @@ diff --git a/packages/web/src/appobj/ConnectionAppObject.svelte b/packages/web/src/appobj/ConnectionAppObject.svelte index afc8e0abb..53e9d5b14 100644 --- a/packages/web/src/appobj/ConnectionAppObject.svelte +++ b/packages/web/src/appobj/ConnectionAppObject.svelte @@ -26,7 +26,7 @@ import { getDatabaseMenuItems } from './DatabaseAppObject.svelte'; import getElectron from '../utility/getElectron'; import getConnectionLabel from '../utility/getConnectionLabel'; - import { getDatabaseList } from '../utility/metadataLoaders'; + import { getDatabaseList, useDbApps } from '../utility/metadataLoaders'; import { getLocalStorage } from '../utility/storageCache'; import { apiCall } from '../utility/api'; @@ -97,7 +97,7 @@ value: 'newdb', label: 'Database name', onConfirm: name => - apiCall('server-connections/create-database', { + apiCall('server-connections/create-database', { conid: data._id, name, }), @@ -153,7 +153,7 @@ ], data.singleDatabase && [ { divider: true }, - getDatabaseMenuItems(data, data.defaultDatabase, $extensions, $currentDatabase), + getDatabaseMenuItems(data, data.defaultDatabase, $extensions, $currentDatabase, $apps), ], ]; }; @@ -186,6 +186,8 @@ statusTitle = null; } } + + $: apps = useDbApps({ conid: data?._id, database: data.defaultDatabase }); export const extractKey = props => props.name; - export function getDatabaseMenuItems(connection, name, $extensions, $currentDatabase) { + export function getDatabaseMenuItems(connection, name, $extensions, $currentDatabase, $apps) { const handleNewQuery = () => { const tooltip = `${getConnectionLabel(connection)}\n${name}`; openNewTab({ @@ -157,8 +157,20 @@ openJsonDocument(db, name); }; + async function handleConfirmSql(sql) { + const resp = await apiCall('database-connections/run-script', { conid: connection._id, database: name, sql }); + const { errorMessage } = resp || {}; + if (errorMessage) { + showModal(ErrorMessageModal, { title: 'Error when executing script', message: errorMessage }); + } else { + showSnackbarSuccess('Saved to database'); + } + } + const driver = findEngineDriver(connection, getExtensions()); + const commands = _.flatten(($apps || []).map(x => x.commands || [])); + return [ { onClick: handleNewQuery, text: 'New query', isNewQuery: true }, !driver?.dialect?.nosql && { onClick: handleNewTable, text: 'New table' }, @@ -180,6 +192,20 @@ _.get($currentDatabase, 'connection._id') == _.get(connection, '_id') && _.get($currentDatabase, 'name') == name && { onClick: handleDisconnect, text: 'Disconnect' }, + + commands.length > 0 && [ + { divider: true }, + commands.map((cmd: any) => ({ + text: cmd.name, + onClick: () => { + showModal(ConfirmSqlModal, { + sql: cmd.sql, + onConfirm: () => handleConfirmSql(cmd.sql), + engine: driver.engine, + }); + }, + })), + ], ]; } @@ -207,18 +233,21 @@ import { showSnackbarSuccess } from '../utility/snackbar'; import { findEngineDriver } from 'dbgate-tools'; import InputTextModal from '../modals/InputTextModal.svelte'; - import { getDatabaseInfo } from '../utility/metadataLoaders'; + import { getDatabaseInfo, useDbApps } from '../utility/metadataLoaders'; import { openJsonDocument } from '../tabs/JsonTab.svelte'; import { apiCall } from '../utility/api'; + import ErrorMessageModal from '../modals/ErrorMessageModal.svelte'; + import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte'; export let data; export let passProps; function createMenu() { - return getDatabaseMenuItems(data.connection, data.name, $extensions, $currentDatabase); + return getDatabaseMenuItems(data.connection, data.name, $extensions, $currentDatabase, $apps); } $: isPinned = !!$pinnedDatabases.find(x => x.name == data.name && x.connection?._id == data.connection?._id); + $: apps = useDbApps({ conid: data?.connection?._id, database: data?.name }); ({ reloadTrigger: `app-files-changed-${folder}`, }); +const dbAppsLoader = ({ conid, database }) => ({ + url: 'apps/get-apps-for-db', + params: { conid, database }, + reloadTrigger: `db-apps-changed-${conid}-${database}`, +}); + const serverStatusLoader = () => ({ url: 'server-connections/server-status', params: {}, @@ -427,6 +433,13 @@ export function useAppFolders(args = {}) { return useCore(appFoldersLoader, args); } +export function getDbApps(args = {}) { + return getCore(dbAppsLoader, args); +} +export function useDbApps(args = {}) { + return useCore(dbAppsLoader, args); +} + export function getInstalledPlugins(args = {}) { return getCore(installedPluginsLoader, args) || []; } diff --git a/packages/web/src/widgets/AppFilesList.svelte b/packages/web/src/widgets/AppFilesList.svelte index 3206586a4..f1bb6a3a3 100644 --- a/packages/web/src/widgets/AppFilesList.svelte +++ b/packages/web/src/widgets/AppFilesList.svelte @@ -3,6 +3,14 @@ 'command.sql': 'SQL commands', 'query.sql': 'SQL queries', }; + + const COMMAND_TEMPLATE = `-- Write SQL command here +-- After save, you can execute it from database context menu, for all databases, which use this application +`; + + const QUERY_TEMPLATE = `-- Write SQL query here +-- After save, you can view it in tables list, for all databases, which use this application +`;