From 83014d3a5bf47f44319d9306637db616cf02e045 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 25 Apr 2021 21:53:48 +0200 Subject: [PATCH 01/26] v4.1.10-beta.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fc26b16c..eea67d756 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "4.1.10-beta.5", + "version": "4.1.10-beta.6", "name": "dbgate-all", "workspaces": [ "packages/*", From 771ca6ad83a21c92ce9bbc523225278d92ebc787 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 26 Apr 2021 17:51:26 +0200 Subject: [PATCH 02/26] v4.1.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eea67d756..5fe7bcc33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "4.1.10-beta.6", + "version": "4.1.10", "name": "dbgate-all", "workspaces": [ "packages/*", From 09593e0b22a78d8b535894a0cfeb3a4b74f6f3e6 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 26 Apr 2021 18:33:57 +0200 Subject: [PATCH 03/26] changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77e958e45..0510a95ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # ChangeLog +### 4.1.10 +- ADDED: Default database option in connectin settings #96 #92 +- FIX: Bundle size optimalization for Windows #97 +- FIX: Popup menu placement on smaller displays #94 +- ADDED: Browse table data with SQL Server 2008 #93 +- FIX: Prevented malicious origins / DNS rebinding #91 +- ADDED: Handle JSON fields in data editor (eg. jsonb field in Postgres) #90 +- FIX: Fixed crash on Windows with Hyper-V #86 +- ADDED: Show database server version in status bar +- ADDED: Show detailed info about error, when connect to database fails +### 4.1.9 +- FIX: Incorrect row count info in query result #83 + ### 4.1.1 - CHANGED: Default plugins are now part of installation ### 4.1.0 From d171d7d7855cd0894e2131efe546a52039f79a99 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 26 Apr 2021 18:56:42 +0200 Subject: [PATCH 04/26] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0510a95ac..305add02f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - FIX: Fixed crash on Windows with Hyper-V #86 - ADDED: Show database server version in status bar - ADDED: Show detailed info about error, when connect to database fails +- ADDED: Portable ZIP distribution for Windows #84 ### 4.1.9 - FIX: Incorrect row count info in query result #83 From 00d0c27502db2116d5d4aac8ece2e480129b4155 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 07:38:44 +0200 Subject: [PATCH 05/26] handle plugin load error --- packages/api/src/controllers/plugins.js | 28 +++++++++++--------- packages/web/src/plugins/PluginsList.svelte | 2 +- packages/web/src/utility/ErrorHandler.svelte | 2 ++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/api/src/controllers/plugins.js b/packages/api/src/controllers/plugins.js index 1802dbc4d..a14ff6e14 100644 --- a/packages/api/src/controllers/plugins.js +++ b/packages/api/src/controllers/plugins.js @@ -64,19 +64,23 @@ module.exports = { const res = []; for (const packageName of _.union(files1, files2)) { if (!/^dbgate-plugin-.*$/.test(packageName)) continue; - const isPackaged = files1.includes(packageName); - const manifest = await fs - .readFile(path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'package.json'), { - encoding: 'utf-8', - }) - .then(x => JSON.parse(x)); - const readmeFile = path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'README.md'); - // @ts-ignore - if (await fs.exists(readmeFile)) { - manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' }); + try { + const isPackaged = files1.includes(packageName); + const manifest = await fs + .readFile(path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'package.json'), { + encoding: 'utf-8', + }) + .then(x => JSON.parse(x)); + const readmeFile = path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'README.md'); + // @ts-ignore + if (await fs.exists(readmeFile)) { + manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' }); + } + manifest.isPackaged = isPackaged; + res.push(manifest); + } catch (err) { + console.log(`Skipped plugin ${packageName}, error:`, err.message); } - manifest.isPackaged = isPackaged; - res.push(manifest); } return res; }, diff --git a/packages/web/src/plugins/PluginsList.svelte b/packages/web/src/plugins/PluginsList.svelte index e90c088da..b27d889ef 100644 --- a/packages/web/src/plugins/PluginsList.svelte +++ b/packages/web/src/plugins/PluginsList.svelte @@ -17,7 +17,7 @@ } -{#each plugins as packageManifest (packageManifest.name)} +{#each plugins || [] as packageManifest (packageManifest.name)}
openPlugin(packageManifest)}>
diff --git a/packages/web/src/utility/ErrorHandler.svelte b/packages/web/src/utility/ErrorHandler.svelte index 11ec79245..4d405e3a4 100644 --- a/packages/web/src/utility/ErrorHandler.svelte +++ b/packages/web/src/utility/ErrorHandler.svelte @@ -13,6 +13,8 @@ console.log('CRASH DETECTED!!!'); const lastDbGateCrashJson = localStorage.getItem('lastDbGateCrash'); const lastDbGateCrash = lastDbGateCrashJson ? JSON.parse(lastDbGateCrashJson) : null; + // let detail = e?.reason?.stack || ''; + // if (detail) detail = '\n\n' + detail; if (lastDbGateCrash && new Date().getTime() - lastDbGateCrash < 30 * 1000) { if ( From 505ab2e07503d63d07392145d153d78017b94324 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 08:28:00 +0200 Subject: [PATCH 06/26] editor theme to be added --- packages/web/src/query/AceEditor.svelte | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/web/src/query/AceEditor.svelte b/packages/web/src/query/AceEditor.svelte index 1274fe046..a739d424f 100644 --- a/packages/web/src/query/AceEditor.svelte +++ b/packages/web/src/query/AceEditor.svelte @@ -11,10 +11,15 @@ import 'ace-builds/src-noconflict/mode-json'; import 'ace-builds/src-noconflict/mode-javascript'; import 'ace-builds/src-noconflict/mode-markdown'; - import 'ace-builds/src-noconflict/theme-github'; - import 'ace-builds/src-noconflict/theme-twilight'; import 'ace-builds/src-noconflict/ext-searchbox'; import 'ace-builds/src-noconflict/ext-language_tools'; + + import 'ace-builds/src-noconflict/theme-github'; + // import 'ace-builds/src-noconflict/theme-sqlserver'; + + import 'ace-builds/src-noconflict/theme-twilight'; + // import 'ace-builds/src-noconflict/theme-monokai'; + import { currentDropDownMenu, currentThemeDefinition } from '../stores'; import _ from 'lodash'; import { handleCommandKeyDown } from '../commands/CommandListener.svelte'; From 519767fd49c92660c0cea369df6599b58552e615 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 08:55:38 +0200 Subject: [PATCH 07/26] fixed postgres split query --- packages/tools/src/index.ts | 1 - packages/tools/src/splitPostgresQuery.ts | 292 ------------------ plugins/dbgate-plugin-postgres/package.json | 9 +- .../src/backend/driver.js | 8 +- yarn.lock | 5 + 5 files changed, 15 insertions(+), 300 deletions(-) delete mode 100644 packages/tools/src/splitPostgresQuery.ts diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 956184ec4..7ab818b54 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -7,6 +7,5 @@ export * from './DatabaseAnalyser'; export * from './driverBase'; export * from './SqlDumper'; export * from './testPermission'; -export * from './splitPostgresQuery'; export * from './SqlGenerator'; export * from './structureTools'; diff --git a/packages/tools/src/splitPostgresQuery.ts b/packages/tools/src/splitPostgresQuery.ts deleted file mode 100644 index 2986def1c..000000000 --- a/packages/tools/src/splitPostgresQuery.ts +++ /dev/null @@ -1,292 +0,0 @@ -const SINGLE_QUOTE = "'"; -const DOUBLE_QUOTE = '"'; -// const BACKTICK = '`'; -const DOUBLE_DASH_COMMENT_START = '--'; -const HASH_COMMENT_START = '#'; -const C_STYLE_COMMENT_START = '/*'; -const SEMICOLON = ';'; -const LINE_FEED = '\n'; -const DELIMITER_KEYWORD = 'DELIMITER'; - -export interface SplitOptions { - multipleStatements?: boolean; - retainComments?: boolean; -} - -interface SqlStatement { - value: string; - supportMulti: boolean; -} - -interface SplitExecutionContext extends Required { - unread: string; - currentDelimiter: string; - currentStatement: SqlStatement; - output: SqlStatement[]; -} - -interface FindExpResult { - expIndex: number; - exp: string | null; - nextIndex: number; -} - -const regexEscapeSetRegex = /[-/\\^$*+?.()|[\]{}]/g; -const singleQuoteStringEndRegex = /(? = { - [SINGLE_QUOTE]: singleQuoteStringEndRegex, - [DOUBLE_QUOTE]: doubleQuoteStringEndRegex, - // [BACKTICK]: backtickQuoteEndRegex, -}; - -function escapeRegex(value: string): string { - return value.replace(regexEscapeSetRegex, '\\$&'); -} - -function buildKeyTokenRegex(delimiter: string): RegExp { - return new RegExp( - '(?:' + - [ - escapeRegex(delimiter), - SINGLE_QUOTE, - DOUBLE_QUOTE, - // BACKTICK, - doubleDashCommentStartRegex.source, - HASH_COMMENT_START, - cStyleCommentStartRegex.source, - delimiterStartRegex.source, - ].join('|') + - ')', - 'i' - ); -} - -function findExp(content: string, regex: RegExp): FindExpResult { - const match = content.match(regex); - let result: FindExpResult; - if (match?.index !== undefined) { - result = { - expIndex: match.index, - exp: match[0], - nextIndex: match.index + match[0].length, - }; - } else { - result = { - expIndex: -1, - exp: null, - nextIndex: content.length, - }; - } - return result; -} - -function findKeyToken(content: string, currentDelimiter: string): FindExpResult { - let regex; - if (currentDelimiter === SEMICOLON) { - regex = semicolonKeyTokenRegex; - } else { - regex = buildKeyTokenRegex(currentDelimiter); - } - return findExp(content, regex); -} - -function findEndQuote(content: string, quote: string): FindExpResult { - if (!(quote in quoteEndRegexDict)) { - throw new TypeError(`Incorrect quote ${quote} supplied`); - } - return findExp(content, quoteEndRegexDict[quote]); -} - -function read( - context: SplitExecutionContext, - readToIndex: number, - nextUnreadIndex?: number, - checkSemicolon?: boolean -): void { - if (checkSemicolon === undefined) { - checkSemicolon = true; - } - const readContent = context.unread.slice(0, readToIndex); - if (checkSemicolon && readContent.includes(SEMICOLON)) { - context.currentStatement.supportMulti = false; - } - context.currentStatement.value += readContent; - if (nextUnreadIndex !== undefined && nextUnreadIndex > 0) { - context.unread = context.unread.slice(nextUnreadIndex); - } else { - context.unread = context.unread.slice(readToIndex); - } -} - -function readTillNewLine(context: SplitExecutionContext, checkSemicolon?: boolean): void { - const findResult = findExp(context.unread, newLineRegex); - read(context, findResult.expIndex, findResult.expIndex, checkSemicolon); -} - -function discard(context: SplitExecutionContext, nextUnreadIndex: number): void { - if (nextUnreadIndex > 0) { - context.unread = context.unread.slice(nextUnreadIndex); - } -} - -function discardTillNewLine(context: SplitExecutionContext): void { - const findResult = findExp(context.unread, newLineRegex); - discard(context, findResult.expIndex); -} - -function publishStatementInMultiMode(splitOutput: SqlStatement[], currentStatement: SqlStatement): void { - if (splitOutput.length === 0) { - splitOutput.push({ - value: '', - supportMulti: true, - }); - } - const lastSplitResult = splitOutput[splitOutput.length - 1]; - if (currentStatement.supportMulti) { - if (lastSplitResult.supportMulti) { - if (lastSplitResult.value !== '' && !lastSplitResult.value.endsWith(LINE_FEED)) { - lastSplitResult.value += LINE_FEED; - } - lastSplitResult.value += currentStatement.value + SEMICOLON; - } else { - splitOutput.push({ - value: currentStatement.value + SEMICOLON, - supportMulti: true, - }); - } - } else { - splitOutput.push({ - value: currentStatement.value, - supportMulti: false, - }); - } -} - -function publishStatement(context: SplitExecutionContext): void { - const trimmed = context.currentStatement.value.trim(); - if (trimmed !== '') { - if (!context.multipleStatements) { - context.output.push({ - value: trimmed, - supportMulti: context.currentStatement.supportMulti, - }); - } else { - context.currentStatement.value = trimmed; - publishStatementInMultiMode(context.output, context.currentStatement); - } - } - context.currentStatement.value = ''; - context.currentStatement.supportMulti = true; -} - -function handleKeyTokenFindResult(context: SplitExecutionContext, findResult: FindExpResult): void { - switch (findResult.exp?.trim()) { - case context.currentDelimiter: - read(context, findResult.expIndex, findResult.nextIndex); - publishStatement(context); - break; - // case BACKTICK: - case SINGLE_QUOTE: - case DOUBLE_QUOTE: { - read(context, findResult.nextIndex); - const findQuoteResult = findEndQuote(context.unread, findResult.exp); - read(context, findQuoteResult.nextIndex, undefined, false); - break; - } - case DOUBLE_DASH_COMMENT_START: { - if (context.retainComments) { - read(context, findResult.nextIndex); - readTillNewLine(context, false); - } else { - read(context, findResult.expIndex, findResult.expIndex + DOUBLE_DASH_COMMENT_START.length); - discardTillNewLine(context); - } - break; - } - case HASH_COMMENT_START: { - if (context.retainComments) { - read(context, findResult.nextIndex); - readTillNewLine(context, false); - } else { - read(context, findResult.expIndex, findResult.nextIndex); - discardTillNewLine(context); - } - break; - } - case C_STYLE_COMMENT_START: { - if (['!', '+'].includes(context.unread[findResult.nextIndex]) || context.retainComments) { - // Should not be skipped, see https://dev.mysql.com/doc/refman/5.7/en/comments.html - read(context, findResult.nextIndex); - const findCommentResult = findExp(context.unread, cStyleCommentEndRegex); - read(context, findCommentResult.nextIndex); - } else { - read(context, findResult.expIndex, findResult.nextIndex); - const findCommentResult = findExp(context.unread, cStyleCommentEndRegex); - discard(context, findCommentResult.nextIndex); - } - break; - } - case DELIMITER_KEYWORD: { - read(context, findResult.expIndex, findResult.nextIndex); - // MySQL client will return `DELIMITER cannot contain a backslash character` if backslash is used - // Shall we reject backslash as well? - const matched = context.unread.match(delimiterTokenRegex); - if (matched?.index !== undefined) { - context.currentDelimiter = matched[0].trim(); - discard(context, matched[0].length); - } - discardTillNewLine(context); - break; - } - case undefined: - case null: - read(context, findResult.nextIndex); - publishStatement(context); - break; - default: - // This should never happen - throw new Error(`Unknown token '${findResult.exp ?? '(null)'}'`); - } -} - -export function splitPostgresQuery(sql: string, options?: SplitOptions): string[] { - options = options ?? {}; - const context: SplitExecutionContext = { - multipleStatements: options.multipleStatements ?? false, - retainComments: options.retainComments ?? false, - unread: sql, - currentDelimiter: SEMICOLON, - currentStatement: { - value: '', - supportMulti: true, - }, - output: [], - }; - let findResult: FindExpResult = { - expIndex: -1, - exp: null, - nextIndex: 0, - }; - let lastUnreadLength; - do { - lastUnreadLength = context.unread.length; - findResult = findKeyToken(context.unread, context.currentDelimiter); - handleKeyTokenFindResult(context, findResult); - // Prevent infinite loop by returning incorrect result - if (lastUnreadLength === context.unread.length) { - read(context, context.unread.length); - } - } while (context.unread !== ''); - publishStatement(context); - return context.output.map(v => v.value); -} diff --git a/plugins/dbgate-plugin-postgres/package.json b/plugins/dbgate-plugin-postgres/package.json index a3c14e8a8..6fc72f3c8 100644 --- a/plugins/dbgate-plugin-postgres/package.json +++ b/plugins/dbgate-plugin-postgres/package.json @@ -31,11 +31,12 @@ }, "devDependencies": { "dbgate-plugin-tools": "^1.0.7", - "webpack": "^4.42.0", - "webpack-cli": "^3.3.11", "dbgate-tools": "^4.1.1", "lodash": "^4.17.15", "pg": "^7.17.0", - "pg-query-stream": "^3.1.1" + "pg-query-stream": "^3.1.1", + "webpack": "^4.42.0", + "webpack-cli": "^3.3.11", + "sql-query-identifier": "^2.1.0" } -} \ No newline at end of file +} diff --git a/plugins/dbgate-plugin-postgres/src/backend/driver.js b/plugins/dbgate-plugin-postgres/src/backend/driver.js index 0373c8dda..5caec35ac 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/driver.js +++ b/plugins/dbgate-plugin-postgres/src/backend/driver.js @@ -1,10 +1,12 @@ const _ = require('lodash'); const stream = require('stream'); +const { identify } = require('sql-query-identifier'); + const driverBase = require('../frontend/driver'); const Analyser = require('./Analyser'); const pg = require('pg'); const pgQueryStream = require('pg-query-stream'); -const { createBulkInsertStreamBase, splitPostgresQuery, makeUniqueColumnNames } = require('dbgate-tools'); +const { createBulkInsertStreamBase, makeUniqueColumnNames } = require('dbgate-tools'); function extractPostgresColumns(result) { if (!result || !result.fields) return []; @@ -119,10 +121,10 @@ const driver = { return { rows: res.rows.map(row => zipDataRow(row, columns)), columns }; }, async stream(client, sql, options) { - const sqlSplitted = splitPostgresQuery(sql); + const sqlSplitted = identify(sql, { dialect: 'psql' }); for (const sqlItem of sqlSplitted) { - await runStreamItem(client, sqlItem, options); + await runStreamItem(client, sqlItem.text, options); } options.done(); diff --git a/yarn.lock b/yarn.lock index 41aeb66b1..d4391016a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8215,6 +8215,11 @@ sql-formatter@^2.3.3: dependencies: lodash "^4.16.0" +sql-query-identifier@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/sql-query-identifier/-/sql-query-identifier-2.1.0.tgz#dbf0f34b11bc14c8ade44de13350271047eb566e" + integrity sha512-DcC+inWZvN6fiTyxv+9uhFoTRC9C8LTeApVl1N7JJTTCzto6yhuaI423DzPPqDk10z4naL2mF9g/eNhUfxuMpA== + sqlstring@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514" From 7b324241434d818eeb528c6bfb0dcc8f512882c2 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 09:31:41 +0200 Subject: [PATCH 08/26] fix --- packages/web/src/datagrid/gridutil.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/web/src/datagrid/gridutil.ts b/packages/web/src/datagrid/gridutil.ts index 60878d367..d5f91ea5f 100644 --- a/packages/web/src/datagrid/gridutil.ts +++ b/packages/web/src/datagrid/gridutil.ts @@ -54,6 +54,9 @@ export function countColumnSizes(grider: Grider, columns, containerWidth, displa context.font = '14px Helvetica'; for (let rowIndex = 0; rowIndex < Math.min(grider.rowCount, 20); rowIndex += 1) { const row = grider.getRowData(rowIndex); + if (!row) { + continue; + } for (let colIndex = 0; colIndex < columns.length; colIndex++) { const uqName = columns[colIndex].uniqueName; From e3d1e4f53e5a9b507f22412fa21372c6a055312e Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 09:32:59 +0200 Subject: [PATCH 09/26] fixed analysing postgre functions #105 --- plugins/dbgate-plugin-postgres/src/backend/sql/routines.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/routines.js b/plugins/dbgate-plugin-postgres/src/backend/sql/routines.js index 80c189604..56ce1f12d 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/sql/routines.js +++ b/plugins/dbgate-plugin-postgres/src/backend/sql/routines.js @@ -8,8 +8,8 @@ select from information_schema.routines where routine_schema != 'information_schema' and routine_schema != 'pg_catalog' and ( - (routine_type = 'PROCEDURE' and ('procedures:' || routine_schema || '.' || routine_schema) =OBJECT_ID_CONDITION) + (routine_type = 'PROCEDURE' and ('procedures:' || routine_schema || '.' || routine_name) =OBJECT_ID_CONDITION) or - (routine_type = 'FUNCTION' and ('functions:' || routine_schema || '.' || routine_schema) =OBJECT_ID_CONDITION) + (routine_type = 'FUNCTION' and ('functions:' || routine_schema || '.' || routine_name) =OBJECT_ID_CONDITION) ) `; From c24cc1dc727b1286821de621e5017835b8c62eb6 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 10:03:13 +0200 Subject: [PATCH 10/26] patched svelte crash #105 --- patches/svelte+3.35.0.patch | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 patches/svelte+3.35.0.patch diff --git a/patches/svelte+3.35.0.patch b/patches/svelte+3.35.0.patch new file mode 100644 index 000000000..e8b2ef0b0 --- /dev/null +++ b/patches/svelte+3.35.0.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/svelte/internal/index.js b/node_modules/svelte/internal/index.js +index ee20a17..7b6fff8 100644 +--- a/node_modules/svelte/internal/index.js ++++ b/node_modules/svelte/internal/index.js +@@ -200,7 +200,7 @@ function insert(target, node, anchor) { + target.insertBefore(node, anchor || null); + } + function detach(node) { +- node.parentNode.removeChild(node); ++ if (node.parentNode) node.parentNode.removeChild(node); + } + function destroy_each(iterations, detaching) { + for (let i = 0; i < iterations.length; i += 1) { From fd6524867e6d0ec4509d1fa8dabf322a310e361f Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 10:40:53 +0200 Subject: [PATCH 11/26] check & load db model in statusbar --- packages/api/src/proc/databaseConnectionProcess.js | 2 ++ packages/web/src/widgets/StatusBar.svelte | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/packages/api/src/proc/databaseConnectionProcess.js b/packages/api/src/proc/databaseConnectionProcess.js index 6a0724670..273da714c 100644 --- a/packages/api/src/proc/databaseConnectionProcess.js +++ b/packages/api/src/proc/databaseConnectionProcess.js @@ -29,6 +29,7 @@ async function checkedAsyncCall(promise) { async function handleFullRefresh() { const driver = requireEngineDriver(storedConnection); + setStatusName('loadStructure'); analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection)); process.send({ msgtype: 'structure', structure: analysedStructure }); setStatusName('ok'); @@ -36,6 +37,7 @@ async function handleFullRefresh() { async function handleIncrementalRefresh() { const driver = requireEngineDriver(storedConnection); + setStatusName('checkStructure'); const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure)); if (newStructure != null) { analysedStructure = newStructure; diff --git a/packages/web/src/widgets/StatusBar.svelte b/packages/web/src/widgets/StatusBar.svelte index ff0a684ae..e22764b3a 100644 --- a/packages/web/src/widgets/StatusBar.svelte +++ b/packages/web/src/widgets/StatusBar.svelte @@ -49,6 +49,10 @@
{#if $status.name == 'pending'} Loading + {:else if $status.name == 'checkStructure'} + Checking model + {:else if $status.name == 'loadStructure'} + Loading model {:else if $status.name == 'ok'} Connected {:else if $status.name == 'error'} From e647ab471e7f4c349c8c0b111d4ffda1b591638d Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 11:17:17 +0200 Subject: [PATCH 12/26] ability to disable background model updates --- .../src/controllers/databaseConnections.js | 2 ++ .../api/src/controllers/serverConnections.js | 3 +- .../api/src/proc/databaseConnectionProcess.js | 12 +++++-- .../api/src/proc/serverConnectionProcess.js | 6 +++- packages/tools/src/index.ts | 1 + packages/tools/src/settingsExtractors.ts | 20 +++++++++++ .../web/src/settings/SettingsModal.svelte | 36 +++++++++++++------ 7 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 packages/tools/src/settingsExtractors.ts diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index 02576a5a9..e56010538 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -4,6 +4,7 @@ const socket = require('../utility/socket'); const { fork } = require('child_process'); const { DatabaseAnalyser } = require('dbgate-tools'); const { handleProcessCommunication } = require('../utility/processComm'); +const config = require('./config'); module.exports = { /** @type {import('dbgate-types').OpenedDatabaseConnection[]} */ @@ -79,6 +80,7 @@ module.exports = { msgtype: 'connect', connection: { ...connection, database }, structure: lastClosed ? lastClosed.structure : null, + globalSettings: await config.getSettings() }); return newOpened; }, diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js index 288ff371d..97548d5fa 100644 --- a/packages/api/src/controllers/serverConnections.js +++ b/packages/api/src/controllers/serverConnections.js @@ -5,6 +5,7 @@ const _ = require('lodash'); const AsyncLock = require('async-lock'); const { handleProcessCommunication } = require('../utility/processComm'); const lock = new AsyncLock(); +const config = require('./config'); module.exports = { opened: [], @@ -65,7 +66,7 @@ module.exports = { if (newOpened.disconnected) return; this.close(conid, false); }); - subprocess.send({ msgtype: 'connect', ...connection }); + subprocess.send({ msgtype: 'connect', ...connection, globalSettings: await config.getSettings() }); return newOpened; }); return res; diff --git a/packages/api/src/proc/databaseConnectionProcess.js b/packages/api/src/proc/databaseConnectionProcess.js index 273da714c..e491d3af7 100644 --- a/packages/api/src/proc/databaseConnectionProcess.js +++ b/packages/api/src/proc/databaseConnectionProcess.js @@ -1,5 +1,6 @@ const stableStringify = require('json-stable-stringify'); const childProcessChecker = require('../utility/childProcessChecker'); +const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools'); const requireEngineDriver = require('../utility/requireEngineDriver'); const connectUtility = require('../utility/connectUtility'); const { handleProcessCommunication } = require('../utility/processComm'); @@ -64,7 +65,7 @@ async function readVersion() { process.send({ msgtype: 'version', version }); } -async function handleConnect({ connection, structure }) { +async function handleConnect({ connection, structure, globalSettings }) { storedConnection = connection; lastPing = new Date().getTime(); @@ -78,7 +79,14 @@ async function handleConnect({ connection, structure }) { } else { handleFullRefresh(); } - setInterval(handleIncrementalRefresh, 30 * 1000); + + if (extractBoolSettingsValue(globalSettings, 'connection.autoRefresh', true)) { + setInterval( + handleIncrementalRefresh, + extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 3, 3600) * 1000 + ); + } + for (const [resolve] of afterConnectCallbacks) { resolve(); } diff --git a/packages/api/src/proc/serverConnectionProcess.js b/packages/api/src/proc/serverConnectionProcess.js index 4adbe3fa0..e47371574 100644 --- a/packages/api/src/proc/serverConnectionProcess.js +++ b/packages/api/src/proc/serverConnectionProcess.js @@ -1,4 +1,5 @@ const stableStringify = require('json-stable-stringify'); +const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools'); const childProcessChecker = require('../utility/childProcessChecker'); const requireEngineDriver = require('../utility/requireEngineDriver'); const { decryptConnection } = require('../utility/crypting'); @@ -51,6 +52,7 @@ function setStatusName(name) { async function handleConnect(connection) { storedConnection = connection; + const { globalSettings } = storedConnection; setStatusName('pending'); lastPing = new Date().getTime(); @@ -59,7 +61,9 @@ async function handleConnect(connection) { systemConnection = await connectUtility(driver, storedConnection); readVersion(); handleRefresh(); - setInterval(handleRefresh, 30 * 1000); + if (extractBoolSettingsValue(globalSettings, 'connection.autoRefresh', true)) { + setInterval(handleRefresh, extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 5, 3600) * 1000); + } } catch (err) { setStatus({ name: 'error', diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 7ab818b54..c3b1c611e 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -9,3 +9,4 @@ export * from './SqlDumper'; export * from './testPermission'; export * from './SqlGenerator'; export * from './structureTools'; +export * from './settingsExtractors'; diff --git a/packages/tools/src/settingsExtractors.ts b/packages/tools/src/settingsExtractors.ts new file mode 100644 index 000000000..843a4442b --- /dev/null +++ b/packages/tools/src/settingsExtractors.ts @@ -0,0 +1,20 @@ +import _ from 'lodash'; + +export function extractIntSettingsValue(settings, name, defaultValue, min = null, max = null) { + const parsed = parseInt(settings[name]); + if (_.isNaN(parsed)) { + return defaultValue; + } + if (_.isNumber(parsed)) { + if (min != null && parsed < min) return min; + if (max != null && parsed > max) return max; + return parsed; + } + return defaultValue; +} + +export function extractBoolSettingsValue(settings, name, defaultValue) { + const res = settings[name]; + if (res == null) return defaultValue; + return !!res; +} diff --git a/packages/web/src/settings/SettingsModal.svelte b/packages/web/src/settings/SettingsModal.svelte index e841341fb..20cc98c76 100644 --- a/packages/web/src/settings/SettingsModal.svelte +++ b/packages/web/src/settings/SettingsModal.svelte @@ -7,6 +7,7 @@ import FormProvider from '../forms/FormProvider.svelte'; import FormSubmit from '../forms/FormSubmit.svelte'; import FormTextField from '../forms/FormTextField.svelte'; + import FormValues from '../forms/FormValues.svelte'; import ModalBase from '../modals/ModalBase.svelte'; import { closeCurrentModal } from '../modals/modalTools'; @@ -32,17 +33,32 @@
Settings
-
Appearance
- + +
Appearance
+ -
Data grid
- - - +
Data grid
+ + + + +
Connection
+ + +
From 47ea474555210e1de87cc169f3335deb47f4d8a0 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 11:28:32 +0200 Subject: [PATCH 13/26] settings optimalization --- packages/api/src/controllers/config.js | 20 ++++++++++++------- .../src/controllers/databaseConnections.js | 2 +- .../api/src/controllers/serverConnections.js | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/api/src/controllers/config.js b/packages/api/src/controllers/config.js index 088af6656..4f6e05e33 100644 --- a/packages/api/src/controllers/config.js +++ b/packages/api/src/controllers/config.js @@ -9,6 +9,16 @@ const currentVersion = require('../currentVersion'); const platformInfo = require('../utility/platformInfo'); module.exports = { + settingsValue: {}, + + async _init() { + try { + this.settingsValue = JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' })); + } catch (err) { + this.settingsValue = {}; + } + }, + get_meta: 'get', async get() { // const toolbarButtons = process.env.TOOLBAR; @@ -47,23 +57,19 @@ module.exports = { getSettings_meta: 'get', async getSettings() { - try { - return JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' })); - } catch (err) { - return {}; - } + return this.settingsValue; }, updateSettings_meta: 'post', async updateSettings(values) { if (!hasPermission(`settings/change`)) return false; - const oldSettings = await this.getSettings(); try { const updated = { - ...oldSettings, + ...this.settingsValue, ...values, }; await fs.writeFile(path.join(datadir(), 'settings.json'), JSON.stringify(updated, undefined, 2)); + this.settingsValue = updated; socket.emitChanged(`settings-changed`); return updated; } catch (err) { diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index e56010538..f8c49c345 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -80,7 +80,7 @@ module.exports = { msgtype: 'connect', connection: { ...connection, database }, structure: lastClosed ? lastClosed.structure : null, - globalSettings: await config.getSettings() + globalSettings: config.settingsValue }); return newOpened; }, diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js index 97548d5fa..e9d253298 100644 --- a/packages/api/src/controllers/serverConnections.js +++ b/packages/api/src/controllers/serverConnections.js @@ -66,7 +66,7 @@ module.exports = { if (newOpened.disconnected) return; this.close(conid, false); }); - subprocess.send({ msgtype: 'connect', ...connection, globalSettings: await config.getSettings() }); + subprocess.send({ msgtype: 'connect', ...connection, globalSettings: config.settingsValue }); return newOpened; }); return res; From c7d69b0fb5bbf9617a421a2a88bdb5b04a19bf89 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 13:25:12 +0200 Subject: [PATCH 14/26] duplicate connection command --- packages/web/src/appobj/ConnectionAppObject.svelte | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/web/src/appobj/ConnectionAppObject.svelte b/packages/web/src/appobj/ConnectionAppObject.svelte index d3e94e020..947e5640c 100644 --- a/packages/web/src/appobj/ConnectionAppObject.svelte +++ b/packages/web/src/appobj/ConnectionAppObject.svelte @@ -19,6 +19,13 @@ onConfirm: () => axiosInstance.post('connections/delete', data), }); }; + const handleDuplicate = () => { + axiosInstance.post('connections/save', { + ...data, + _id: undefined, + displayName: `${data.displayName || data.server} - copy`, + }); + }; const handleCreateDatabase = () => { showModal(InputTextModal, { header: 'Create database', @@ -54,6 +61,10 @@ text: 'Delete', onClick: handleDelete, }, + { + text: 'Duplicate', + onClick: handleDuplicate, + }, ], !data.singleDatabase && [ !$openedConnections.includes(data._id) && { From a8b71d452b0e829b40b528b9c14436c36775bdc6 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 14:05:32 +0200 Subject: [PATCH 15/26] ssh tunnel keyfile auth fix #106 --- packages/api/src/utility/sshTunnel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/utility/sshTunnel.js b/packages/api/src/utility/sshTunnel.js index cf2463bea..d30eced35 100644 --- a/packages/api/src/utility/sshTunnel.js +++ b/packages/api/src/utility/sshTunnel.js @@ -34,7 +34,7 @@ async function getSshConnection(connection) { password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined, agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined, privateKey: - connection.sshMode == 'keyFile' && connection.sshKeyFile ? await fs.readFile(connection.sshKeyFile) : undefined, + connection.sshMode == 'keyFile' && connection.sshKeyfile ? await fs.readFile(connection.sshKeyfile) : undefined, skipAutoPrivateKey: true, noReadline: true, }; From c4c22744888bee2e1c15676f8f0c2a7c96be7719 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 14:06:34 +0200 Subject: [PATCH 16/26] v4.1.11-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5fe7bcc33..6219697d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "4.1.10", + "version": "4.1.11-beta.1", "name": "dbgate-all", "workspaces": [ "packages/*", From 7d789d571232e759602f20415cf5fa7f10a27bb3 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 20:44:46 +0200 Subject: [PATCH 17/26] #109 all tables button in export fixed + added All collections button for nosql --- .../web/src/impexp/FormTablesSelect.svelte | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/web/src/impexp/FormTablesSelect.svelte b/packages/web/src/impexp/FormTablesSelect.svelte index 2757ab6fe..12c3004c9 100644 --- a/packages/web/src/impexp/FormTablesSelect.svelte +++ b/packages/web/src/impexp/FormTablesSelect.svelte @@ -15,11 +15,7 @@ const { values, setFieldValue } = getFormContext(); $: dbinfo = useDatabaseInfo({ conid: $values[conidName], database: $values[databaseName] }); - $: tablesOptions = [ - ...(($dbinfo && $dbinfo.tables) || []), - ...(($dbinfo && $dbinfo.views) || []), - ...(($dbinfo && $dbinfo.collections) || []), - ] + $: tablesOptions = _.compact([...($dbinfo?.tables || []), ...($dbinfo?.views || []), ...($dbinfo?.collections || [])]) .filter(x => !$values[schemaName] || x.schemaName == $values[schemaName]) .map(x => ({ value: x.pureName, @@ -31,18 +27,20 @@
- - setFieldValue(name, _.uniq([...($values[name] || []), ...($dbinfo && $dbinfo.tables.map(x => x.pureName))]))} - /> - - setFieldValue(name, _.uniq([...($values[name] || []), ...($dbinfo && $dbinfo.views.map(x => x.pureName))]))} - /> + {#each ['tables', 'views', 'collections'] as field} + {#if $dbinfo && $dbinfo[field]?.length > 0} + + setFieldValue( + name, + _.compact(_.uniq([...($values[name] || []), ...($dbinfo[field]?.map(x => x.pureName) || [])])) + )} + /> + {/if} + {/each} + setFieldValue(name, [])} />
From 4522c37bfab70feb36a7e203cb1c4f8b83895268 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 29 Apr 2021 20:47:35 +0200 Subject: [PATCH 18/26] docker beta build --- .github/workflows/build-docker-beta.yaml | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/build-docker-beta.yaml diff --git a/.github/workflows/build-docker-beta.yaml b/.github/workflows/build-docker-beta.yaml new file mode 100644 index 000000000..3b454331e --- /dev/null +++ b/.github/workflows/build-docker-beta.yaml @@ -0,0 +1,47 @@ +name: Docker image + +# on: [push] + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' + +jobs: + build: + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-18.04] + + steps: + - name: Context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: actions/checkout@v1 + with: + fetch-depth: 1 + - name: Use Node.js 10.x + uses: actions/setup-node@v1 + with: + node-version: 10.x + - name: yarn install + run: | + yarn install + - name: setCurrentVersion + run: | + yarn setCurrentVersion + - name: Prepare docker image + run: | + yarn run prepare:docker + - name: Build docker image + run: | + docker build ./docker -t dbgate + - name: Push docker image + run: | + docker tag dbgate dbgate/dbgate:beta + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} + docker push dbgate/dbgate:beta From bd6c116cc0de8f9a4de7bfafc4d2a11df1c5d5da Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 30 Apr 2021 17:21:35 +0200 Subject: [PATCH 19/26] timg safe compare token fixes #91 --- packages/api/src/main.js | 3 ++- packages/api/src/utility/timingSafeCheckToken.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 packages/api/src/utility/timingSafeCheckToken.js diff --git a/packages/api/src/main.js b/packages/api/src/main.js index 2b09c1e7a..3171aec00 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -31,6 +31,7 @@ const scheduler = require('./controllers/scheduler'); const { rundir } = require('./utility/directories'); const platformInfo = require('./utility/platformInfo'); const processArgs = require('./utility/processArgs'); +const timingSafeCheckToken = require('./utility/timingSafeCheckToken'); let authorization = null; let checkLocalhostOrigin = null; @@ -56,7 +57,7 @@ function start() { } app.use(function (req, res, next) { - if (authorization && req.headers.authorization != authorization) { + if (authorization && !timingSafeCheckToken(req.headers.authorization, authorization)) { return res.status(403).json({ error: 'Not authorized!' }); } if (checkLocalhostOrigin) { diff --git a/packages/api/src/utility/timingSafeCheckToken.js b/packages/api/src/utility/timingSafeCheckToken.js new file mode 100644 index 000000000..bdfde432a --- /dev/null +++ b/packages/api/src/utility/timingSafeCheckToken.js @@ -0,0 +1,9 @@ +const crypto = require('crypto'); + +function timingSafeCheckToken(a, b) { + if (!a || !b) return false; + if (a.length != b.length) return false; + return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)); +} + +module.exports = timingSafeCheckToken; From 38aae142eab17e925de99caf316228787512453b Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 30 Apr 2021 17:30:18 +0200 Subject: [PATCH 20/26] loading structure status fix --- packages/web/src/widgets/SqlObjectList.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/src/widgets/SqlObjectList.svelte b/packages/web/src/widgets/SqlObjectList.svelte index d1bb38871..26da6309e 100644 --- a/packages/web/src/widgets/SqlObjectList.svelte +++ b/packages/web/src/widgets/SqlObjectList.svelte @@ -42,7 +42,7 @@ Refresh -{:else if objectList.length == 0 && $status && $status.name != 'pending' && $objects} +{:else if objectList.length == 0 && $status && $status.name != 'pending' && $status.name != 'checkStructure' && $status.name != 'loadStructure' && $objects} Refresh - {#if ($status && $status.name == 'pending' && $objects) || !$objects} + {#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects} {:else} Date: Fri, 30 Apr 2021 18:03:34 +0200 Subject: [PATCH 21/26] add to favorites moved from toolbar into tab context menu --- packages/web/src/widgets/TabsPanel.svelte | 48 ++++++++++++++--------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/web/src/widgets/TabsPanel.svelte b/packages/web/src/widgets/TabsPanel.svelte index 51b32932c..8a893aeb6 100644 --- a/packages/web/src/widgets/TabsPanel.svelte +++ b/packages/web/src/widgets/TabsPanel.svelte @@ -87,9 +87,9 @@ registerCommand({ id: 'tabs.addToFavorites', category: 'Tabs', - name: 'Favorites', - icon: 'icon favorite', - toolbar: true, + name: 'Add current tab to favorites', + // icon: 'icon favorite', + // toolbar: true, testEnabled: () => getActiveTab()?.tabComponent && tabs[getActiveTab()?.tabComponent] && @@ -146,9 +146,10 @@ } }; - const getContextMenu = (tabid, props) => () => { + const getContextMenu = tab => () => { + const { tabid, props, tabComponent } = tab; const { conid, database } = props || {}; - const res = [ + return [ { text: 'Close', onClick: () => closeTab(tabid), @@ -161,20 +162,29 @@ text: 'Close others', onClick: () => closeOthers(tabid), }, + tabComponent && + tabs[tabComponent] && + tabs[tabComponent].allowAddToFavorites && + tabs[tabComponent].allowAddToFavorites(props) && [ + { divider: true }, + { + text: 'Add to favorites', + onClick: () => showModal(FavoriteModal, { savingTab: tab }), + }, + ], + conid && + database && [ + { divider: true }, + { + text: `Close with same DB - ${database}`, + onClick: () => closeWithSameDb(tabid), + }, + { + text: `Close with other DB than ${database}`, + onClick: () => closeWithOtherDb(tabid), + }, + ], ]; - if (conid && database) { - res.push( - { - text: `Close with same DB - ${database}`, - onClick: () => closeWithSameDb(tabid), - }, - { - text: `Close with other DB than ${database}`, - onClick: () => closeWithOtherDb(tabid), - } - ); - } - return res; }; const handleSetDb = async props => { @@ -216,7 +226,7 @@ class:selected={tab.selected} on:click={e => handleTabClick(e, tab.tabid)} on:mouseup={e => handleMouseUp(e, tab.tabid)} - use:contextMenu={getContextMenu(tab.tabid, tab.props)} + use:contextMenu={getContextMenu(tab)} > From 14bbc7b0578ffebd9e037232b6a04046d0a41c3d Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 30 Apr 2021 18:46:44 +0200 Subject: [PATCH 22/26] duplicate tab popup menu --- packages/web/src/query/useEditorData.ts | 15 ++++++++++ packages/web/src/utility/openNewTab.ts | 35 ++++++++++++++++++++++- packages/web/src/widgets/TabsPanel.svelte | 5 ++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/web/src/query/useEditorData.ts b/packages/web/src/query/useEditorData.ts index b786abd65..7bf041df0 100644 --- a/packages/web/src/query/useEditorData.ts +++ b/packages/web/src/query/useEditorData.ts @@ -19,6 +19,8 @@ function getParsedLocalStorage(key) { return null; } +const saveHandlersList = []; + export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null, onInitialData = null }) { const localStorageKey = `tabdata_editor_${tabid}`; let changeCounter = 0; @@ -90,6 +92,11 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n })); }; + const saveToStorageIfNeeded = async () => { + if (savedCounter == changeCounter) return; // all saved + await saveToStorage(); + }; + const saveToStorage = async () => { if (value == null) return; try { @@ -128,11 +135,13 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n onMount(() => { window.addEventListener('beforeunload', saveToStorageSync); initialLoad(); + saveHandlersList.push(saveToStorageIfNeeded); }); onDestroy(() => { saveToStorage(); window.removeEventListener('beforeunload', saveToStorageSync); + _.remove(saveHandlersList, x => x == saveToStorageIfNeeded); }); return { @@ -144,3 +153,9 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n initialLoad, }; } + +export async function saveAllPendingEditorData() { + for (const item of saveHandlersList) { + await item(); + } +} diff --git a/packages/web/src/utility/openNewTab.ts b/packages/web/src/utility/openNewTab.ts index 928afbb9d..ccc47190c 100644 --- a/packages/web/src/utility/openNewTab.ts +++ b/packages/web/src/utility/openNewTab.ts @@ -6,6 +6,7 @@ import tabs from '../tabs'; import { setSelectedTabFunc } from './common'; import localforage from 'localforage'; import stableStringify from 'json-stable-stringify'; +import { saveAllPendingEditorData } from '../query/useEditorData'; function findFreeNumber(numbers: number[]) { if (numbers.length == 0) return 1; @@ -74,9 +75,9 @@ export default async function openNewTab(newTab, initialData = undefined, option openedTabs.update(files => [ ...(files || []).map(x => ({ ...x, selected: false })), { + ...newTab, tabid, selected: true, - ...newTab, }, ]); @@ -91,3 +92,35 @@ export default async function openNewTab(newTab, initialData = undefined, option // }, // ]); } + +export async function duplicateTab(tab) { + await saveAllPendingEditorData(); + + let title = tab.title; + const mtitle = title.match(/^(.*#)[\d]+$/); + if (mtitle) title = mtitle[1]; + + const keyRegex = /^tabdata_([^_]+)_([^_]+)$/; + const initialData = {}; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + const m = key.match(keyRegex); + if (m && m[2] == tab.tabid) { + initialData[m[1]] = JSON.parse(localStorage.getItem(key)); + } + } + for (const key of await localforage.keys()) { + const m = key.match(keyRegex); + if (m && m[2] == tab.tabid) { + initialData[m[1]] = await localforage.getItem(key); + } + } + openNewTab( + { + ..._.omit(tab, ['tabid']), + title, + }, + initialData, + { forceNewTab: true } + ); +} diff --git a/packages/web/src/widgets/TabsPanel.svelte b/packages/web/src/widgets/TabsPanel.svelte index 8a893aeb6..06e4d4b26 100644 --- a/packages/web/src/widgets/TabsPanel.svelte +++ b/packages/web/src/widgets/TabsPanel.svelte @@ -113,6 +113,7 @@ import { setSelectedTab } from '../utility/common'; import contextMenu from '../utility/contextMenu'; import { getConnectionInfo } from '../utility/metadataLoaders'; + import { duplicateTab } from '../utility/openNewTab'; $: currentDbKey = $currentDatabase && $currentDatabase.name && $currentDatabase.connection @@ -162,6 +163,10 @@ text: 'Close others', onClick: () => closeOthers(tabid), }, + { + text: 'Duplicate', + onClick: () => duplicateTab(tab), + }, tabComponent && tabs[tabComponent] && tabs[tabComponent].allowAddToFavorites && From 8baad563159f3ccad6c0d685acfaa0dc660f0b03 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 30 Apr 2021 20:35:43 +0200 Subject: [PATCH 23/26] toolbar shows tab related commands aligned to right --- packages/web/src/commands/registerCommand.ts | 1 + packages/web/src/commands/stdCommands.ts | 3 + packages/web/src/datagrid/DataGridCore.svelte | 3 + packages/web/src/formview/FormView.svelte | 7 ++ .../web/src/tabs/CollectionDataTab.svelte | 1 + packages/web/src/tabs/FreeTableTab.svelte | 1 + .../web/src/tabs/MarkdownEditorTab.svelte | 1 + packages/web/src/tabs/TableDataTab.svelte | 1 + packages/web/src/widgets/Toolbar.svelte | 80 ++++++++++++++----- 9 files changed, 78 insertions(+), 20 deletions(-) diff --git a/packages/web/src/commands/registerCommand.ts b/packages/web/src/commands/registerCommand.ts index 632092371..c125f484e 100644 --- a/packages/web/src/commands/registerCommand.ts +++ b/packages/web/src/commands/registerCommand.ts @@ -27,6 +27,7 @@ export interface GlobalCommand { menuName?: string; toolbarOrder?: number; disableHandleKeyText?: string; + isRelatedToTab?: boolean, } export default function registerCommand(command: GlobalCommand) { diff --git a/packages/web/src/commands/stdCommands.ts b/packages/web/src/commands/stdCommands.ts index f8502dc3e..e145139db 100644 --- a/packages/web/src/commands/stdCommands.ts +++ b/packages/web/src/commands/stdCommands.ts @@ -251,6 +251,7 @@ export function registerFileCommands({ // keyText: 'Ctrl+S', icon: 'icon save', toolbar: true, + isRelatedToTab: true, testEnabled: () => getCurrentEditor() != null, onClick: () => saveTabFile(getCurrentEditor(), false, folder, format, fileExtension), }); @@ -271,6 +272,7 @@ export function registerFileCommands({ name: 'Execute', icon: 'icon run', toolbar: true, + isRelatedToTab: true, keyText: 'F5 | Ctrl+Enter', testEnabled: () => getCurrentEditor() != null && !getCurrentEditor()?.isBusy(), onClick: () => getCurrentEditor().execute(), @@ -281,6 +283,7 @@ export function registerFileCommands({ name: 'Kill', icon: 'icon close', toolbar: true, + isRelatedToTab: true, testEnabled: () => getCurrentEditor()?.canKill && getCurrentEditor().canKill(), onClick: () => getCurrentEditor().kill(), }); diff --git a/packages/web/src/datagrid/DataGridCore.svelte b/packages/web/src/datagrid/DataGridCore.svelte index 879b9ab0c..63e2d9022 100644 --- a/packages/web/src/datagrid/DataGridCore.svelte +++ b/packages/web/src/datagrid/DataGridCore.svelte @@ -7,6 +7,7 @@ name: 'Refresh', keyText: 'F5', toolbar: true, + isRelatedToTab: true, icon: 'icon reload', testEnabled: () => getCurrentDataGrid()?.getDisplay()?.supportsReload, onClick: () => getCurrentDataGrid().refresh(), @@ -63,6 +64,7 @@ group: 'undo', icon: 'icon undo', toolbar: true, + isRelatedToTab: true, testEnabled: () => getCurrentDataGrid()?.getGrider()?.canUndo, onClick: () => getCurrentDataGrid().undo(), }); @@ -74,6 +76,7 @@ group: 'redo', icon: 'icon redo', toolbar: true, + isRelatedToTab: true, testEnabled: () => getCurrentDataGrid()?.getGrider()?.canRedo, onClick: () => getCurrentDataGrid().redo(), }); diff --git a/packages/web/src/formview/FormView.svelte b/packages/web/src/formview/FormView.svelte index 6ea7882e0..79de2c0ef 100644 --- a/packages/web/src/formview/FormView.svelte +++ b/packages/web/src/formview/FormView.svelte @@ -18,6 +18,7 @@ name: 'Refresh', keyText: 'F5', toolbar: true, + isRelatedToTab: true, icon: 'icon reload', testEnabled: () => getCurrentDataForm() != null, onClick: () => getCurrentDataForm().refresh(), @@ -58,6 +59,7 @@ group: 'undo', icon: 'icon undo', toolbar: true, + isRelatedToTab: true, testEnabled: () => getCurrentDataForm()?.getFormer()?.canUndo, onClick: () => getCurrentDataForm().getFormer().undo(), }); @@ -69,6 +71,7 @@ group: 'redo', icon: 'icon redo', toolbar: true, + isRelatedToTab: true, testEnabled: () => getCurrentDataForm()?.getFormer()?.canRedo, onClick: () => getCurrentDataForm().getFormer().redo(), }); @@ -104,6 +107,7 @@ name: 'First', keyText: 'Ctrl+Home', toolbar: true, + isRelatedToTab: true, icon: 'icon arrow-begin', testEnabled: () => getCurrentDataForm() != null, onClick: () => getCurrentDataForm().navigate('begin'), @@ -115,6 +119,7 @@ name: 'Previous', keyText: 'Ctrl+ArrowUp', toolbar: true, + isRelatedToTab: true, icon: 'icon arrow-left', testEnabled: () => getCurrentDataForm() != null, onClick: () => getCurrentDataForm().navigate('previous'), @@ -126,6 +131,7 @@ name: 'Next', keyText: 'Ctrl+ArrowDown', toolbar: true, + isRelatedToTab: true, icon: 'icon arrow-right', testEnabled: () => getCurrentDataForm() != null, onClick: () => getCurrentDataForm().navigate('next'), @@ -137,6 +143,7 @@ name: 'Last', keyText: 'Ctrl+End', toolbar: true, + isRelatedToTab: true, icon: 'icon arrow-end', testEnabled: () => getCurrentDataForm() != null, onClick: () => getCurrentDataForm().navigate('end'), diff --git a/packages/web/src/tabs/CollectionDataTab.svelte b/packages/web/src/tabs/CollectionDataTab.svelte index 401fc2e10..e49af5e63 100644 --- a/packages/web/src/tabs/CollectionDataTab.svelte +++ b/packages/web/src/tabs/CollectionDataTab.svelte @@ -11,6 +11,7 @@ name: 'Save', // keyText: 'Ctrl+S', toolbar: true, + isRelatedToTab: true, icon: 'icon save', testEnabled: () => getCurrentEditor()?.canSave(), onClick: () => getCurrentEditor().save(), diff --git a/packages/web/src/tabs/FreeTableTab.svelte b/packages/web/src/tabs/FreeTableTab.svelte index a2d55b68b..bf3e11f67 100644 --- a/packages/web/src/tabs/FreeTableTab.svelte +++ b/packages/web/src/tabs/FreeTableTab.svelte @@ -8,6 +8,7 @@ name: 'Save', // keyText: 'Ctrl+S', toolbar: true, + isRelatedToTab: true, icon: 'icon save', testEnabled: () => getCurrentEditor() != null, onClick: () => getCurrentEditor().save(), diff --git a/packages/web/src/tabs/MarkdownEditorTab.svelte b/packages/web/src/tabs/MarkdownEditorTab.svelte index 7c9276c35..fbf85083d 100644 --- a/packages/web/src/tabs/MarkdownEditorTab.svelte +++ b/packages/web/src/tabs/MarkdownEditorTab.svelte @@ -19,6 +19,7 @@ name: 'Preview', icon: 'icon run', toolbar: true, + isRelatedToTab: true, keyText: 'F5 | Ctrl+Enter', testEnabled: () => getCurrentEditor() != null, onClick: () => getCurrentEditor().preview(), diff --git a/packages/web/src/tabs/TableDataTab.svelte b/packages/web/src/tabs/TableDataTab.svelte index 918c174e1..8b964b961 100644 --- a/packages/web/src/tabs/TableDataTab.svelte +++ b/packages/web/src/tabs/TableDataTab.svelte @@ -8,6 +8,7 @@ name: 'Save', // keyText: 'Ctrl+S', toolbar: true, + isRelatedToTab: true, icon: 'icon save', testEnabled: () => getCurrentEditor()?.canSave(), onClick: () => getCurrentEditor().save(), diff --git a/packages/web/src/widgets/Toolbar.svelte b/packages/web/src/widgets/Toolbar.svelte index 1b5c95d6d..b2396ec50 100644 --- a/packages/web/src/widgets/Toolbar.svelte +++ b/packages/web/src/widgets/Toolbar.svelte @@ -10,7 +10,8 @@ import _ from 'lodash'; import { openFavorite } from '../appobj/FavoriteFileAppObject.svelte'; import runCommand from '../commands/runCommand'; - import { commands, commandsCustomized } from '../stores'; + import FontIcon from '../icons/FontIcon.svelte'; + import { activeTab, commands, commandsCustomized } from '../stores'; import getElectron from '../utility/getElectron'; import { useFavorites } from '../utility/metadataLoaders'; import ToolbarButton from './ToolbarButton.svelte'; @@ -25,26 +26,48 @@ ); -
- {#if !electron} - runCommand('about.show')} /> - {/if} - {#each ($favorites || []).filter(x => x.showInToolbar) as item} - openFavorite(item)} icon={item.icon || 'icon favorite'}> - {item.title} - - {/each} +
+
+ {#if !electron} + runCommand('about.show')} /> + {/if} + {#each ($favorites || []).filter(x => x.showInToolbar) as item} + openFavorite(item)} icon={item.icon || 'icon favorite'}> + {item.title} + + {/each} - {#each list as command} - - {command.toolbarName || command.name} - - {/each} + {#each list.filter(x => !x.isRelatedToTab) as command} + + {command.toolbarName || command.name} + + {/each} +
+
+ {#if $activeTab && list.filter(x => x.isRelatedToTab).length > 0} +
+
+ + {$activeTab.title}: +
+
+ {/if} + {#each list.filter(x => x.isRelatedToTab) as command} + + {command.toolbarName || command.name} + + {/each} +
From e02305879e1f80acaf3d2e5f2bc37c348a0aa6d9 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 30 Apr 2021 20:42:34 +0200 Subject: [PATCH 24/26] v4.1.11-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6219697d8..4b008dd36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "4.1.11-beta.1", + "version": "4.1.11-beta.2", "name": "dbgate-all", "workspaces": [ "packages/*", From 988e4345d4beeb99cab8802c5931f43264f637b4 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 3 May 2021 18:36:38 +0200 Subject: [PATCH 25/26] v4.1.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b008dd36..8cb5b56a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "4.1.11-beta.2", + "version": "4.1.11", "name": "dbgate-all", "workspaces": [ "packages/*", From 4802c36b5473d6eed2a2ca0bfafdef3eb5c66d30 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 3 May 2021 18:42:04 +0200 Subject: [PATCH 26/26] changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 305add02f..9235a563d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # ChangeLog +### 4.1.11 +- FIX: fixed processing postgre query containing $$ +- FIX: fixed postgre analysing procedures & functions +- FIX: patched svelte crash #105 +- ADDED: ability to disbale background DB model updates +- ADDED: Duplicate connection +- ADDED: Duplicate tab +- FIX: SSH tunnel connection using keyfile auth #106 +- FIX: All tables button fix in export #109 +- CHANGED: Add to favorites moved from toolbar to tab context menu +- CHANGED: Toolbar design - current tab related commands are delimited + ### 4.1.10 - ADDED: Default database option in connectin settings #96 #92 - FIX: Bundle size optimalization for Windows #97