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
+`;