app commands works

This commit is contained in:
Jan Prochazka
2022-01-27 17:01:58 +01:00
parent 595c9424df
commit a76ba60272
10 changed files with 174 additions and 25 deletions

View File

@@ -1,7 +1,9 @@
const fs = require('fs-extra'); const fs = require('fs-extra');
const _ = require('lodash');
const path = require('path'); const path = require('path');
const { appdir } = require('../utility/directories'); const { appdir } = require('../utility/directories');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
const connections = require('./connections');
module.exports = { module.exports = {
folders_meta: true, folders_meta: true,
@@ -50,6 +52,16 @@ module.exports = {
return [...refsType(), ...fileType('.command.sql', 'command.sql'), ...fileType('.query.sql', 'query.sql')]; 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, refreshFiles_meta: true,
async refreshFiles({ folder }) { async refreshFiles({ folder }) {
socket.emitChanged(`app-files-changed-${folder}`); socket.emitChanged(`app-files-changed-${folder}`);
@@ -64,6 +76,7 @@ module.exports = {
async deleteFile({ folder, file, fileType }) { async deleteFile({ folder, file, fileType }) {
await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`)); await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`));
socket.emitChanged(`app-files-changed-${folder}`); socket.emitChanged(`app-files-changed-${folder}`);
this.emitChangedDbApp(folder);
}, },
renameFile_meta: true, renameFile_meta: true,
@@ -73,6 +86,7 @@ module.exports = {
path.join(path.join(appdir(), folder), `${newFile}.${fileType}`) path.join(path.join(appdir(), folder), `${newFile}.${fileType}`)
); );
socket.emitChanged(`app-files-changed-${folder}`); socket.emitChanged(`app-files-changed-${folder}`);
this.emitChangedDbApp(folder);
}, },
renameFolder_meta: true, renameFolder_meta: true,
@@ -97,4 +111,54 @@ module.exports = {
} }
return `${name}${index}`; 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;
},
}; };

View File

@@ -178,6 +178,9 @@ module.exports = {
res = await this.datastore.insert(encrypted); res = await this.datastore.insert(encrypted);
} }
socket.emitChanged('connection-list-changed'); socket.emitChanged('connection-list-changed');
for (const db of connection.databases || []) {
socket.emitChanged(`db-apps-changed-${connection._id}-${db.name}`);
}
return res; return res;
}, },
@@ -201,6 +204,7 @@ module.exports = {
} }
const res = await this.datastore.update({ _id: conid }, { $set: { databases } }); const res = await this.datastore.update({ _id: conid }, { $set: { databases } });
socket.emitChanged('connection-list-changed'); socket.emitChanged('connection-list-changed');
socket.emitChanged(`db-apps-changed-${conid}-${database}`);
return res; return res;
}, },

View File

@@ -7,6 +7,7 @@ const hasPermission = require('../utility/hasPermission');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
const scheduler = require('./scheduler'); const scheduler = require('./scheduler');
const getDiagramExport = require('../utility/getDiagramExport'); const getDiagramExport = require('../utility/getDiagramExport');
const apps = require('./apps');
function serialize(format, data) { function serialize(format, data) {
if (format == 'text') return data; if (format == 'text') return data;
@@ -94,8 +95,10 @@ module.exports = {
socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`); socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`);
return true; return true;
} else if (folder.startsWith('app:')) { } else if (folder.startsWith('app:')) {
await fs.writeFile(path.join(appdir(), folder.substring('app:'.length), file), serialize(format, data)); const app = folder.substring('app:'.length);
socket.emitChanged(`app-files-changed-${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; return true;
} else { } else {
if (!hasPermission(`files/${folder}/write`)) return false; if (!hasPermission(`files/${folder}/write`)) return false;

16
packages/types/appdefs.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
interface ApplicationCommand {
name: string;
sql: string;
}
interface ApplicationQuery {
name: string;
sql: string;
}
interface ApplicationDefinition {
name: string;
queries: ApplicationQuery[];
commands: ApplicationCommand[];
}

View File

@@ -34,7 +34,7 @@
'query.sql': 'img app-query', 'query.sql': 'img app-query',
}; };
function getAppIcon( data) { function getAppIcon(data) {
return APP_ICONS[data.fileType]; return APP_ICONS[data.fileType];
} }
</script> </script>
@@ -42,24 +42,14 @@
<script lang="ts"> <script lang="ts">
import _ from 'lodash'; import _ from 'lodash';
import { filterName } from 'dbgate-tools'; import { filterName } from 'dbgate-tools';
import ImportExportModal from '../modals/ImportExportModal.svelte';
import { showModal } from '../modals/modalTools'; import { showModal } from '../modals/modalTools';
import { archiveFilesAsDataSheets, currentArchive, extensions, getCurrentDatabase } from '../stores';
import createQuickExportMenu from '../utility/createQuickExportMenu';
import { exportElectronFile } from '../utility/exportElectronFile';
import openNewTab from '../utility/openNewTab'; import openNewTab from '../utility/openNewTab';
import AppObjectCore from './AppObjectCore.svelte'; import AppObjectCore from './AppObjectCore.svelte';
import getConnectionLabel from '../utility/getConnectionLabel';
import InputTextModal from '../modals/InputTextModal.svelte'; import InputTextModal from '../modals/InputTextModal.svelte';
import ConfirmModal from '../modals/ConfirmModal.svelte'; import ConfirmModal from '../modals/ConfirmModal.svelte';
import {
isArchiveFileMarkedAsDataSheet,
markArchiveFileAsDataSheet,
markArchiveFileAsReadonly,
} from '../utility/archiveTools';
import { apiCall } from '../utility/api'; import { apiCall } from '../utility/api';
import { currentDatabase, currentDatabase } from '../stores';
export let data; export let data;
@@ -108,6 +98,7 @@
{ text: 'Delete', onClick: handleDelete }, { text: 'Delete', onClick: handleDelete },
{ text: 'Rename', onClick: handleRename }, { text: 'Rename', onClick: handleRename },
data.fileType.endsWith('.sql') && { text: 'Open SQL', onClick: handleOpenSqlFile }, data.fileType.endsWith('.sql') && { text: 'Open SQL', onClick: handleOpenSqlFile },
// data.fileType.endsWith('.yaml') && { text: 'Open YAML', onClick: handleOpenYamlFile }, // data.fileType.endsWith('.yaml') && { text: 'Open YAML', onClick: handleOpenYamlFile },
]; ];
} }
@@ -117,7 +108,7 @@
{...$$restProps} {...$$restProps}
{data} {data}
title={data.fileLabel} title={data.fileLabel}
icon={getAppIcon( data)} icon={getAppIcon(data)}
menu={createMenu} menu={createMenu}
on:click={handleClick} on:click={handleClick}
/> />

View File

@@ -7,7 +7,7 @@
import _ from 'lodash'; import _ from 'lodash';
import { filterName } from 'dbgate-tools'; import { filterName } from 'dbgate-tools';
import { currentApplication } from '../stores'; import { currentApplication, currentDatabase } from '../stores';
import AppObjectCore from './AppObjectCore.svelte'; import AppObjectCore from './AppObjectCore.svelte';
import { showModal } from '../modals/modalTools'; import { showModal } from '../modals/modalTools';
@@ -45,10 +45,25 @@
}); });
}; };
function setOnCurrentDb(value) {
apiCall('connections/update-database', {
conid: $currentDatabase?.connection?._id,
database: $currentDatabase?.name,
values: {
[`useApp:${data.name}`]: value,
},
});
}
function createMenu() { function createMenu() {
return [ return [
{ text: 'Delete', onClick: handleDelete }, { text: 'Delete', onClick: handleDelete },
{ text: 'Rename', onClick: handleRename }, { text: 'Rename', onClick: handleRename },
$currentDatabase && [
{ text: 'Enable on current database', onClick: () => setOnCurrentDb(true) },
{ text: 'Disable on current database', onClick: () => setOnCurrentDb(false) },
],
]; ];
} }
</script> </script>

View File

@@ -26,7 +26,7 @@
import { getDatabaseMenuItems } from './DatabaseAppObject.svelte'; import { getDatabaseMenuItems } from './DatabaseAppObject.svelte';
import getElectron from '../utility/getElectron'; import getElectron from '../utility/getElectron';
import getConnectionLabel from '../utility/getConnectionLabel'; import getConnectionLabel from '../utility/getConnectionLabel';
import { getDatabaseList } from '../utility/metadataLoaders'; import { getDatabaseList, useDbApps } from '../utility/metadataLoaders';
import { getLocalStorage } from '../utility/storageCache'; import { getLocalStorage } from '../utility/storageCache';
import { apiCall } from '../utility/api'; import { apiCall } from '../utility/api';
@@ -153,7 +153,7 @@
], ],
data.singleDatabase && [ data.singleDatabase && [
{ divider: true }, { divider: true },
getDatabaseMenuItems(data, data.defaultDatabase, $extensions, $currentDatabase), getDatabaseMenuItems(data, data.defaultDatabase, $extensions, $currentDatabase, $apps),
], ],
]; ];
}; };
@@ -186,6 +186,8 @@
statusTitle = null; statusTitle = null;
} }
} }
$: apps = useDbApps({ conid: data?._id, database: data.defaultDatabase });
</script> </script>
<AppObjectCore <AppObjectCore

View File

@@ -1,7 +1,7 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
export const extractKey = props => props.name; export const extractKey = props => props.name;
export function getDatabaseMenuItems(connection, name, $extensions, $currentDatabase) { export function getDatabaseMenuItems(connection, name, $extensions, $currentDatabase, $apps) {
const handleNewQuery = () => { const handleNewQuery = () => {
const tooltip = `${getConnectionLabel(connection)}\n${name}`; const tooltip = `${getConnectionLabel(connection)}\n${name}`;
openNewTab({ openNewTab({
@@ -157,8 +157,20 @@
openJsonDocument(db, name); 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 driver = findEngineDriver(connection, getExtensions());
const commands = _.flatten(($apps || []).map(x => x.commands || []));
return [ return [
{ onClick: handleNewQuery, text: 'New query', isNewQuery: true }, { onClick: handleNewQuery, text: 'New query', isNewQuery: true },
!driver?.dialect?.nosql && { onClick: handleNewTable, text: 'New table' }, !driver?.dialect?.nosql && { onClick: handleNewTable, text: 'New table' },
@@ -180,6 +192,20 @@
_.get($currentDatabase, 'connection._id') == _.get(connection, '_id') && _.get($currentDatabase, 'connection._id') == _.get(connection, '_id') &&
_.get($currentDatabase, 'name') == name && { onClick: handleDisconnect, text: 'Disconnect' }, _.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,
});
},
})),
],
]; ];
} }
</script> </script>
@@ -207,18 +233,21 @@
import { showSnackbarSuccess } from '../utility/snackbar'; import { showSnackbarSuccess } from '../utility/snackbar';
import { findEngineDriver } from 'dbgate-tools'; import { findEngineDriver } from 'dbgate-tools';
import InputTextModal from '../modals/InputTextModal.svelte'; import InputTextModal from '../modals/InputTextModal.svelte';
import { getDatabaseInfo } from '../utility/metadataLoaders'; import { getDatabaseInfo, useDbApps } from '../utility/metadataLoaders';
import { openJsonDocument } from '../tabs/JsonTab.svelte'; import { openJsonDocument } from '../tabs/JsonTab.svelte';
import { apiCall } from '../utility/api'; import { apiCall } from '../utility/api';
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
export let data; export let data;
export let passProps; export let passProps;
function createMenu() { 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); $: isPinned = !!$pinnedDatabases.find(x => x.name == data.name && x.connection?._id == data.connection?._id);
$: apps = useDbApps({ conid: data?.connection?._id, database: data?.name });
</script> </script>
<AppObjectCore <AppObjectCore

View File

@@ -115,6 +115,12 @@ const appFilesLoader = ({ folder }) => ({
reloadTrigger: `app-files-changed-${folder}`, 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 = () => ({ const serverStatusLoader = () => ({
url: 'server-connections/server-status', url: 'server-connections/server-status',
params: {}, params: {},
@@ -427,6 +433,13 @@ export function useAppFolders(args = {}) {
return useCore(appFoldersLoader, 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 = {}) { export function getInstalledPlugins(args = {}) {
return getCore(installedPluginsLoader, args) || []; return getCore(installedPluginsLoader, args) || [];
} }

View File

@@ -3,6 +3,14 @@
'command.sql': 'SQL commands', 'command.sql': 'SQL commands',
'query.sql': 'SQL queries', '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
`;
</script> </script>
<script lang="ts"> <script lang="ts">
@@ -39,7 +47,7 @@
apiCall('apps/refresh-files', { folder }); apiCall('apps/refresh-files', { folder });
}; };
function handleNewSqlFile(fileType, header) { function handleNewSqlFile(fileType, header, initialData) {
showModal(InputTextModal, { showModal(InputTextModal, {
value: '', value: '',
label: 'New file name', label: 'New file name',
@@ -47,6 +55,7 @@
onConfirm: async file => { onConfirm: async file => {
newQuery({ newQuery({
title: file, title: file,
initialData,
// @ts-ignore // @ts-ignore
savedFile: file + '.' + fileType, savedFile: file + '.' + fileType,
savedFolder: 'app:' + $currentApplication, savedFolder: 'app:' + $currentApplication,
@@ -59,8 +68,11 @@
function createAddMenu() { function createAddMenu() {
return [ return [
{ text: 'New SQL command', onClick: () => handleNewSqlFile('command.sql', 'Create new SQL command') }, {
{ text: 'New query view', onClick: () => handleNewSqlFile('query.sql', 'Create new SQL query') }, text: 'New SQL command',
onClick: () => handleNewSqlFile('command.sql', 'Create new SQL command', COMMAND_TEMPLATE),
},
{ text: 'New query view', onClick: () => handleNewSqlFile('query.sql', 'Create new SQL query', QUERY_TEMPLATE) },
]; ];
} }
</script> </script>