Compare commits

...

553 Commits

Author SHA1 Message Date
Jan Prochazka 4d32e57947 v5.3.1-beta.2 2024-06-17 17:06:55 +02:00
Jan Prochazka 4d25fef37d fixed singledb docker connections 2024-06-17 17:06:00 +02:00
Jan Prochazka 25835ee19f v5.3.1-beta.1 2024-06-17 11:38:45 +02:00
Jan Prochazka 2cd3aac158 #816 fixed redshift DB 2024-06-17 11:38:18 +02:00
Jan Prochazka ee671297bf added version to error gist 2024-06-07 15:25:26 +02:00
Jan Prochazka ce895f92cd Merge branch 'master' of github.com:dbgate/dbgate 2024-06-07 10:56:30 +02:00
Jan Prochazka 4e746a3055 Merge pull request #787 from michael-pattern/feat/763/per-user_permissions_when_using_oauth-follow-up
feat: per-user permissions when using oauth, follow-up
2024-06-07 10:54:45 +02:00
Jan Prochazka 5bc0af1fba changelog 2024-06-07 10:27:36 +02:00
Jan Prochazka 66a9e0d14a v5.3.0 2024-06-07 10:18:15 +02:00
Jan Prochazka b536b56348 v5.2.10-beta.4 2024-06-03 13:09:58 +02:00
Jan Prochazka ad6a22d2a6 #811 fixed columns in mongodb query 2024-06-03 12:43:59 +02:00
Jan Prochazka 64d9b26d79 allow specify username to redis connection 2024-06-03 11:00:13 +02:00
Jan Prochazka 284606e3d5 collection allows switch tab 2024-06-03 10:29:33 +02:00
Jan Prochazka e8129fd499 handle error with no structure 2024-06-03 10:27:25 +02:00
Jan Prochazka d454da325f fix: DB list for different connection 2024-06-03 10:14:17 +02:00
Jan Prochazka 5c703c786d Merge branch 'master' of github.com:dbgate/dbgate 2024-06-03 09:50:14 +02:00
Jan Prochazka 864c5bb208 switch database command 2024-06-03 09:50:08 +02:00
Jan Prochazka 504d16f189 v5.2.10-beta.3 2024-06-03 08:06:47 +02:00
Jan Prochazka d87af2a820 oracle version getter 2024-06-03 08:06:05 +02:00
Jan Prochazka 4e6e0a79c4 oracle version fallback 2024-06-03 07:53:12 +02:00
Jan Prochazka f4fbb28124 oracle: bulk inserter 2024-06-01 16:28:46 +02:00
Jan Prochazka 1c03a8ce9e v5.2.10-beta.2 2024-06-01 14:38:22 +02:00
Jan Prochazka 5bf2cf2784 Merge branch 'new-oracle-driver' 2024-06-01 14:38:01 +02:00
Jan Prochazka e572cd392c removed experimental oracle status 2024-06-01 14:36:57 +02:00
Jan Prochazka a812ff510d oracle: import data works 2024-06-01 14:35:40 +02:00
Jan Prochazka cafe0e68c3 fixes 2024-06-01 13:08:51 +02:00
Jan Prochazka 728f3621eb fix 2024-06-01 12:37:57 +02:00
Jan Prochazka ca0ae2084c oracle: handle statements in stream 2024-06-01 10:49:38 +02:00
Jan Prochazka 0cc7a98391 oracle: fixed commit 2024-06-01 08:36:13 +02:00
Jan Prochazka 68a40e5da6 oracle: upgraded query spliiter 2024-06-01 08:16:37 +02:00
Jan Prochazka bbf2e2f7ed fixes 2024-06-01 08:12:18 +02:00
Jan Prochazka 1f75a818c8 oracle: code cleanup, not null detection 2024-05-31 17:03:35 +02:00
Jan Prochazka ebdcd9ad94 oracle: detect autoincrement column 2024-05-31 16:49:43 +02:00
Jan Prochazka 2d1ac97191 oracle: fixed analysing constraints 2024-05-31 16:45:33 +02:00
Jan Prochazka 8f5b395935 oracle analyser code cleanup 2024-05-31 16:06:39 +02:00
Jan Prochazka df60d40134 oracle - using default schema 2024-05-31 15:21:49 +02:00
Jan Prochazka 2723c41832 oracle analyser per schema 2024-05-31 14:35:35 +02:00
Jan Prochazka a2102a51a1 use oracledb purejs client 2024-05-31 10:17:48 +02:00
Jan Prochazka 958d786dfb use .env instead of .env.local 2024-05-31 10:03:06 +02:00
Jan Prochazka e2526082b8 v5.2.10-beta.1 2024-05-31 08:54:35 +02:00
Jan Prochazka 0d22c675b6 #803 2024-05-31 08:28:32 +02:00
Jan Prochazka ab481121f9 changelog 2024-05-29 20:50:48 +02:00
Jan Prochazka e9ee52ac9d v5.2.9 2024-05-29 20:45:58 +02:00
Jan Prochazka 5eecb45961 printSecrets job 2024-05-29 07:17:25 +02:00
Jan Prochazka 94e991b059 v5.2.9-beta.6 2024-05-29 07:10:33 +02:00
Jan Prochazka 373a35fe65 Revert "Merge pull request #744 from Bare7a/postgresql-user-type-enhancements"
This reverts commit fdabe1eeaa, reversing
changes made to 4429b1d618.
2024-05-29 07:09:43 +02:00
Jan Prochazka eba5bd9c2b v5.2.9-beta.5 2024-05-27 18:06:27 +02:00
Jan Prochazka ca3507f5d4 fixed electron logs 2024-05-27 18:04:54 +02:00
Jan Prochazka 4f5db15c20 logging 2024-05-27 14:05:44 +02:00
Jan Prochazka 22bed04d13 gist - configurable token 2024-05-27 14:01:58 +02:00
Jan Prochazka d3737b4e08 v5.2.9-beta.4 2024-05-27 12:13:34 +02:00
Jan Prochazka 20c1315380 pg more analyser logs 2024-05-27 12:02:40 +02:00
Jan Prochazka e9442bd633 postgreSQL - extended nalyser logs 2024-05-27 11:43:38 +02:00
Jan Prochazka 59bd699fc6 v5.2.9-beta.3 2024-05-24 14:19:41 +02:00
Jan Prochazka cc3fd605de add gist upload secret 2024-05-24 14:18:57 +02:00
Jan Prochazka 0adf730f0b use gist secret 2024-05-24 14:13:12 +02:00
Jan Prochazka 5ab0907bd8 upload error to gist 2024-05-24 13:12:07 +02:00
Jan Prochazka e04da15f72 fix mongo reload - using setLoadedRows instead of bind:loadedRows 2024-05-24 10:47:38 +02:00
Jan Prochazka 5fe55af3b7 feat(mongo): fixed data writes after mongo driver upgrade #798 2024-05-24 09:37:44 +02:00
Jan Prochazka 79793d1b58 socket fix 2024-05-24 09:36:04 +02:00
Jan Prochazka d00ee890e5 fixed stream 2024-05-20 20:15:53 +02:00
Jan Prochazka 7d984d8faf feat(mjs): added crypto imports 2024-05-20 19:48:21 +02:00
Jan Prochazka 153f40f13e removed uuid dependency from api package 2024-05-20 19:15:03 +02:00
Jan Prochazka 134d8d1b1a feat(mjs): refactor 2024-05-20 19:00:11 +02:00
Jan Prochazka 2678daab4d Merge pull request #779 from Shah-Panam/Oauth-Allowed-Groups
Added OAuth Allowed Groups Option
2024-05-20 15:08:58 +02:00
Jan Prochazka 781ee15304 Merge commit '51d4bc9a75cdf28831b941f8ed73bd4644a2ca4e' 2024-05-20 14:03:18 +02:00
Jan Prochazka 51d4bc9a75 stream filtering by connection id 2024-05-20 14:02:47 +02:00
Jan Prochazka df3313e647 don't wrap statusbar 2024-05-20 12:48:50 +02:00
michael-pattern 4214b4f613 Use LOGIN_PERMISSIONS_* to compile permissions directly instead of creating logins. Accept req.user.login in hasPermission 2024-05-17 10:59:20 -04:00
ProjectInfinity 685c0f7dbc v5.2.9-beta.2 2024-05-17 13:53:28 +02:00
ProjectInfinity 4417edf73b Change from 127.0.0.1 to localhost for SSH tunnel 2024-05-17 13:06:48 +02:00
ProjectInfinity faf94c1a24 v5.2.9-beta.1 2024-05-17 13:02:46 +02:00
Jan Prochazka c82a877271 #782 disable auto upgrade workaround 2024-05-17 12:15:09 +02:00
Jan Prochazka 8ba85acd3c changelog 2024-05-17 06:45:06 +02:00
Jan Prochazka ba65704d55 v5.2.8 2024-05-17 06:40:13 +02:00
Jan Prochazka 68f77d4ed7 v5.2.8-beta.24 2024-05-15 18:57:10 +02:00
Jan Prochazka 008f6be6ac Merge branch 'master' of github.com:dbgate/dbgate 2024-05-15 18:56:57 +02:00
Jan Prochazka 7324cef87a write uncaught exception to console 2024-05-15 18:56:52 +02:00
michael-pattern cb9921918f Make use of LOGINS and OAUTH_PERMISSIONS exclusive 2024-05-15 12:40:09 -04:00
Panam Shah 9839dc795b Added OAuth Allowed Groups Option 2024-05-14 11:40:53 +00:00
Jan Prochazka 471fcdc131 added flatpak metainfo 2024-05-13 16:28:21 +02:00
Jan Prochazka c2abc83f99 reverted ubuntu upgrade 2024-05-13 14:47:32 +02:00
Jan Prochazka a23bda7294 v5.2.8-beta.23 2024-05-13 14:26:55 +02:00
Jan Prochazka a2d643305b Merge commit '9b4683ef53e815a4190ae90b8a83365235208d0e' 2024-05-13 14:26:38 +02:00
Jan Prochazka dd36427a80 upgraded docker ubuntu base 2024-05-13 14:25:26 +02:00
Jan Prochazka 9b4683ef53 v5.2.8-beta.22 2024-05-13 09:27:57 +02:00
Jan Prochazka a24271f045 build tar.gz for linux 2024-05-13 09:27:19 +02:00
Jan Prochazka f74e57bec2 handle not found connection in ConnectionTab 2024-05-13 09:26:54 +02:00
Jan Prochazka 4fb6b49b86 small fix 2024-05-13 08:40:43 +02:00
Jan Prochazka 72c380cef5 v5.2.8-beta.17 2024-05-09 14:23:42 +02:00
Jan Prochazka 39cdaf88f4 Merge pull request #770 from michael-pattern/feat/763/per-user_permissions_when_using_oauth
feat: per-user permissions when using oauth
2024-05-09 14:20:12 +02:00
Jan Prochazka 52c77031c5 Merge pull request #742 from frenchtoasters/trustCertEnv
Adding env for parsing for trustServerCertificate connection value
2024-05-09 14:02:45 +02:00
Jan Prochazka fdabe1eeaa Merge pull request #744 from Bare7a/postgresql-user-type-enhancements
Postgresql - Show proper types for Composite Types, Enums and Arrays
2024-05-09 13:21:04 +02:00
Jan Prochazka 4429b1d618 revert changes button #759 2024-05-09 13:07:31 +02:00
Jan Prochazka 5c24774170 fixed copy & paste commands 2024-05-09 13:03:05 +02:00
Jan Prochazka 792be82acd changelog 2024-05-09 12:20:34 +02:00
Jan Prochazka 6e3cd08d8a redis driver upgraded 2024-05-09 12:16:14 +02:00
michael-pattern 696d870c2f Allow password-based user login only when password is truthy 2024-05-08 17:52:50 -04:00
michael-pattern 26471517a9 Only add users to basicAuth when password is truthy 2024-05-08 17:52:05 -04:00
michael-pattern 58233a2fd5 Create login entries when the OAUTH_PERMISSIONS flag is truthy 2024-05-08 17:51:27 -04:00
Jan Prochazka 230948c4b4 changelog 2024-05-08 19:27:31 +02:00
Jan Prochazka df593074c2 test container node version => 18 2024-05-08 19:00:28 +02:00
Jan Prochazka 474ecb1b71 Merge branch 'update-packages-2' 2024-05-08 18:55:52 +02:00
Jan Prochazka e8e5781b59 removed better-sqlite prebuilds 2024-05-08 17:26:34 +02:00
Jan Prochazka a042ff363e v5.2.8-beta.16 2024-05-08 10:35:59 +02:00
Jan Prochazka 63bdf817c6 upgraded webpack 2024-05-08 10:35:14 +02:00
Jan Prochazka 82eed3b86e v5.2.8-beta.15 2024-05-08 10:27:52 +02:00
Jan Prochazka 21a24f9ba2 node version => 18 2024-05-08 10:27:35 +02:00
Jan Prochazka 550354fe09 v5.2.8-beta.14 2024-05-08 10:22:45 +02:00
Jan Prochazka e14e7efa1a fixed build error - upgraded chart.js 2024-05-08 10:22:25 +02:00
Jan Prochazka b1cf418058 upgrade electron && db drivers 2024-05-08 10:17:45 +02:00
Jan Prochazka bde4127b33 v5.2.8-beta.13 2024-05-08 08:33:34 +02:00
Jan Prochazka e981cb2734 upgrade webpack 2024-05-08 08:31:10 +02:00
Jan Prochazka 6f4c0edb46 upgraded mongodb driver 2024-05-08 08:21:51 +02:00
Jan Prochazka 6591e45a6e v5.2.8-beta.12 2024-05-08 07:47:02 +02:00
Jan Prochazka 397a6b54ff Merge branch 'master' of github.com:dbgate/dbgate 2024-05-08 07:46:31 +02:00
Jan Prochazka 51555da376 fixed missing env 2024-05-08 07:46:28 +02:00
ProjectInfinity 8e6b1973c7 Merge branch 'copy-paste-improvements' 2024-04-17 17:56:51 +02:00
ProjectInfinity 5a8627c39f Add copy to column list 2024-04-17 17:44:10 +02:00
Bare7a 26a46d9037 updated tableModifications.js 2024-04-06 17:39:58 +03:00
Bare7a b9a974ca27 updated tableModifications.js 2024-04-06 17:37:14 +03:00
Bare7a e4ed163723 updated tableModifications.js 2024-04-06 17:33:47 +03:00
Bare7a 4d8c62f3f5 updated tableModifications.js 2024-04-06 17:31:25 +03:00
Bare7a 4b2e28483b Updated columns.js 2024-04-06 17:30:09 +03:00
Bare7a 3cd070e211 Update columns.js 2024-04-06 17:21:06 +03:00
Bare7a 1e818e7756 Postgresql - Show proper types for Composite Types, Enums and Arrays 2024-04-06 17:13:36 +03:00
Jan Prochazka 91efb7abda v5.2.8-beta.11 2024-04-05 10:23:57 +02:00
Jan Prochazka 5659311ba2 v5.2.8-beta.10 2024-04-05 10:22:54 +02:00
Jan Prochazka 232031ff5b Merge branch 'master' of github.com:dbgate/dbgate 2024-04-05 10:20:35 +02:00
Jan Prochazka 0e242321ed upgraded electron-builder-notarize 2024-04-05 10:20:31 +02:00
Tyler French 83f3391b24 Adding env for parsing for trustServerCertificate connection value 2024-04-03 15:29:19 -04:00
ProjectInfinity 715c6f7f29 Make TabControl scrollable
Fixes #730
2024-03-28 03:40:01 +01:00
ProjectInfinity 0fc20f7238 Fix filter being unclickable
A commit caused the filter input to be unclickable, this fixes that.
2024-03-26 00:09:12 +01:00
ProjectInfinity c824e32f0a Add copy name to table list 2024-03-14 00:24:53 +01:00
ProjectInfinity 10a916bce6 Add copy/paste to query tab and database list 2024-03-13 23:59:02 +01:00
Jan Prochazka 1080147085 v5.2.8-beta.9 2024-02-16 08:43:22 +01:00
Jan Prochazka f0ebe260e2 v5.2.6-beta.8 2024-02-16 08:42:53 +01:00
Jan Prochazka 9ad82caac5 Revert "Merge branch 'update-packages'"
This reverts commit c4f322bda2, reversing
changes made to f090661eb9.
2024-02-12 13:08:30 +01:00
Jan Prochazka 92d13dda31 v5.2.8-beta.7 2024-02-12 12:59:37 +01:00
Jan Prochazka c4f322bda2 Merge branch 'update-packages' 2024-02-12 12:59:23 +01:00
Jan Prochazka 504bbeac52 upgraded mongodb driver 2024-02-12 12:55:20 +01:00
Infinity f090661eb9 Merge pull request #704 from KKishikawa/fix/unicode-query-file-enc-error
fix(app): Encoding error when opening Unicode query files
2024-02-01 22:48:11 +01:00
Kenta Kishikawa a526797013 fix: Encoding error when opening Unicode query files 2024-01-31 00:50:12 +09:00
Infinity beb1a00874 Merge pull request #697 from KKishikawa/fix/restore-opend-tabs-correctly
fix: localStorageGabageCollector not working
2024-01-26 00:45:45 +01:00
Kenta Kishikawa 2ddf10dfda fix: localStorageGabageCollector not working 2024-01-25 22:04:15 +09:00
Luboš Nguyen cdde770810 enum + set for mysql (#693)
* enum + set for mysql

* enum + set for mysql | dropdown

* enum for mysql | removed empty option
2024-01-23 10:05:49 +01:00
Infinity f2f8b9ef7e Merge pull request #690 from KKishikawa/fix/correctly-select-save-folder-dump
fix(app): Correctly select the save folder for dump
2024-01-20 19:15:55 +01:00
Infinity 1e1c26a16f Update Dockerfile - Match Node version 2024-01-19 01:53:43 +01:00
Kenta Kishikawa 4b3897c7f0 fix(app): Correctly select the save folder for dump 2024-01-15 00:08:35 +09:00
Infinity 3e8dabc1e4 Merge pull request #685 from mohamedelhefni/add-vim-support
feat(web): add support for vim keybindings
2024-01-08 20:08:27 +01:00
Mohamed Elhefni 81eda4d0d3 chore(web): move keybind mode setting to Genral section in settings modal 2024-01-08 12:09:15 +02:00
Mohamed Elhefni 090329593e feat(web): add support for vim keybindings 2024-01-06 14:06:40 +02:00
Infinity aa66367f86 Merge pull request #677 from KKishikawa/fix-download-with-auth-header
fix: download with auth header
2024-01-04 23:46:18 +01:00
ProjectInfinity f7e43d6608 v5.2.8-beta.4 2023-12-24 15:21:50 +01:00
ProjectInfinity c22bb6905c Use webpack5 for mysql plugin 2023-12-24 15:17:06 +01:00
ProjectInfinity 9a1a0dd0db Update workflows with node16 lts 2023-12-24 05:35:40 +01:00
ProjectInfinity 695c7c2a74 Increase mysql2 version 2023-12-24 05:28:36 +01:00
Infinity edf6819ece Merge pull request #674 from KKishikawa/fix-mac-select-all-shotcut
fix: Mac - shortcut for select all
2023-12-24 03:58:26 +01:00
Kenta Kishikawa 9f62a15eeb fix: download with auth header 2023-12-13 01:39:31 +09:00
Infinity 46df729195 Merge pull request #646 from enwawerueli/devel
fix(web): fix typo in column filter modal
2023-12-11 21:03:09 +01:00
Kenta Kishikawa f0fc50097b fix: Mac - shortcut for select all 2023-12-07 01:07:58 +09:00
ProjectInfinity aa7d91f2c5 Fix inconsistencies in max/unmaximize window buttons
Also adds maximize action to command palette
2023-12-05 23:45:51 +01:00
Infinity 843675a056 Merge pull request #671 from KKishikawa/save-menu-are-not-working
fix(web): file menu save and save as not working
2023-12-05 15:38:06 +01:00
Kenta Kishikawa 129c3ed217 fix(web): file menu save and save as not working 2023-12-04 14:17:46 +00:00
Infinity 79d1b7893d Merge pull request #668 from KKishikawa/fix-import-export-query-editor-overlaps
fix: query editor on import/export screen overlaps with selector
2023-11-28 21:44:27 +01:00
Kenta Kishikawa 690940d070 fix: query editor on import/export screen overlaps with selector 2023-11-27 10:05:10 +00:00
ProjectInfinity 89132ac47a Docker: Match GH runner
Experiment to see if this solves potential glibc issues.
2023-11-09 02:20:09 +01:00
ProjectInfinity 267fe7ca1f v5.2.8-beta.1 2023-11-09 01:23:37 +01:00
ProjectInfinity e4d18bfc43 Update Docker image to 14-bullseye
Debian Buster is so old that dependencies are starting to no longer support it.
2023-11-09 01:14:16 +01:00
enwawerueli 8bda4a7d2e fix(web): fix typo in column filter modal 2023-10-25 01:08:58 +03:00
Jan Prochazka aea2c64703 changeog 2023-10-06 14:12:48 +02:00
Jan Prochazka 80a18ec724 v5.2.7 2023-10-06 14:06:21 +02:00
Jan Prochazka a28aad9544 v5.2.7-beta.2 2023-09-30 12:37:57 +02:00
Jan Prochazka ce09fcb7fd try to fix e_channel_closed error 2023-09-30 12:29:28 +02:00
Jan Prochazka 731c4c046c fixed No mapping #575 2023-09-30 11:43:50 +02:00
Jan Prochazka a98d5d29ca fixed Data import from files #633 2023-09-30 10:28:54 +02:00
Jan Prochazka c75ae033ba v5.2.7-beta.1 2023-09-28 11:46:13 +02:00
Jan Prochazka 067d91bf8c v5.2.7-alpha.1 2023-09-28 11:22:23 +02:00
Jan Prochazka 3a24bcebf8 Merge branch 'master' of github.com:dbgate/dbgate 2023-09-28 11:21:01 +02:00
Jan Prochazka d6104c8375 disabled shell scripting for NPM distribution by default 2023-09-28 11:20:58 +02:00
Jan Prochazka 1c5a90226e Merge pull request #627 from peteyus/issue/626/add-headers-on-file-upload
add API headers on upload call
2023-09-28 09:33:27 +02:00
Jan Prochazka dccfcfe8db readme 2023-09-28 09:28:15 +02:00
Jan Prochazka 069ccf814e Merge pull request #625 from bluelakee02/bluelake/fix-missing-links-readme
Remove missing links to jenasoft
2023-09-28 09:24:44 +02:00
Jan Prochazka 032f94e56a Merge pull request #596 from vasekch/patch-1
Pass signals in entrypoint.sh
2023-09-28 08:33:53 +02:00
Jan Prochazka f95beaefff Merge pull request #592 from kikyous/master
fix body overflow when context menu height great than viewport
2023-09-28 08:32:08 +02:00
Peter Jorgensen 46afb1f1df add API headers on upload call 2023-09-18 21:56:42 -06:00
bluelakee02 c40878f1e2 Remove missing links to jenasoft 2023-09-17 16:45:57 +02:00
Vašek Chalupníček 3017fd4ed4 Update entrypoint.sh
Replace entrypoint.sh process with node process in final command to allow passing signals to the node process (e.g. SIGTERM upon stopping the container)
2023-07-18 13:08:39 +02:00
chen 2464935fa5 update style 2023-07-12 13:18:32 +08:00
Jan Prochazka fadd5b138c readme 2023-06-02 13:11:31 +02:00
Jan Prochazka f1212ec956 v5.2.6 2023-06-02 13:07:29 +02:00
Jan Prochazka 409278eca4 mongodb: encode username and password #560 2023-05-27 08:41:54 +02:00
Jan Prochazka 914d8dfe8a v5.2.6-beta.9 2023-05-27 08:07:23 +02:00
Jan Prochazka 914909f5de #561 used asarUnpack option 2023-05-27 08:07:11 +02:00
Jan Prochazka a403509ee7 v5.2.6-beta.8 2023-05-26 19:35:17 +02:00
Jan Prochazka 866ce9aea7 try to fix mac build 2023-05-26 19:35:02 +02:00
Jan Prochazka 06a2a4e57d v5.2.6-beta.7 2023-05-26 18:18:47 +02:00
Jan Prochazka 4cb8f7152d changed macos runner 2023-05-26 18:18:40 +02:00
Jan Prochazka 773e446fd7 v5.2.6-beta.6 2023-05-26 17:48:56 +02:00
Jan Prochazka b65d211def postpone creation of native modules #561 2023-05-26 17:48:22 +02:00
Jan Prochazka 171f97ee0c Revert "upgrade msnodesqlv8 - try to fix #561"
This reverts commit 756cf8a099.
2023-05-26 17:35:13 +02:00
Jan Prochazka 424bd3a036 v5.2.6-beta.2 2023-05-26 14:43:22 +02:00
Jan Prochazka 756cf8a099 upgrade msnodesqlv8 - try to fix #561 2023-05-26 14:36:54 +02:00
Jan Prochazka d109464fdd v5.2.6-beta.1 2023-05-21 09:48:41 +02:00
Jan Prochazka 05bb40b780 Merge pull request #556 from Flusinerd/master
fix(plugins/mssql): fix typo in datetimeoffset dataType
2023-05-19 17:23:16 +02:00
Jan Prochazka a830fadc7a Merge pull request #537 from ProjectInfinity/fix-sql-export-timestamp
Fix - SQL export is using the wrong hour formatting
2023-05-19 17:19:00 +02:00
Jan Prochazka e7acaf3fc7 Merge pull request #524 from ProjectInfinity/improve-diagram-view
Fixes missing toolstrip and adds up to 200% zoom to diagram view
2023-05-19 17:13:08 +02:00
Jan Krüger 4f2b3c15e2 fix(plugins/mssql): fix typo in datetimeoffset dataType
fixes #555
2023-05-09 18:13:32 +02:00
Infinity 54b1cde5c9 Fix - SQL export is using the wrong hour formatting 2023-04-11 08:10:45 +00:00
Infinity ffe1c4c7cd Fixes missing toolstrip and adds up to 200% zoom to diagram view 2023-03-26 05:00:21 +00:00
Jan Prochazka fee438c6d1 type 2023-03-19 07:04:04 +01:00
Jan Prochazka 06b48b1c63 changelog 2023-03-17 13:36:54 +01:00
Jan Prochazka 9abb1ed19c v5.2.5 2023-03-17 13:33:32 +01:00
Jan Prochazka 58f4370bb6 v5.2.5-beta.17 2023-03-13 20:42:39 +01:00
Jan Prochazka 86c02a76d0 reverted xlsx 2023-03-13 20:42:27 +01:00
Jan Prochazka a2bc636396 convertor from forage 2023-03-13 20:33:51 +01:00
Jan Prochazka 00bf1e64a1 removed misleading WidgetColumnBarItrem.show attr 2023-03-13 20:18:13 +01:00
Jan Prochazka a45782098a fixes database widget collapsing problems 2023-03-13 20:03:52 +01:00
Jan Prochazka df4230ea1d pinned objects fixes 2023-03-13 19:50:06 +01:00
Jan Prochazka 886e0a059e v5.2.5-beta.16 2023-03-12 09:09:39 +01:00
Jan Prochazka f83c4ef799 Merge branch 'develop' 2023-03-12 09:08:40 +01:00
Jan Prochazka 66d1b4ca49 disable collapse last widget items 2023-03-12 09:06:11 +01:00
Jan Prochazka b2f55522a8 fixed Resizing window resets window contents #479 2023-03-12 08:45:28 +01:00
Jan Prochazka edc3a7409a tool strip wrappable, table data commands moved from statusbar to toolstrip 2023-03-12 08:36:22 +01:00
Jan Prochazka 09e584326f better mac icon 2023-03-11 11:55:23 +01:00
Jan Prochazka feed0cd8db postgres analyse index desc #514 2023-03-11 10:33:13 +01:00
Jan Prochazka 9d4105335f v5.2.5-beta.14 2023-03-06 19:11:59 +01:00
Jan Prochazka c15261227b upgraded ubuntu builder version 18.04=>22.04 2023-03-06 19:11:47 +01:00
Jan Prochazka de567bdd31 v5.2.5-beta.11 2023-03-06 18:49:58 +01:00
Jan Prochazka 6736e8d0cf allow collapse multitab group 2023-03-06 18:49:30 +01:00
Jan Prochazka 36ccba7988 fixed split margin 2023-03-06 18:49:26 +01:00
Jan Prochazka 75c5d30ad3 v5.2.5-beta.9 2023-03-05 20:28:22 +01:00
Jan Prochazka cd10095dc0 Merge branch 'master' into develop 2023-03-05 17:20:22 +01:00
Jan Prochazka a64e42f1c2 changelog 2023-03-05 17:19:03 +01:00
Jan Prochazka 3c3f8514da v5.2.4 2023-03-05 17:17:58 +01:00
Jan Prochazka 961d11b610 split group condition 2023-03-05 17:13:05 +01:00
Jan Prochazka c646a83608 close functions in tab group 2023-03-05 17:11:02 +01:00
Jan Prochazka d1bdebb4ed abilit to split whole group 2023-03-05 16:59:41 +01:00
Jan Prochazka aa4406942f tabs saves to forage instead of storage 2023-03-05 15:43:45 +01:00
Jan Prochazka ff044ebec8 tab drag&drop fix 2023-03-05 15:28:29 +01:00
Jan Prochazka f5d41c89e6 show selected tab in multitab 2023-03-05 12:20:07 +01:00
Jan Prochazka d283429f40 open new tab - corrent multiTabIndex 2023-03-05 12:14:43 +01:00
Jan Prochazka 15d005be13 drag & drop between mutlitabs 2023-03-05 11:47:13 +01:00
Jan Prochazka f404e9956e refactor - visibleSecondary not stored, computed in component 2023-03-05 11:34:42 +01:00
Jan Prochazka 2dadd1f437 vertical split tabs #394 2023-03-05 10:43:04 +01:00
Jan Prochazka 1061d2aba2 tabs container style refactor 2023-03-04 10:50:10 +01:00
Jan Prochazka ff36870763 tabs files refactor 2023-03-04 10:39:57 +01:00
Jan Prochazka 991176d433 v5.2.4-beta.2 2023-03-03 18:26:27 +01:00
Jan Prochazka 406e3c022c mac icon #494 2023-03-03 18:25:58 +01:00
Jan Prochazka 2688c31123 v5.2.4-alpha.1 2023-03-03 18:05:08 +01:00
Jan Prochazka 578282c419 fixed reference #508 2023-03-03 18:04:51 +01:00
Jan Prochazka 9505643a26 v5.2.3 2023-02-27 18:11:49 +01:00
Jan Prochazka 2169d1a288 changelog 2023-02-26 17:07:44 +01:00
Jan Prochazka 62ebe49ac0 v5.2.3-beta.9 2023-02-26 16:50:57 +01:00
Jan Prochazka a2043b237f multi column condition in perspectives 2023-02-26 16:48:32 +01:00
Jan Prochazka 7c03d31b84 mutli column condition for JSL data 2023-02-26 15:44:29 +01:00
Jan Prochazka b26be02203 multi column filter #491 2023-02-26 15:26:39 +01:00
Jan Prochazka a251e92598 filters refactor fix 2023-02-26 15:00:54 +01:00
Jan Prochazka 1a28922a62 refactor - simplified filters component 2023-02-26 14:42:54 +01:00
Jan Prochazka 65c3ff8ec9 fix 2023-02-26 11:42:19 +01:00
Jan Prochazka 56fe578884 removed free table refs 2023-02-26 11:39:30 +01:00
Jan Prochazka 4dbb3a72d4 fixed problem with closing queries in progress 2023-02-26 11:08:20 +01:00
Jan Prochazka 5fd7982f06 marked oracle support as experimantal 2023-02-26 10:15:18 +01:00
Jan Prochazka 0ca5114b71 Merge branch 'develop' 2023-02-26 10:14:42 +01:00
Jan Prochazka d1ae7fe6e9 oracle support marked as experimental 2023-02-26 10:08:41 +01:00
Jan Prochazka 1417f53c56 disable SSL tab for oracle 2023-02-26 10:03:25 +01:00
Jan Prochazka 7a606cf8ef oracle port config #496 2023-02-26 10:01:53 +01:00
Jan Prochazka 622773fccd optimalization of loading oracle structure 2023-02-26 09:40:12 +01:00
Jan Prochazka 64ceea3779 fiuxed dependency 2023-02-26 09:05:36 +01:00
Jan Prochazka a588d72b26 create default archive by default 2023-02-26 08:58:23 +01:00
Jan Prochazka 7ec23ecca4 fixed modify archive for windows 2023-02-26 08:41:30 +01:00
Jan Prochazka 0c62349802 fixed error reporting problems 2023-02-25 20:25:27 +01:00
Jan Prochazka c817bf5911 added import/export tab (not used) 2023-02-25 18:24:00 +01:00
Jan Prochazka 2d74b831c5 fixed sqlite data duplicator 2023-02-25 13:33:33 +01:00
Jan Prochazka 490efb065a fixes sqlite autoincrement column creation 2023-02-25 13:31:24 +01:00
Jan Prochazka 6ccaa05bec Merge pull request #505 from mhf-ir/master
fix: connection ssl require file path instread of file content
2023-02-25 12:26:55 +01:00
Jan Prochazka eb04f56662 fixed TS + code tidy 2023-02-25 11:57:30 +01:00
Jan Prochazka 4e97f54bd4 archive file - save as 2023-02-25 11:43:14 +01:00
Jan Prochazka 9fe689625e simplified tab register 2023-02-25 11:36:16 +01:00
Jan Prochazka fa24d47c03 fixed tab component 2023-02-25 11:34:55 +01:00
Jan Prochazka 1c73920dd5 save jsl data 2023-02-25 11:34:19 +01:00
Jan Prochazka a77492440e removed free table (data sheet) concept 2023-02-25 09:51:08 +01:00
Jan Prochazka 7c4a47c4c6 running row macros 2023-02-24 19:04:22 +01:00
Jan Prochazka a519c78301 quick export - current archive 2023-02-24 17:22:11 +01:00
Jan Prochazka d024b6f25c run macro on jsl data 2023-02-24 16:48:37 +01:00
Muhammad Hussein Fattahizadeh 0c6e113e3e fix: connection ssl require file path instread of file content 2023-02-22 18:53:41 +03:30
Jan Prochazka 6ff4acc50d removed marking archive as data sheet 2023-02-21 07:37:37 +01:00
Jan Prochazka fabf333664 v5.2.3-beta.8 2023-02-19 19:24:00 +01:00
Jan Prochazka 29eef5619d dynamic structure switch 2023-02-19 19:23:51 +01:00
Jan Prochazka eb098bb33a upgraded xlsx package 2023-02-17 14:13:25 +01:00
Jan Prochazka 36c792f44e excel import fix 2023-02-17 13:57:30 +01:00
Jan Prochazka c7aaf06506 v5.2.3-beta.7 2023-02-17 12:15:19 +01:00
Jan Prochazka 7b6a1543de duplicator UX 2023-02-17 12:14:58 +01:00
Jan Prochazka 67e287cfdf added links from duplicator 2023-02-17 10:41:01 +01:00
Jan Prochazka 7802cde14d duplicator fixes 2023-02-17 10:26:44 +01:00
Jan Prochazka 6b783027e5 data duplicator fix 2023-02-17 10:00:21 +01:00
Jan Prochazka 1ab58a491a data duplicator test 2023-02-17 09:27:16 +01:00
Jan Prochazka b6c5f26eb4 data duplicator test 2023-02-17 09:15:13 +01:00
Jan Prochazka 6a0feb235a fixed compilation error 2023-02-17 08:46:31 +01:00
Jan Prochazka 1365f2b47c duplicator options 2023-02-16 18:27:05 +01:00
Jan Prochazka 8109dd862e change theme fix 2023-02-16 18:00:04 +01:00
Jan Prochazka fb1c2c61fb duplicator improvements 2023-02-16 17:25:54 +01:00
Jan Prochazka b514f8ae35 using readline instead of line-reader-fixes freeze 2023-02-16 15:11:33 +01:00
Jan Prochazka 3114a05c3b save structure changes to jsonl file 2023-02-16 13:33:28 +01:00
Jan Prochazka edf0637a35 change structure generates data commands 2023-02-16 13:14:56 +01:00
Jan Prochazka cd1267b464 schema editing in dataset 2023-02-16 11:47:17 +01:00
Jan Prochazka 675ef6e593 v5.2.3-beta.6 2023-02-13 20:35:37 +01:00
Jan Prochazka 60bd3c157e fixed multi-db perspectives 2023-02-13 20:35:10 +01:00
Jan Prochazka aceffd5681 v5.2.3-beta.5 2023-02-12 20:25:07 +01:00
Jan Prochazka 83f01c52f2 data duplicator style 2023-02-12 19:52:53 +01:00
Jan Prochazka 5e207a6c16 build fix 2023-02-12 12:44:40 +01:00
Jan Prochazka 10d5667c83 pg fix 2023-02-12 12:31:38 +01:00
Jan Prochazka d1e1b2ce9c Merge branch 'develop' 2023-02-12 12:15:59 +01:00
Jan Prochazka bb2f1399ba data duplicator runs in transaction 2023-02-12 12:14:07 +01:00
Jan Prochazka 5b6f90abc5 data duplicator - logs 2023-02-12 12:09:20 +01:00
Jan Prochazka 1d24562ead duplicator 2023-02-12 11:43:13 +01:00
Jan Prochazka fb8174b3e9 delete cascade fix 2023-02-12 11:43:02 +01:00
Jan Prochazka 4e194539d9 fix 2023-02-11 10:24:52 +01:00
Jan Prochazka b5e37053b8 data duplicator works in simple case 2023-02-11 10:17:10 +01:00
Jan Prochazka f3dd187df7 useEditorData fix 2023-02-11 09:53:08 +01:00
Jan Prochazka b5f504f3b1 data duplicator tab - configurator 2023-02-10 16:50:27 +01:00
Jan Prochazka 8df2a8a6df more mirroe archive commands 2023-02-10 15:14:02 +01:00
Jan Prochazka dd46604069 correct saving jsonl data 2023-02-10 11:37:18 +01:00
Jan Prochazka cc9402dd84 save archive algorithm 2023-02-10 11:25:18 +01:00
Jan Prochazka be0f68fb7f editing changeset on archive file 2023-02-10 10:22:38 +01:00
Jan Prochazka a3db8e2903 html & xml autio select highlighter #485 2023-02-08 07:34:28 +01:00
Jan Prochazka 87c29faadd html & xsml syntax highlight #485 2023-02-06 20:39:50 +01:00
Jan Prochazka 9bf610707e v5.2.3-beta.4 2023-02-06 20:30:24 +01:00
Jan Prochazka 28a568901a fixed rimraf usage 2023-02-06 20:29:58 +01:00
Jan Prochazka 1ba43af48d v5.2.3-beta.3 2023-02-05 20:23:29 +01:00
Jan Prochazka 356b623eaf downgraded rimraf, so that it passes old build 2023-02-05 20:23:16 +01:00
Jan Prochazka 85c3d6fe6f v5.2.3-beta.2 2023-02-05 20:07:56 +01:00
Jan Prochazka d9eb0f0976 intelisense fix #484 2023-02-05 20:03:15 +01:00
Jan Prochazka d61a7c54ce table data edit - shows editing mark 2023-02-05 19:58:45 +01:00
Jan Prochazka cd000098f1 save table structure uses transaction 2023-02-05 19:24:22 +01:00
Jan Prochazka e9a01a1ffd used transaction for save table data 2023-02-05 19:17:46 +01:00
Jan Prochazka 722789ca01 fix 2023-02-05 18:51:34 +01:00
Jan Prochazka 83ba530112 explicit order criteria only on MSSQL #436 2023-02-04 15:58:45 +01:00
Jan Prochazka 57fa9335d4 sort JSONL data & query results 2023-02-04 15:27:55 +01:00
Jan Prochazka 3babe95944 v5.2.3-beta.1 2023-02-04 09:46:29 +01:00
Jan Prochazka aab1229220 fixed typo #481 2023-02-04 09:44:49 +01:00
Jan Prochazka 7b64587f6a fixed crash #452 2023-02-03 11:11:44 +01:00
Jan Prochazka 6a5157140e mysql default value #455 2023-02-03 11:06:59 +01:00
Jan Prochazka 47e0173f84 arm64 windows installer added to build #473 2023-02-03 10:01:11 +01:00
Jan Prochazka 8fe6cb1f71 fixed reading DB with mongo views #476 2023-02-03 09:59:53 +01:00
Jan Prochazka dc6eff7f9e fixed show DB 2023-02-03 09:33:38 +01:00
Jan Prochazka dad9e3ea48 changelog 2023-02-01 18:31:18 +01:00
Jan Prochazka 166c2254ec v5.2.2 2023-02-01 18:22:21 +01:00
Jan Prochazka 5ab4b9ee13 v5.2.2-alpha.13 2023-01-29 08:40:35 +01:00
Jan Prochazka 1c87b1b994 fixed dependency 2023-01-29 08:40:12 +01:00
Jan Prochazka 072c340d5f added missing dependency 2023-01-29 08:35:50 +01:00
Jan Prochazka 5bc7a8e763 v5.2.2-alpha.12 2023-01-29 08:30:09 +01:00
Jan Prochazka 655dec369f fix 2023-01-29 08:30:00 +01:00
Jan Prochazka 9356ef6667 dbmodel docs 2023-01-29 08:27:28 +01:00
Jan Prochazka b3308dc389 v5.2.2-alpha.11 2023-01-28 20:19:30 +01:00
Jan Prochazka 7cbcafb6f7 dbmodel added to build 2023-01-28 20:19:16 +01:00
Jan Prochazka adbb335062 v5.2.2-alpha.10 2023-01-28 20:15:05 +01:00
Jan Prochazka bc1c827225 dbmodel commandline tool 2023-01-28 20:14:44 +01:00
Jan Prochazka 258338cd2e dbmodel tool initial import 2023-01-28 18:48:52 +01:00
Jan Prochazka cf00af9e30 v5.2.2-beta.9 2023-01-28 16:33:08 +01:00
Jan Prochazka 0f515bb762 bigger timeout to yarn 2023-01-28 16:32:55 +01:00
Jan Prochazka 5ca3a66f17 remove call of snapcraft login 2023-01-28 16:30:43 +01:00
Jan Prochazka 4f857ab1f8 v5.2.2-beta.8 2023-01-28 13:45:54 +01:00
Jan Prochazka 5ed97079b1 fixed snapcraft login 2023-01-28 13:45:43 +01:00
Jan Prochazka 16408d85f8 support for binary values in filters #467 2023-01-28 12:57:17 +01:00
Jan Prochazka cc388362d6 close query sessions after timeout #468 2023-01-28 11:40:52 +01:00
Jan Prochazka 079cac6eda use pinomin package 2023-01-28 10:22:12 +01:00
Jan Prochazka a43522752c logger refactor 2023-01-28 09:17:57 +01:00
Jan Prochazka dbcc732688 appname sent to connection - tedious 2023-01-27 16:42:01 +01:00
Jan Prochazka 3f525cacc1 appname added to pg connection string 2023-01-27 16:31:20 +01:00
Jan Prochazka 2fee308185 pinomin time field 2023-01-27 16:31:06 +01:00
Jan Prochazka 331c303e8f v5.2.2-beta.7 2023-01-27 15:40:50 +01:00
Jan Prochazka 7c8d225868 added missing file 2023-01-27 15:40:20 +01:00
Jan Prochazka dd44798ff4 v5.2.2-beta.6 2023-01-27 15:38:36 +01:00
Jan Prochazka 2dd8749bc6 simplified logging 2023-01-27 15:37:16 +01:00
Jan Prochazka 174d7fde5c pinomin logger 2023-01-27 15:37:04 +01:00
Jan Prochazka af3d271361 v5.2.2-beta.5 2023-01-23 20:11:59 +01:00
Jan Prochazka 17e83c700e try remove console logging for electron 2023-01-23 20:11:47 +01:00
Jan Prochazka 513fe6184a v5.2.2-beta.4 2023-01-23 19:41:42 +01:00
Jan Prochazka b56f11156d try to fix electron errors after start 2023-01-23 19:41:31 +01:00
Jan Prochazka 80e8b210be handle errors when sending to subprocess #458 2023-01-23 19:28:05 +01:00
Jan Prochazka d60687485b v5.2.2-beta.3 2023-01-23 18:26:53 +01:00
Jan Prochazka 7a62ef0cc3 remove handle electron errors 2023-01-23 18:26:43 +01:00
Jan Prochazka 0e58e94153 v5.2.2-beta.2 2023-01-22 19:29:00 +01:00
Jan Prochazka 8926e3bc84 Merge branch 'develop' 2023-01-22 19:27:57 +01:00
Jan Prochazka ef62948b5a form view works for JSL data 2023-01-22 19:27:39 +01:00
Jan Prochazka f014a4e6b4 added loadingformview 2023-01-22 19:12:32 +01:00
Jan Prochazka e589a994fa form view cleanup 2023-01-22 18:31:00 +01:00
Jan Prochazka 6fdb9cc5c9 form works also for views 2023-01-22 18:26:49 +01:00
Jan Prochazka 11bb8faf91 form view - open reference 2023-01-22 18:22:18 +01:00
Jan Prochazka 98b26bb119 form view filters 2023-01-22 18:03:29 +01:00
Jan Prochazka 268c010a22 form view refactor - handle hiearchic columns 2023-01-22 17:27:13 +01:00
Jan Prochazka 6dd3945724 form view refactor - basically works 2023-01-22 16:26:48 +01:00
Jan Prochazka ba644a37b7 removed hostname from logs 2023-01-22 12:35:11 +01:00
Jan Prochazka e9322cc1ba fix 2023-01-22 12:27:10 +01:00
Jan Prochazka f266acb807 #455 column default value help text 2023-01-22 12:21:12 +01:00
Jan Prochazka 9f66c5e28a logger info 2023-01-22 12:12:56 +01:00
Jan Prochazka 61d93fb9d9 Merge branch 'develop' 2023-01-22 12:07:06 +01:00
Jan Prochazka c87e38fd17 log & report unhandled electron error 2023-01-22 11:56:09 +01:00
Jan Prochazka 7eb6357c8d #360 allow to set log level 2023-01-22 10:55:10 +01:00
Jan Prochazka 1cf02488b4 configuring logger for electron 2023-01-22 10:35:02 +01:00
Jan Prochazka 5249713a3c show logs from menu 2023-01-22 10:31:16 +01:00
Jan Prochazka 1bf8f38793 added process name to logger output 2023-01-22 10:12:46 +01:00
Jan Prochazka e1f92fef13 pipe logs from forks into pino logger 2023-01-22 10:00:01 +01:00
Jan Prochazka af01d95348 pino multistream - file logging 2023-01-22 09:50:35 +01:00
Jan Prochazka d4f0882054 fixed error logging 2023-01-21 18:00:59 +01:00
Jan Prochazka cc0f05168d defined logger caller 2023-01-21 17:49:16 +01:00
Jan Prochazka 4d93be61b5 PINO JSON logging 2023-01-21 17:32:28 +01:00
Jan Prochazka dd230b008f Merge branch 'master' of github.com:dbgate/dbgate 2023-01-21 13:57:12 +01:00
Jan Prochazka 16238f8f94 Merge branch 'develop' 2023-01-21 13:56:55 +01:00
Jan Prochazka 20570c1988 Merge pull request #460 from ProjectInfinity/fix-sql-formatter
Update sql-formatter, fixes #450
2023-01-21 13:15:43 +01:00
Jan Prochazka 44dadcd256 fixed sqlite analyser 2023-01-21 11:01:19 +01:00
Jan Prochazka cf07123f51 fixed msql analyser 2023-01-21 10:52:50 +01:00
Jan Prochazka b56134d308 #457 fixed ctrl+tab 2023-01-21 10:40:13 +01:00
Jan Prochazka f9f879272b analyser refactor + optimalization 2023-01-21 10:13:08 +01:00
Jan Prochazka 3dfae351a6 foreign key loading optimalization #451 2023-01-21 09:34:29 +01:00
Infinity 822482ab4e Update sql-formatter, fixes #450 2023-01-19 15:08:51 +01:00
Jan Prochazka 451f671426 v5.2.2-beta.1 2023-01-06 18:49:09 +01:00
Jan Prochazka b06d747399 #451 loading fks on postgres cleanup & fix 2023-01-06 18:40:47 +01:00
Jan Prochazka 37eeaf0cce v5.2.1 2023-01-06 18:03:55 +01:00
Jan Prochazka 5f0ee80306 changelog 2023-01-06 18:03:44 +01:00
Jan Prochazka d8f25c17f7 fix 2023-01-06 14:32:42 +01:00
Jan Prochazka f6173335da v5.2.1-beta.3 2023-01-06 09:07:51 +01:00
Jan Prochazka 9fdc15b8aa used persmissions fixed 2023-01-06 09:06:54 +01:00
Jan Prochazka 77300f2078 fix login page 2023-01-06 08:34:27 +01:00
Jan Prochazka 3ab887f8e9 v5.2.1-beta.2 2023-01-05 10:19:03 +01:00
Jan Prochazka 5684eab3e2 OAuth scope added #407 2023-01-05 10:18:53 +01:00
Jan Prochazka 9ce743a8d3 v5.2.1-beta.1 2023-01-05 09:23:49 +01:00
Jan Prochazka 680c0057b1 fixed client_id param in oauth #407 2023-01-05 09:23:31 +01:00
Jan Prochazka e9fffc063b changelog 2023-01-03 22:35:41 +01:00
Jan Prochazka a0bc6f314c v5.2.0 2023-01-03 22:35:17 +01:00
Jan Prochazka af1bb005e5 changelog 2023-01-02 19:53:22 +01:00
Jan Prochazka 34d891e935 changelog preparation 2023-01-02 19:52:45 +01:00
Jan Prochazka dcccfe11c8 v5.1.7-alpha.14 2023-01-02 18:48:58 +01:00
Jan Prochazka 8823cff3a1 oracle build fix 2023-01-02 18:48:28 +01:00
Jan Prochazka 18320352ff v5.1.7-alpha.13 2023-01-02 18:35:35 +01:00
Jan Prochazka d3292810f8 v5.1.7-beta.12 2023-01-01 19:55:59 +01:00
Jan Prochazka 7cd493e518 fixed(oracle) - removed incorrect query result row 2023-01-01 19:55:08 +01:00
Jan Prochazka 6c4b56a28b fixed loading materialized views in oracle 2023-01-01 19:50:19 +01:00
Jan Prochazka 0c795e33c3 commented out some console.log in oracle driver 2023-01-01 19:48:36 +01:00
Jan Prochazka fd2e1e0cae v5.1.7-beta.11 2023-01-01 12:25:13 +01:00
Jan Prochazka 13fd7a0aad memoize connection folder expand state #425 2023-01-01 12:24:42 +01:00
Jan Prochazka d5e240a701 rename, delete connection folder #425 2023-01-01 12:16:59 +01:00
Jan Prochazka 2151252032 fix 2023-01-01 10:29:54 +01:00
Jan Prochazka cd175973d9 fixed file filters #445 2022-12-31 14:33:58 +01:00
Jan Prochazka 10789a75a8 force text display 2022-12-31 14:17:47 +01:00
Jan Prochazka f775fbad29 force text display 2022-12-31 14:16:08 +01:00
Jan Prochazka dbdb50f796 fix 2022-12-31 13:50:51 +01:00
Jan Prochazka 61a2002627 deep refresh on datagrid 2022-12-31 13:39:07 +01:00
Jan Prochazka 4d8e0d44d1 ALTER VIEW, ALTER PROCEDURE scripts 2022-12-31 13:05:16 +01:00
Jan Prochazka e13808945c removed unused imports 2022-12-31 12:44:44 +01:00
Jan Prochazka 3aa7e6c022 map view refactor 2022-12-31 12:43:27 +01:00
Jan Prochazka cb0a9770d2 map cell view improved 2022-12-31 12:29:47 +01:00
Jan Prochazka 4a2b33276d clone mongto rows without _id #404 2022-12-31 11:18:18 +01:00
Jan Prochazka fb1cbc71f2 clear perspective cache reloads also patterns 2022-12-31 10:48:14 +01:00
Jan Prochazka b8fcbbbc93 drag & drop memory in designer 2022-12-31 10:37:25 +01:00
Jan Prochazka 6b5d2114bf designer - column filter 2022-12-31 10:05:09 +01:00
Jan Prochazka 22b8b30768 Merge branch 'develop' 2022-12-30 19:10:46 +01:00
Jan Prochazka 175d85a462 fix 2022-12-30 19:10:10 +01:00
Jan Prochazka ed69c55e91 Merge branch 'persubjoin' into develop 2022-12-30 18:55:32 +01:00
Jan Prochazka 637184a28e fix 2022-12-30 18:54:47 +01:00
Jan Prochazka 242e24b783 fix 2022-12-30 12:47:18 +01:00
Jan Prochazka d407c72f78 handle $oid 2022-12-30 12:24:05 +01:00
Jan Prochazka 380ab2e69e fixes 2022-12-30 10:30:38 +01:00
Jan Prochazka 646a83b288 fix 2022-12-30 09:04:40 +01:00
Jan Prochazka eb80eb1afa perspective subloading works 2022-12-29 20:25:21 +01:00
Jan Prochazka b0f4965fb9 node load props impl - naive 2022-12-28 16:23:43 +01:00
Jan Prochazka 24b5e52666 refactor 2022-12-28 10:11:19 +01:00
Jan Prochazka f45c9e38cb subcolumns in designer 2022-12-28 09:57:32 +01:00
Jan Prochazka 78b8fc0531 ux in DB login modal 2022-12-28 09:53:12 +01:00
Jan Prochazka 06d6815df4 readme 2022-12-26 09:34:50 +01:00
Jan Prochazka 4566654acb v5.1.7-beta.10 2022-12-25 19:59:39 +01:00
Jan Prochazka eb3a7f7253 Merge branch 'askpassword' 2022-12-25 19:33:21 +01:00
Jan Prochazka c340ac9112 disconnect command 2022-12-25 19:27:24 +01:00
Jan Prochazka 5c1c4e1fa6 single connection multi db layout 2022-12-25 18:57:08 +01:00
Jan Prochazka bbb6c5e5f5 renamed singleDatabase => singleDbConnection 2022-12-25 18:01:36 +01:00
Jan Prochazka 54278f6276 single connection config 2022-12-25 17:52:59 +01:00
Jan Prochazka a6fa116b5e renamed singleDatabaseMode to lockedDatabaseMode 2022-12-25 17:32:14 +01:00
Jan Prochazka 3792f1001e .env file 2022-12-25 17:32:12 +01:00
Jan Prochazka 8d1d6537a4 ask password works! 2022-12-25 17:12:12 +01:00
Jan Prochazka 783f26b500 structured reload trigger 2022-12-25 15:35:56 +01:00
Jan Prochazka 1eea117062 connection testing 2022-12-25 12:48:10 +01:00
Jan Prochazka d66fc06403 ask password logic & modal 2022-12-25 10:21:19 +01:00
Jan Prochazka fa13990189 single database mode 2022-12-23 11:03:03 +01:00
Jan Prochazka 45652cfc33 docs #444 2022-12-22 20:51:35 +01:00
Jan Prochazka 89219722a9 mongo: create collection backup 2022-12-22 20:49:22 +01:00
Jan Prochazka b0d78250e1 row count info added to mongoDB 2022-12-22 17:35:46 +01:00
Jan Prochazka 0e92d51f3c formatKeyText called in CommandPalette 2022-12-22 17:17:11 +01:00
Jan Prochazka 535737ba72 code cleanup 2022-12-22 17:11:44 +01:00
Jan Prochazka 2213cda1c6 #246 fuzzy search in ctrl+p+capital search 2022-12-22 17:10:23 +01:00
Jan Prochazka b712e3c6ae v5.1.7-beta.9 2022-12-18 18:57:22 +01:00
Jan Prochazka f7f35ee306 fixed package version 2022-12-18 18:54:42 +01:00
Jan Prochazka 973015aed8 Merge remote-tracking branch 'rinie/oracle' 2022-12-18 18:47:05 +01:00
Jan Prochazka 2ae50ccbad Merge branch 'master' of github.com:dbgate/dbgate 2022-12-18 18:39:39 +01:00
Jan Prochazka f2d8dfaf18 PR #440 - handle on startup 2022-12-18 18:39:35 +01:00
Jan Prochazka b6afd24172 fix 2022-12-18 18:29:21 +01:00
Jan Prochazka 245ec58505 Merge branch 'profiler' 2022-12-18 18:26:25 +01:00
Jan Prochazka 1d8264c935 Merge pull request #440 from ProjectInfinity/maximize-button
Make maximize button reflect window state
2022-12-18 18:18:02 +01:00
Jan Prochazka 0ff4f0d7e9 profile refactoring, fixes 2022-12-18 17:03:47 +01:00
Jan Prochazka 3bbdc56309 max duration profiler measure 2022-12-18 16:18:56 +01:00
Jan Prochazka 2e37788471 profiler charts 2022-12-18 13:48:24 +01:00
Jan Prochazka 9a2631dc09 profiler charts 2022-12-18 12:29:21 +01:00
Jan Prochazka dbfdaafb86 jsonl filtering fixes 2022-12-18 09:08:03 +01:00
Jan Prochazka cf3df9cda3 short json value shown in grid 2022-12-17 20:22:42 +01:00
Jan Prochazka 274fcd339b archive file - open in profiler 2022-12-17 20:07:26 +01:00
Jan Prochazka 123e00ecbc mongo profiler formatter 2022-12-17 12:34:28 +01:00
Jan Prochazka 34a4f9adbf save profiler output to archive 2022-12-17 08:57:16 +01:00
Jan Prochazka 0e819bcc45 mongodb profiler 2022-12-16 14:52:49 +01:00
ProjectInfinity 570cb2d96b Make maximize button reflect window state 2022-12-16 14:44:26 +01:00
Jan Prochazka c1ba758b01 mongo profile view - shows collection tab 2022-12-16 09:42:38 +01:00
Jan Prochazka 11daa56335 mongo filter: empty array, not empty array 2022-12-16 08:06:37 +01:00
Jan Prochazka a9257cf4f8 camel case search 2022-12-15 20:37:38 +01:00
Jan Prochazka 1a2acd764d improved editor margin #422 2022-12-15 18:50:23 +01:00
Jan Prochazka 27b0af6408 Merge branch 'master' of github.com:dbgate/dbgate 2022-12-15 17:39:24 +01:00
Jan Prochazka 3c63738809 fixed missing versioned tables #433 2022-12-15 17:38:02 +01:00
Jan Prochazka 9305e767cd Merge pull request #437 from horaciod/patch-1
Typo in export action
2022-12-15 17:30:24 +01:00
Jan Prochazka 2fddf32e54 fixed broken F5 2022-12-15 17:15:19 +01:00
Jan Prochazka 469fd76f89 upgrade dbgate-query-splitter 2022-12-15 17:10:01 +01:00
Horacio Degiorgi 1f682d91c9 Typo in export action 2022-12-14 10:51:31 -03:00
Jan Prochazka 87c3b39ae9 v5.1.7-beta.8 2022-12-09 15:50:07 +01:00
Jan Prochazka a1032138da Merge branch 'summary' 2022-12-09 15:49:22 +01:00
Jan Prochazka 9fa6155cd9 refresh server summary 2022-12-09 15:48:48 +01:00
Jan Prochazka ea77b4fc1a view profile data 2022-12-09 15:41:42 +01:00
Jan Prochazka 61dc9da3f0 set mongo profiling 2022-12-09 15:14:21 +01:00
Jan Prochazka 9d6fe2460f fix 2022-12-09 08:47:26 +01:00
Jan Prochazka e6ac878b74 mongo summary improved 2022-12-08 19:51:01 +01:00
Jan Prochazka ceea1a9047 mongo server summary 2022-12-07 22:05:47 +01:00
Jan Prochazka f7bd12881e v5.1.7-beta.7 2022-12-06 19:55:28 +01:00
Jan Prochazka 4d74626e7f API url fix 2022-12-06 19:55:18 +01:00
Jan Prochazka a2884a580f v5.1.7-beta.6 2022-12-06 19:39:14 +01:00
Jan Prochazka c8c7df3691 static content server without authorization 2022-12-06 19:38:52 +01:00
Jan Prochazka 9f8ac81038 v5.1.7-beta.5 2022-12-06 18:48:50 +01:00
Jan Prochazka ae8c5c0cc1 v5.1.7-beta.4 2022-11-28 21:21:37 +01:00
Jan Prochazka 3dc63507ad fix 2022-11-28 21:21:24 +01:00
Jan Prochazka 6ddb8b8bf9 fixed mongoUrl regression 2022-11-28 21:07:48 +01:00
Jan Prochazka 688434d25b v5.1.7-beta.3 2022-11-27 20:33:16 +01:00
Jan Prochazka df2074173b js fix 2022-11-27 20:33:09 +01:00
Jan Prochazka b825167687 v5.1.7-beta.2 2022-11-27 19:45:10 +01:00
Jan Prochazka 621181d532 Merge branch 'oracle' 2022-11-27 19:44:28 +01:00
Jan Prochazka c2b6b08105 Merge branch 'oauth' 2022-11-27 19:43:40 +01:00
Rinie Kervel 97cb9f2752 just before own repo 2022-11-22 10:40:38 +01:00
Rinie Kervel 61287c5480 try native plugin 2022-11-21 15:36:38 +01:00
Jan Prochazka 9c1c008b0d v5.1.7-beta.1 2022-11-20 17:08:19 +01:00
Jan Prochazka 896cc21386 oracledb added to native dependencies 2022-11-20 17:07:21 +01:00
Jan Prochazka a7a8ea053b fill native modules - oracledb 2022-11-20 17:03:55 +01:00
Jan Prochazka 522170d5c3 changelog 2022-11-14 19:44:30 +01:00
Jan Prochazka 3891e7768d v5.1.6 2022-11-14 19:35:58 +01:00
Rinie Kervel 48b1e28ee1 fix lowercase for tablelist do not always convert column names to lower 2022-11-07 12:09:50 +01:00
Rinie Kervel a0cefbc1ca merge dbgate master and test drivers 2022-10-30 08:32:53 +01:00
Rinie Kervel 5c0c145fd6 Merge branch 'dbgate:master' into oracle 2022-10-30 08:13:51 +01:00
Rinie Kervel 64168577ab zap fastmode as IN =OBJECT_ID_CONDITION does not work 2022-10-13 12:05:08 +02:00
Rinie Kervel 51952ecfdd Oracle driver first data 2022-10-11 17:04:38 +02:00
Rinie Kervel 4939b74179 get version result and login from oracle 2022-09-26 17:54:24 +02:00
387 changed files with 13347 additions and 6705 deletions
+17 -13
View File
@@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macOS-10.15, windows-2022, ubuntu-18.04]
os: [macos-12, windows-2022, ubuntu-22.04]
# os: [macOS-10.15]
steps:
@@ -23,19 +23,27 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 14.x
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 18.x
- name: yarn adjustPackageJson
run: |
yarn adjustPackageJson
- name: yarn set timeout
run: |
yarn config set network-timeout 100000
- name: yarn install
run: |
yarn install
- name: setCurrentVersion
run: |
yarn setCurrentVersion
- name: printSecrets
run: |
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
- name: fillNativeModulesElectron
run: |
yarn fillNativeModulesElectron
@@ -43,7 +51,7 @@ jobs:
run: |
yarn fillPackagedPlugins
- name: Install Snapcraft
if: matrix.os == 'ubuntu-18.04'
if: matrix.os == 'ubuntu-22.04'
uses: samuelmeuli/action-snapcraft@v1
- name: Publish
run: |
@@ -60,20 +68,15 @@ jobs:
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Save snap login
if: matrix.os == 'ubuntu-18.04'
run: 'echo "$SNAPCRAFT_LOGIN" > snapcraft.login'
shell: bash
env:
SNAPCRAFT_LOGIN: ${{secrets.SNAPCRAFT_LOGIN}}
- name: publishSnap
if: matrix.os == 'ubuntu-18.04'
if: matrix.os == 'ubuntu-22.04'
run: |
snapcraft login --with snapcraft.login
snapcraft upload --release=beta app/dist/*.snap
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
- name: Copy artifacts
run: |
@@ -91,6 +94,7 @@ jobs:
mv app/dist/*.exe artifacts/ || true
mv app/dist/*.zip artifacts/ || true
mv app/dist/*.tar.gz artifacts/ || true
mv app/dist/*.AppImage artifacts/ || true
mv app/dist/*.deb artifacts/ || true
mv app/dist/*.snap artifacts/ || true
+20 -16
View File
@@ -16,8 +16,8 @@ jobs:
strategy:
fail-fast: false
matrix:
# os: [ubuntu-18.04, windows-2016]
os: [macOS-10.15, windows-2022, ubuntu-18.04]
# os: [ubuntu-22.04, windows-2016]
os: [macos-12, windows-2022, ubuntu-22.04]
steps:
- name: Context
@@ -27,13 +27,16 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 14.x
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 18.x
- name: yarn adjustPackageJson
run: |
yarn adjustPackageJson
- name: yarn set timeout
run: |
yarn config set network-timeout 100000
- name: yarn install
run: |
# yarn --version
@@ -42,6 +45,11 @@ jobs:
- name: setCurrentVersion
run: |
yarn setCurrentVersion
- name: printSecrets
run: |
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
- name: fillNativeModulesElectron
run: |
yarn fillNativeModulesElectron
@@ -49,7 +57,7 @@ jobs:
run: |
yarn fillPackagedPlugins
- name: Install Snapcraft
if: matrix.os == 'ubuntu-18.04'
if: matrix.os == 'ubuntu-22.04'
uses: samuelmeuli/action-snapcraft@v1
- name: Publish
run: |
@@ -66,24 +74,19 @@ jobs:
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: generatePadFile
run: |
yarn generatePadFile
- name: Save snap login
if: matrix.os == 'ubuntu-18.04'
run: 'echo "$SNAPCRAFT_LOGIN" > snapcraft.login'
shell: bash
env:
SNAPCRAFT_LOGIN: ${{secrets.SNAPCRAFT_LOGIN}}
- name: publishSnap
if: matrix.os == 'ubuntu-18.04'
if: matrix.os == 'ubuntu-22.04'
run: |
snapcraft login --with snapcraft.login
snapcraft upload --release=stable app/dist/*.snap
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
- name: Copy artifacts
run: |
@@ -101,6 +104,7 @@ jobs:
mv app/dist/*.exe artifacts/ || true
mv app/dist/*.zip artifacts/ || true
mv app/dist/*.tar.gz artifacts/ || true
mv app/dist/*.AppImage artifacts/ || true
mv app/dist/*.deb artifacts/ || true
mv app/dist/*.dmg artifacts/ || true
@@ -137,12 +141,12 @@ jobs:
mv app/dist/dbgate-pad.xml artifacts/ || true
- name: Copy latest-linux.yml
if: matrix.os == 'ubuntu-18.04'
if: matrix.os == 'ubuntu-22.04'
run: |
mv app/dist/latest-linux.yml artifacts/latest-linux.yml || true
- name: Copy latest-mac.yml
if: matrix.os == 'macOS-10.15'
if: matrix.os == 'macos-12'
run: |
mv app/dist/latest-mac.yml artifacts/latest-mac.yml || true
+10 -3
View File
@@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-18.04]
os: [ubuntu-22.04]
steps:
- name: Context
@@ -53,18 +53,25 @@ jobs:
type=match,pattern=\d+.\d+.\d+,suffix=-alpine,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
type=raw,value=alpine,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
- name: Use Node.js 14.x
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 18.x
- name: yarn install
run: |
# yarn --version
# yarn config set network-timeout 300000
yarn install
- name: setCurrentVersion
run: |
yarn setCurrentVersion
- name: printSecrets
run: |
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
- name: Prepare docker image
run: |
yarn run prepare:docker
+19 -3
View File
@@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-18.04]
os: [ubuntu-22.04]
steps:
- name: Context
@@ -30,10 +30,10 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 14.x
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 18.x
- name: Configure NPM token
env:
@@ -49,6 +49,12 @@ jobs:
run: |
yarn setCurrentVersion
- name: printSecrets
run: |
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
- name: Publish types
working-directory: packages/types
run: |
@@ -94,6 +100,11 @@ jobs:
run: |
npm publish
- name: Publish dbmodel
working-directory: packages/dbmodel
run: |
npm publish
- name: Publish dbgate-plugin-csv
working-directory: plugins/dbgate-plugin-csv
run: |
@@ -138,3 +149,8 @@ jobs:
working-directory: plugins/dbgate-plugin-redis
run: |
npm publish
- name: Publish dbgate-plugin-oracle
working-directory: plugins/dbgate-plugin-oracle
run: |
npm publish
+1 -1
View File
@@ -8,7 +8,7 @@ on:
jobs:
test-runner:
runs-on: ubuntu-latest
container: node:14.18
container: node:18
steps:
- name: Context
+161
View File
@@ -8,6 +8,167 @@ Builds:
- linux - application for linux
- win - application for Windows
### 5.3.0
- CHANGED: New Oracle driver, much better Oracle support. Works now also in docker distribution
- FIXED: Connection to oracle with service name #809
- ADDED: Connect to redis using a custom username #807
- FIXED: Unable to open SQL files #797
- FIXED: MongoDB query without columns #811
- ADDED: Switch connection for opened file #814
### 5.2.9
- FIXED: PostgresSQL doesn't show tables when connected #793 #805
- FIXED: MongoDB write operations fail #798 #802
- FIXED: Elecrron app logging losed most of log messages
- FIXED: Connection error with SSH tunnel
- ADDED: option to disable autoupgrades (with --disable-auto-upgrade)
- ADDED: Send error context to github gist
### 5.2.8
- FIXED: file menu save and save as not working
- FIXED: query editor on import/export screen overlaps with selector
- FIXED: Fixed inconsistencies in max/unmaximize window buttons
- FIXED: shortcut for select all
- FIXED: download with auth header
- CHANGED: Upgraded database drivers for mysql, postgres, sqlite, mssql, mongo, redis
- CHANGED: Upgraded electron version (now using v30)
- ADDED: Vim keyboard bindings for editor
- FIXED: Correctly select the save folder for dump
- ADDED: enum + set for mysql (#693)
- FIXED: localStorageGabageCollector not working
- FIXED: Encoding error when opening Unicode query files
- ADDED: Add copy/paste to query tab and database list
- ADDED: Add copy name to table list
- FIXED: Make TabControl scrollable (#730)
- ADDED: Add copy to column list
- FIXED: Problems with SQLite + glibc in docker containers
- ADDED: Button for discard/reset changes (#759)
- FIXED: Don't show error dialog when subprocess fails, as DbGate handles this correctly (#751, #746, #542, #272)
### 5.2.7
- FIXED: fix body overflow when context menu height great than viewport #592
- FIXED: Pass signals in entrypoint.sh #596
- FIXED: Remove missing links to jenasoft #625
- FIXED: add API headers on upload call #627
- FIXED: Disabled shell scripting for NPM distribution by default
- FIXED: Fixed data import from files #633
- FIXED: Fixed showing GPS positions #575
- CHANGED: Improved stability of electron client on Windows and Mac (fewer EPIPE errors)
### 5.2.6
- FIXED: DbGate creates a lot of .tmp.node files in the temp directory #561
- FIXED: Typo in datetimeoffset dataType #556
- FIXED: SQL export is using the wrong hour formatting #537
- FIXED: Missing toolstrip and adds up to 200% zoom to diagram view #524
- FIXED: MongoDB password could contain special characters #560
### 5.2.5
- ADDED: Split Windows #394
- FIXED: Postgres index asc/desc #514
- FIXED: Excel export not working since 5.2.3 #511
- ADDED: Include macOS specific app icon #494
- FIXED: Resizing window resets window contents #479
- FIXED: Solved some minor problems with widget collapsing
### 5.2.4
- FIXED: npm version crash (#508)
### 5.2.3
- ADDED: Search entire table (multi column filter) #491
- ADDED: OracleDB - connection to toher than default ports #496
- CHANGED: OracleDB - status of support set to experimental
- FIXED: OracleDB database URL - fixes: Connect to default Oracle database #489
- ADDED: HTML, XML code highlighting for Edit cell value #485
- FIXED: Intellisense - incorrect alias after ORDER BY clause #484
- FIXED: Typo in SQL-Generator #481
- ADDED: Data duplicator #480
- FIXED: MongoDB - support for views #476
- FIXED: "SQL:CREATE TABLE" generated SQL default value syntax errors #455
- FIXED: Crash when right-clicking on tables #452
- FIXED: View sort #436
- ADDED: Arm64 version for Windows #473
- ADDED: Sortable query results and data archive
- CHANGED: Use transactions for saving table data
- CHANGED: Save table structure uses transactions
- ADDED: Table data editing - shows editing mark
- ADDED: Editing data archive files
- FIXED: Delete cascade options when using more than 2 tables
- ADDED: Save to current archive commands
- ADDED: Current archive mark is on status bar
- FIXED: Changed package used for parsing JSONL files when browsing - fixes backend freezing
- FIXED: SSL option for mongodb #504
- REMOVED: Data sheet editor
- FIXED: Creating SQLite autoincrement column
- FIXED: Better error reporting from exports/import/dulicator
- CHANGED: Optimalizede OracleDB analysing algorithm
- ADDED: Mutli column filter for perspectives
- FIXED: Fixed some scenarios using tables from different DBs
- FIXED: Sessions with long-running queries are not killed
### 5.2.2
- FIXED: Optimalized load DB structure for PostgreSQL #451
- ADDED: Auto-closing query connections after configurable (15 minutes default) no-activity interval #468
- ADDED: Set application-name connection parameter (for PostgreSQL and MS SQL) for easier identifying of DbGate connections
- ADDED: Filters supports binary IDs #467
- FIXED: Ctrl+Tab works (switching tabs) #457
- FIXED: Format code supports non-standard letters #450
- ADDED: New logging system, log to file, ability to reduce logging #360 (using https://www.npmjs.com/package/pinomin)
- FIXED: crash on Windows and Mac after system goes in suspend mode #458
- ADDED: dbmodel standalone NPM package (https://www.npmjs.com/package/dbmodel) - deploy database via commandline tool
### 5.2.1
- FIXED: client_id param in OAuth
- ADDED: OAuth scope parameter
- FIXED: login page - password was not sent, when submitting by pressing ENTER
- FIXED: Used permissions fix
- FIXED: Export modal - fixed crash when selecting different database
### 5.2.0
- ADDED: Oracle database support #380
- ADDED: OAuth authentification #407
- ADDED: Active directory (Windows) authentification #261
- ADDED: Ask database credentials when login to DB
- ADDED: Login form instead of simple authorization (simple auth is possible with special configuration)
- FIXED: MongoDB - connection uri regression
- ADDED: MongoDB server summary tab
- FIXED: Broken versioned tables in MariaDB #433
- CHANGED: Improved editor margin #422
- ADDED: Implemented camel case search in all search boxes
- ADDED: MonhoDB filter empty array, not empty array
- ADDED: Maximize button reflects window state
- ADDED: MongoDB - database profiler
- CHANGED: Short JSON values are shown directly in grid
- FIXED: Fixed filtering nested fields in NDJSON viewer
- CHANGED: Improved fuzzy search after Ctrl+P #246
- ADDED: MongoDB: Create collection backup
- ADDED: Single database mode
- ADDED: Perspective designer supports joins from MongoDB nested documents and arrays
- FIXED: Perspective designer joins on MongoDB ObjectId fields
- ADDED: Filtering columns in designer (query designer, diagram designer, perspective designer)
- FIXED: Clone MongoDB rows without _id attribute #404
- CHANGED: Improved cell view with GPS latitude, longitude fields
- ADDED: SQL: ALTER VIEW and SQL:ALTER PROCEDURE scripts
- ADDED: Ctrl+F5 refreshes data grid also with database structure #428
- ADDED: Perspective display modes: text, force text #439
- FIXED: Fixed file filters #445
- ADDED: Rename, remove connection folder, memoize opened state after app restart #425
- FIXED: Show SQLServer alter store procedure #435
### 5.1.6
- ADDED: Connection folders support #274
- ADDED: Keyboard shortcut to hide result window and show/hide the side toolbar #406
- ADDED: Ability to show/hide query results #406
- FIXED: Double click does not maximize window on MacOS #416
- FIXED: Some perspective rendering errors
- FIXED: Connection to MongoDB via database URL info SSH tunnel is used
- CHANGED: Updated windows code signing certificate
- ADDED: Query session cleanup (kill query sessions, if browser tab is closed)
- CHANGED: More strict timeouts to kill database and server connections (reduces resource consumption)
### 5.1.5
- ADDED: Support perspectives for MongoDB - MongoDB query designer
- ADDED: Show JSON content directly in the overview #395
+11 -7
View File
@@ -22,6 +22,7 @@ DbGate is licensed under MIT license and is completely free.
* MySQL
* PostgreSQL
* SQL Server
* Oracle
* MongoDB
* Redis
* SQLite
@@ -66,23 +67,23 @@ DbGate is licensed under MIT license and is completely free.
* Mongo JavaScript editor, execute Mongo script (with NodeJs syntax)
* Redis tree view, generate script from keys, run Redis script
* Runs as application for Windows, Linux and Mac. Or in Docker container on server and in web Browser on client.
* Import, export from/to CSV, Excel, JSON, XML
* Import, export from/to CSV, Excel, JSON, NDJSON, XML
* Free table editor - quick table data editing (cleanup data after import/before export, prototype tables etc.)
* Archives - backup your data in JSON files on local filesystem (or on DbGate server, when using web application)
* Archives - backup your data in NDJSON files on local filesystem (or on DbGate server, when using web application)
* Charts, export chart to HTML page
* For detailed info, how to run DbGate in docker container, visit [docker hub](https://hub.docker.com/r/dbgate/dbgate)
* Extensible plugin architecture
* Perspectives - nested table view over complex relational data
* Perspectives - nested table view over complex relational data, query designer on MongoDB databases
## How to contribute
Any contributions are welcome. If you want to contribute without coding, consider following:
* Tell your friends about DbGate or share on social networks - when more people will use DbGate, it will grow to be better
* Write review on [Slant.co](https://www.slant.co/improve/options/41086/~dbgate-review) or [G2](https://www.g2.com/products/dbgate/reviews)
* Create issue, if you find problem in app, or you have idea to new feature. If issue already exists, you could leave comment on it, to prioritise most wanted issues.
* Create issue, if you find problem in app, or you have idea to new feature. If issue already exists, you could leave comment on it, to prioritise most wanted issues
* Create some tutorial video on [youtube](https://www.youtube.com/playlist?list=PLCo7KjCVXhr0RfUSjM9wJMsp_ShL1q61A)
* Become a backer on [GitHub sponsors](https://github.com/sponsors/dbgate) or [Open collective](https://opencollective.com/dbgate)
* Where a small coding is acceptable for you, you could [create plugin](https://dbgate.org/docs/plugin-development.html). Plugins for new themes can be created actually without JS coding.
* Where a small coding is acceptable for you, you could [create plugin](https://dbgate.org/docs/plugin-development.html). Plugins for new themes can be created actually without JS coding
Thank you!
@@ -93,7 +94,7 @@ There are many database managers now, so why DbGate?
* Many data browsing functions based using foreign keys - master/detail, expand columns, expandable form view
## Design goals
* Application simplicity - DbGate takes the best and only the best from old [DbGate](http://www.jenasoft.com/dbgate), [DatAdmin](http://www.jenasoft.com/datadmin) and [DbMouse](http://www.jenasoft.com/dbmouse) .
* Application simplicity - DbGate takes the best and only the best from old DbGate, [DatAdmin](https://www.softpedia.com/get/Internet/Servers/Database-Utils/DatAdmin-Personal.shtml), [DbMouse](https://www.softpedia.com/get/Internet/Servers/Database-Utils/DbMouse.shtml) and [SQL Database Studio](https://en.wikipedia.org/wiki/SQL_Database_Studio)
* Minimal dependencies
* Frontend - Svelte
* Backend - NodeJs, ExpressJs, database connection drivers
@@ -174,4 +175,7 @@ cd dbgate-plugin-my-new-plugin # this directory is created by wizard, edit, what
yarn plugin # this compiles plugin and copies it into existing DbGate installation
```
After restarting DbGate, you could use your new plugin from DbGate.
After restarting DbGate, you could use your new plugin from DbGate.
## Logging
DbGate uses [pinomin logger](https://github.com/dbgate/pinomin). So by default, it produces JSON log messages into console and log files. If you want to see formatted logs, please use [pino-pretty](https://github.com/pinojs/pino-pretty) log formatter.
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>org.dbgate.DbGate</id>
<name>DbGate</name>
<summary>(no)SQL database client</summary>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license>
<developer_name>Jan Prochazka</developer_name>
<description>
<p>DbGate is cross-platform database manager. It's designed to be simple to use and effective, when working with more databases simultaneously. But there are also many advanced features like schema compare, visual query designer, chart visualisation or batch export and import.</p>
</description>
<url type="homepage">https://dbgate.org/</url>
<url type="vcs-browser">https://github.com/dbgate/dbgate</url>
<url type="contact">https://dbgate.org/about/</url>
<url type="donation">https://github.com/sponsors/dbgate</url>
<url type="bugtracker">https://github.com/dbgate/dbgate/issues</url>
<launchable type="desktop-id">org.dbgate.DbGate.desktop</launchable>
<screenshots>
<screenshot type="default">
<image>
https://github.com/dbgate/dbgate/raw/c2abc83f994a56945c27fccea3df84b48005961f/img/screenshot1.png</image>
</screenshot>
<screenshot>
<image>
https://github.com/dbgate/dbgate/raw/c2abc83f994a56945c27fccea3df84b48005961f/img/screenshot2.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1"/>
<releases>
<release version="5.2.7" date="2024-05-13"/>
</releases>
</component>
+15 -7
View File
@@ -19,9 +19,10 @@
"appId": "org.dbgate",
"productName": "DbGate",
"afterSign": "electron-builder-notarize",
"asarUnpack": "**/*.node",
"mac": {
"category": "database",
"icon": "icon512.png",
"icon": "icon512-mac.png",
"hardenedRuntime": true,
"entitlements": "entitlements.mac.plist",
"entitlementsInherit": "entitlements.mac.plist",
@@ -47,7 +48,8 @@
"armv7l",
"arm64"
]
}
},
"tar.gz"
],
"icon": "icons/",
"category": "Development",
@@ -71,7 +73,13 @@
},
"win": {
"target": [
"nsis",
{
"target": "nsis",
"arch": [
"x64",
"arm64"
]
},
{
"target": "zip",
"arch": [
@@ -107,12 +115,12 @@
"devDependencies": {
"copyfiles": "^2.2.0",
"cross-env": "^6.0.3",
"electron": "17.4.10",
"electron": "30.0.2",
"electron-builder": "23.1.0",
"electron-builder-notarize": "^1.5.0"
"electron-builder-notarize": "^1.5.2"
},
"optionalDependencies": {
"better-sqlite3": "7.6.2",
"msnodesqlv8": "^2.6.0"
"better-sqlite3": "9.6.0",
"msnodesqlv8": "^4.2.1"
}
}
+57 -6
View File
@@ -1,6 +1,8 @@
const electron = require('electron');
const os = require('os');
const fs = require('fs');
// const unhandled = require('electron-unhandled');
// const { openNewGitHubIssue, debugInfo } = require('electron-util');
const { Menu, ipcMain } = require('electron');
const { autoUpdater } = require('electron-updater');
const log = require('electron-log');
@@ -15,6 +17,7 @@ const path = require('path');
const url = require('url');
const mainMenuDefinition = require('./mainMenuDefinition');
const { settings } = require('cluster');
let disableAutoUpgrade = false;
// require('@electron/remote/main').initialize();
@@ -22,16 +25,46 @@ const configRootPath = path.join(app.getPath('userData'), 'config-root.json');
let initialConfig = {};
let apiLoaded = false;
let mainModule;
// let getLogger;
// let loadLogsContent;
process.on('uncaughtException', function (error) {
console.error('uncaughtException', error);
});
const isMac = () => os.platform() == 'darwin';
// unhandled({
// showDialog: true,
// reportButton: error => {
// openNewGitHubIssue({
// user: 'dbgate',
// repo: 'dbgate',
// body: `PLEASE DELETE SENSITIVE INFO BEFORE POSTING ISSUE!!!\n\n\`\`\`\n${
// error.stack
// }\n\`\`\`\n\n---\n\n${debugInfo()}\n\n\`\`\`\n${loadLogsContent ? loadLogsContent(50) : ''}\n\`\`\``,
// });
// },
// logger: error => (getLogger ? getLogger('electron').fatal(error) : console.error(error)),
// });
try {
initialConfig = JSON.parse(fs.readFileSync(configRootPath, { encoding: 'utf-8' }));
disableAutoUpgrade = initialConfig['disableAutoUpgrade'] || false;
} catch (err) {
console.log('Error loading config-root:', err.message);
initialConfig = {};
}
if (process.argv.includes('--disable-auto-upgrade')) {
console.log('Disabling auto-upgrade');
disableAutoUpgrade = true;
}
if (process.argv.includes('--enable-auto-upgrade')) {
console.log('Enabling auto-upgrade');
disableAutoUpgrade = false;
}
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
@@ -154,6 +187,10 @@ ipcMain.on('app-started', async (event, arg) => {
mainWindow.webContents.send('run-command', runCommandOnLoad);
runCommandOnLoad = null;
}
if (initialConfig['winIsMaximized']) {
mainWindow.webContents.send('setIsMaximized', true);
}
});
ipcMain.on('window-action', async (event, arg) => {
if (!mainWindow) {
@@ -164,11 +201,7 @@ ipcMain.on('window-action', async (event, arg) => {
mainWindow.minimize();
break;
case 'maximize':
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow.maximize();
}
mainWindow.isMaximized() ? mainWindow.unmaximize() : mainWindow.maximize();
break;
case 'close':
mainWindow.close();
@@ -211,6 +244,9 @@ ipcMain.on('window-action', async (event, arg) => {
case 'paste':
mainWindow.webContents.paste();
break;
case 'selectAll':
mainWindow.webContents.selectAll();
break;
}
});
@@ -296,6 +332,7 @@ function createWindow() {
JSON.stringify({
winBounds: mainWindow.getBounds(),
winIsMaximized: mainWindow.isMaximized(),
disableAutoUpgrade,
}),
'utf-8'
);
@@ -308,6 +345,14 @@ function createWindow() {
mainWindow.setIcon(path.resolve(__dirname, '../icon.png'));
}
// mainWindow.webContents.toggleDevTools();
mainWindow.on('maximize', () => {
mainWindow.webContents.send('setIsMaximized', true);
});
mainWindow.on('unmaximize', () => {
mainWindow.webContents.send('setIsMaximized', false);
});
}
if (!apiLoaded) {
@@ -327,9 +372,12 @@ function createWindow() {
// path.join(__dirname, process.env.DEVMODE ? '../../packages/api/src/index' : '../packages/api/dist/bundle.js')
// )
// );
api.configureLogger();
const main = api.getMainModule();
main.useAllControllers(null, electron);
mainModule = main;
// getLogger = api.getLogger;
// loadLogsContent = api.loadLogsContent;
apiLoaded = true;
}
mainModule.setElectronSender(mainWindow.webContents);
@@ -347,7 +395,10 @@ function createWindow() {
}
function onAppReady() {
if (!process.env.DEVMODE) {
if (disableAutoUpgrade) {
console.log('Auto-upgrade is disabled, run dbgate --enable-auto-upgrade to enable');
}
if (!process.env.DEVMODE && !disableAutoUpgrade) {
autoUpdater.checkForUpdatesAndNotify();
}
createWindow();
+6
View File
@@ -21,6 +21,7 @@ module.exports = ({ editMenu }) => [
{ divider: true },
{ command: 'file.exit', hideDisabled: true },
{ command: 'app.logout', hideDisabled: true, skipInApp: true },
{ command: 'app.disconnect', hideDisabled: true, skipInApp: true },
],
},
{
@@ -47,6 +48,7 @@ module.exports = ({ editMenu }) => [
{ command: 'edit.cut' },
{ command: 'edit.copy' },
{ command: 'edit.paste' },
{ command: 'edit.selectAll' },
],
}
: null,
@@ -85,6 +87,10 @@ module.exports = ({ editMenu }) => [
{ command: 'sql.generator', hideDisabled: true },
{ command: 'file.import', hideDisabled: true },
{ command: 'new.modelCompare', hideDisabled: true },
{ divider: true },
{ command: 'folder.showLogs', hideDisabled: true },
{ command: 'folder.showData', hideDisabled: true },
{ command: 'new.gist', hideDisabled: true },
],
},
{
+459 -340
View File
File diff suppressed because it is too large Load Diff
+14 -2
View File
@@ -1,9 +1,21 @@
FROM node:14
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
curl \
gnupg \
iputils-ping \
iproute2 \
&& rm -rf /var/lib/apt/lists/*
unixodbc \
gcc \
g++ \
make
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/nodesource-archive-keyring.gpg] https://deb.nodesource.com/node_18.x jammy main" | tee /etc/apt/sources.list.d/nodesource.list \
&& echo "deb-src [signed-by=/usr/share/keyrings/nodesource-archive-keyring.gpg] https://deb.nodesource.com/node_18.x jammy main" | tee -a /etc/apt/sources.list.d/nodesource.list \
&& apt-get update && apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/* \
&& npm install -g yarn
WORKDIR /home/dbgate-docker
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:14-alpine
FROM node:18-alpine
WORKDIR /home/dbgate-docker
+1 -1
View File
@@ -8,4 +8,4 @@ then
echo "$HOST_IP $HOST_DOMAIN" >> /etc/hosts
fi
node bundle.js --listen-api
exec node bundle.js --listen-api
+1 -1
View File
@@ -7,7 +7,7 @@ if (process.platform == 'win32') {
}
fillContent += `content['better-sqlite3'] = () => require('better-sqlite3');`;
const getContent = (empty) => `
const getContent = empty => `
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
const content = {};
@@ -1,7 +1,6 @@
const stableStringify = require('json-stable-stringify');
const _ = require('lodash');
const fp = require('lodash/fp');
const uuidv1 = require('uuid/v1');
const { testWrapper } = require('../tools');
const engines = require('../engines');
const { getAlterDatabaseScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools');
@@ -1,9 +1,9 @@
const stableStringify = require('json-stable-stringify');
const _ = require('lodash');
const fp = require('lodash/fp');
const uuidv1 = require('uuid/v1');
const { testWrapper } = require('../tools');
const engines = require('../engines');
const crypto = require('crypto');
const { getAlterTableScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools');
function pickImportantTableInfo(table) {
@@ -76,7 +76,7 @@ describe('Alter table', () => {
tbl.columns.push({
columnName: 'added',
dataType: 'int',
pairingId: uuidv1(),
pairingId: crypto.randomUUID(),
notNull: false,
autoIncrement: false,
});
@@ -0,0 +1,94 @@
const engines = require('../engines');
const stream = require('stream');
const { testWrapper } = require('../tools');
const dataDuplicator = require('dbgate-api/src/shell/dataDuplicator');
const { runCommandOnDriver } = require('dbgate-tools');
describe('Data duplicator', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Insert simple data - %s',
testWrapper(async (conn, driver, engine) => {
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't1',
columns: [
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
})
);
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't2',
columns: [
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
{ columnName: 'valfk', dataType: 'int', notNull: true },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
foreignKeys: [{ refTableName: 't1', columns: [{ columnName: 'valfk', refColumnName: 'id' }] }],
})
);
const gett1 = () =>
stream.Readable.from([
{ __isStreamHeader: true, __isDynamicStructure: true },
{ id: 1, val: 'v1' },
{ id: 2, val: 'v2' },
{ id: 3, val: 'v3' },
]);
const gett2 = () =>
stream.Readable.from([
{ __isStreamHeader: true, __isDynamicStructure: true },
{ id: 1, val: 'v1', valfk: 1 },
{ id: 2, val: 'v2', valfk: 2 },
{ id: 3, val: 'v3', valfk: 3 },
]);
await dataDuplicator({
systemConnection: conn,
driver,
items: [
{
name: 't1',
operation: 'copy',
openStream: gett1,
},
{
name: 't2',
operation: 'copy',
openStream: gett2,
},
],
});
await dataDuplicator({
systemConnection: conn,
driver,
items: [
{
name: 't1',
operation: 'copy',
openStream: gett1,
},
{
name: 't2',
operation: 'copy',
openStream: gett2,
},
],
});
const res1 = await driver.query(conn, `select count(*) as cnt from t1`);
expect(res1.rows[0].cnt.toString()).toEqual('6');
const res2 = await driver.query(conn, `select count(*) as cnt from t2`);
expect(res2.rows[0].cnt.toString()).toEqual('6');
})
);
});
+2 -2
View File
@@ -136,8 +136,8 @@ const filterLocal = [
'-MySQL',
'-MariaDB',
'-PostgreSQL',
'SQL Server',
'-SQLite',
'-SQL Server',
'SQLite',
'-CockroachDB',
];
+2
View File
@@ -13,6 +13,8 @@
"wait:ci": "cross-env DEVMODE=1 CITEST=1 node wait.js",
"test:local": "cross-env DEVMODE=1 LOCALTEST=1 jest",
"test:local:path": "cross-env DEVMODE=1 LOCALTEST=1 jest --runTestsByPath __tests__/data-duplicator.spec.js",
"test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults",
"run:local": "docker-compose down && docker-compose up -d && yarn wait:local && yarn test:local"
+3
View File
@@ -104,3 +104,6 @@ magick icon.png -resize 16x16! ../app/icons/16x16.png
magick icon.png -resize 192x192! ../packages/web/public/logo192.png
magick icon.png -resize 512x512! ../packages/web/public/logo512.png
magick icon.png -define icon:auto-resize="256,128,96,64,48,32,16" ../packages/web/public/favicon.ico
convert icon.png -resize 800x800 -background transparent -gravity center -extent 1000x1000 iconmac.png
magick composite iconmac.png macbg.png -resize 600x600! ../app/icon512-mac.png
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

+12 -7
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "5.1.6-beta.7",
"version": "5.3.1-beta.2",
"name": "dbgate-all",
"workspaces": [
"packages/*",
@@ -8,15 +8,18 @@
"integration-tests"
],
"scripts": {
"start:api": "yarn workspace dbgate-api start",
"start:app": "cd app && yarn start",
"start:api": "yarn workspace dbgate-api start | pino-pretty",
"start:api:json": "yarn workspace dbgate-api start",
"start:app": "cd app && yarn start | pino-pretty",
"start:app:singledb": "CONNECTIONS=con1 SERVER_con1=localhost ENGINE_con1=mysql@dbgate-plugin-mysql USER_con1=root PASSWORD_con1=Pwd2020Db SINGLE_CONNECTION=con1 SINGLE_DATABASE=Chinook yarn start:app",
"start:api:debug": "cross-env DEBUG=* yarn workspace dbgate-api start",
"start:app:debug": "cd app && cross-env DEBUG=* yarn start",
"start:api:debug:ssh": "cross-env DEBUG=ssh yarn workspace dbgate-api start",
"start:app:debug:ssh": "cd app && cross-env DEBUG=ssh yarn start",
"start:api:portal": "yarn workspace dbgate-api start:portal",
"start:api:singledb": "yarn workspace dbgate-api start:singledb",
"start:api:auth": "yarn workspace dbgate-api start:auth",
"start:api:portal": "yarn workspace dbgate-api start:portal | pino-pretty",
"start:api:singledb": "yarn workspace dbgate-api start:singledb | pino-pretty",
"start:api:auth": "yarn workspace dbgate-api start:auth | pino-pretty",
"start:api:dblogin": "yarn workspace dbgate-api start:dblogin | pino-pretty",
"start:web": "yarn workspace dbgate-web dev",
"start:sqltree": "yarn workspace dbgate-sqltree start",
"start:tools": "yarn workspace dbgate-tools start",
@@ -36,6 +39,7 @@
"build:app:local": "yarn plugins:copydist && cd app && yarn build:local",
"start:app:local": "cd app && yarn start:local",
"setCurrentVersion": "node setCurrentVersion",
"printSecrets": "node printSecrets",
"generatePadFile": "node generatePadFile",
"adjustPackageJson": "node adjustPackageJson",
"fillNativeModules": "node fillNativeModules",
@@ -56,7 +60,8 @@
},
"dependencies": {
"concurrently": "^5.1.0",
"patch-package": "^6.2.1"
"patch-package": "^6.2.1",
"pino-pretty": "^9.1.1"
},
"devDependencies": {
"copyfiles": "^2.2.0",
+14
View File
@@ -0,0 +1,14 @@
DEVMODE=1
CONNECTIONS=mysql
SINGLE_CONNECTION=mysql
# SINGLE_DATABASE=Chinook
LABEL_mysql=MySql localhost
SERVER_mysql=localhost
# USER_mysql=root
PORT_mysql=3306
# PASSWORD_mysql=Pwd2020Db
ENGINE_mysql=mysql@dbgate-plugin-mysql
# PASSWORD_MODE_mysql=askPassword
PASSWORD_MODE_mysql=askUser
+11 -3
View File
@@ -1,6 +1,6 @@
DEVMODE=1
CONNECTIONS=mysql,postgres,mongo,mongo2,mysqlssh,sqlite,relational
CONNECTIONS=mysql,postgres,postgres1,mongo,mongo2,mysqlssh,sqlite,relational
LABEL_mysql=MySql localhost
SERVER_mysql=localhost
@@ -12,10 +12,18 @@ ENGINE_mysql=mysql@dbgate-plugin-mysql
LABEL_postgres=Postgres localhost
SERVER_postgres=localhost
USER_postgres=postgres
PASSWORD_postgres=test
PORT_postgres=5433
PASSWORD_postgres=Pwd2020Db
PORT_postgres=5432
ENGINE_postgres=postgres@dbgate-plugin-postgres
LABEL_postgres1=Postgres localhost test DB
SERVER_postgres1=localhost
USER_postgres1=postgres
PASSWORD_postgres1=Pwd2020Db
PORT_postgres1=5432
ENGINE_postgres1=postgres@dbgate-plugin-postgres
DATABASE_postgres1=test
LABEL_mongo=Mongo URL
URL_mongo=mongodb://localhost:27017
ENGINE_mongo=mongo@dbgate-plugin-mongo
+2 -2
View File
@@ -5,8 +5,8 @@ CONNECTIONS=mysql
LABEL_mysql=MySql localhost
SERVER_mysql=localhost
USER_mysql=root
PASSWORD_mysql=test
PORT_mysql=3307
PASSWORD_mysql=Pwd2020Db
PORT_mysql=3306
ENGINE_mysql=mysql@dbgate-plugin-mysql
DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]
+13 -8
View File
@@ -26,7 +26,8 @@
"compare-versions": "^3.6.0",
"cors": "^2.8.5",
"cross-env": "^6.0.3",
"dbgate-query-splitter": "^4.9.2",
"dbgate-datalib": "^5.0.0-alpha.1",
"dbgate-query-splitter": "^4.10.1",
"dbgate-sqltree": "^5.0.0-alpha.1",
"dbgate-tools": "^5.0.0-alpha.1",
"debug": "^4.3.4",
@@ -36,6 +37,7 @@
"express": "^4.17.1",
"express-basic-auth": "^1.2.0",
"express-fileupload": "^1.2.0",
"external-sorting": "^1.3.1",
"fs-extra": "^9.1.0",
"fs-reverse": "^0.0.3",
"get-port": "^5.1.1",
@@ -46,20 +48,23 @@
"jsonwebtoken": "^8.5.1",
"line-reader": "^0.4.0",
"lodash": "^4.17.21",
"moment": "^2.24.0",
"ncp": "^2.0.0",
"node-cron": "^2.0.3",
"on-finished": "^2.4.1",
"pinomin": "^1.0.4",
"portfinder": "^1.0.28",
"rimraf": "^3.0.0",
"simple-encryptor": "^4.0.0",
"ssh2": "^1.11.0",
"tar": "^6.0.5",
"uuid": "^3.4.0"
"tar": "^6.0.5"
},
"scripts": {
"start": "env-cmd node src/index.js --listen-api",
"start": "env-cmd -f .env node src/index.js --listen-api",
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
"start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api",
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
"ts": "tsc",
@@ -73,11 +78,11 @@
"node-loader": "^1.0.2",
"nodemon": "^2.0.2",
"typescript": "^4.4.3",
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4"
},
"optionalDependencies": {
"better-sqlite3": "7.6.2",
"msnodesqlv8": "^2.6.0"
"better-sqlite3": "9.6.0",
"msnodesqlv8": "^4.2.1"
}
}
+6 -6
View File
@@ -58,7 +58,7 @@ module.exports = {
refreshFiles_meta: true,
async refreshFiles({ folder }) {
socket.emitChanged(`app-files-changed-${folder}`);
socket.emitChanged('app-files-changed', { app: folder });
},
refreshFolders_meta: true,
@@ -69,7 +69,7 @@ module.exports = {
deleteFile_meta: true,
async deleteFile({ folder, file, fileType }) {
await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`));
socket.emitChanged(`app-files-changed-${folder}`);
socket.emitChanged('app-files-changed', { app: folder });
this.emitChangedDbApp(folder);
},
@@ -79,7 +79,7 @@ module.exports = {
path.join(path.join(appdir(), folder), `${file}.${fileType}`),
path.join(path.join(appdir(), folder), `${newFile}.${fileType}`)
);
socket.emitChanged(`app-files-changed-${folder}`);
socket.emitChanged('app-files-changed', { app: folder });
this.emitChangedDbApp(folder);
},
@@ -95,7 +95,7 @@ module.exports = {
if (!folder) throw new Error('Missing folder parameter');
await fs.rmdir(path.join(appdir(), folder), { recursive: true });
socket.emitChanged(`app-folders-changed`);
socket.emitChanged(`app-files-changed-${folder}`);
socket.emitChanged('app-files-changed', { app: folder });
socket.emitChanged('used-apps-changed');
},
@@ -219,7 +219,7 @@ module.exports = {
await fs.writeFile(file, JSON.stringify(json, undefined, 2));
socket.emitChanged(`app-files-changed-${appFolder}`);
socket.emitChanged('app-files-changed', { app: appFolder });
socket.emitChanged('used-apps-changed');
},
@@ -271,7 +271,7 @@ module.exports = {
const file = path.join(appdir(), appFolder, fileName);
if (!(await fs.exists(file))) {
await fs.writeFile(file, JSON.stringify(content, undefined, 2));
socket.emitChanged(`app-files-changed-${appFolder}`);
socket.emitChanged('app-files-changed', { app: appFolder });
socket.emitChanged('used-apps-changed');
return true;
}
+86 -33
View File
@@ -1,10 +1,17 @@
const fs = require('fs-extra');
const readline = require('readline');
const crypto = require('crypto');
const path = require('path');
const { archivedir, clearArchiveLinksCache, resolveArchiveFolder } = require('../utility/directories');
const socket = require('../utility/socket');
const { saveFreeTableData } = require('../utility/freeTableStorage');
const loadFilesRecursive = require('../utility/loadFilesRecursive');
const getJslFileName = require('../utility/getJslFileName');
const { getLogger } = require('dbgate-tools');
const dbgateApi = require('../shell');
const jsldata = require('./jsldata');
const platformInfo = require('../utility/platformInfo');
const logger = getLogger('archive');
module.exports = {
folders_meta: true,
@@ -67,25 +74,28 @@ module.exports = {
...fileType('.matview.sql', 'matview.sql'),
];
} catch (err) {
console.log('Error reading archive files', err.message);
logger.error({ err }, 'Error reading archive files');
return [];
}
},
refreshFiles_meta: true,
async refreshFiles({ folder }) {
socket.emitChanged(`archive-files-changed-${folder}`);
socket.emitChanged('archive-files-changed', { folder });
return true;
},
refreshFolders_meta: true,
async refreshFolders() {
socket.emitChanged(`archive-folders-changed`);
return true;
},
deleteFile_meta: true,
async deleteFile({ folder, file, fileType }) {
await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`));
socket.emitChanged(`archive-files-changed-${folder}`);
socket.emitChanged(`archive-files-changed`, { folder });
return true;
},
renameFile_meta: true,
@@ -94,7 +104,47 @@ module.exports = {
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`)
);
socket.emitChanged(`archive-files-changed-${folder}`);
socket.emitChanged(`archive-files-changed`, { folder });
return true;
},
modifyFile_meta: true,
async modifyFile({ folder, file, changeSet, mergedRows, mergeKey, mergeMode }) {
await jsldata.closeDataStore(`archive://${folder}/${file}`);
const changedFilePath = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
if (!fs.existsSync(changedFilePath)) {
if (!mergedRows) {
return false;
}
const fileStream = fs.createWriteStream(changedFilePath);
for (const row of mergedRows) {
await fileStream.write(JSON.stringify(row) + '\n');
}
await fileStream.close();
socket.emitChanged(`archive-files-changed`, { folder });
return true;
}
const tmpchangedFilePath = path.join(resolveArchiveFolder(folder), `${file}-${crypto.randomUUID()}.jsonl`);
const reader = await dbgateApi.modifyJsonLinesReader({
fileName: changedFilePath,
changeSet,
mergedRows,
mergeKey,
mergeMode,
});
const writer = await dbgateApi.jsonLinesWriter({ fileName: tmpchangedFilePath });
await dbgateApi.copyStream(reader, writer);
if (platformInfo.isWindows) {
await fs.copyFile(tmpchangedFilePath, changedFilePath);
await fs.unlink(tmpchangedFilePath);
} else {
await fs.unlink(changedFilePath);
await fs.rename(tmpchangedFilePath, changedFilePath);
}
return true;
},
renameFolder_meta: true,
@@ -102,6 +152,7 @@ module.exports = {
const uniqueName = await this.getNewArchiveFolder({ database: newFolder });
await fs.rename(path.join(archivedir(), folder), path.join(archivedir(), uniqueName));
socket.emitChanged(`archive-folders-changed`);
return true;
},
deleteFolder_meta: true,
@@ -113,40 +164,42 @@ module.exports = {
await fs.rmdir(path.join(archivedir(), folder), { recursive: true });
}
socket.emitChanged(`archive-folders-changed`);
},
saveFreeTable_meta: true,
async saveFreeTable({ folder, file, data }) {
await saveFreeTableData(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), data);
socket.emitChanged(`archive-files-changed-${folder}`);
return true;
},
loadFreeTable_meta: true,
async loadFreeTable({ folder, file }) {
return new Promise((resolve, reject) => {
const fileStream = fs.createReadStream(path.join(resolveArchiveFolder(folder), `${file}.jsonl`));
const liner = readline.createInterface({
input: fileStream,
});
let structure = null;
const rows = [];
liner.on('line', line => {
const data = JSON.parse(line);
if (structure) rows.push(data);
else structure = data;
});
liner.on('close', () => {
resolve({ structure, rows });
fileStream.close();
});
});
},
saveText_meta: true,
async saveText({ folder, file, text }) {
await fs.writeFile(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), text);
socket.emitChanged(`archive-files-changed-${folder}`);
socket.emitChanged(`archive-files-changed`, { folder });
return true;
},
saveJslData_meta: true,
async saveJslData({ folder, file, jslid, changeSet }) {
const source = getJslFileName(jslid);
const target = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
if (changeSet) {
const reader = await dbgateApi.modifyJsonLinesReader({
fileName: source,
changeSet,
});
const writer = await dbgateApi.jsonLinesWriter({ fileName: target });
await dbgateApi.copyStream(reader, writer);
} else {
await fs.copyFile(source, target);
socket.emitChanged(`archive-files-changed`, { folder });
}
return true;
},
saveRows_meta: true,
async saveRows({ folder, file, rows }) {
const fileStream = fs.createWriteStream(path.join(resolveArchiveFolder(folder), `${file}.jsonl`));
for (const row of rows) {
await fileStream.write(JSON.stringify(row) + '\n');
}
await fileStream.close();
socket.emitChanged(`archive-files-changed`, { folder });
return true;
},
+35 -9
View File
@@ -1,11 +1,14 @@
const axios = require('axios');
const jwt = require('jsonwebtoken');
const getExpressPath = require('../utility/getExpressPath');
const uuidv1 = require('uuid/v1');
const { getLogins } = require('../utility/hasPermission');
const { getLogger } = require('dbgate-tools');
const AD = require('activedirectory2').promiseWrapper;
const crypto = require('crypto');
const tokenSecret = uuidv1();
const logger = getLogger('auth');
const tokenSecret = crypto.randomUUID();
function shouldAuthorizeApi() {
const logins = getLogins();
@@ -51,7 +54,7 @@ function authMiddleware(req, res, next) {
return next();
}
console.log('Sending invalid token error', err.message);
logger.error({ err }, 'Sending invalid token error');
return unauthorizedResponse(req, res, 'invalid token');
}
@@ -62,20 +65,24 @@ module.exports = {
async oauthToken(params) {
const { redirectUri, code } = params;
const scopeParam = process.env.OAUTH_SCOPE ? `&scope=${process.env.OAUTH_SCOPE}` : '';
const resp = await axios.default.post(
`${process.env.OAUTH_TOKEN}`,
`grant_type=authorization_code&code=${encodeURIComponent(code)}&redirect_uri=${encodeURIComponent(
redirectUri
)}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}`
)}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}${scopeParam}`
);
const { access_token, refresh_token } = resp.data;
const payload = jwt.decode(access_token);
console.log('User payload returned from OAUTH:', payload);
logger.info({ payload }, 'User payload returned from OAUTH');
const login = process.env.OAUTH_LOGIN_FIELD ? payload[process.env.OAUTH_LOGIN_FIELD] : 'oauth';
const login =
process.env.OAUTH_LOGIN_FIELD && payload && payload[process.env.OAUTH_LOGIN_FIELD]
? payload[process.env.OAUTH_LOGIN_FIELD]
: 'oauth';
if (
process.env.OAUTH_ALLOWED_LOGINS &&
@@ -83,6 +90,24 @@ module.exports = {
) {
return { error: `Username ${login} not allowed to log in` };
}
const groups =
process.env.OAUTH_GROUP_FIELD && payload && payload[process.env.OAUTH_GROUP_FIELD]
? payload[process.env.OAUTH_GROUP_FIELD]
: [];
const allowedGroups =
process.env.OAUTH_ALLOWED_GROUPS
? process.env.OAUTH_ALLOWED_GROUPS.split(',').map(group => group.toLowerCase().trim())
: [];
if (
process.env.OAUTH_ALLOWED_GROUPS &&
!groups.some(group => allowedGroups.includes(group.toLowerCase().trim()))
) {
return { error: `Username ${login} does not belong to an allowed group` };
}
if (access_token) {
return {
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
@@ -113,12 +138,12 @@ module.exports = {
!process.env.AD_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
) {
return { error: `Username ${login} not allowed to log in` };
}
}
return {
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
};
} catch (err) {
console.log('Failed active directory authentization', err.message);
logger.error({ err }, 'Failed active directory authentization');
return {
error: err.message,
};
@@ -129,7 +154,8 @@ module.exports = {
if (!logins) {
return { error: 'Logins not configured' };
}
if (logins.find(x => x.login == login)?.password == password) {
const foundLogin = logins.find(x => x.login == login);
if (foundLogin && foundLogin.password && foundLogin.password == password) {
return {
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
};
+10 -3
View File
@@ -2,7 +2,7 @@ const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const axios = require('axios');
const { datadir } = require('../utility/directories');
const { datadir, getLogsFilePath } = require('../utility/directories');
const { hasPermission, getLogins } = require('../utility/hasPermission');
const socket = require('../utility/socket');
const _ = require('lodash');
@@ -28,12 +28,15 @@ module.exports = {
get_meta: true,
async get(_params, req) {
const logins = getLogins();
const login = req.user ? req.user.login : logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null;
const loginName =
req && req.user && req.user.login ? req.user.login : req && req.auth && req.auth.user ? req.auth.user : null;
const login = logins && loginName ? logins.find(x => x.login == loginName) : null;
const permissions = login ? login.permissions : process.env.PERMISSIONS;
return {
runAsPortal: !!connections.portalConnections,
singleDatabase: connections.singleDatabase,
singleDbConnection: connections.singleDbConnection,
singleConnection: connections.singleConnection,
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
allowShellConnection: platformInfo.allowShellConnection,
allowShellScripting: platformInfo.allowShellScripting,
@@ -41,8 +44,12 @@ module.exports = {
permissions,
login,
oauth: process.env.OAUTH_AUTH,
oauthClient: process.env.OAUTH_CLIENT_ID,
oauthScope: process.env.OAUTH_SCOPE,
oauthLogout: process.env.OAUTH_LOGOUT,
isLoginForm: !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH),
logsFilePath: getLogsFilePath(),
connectionsFilePath: path.join(datadir(), 'connections.jsonl'),
...currentVersion,
};
},
+94 -16
View File
@@ -2,6 +2,7 @@ const path = require('path');
const { fork } = require('child_process');
const _ = require('lodash');
const fs = require('fs-extra');
const crypto = require('crypto');
const { datadir, filesdir } = require('../utility/directories');
const socket = require('../utility/socket');
@@ -11,9 +12,14 @@ const { pickSafeConnectionInfo } = require('../utility/crypting');
const JsonLinesDatabase = require('../utility/JsonLinesDatabase');
const processArgs = require('../utility/processArgs');
const { safeJsonParse } = require('dbgate-tools');
const { safeJsonParse, getLogger } = require('dbgate-tools');
const platformInfo = require('../utility/platformInfo');
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
const pipeForkLogs = require('../utility/pipeForkLogs');
const logger = getLogger('connections');
let volatileConnections = {};
function getNamedArgs() {
const res = {};
@@ -49,11 +55,13 @@ function getPortalCollections() {
server: process.env[`SERVER_${id}`],
user: process.env[`USER_${id}`],
password: process.env[`PASSWORD_${id}`],
passwordMode: process.env[`PASSWORD_MODE_${id}`],
port: process.env[`PORT_${id}`],
databaseUrl: process.env[`URL_${id}`],
useDatabaseUrl: !!process.env[`URL_${id}`],
databaseFile: process.env[`FILE_${id}`],
socketPath: process.env[`SOCKET_PATH_${id}`],
serviceName: process.env[`SERVICE_NAME_${id}`],
authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined),
defaultDatabase:
process.env[`DATABASE_${id}`] ||
@@ -81,14 +89,15 @@ function getPortalCollections() {
sslCertFilePassword: process.env[`SSL_CERT_FILE_PASSWORD_${id}`],
sslKeyFile: process.env[`SSL_KEY_FILE_${id}`],
sslRejectUnauthorized: process.env[`SSL_REJECT_UNAUTHORIZED_${id}`],
trustServerCertificate: process.env[`SSL_TRUST_CERTIFICATE_${id}`],
}));
console.log('Using connections from ENV variables:');
console.log(JSON.stringify(connections.map(pickSafeConnectionInfo), undefined, 2));
logger.info({ connections: connections.map(pickSafeConnectionInfo) }, 'Using connections from ENV variables');
const noengine = connections.filter(x => !x.engine);
if (noengine.length > 0) {
console.log(
'Warning: Invalid CONNECTIONS configutation, missing ENGINE for connection ID:',
noengine.map(x => x._id)
logger.warn(
{ connections: noengine.map(x => x._id) },
'Invalid CONNECTIONS configutation, missing ENGINE for connection ID'
);
}
return connections;
@@ -126,9 +135,10 @@ function getPortalCollections() {
return null;
}
const portalConnections = getPortalCollections();
function getSingleDatabase() {
function getSingleDbConnection() {
if (process.env.SINGLE_CONNECTION && process.env.SINGLE_DATABASE) {
// @ts-ignore
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
@@ -152,12 +162,31 @@ function getSingleDatabase() {
return null;
}
const singleDatabase = getSingleDatabase();
function getSingleConnection() {
if (getSingleDbConnection()) return null;
if (process.env.SINGLE_CONNECTION) {
// @ts-ignore
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
if (connection) {
return connection;
}
}
// @ts-ignore
const arg0 = (portalConnections || []).find(x => x._id == 'argv');
if (arg0) {
return arg0;
}
return null;
}
const singleDbConnection = getSingleDbConnection();
const singleConnection = getSingleConnection();
module.exports = {
datastore: null,
opened: [],
singleDatabase,
singleDbConnection,
singleConnection,
portalConnections,
async _init() {
@@ -179,13 +208,20 @@ module.exports = {
test_meta: true,
test(connection) {
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
'--is-forked-api',
'--start-process',
'connectProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
]);
const subprocess = fork(
global['API_PACKAGE'] || process.argv[1],
[
'--is-forked-api',
'--start-process',
'connectProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
],
{
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
}
);
pipeForkLogs(subprocess);
subprocess.send(connection);
return new Promise(resolve => {
subprocess.on('message', resp => {
@@ -199,6 +235,36 @@ module.exports = {
});
},
saveVolatile_meta: true,
async saveVolatile({ conid, user, password, test }) {
const old = await this.getCore({ conid });
const res = {
...old,
_id: crypto.randomUUID(),
password,
passwordMode: undefined,
unsaved: true,
};
if (old.passwordMode == 'askUser') {
res.user = user;
}
if (test) {
const testRes = await this.test(res);
if (testRes.msgtype == 'connected') {
volatileConnections[res._id] = res;
return {
...res,
msgtype: 'connected',
};
}
return testRes;
} else {
volatileConnections[res._id] = res;
return res;
}
},
save_meta: true,
async save(connection) {
if (portalConnections) return;
@@ -229,6 +295,14 @@ module.exports = {
return res;
},
batchChangeFolder_meta: true,
async batchChangeFolder({ folder, newFolder }, req) {
// const updated = await this.datastore.find(x => x.parent == folder);
const res = await this.datastore.updateAll(x => (x.parent == folder ? { ...x, parent: newFolder } : x));
socket.emitChanged('connection-list-changed');
return res;
},
updateDatabase_meta: true,
async updateDatabase({ conid, database, values }, req) {
if (portalConnections) return;
@@ -258,6 +332,10 @@ module.exports = {
async getCore({ conid, mask = false }) {
if (!conid) return null;
const volatile = volatileConnections[conid];
if (volatile) {
return volatile;
}
if (portalConnections) {
const res = portalConnections.find(x => x._id == conid) || null;
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
@@ -1,4 +1,3 @@
const uuidv1 = require('uuid/v1');
const connections = require('./connections');
const archive = require('./archive');
const socket = require('../utility/socket');
@@ -12,6 +11,7 @@ const {
matchPairedObjects,
extendDatabaseInfo,
modelCompareDbDiffOptions,
getLogger,
} = require('dbgate-tools');
const { html, parse } = require('diff2html');
const { handleProcessCommunication } = require('../utility/processComm');
@@ -27,6 +27,11 @@ const { createTwoFilesPatch } = require('diff');
const diff2htmlPage = require('../utility/diff2htmlPage');
const processArgs = require('../utility/processArgs');
const { testConnectionPermission } = require('../utility/hasPermission');
const { MissingCredentialsError } = require('../utility/exceptions');
const pipeForkLogs = require('../utility/pipeForkLogs');
const crypto = require('crypto');
const logger = getLogger('databaseConnections');
module.exports = {
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
@@ -42,24 +47,24 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (!existing) return;
existing.structure = structure;
socket.emitChanged(`database-structure-changed-${conid}-${database}`);
socket.emitChanged('database-structure-changed', { conid, database });
},
handle_structureTime(conid, database, { analysedTime }) {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (!existing) return;
existing.analysedTime = analysedTime;
socket.emitChanged(`database-status-changed-${conid}-${database}`);
socket.emitChanged(`database-status-changed`, { conid, database });
},
handle_version(conid, database, { version }) {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (!existing) return;
existing.serverVersion = version;
socket.emitChanged(`database-server-version-changed-${conid}-${database}`);
socket.emitChanged(`database-server-version-changed`, { conid, database });
},
handle_error(conid, database, props) {
const { error } = props;
console.log(`Error in database connection ${conid}, database ${database}: ${error}`);
logger.error(`Error in database connection ${conid}, database ${database}: ${error}`);
},
handle_response(conid, database, { msgid, ...response }) {
const [resolve, reject] = this.requests[msgid];
@@ -72,7 +77,7 @@ module.exports = {
if (!existing) return;
if (existing.status && status && existing.status.counter > status.counter) return;
existing.status = status;
socket.emitChanged(`database-status-changed-${conid}-${database}`);
socket.emitChanged(`database-status-changed`, { conid, database });
},
handle_ping() {},
@@ -81,13 +86,23 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) return existing;
const connection = await connections.getCore({ conid });
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
'--is-forked-api',
'--start-process',
'databaseConnectionProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
]);
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
}
const subprocess = fork(
global['API_PACKAGE'] || process.argv[1],
[
'--is-forked-api',
'--start-process',
'databaseConnectionProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
],
{
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
}
);
pipeForkLogs(subprocess);
const lastClosed = this.closed[`${conid}/${database}`];
const newOpened = {
conid,
@@ -122,10 +137,15 @@ module.exports = {
/** @param {import('dbgate-types').OpenedDatabaseConnection} conn */
sendRequest(conn, message) {
const msgid = uuidv1();
const msgid = crypto.randomUUID();
const promise = new Promise((resolve, reject) => {
this.requests[msgid] = [resolve, reject];
conn.subprocess.send({ msgid, ...message });
try {
conn.subprocess.send({ msgid, ...message });
} catch (err) {
logger.error({ err }, 'Error sending request do process');
this.close(conn.conid, conn.database);
}
});
return promise;
},
@@ -133,7 +153,7 @@ module.exports = {
queryData_meta: true,
async queryData({ conid, database, sql }, req) {
testConnectionPermission(conid, req);
console.log(`Processing query, conid=${conid}, database=${database}, sql=${sql}`);
logger.info({ conid, database, sql }, 'Processing query');
const opened = await this.ensureOpened(conid, database);
// if (opened && opened.status && opened.status.name == 'error') {
// return opened.status;
@@ -151,11 +171,11 @@ module.exports = {
},
runScript_meta: true,
async runScript({ conid, database, sql }, req) {
async runScript({ conid, database, sql, useTransaction }, req) {
testConnectionPermission(conid, req);
console.log(`Processing script, conid=${conid}, database=${database}, sql=${sql}`);
logger.info({ conid, database, sql }, 'Processing script');
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'runScript', sql });
const res = await this.sendRequest(opened, { msgtype: 'runScript', sql, useTransaction });
return res;
},
@@ -272,8 +292,19 @@ module.exports = {
let existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) {
existing.subprocess.send({ msgtype: 'ping' });
try {
existing.subprocess.send({ msgtype: 'ping' });
} catch (err) {
logger.error({ err }, 'Error pinging DB connection');
this.close(conid, database);
return {
status: 'error',
message: 'Ping failed',
};
}
} else {
// @ts-ignore
existing = await this.ensureOpened(conid, database);
}
@@ -304,7 +335,13 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) {
existing.disconnected = true;
if (kill) existing.subprocess.kill();
if (kill) {
try {
existing.subprocess.kill();
} catch (err) {
logger.error({ err }, 'Error killing subprocess');
}
}
this.opened = this.opened.filter(x => x.conid != conid || x.database != database);
this.closed[`${conid}/${database}`] = {
status: {
@@ -313,7 +350,7 @@ module.exports = {
},
structure: existing.structure,
};
socket.emitChanged(`database-status-changed-${conid}-${database}`);
socket.emitChanged(`database-status-changed`, { conid, database });
}
},
+9 -9
View File
@@ -1,6 +1,6 @@
const uuidv1 = require('uuid/v1');
const fs = require('fs-extra');
const path = require('path');
const crypto = require('crypto');
const { filesdir, archivedir, resolveArchiveFolder, uploadsdir, appdir } = require('../utility/directories');
const getChartExport = require('../utility/getChartExport');
const { hasPermission } = require('../utility/hasPermission');
@@ -49,7 +49,7 @@ module.exports = {
async delete({ folder, file }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false;
await fs.unlink(path.join(filesdir(), folder, file));
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`);
return true;
},
@@ -58,7 +58,7 @@ module.exports = {
async rename({ folder, file, newFile }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false;
await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`);
return true;
},
@@ -66,7 +66,7 @@ module.exports = {
refresh_meta: true,
async refresh({ folders }, req) {
for (const folder of folders) {
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`);
}
return true;
@@ -76,7 +76,7 @@ module.exports = {
async copy({ folder, file, newFile }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false;
await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`);
return true;
},
@@ -112,13 +112,13 @@ module.exports = {
if (!hasPermission(`archive/write`, req)) return false;
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
await fs.writeFile(path.join(dir, file), serialize(format, data));
socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`);
socket.emitChanged(`archive-files-changed`, { folder: folder.substring('archive:'.length) });
return true;
} else if (folder.startsWith('app:')) {
if (!hasPermission(`apps/write`, req)) return false;
const app = folder.substring('app:'.length);
await fs.writeFile(path.join(appdir(), app, file), serialize(format, data));
socket.emitChanged(`app-files-changed-${app}`);
socket.emitChanged(`app-files-changed`, { app });
socket.emitChanged('used-apps-changed');
apps.emitChangedDbApp(folder);
return true;
@@ -129,7 +129,7 @@ module.exports = {
await fs.mkdir(dir);
}
await fs.writeFile(path.join(dir, file), serialize(format, data));
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`);
if (folder == 'shell') {
scheduler.reload();
@@ -164,7 +164,7 @@ module.exports = {
generateUploadsFile_meta: true,
async generateUploadsFile({ extension }) {
const fileName = `${uuidv1()}.${extension || 'html'}`;
const fileName = `${crypto.randomUUID()}.${extension || 'html'}`;
return {
fileName,
filePath: path.join(uploadsdir(), fileName),
+117 -15
View File
@@ -4,9 +4,9 @@ const lineReader = require('line-reader');
const _ = require('lodash');
const { __ } = require('lodash/fp');
const DatastoreProxy = require('../utility/DatastoreProxy');
const { saveFreeTableData } = require('../utility/freeTableStorage');
const getJslFileName = require('../utility/getJslFileName');
const JsonLinesDatastore = require('../utility/JsonLinesDatastore');
const requirePluginFunction = require('../utility/requirePluginFunction');
const socket = require('../utility/socket');
function readFirstLine(file) {
@@ -99,16 +99,27 @@ module.exports = {
// return readerInfo;
// },
async ensureDatastore(jslid) {
async ensureDatastore(jslid, formatterFunction) {
let datastore = this.datastores[jslid];
if (!datastore) {
datastore = new JsonLinesDatastore(getJslFileName(jslid));
if (!datastore || datastore.formatterFunction != formatterFunction) {
if (datastore) {
datastore._closeReader();
}
datastore = new JsonLinesDatastore(getJslFileName(jslid), formatterFunction);
// datastore = new DatastoreProxy(getJslFileName(jslid));
this.datastores[jslid] = datastore;
}
return datastore;
},
async closeDataStore(jslid) {
const datastore = this.datastores[jslid];
if (datastore) {
await datastore._closeReader();
delete this.datastores[jslid];
}
},
getInfo_meta: true,
async getInfo({ jslid }) {
const file = getJslFileName(jslid);
@@ -131,9 +142,15 @@ module.exports = {
},
getRows_meta: true,
async getRows({ jslid, offset, limit, filters }) {
const datastore = await this.ensureDatastore(jslid);
return datastore.getRows(offset, limit, _.isEmpty(filters) ? null : filters);
async getRows({ jslid, offset, limit, filters, sort, formatterFunction }) {
const datastore = await this.ensureDatastore(jslid, formatterFunction);
return datastore.getRows(offset, limit, _.isEmpty(filters) ? null : filters, _.isEmpty(sort) ? null : sort);
},
exists_meta: true,
async exists({ jslid }) {
const fileName = getJslFileName(jslid);
return fs.existsSync(fileName);
},
getStats_meta: true,
@@ -150,8 +167,8 @@ module.exports = {
},
loadFieldValues_meta: true,
async loadFieldValues({ jslid, field, search }) {
const datastore = await this.ensureDatastore(jslid);
async loadFieldValues({ jslid, field, search, formatterFunction }) {
const datastore = await this.ensureDatastore(jslid, formatterFunction);
const res = new Set();
await datastore.enumRows(row => {
if (!filterName(search, row[field])) return true;
@@ -177,15 +194,100 @@ module.exports = {
// }
},
saveFreeTable_meta: true,
async saveFreeTable({ jslid, data }) {
saveFreeTableData(getJslFileName(jslid), data);
return true;
},
saveText_meta: true,
async saveText({ jslid, text }) {
await fs.promises.writeFile(getJslFileName(jslid), text);
return true;
},
saveRows_meta: true,
async saveRows({ jslid, rows }) {
const fileStream = fs.createWriteStream(getJslFileName(jslid));
for (const row of rows) {
await fileStream.write(JSON.stringify(row) + '\n');
}
await fileStream.close();
return true;
},
extractTimelineChart_meta: true,
async extractTimelineChart({ jslid, timestampFunction, aggregateFunction, measures }) {
const timestamp = requirePluginFunction(timestampFunction);
const aggregate = requirePluginFunction(aggregateFunction);
const datastore = new JsonLinesDatastore(getJslFileName(jslid));
let mints = null;
let maxts = null;
// pass 1 - counts stats, time range
await datastore.enumRows(row => {
const ts = timestamp(row);
if (!mints || ts < mints) mints = ts;
if (!maxts || ts > maxts) maxts = ts;
return true;
});
const minTime = new Date(mints).getTime();
const maxTime = new Date(maxts).getTime();
const duration = maxTime - minTime;
const STEPS = 100;
let stepCount = duration > 100 * 1000 ? STEPS : Math.round((maxTime - minTime) / 1000);
if (stepCount < 2) {
stepCount = 2;
}
const stepDuration = duration / stepCount;
const labels = _.range(stepCount).map(i => new Date(minTime + stepDuration / 2 + stepDuration * i));
// const datasets = measures.map(m => ({
// label: m.label,
// data: Array(stepCount).fill(0),
// }));
const mproc = measures.map(m => ({
...m,
}));
const data = Array(stepCount)
.fill(0)
.map(() => ({}));
// pass 2 - count measures
await datastore.enumRows(row => {
const ts = timestamp(row);
let part = Math.round((new Date(ts).getTime() - minTime) / stepDuration);
if (part < 0) part = 0;
if (part >= stepCount) part - stepCount - 1;
if (data[part]) {
data[part] = aggregate(data[part], row, stepDuration);
}
return true;
});
datastore._closeReader();
// const measureByField = _.fromPairs(measures.map((m, i) => [m.field, i]));
// for (let mindex = 0; mindex < measures.length; mindex++) {
// for (let stepIndex = 0; stepIndex < stepCount; stepIndex++) {
// const measure = measures[mindex];
// if (measure.perSecond) {
// datasets[mindex].data[stepIndex] /= stepDuration / 1000;
// }
// if (measure.perField) {
// datasets[mindex].data[stepIndex] /= datasets[measureByField[measure.perField]].data[stepIndex];
// }
// }
// }
// for (let i = 0; i < measures.length; i++) {
// if (measures[i].hidden) {
// datasets[i] = null;
// }
// }
return {
labels,
datasets: mproc.map(m => ({
label: m.label,
data: data.map(d => d[m.field] || 0),
})),
};
},
};
+35 -21
View File
@@ -1,15 +1,22 @@
const crypto = require('crypto');
const _ = require('lodash');
const path = require('path');
const fs = require('fs-extra');
const uuidv1 = require('uuid/v1');
const byline = require('byline');
const socket = require('../utility/socket');
const { fork } = require('child_process');
const { rundir, uploadsdir, pluginsdir, getPluginBackendPath, packagedPluginList } = require('../utility/directories');
const { extractShellApiPlugins, extractShellApiFunctionName, jsonScriptToJavascript } = require('dbgate-tools');
const {
extractShellApiPlugins,
extractShellApiFunctionName,
jsonScriptToJavascript,
getLogger,
safeJsonParse,
} = require('dbgate-tools');
const { handleProcessCommunication } = require('../utility/processComm');
const processArgs = require('../utility/processArgs');
const platformInfo = require('../utility/platformInfo');
const logger = getLogger('runners');
function extractPlugins(script) {
const requireRegex = /\s*\/\/\s*@require\s+([^\s]+)\s*\n/g;
@@ -29,13 +36,14 @@ const requirePluginsTemplate = (plugins, isExport) =>
const scriptTemplate = (script, isExport) => `
const dbgateApi = require(${isExport ? `'dbgate-api'` : 'process.env.DBGATE_API'});
const logger = dbgateApi.getLogger('script');
dbgateApi.initializeApiEnvironment();
${requirePluginsTemplate(extractPlugins(script), isExport)}
require=null;
async function run() {
${script}
await dbgateApi.finalizer.run();
console.log('Finished job script');
logger.info('Finished job script');
}
dbgateApi.runScript(run);
`;
@@ -59,20 +67,23 @@ module.exports = {
requests: {},
dispatchMessage(runid, message) {
if (message) console.log('...', message.message);
if (_.isString(message)) {
socket.emit(`runner-info-${runid}`, {
message,
if (message) {
const json = safeJsonParse(message.message);
if (json) logger.log(json);
else logger.info(message.message);
const toEmit = {
time: new Date(),
severity: 'info',
});
}
if (_.isPlainObject(message)) {
socket.emit(`runner-info-${runid}`, {
time: new Date(),
severity: 'info',
...message,
});
message: json ? json.msg : message.message,
};
if (json && json.level >= 50) {
toEmit.severity = 'error';
}
socket.emit(`runner-info-${runid}`, toEmit);
}
},
@@ -98,13 +109,15 @@ module.exports = {
fs.writeFileSync(`${scriptFile}`, scriptText);
fs.mkdirSync(directory);
const pluginNames = _.union(fs.readdirSync(pluginsdir()), packagedPluginList);
console.log(`RUNNING SCRIPT ${scriptFile}`);
logger.info({ scriptFile }, 'Running script');
// const subprocess = fork(scriptFile, ['--checkParent', '--max-old-space-size=8192'], {
const subprocess = fork(
scriptFile,
[
'--checkParent', // ...process.argv.slice(3)
'--is-forked-api',
'--process-display-name',
'script',
...processArgs.getPassArgs(),
],
{
@@ -117,14 +130,15 @@ module.exports = {
},
}
);
const pipeDispatcher = severity => data =>
this.dispatchMessage(runid, { severity, message: data.toString().trim() });
const pipeDispatcher = severity => data => {
return this.dispatchMessage(runid, { severity, message: data.toString().trim() });
};
byline(subprocess.stdout).on('data', pipeDispatcher('info'));
byline(subprocess.stderr).on('data', pipeDispatcher('error'));
subprocess.on('exit', code => {
this.rejectRequest(runid, { message: 'No data retured, maybe input data source is too big' });
console.log('... EXIT process', code);
logger.info({ code, pid: subprocess.pid }, 'Exited process');
socket.emit(`runner-done-${runid}`, code);
});
subprocess.on('error', error => {
@@ -151,7 +165,7 @@ module.exports = {
start_meta: true,
async start({ script }) {
const runid = uuidv1();
const runid = crypto.randomUUID()
if (script.type == 'json') {
const js = jsonScriptToJavascript(script);
@@ -199,7 +213,7 @@ module.exports = {
loadReader_meta: true,
async loadReader({ functionName, props }) {
const promise = new Promise((resolve, reject) => {
const runid = uuidv1();
const runid = crypto.randomUUID();
this.requests[runid] = [resolve, reject];
this.startCore(runid, loaderScriptTemplate(functionName, props, runid));
});
+4 -1
View File
@@ -4,6 +4,9 @@ const path = require('path');
const cron = require('node-cron');
const runners = require('./runners');
const { hasPermission } = require('../utility/hasPermission');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('scheduler');
const scheduleRegex = /\s*\/\/\s*@schedule\s+([^\n]+)\n/;
@@ -21,7 +24,7 @@ module.exports = {
if (!match) return;
const pattern = match[1];
if (!cron.validate(pattern)) return;
console.log(`Schedule script ${file} with pattern ${pattern}`);
logger.info(`Schedule script ${file} with pattern ${pattern}`);
const task = cron.schedule(pattern, () => runners.start({ script: text }));
this.tasks.push(task);
},
+108 -13
View File
@@ -8,23 +8,29 @@ const lock = new AsyncLock();
const config = require('./config');
const processArgs = require('../utility/processArgs');
const { testConnectionPermission } = require('../utility/hasPermission');
const { MissingCredentialsError } = require('../utility/exceptions');
const pipeForkLogs = require('../utility/pipeForkLogs');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('serverConnection');
module.exports = {
opened: [],
closed: {},
lastPinged: {},
requests: {},
handle_databases(conid, { databases }) {
const existing = this.opened.find(x => x.conid == conid);
if (!existing) return;
existing.databases = databases;
socket.emitChanged(`database-list-changed-${conid}`);
socket.emitChanged(`database-list-changed`, { conid });
},
handle_version(conid, { version }) {
const existing = this.opened.find(x => x.conid == conid);
if (!existing) return;
existing.version = version;
socket.emitChanged(`server-version-changed-${conid}`);
socket.emitChanged(`server-version-changed`, { conid });
},
handle_status(conid, { status }) {
const existing = this.opened.find(x => x.conid == conid);
@@ -33,19 +39,37 @@ 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 () => {
const existing = this.opened.find(x => x.conid == conid);
if (existing) return existing;
const connection = await connections.getCore({ conid });
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
'--is-forked-api',
'--start-process',
'serverConnectionProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
]);
if (!connection) {
throw new Error(`Connection with conid="${conid}" not fund`);
}
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
}
const subprocess = fork(
global['API_PACKAGE'] || process.argv[1],
[
'--is-forked-api',
'--start-process',
'serverConnectionProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
],
{
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
}
);
pipeForkLogs(subprocess);
const newOpened = {
conid,
subprocess,
@@ -80,7 +104,13 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid);
if (existing) {
existing.disconnected = true;
if (kill) existing.subprocess.kill();
if (kill) {
try {
existing.subprocess.kill();
} catch (err) {
logger.error({ err }, 'Error killing subprocess');
}
}
this.opened = this.opened.filter(x => x.conid != conid);
this.closed[conid] = {
...existing.status,
@@ -99,6 +129,7 @@ module.exports = {
listDatabases_meta: true,
async listDatabases({ conid }, req) {
if (!conid) return [];
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
return opened.databases;
@@ -120,18 +151,24 @@ module.exports = {
},
ping_meta: true,
async ping({ connections }) {
async ping({ conidArray, strmid }) {
await Promise.all(
_.uniq(connections).map(async conid => {
_.uniq(conidArray).map(async conid => {
const last = this.lastPinged[conid];
if (last && new Date().getTime() - last < 30 * 1000) {
return Promise.resolve();
}
this.lastPinged[conid] = new Date().getTime();
const opened = await this.ensureOpened(conid);
opened.subprocess.send({ msgtype: 'ping' });
try {
opened.subprocess.send({ msgtype: 'ping' });
} catch (err) {
logger.error({ err }, 'Error pinging server connection');
this.close(conid);
}
})
);
socket.setStreamIdFilter(strmid, { conid: conidArray });
return { status: 'ok' };
},
@@ -161,4 +198,62 @@ module.exports = {
opened.subprocess.send({ msgtype: 'dropDatabase', name });
return { status: 'ok' };
},
sendRequest(conn, message) {
const msgid = crypto.randomUUID();
const promise = new Promise((resolve, reject) => {
this.requests[msgid] = [resolve, reject];
try {
conn.subprocess.send({ msgid, ...message });
} catch (err) {
logger.error({ err }, 'Error sending request');
this.close(conn.conid);
}
});
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 });
},
summaryCommand_meta: true,
async summaryCommand({ conid, command, row }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
if (opened.connection.isReadOnly) return false;
return this.loadDataCore('summaryCommand', { conid, command, row });
},
getOpenedConnectionReport() {
return this.opened.map(con => ({
status: con.status,
versionText: con.version?.versionText,
databaseCount: con.databases.length,
connection: _.pick(con.connection, [
'engine',
'useSshTunnel',
'authType',
'trustServerCertificate',
'useSsl',
'sshMode',
]),
}));
},
};
+63 -12
View File
@@ -1,5 +1,5 @@
const crypto = require('crypto');
const _ = require('lodash');
const uuidv1 = require('uuid/v1');
const connections = require('./connections');
const socket = require('../utility/socket');
const { fork } = require('child_process');
@@ -8,6 +8,11 @@ const path = require('path');
const { handleProcessCommunication } = require('../utility/processComm');
const processArgs = require('../utility/processArgs');
const { appdir } = require('../utility/directories');
const { getLogger } = require('dbgate-tools');
const pipeForkLogs = require('../utility/pipeForkLogs');
const config = require('./config');
const logger = getLogger('sessions');
module.exports = {
/** @type {import('dbgate-types').OpenedSession[]} */
@@ -80,15 +85,22 @@ module.exports = {
create_meta: true,
async create({ conid, database }) {
const sesid = uuidv1();
const sesid = crypto.randomUUID();
const connection = await connections.getCore({ conid });
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
'--is-forked-api',
'--start-process',
'sessionProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
]);
const subprocess = fork(
global['API_PACKAGE'] || process.argv[1],
[
'--is-forked-api',
'--start-process',
'sessionProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
],
{
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
}
);
pipeForkLogs(subprocess);
const newOpened = {
conid,
database,
@@ -109,7 +121,12 @@ module.exports = {
socket.emit(`session-closed-${sesid}`);
});
subprocess.send({ msgtype: 'connect', ...connection, database });
subprocess.send({
msgtype: 'connect',
...connection,
database,
globalSettings: await config.getSettings(),
});
return _.pick(newOpened, ['conid', 'database', 'sesid']);
},
@@ -120,7 +137,7 @@ module.exports = {
throw new Error('Invalid session');
}
console.log(`Processing query, sesid=${sesid}, sql=${sql}`);
logger.info({ sesid, sql }, 'Processing query');
this.dispatchMessage(sesid, 'Query execution started');
session.subprocess.send({ msgtype: 'executeQuery', sql });
@@ -150,6 +167,31 @@ module.exports = {
return true;
},
startProfiler_meta: true,
async startProfiler({ sesid }) {
const jslid = uuidv1();
const session = this.opened.find(x => x.sesid == sesid);
if (!session) {
throw new Error('Invalid session');
}
logger.info({ sesid }, 'Starting profiler');
session.loadingReader_jslid = jslid;
session.subprocess.send({ msgtype: 'startProfiler', jslid });
return { state: 'ok', jslid };
},
stopProfiler_meta: true,
async stopProfiler({ sesid }) {
const session = this.opened.find(x => x.sesid == sesid);
if (!session) {
throw new Error('Invalid session');
}
session.subprocess.send({ msgtype: 'stopProfiler' });
return { state: 'ok' };
},
// cancel_meta: true,
// async cancel({ sesid }) {
// const session = this.opened.find((x) => x.sesid == sesid);
@@ -177,7 +219,16 @@ module.exports = {
if (!session) {
throw new Error('Invalid session');
}
session.subprocess.send({ msgtype: 'ping' });
try {
session.subprocess.send({ msgtype: 'ping' });
} catch (err) {
logger.error({ err }, 'Error pinging session');
return {
status: 'error',
message: 'Ping failed',
};
}
return { state: 'ok' };
},
+101 -4
View File
@@ -1,6 +1,18 @@
const crypto = require('crypto');
const path = require('path');
const { uploadsdir } = require('../utility/directories');
const uuidv1 = require('uuid/v1');
const { uploadsdir, getLogsFilePath } = require('../utility/directories');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('uploads');
const axios = require('axios');
const os = require('os');
const fs = require('fs/promises');
const { read } = require('./queryHistory');
const platformInfo = require('../utility/platformInfo');
const _ = require('lodash');
const serverConnections = require('./serverConnections');
const config = require('./config');
const gistSecret = require('../gistSecret');
const currentVersion = require('../currentVersion');
module.exports = {
upload_meta: {
@@ -13,9 +25,9 @@ module.exports = {
res.json(null);
return;
}
const uploadName = uuidv1();
const uploadName = crypto.randomUUID();
const filePath = path.join(uploadsdir(), uploadName);
console.log(`Uploading file ${data.name}, size=${data.size}`);
logger.info(`Uploading file ${data.name}, size=${data.size}`);
data.mv(filePath, () => {
res.json({
@@ -33,4 +45,89 @@ module.exports = {
get(req, res) {
res.sendFile(path.join(uploadsdir(), req.query.file));
},
async getGistToken() {
const settings = await config.getSettings();
return settings['other.gistCreateToken'] || gistSecret;
},
uploadErrorToGist_meta: true,
async uploadErrorToGist() {
const logs = await fs.readFile(getLogsFilePath(), { encoding: 'utf-8' });
const connections = await serverConnections.getOpenedConnectionReport();
try {
const response = await axios.default.post(
'https://api.github.com/gists',
{
description: `DbGate ${currentVersion.version} error report`,
public: false,
files: {
'logs.jsonl': {
content: logs,
},
'os.json': {
content: JSON.stringify(
{
release: os.release(),
arch: os.arch(),
machine: os.machine(),
platform: os.platform(),
type: os.type(),
},
null,
2
),
},
'platform.json': {
content: JSON.stringify(
_.omit(
{
...platformInfo,
},
['defaultKeyfile', 'sshAuthSock']
),
null,
2
),
},
'connections.json': {
content: JSON.stringify(connections, null, 2),
},
'version.json': {
content: JSON.stringify(currentVersion, null, 2),
},
},
},
{
headers: {
Authorization: `token ${await this.getGistToken()}`,
'Content-Type': 'application/json',
Accept: 'application/vnd.github.v3+json',
},
}
);
return response.data;
} catch (err) {
logger.error({ err }, 'Error uploading gist');
return {
apiErrorMessage: err.message,
};
// console.error('Error creating gist:', error.response ? error.response.data : error.message);
}
},
deleteGist_meta: true,
async deleteGist({ url }) {
const response = await axios.default.delete(url, {
headers: {
Authorization: `token ${await this.getGistToken()}`,
'Content-Type': 'application/json',
Accept: 'application/vnd.github.v3+json',
},
});
return true;
},
};
+1
View File
@@ -0,0 +1 @@
module.exports = process.env.GIST_UPLOAD_SECRET;
+102 -2
View File
@@ -1,5 +1,102 @@
const shell = require('./shell');
const { setLogConfig, getLogger, setLoggerName } = require('dbgate-tools');
const processArgs = require('./utility/processArgs');
const fs = require('fs');
const moment = require('moment');
const path = require('path');
const { logsdir, setLogsFilePath, getLogsFilePath } = require('./utility/directories');
const { createLogger } = require('pinomin');
if (processArgs.startProcess) {
setLoggerName(processArgs.startProcess.replace(/Process$/, ''));
}
if (processArgs.processDisplayName) {
setLoggerName(processArgs.processDisplayName);
}
// function loadLogsContent(maxLines) {
// const text = fs.readFileSync(getLogsFilePath(), { encoding: 'utf8' });
// if (maxLines) {
// const lines = text
// .split('\n')
// .map(x => x.trim())
// .filter(x => x);
// return lines.slice(-maxLines).join('\n');
// }
// return text;
// }
function configureLogger() {
const logsFilePath = path.join(logsdir(), `${moment().format('YYYY-MM-DD-HH-mm')}-${process.pid}.ndjson`);
setLogsFilePath(logsFilePath);
setLoggerName('main');
const consoleLogLevel = process.env.CONSOLE_LOG_LEVEL || process.env.LOG_LEVEL || 'info';
const fileLogLevel = process.env.FILE_LOG_LEVEL || process.env.LOG_LEVEL || 'debug';
const logConfig = {
base: { pid: process.pid },
targets: [
{
type: 'console',
// @ts-ignore
level: consoleLogLevel,
},
{
type: 'stream',
// @ts-ignore
level: fileLogLevel,
stream: fs.createWriteStream(logsFilePath, { flags: 'a' }),
},
],
};
// logger.info(`Initialized logging, console log level: ${consoleLogLevel}, file log level: ${fileLogLevel}`);
// const streams = [];
// if (!platformInfo.isElectron) {
// streams.push({
// stream: process.stdout,
// level: process.env.CONSOLE_LOG_LEVEL || process.env.LOG_LEVEL || 'info',
// });
// }
// streams.push({
// stream: fs.createWriteStream(logsFilePath),
// level: process.env.FILE_LOG_LEVEL || process.env.LOG_LEVEL || 'info',
// });
// let logger = pinoms({
// redact: { paths: ['hostname'], remove: true },
// streams,
// });
// // @ts-ignore
// let logger = pino({
// redact: { paths: ['hostname'], remove: true },
// transport: {
// targets: [
// {
// level: process.env.CONSOLE_LOG_LEVEL || process.env.LOG_LEVEL || 'info',
// target: 'pino/file',
// },
// {
// level: process.env.FILE_LOG_LEVEL || process.env.LOG_LEVEL || 'info',
// target: 'pino/file',
// options: { destination: logsFilePath },
// },
// ],
// },
// });
// @ts-ignore
setLogConfig(logConfig);
}
if (processArgs.listenApi) {
configureLogger();
}
const shell = require('./shell/index');
const dbgateTools = require('dbgate-tools');
global['DBGATE_TOOLS'] = dbgateTools;
@@ -8,7 +105,7 @@ if (processArgs.startProcess) {
const proc = require('./proc');
const module = proc[processArgs.startProcess];
module.start();
}
}
if (processArgs.listenApi) {
const main = require('./main');
@@ -17,5 +114,8 @@ if (processArgs.listenApi) {
module.exports = {
...shell,
getLogger,
configureLogger,
// loadLogsContent,
getMainModule: () => require('./main'),
};
+29 -22
View File
@@ -33,6 +33,9 @@ const platformInfo = require('./utility/platformInfo');
const getExpressPath = require('./utility/getExpressPath');
const { getLogins } = require('./utility/hasPermission');
const _ = require('lodash');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('main');
function start() {
// console.log('process.argv', process.argv);
@@ -45,7 +48,7 @@ function start() {
if (logins && process.env.BASIC_AUTH) {
app.use(
basicAuth({
users: _.fromPairs(logins.map(x => [x.login, x.password])),
users: _.fromPairs(logins.filter(x => x.password).map(x => [x.login, x.password])),
challenge: true,
realm: 'DbGate Web App',
})
@@ -54,11 +57,27 @@ function start() {
app.use(cors());
if (platformInfo.isDocker) {
// server static files inside docker container
app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
} else if (platformInfo.isNpmDist) {
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
} else if (process.env.DEVWEB) {
// console.log('__dirname', __dirname);
// console.log(path.join(__dirname, '../../web/public/build'));
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../web/public')));
} else {
app.get(getExpressPath('/'), (req, res) => {
res.send('DbGate API');
});
}
if (auth.shouldAuthorizeApi()) {
app.use(auth.authMiddleware);
}
app.get(getExpressPath('/stream'), async function (req, res) {
const strmid = req.query.strmid;
res.set({
'Cache-Control': 'no-cache',
'Content-Type': 'text/event-stream',
@@ -69,9 +88,9 @@ function start() {
// Tell the client to retry every 10 seconds if connectivity is lost
res.write('retry: 10000\n\n');
socket.addSseResponse(res);
socket.addSseResponse(res, strmid);
onFinished(req, () => {
socket.removeSseResponse(res);
socket.removeSseResponse(strmid);
});
});
@@ -93,14 +112,10 @@ function start() {
app.use(getExpressPath('/runners/data'), express.static(rundir()));
if (platformInfo.isDocker) {
// server static files inside docker container
app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
const port = process.env.PORT || 3000;
console.log('DbGate API listening on port (docker build)', port);
logger.info(`DbGate API listening on port ${port} (docker build)`);
server.listen(port);
} else if (platformInfo.isNpmDist) {
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
getPort({
port: parseInt(
// @ts-ignore
@@ -108,35 +123,27 @@ function start() {
),
}).then(port => {
server.listen(port, () => {
console.log(`DbGate API listening on port ${port} (NPM build)`);
logger.info(`DbGate API listening on port ${port} (NPM build)`);
});
});
} else if (process.env.DEVWEB) {
console.log('__dirname', __dirname);
console.log(path.join(__dirname, '../../web/public/build'));
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../web/public')));
const port = process.env.PORT || 3000;
console.log('DbGate API & web listening on port (dev web build)', port);
logger.info(`DbGate API & web listening on port ${port} (dev web build)`);
server.listen(port);
} else {
app.get(getExpressPath('/'), (req, res) => {
res.send('DbGate API');
});
const port = process.env.PORT || 3000;
console.log('DbGate API listening on port (dev API build)', port);
logger.info(`DbGate API listening on port ${port} (dev API build)`);
server.listen(port);
}
function shutdown() {
console.log('\nShutting down DbGate API server');
logger.info('\nShutting down DbGate API server');
server.close(() => {
console.log('Server shut down, terminating');
logger.info('Server shut down, terminating');
process.exit(0);
});
setTimeout(() => {
console.log('Server close timeout, terminating');
logger.info('Server close timeout, terminating');
process.exit(0);
}, 1000);
}
@@ -1,7 +1,7 @@
const stableStringify = require('json-stable-stringify');
const { splitQuery } = require('dbgate-query-splitter');
const childProcessChecker = require('../utility/childProcessChecker');
const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools');
const { extractBoolSettingsValue, extractIntSettingsValue, getLogger } = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver');
const connectUtility = require('../utility/connectUtility');
const { handleProcessCommunication } = require('../utility/processComm');
@@ -9,6 +9,8 @@ const { SqlGenerator } = require('dbgate-tools');
const generateDeploySql = require('../shell/generateDeploySql');
const { dumpSqlSelect } = require('dbgate-sqltree');
const logger = getLogger('dbconnProcess');
let systemConnection;
let storedConnection;
let afterConnectCallbacks = [];
@@ -156,12 +158,12 @@ function resolveAnalysedPromises() {
afterAnalyseCallbacks = [];
}
async function handleRunScript({ msgid, sql }, skipReadonlyCheck = false) {
async function handleRunScript({ msgid, sql, useTransaction }, skipReadonlyCheck = false) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
try {
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
await driver.script(systemConnection, sql);
await driver.script(systemConnection, sql, { useTransaction });
process.send({ msgtype: 'response', msgid });
} catch (err) {
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
@@ -269,7 +271,7 @@ async function handleSqlPreview({ msgid, objects, options }) {
process.send({ msgtype: 'response', msgid, sql: dmp.s, isTruncated: generator.isTruncated });
if (generator.isUnhandledException) {
setTimeout(() => {
console.log('Exiting because of unhandled exception');
logger.error('Exiting because of unhandled exception');
process.exit(0);
}, 500);
}
@@ -336,7 +338,7 @@ function start() {
setInterval(() => {
const time = new Date().getTime();
if (time - lastPing > 40 * 1000) {
console.log('Database connection not alive, exiting');
logger.info('Database connection not alive, exiting');
process.exit(0);
}
}, 10 * 1000);
@@ -345,9 +347,9 @@ function start() {
if (handleProcessCommunication(message)) return;
try {
await handleMessage(message);
} catch (e) {
console.error('Error in DB connection', e);
process.send({ msgtype: 'error', error: e.message });
} catch (err) {
logger.error({ err }, 'Error in DB connection');
process.send({ msgtype: 'error', error: err.message });
}
});
}
@@ -1,15 +1,17 @@
const stableStringify = require('json-stable-stringify');
const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools');
const { extractBoolSettingsValue, extractIntSettingsValue, getLogger } = require('dbgate-tools');
const childProcessChecker = require('../utility/childProcessChecker');
const requireEngineDriver = require('../utility/requireEngineDriver');
const connectUtility = require('../utility/connectUtility');
const { handleProcessCommunication } = require('../utility/processComm');
const logger = getLogger('srvconnProcess');
let systemConnection;
let storedConnection;
let lastDatabases = null;
let lastStatus = null;
let lastPing = null;
let afterConnectCallbacks = [];
async function handleRefresh() {
const driver = requireEngineDriver(storedConnection);
@@ -74,6 +76,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() {
@@ -88,15 +102,36 @@ async function handleDatabaseOp(op, { name }) {
} else {
const dmp = driver.createDumper();
dmp[op](name);
console.log(`RUNNING SCRIPT: ${dmp.s}`);
logger.info({ sql: dmp.s }, 'Running script');
await driver.query(systemConnection, dmp.s);
}
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 }) {
return handleDriverDataCore(msgid, driver => driver.serverSummary(systemConnection));
}
async function handleSummaryCommand({ msgid, command, row }) {
return handleDriverDataCore(msgid, driver => driver.summaryCommand(systemConnection, command, row));
}
const messageHandlers = {
connect: handleConnect,
ping: handlePing,
serverSummary: handleServerSummary,
summaryCommand: handleSummaryCommand,
createDatabase: props => handleDatabaseOp('createDatabase', props),
dropDatabase: props => handleDatabaseOp('dropDatabase', props),
};
@@ -112,7 +147,7 @@ function start() {
setInterval(() => {
const time = new Date().getTime();
if (time - lastPing > 40 * 1000) {
console.log('Server connection not alive, exiting');
logger.info('Server connection not alive, exiting');
process.exit(0);
}
}, 10 * 1000);
+80 -16
View File
@@ -1,4 +1,4 @@
const uuidv1 = require('uuid/v1');
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const _ = require('lodash');
@@ -10,12 +10,18 @@ const requireEngineDriver = require('../utility/requireEngineDriver');
const { decryptConnection } = require('../utility/crypting');
const connectUtility = require('../utility/connectUtility');
const { handleProcessCommunication } = require('../utility/processComm');
const { getLogger, extractIntSettingsValue, extractBoolSettingsValue } = require('dbgate-tools');
const logger = getLogger('sessionProcess');
let systemConnection;
let storedConnection;
let afterConnectCallbacks = [];
// let currentHandlers = [];
let lastPing = null;
let lastActivity = null;
let currentProfiler = null;
let executingScripts = 0;
class TableWriter {
constructor() {
@@ -25,7 +31,7 @@ class TableWriter {
}
initializeFromQuery(structure, resultIndex) {
this.jslid = uuidv1();
this.jslid = crypto.randomUUID();
this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
fs.writeFileSync(
this.currentFile,
@@ -210,7 +216,38 @@ function waitConnected() {
});
}
async function handleStartProfiler({ jslid }) {
lastActivity = new Date().getTime();
await waitConnected();
const driver = requireEngineDriver(storedConnection);
if (!allowExecuteCustomScript(driver)) {
process.send({ msgtype: 'done' });
return;
}
const writer = new TableWriter();
writer.initializeFromReader(jslid);
currentProfiler = await driver.startProfiler(systemConnection, {
row: data => writer.rowFromReader(data),
});
currentProfiler.writer = writer;
}
async function handleStopProfiler({ jslid }) {
lastActivity = new Date().getTime();
const driver = requireEngineDriver(storedConnection);
currentProfiler.writer.close();
driver.stopProfiler(systemConnection, currentProfiler);
currentProfiler = null;
}
async function handleExecuteQuery({ sql }) {
lastActivity = new Date().getTime();
await waitConnected();
const driver = requireEngineDriver(storedConnection);
@@ -227,23 +264,30 @@ async function handleExecuteQuery({ sql }) {
//process.send({ msgtype: 'error', error: e.message });
}
const resultIndexHolder = {
value: 0,
};
for (const sqlItem of splitQuery(sql, {
...driver.getQuerySplitterOptions('stream'),
returnRichInfo: true,
})) {
await handleStream(driver, resultIndexHolder, sqlItem);
// const handler = new StreamHandler(resultIndex);
// const stream = await driver.stream(systemConnection, sqlItem, handler);
// handler.stream = stream;
// resultIndex = handler.resultIndex;
executingScripts++;
try {
const resultIndexHolder = {
value: 0,
};
for (const sqlItem of splitQuery(sql, {
...driver.getQuerySplitterOptions('stream'),
returnRichInfo: true,
})) {
await handleStream(driver, resultIndexHolder, sqlItem);
// const handler = new StreamHandler(resultIndex);
// const stream = await driver.stream(systemConnection, sqlItem, handler);
// handler.stream = stream;
// resultIndex = handler.resultIndex;
}
process.send({ msgtype: 'done' });
} finally {
executingScripts--;
}
process.send({ msgtype: 'done' });
}
async function handleExecuteReader({ jslid, sql, fileName }) {
lastActivity = new Date().getTime();
await waitConnected();
const driver = requireEngineDriver(storedConnection);
@@ -280,6 +324,8 @@ const messageHandlers = {
connect: handleConnect,
executeQuery: handleExecuteQuery,
executeReader: handleExecuteReader,
startProfiler: handleStartProfiler,
stopProfiler: handleStopProfiler,
ping: handlePing,
// cancel: handleCancel,
};
@@ -297,7 +343,25 @@ function start() {
setInterval(() => {
const time = new Date().getTime();
if (time - lastPing > 25 * 1000) {
console.log('Session not alive, exiting');
logger.info('Session not alive, exiting');
process.exit(0);
}
const useSessionTimeout =
storedConnection && storedConnection.globalSettings
? extractBoolSettingsValue(storedConnection.globalSettings, 'session.autoClose', true)
: false;
const sessionTimeout =
storedConnection && storedConnection.globalSettings
? extractIntSettingsValue(storedConnection.globalSettings, 'session.autoCloseTimeout', 15, 1, 120)
: 15;
if (
useSessionTimeout &&
time - lastActivity > sessionTimeout * 60 * 1000 &&
!currentProfiler &&
executingScripts == 0
) {
logger.info('Session not active, exiting');
process.exit(0);
}
}, 10 * 1000);
+5 -2
View File
@@ -3,6 +3,9 @@ const platformInfo = require('../utility/platformInfo');
const childProcessChecker = require('../utility/childProcessChecker');
const { handleProcessCommunication } = require('../utility/processComm');
const { SSHConnection } = require('../utility/SSHConnection');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('sshProcess');
async function getSshConnection(connection) {
const sshConfig = {
@@ -35,8 +38,8 @@ async function handleStart({ connection, tunnelConfig }) {
tunnelConfig,
});
} catch (err) {
console.log('Error creating SSH tunnel connection:', err.message);
logger.error({ err }, 'Error creating SSH tunnel connection:');
process.send({
msgtype: 'error',
connection,
+4 -1
View File
@@ -3,11 +3,14 @@ const fs = require('fs');
const { archivedir, resolveArchiveFolder } = require('../utility/directories');
// const socket = require('../utility/socket');
const jsonLinesWriter = require('./jsonLinesWriter');
const { getLogger } = require('dbgate-tools');
const logger = getLogger();
function archiveWriter({ folderName, fileName }) {
const dir = resolveArchiveFolder(folderName);
if (!fs.existsSync(dir)) {
console.log(`Creating directory ${dir}`);
logger.info(`Creating directory ${dir}`);
fs.mkdirSync(dir);
}
const jsonlFile = path.join(dir, `${fileName}.jsonl`);
+50
View File
@@ -0,0 +1,50 @@
const stream = require('stream');
const path = require('path');
const { quoteFullName, fullNameToString, getLogger } = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver');
const connectUtility = require('../utility/connectUtility');
const logger = getLogger('dataDuplicator');
const { DataDuplicator } = require('dbgate-datalib');
const copyStream = require('./copyStream');
const jsonLinesReader = require('./jsonLinesReader');
const { resolveArchiveFolder } = require('../utility/directories');
async function dataDuplicator({
connection,
archive,
items,
options,
analysedStructure = null,
driver,
systemConnection,
}) {
if (!driver) driver = requireEngineDriver(connection);
const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
logger.info(`Connected.`);
if (!analysedStructure) {
analysedStructure = await driver.analyseFull(pool);
}
const dupl = new DataDuplicator(
pool,
driver,
analysedStructure,
items.map(item => ({
name: item.name,
operation: item.operation,
matchColumns: item.matchColumns,
openStream:
item.openStream ||
(() => jsonLinesReader({ fileName: path.join(resolveArchiveFolder(archive), `${item.name}.jsonl`) })),
})),
stream,
copyStream,
options
);
await dupl.run();
}
module.exports = dataDuplicator;
+2 -2
View File
@@ -1,11 +1,11 @@
const crypto = require('crypto');
const path = require('path');
const uuidv1 = require('uuid/v1');
const { uploadsdir } = require('../utility/directories');
const { downloadFile } = require('../utility/downloader');
async function download(url) {
if (url && url.match(/(^http:\/\/)|(^https:\/\/)/)) {
const tmpFile = path.join(uploadsdir(), uuidv1());
const tmpFile = path.join(uploadsdir(), crypto.randomUUID());
await downloadFile(url, tmpFile);
return tmpFile;
}
+5 -2
View File
@@ -1,5 +1,8 @@
const requireEngineDriver = require('../utility/requireEngineDriver');
const connectUtility = require('../utility/connectUtility');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('dumpDb');
function doDump(dumper) {
return new Promise((resolve, reject) => {
@@ -21,11 +24,11 @@ async function dumpDatabase({
databaseName,
schemaName,
}) {
console.log(`Dumping database`);
logger.info(`Dumping database`);
if (!driver) driver = requireEngineDriver(connection);
const pool = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
console.log(`Connected.`);
logger.info(`Connected.`);
const dumper = await driver.createBackupDumper(pool, {
outputFile,
+5 -2
View File
@@ -1,12 +1,15 @@
const requireEngineDriver = require('../utility/requireEngineDriver');
const connectUtility = require('../utility/connectUtility');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('execQuery');
async function executeQuery({ connection = undefined, systemConnection = undefined, driver = undefined, sql }) {
console.log(`Execute query ${sql}`);
logger.info({ sql }, `Execute query`);
if (!driver) driver = requireEngineDriver(connection);
const pool = systemConnection || (await connectUtility(driver, connection, 'script'));
console.log(`Connected.`);
logger.info(`Connected.`);
await driver.script(pool, sql);
}
+17 -9
View File
@@ -1,18 +1,26 @@
const stream = require('stream');
async function fakeObjectReader({ delay = 0 } = {}) {
async function fakeObjectReader({ delay = 0, dynamicData = null } = {}) {
const pass = new stream.PassThrough({
objectMode: true,
});
function doWrite() {
pass.write({ columns: [{ columnName: 'id' }, { columnName: 'country' }], __isStreamHeader: true });
pass.write({ id: 1, country: 'Czechia' });
pass.write({ id: 2, country: 'Austria' });
pass.write({ country: 'Germany', id: 3 });
pass.write({ country: 'Romania', id: 4 });
pass.write({ country: 'Great Britain', id: 5 });
pass.write({ country: 'Bosna, Hecegovina', id: 6 });
pass.end();
if (dynamicData) {
pass.write({ __isStreamHeader: true, __isDynamicStructure: true });
for (const item of dynamicData) {
pass.write(item);
}
pass.end();
} else {
pass.write({ columns: [{ columnName: 'id' }, { columnName: 'country' }], __isStreamHeader: true });
pass.write({ id: 1, country: 'Czechia' });
pass.write({ id: 2, country: 'Austria' });
pass.write({ country: 'Germany', id: 3 });
pass.write({ country: 'Romania', id: 4 });
pass.write({ country: 'Great Britain', id: 5 });
pass.write({ country: 'Bosna, Hecegovina', id: 6 });
pass.end();
}
}
if (delay) {
@@ -0,0 +1,30 @@
const requireEngineDriver = require('../utility/requireEngineDriver');
const {
extendDatabaseInfo,
databaseInfoFromYamlModel,
getAlterDatabaseScript,
DatabaseAnalyser,
} = require('dbgate-tools');
const importDbModel = require('../utility/importDbModel');
const fs = require('fs');
async function generateModelSql({ engine, driver, modelFolder, loadedDbModel, outputFile }) {
if (!driver) driver = requireEngineDriver(engine);
const dbInfo = extendDatabaseInfo(
loadedDbModel ? databaseInfoFromYamlModel(loadedDbModel) : await importDbModel(modelFolder)
);
const { sql } = getAlterDatabaseScript(
DatabaseAnalyser.createEmptyStructure(),
dbInfo,
{},
DatabaseAnalyser.createEmptyStructure(),
dbInfo,
driver
);
fs.writeFileSync(outputFile, sql);
}
module.exports = generateModelSql;
+5 -2
View File
@@ -4,6 +4,9 @@ const connectUtility = require('../utility/connectUtility');
const { splitQueryStream } = require('dbgate-query-splitter/lib/splitQueryStream');
const download = require('./download');
const stream = require('stream');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('importDb');
class ImportStream extends stream.Transform {
constructor(pool, driver) {
@@ -38,11 +41,11 @@ function awaitStreamEnd(stream) {
}
async function importDatabase({ connection = undefined, systemConnection = undefined, driver = undefined, inputFile }) {
console.log(`Importing database`);
logger.info(`Importing database`);
if (!driver) driver = requireEngineDriver(connection);
const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
console.log(`Connected.`);
logger.info(`Connected.`);
const downloadedFile = await download(inputFile);
+8
View File
@@ -23,6 +23,10 @@ const deployDb = require('./deployDb');
const initializeApiEnvironment = require('./initializeApiEnvironment');
const dumpDatabase = require('./dumpDatabase');
const importDatabase = require('./importDatabase');
const loadDatabase = require('./loadDatabase');
const generateModelSql = require('./generateModelSql');
const modifyJsonLinesReader = require('./modifyJsonLinesReader');
const dataDuplicator = require('./dataDuplicator');
const dbgateApi = {
queryReader,
@@ -49,6 +53,10 @@ const dbgateApi = {
initializeApiEnvironment,
dumpDatabase,
importDatabase,
loadDatabase,
generateModelSql,
modifyJsonLinesReader,
dataDuplicator,
};
requirePlugin.initializeDbgateApi(dbgateApi);
+4 -1
View File
@@ -1,6 +1,9 @@
const { getLogger } = require('dbgate-tools');
const fs = require('fs');
const stream = require('stream');
const logger = getLogger('jsonArrayWriter');
class StringifyStream extends stream.Transform {
constructor() {
super({ objectMode: true });
@@ -38,7 +41,7 @@ class StringifyStream extends stream.Transform {
}
async function jsonArrayWriter({ fileName, encoding = 'utf-8' }) {
console.log(`Writing file ${fileName}`);
logger.info(`Writing file ${fileName}`);
const stringify = new StringifyStream();
const fileStream = fs.createWriteStream(fileName, encoding);
stringify.pipe(fileStream);
+8 -2
View File
@@ -1,6 +1,8 @@
const fs = require('fs');
const stream = require('stream');
const byline = require('byline');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('jsonLinesReader');
class ParseStream extends stream.Transform {
constructor({ limitRows }) {
@@ -31,9 +33,13 @@ class ParseStream extends stream.Transform {
}
async function jsonLinesReader({ fileName, encoding = 'utf-8', limitRows = undefined }) {
console.log(`Reading file ${fileName}`);
logger.info(`Reading file ${fileName}`);
const fileStream = fs.createReadStream(fileName, encoding);
const fileStream = fs.createReadStream(
fileName,
// @ts-ignore
encoding
);
const liner = byline(fileStream);
const parser = new ParseStream({ limitRows });
liner.pipe(parser);
+6 -2
View File
@@ -1,5 +1,7 @@
const { getLogger } = require('dbgate-tools');
const fs = require('fs');
const stream = require('stream');
const logger = getLogger('jsonLinesWriter');
class StringifyStream extends stream.Transform {
constructor({ header }) {
@@ -10,7 +12,9 @@ class StringifyStream extends stream.Transform {
_transform(chunk, encoding, done) {
let skip = false;
if (!this.wasHeader) {
skip = (chunk.__isStreamHeader && !this.header) || (chunk.__isStreamHeader && chunk.__isDynamicStructure);
skip =
(chunk.__isStreamHeader && !this.header) ||
(chunk.__isStreamHeader && chunk.__isDynamicStructure && !chunk.__keepDynamicStreamHeader);
this.wasHeader = true;
}
if (!skip) {
@@ -21,7 +25,7 @@ class StringifyStream extends stream.Transform {
}
async function jsonLinesWriter({ fileName, encoding = 'utf-8', header = true }) {
console.log(`Writing file ${fileName}`);
logger.info(`Writing file ${fileName}`);
const stringify = new StringifyStream({ header });
const fileStream = fs.createWriteStream(fileName, encoding);
stringify.pipe(fileStream);
+21
View File
@@ -0,0 +1,21 @@
const requireEngineDriver = require('../utility/requireEngineDriver');
const connectUtility = require('../utility/connectUtility');
const { getLogger } = require('dbgate-tools');
const exportDbModel = require('../utility/exportDbModel');
const logger = getLogger('analyseDb');
async function loadDatabase({ connection = undefined, systemConnection = undefined, driver = undefined, outputDir }) {
logger.info(`Analysing database`);
if (!driver) driver = requireEngineDriver(connection);
const pool = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
logger.info(`Connected.`);
const dbInfo = await driver.analyseFull(pool);
logger.info(`Analyse finished`);
await exportDbModel(dbInfo, outputDir);
}
module.exports = loadDatabase;
@@ -0,0 +1,145 @@
const fs = require('fs');
const _ = require('lodash');
const stream = require('stream');
const byline = require('byline');
const { getLogger, processJsonDataUpdateCommands, removeTablePairingId } = require('dbgate-tools');
const logger = getLogger('modifyJsonLinesReader');
const stableStringify = require('json-stable-stringify');
class ParseStream extends stream.Transform {
constructor({ limitRows, changeSet, mergedRows, mergeKey, mergeMode }) {
super({ objectMode: true });
this.limitRows = limitRows;
this.changeSet = changeSet;
this.wasHeader = false;
this.currentRowIndex = 0;
if (mergeMode == 'merge') {
if (mergedRows && mergeKey) {
this.mergedRowsDict = {};
for (const row of mergedRows) {
const key = stableStringify(_.pick(row, mergeKey));
this.mergedRowsDict[key] = row;
}
}
}
this.mergedRowsArray = mergedRows;
this.mergeKey = mergeKey;
this.mergeMode = mergeMode;
}
_transform(chunk, encoding, done) {
let obj = JSON.parse(chunk);
if (obj.__isStreamHeader) {
if (this.changeSet && this.changeSet.structure) {
this.push({
...removeTablePairingId(this.changeSet.structure),
__isStreamHeader: true,
});
} else {
this.push(obj);
}
this.wasHeader = true;
done();
return;
}
if (this.changeSet) {
if (!this.wasHeader && this.changeSet.structure) {
this.push({
...removeTablePairingId(this.changeSet.structure),
__isStreamHeader: true,
});
this.wasHeader = true;
}
if (!this.limitRows || this.currentRowIndex < this.limitRows) {
if (this.changeSet.deletes.find(x => x.existingRowIndex == this.currentRowIndex)) {
obj = null;
}
const update = this.changeSet.updates.find(x => x.existingRowIndex == this.currentRowIndex);
if (update) {
if (update.document) {
obj = update.document;
} else {
obj = {
...obj,
...update.fields,
};
}
}
if (obj) {
if (this.changeSet.dataUpdateCommands) {
obj = processJsonDataUpdateCommands(obj, this.changeSet.dataUpdateCommands);
}
this.push(obj);
}
this.currentRowIndex += 1;
}
} else if (this.mergedRowsArray && this.mergeKey && this.mergeMode) {
if (this.mergeMode == 'merge') {
const key = stableStringify(_.pick(obj, this.mergeKey));
if (this.mergedRowsDict[key]) {
this.push({ ...obj, ...this.mergedRowsDict[key] });
delete this.mergedRowsDict[key];
} else {
this.push(obj);
}
} else if (this.mergeMode == 'append') {
this.push(obj);
}
} else {
this.push(obj);
}
done();
}
_flush(done) {
if (this.changeSet) {
for (const insert of this.changeSet.inserts) {
this.push({
...insert.document,
...insert.fields,
});
}
} else if (this.mergedRowsArray && this.mergeKey) {
if (this.mergeMode == 'merge') {
for (const row of this.mergedRowsArray) {
const key = stableStringify(_.pick(row, this.mergeKey));
if (this.mergedRowsDict[key]) {
this.push(row);
}
}
} else {
for (const row of this.mergedRowsArray) {
this.push(row);
}
}
}
done();
}
}
async function modifyJsonLinesReader({
fileName,
encoding = 'utf-8',
limitRows = undefined,
changeSet = null,
mergedRows = null,
mergeKey = null,
mergeMode = 'merge',
}) {
logger.info(`Reading file ${fileName} with change set`);
const fileStream = fs.createReadStream(
fileName,
// @ts-ignore
encoding
);
const liner = byline(fileStream);
const parser = new ParseStream({ limitRows, changeSet, mergedRows, mergeKey, mergeMode });
liner.pipe(parser);
return parser;
}
module.exports = modifyJsonLinesReader;
+4 -2
View File
@@ -1,5 +1,7 @@
const requireEngineDriver = require('../utility/requireEngineDriver');
const connectUtility = require('../utility/connectUtility');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('queryReader');
async function queryReader({
connection,
@@ -14,12 +16,12 @@ async function queryReader({
// if (!sql && !json) {
// throw new Error('One of sql or json must be set');
// }
console.log(`Reading query ${query || sql}`);
logger.info({ sql: query || sql }, `Reading query`);
// else console.log(`Reading query ${JSON.stringify(json)}`);
const driver = requireEngineDriver(connection);
const pool = await connectUtility(driver, connection, queryType == 'json' ? 'read' : 'script');
console.log(`Connected.`);
logger.info(`Connected.`);
const reader =
queryType == 'json' ? await driver.readJsonQuery(pool, query) : await driver.readQuery(pool, query || sql);
return reader;
+3 -1
View File
@@ -3,6 +3,8 @@ const fs = require('fs');
const { pluginsdir, packagedPluginsDir, getPluginBackendPath } = require('../utility/directories');
const nativeModules = require('../nativeModules');
const platformInfo = require('../utility/platformInfo');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('requirePlugin');
const loadedPlugins = {};
@@ -17,7 +19,7 @@ function requirePlugin(packageName, requiredPlugin = null) {
if (requiredPlugin == null) {
let module;
const modulePath = getPluginBackendPath(packageName);
console.log(`Loading module ${packageName} from ${modulePath}`);
logger.info(`Loading module ${packageName} from ${modulePath}`);
try {
// @ts-ignore
module = __non_webpack_require__(modulePath);
+3 -1
View File
@@ -1,5 +1,7 @@
const { getLogger } = require('dbgate-tools');
const childProcessChecker = require('../utility/childProcessChecker');
const processArgs = require('../utility/processArgs');
const logger = getLogger();
async function runScript(func) {
if (processArgs.checkParent) {
@@ -9,7 +11,7 @@ async function runScript(func) {
await func();
process.exit(0);
} catch (err) {
console.log(err);
logger.error({ err }, `Error running script: ${err.message}`);
process.exit(1);
}
}
+3 -2
View File
@@ -1,8 +1,9 @@
const fs = require('fs');
const stream = require('stream');
const path = require('path');
const { driverBase } = require('dbgate-tools');
const { driverBase, getLogger } = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver');
const logger = getLogger('sqlDataWriter');
class SqlizeStream extends stream.Transform {
constructor({ fileName, dataName }) {
@@ -40,7 +41,7 @@ class SqlizeStream extends stream.Transform {
}
async function sqlDataWriter({ fileName, dataName, driver, encoding = 'utf-8' }) {
console.log(`Writing file ${fileName}`);
logger.info(`Writing file ${fileName}`);
const stringify = new SqlizeStream({ fileName, dataName });
const fileStream = fs.createWriteStream(fileName, encoding);
stringify.pipe(fileStream);
+6 -5
View File
@@ -1,17 +1,18 @@
const { quoteFullName, fullNameToString } = require('dbgate-tools');
const { quoteFullName, fullNameToString, getLogger } = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver');
const connectUtility = require('../utility/connectUtility');
const logger = getLogger('tableReader');
async function tableReader({ connection, pureName, schemaName }) {
const driver = requireEngineDriver(connection);
const pool = await connectUtility(driver, connection, 'read');
console.log(`Connected.`);
logger.info(`Connected.`);
const fullName = { pureName, schemaName };
if (driver.databaseEngineTypes.includes('document')) {
// @ts-ignore
console.log(`Reading collection ${fullNameToString(fullName)}`);
logger.info(`Reading collection ${fullNameToString(fullName)}`);
// @ts-ignore
return await driver.readQuery(pool, JSON.stringify(fullName));
}
@@ -20,14 +21,14 @@ async function tableReader({ connection, pureName, schemaName }) {
const query = `select * from ${quoteFullName(driver.dialect, fullName)}`;
if (table) {
// @ts-ignore
console.log(`Reading table ${fullNameToString(table)}`);
logger.info(`Reading table ${fullNameToString(table)}`);
// @ts-ignore
return await driver.readQuery(pool, query, table);
}
const view = await driver.analyseSingleObject(pool, fullName, 'views');
if (view) {
// @ts-ignore
console.log(`Reading view ${fullNameToString(view)}`);
logger.info(`Reading view ${fullNameToString(view)}`);
// @ts-ignore
return await driver.readQuery(pool, query, view);
}
+4 -3
View File
@@ -1,16 +1,17 @@
const { fullNameToString } = require('dbgate-tools');
const { fullNameToString, getLogger } = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver');
const connectUtility = require('../utility/connectUtility');
const logger = getLogger('tableWriter');
async function tableWriter({ connection, schemaName, pureName, driver, systemConnection, ...options }) {
console.log(`Writing table ${fullNameToString({ schemaName, pureName })}`);
logger.info(`Writing table ${fullNameToString({ schemaName, pureName })}`);
if (!driver) {
driver = requireEngineDriver(connection);
}
const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
console.log(`Connected.`);
logger.info(`Connected.`);
return await driver.writeTable(pool, { schemaName, pureName }, options);
}
+32 -12
View File
@@ -1,7 +1,10 @@
const crypto = require('crypto');
const { fork } = require('child_process');
const uuidv1 = require('uuid/v1');
const { handleProcessCommunication } = require('./processComm');
const processArgs = require('../utility/processArgs');
const pipeForkLogs = require('./pipeForkLogs');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('DatastoreProxy');
class DatastoreProxy {
constructor(file) {
@@ -30,13 +33,20 @@ class DatastoreProxy {
async ensureSubprocess() {
if (!this.subprocess) {
this.subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
'--is-forked-api',
'--start-process',
'jslDatastoreProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
]);
this.subprocess = fork(
global['API_PACKAGE'] || process.argv[1],
[
'--is-forked-api',
'--start-process',
'jslDatastoreProcess',
...processArgs.getPassArgs(),
// ...process.argv.slice(3),
],
{
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
}
);
pipeForkLogs(this.subprocess);
this.subprocess.on('message', message => {
// @ts-ignore
@@ -57,19 +67,29 @@ class DatastoreProxy {
async getRows(offset, limit) {
await this.ensureSubprocess();
const msgid = uuidv1();
const msgid = crypto.randomUUID();
const promise = new Promise((resolve, reject) => {
this.requests[msgid] = [resolve, reject];
this.subprocess.send({ msgtype: 'read', msgid, offset, limit });
try {
this.subprocess.send({ msgtype: 'read', msgid, offset, limit });
} catch (err) {
logger.error({ err }, 'Error getting rows');
this.subprocess = null;
}
});
return promise;
}
async notifyChangedCore() {
const msgid = uuidv1();
const msgid = crypto.randomUUID();
const promise = new Promise((resolve, reject) => {
this.requests[msgid] = [resolve, reject];
this.subprocess.send({ msgtype: 'notify', msgid });
try {
this.subprocess.send({ msgtype: 'notify', msgid });
} catch (err) {
logger.error({ err }, 'Error notifying subprocess');
this.subprocess = null;
}
});
return promise;
}
@@ -1,6 +1,6 @@
const crypto = require('crypto');
const AsyncLock = require('async-lock');
const fs = require('fs-extra');
const uuidv1 = require('uuid/v1');
const lock = new AsyncLock();
@@ -57,7 +57,7 @@ class JsonLinesDatabase {
? obj
: {
...obj,
_id: uuidv1(),
_id: crypto.randomUUID(),
};
this.data.push(elem);
await this._save();
@@ -90,6 +90,12 @@ class JsonLinesDatabase {
return obj;
}
async updateAll(mapFunction) {
await this._ensureLoaded();
this.data = this.data.map(mapFunction);
await this._save();
}
async patch(id, values) {
await this._ensureLoaded();
this.data = this.data.map(x => (x._id == id ? { ...x, ...values } : x));
+79 -36
View File
@@ -1,38 +1,64 @@
const lineReader = require('line-reader');
const crypto = require('crypto');
const fs = require('fs');
const os = require('os');
const rimraf = require('rimraf');
const path = require('path');
const AsyncLock = require('async-lock');
const lock = new AsyncLock();
const stableStringify = require('json-stable-stringify');
const { evaluateCondition } = require('dbgate-sqltree');
function fetchNextLineFromReader(reader) {
return new Promise((resolve, reject) => {
if (!reader.hasNextLine()) {
resolve(null);
return;
}
reader.nextLine((err, line) => {
if (err) {
reject(err);
} else {
resolve(line);
}
});
});
}
const requirePluginFunction = require('./requirePluginFunction');
const esort = require('external-sorting');
const { jsldir } = require('./directories');
const LineReader = require('./LineReader');
class JsonLinesDatastore {
constructor(file) {
constructor(file, formatterFunction) {
this.file = file;
this.formatterFunction = formatterFunction;
this.reader = null;
this.readedDataRowCount = 0;
this.readedSchemaRow = false;
// this.firstRowToBeReturned = null;
this.notifyChangedCallback = null;
this.currentFilter = null;
this.currentSort = null;
this.rowFormatter = requirePluginFunction(formatterFunction);
this.sortedFiles = {};
}
_closeReader() {
static async sortFile(infile, outfile, sort) {
const tempDir = path.join(os.tmpdir(), crypto.randomUUID());
fs.mkdirSync(tempDir);
await esort
.default({
input: fs.createReadStream(infile),
output: fs.createWriteStream(outfile),
deserializer: JSON.parse,
serializer: JSON.stringify,
tempDir,
maxHeap: 100,
comparer: (a, b) => {
for (const item of sort) {
const { uniqueName, order } = item;
if (a[uniqueName] < b[uniqueName]) {
return order == 'ASC' ? -1 : 1;
}
if (a[uniqueName] > b[uniqueName]) {
return order == 'ASC' ? 1 : -1;
}
}
return 0;
},
})
.asc();
await new Promise(resolve => rimraf(tempDir, resolve));
}
async _closeReader() {
// console.log('CLOSING READER', this.reader);
if (!this.reader) return;
const reader = this.reader;
this.reader = null;
@@ -40,7 +66,8 @@ class JsonLinesDatastore {
this.readedSchemaRow = false;
// this.firstRowToBeReturned = null;
this.currentFilter = null;
reader.close(() => {});
this.currentSort = null;
await reader.close();
}
async notifyChanged(callback) {
@@ -53,13 +80,17 @@ class JsonLinesDatastore {
if (call) call();
}
async _openReader() {
return new Promise((resolve, reject) =>
lineReader.open(this.file, (err, reader) => {
if (err) reject(err);
resolve(reader);
})
);
async _openReader(fileName) {
// console.log('OPENING READER', fileName);
// console.log(fs.readFileSync(fileName, 'utf-8'));
const fileStream = fs.createReadStream(fileName);
return new LineReader(fileStream);
}
parseLine(line) {
const res = JSON.parse(line);
return this.rowFormatter ? this.rowFormatter(res) : res;
}
async _readLine(parse) {
@@ -69,7 +100,7 @@ class JsonLinesDatastore {
// return res;
// }
for (;;) {
const line = await fetchNextLineFromReader(this.reader);
const line = await this.reader.readLine();
if (!line) {
// EOF
return null;
@@ -84,14 +115,14 @@ class JsonLinesDatastore {
}
}
if (this.currentFilter) {
const parsedLine = JSON.parse(line);
const parsedLine = this.parseLine(line);
if (evaluateCondition(this.currentFilter, parsedLine)) {
this.readedDataRowCount += 1;
return parse ? parsedLine : true;
}
} else {
this.readedDataRowCount += 1;
return parse ? JSON.parse(line) : true;
return parse ? this.parseLine(line) : true;
}
}
@@ -132,14 +163,19 @@ class JsonLinesDatastore {
// });
}
async _ensureReader(offset, filter) {
if (this.readedDataRowCount > offset || stableStringify(filter) != stableStringify(this.currentFilter)) {
async _ensureReader(offset, filter, sort) {
if (
this.readedDataRowCount > offset ||
stableStringify(filter) != stableStringify(this.currentFilter) ||
stableStringify(sort) != stableStringify(this.currentSort)
) {
this._closeReader();
}
if (!this.reader) {
const reader = await this._openReader();
const reader = await this._openReader(sort ? this.sortedFiles[stableStringify(sort)] : this.file);
this.reader = reader;
this.currentFilter = filter;
this.currentSort = sort;
}
// if (!this.readedSchemaRow) {
// const line = await this._readLine(true); // skip structure
@@ -171,13 +207,20 @@ class JsonLinesDatastore {
});
}
async getRows(offset, limit, filter) {
async getRows(offset, limit, filter, sort) {
const res = [];
if (sort && !this.sortedFiles[stableStringify(sort)]) {
const jslid = uuidv1();
const sortedFile = path.join(jsldir(), `${jslid}.jsonl`);
await JsonLinesDatastore.sortFile(this.file, sortedFile, sort);
this.sortedFiles[stableStringify(sort)] = sortedFile;
}
await lock.acquire('reader', async () => {
await this._ensureReader(offset, filter);
await this._ensureReader(offset, filter, sort);
// console.log(JSON.stringify(this.currentFilter, undefined, 2));
for (let i = 0; i < limit; i += 1) {
const line = await this._readLine(true);
// console.log('READED LINE', i);
if (line == null) break;
res.push(line);
}
+88
View File
@@ -0,0 +1,88 @@
const readline = require('readline');
class Queue {
constructor() {
this.elements = {};
this.head = 0;
this.tail = 0;
}
enqueue(element) {
this.elements[this.tail] = element;
this.tail++;
}
dequeue() {
const item = this.elements[this.head];
delete this.elements[this.head];
this.head++;
return item;
}
peek() {
return this.elements[this.head];
}
getLength() {
return this.tail - this.head;
}
isEmpty() {
return this.getLength() === 0;
}
}
class LineReader {
constructor(input) {
this.input = input;
this.queue = new Queue();
this.resolve = null;
this.isEnded = false;
this.rl = readline.createInterface({
input,
});
this.input.pause();
this.rl.on('line', line => {
this.input.pause();
if (this.resolve) {
const resolve = this.resolve;
this.resolve = null;
resolve(line);
return;
}
this.queue.enqueue(line);
});
this.rl.on('close', () => {
if (this.resolve) {
const resolve = this.resolve;
this.resolve = null;
this.isEnded = true;
resolve(null);
return;
}
this.queue.enqueue(null);
});
}
readLine() {
if (this.isEnded) {
return Promise.resolve(null);
}
if (!this.queue.isEmpty()) {
const res = this.queue.dequeue();
if (res == null) this.isEnded = true;
return Promise.resolve(res);
}
this.input.resume();
return new Promise(resolve => {
this.resolve = resolve;
});
}
close() {
this.isEnded = true;
return new Promise(resolve => this.input.close(resolve));
}
}
module.exports = LineReader;
@@ -1,14 +1,18 @@
const { getLogger } = require('dbgate-tools');
const logger = getLogger('childProcessChecked');
let counter = 0;
function childProcessChecker() {
setInterval(() => {
try {
process.send({ msgtype: 'ping', counter: counter++ });
} catch (ex) {
} catch (err) {
// This will come once parent dies.
// One way can be to check for error code ERR_IPC_CHANNEL_CLOSED
// and call process.exit()
console.log('parent died', ex.toString());
logger.error({ err }, 'parent died');
process.exit(1);
}
}, 1000);
+2 -2
View File
@@ -2,7 +2,7 @@ const fs = require('fs-extra');
const path = require('path');
const ageSeconds = 3600;
async function cleanDirectory(directory) {
async function cleanDirectory(directory, age = undefined) {
const files = await fs.readdir(directory);
const now = new Date().getTime();
@@ -10,7 +10,7 @@ async function cleanDirectory(directory) {
const full = path.join(directory, file);
const stat = await fs.stat(full);
const mtime = stat.mtime.getTime();
const expirationTime = mtime + ageSeconds * 1000;
const expirationTime = mtime + (age || ageSeconds) * 1000;
if (now > expirationTime) {
if (stat.isDirectory()) {
await fs.rmdir(full, { recursive: true });
+4 -1
View File
@@ -52,7 +52,7 @@ async function connectUtility(driver, storedConnection, connectionMode, addition
throw new Error(tunnel.message);
}
connection.server = '127.0.0.1';
connection.server = 'localhost';
connection.port = tunnel.localPort;
}
@@ -62,14 +62,17 @@ async function connectUtility(driver, storedConnection, connectionMode, addition
if (connection.sslCaFile) {
connection.ssl.ca = await fs.readFile(connection.sslCaFile);
connection.ssl.sslCaFile = connection.sslCaFile;
}
if (connection.sslCertFile) {
connection.ssl.cert = await fs.readFile(connection.sslCertFile);
connection.ssl.sslCertFile = connection.sslCertFile;
}
if (connection.sslKeyFile) {
connection.ssl.key = await fs.readFile(connection.sslKeyFile);
connection.ssl.sslKeyFile = connection.sslKeyFile;
}
if (connection.sslCertFilePassword) {
+32 -11
View File
@@ -1,20 +1,24 @@
const os = require('os');
const path = require('path');
const fs = require('fs');
const _ = require('lodash');
const cleanDirectory = require('./cleanDirectory');
const platformInfo = require('./platformInfo');
const processArgs = require('./processArgs');
const consoleObjectWriter = require('../shell/consoleObjectWriter');
const { getLogger } = require('dbgate-tools');
let logsFilePath;
const createDirectories = {};
const ensureDirectory = (dir, clean) => {
if (!createDirectories[dir]) {
if (clean && fs.existsSync(dir) && !platformInfo.isForkedApi) {
console.log(`Cleaning directory ${dir}`);
cleanDirectory(dir);
getLogger('directories').info(`Cleaning directory ${dir}`);
cleanDirectory(dir, _.isNumber(clean) ? clean : null);
}
if (!fs.existsSync(dir)) {
console.log(`Creating directory ${dir}`);
getLogger('directories').info(`Creating directory ${dir}`);
fs.mkdirSync(dir);
}
createDirectories[dir] = true;
@@ -38,20 +42,26 @@ function datadir() {
return dir;
}
const dirFunc = (dirname, clean = false) => () => {
const dir = path.join(datadir(), dirname);
ensureDirectory(dir, clean);
const dirFunc =
(dirname, clean, subdirs = []) =>
() => {
const dir = path.join(datadir(), dirname);
ensureDirectory(dir, clean);
for (const subdir of subdirs) {
ensureDirectory(path.join(dir, subdir), false);
}
return dir;
};
return dir;
};
const jsldir = dirFunc('jsl', true);
const rundir = dirFunc('run', true);
const uploadsdir = dirFunc('uploads', true);
const pluginsdir = dirFunc('plugins');
const archivedir = dirFunc('archive');
const archivedir = dirFunc('archive', false, ['default']);
const appdir = dirFunc('apps');
const filesdir = dirFunc('files');
const logsdir = dirFunc('logs', 3600 * 24 * 7);
function packagedPluginsDir() {
// console.log('CALL DIR FROM', new Error('xxx').stack);
@@ -127,11 +137,19 @@ function migrateDataDir() {
if (fs.existsSync(oldDir) && !fs.existsSync(newDir)) {
fs.renameSync(oldDir, newDir);
}
} catch (e) {
console.log('Error migrating data dir:', e.message);
} catch (err) {
getLogger('directories').error({ err }, 'Error migrating data dir');
}
}
function setLogsFilePath(value) {
logsFilePath = value;
}
function getLogsFilePath() {
return logsFilePath;
}
migrateDataDir();
module.exports = {
@@ -144,9 +162,12 @@ module.exports = {
ensureDirectory,
pluginsdir,
filesdir,
logsdir,
packagedPluginsDir,
packagedPluginList,
getPluginBackendPath,
resolveArchiveFolder,
clearArchiveLinksCache,
getLogsFilePath,
setLogsFilePath,
};
+3 -3
View File
@@ -1,7 +1,7 @@
const crypto = require('crypto');
// const pacote = require('pacote');
const axios = require('axios');
// const tarballExtract = require('tarball-extract');
const uuidv1 = require('uuid/v1');
const path = require('path');
const fs = require('fs');
const zlib = require('zlib');
@@ -38,9 +38,9 @@ async function downloadPackage(packageName, directory) {
const tarball = infoResp.data.versions[latest].dist.tarball;
const tmpFile = path.join(uploadsdir(), uuidv1() + '.tgz');
const tmpFile = path.join(uploadsdir(), crypto.randomUUID() + '.tgz');
await downloadFile(tarball, tmpFile);
const tmpDir = path.join(uploadsdir(), uuidv1());
const tmpDir = path.join(uploadsdir(), crypto.randomUUID());
fs.mkdirSync(tmpDir);
await extractTarball(tmpFile, tmpDir);
await copyDirectory(path.join(tmpDir, 'package'), directory);
+9
View File
@@ -0,0 +1,9 @@
class MissingCredentialsError {
constructor(detail) {
this.detail = detail;
}
}
module.exports = {
MissingCredentialsError,
};
@@ -1,15 +0,0 @@
const fs = require('fs-extra');
async function saveFreeTableData(file, data) {
const { structure, rows } = data;
const fileStream = fs.createWriteStream(file);
await fileStream.write(JSON.stringify({ __isStreamHeader: true, ...structure }) + '\n');
for (const row of rows) {
await fileStream.write(JSON.stringify(row) + '\n');
}
await fileStream.close();
}
module.exports = {
saveFreeTableData,
};
+10 -1
View File
@@ -9,7 +9,8 @@ function hasPermission(tested, req) {
return true;
}
const { user } = (req && req.auth) || {};
const key = user || '';
const { login } = (process.env.OAUTH_PERMISSIONS && req && req.user) || {};
const key = user || login || '';
const logins = getLogins();
if (!userPermissions[key]) {
@@ -53,6 +54,14 @@ function getLogins() {
}
}
}
else if (process.env.OAUTH_PERMISSIONS) {
const login_permission_keys = Object.keys(process.env).filter((key) => _.startsWith(key, 'LOGIN_PERMISSIONS_'))
for (const permissions_key of login_permission_keys) {
const login = permissions_key.replace('LOGIN_PERMISSIONS_', '');
const permissions = process.env[permissions_key];
userPermissions[login] = compilePermissions(permissions);
}
}
loginsCache = res.length > 0 ? res : null;
loginsLoaded = true;
+19
View File
@@ -0,0 +1,19 @@
const byline = require('byline');
const { safeJsonParse, getLogger } = require('dbgate-tools');
const logger = getLogger();
const logDispatcher = method => data => {
const json = safeJsonParse(data.toString());
if (json && json.level) {
logger.log(json);
} else {
logger[method](json || data.toString());
}
};
function pipeForkLogs(subprocess) {
byline(subprocess.stdout).on('data', logDispatcher('info'));
byline(subprocess.stderr).on('data', logDispatcher('error'));
}
module.exports = pipeForkLogs;
+2 -2
View File
@@ -39,8 +39,8 @@ const platformInfo = {
environment: process.env.NODE_ENV,
platform,
runningInWebpack: !!process.env.WEBPACK_DEV_SERVER_URL,
allowShellConnection: !processArgs.listenApiChild || !!process.env.SHELL_CONNECTION || !!isElectron(),
allowShellScripting: !processArgs.listenApiChild || !!process.env.SHELL_SCRIPTING || !!isElectron(),
allowShellConnection: (!processArgs.listenApiChild && !isNpmDist) || !!process.env.SHELL_CONNECTION || !!isElectron(),
allowShellScripting: (!processArgs.listenApiChild && !isNpmDist) || !!process.env.SHELL_SCRIPTING || !!isElectron(),
defaultKeyfile: path.join(os.homedir(), '.ssh/id_rsa'),
};
+2
View File
@@ -11,6 +11,7 @@ const startProcess = getNamedArg('--start-process');
const isForkedApi = process.argv.includes('--is-forked-api');
const pluginsDir = getNamedArg('--plugins-dir');
const workspaceDir = getNamedArg('--workspace-dir');
const processDisplayName = getNamedArg('--process-display-name');
const listenApi = process.argv.includes('--listen-api');
const listenApiChild = process.argv.includes('--listen-api-child') || listenApi;
@@ -37,4 +38,5 @@ module.exports = {
workspaceDir,
listenApi,
listenApiChild,
processDisplayName,
};
@@ -0,0 +1,16 @@
const _ = require('lodash');
const requirePlugin = require('../shell/requirePlugin');
function requirePluginFunction(functionName) {
if (!functionName) return null;
if (functionName.includes('@')) {
const [shortName, packageName] = functionName.split('@');
const plugin = requirePlugin(packageName);
if (plugin.functions) {
return plugin.functions[shortName];
}
}
return null;
}
module.exports = requirePluginFunction;
+36 -9
View File
@@ -1,6 +1,7 @@
const _ = require('lodash');
const stableStringify = require('json-stable-stringify');
const sseResponses = [];
const sseResponses = {};
let electronSender = null;
let pingConfigured = false;
@@ -11,12 +12,15 @@ module.exports = {
pingConfigured = true;
}
},
addSseResponse(value) {
sseResponses.push(value);
addSseResponse(value, strmid) {
sseResponses[strmid] = {
...sseResponses[strmid],
response: value,
};
this.ensurePing();
},
removeSseResponse(value) {
_.remove(sseResponses, x => x == value);
removeSseResponse(strmid) {
delete sseResponses[strmid];
},
setElectronSender(value) {
electronSender = value;
@@ -26,13 +30,36 @@ module.exports = {
if (electronSender) {
electronSender.send(message, data == null ? null : data);
}
for (const res of sseResponses) {
res.write(`event: ${message}\ndata: ${JSON.stringify(data == null ? null : data)}\n\n`);
for (const strmid in sseResponses) {
let skipThisStream = false;
if (sseResponses[strmid].filter) {
for (const key in sseResponses[strmid].filter) {
if (data && data[key]) {
if (!sseResponses[strmid].filter[key].includes(data[key])) {
skipThisStream = true;
break;
}
}
}
}
if (skipThisStream) {
continue;
}
sseResponses[strmid].response?.write(
`event: ${message}\ndata: ${stableStringify(data == null ? null : data)}\n\n`
);
}
},
emitChanged(key) {
emitChanged(key, params = undefined) {
// console.log('EMIT CHANGED', key);
this.emit('changed-cache', key);
this.emit('changed-cache', { key, ...params });
// this.emit(key);
},
setStreamIdFilter(strmid, filter) {
sseResponses[strmid] = {
...sseResponses[strmid],
filter,
};
},
};
+23 -14
View File
@@ -5,6 +5,9 @@ const AsyncLock = require('async-lock');
const lock = new AsyncLock();
const { fork } = require('child_process');
const processArgs = require('../utility/processArgs');
const { getLogger } = require('dbgate-tools');
const pipeForkLogs = require('./pipeForkLogs');
const logger = getLogger('sshTunnel');
const sshTunnelCache = {};
@@ -21,18 +24,24 @@ const CONNECTION_FIELDS = [
const TUNNEL_FIELDS = [...CONNECTION_FIELDS, 'server', 'port'];
function callForwardProcess(connection, tunnelConfig, tunnelCacheKey) {
let subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
'--is-forked-api',
'--start-process',
'sshForwardProcess',
...processArgs.getPassArgs(),
]);
let subprocess = fork(
global['API_PACKAGE'] || process.argv[1],
['--is-forked-api', '--start-process', 'sshForwardProcess', ...processArgs.getPassArgs()],
{
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
}
);
pipeForkLogs(subprocess);
subprocess.send({
msgtype: 'connect',
connection,
tunnelConfig,
});
try {
subprocess.send({
msgtype: 'connect',
connection,
tunnelConfig,
});
} catch (err) {
logger.error({ err }, 'Error connecting SSH');
}
return new Promise((resolve, reject) => {
subprocess.on('message', resp => {
// @ts-ignore
@@ -45,7 +54,7 @@ function callForwardProcess(connection, tunnelConfig, tunnelCacheKey) {
}
});
subprocess.on('exit', code => {
console.log('SSH forward process exited');
logger.info('SSH forward process exited');
delete sshTunnelCache[tunnelCacheKey];
});
});
@@ -65,13 +74,13 @@ async function getSshTunnel(connection) {
toHost: connection.server,
};
try {
console.log(
logger.info(
`Creating SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`
);
const subprocess = await callForwardProcess(connection, tunnelConfig, tunnelCacheKey);
console.log(
logger.info(
`Created SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`
);
+9 -3
View File
@@ -1,11 +1,17 @@
const uuidv1 = require('uuid/v1');
const crypto = require('crypto');
const { getLogger } = require('dbgate-tools');
const { getSshTunnel } = require('./sshTunnel');
const logger = getLogger('sshTunnelProxy');
const dispatchedMessages = {};
async function handleGetSshTunnelRequest({ msgid, connection }, subprocess) {
const response = await getSshTunnel(connection);
subprocess.send({ msgtype: 'getsshtunnel-response', msgid, response });
try {
subprocess.send({ msgtype: 'getsshtunnel-response', msgid, response });
} catch (err) {
logger.error({ err }, 'Error sending to SSH tunnel');
}
}
function handleGetSshTunnelResponse({ msgid, response }, subprocess) {
@@ -16,7 +22,7 @@ function handleGetSshTunnelResponse({ msgid, response }, subprocess) {
async function getSshTunnelProxy(connection) {
if (!process.send) return getSshTunnel(connection);
const msgid = uuidv1();
const msgid = crypto.randomUUID();
process.send({ msgtype: 'getsshtunnel-request', msgid, connection });
return new Promise((resolve, reject) => {
dispatchedMessages[msgid] = { resolve, reject };
+23 -5
View File
@@ -1,7 +1,10 @@
const _ = require('lodash');
const express = require('express');
const getExpressPath = require('./getExpressPath');
const { MissingCredentialsError } = require('./exceptions');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('useController');
/**
* @param {string} route
*/
@@ -9,11 +12,11 @@ module.exports = function useController(app, electron, route, controller) {
const router = express.Router();
if (controller._init) {
console.log(`Calling init controller for controller ${route}`);
logger.info(`Calling init controller for controller ${route}`);
try {
controller._init();
} catch (err) {
console.log(`Error initializing controller, exiting application`, err);
logger.error({ err }, `Error initializing controller, exiting application`);
process.exit(1);
}
}
@@ -37,6 +40,13 @@ module.exports = function useController(app, electron, route, controller) {
if (data === undefined) return null;
return data;
} catch (err) {
if (err instanceof MissingCredentialsError) {
return {
missingCredentials: true,
apiErrorMessage: 'Missing credentials',
detail: err.detail,
};
}
return { apiErrorMessage: err.message };
}
});
@@ -67,9 +77,17 @@ module.exports = function useController(app, electron, route, controller) {
try {
const data = await controller[key]({ ...req.body, ...req.query }, req);
res.json(data);
} catch (e) {
console.log(e);
res.status(500).json({ apiErrorMessage: e.message });
} catch (err) {
logger.error({ err }, `Error when processing route ${route}/${key}`);
if (err instanceof MissingCredentialsError) {
res.json({
missingCredentials: true,
apiErrorMessage: 'Missing credentials',
detail: err.detail,
});
} else {
res.status(500).json({ apiErrorMessage: err.message });
}
}
});
}
+31 -8
View File
@@ -9,23 +9,31 @@ import {
AllowIdentityInsert,
Expression,
} from 'dbgate-sqltree';
import type { NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
import type { NamedObjectInfo, DatabaseInfo, TableInfo } from 'dbgate-types';
import { JsonDataObjectUpdateCommand } from 'dbgate-tools';
export interface ChangeSetItem {
pureName: string;
schemaName?: string;
insertedRowIndex?: number;
existingRowIndex?: number;
document?: any;
condition?: { [column: string]: string };
fields?: { [column: string]: string };
}
export interface ChangeSet {
export interface ChangeSetItemFields {
inserts: ChangeSetItem[];
updates: ChangeSetItem[];
deletes: ChangeSetItem[];
}
export interface ChangeSet extends ChangeSetItemFields {
structure?: TableInfo;
dataUpdateCommands?: JsonDataObjectUpdateCommand[];
setColumnMode?: 'fixed' | 'variable';
}
export function createChangeSet(): ChangeSet {
return {
inserts: [],
@@ -38,6 +46,7 @@ export interface ChangeSetRowDefinition {
pureName: string;
schemaName: string;
insertedRowIndex?: number;
existingRowIndex?: number;
condition?: { [column: string]: string };
}
@@ -49,7 +58,7 @@ export interface ChangeSetFieldDefinition extends ChangeSetRowDefinition {
export function findExistingChangeSetItem(
changeSet: ChangeSet,
definition: ChangeSetRowDefinition
): [keyof ChangeSet, ChangeSetItem] {
): [keyof ChangeSetItemFields, ChangeSetItem] {
if (!changeSet || !definition) return ['updates', null];
if (definition.insertedRowIndex != null) {
return [
@@ -66,7 +75,8 @@ export function findExistingChangeSetItem(
x =>
x.pureName == definition.pureName &&
x.schemaName == definition.schemaName &&
_.isEqual(x.condition, definition.condition)
((definition.existingRowIndex != null && x.existingRowIndex == definition.existingRowIndex) ||
(definition.existingRowIndex == null && _.isEqual(x.condition, definition.condition)))
);
if (inUpdates) return ['updates', inUpdates];
@@ -74,7 +84,8 @@ export function findExistingChangeSetItem(
x =>
x.pureName == definition.pureName &&
x.schemaName == definition.schemaName &&
_.isEqual(x.condition, definition.condition)
((definition.existingRowIndex != null && x.existingRowIndex == definition.existingRowIndex) ||
(definition.existingRowIndex == null && _.isEqual(x.condition, definition.condition)))
);
if (inDeletes) return ['deletes', inDeletes];
@@ -119,6 +130,7 @@ export function setChangeSetValue(
schemaName: definition.schemaName,
condition: definition.condition,
insertedRowIndex: definition.insertedRowIndex,
existingRowIndex: definition.existingRowIndex,
fields: {
[definition.uniqueName]: value,
},
@@ -162,6 +174,7 @@ export function setChangeSetRowData(
schemaName: definition.schemaName,
condition: definition.condition,
insertedRowIndex: definition.insertedRowIndex,
existingRowIndex: definition.existingRowIndex,
document,
},
],
@@ -395,6 +408,7 @@ export function deleteChangeSetRows(changeSet: ChangeSet, definition: ChangeSetR
pureName: definition.pureName,
schemaName: definition.schemaName,
condition: definition.condition,
existingRowIndex: definition.existingRowIndex,
},
],
};
@@ -402,9 +416,11 @@ export function deleteChangeSetRows(changeSet: ChangeSet, definition: ChangeSetR
}
export function getChangeSetInsertedRows(changeSet: ChangeSet, name?: NamedObjectInfo) {
if (!name) return [];
// if (!name) return [];
if (!changeSet) return [];
const rows = changeSet.inserts.filter(x => x.pureName == name.pureName && x.schemaName == name.schemaName);
const rows = changeSet.inserts.filter(
x => name == null || (x.pureName == name.pureName && x.schemaName == name.schemaName)
);
const maxIndex = _.maxBy(rows, x => x.insertedRowIndex)?.insertedRowIndex;
if (maxIndex == null) return [];
const res = Array(maxIndex + 1).fill({});
@@ -447,5 +463,12 @@ export function changeSetInsertDocuments(changeSet: ChangeSet, documents: any[],
export function changeSetContainsChanges(changeSet: ChangeSet) {
if (!changeSet) return false;
return changeSet.deletes.length > 0 || changeSet.updates.length > 0 || changeSet.inserts.length > 0;
return (
changeSet.deletes.length > 0 ||
changeSet.updates.length > 0 ||
changeSet.inserts.length > 0 ||
!!changeSet.structure ||
!!changeSet.setColumnMode ||
changeSet.dataUpdateCommands?.length > 0
);
}
+270
View File
@@ -0,0 +1,270 @@
import { createAsyncWriteStream, getLogger, runCommandOnDriver, runQueryOnDriver } from 'dbgate-tools';
import { DatabaseInfo, EngineDriver, ForeignKeyInfo, TableInfo } from 'dbgate-types';
import _pick from 'lodash/pick';
import _omit from 'lodash/omit';
const logger = getLogger('dataDuplicator');
export interface DataDuplicatorItem {
openStream: () => Promise<ReadableStream>;
name: string;
operation: 'copy' | 'lookup' | 'insertMissing';
matchColumns: string[];
}
export interface DataDuplicatorOptions {
rollbackAfterFinish?: boolean;
skipRowsWithUnresolvedRefs?: boolean;
}
class DuplicatorReference {
constructor(
public base: DuplicatorItemHolder,
public ref: DuplicatorItemHolder,
public isMandatory: boolean,
public foreignKey: ForeignKeyInfo
) {}
get columnName() {
return this.foreignKey.columns[0].columnName;
}
}
class DuplicatorItemHolder {
references: DuplicatorReference[] = [];
backReferences: DuplicatorReference[] = [];
table: TableInfo;
isPlanned = false;
idMap = {};
autoColumn: string;
refByColumn: { [columnName: string]: DuplicatorReference } = {};
isReferenced: boolean;
get name() {
return this.item.name;
}
constructor(public item: DataDuplicatorItem, public duplicator: DataDuplicator) {
this.table = duplicator.db.tables.find(x => x.pureName.toUpperCase() == item.name.toUpperCase());
this.autoColumn = this.table.columns.find(x => x.autoIncrement)?.columnName;
if (
this.table.primaryKey?.columns?.length != 1 ||
this.table.primaryKey?.columns?.[0].columnName != this.autoColumn
) {
this.autoColumn = null;
}
}
initializeReferences() {
for (const fk of this.table.foreignKeys) {
if (fk.columns?.length != 1) continue;
const refHolder = this.duplicator.itemHolders.find(y => y.name.toUpperCase() == fk.refTableName.toUpperCase());
if (refHolder == null) continue;
const isMandatory = this.table.columns.find(x => x.columnName == fk.columns[0]?.columnName)?.notNull;
const newref = new DuplicatorReference(this, refHolder, isMandatory, fk);
this.references.push(newref);
this.refByColumn[newref.columnName] = newref;
refHolder.isReferenced = true;
}
}
createInsertObject(chunk) {
const res = _omit(
_pick(
chunk,
this.table.columns.map(x => x.columnName)
),
[this.autoColumn, ...this.backReferences.map(x => x.columnName)]
);
for (const key in res) {
const ref = this.refByColumn[key];
if (ref) {
// remap id
res[key] = ref.ref.idMap[res[key]];
if (ref.isMandatory && res[key] == null) {
// mandatory refertence not matched
if (this.duplicator.options.skipRowsWithUnresolvedRefs) {
return null;
}
throw new Error(`Unresolved reference, base=${ref.base.name}, ref=${ref.ref.name}, ${key}=${chunk[key]}`);
}
}
}
return res;
}
async runImport() {
const readStream = await this.item.openStream();
const driver = this.duplicator.driver;
const pool = this.duplicator.pool;
let inserted = 0;
let mapped = 0;
let missing = 0;
let skipped = 0;
let lastLogged = new Date();
const writeStream = createAsyncWriteStream(this.duplicator.stream, {
processItem: async chunk => {
if (chunk.__isStreamHeader) {
return;
}
const doCopy = async () => {
// console.log('chunk', this.name, JSON.stringify(chunk));
const insertedObj = this.createInsertObject(chunk);
// console.log('insertedObj', this.name, JSON.stringify(insertedObj));
if (insertedObj == null) {
skipped += 1;
return;
}
let res = await runQueryOnDriver(pool, driver, dmp => {
dmp.put(
'^insert ^into %f (%,i) ^values (%,v)',
this.table,
Object.keys(insertedObj),
Object.values(insertedObj)
);
if (
this.autoColumn &&
this.isReferenced &&
!this.duplicator.driver.dialect.requireStandaloneSelectForScopeIdentity
) {
dmp.selectScopeIdentity(this.table);
}
});
inserted += 1;
if (this.autoColumn && this.isReferenced) {
if (this.duplicator.driver.dialect.requireStandaloneSelectForScopeIdentity) {
res = await runQueryOnDriver(pool, driver, dmp => dmp.selectScopeIdentity(this.table));
}
// console.log('IDRES', JSON.stringify(res));
const resId = Object.entries(res?.rows?.[0])?.[0]?.[1];
if (resId != null) {
this.idMap[chunk[this.autoColumn]] = resId;
}
}
};
switch (this.item.operation) {
case 'copy': {
await doCopy();
break;
}
case 'insertMissing':
case 'lookup': {
const res = await runQueryOnDriver(pool, driver, dmp =>
dmp.put(
'^select %i ^from %f ^where %i = %v',
this.autoColumn,
this.table,
this.item.matchColumns[0],
chunk[this.item.matchColumns[0]]
)
);
const resId = Object.entries(res?.rows?.[0])?.[0]?.[1];
if (resId != null) {
mapped += 1;
this.idMap[chunk[this.autoColumn]] = resId;
} else if (this.item.operation == 'insertMissing') {
await doCopy();
} else {
missing += 1;
}
break;
}
}
if (new Date().getTime() - lastLogged.getTime() > 5000) {
logger.info(
`Duplicating ${this.item.name} in progress, inserted ${inserted} rows, mapped ${mapped} rows, missing ${missing} rows, skipped ${skipped} rows`
);
lastLogged = new Date();
}
// this.idMap[oldId] = newId;
},
});
await this.duplicator.copyStream(readStream, writeStream);
// await this.duplicator.driver.writeQueryStream(this.duplicator.pool, {
// mapResultId: (oldId, newId) => {
// this.idMap[oldId] = newId;
// },
// });
return { inserted, mapped, missing, skipped };
}
}
export class DataDuplicator {
itemHolders: DuplicatorItemHolder[];
itemPlan: DuplicatorItemHolder[] = [];
constructor(
public pool: any,
public driver: EngineDriver,
public db: DatabaseInfo,
public items: DataDuplicatorItem[],
public stream,
public copyStream: (input, output) => Promise<void>,
public options: DataDuplicatorOptions = {}
) {
this.itemHolders = items.map(x => new DuplicatorItemHolder(x, this));
this.itemHolders.forEach(x => x.initializeReferences());
}
findItemToPlan(): DuplicatorItemHolder {
for (const item of this.itemHolders) {
if (item.isPlanned) continue;
if (item.references.every(x => x.ref.isPlanned)) {
return item;
}
}
for (const item of this.itemHolders) {
if (item.isPlanned) continue;
if (item.references.every(x => x.ref.isPlanned || !x.isMandatory)) {
const backReferences = item.references.filter(x => !x.ref.isPlanned);
item.backReferences = backReferences;
return item;
}
}
throw new Error('Cycle in mandatory references');
}
createPlan() {
while (this.itemPlan.length < this.itemHolders.length) {
const item = this.findItemToPlan();
item.isPlanned = true;
this.itemPlan.push(item);
}
}
async run() {
this.createPlan();
await runCommandOnDriver(this.pool, this.driver, dmp => dmp.beginTransaction());
try {
for (const item of this.itemPlan) {
const stats = await item.runImport();
logger.info(
`Duplicated ${item.name}, inserted ${stats.inserted} rows, mapped ${stats.mapped} rows, missing ${stats.missing} rows, skipped ${stats.skipped} rows`
);
}
} catch (err) {
logger.error({ err }, `Failed duplicator job, rollbacking. ${err.message}`);
await runCommandOnDriver(this.pool, this.driver, dmp => dmp.rollbackTransaction());
return;
}
if (this.options.rollbackAfterFinish) {
logger.info('Rollbacking transaction, nothing was changed');
await runCommandOnDriver(this.pool, this.driver, dmp => dmp.rollbackTransaction());
} else {
logger.info('Committing duplicator transaction');
await runCommandOnDriver(this.pool, this.driver, dmp => dmp.commitTransaction());
}
}
}
-120
View File
@@ -1,120 +0,0 @@
import _ from 'lodash';
import { GridConfig, GridCache, GridConfigColumns, createGridCache, GroupFunc } from './GridConfig';
import type { TableInfo, EngineDriver, DatabaseInfo, SqlDialect } from 'dbgate-types';
import { getFilterValueExpression } from 'dbgate-filterparser';
import { ChangeCacheFunc, ChangeConfigFunc, DisplayColumn } from './GridDisplay';
export class FormViewDisplay {
isLoadedCorrectly = true;
columns: DisplayColumn[];
public baseTable: TableInfo;
dialect: SqlDialect;
constructor(
public config: GridConfig,
protected setConfig: ChangeConfigFunc,
public cache: GridCache,
protected setCache: ChangeCacheFunc,
public driver?: EngineDriver,
public dbinfo: DatabaseInfo = null,
public serverVersion = null
) {
this.dialect = (driver?.dialectByVersion && driver?.dialectByVersion(serverVersion)) || driver?.dialect;
}
addFilterColumn(column) {
if (!column) return;
this.setConfig(cfg => ({
...cfg,
formFilterColumns: [...(cfg.formFilterColumns || []), column.uniqueName],
}));
}
filterCellValue(column, rowData) {
if (!column || !rowData) return;
const value = rowData[column.uniqueName];
const expr = getFilterValueExpression(value, column.dataType);
if (expr) {
this.setConfig(cfg => ({
...cfg,
filters: {
...cfg.filters,
[column.uniqueName]: expr,
},
addedColumns: cfg.addedColumns.includes(column.uniqueName)
? cfg.addedColumns
: [...cfg.addedColumns, column.uniqueName],
}));
this.reload();
}
}
setFilter(uniqueName, value) {
this.setConfig(cfg => ({
...cfg,
filters: {
...cfg.filters,
[uniqueName]: value,
},
}));
this.reload();
}
removeFilter(uniqueName) {
const reloadRequired = !!this.config.filters[uniqueName];
this.setConfig(cfg => ({
...cfg,
formFilterColumns: (cfg.formFilterColumns || []).filter(x => x != uniqueName),
filters: _.omit(cfg.filters || [], uniqueName),
}));
if (reloadRequired) this.reload();
}
reload() {
this.setCache(cache => ({
// ...cache,
...createGridCache(),
refreshTime: new Date().getTime(),
}));
}
getKeyValue(columnName) {
const { formViewKey, formViewKeyRequested } = this.config;
if (formViewKeyRequested && formViewKeyRequested[columnName]) return formViewKeyRequested[columnName];
if (formViewKey && formViewKey[columnName]) return formViewKey[columnName];
return null;
}
requestKeyValue(columnName, value) {
if (this.getKeyValue(columnName) == value) return;
this.setConfig(cfg => ({
...cfg,
formViewKeyRequested: {
...cfg.formViewKey,
...cfg.formViewKeyRequested,
[columnName]: value,
},
}));
this.reload();
}
extractKey(row) {
if (!row || !this.baseTable || !this.baseTable.primaryKey) {
return null;
}
const formViewKey = _.pick(
row,
this.baseTable.primaryKey.columns.map(x => x.columnName)
);
return formViewKey;
}
cancelRequestKey(rowData) {
this.setConfig(cfg => ({
...cfg,
formViewKeyRequested: null,
formViewKey: rowData ? this.extractKey(rowData) : cfg.formViewKey,
}));
}
}
+2 -2
View File
@@ -27,10 +27,10 @@ export interface GridConfig extends GridConfigColumns {
childConfig?: GridConfig;
reference?: GridReferenceDefinition;
isFormView?: boolean;
formViewKey?: { [uniqueName: string]: string };
formViewKeyRequested?: { [uniqueName: string]: string };
formViewRecordNumber?: number;
formFilterColumns: string[];
formColumnFilterText?: string;
multiColumnFilter?: string;
}
export interface GridCache {
+112 -32
View File
@@ -14,7 +14,7 @@ import type {
import { parseFilter, getFilterType } from 'dbgate-filterparser';
import { filterName } from 'dbgate-tools';
import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet';
import { Expression, Select, treeToSql, dumpSqlSelect, Condition } from 'dbgate-sqltree';
import { Expression, Select, treeToSql, dumpSqlSelect, Condition, CompoudCondition } from 'dbgate-sqltree';
import { isTypeLogical } from 'dbgate-tools';
export interface DisplayColumn {
@@ -70,6 +70,7 @@ export abstract class GridDisplay {
}
dialect: SqlDialect;
columns: DisplayColumn[];
formColumns: DisplayColumn[] = [];
baseTable?: TableInfo;
baseView?: ViewInfo;
baseCollection?: CollectionInfo;
@@ -83,6 +84,7 @@ export abstract class GridDisplay {
return this.baseTable || this.baseView;
}
changeSetKeyFields: string[] = null;
editableStructure: TableInfo = null;
sortable = false;
groupable = false;
filterable = false;
@@ -211,6 +213,32 @@ export abstract class GridDisplay {
}
}
if (this.baseTableOrView && this.config.multiColumnFilter) {
try {
const condition = parseFilter(this.config.multiColumnFilter, 'string');
if (condition) {
const orCondition: CompoudCondition = {
conditionType: 'or',
conditions: [],
};
for (const column of this.baseTableOrView.columns) {
orCondition.conditions.push(
_.cloneDeepWith(condition, (expr: Expression) => {
if (expr.exprType == 'placeholder') {
return this.createColumnExpression(column, { alias: 'basetbl' });
}
})
);
}
if (orCondition.conditions.length > 0) {
conditions.push(orCondition);
}
}
} catch (err) {
console.warn(err.message);
}
}
if (conditions.length > 0) {
select.where = {
conditionType: 'and',
@@ -329,6 +357,16 @@ export abstract class GridDisplay {
...cfg.filters,
[uniqueName]: value,
},
formViewRecordNumber: 0,
}));
this.reload();
}
setMutliColumnFilter(value) {
this.setConfig(cfg => ({
...cfg,
multiColumnFilter: value,
formViewRecordNumber: 0,
}));
this.reload();
}
@@ -351,6 +389,7 @@ export abstract class GridDisplay {
this.setConfig(cfg => ({
...cfg,
filters: _.omit(cfg.filters, [uniqueName]),
formFilterColumns: (cfg.formFilterColumns || []).filter(x => x != uniqueName),
}));
this.reload();
}
@@ -457,30 +496,39 @@ export abstract class GridDisplay {
return _.pick(row, this.changeSetKeyFields);
}
getChangeSetField(row, uniqueName, insertedRowIndex): ChangeSetFieldDefinition {
getChangeSetField(
row,
uniqueName,
insertedRowIndex,
existingRowIndex = null,
baseNameOmitable = false
): ChangeSetFieldDefinition {
const col = this.columns.find(x => x.uniqueName == uniqueName);
if (!col) return null;
const baseObj = this.baseTableOrSimilar;
if (!baseObj) return null;
if (baseObj.pureName != col.pureName || baseObj.schemaName != col.schemaName) {
return null;
if (!baseNameOmitable) {
if (!baseObj) return null;
if (baseObj.pureName != col.pureName || baseObj.schemaName != col.schemaName) {
return null;
}
}
return {
...this.getChangeSetRow(row, insertedRowIndex),
...this.getChangeSetRow(row, insertedRowIndex, existingRowIndex, baseNameOmitable),
uniqueName: uniqueName,
columnName: col.columnName,
};
}
getChangeSetRow(row, insertedRowIndex): ChangeSetRowDefinition {
getChangeSetRow(row, insertedRowIndex, existingRowIndex, baseNameOmitable = false): ChangeSetRowDefinition {
const baseObj = this.baseTableOrSimilar;
if (!baseObj) return null;
if (!baseNameOmitable && !baseObj) return null;
return {
pureName: baseObj.pureName,
schemaName: baseObj.schemaName,
pureName: baseObj?.pureName,
schemaName: baseObj?.schemaName,
insertedRowIndex,
condition: insertedRowIndex == null ? this.getChangeSetCondition(row) : null,
existingRowIndex,
condition: insertedRowIndex == null && existingRowIndex == null ? this.getChangeSetCondition(row) : null,
};
}
@@ -516,13 +564,15 @@ export abstract class GridDisplay {
alias: 'basetbl',
},
columns: columns.map(col => this.createColumnExpression(col, { alias: 'basetbl' })),
orderBy: [
{
exprType: 'column',
columnName: orderColumnName,
direction: 'ASC',
},
],
orderBy: this.driver?.requiresDefaultSortCriteria
? [
{
exprType: 'column',
columnName: orderColumnName,
direction: 'ASC',
},
]
: null,
};
const displayedColumnInfo = _.keyBy(
this.columns.map(col => ({ ...col, sourceAlias: 'basetbl' })),
@@ -688,7 +738,7 @@ export abstract class GridDisplay {
// return sql;
}
compileFilters(): Condition {
compileJslFilters(): Condition {
const filters = this.config && this.config.filters;
if (!filters) return null;
const conditions = [];
@@ -711,6 +761,17 @@ export abstract class GridDisplay {
// filter parse error - ignore filter
}
}
if (this.config.multiColumnFilter) {
const placeholderCondition = parseFilter(this.config.multiColumnFilter, 'string');
if (placeholderCondition) {
conditions.push({
conditionType: 'anyColumnPass',
placeholderCondition,
});
}
}
if (conditions.length == 0) return null;
return {
conditionType: 'and',
@@ -718,22 +779,11 @@ export abstract class GridDisplay {
};
}
switchToFormView(rowData) {
if (!this.baseTable) return;
const { primaryKey } = this.baseTable;
if (!primaryKey) return;
const { columns } = primaryKey;
switchToFormView(rowIndex) {
this.setConfig(cfg => ({
...cfg,
isFormView: true,
formViewKey: rowData
? _.pick(
rowData,
columns.map(x => x.columnName)
)
: null,
formViewKeyRequested: null,
formViewRecordNumber: rowIndex,
}));
}
@@ -743,6 +793,36 @@ export abstract class GridDisplay {
isJsonView: true,
}));
}
formViewNavigate(command, allRowCount) {
switch (command) {
case 'begin':
this.setConfig(cfg => ({
...cfg,
formViewRecordNumber: 0,
}));
break;
case 'previous':
this.setConfig(cfg => ({
...cfg,
formViewRecordNumber: Math.max((cfg.formViewRecordNumber || 0) - 1, 0),
}));
break;
case 'next':
this.setConfig(cfg => ({
...cfg,
formViewRecordNumber: Math.max((cfg.formViewRecordNumber || 0) + 1, 0),
}));
break;
case 'end':
this.setConfig(cfg => ({
...cfg,
formViewRecordNumber: Math.max(allRowCount - 1, 0),
}));
break;
}
this.reload();
}
}
export function reloadDataCacheFunc(cache: GridCache): GridCache {
+7 -1
View File
@@ -13,14 +13,18 @@ export class JslGridDisplay extends GridDisplay {
setCache: ChangeCacheFunc,
rows: any,
isDynamicStructure: boolean,
supportsReload: boolean
supportsReload: boolean,
editable: boolean = false
) {
super(config, setConfig, cache, setCache, null);
this.filterable = true;
this.sortable = true;
this.supportsReload = supportsReload;
this.isDynamicStructure = isDynamicStructure;
this.filterTypeOverride = 'eval';
this.editable = editable;
this.editableStructure = editable ? structure : null;
if (structure?.columns) {
this.columns = _.uniqBy(
@@ -48,5 +52,7 @@ export class JslGridDisplay extends GridDisplay {
}
if (!this.columns) this.columns = [];
this.formColumns = this.columns;
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ export interface MacroDefinition {
name: string;
group: string;
description?: string;
type: 'transformValue';
type: 'transformValue' | 'transformRow';
code: string;
args?: MacroArgument[];
}
+9 -2
View File
@@ -5,6 +5,7 @@ import _difference from 'lodash/difference';
import debug from 'debug';
import stableStringify from 'json-stable-stringify';
import { PerspectiveDataPattern } from './PerspectiveDataPattern';
import { perspectiveValueMatcher } from './perspectiveTools';
const dbg = debug('dbgate:PerspectiveCache');
@@ -17,7 +18,9 @@ export class PerspectiveBindingGroup {
bindingValues: any[];
matchRow(row) {
return this.table.bindingColumns.every((column, index) => row[column] == this.bindingValues[index]);
return this.table.bindingColumns.every((column, index) =>
perspectiveValueMatcher(row[column], this.bindingValues[index])
);
}
}
@@ -69,7 +72,11 @@ export class PerspectiveCacheTable {
}
storeGroupSize(props: PerspectiveDataLoadProps, bindingValues: any[], count: number) {
const originalBindingValue = props.bindingValues.find(v => _zip(v, bindingValues).every(([x, y]) => x == y));
const originalBindingValue = props.bindingValues.find(v =>
_zip(v, bindingValues).every(([x, y]) => perspectiveValueMatcher(x, y))
);
// console.log('storeGroupSize NEW', bindingValues);
// console.log('storeGroupSize ORIGINAL', originalBindingValue);
if (originalBindingValue) {
const key = stableStringify(originalBindingValue);
// console.log('SET SIZE', originalBindingValue, bindingValues, key, count);

Some files were not shown because too many files have changed in this diff Show More