From ceea1a904715bbe4ffeff445acdff13d5099352c Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Wed, 7 Dec 2022 22:05:47 +0100 Subject: [PATCH] mongo server summary --- .../api/src/controllers/serverConnections.js | 36 +++++++++++++++++++ .../api/src/proc/serverConnectionProcess.js | 29 +++++++++++++++ packages/types/engines.d.ts | 15 +++++++- .../web/src/appobj/ConnectionAppObject.svelte | 17 ++++++++- .../web/src/elements/ObjectListControl.svelte | 10 +++--- packages/web/src/tabs/ServerSummaryTab.svelte | 23 ++++++++++++ packages/web/src/tabs/index.js | 2 ++ .../dbgate-plugin-mongo/src/backend/driver.js | 19 ++++++++++ .../src/frontend/driver.js | 1 + 9 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 packages/web/src/tabs/ServerSummaryTab.svelte diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js index 5b06d307..c99d2868 100644 --- a/packages/api/src/controllers/serverConnections.js +++ b/packages/api/src/controllers/serverConnections.js @@ -1,6 +1,7 @@ const connections = require('./connections'); const socket = require('../utility/socket'); const { fork } = require('child_process'); +const uuidv1 = require('uuid/v1'); const _ = require('lodash'); const AsyncLock = require('async-lock'); const { handleProcessCommunication } = require('../utility/processComm'); @@ -13,6 +14,7 @@ module.exports = { opened: [], closed: {}, lastPinged: {}, + requests: {}, handle_databases(conid, { databases }) { const existing = this.opened.find(x => x.conid == conid); @@ -33,6 +35,11 @@ module.exports = { socket.emitChanged(`server-status-changed`); }, handle_ping() {}, + handle_response(conid, { msgid, ...response }) { + const [resolve, reject] = this.requests[msgid]; + resolve(response); + delete this.requests[msgid]; + }, async ensureOpened(conid) { const res = await lock.acquire(conid, async () => { @@ -161,4 +168,33 @@ module.exports = { opened.subprocess.send({ msgtype: 'dropDatabase', name }); return { status: 'ok' }; }, + + sendRequest(conn, message) { + const msgid = uuidv1(); + const promise = new Promise((resolve, reject) => { + this.requests[msgid] = [resolve, reject]; + conn.subprocess.send({ msgid, ...message }); + }); + return promise; + }, + + async loadDataCore(msgtype, { conid, ...args }, req) { + testConnectionPermission(conid, req); + const opened = await this.ensureOpened(conid); + const res = await this.sendRequest(opened, { msgtype, ...args }); + if (res.errorMessage) { + console.error(res.errorMessage); + + return { + errorMessage: res.errorMessage, + }; + } + return res.result || null; + }, + + serverSummary_meta: true, + async serverSummary({ conid }, req) { + testConnectionPermission(conid, req); + return this.loadDataCore('serverSummary', { conid }); + }, }; diff --git a/packages/api/src/proc/serverConnectionProcess.js b/packages/api/src/proc/serverConnectionProcess.js index a700b026..175bb637 100644 --- a/packages/api/src/proc/serverConnectionProcess.js +++ b/packages/api/src/proc/serverConnectionProcess.js @@ -10,6 +10,7 @@ let storedConnection; let lastDatabases = null; let lastStatus = null; let lastPing = null; +let afterConnectCallbacks = []; async function handleRefresh() { const driver = requireEngineDriver(storedConnection); @@ -74,6 +75,18 @@ async function handleConnect(connection) { // console.error(err); setTimeout(() => process.exit(1), 1000); } + + for (const [resolve] of afterConnectCallbacks) { + resolve(); + } + afterConnectCallbacks = []; +} + +function waitConnected() { + if (systemConnection) return Promise.resolve(); + return new Promise((resolve, reject) => { + afterConnectCallbacks.push([resolve, reject]); + }); } function handlePing() { @@ -94,9 +107,25 @@ async function handleDatabaseOp(op, { name }) { await handleRefresh(); } +async function handleDriverDataCore(msgid, callMethod) { + await waitConnected(); + const driver = requireEngineDriver(storedConnection); + try { + const result = await callMethod(driver); + process.send({ msgtype: 'response', msgid, result }); + } catch (err) { + process.send({ msgtype: 'response', msgid, errorMessage: err.message }); + } +} + +async function handleServerSummary({ msgid, options }) { + return handleDriverDataCore(msgid, driver => driver.serverSummary(systemConnection)); +} + const messageHandlers = { connect: handleConnect, ping: handlePing, + serverSummary: handleServerSummary, createDatabase: props => handleDatabaseOp('createDatabase', props), dropDatabase: props => handleDatabaseOp('dropDatabase', props), }; diff --git a/packages/types/engines.d.ts b/packages/types/engines.d.ts index 6673b64a..f0e0b8dd 100644 --- a/packages/types/engines.d.ts +++ b/packages/types/engines.d.ts @@ -55,6 +55,17 @@ export interface SqlBackupDumper { run(); } +export interface SummaryColumn { + fieldName: string; + header: string; + dataType: 'string' | 'number' | 'bytes'; +} +export interface ServerSummaryDatabase {} +export interface ServerSummary { + columns: SummaryColumn[]; + databases: ServerSummaryDatabase[]; +} + export interface EngineDriver { engine: string; title: string; @@ -65,6 +76,7 @@ export interface EngineDriver { supportedKeyTypes: SupportedDbKeyType[]; supportsDatabaseUrl?: boolean; supportsDatabaseDump?: boolean; + supportsServerSummary?: boolean; isElectronOnly?: boolean; supportedCreateDatabase?: boolean; showConnectionField?: (field: string, values: any) => boolean; @@ -81,7 +93,7 @@ export interface EngineDriver { stream(pool: any, sql: string, options: StreamOptions); readQuery(pool: any, sql: string, structure?: TableInfo): Promise; readJsonQuery(pool: any, query: any, structure?: TableInfo): Promise; - writeTable(pool: any, name: NamedObjectInfo, options: WriteTableOptions): Promise; + writeTable(pool: any, name: NamedObjectInfo, options: WriteTableOptions): Promise; analyseSingleObject( pool: any, name: NamedObjectInfo, @@ -116,6 +128,7 @@ export interface EngineDriver { getNewObjectTemplates(): NewObjectTemplate[]; // direct call of pool method, only some methods could be supported, on only some drivers callMethod(pool, method, args); + loadSummary(pool): Promise; analyserClass?: any; dumperClass?: any; diff --git a/packages/web/src/appobj/ConnectionAppObject.svelte b/packages/web/src/appobj/ConnectionAppObject.svelte index 5b00eeae..c3b415b2 100644 --- a/packages/web/src/appobj/ConnectionAppObject.svelte +++ b/packages/web/src/appobj/ConnectionAppObject.svelte @@ -104,7 +104,7 @@ import ImportDatabaseDumpModal from '../modals/ImportDatabaseDumpModal.svelte'; import { closeMultipleTabs } from '../widgets/TabsPanel.svelte'; import AboutModal from '../modals/AboutModal.svelte'; -import { tick } from 'svelte'; + import { tick } from 'svelte'; export let data; export let passProps; @@ -195,6 +195,16 @@ import { tick } from 'svelte'; }), }); }; + const handleServerSummary = () => { + openNewTab({ + title: getConnectionLabel(data), + icon: 'img server', + tabComponent: 'ServerSummaryTab', + props: { + conid: data._id, + }, + }); + }; const handleNewQuery = () => { const tooltip = `${getConnectionLabel(data)}`; openNewTab({ @@ -244,6 +254,11 @@ import { tick } from 'svelte'; text: 'Create database', onClick: handleCreateDatabase, }, + $openedConnections.includes(data._id) && + driver?.supportsServerSummary && { + text: 'Server summary', + onClick: handleServerSummary, + }, ], data.singleDatabase && [ { divider: true }, diff --git a/packages/web/src/elements/ObjectListControl.svelte b/packages/web/src/elements/ObjectListControl.svelte index f3b1770b..e889124e 100644 --- a/packages/web/src/elements/ObjectListControl.svelte +++ b/packages/web/src/elements/ObjectListControl.svelte @@ -1,8 +1,7 @@ @@ -31,14 +31,14 @@
diff --git a/packages/web/src/tabs/ServerSummaryTab.svelte b/packages/web/src/tabs/ServerSummaryTab.svelte new file mode 100644 index 00000000..2752c743 --- /dev/null +++ b/packages/web/src/tabs/ServerSummaryTab.svelte @@ -0,0 +1,23 @@ + + +{#await apiCall('server-connections/server-summary', { conid, refreshToken })} + +{:then summary} + +{/await} diff --git a/packages/web/src/tabs/index.js b/packages/web/src/tabs/index.js index 0cc22df2..db9ab503 100644 --- a/packages/web/src/tabs/index.js +++ b/packages/web/src/tabs/index.js @@ -26,6 +26,7 @@ import * as QueryDataTab from './QueryDataTab.svelte'; import * as ConnectionTab from './ConnectionTab.svelte'; import * as MapTab from './MapTab.svelte'; import * as PerspectiveTab from './PerspectiveTab.svelte'; +import * as ServerSummaryTab from './ServerSummaryTab.svelte'; export default { TableDataTab, @@ -56,4 +57,5 @@ export default { ConnectionTab, MapTab, PerspectiveTab, + ServerSummaryTab, }; diff --git a/plugins/dbgate-plugin-mongo/src/backend/driver.js b/plugins/dbgate-plugin-mongo/src/backend/driver.js index a1041287..c44ac93d 100644 --- a/plugins/dbgate-plugin-mongo/src/backend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/backend/driver.js @@ -351,6 +351,25 @@ const driver = { return res; }, + + async serverSummary(pool) { + const res = await pool.__getDatabase().admin().listDatabases(); + return { + columns: [ + { + fieldName: 'name', + dataType: 'string', + header: 'Name', + }, + { + fieldName: 'sizeOnDisk', + dataType: 'bytes', + header: 'Size', + }, + ], + databases: res.databases, + }; + }, }; module.exports = driver; diff --git a/plugins/dbgate-plugin-mongo/src/frontend/driver.js b/plugins/dbgate-plugin-mongo/src/frontend/driver.js index e2cc3701..ca50a073 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/driver.js @@ -32,6 +32,7 @@ const driver = { editorMode: 'javascript', defaultPort: 27017, supportsDatabaseUrl: true, + supportsServerSummary: true, databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname', getQuerySplitterOptions: () => mongoSplitterOptions,