Compare commits

...

823 Commits

Author SHA1 Message Date
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
Jan Prochazka 8489c171f3 AD_ALLOWED_LOGINS support 2022-11-27 18:32:01 +01:00
Jan Prochazka 592865b16e configurable token lifetime 2022-11-27 11:06:33 +01:00
Jan Prochazka 012d3ec2e1 logout button from not logged page 2022-11-27 10:56:50 +01:00
Jan Prochazka d84adcca5d more robust oauth 2022-11-27 10:43:25 +01:00
Jan Prochazka b1ae7d53b9 forms login 2022-11-26 11:21:37 +01:00
Jan Prochazka 9a5287725b login WIP 2022-11-25 16:59:41 +01:00
Jan Prochazka 5ccd724166 support for acticve directory #261 2022-11-25 16:38:17 +01:00
Jan Prochazka 5e4c286427 ignore auth .env 2022-11-25 16:15:41 +01:00
Jan Prochazka 70413b954b login page 2022-11-25 13:36:18 +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 07b2a3e923 oauth disabling API 2022-11-17 20:09:27 +01:00
Jan Prochazka 94a91d5fed better oauth handle 2022-11-17 19:55:01 +01:00
Jan Prochazka 576fc2062c fix 2022-11-17 19:26:39 +01:00
Jan Prochazka 37a8783751 oauth working, but cycling sometimes 2022-11-17 12:43:38 +01:00
Jan Prochazka f42d78b2fb oauth returns access token 2022-11-14 21:20:58 +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
Jan Prochazka 792fa75ccd v5.1.6-beta.7 2022-11-13 18:39:30 +01:00
Jan Prochazka cbd3f1bae9 fixed number format 2022-11-13 18:39:13 +01:00
Jan Prochazka cd92231769 v5.1.6-beta.6 2022-11-13 17:56:19 +01:00
Jan Prochazka ecad1ae01b imrpoved closing inactive sessions 2022-11-13 17:21:47 +01:00
Jan Prochazka dc576e6ced changed timeouts for connection cleanup 2022-11-13 11:59:57 +01:00
Jan Prochazka 6cca81f8f1 cleanup of not used sessions 2022-11-13 11:52:31 +01:00
Jan Prochazka a9f1f19696 wincert renamed 2022-11-12 20:17:21 +01:00
Jan Prochazka 390ddac75b v5.1.6-beta.5 2022-11-12 19:43:30 +01:00
Jan Prochazka e2e7c6f06b cert update 2022-11-12 19:43:20 +01:00
Jan Prochazka 3a3d0683d5 v5.1.6-beta.4 2022-11-12 18:49:08 +01:00
Jan Prochazka d5534dcf07 added win.publisherName 2022-11-12 18:48:30 +01:00
Jan Prochazka b0a86f9f4a v5.1.6-beta.3 2022-11-12 18:11:54 +01:00
Jan Prochazka b833a30148 wincert changed 2022-11-12 18:11:42 +01:00
Jan Prochazka d9c1bbaa39 v5.1.6-beta.2 2022-11-10 16:43:14 +01:00
Jan Prochazka 4b74dbbd68 fixed #416: Double click does not maximize window do on MacOS 2022-11-10 13:41:55 +01:00
Jan Prochazka 9bcc61551c #406 show/hide reult window 2022-11-10 12:35:25 +01:00
Jan Prochazka ed71ef312d #406 keyboard shortcut to show/hide sidebar 2022-11-10 11:36:31 +01:00
Jan Prochazka 4fa043b7e5 fix 2022-11-10 11:06:40 +01:00
Jan Prochazka 83725dd349 fixed loading perspective 2022-11-10 10:51:51 +01:00
Jan Prochazka 4e25b71b06 removed folder text field 2022-11-10 10:11:47 +01:00
Jan Prochazka 607ae7c872 #274 allow to create folder 2022-11-10 10:10:53 +01:00
Jan Prochazka 66ade5823f ts fix 2022-11-10 09:46:54 +01:00
Jan Prochazka ebfa0a1939 allow drop on group #274 2022-11-10 09:45:41 +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
Jan Prochazka 909591404f v5.1.6-beta.1 2022-11-07 07:43:09 +01:00
Jan Prochazka 7a5f2a70ad fixed/removed svelte warnings 2022-11-06 13:32:17 +01:00
Jan Prochazka d41b254058 import type refactor 2022-11-06 13:31:13 +01:00
Jan Prochazka 435d06ffb9 rollup config - ignore some svelte warnings 2022-11-06 13:29:58 +01:00
Jan Prochazka f4a4eb7f9e ts const fix 2022-11-06 13:29:00 +01:00
Jan Prochazka 9910bbead3 Merge pull request #411 from qlaffont/master
FEAT: Folders support
2022-11-05 10:45:10 +01:00
Jan Prochazka cb619a0fe0 drag & drop into/from connection folder 2022-11-05 10:36:41 +01:00
Quentin Laffont b0d61f974c feat(app): able to set a parent 2022-11-04 10:05:17 +01:00
Quentin Laffont 8c051ff5f7 Merge pull request #1 from dbgate/master
update
2022-11-04 08:08:05 +01:00
Jan Prochazka f713a4b183 import type refactor 2022-11-03 16:45:28 +01:00
Jan Prochazka 6c7e263f0e Merge branch 'master' of github.com:dbgate/dbgate 2022-11-03 16:45:05 +01:00
Jan Prochazka ec3bfb4fae Screen postcss fix 2022-11-03 16:41:31 +01:00
Jan Prochazka 712ec8e6ee Merge pull request #409 from notz/fix-mongodb-url-with-ssh
Fix connection to mongodb via database url if a ssh tunnel is used
2022-11-03 13:41:42 +01:00
Gernot Pansy 4da0b25f44 Fix connection to mongodb via database url if a ssh tunnel is used
* Replaces the the port with the tunnel port in the url
2022-11-03 11:01:24 +01:00
Jan Prochazka 9b60b7a003 fix perspective error 2022-11-03 09:18:41 +01:00
Quentin Laffont 8ed73195c5 chore(app): add node version 2022-11-03 09:13:54 +01:00
Quentin Laffont c69fcd5eff feat(connections): able to save parent Id 2022-11-03 08:35:49 +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
Jan Prochazka 310774db3b v5.1.5 2022-10-15 22:10:11 +02:00
Jan Prochazka 1dd166b563 Merge branch 'develop' 2022-10-15 22:09:45 +02:00
Jan Prochazka 0497f541cb changelog 2022-10-15 22:08:57 +02:00
Jan Prochazka 42333a97b8 v5.1.5-beta.4 2022-10-13 15:36:24 +02:00
Jan Prochazka 494c3c8e4a remap Command+H on mac #390 2022-10-13 15:28:50 +02:00
Jan Prochazka 69a87bc076 perspective expand fix 2022-10-13 15:22:23 +02:00
Jan Prochazka bf4eb19ef5 perspective fixes 2022-10-13 15:01:45 +02:00
Jan Prochazka 225518df3e show json icon in perspectives 2022-10-13 14:09:08 +02:00
Jan Prochazka 0028240552 perspective fix 2022-10-13 13:49:48 +02:00
Jan Prochazka 44be1bdd11 menu label 2022-10-13 12:36:48 +02:00
Rinie Kervel 64168577ab zap fastmode as IN =OBJECT_ID_CONDITION does not work 2022-10-13 12:05:08 +02:00
Jan Prochazka e0703b1bae Merge branch 'master' into develop 2022-10-13 11:02:40 +02:00
Jan Prochazka a240681d6d auto view json #395 2022-10-13 11:02:25 +02:00
Jan Prochazka f5906587db perspectives: prevent multi-load 2022-10-13 10:52:10 +02:00
Rinie Kervel 51952ecfdd Oracle driver first data 2022-10-11 17:04:38 +02:00
Jan Prochazka dc0001a8cd sql case configuration #389 2022-10-08 21:14:18 +02:00
Jan Prochazka f19835203f fixed pager component #388 2022-10-08 09:08:42 +02:00
Jan Prochazka 2a2debbb88 fix nested mongo id as $oid #387 2022-10-08 09:00:19 +02:00
Jan Prochazka 23cb3a4b12 table&database ctx menu improvement 2022-10-08 08:34:31 +02:00
Jan Prochazka 13d4d34453 cond-disable ER diagram and query design #386 2022-10-08 08:09:45 +02:00
Jan Prochazka 2adca64159 socketPath configurable with env variables #358 2022-10-04 20:58:10 +02:00
Jan Prochazka 18519b5519 v5.1.5-beta.3 2022-10-02 21:04:41 +02:00
Jan Prochazka 4ddea55d23 Merge branch 'master' into develop 2022-10-02 21:04:25 +02:00
Jan Prochazka 5858061349 v5.1.5-beta.2 2022-10-02 21:03:22 +02:00
Jan Prochazka d86a5c0cb4 fix 2022-10-02 21:02:23 +02:00
Jan Prochazka c712005e33 v5.1.5-beta.1 2022-10-02 21:00:29 +02:00
Jan Prochazka 7e28e2257e Merge branch 'master' into develop 2022-10-02 20:59:54 +02:00
Jan Prochazka d0c7d591c8 don't tag BETA docker images with version 2022-10-02 20:58:52 +02:00
Jan Prochazka 17b73a58c8 changelog 2022-10-02 20:56:05 +02:00
Jan Prochazka d765591e8c v5.1.4 2022-10-02 20:52:53 +02:00
Jan Prochazka be0aeeb2c8 perspective - pattern for SQL sources 2022-10-02 20:52:22 +02:00
Jan Prochazka 23b345c898 perspective mongo fixes 2022-10-02 17:48:03 +02:00
Jan Prochazka 1d85a17533 fix 2022-10-02 15:43:00 +02:00
Jan Prochazka 7a3c46b691 perspective display - mongo nested objects 2022-10-02 12:01:07 +02:00
Jan Prochazka d647d30258 perspective nosql test 2022-10-02 11:30:05 +02:00
Jan Prochazka 8b511a0532 removed commented code 2022-10-02 10:19:53 +02:00
Jan Prochazka ccb52e9b58 fix 2022-10-02 10:19:01 +02:00
Jan Prochazka f60e1190c8 perspectives: mongo join works 2022-10-02 09:44:52 +02:00
Jan Prochazka da5dd7ac62 perspective mongo sort 2022-10-01 19:18:18 +02:00
Jan Prochazka 08abec7c3e perspective mongo condition 2022-10-01 18:50:54 +02:00
Jan Prochazka b3839def32 mongo perspective stuff - basic skeleton works 2022-10-01 17:48:47 +02:00
Jan Prochazka efe15bf0bb mongo perspective fixes 2022-10-01 16:44:34 +02:00
Jan Prochazka f9e167fc7b perspective data pattern 2022-10-01 14:43:25 +02:00
Jan Prochazka b35e8fcdf4 v5.1.4-beta.11 2022-09-29 19:42:50 +02:00
Jan Prochazka 4bdd988682 fix 2022-09-29 19:42:34 +02:00
Jan Prochazka 94f21472be v5.1.4-beta.10 2022-09-29 19:36:18 +02:00
Jan Prochazka dd33d96ef6 close tabs question 2022-09-29 19:35:58 +02:00
Jan Prochazka 7604889b72 unsaved file marker 2022-09-29 13:58:09 +02:00
Jan Prochazka 1382461bdc current query part fix 2022-09-29 11:01:17 +02:00
Jan Prochazka 833f029ab5 drop database #384 2022-09-29 09:58:35 +02:00
Jan Prochazka 04d39f6646 single docker builder 2022-09-28 20:47:32 +02:00
Jan Prochazka 4de8a5b038 v5.1.4-docker.9 2022-09-28 20:44:32 +02:00
Jan Prochazka 1dfdeed018 actions fix 2022-09-28 20:42:59 +02:00
Jan Prochazka 4892e46795 v5.1.4-docker.8 2022-09-28 20:37:42 +02:00
Jan Prochazka 5aff68d313 single docker build file 2022-09-28 20:37:32 +02:00
Jan Prochazka cdd4382266 v5.1.4-docker.7 2022-09-28 20:12:37 +02:00
Jan Prochazka bbd00ac94d multi platform 2022-09-28 20:12:25 +02:00
Jan Prochazka dba3183c94 v5.1.4-docker.6 2022-09-28 20:09:19 +02:00
Jan Prochazka a2906cca9d docker build fix 2022-09-28 20:09:05 +02:00
Jan Prochazka 140291696b v5.1.4-docker.5 2022-09-28 19:58:29 +02:00
Jan Prochazka 975643fb24 fix 2022-09-28 19:58:14 +02:00
Jan Prochazka bf9a933fb1 v5.1.4-docker.4 2022-09-28 19:44:09 +02:00
Jan Prochazka 643b792069 docker for arm platform #383 2022-09-28 19:43:54 +02:00
Jan Prochazka b4d0ccbd8c v5.1.4-docker.3 2022-09-28 19:40:06 +02:00
Jan Prochazka c9bf949d02 version fix 2022-09-28 19:37:57 +02:00
Jan Prochazka 074390ac11 v5.1.4-docker.2 2022-09-28 19:22:56 +02:00
Jan Prochazka 45e54475d0 docker build 2022-09-28 19:22:41 +02:00
Jan Prochazka f157fc77d4 docker fix 2022-09-28 19:18:30 +02:00
Jan Prochazka dac1110404 v5.1.4-docker.1 2022-09-28 19:02:43 +02:00
Jan Prochazka da00e1c228 docker build with tags 2022-09-28 19:02:13 +02:00
Jan Prochazka 9ed1cdf4b7 redis key separator fix #379 2022-09-28 18:18:03 +02:00
Jan Prochazka 18b7792370 customizable redis key separator #379 2022-09-28 18:11:46 +02:00
Jan Prochazka 53b6b71a29 non default schema name in tab title 2022-09-28 16:56:47 +02:00
Jan Prochazka b2204e1d77 using gutte3r decorations for active query part 2022-09-28 13:21:21 +02:00
Jan Prochazka e7ac7558ca better UX of code editor 2022-09-28 13:04:50 +02:00
Jan Prochazka c5a7f458ba better editor SQL splitting 2022-09-28 12:24:06 +02:00
Jan Prochazka 8ce5e68c0d typo 2022-09-28 09:19:53 +02:00
Jan Prochazka e9256fe20e changelog 2022-09-26 20:19:07 +02:00
Jan Prochazka 5913788035 v5.1.3 2022-09-26 19:53:21 +02:00
Rinie Kervel 4939b74179 get version result and login from oracle 2022-09-26 17:54:24 +02:00
Jan Prochazka 6c9c4be311 v5.1.3-beta.2 2022-09-25 20:11:22 +02:00
Jan Prochazka 1454ddacb8 fix 2022-09-25 20:10:56 +02:00
Jan Prochazka 2b26779ea8 fixes + sqlite error line number 2022-09-25 20:06:34 +02:00
Jan Prochazka 7781ad69cf sql error line number - postgres 2022-09-25 19:50:31 +02:00
Jan Prochazka 1a7f06342f query error markers 2022-09-25 19:45:47 +02:00
Jan Prochazka 2f820d8dac sql editor - play icon to execute sql fragment 2022-09-25 14:43:10 +02:00
Jan Prochazka 1535dfd407 youtube link 2022-09-25 11:42:30 +02:00
Jan Prochazka 3fe7d652b2 multiline json editing 2022-09-25 09:29:38 +02:00
Jan Prochazka 7fc8b2901b multiline cell editor #378 #371 #359 2022-09-25 08:58:39 +02:00
Jan Prochazka a56f59ceba readme 2022-09-23 21:36:24 +02:00
Jan Prochazka 2ac1072357 v5.1.3-beta.1 2022-09-22 18:46:33 +02:00
Jan Prochazka 24c26a6d87 UX fix 2022-09-22 18:46:14 +02:00
Jan Prochazka 83693e9f2c perspectives: show row count 2022-09-22 18:23:04 +02:00
Jan Prochazka 59efdd735c truncate table context menu #333 2022-09-22 16:01:52 +02:00
Jan Prochazka 41afd177ef editing multiline cell value #378 #371 #359 2022-09-22 15:05:05 +02:00
Jan Prochazka 0137b191b9 changelog 2022-09-19 19:36:59 +02:00
Jan Prochazka 054b90c90d changelog 2022-09-19 19:35:47 +02:00
Jan Prochazka a46526cbc8 v5.1.2 2022-09-19 19:29:28 +02:00
Jan Prochazka 35c42d0a83 v5.1.2-beta.5 2022-09-18 19:50:59 +02:00
Jan Prochazka 6e2ecd0b05 perspective: show undefined
cells
2022-09-18 18:51:10 +02:00
Jan Prochazka a98a4617ae #371 wip 2022-09-18 18:43:06 +02:00
Jan Prochazka 1a716f0bce sql condition in filter dialog #369 2022-09-18 14:57:30 +02:00
Jan Prochazka 973f64f4d7 custom SQL condition #369 2022-09-17 21:42:20 +02:00
Jan Prochazka a89c6810aa query design ordering more posibilities #372 2022-09-17 10:06:23 +02:00
Jan Prochazka 3d45b00a7c form view cursor commands on toolbar #370 2022-09-17 09:59:37 +02:00
Jan Prochazka f93524e24f condition editor-allow combine NULL,NOT NULL #368 2022-09-17 09:48:54 +02:00
Jan Prochazka 9aded740ca #373 fixed mongodb export 2022-09-17 09:25:12 +02:00
Jan Prochazka 66f30ff26e v5.1.2-beta.4 2022-09-15 16:49:12 +02:00
Jan Prochazka 4ced94f070 perspective image display 2022-09-15 16:48:57 +02:00
Jan Prochazka fe61e5e631 json view in perspective improvement 2022-09-15 16:04:44 +02:00
Jan Prochazka 24b0d278fd v5.1.2-beta.3 2022-09-12 20:46:42 +02:00
Jan Prochazka de5b075ba5 perspective inline json view 2022-09-12 20:45:26 +02:00
Jan Prochazka 1665c014e1 perspective fix 2022-09-12 20:16:45 +02:00
Jan Prochazka 586a06da91 v5.1.2-beta.2 2022-09-11 20:14:31 +02:00
Jan Prochazka eb1eb18163 reorder query design columns #362 2022-09-11 10:24:10 +02:00
Jan Prochazka 1983576b2f fixed editing sort order in query designer #363 2022-09-11 08:42:32 +02:00
Jan Prochazka ffbb91678c v5.1.2-beta.1 2022-09-08 15:49:54 +02:00
Jan Prochazka 0293766bad connecting via socket for mysql and postgres #358 2022-09-08 14:23:13 +02:00
Jan Prochazka 5eda39cb62 rows affected info for postgresql 2022-09-08 11:04:43 +02:00
Jan Prochazka b7c8a60c19 affected rows info for MySQL & MariaDB #361 2022-09-08 10:51:06 +02:00
Jan Prochazka 51101d91ea screenshot 2022-09-05 20:53:46 +02:00
Jan Prochazka cc9acf71ce readm screenshot 2022-09-05 20:51:12 +02:00
Jan Prochazka d27f8644d8 readme screenshot 2022-09-05 20:43:23 +02:00
Jan Prochazka 347448e3c2 changelog 2022-09-05 20:13:31 +02:00
Jan Prochazka 0a008a760b v5.1.1 2022-09-05 20:08:45 +02:00
Jan Prochazka 462be9e2bd v5.1.1-beta.5 2022-09-04 20:31:42 +02:00
Jan Prochazka f078872c5b postgre rename column fixed #350 2022-09-04 11:00:36 +02:00
Jan Prochazka fdecef7e78 analyse computed columns on ms sql #354 2022-09-04 10:04:24 +02:00
Jan Prochazka 8acafbbd6e Clear filter hotkey 2022-09-04 08:55:42 +02:00
Jan Prochazka 5b8d70747f fixed datetime null condition #356 2022-09-04 08:47:58 +02:00
Jan Prochazka c9a9c7d0f7 v5.1.1-beta.4 2022-09-01 19:19:23 +02:00
Jan Prochazka 50eb5012b1 perspective: place nodes not in tree 2022-09-01 19:18:46 +02:00
Jan Prochazka 917c2f49a0 temp root in perspectives 2022-09-01 19:12:03 +02:00
Jan Prochazka 5724067974 fix 2022-09-01 18:35:50 +02:00
Jan Prochazka 428de38b41 grayed nodes in perspective designer 2022-09-01 17:51:40 +02:00
Jan Prochazka 9e73e16b7f parent filter icon in designer 2022-09-01 17:38:43 +02:00
Jan Prochazka 1e91097bf2 splitter doesn't recreate children on collapse 2022-09-01 16:20:35 +02:00
Jan Prochazka 61f82be9f3 fix 2022-09-01 15:53:44 +02:00
Jan Prochazka 91e1c83a91 perspective - fixed work with specific DB 2022-09-01 15:50:03 +02:00
Jan Prochazka e8452704eb perspective collapsible splitters 2022-09-01 14:55:20 +02:00
Jan Prochazka 357fcbdf47 collapsible vertical splitter 2022-09-01 14:48:29 +02:00
Jan Prochazka 02abb4f512 add fk reference from designer 2022-09-01 14:22:54 +02:00
Jan Prochazka 14f71e80d3 skip loading data without columns 2022-09-01 13:35:51 +02:00
Jan Prochazka fdcf1c4c9a fixed perspective tests 2022-09-01 12:03:19 +02:00
Jan Prochazka 97e96aaba6 some perspective tests fixed 2022-09-01 11:59:25 +02:00
Jan Prochazka 174b0efd2e perspective checked fix 2022-09-01 11:21:31 +02:00
Jan Prochazka eab5f4fe5e new perspective command 2022-09-01 11:18:25 +02:00
Jan Prochazka a910e91a91 fix perspective 2022-09-01 10:56:58 +02:00
Jan Prochazka 3e83a69ef7 handle invalid perspective format 2022-09-01 10:10:19 +02:00
Jan Prochazka e3b833927d v5.1.1-beta.3 2022-08-30 21:40:40 +02:00
Jan Prochazka 6582c7831e perspective: fixed menu not not rooted nodes 2022-08-30 21:40:27 +02:00
Jan Prochazka 0d2169c996 v5.1.1-beta.2 2022-08-29 22:40:14 +02:00
Jan Prochazka e64d013fee perspective: view fix, UX 2022-08-29 22:37:04 +02:00
Jan Prochazka c1627b8546 v5.1.1-beta.1 2022-08-28 14:10:24 +02:00
Jan Prochazka 2f74eab048 better paint perspective 2022-08-28 14:08:13 +02:00
Jan Prochazka f7a269383f perspective designer table icon 2022-08-28 13:35:22 +02:00
Jan Prochazka 5f9156995b perspective set root command 2022-08-28 13:22:41 +02:00
Jan Prochazka f886b8c95d perspective alias 2022-08-28 13:11:42 +02:00
Jan Prochazka 2284264a92 perspective filter menu 2022-08-28 12:10:53 +02:00
Jan Prochazka f405db7685 show sort in perspective deisgner and tree 2022-08-28 12:02:38 +02:00
Jan Prochazka 14110cb6db reimplemented filter, sort for new model 2022-08-28 10:50:12 +02:00
Jan Prochazka 1e347f6535 perspective fixes 2022-08-28 08:42:07 +02:00
Jan Prochazka 0813f4387d arrange fix 2022-08-28 08:09:54 +02:00
Jan Prochazka 894a864110 perspective fixes 2022-08-28 07:52:36 +02:00
Jan Prochazka 4e799885b5 perspective default columns - before refactor 2022-08-28 07:25:02 +02:00
Jan Prochazka 650f9a3db9 fix 2022-08-27 20:11:20 +02:00
Jan Prochazka 6b5e33d97e default columns processor 2022-08-27 19:50:51 +02:00
Jan Prochazka 24923db199 ensure node config 2022-08-27 19:17:49 +02:00
Jan Prochazka 80faf0fd68 alias fix 2022-08-27 18:19:54 +02:00
Jan Prochazka 33b11eef38 custom joins 2022-08-27 18:07:49 +02:00
Jan Prochazka b6a0fe6713 node checked & column checked distuingish 2022-08-27 16:02:15 +02:00
Jan Prochazka 2b68a6e1de FK secondary checkbox 2022-08-27 15:26:11 +02:00
Jan Prochazka e124291267 perspective designer - checking columns 2022-08-27 12:27:07 +02:00
Jan Prochazka 1a16d7c69e changed perspective checked logic 2022-08-27 11:57:54 +02:00
Jan Prochazka 6cb2616d87 designer customization 2022-08-27 10:42:58 +02:00
Jan Prochazka 395863da3f simplify designer settings 2022-08-27 10:17:11 +02:00
Jan Prochazka fec2df9d2f perspective tree layout 2022-08-27 10:06:06 +02:00
Jan Prochazka 9e3a457ef5 perspective designer auto arrange 2022-08-27 09:19:17 +02:00
Jan Prochazka 728ad21d2f perspective arrange button 2022-08-26 19:17:35 +02:00
Jan Prochazka d2f18bc048 checkedNodes => checkedColumns 2022-08-26 18:25:40 +02:00
Jan Prochazka 0ae7939f93 table designer checkbox 2022-08-26 18:23:43 +02:00
Jan Prochazka 7ac0b907e2 perspective designer 2022-08-25 21:11:07 +02:00
Jan Prochazka 1bd4b77744 fix 2022-08-25 18:31:55 +02:00
Jan Prochazka 5e4ae3208b fixed bug in perspective display + fixed test 2022-08-25 18:01:00 +02:00
Jan Prochazka daf7629f5f perspective designer WIP 2022-08-25 15:20:16 +02:00
Jan Prochazka aeceb34d19 perspective refactor WIP 2022-08-25 13:18:55 +02:00
Jan Prochazka 2a98918857 open qdesign and perspective file #349 2022-08-25 09:13:02 +02:00
Jan Prochazka ce9d583989 added restore-terminals config 2022-08-19 15:47:55 +02:00
Jan Prochazka 7c87baf451 custom editor size #345 2022-08-19 15:10:04 +02:00
Jan Prochazka f80c6fec99 readme 2022-08-08 20:49:32 +02:00
Jan Prochazka b04af4c5e3 typo 2022-08-08 20:41:06 +02:00
Jan Prochazka fe65193189 typo 2022-08-08 20:40:32 +02:00
Jan Prochazka a75e463ef5 v5.1.0 2022-08-08 20:20:31 +02:00
Jan Prochazka 7eb59ad3a0 changelog - docs link 2022-08-08 19:50:07 +02:00
Jan Prochazka 7a9f8a460f v5.1.0-beta.4 2022-08-08 19:47:03 +02:00
Jan Prochazka 289752c023 Merge branch 'develop' 2022-08-08 19:46:39 +02:00
Jan Prochazka 98f2c06c21 perspectives: default column algorithm 2022-08-08 19:46:12 +02:00
Jan Prochazka 530b1cade3 perspective UX 2022-08-08 19:40:50 +02:00
Jan Prochazka 65aa8fb4e3 fixed multi - database structure store 2022-08-08 19:37:40 +02:00
Jan Prochazka 4c0f17a0b2 changelog 2022-08-07 20:35:04 +02:00
Jan Prochazka e4371c526b v5.1.0-beta.3 2022-08-07 20:29:04 +02:00
Jan Prochazka e39f0a1f4b perspective parent filter fix 2022-08-07 20:28:41 +02:00
Jan Prochazka 842f77d02b v5.1.0-beta.2 2022-08-07 19:37:04 +02:00
Jan Prochazka 2571e6ac7e fixed tests 2022-08-07 19:36:37 +02:00
Jan Prochazka 1599a7ea01 v5.1.0-beta.1 2022-08-07 19:30:25 +02:00
Jan Prochazka cb1d81b586 perspectives: parent filter switch in filters 2022-08-07 19:30:00 +02:00
Jan Prochazka 339588b8a0 parent filter implementation 2022-08-07 19:16:23 +02:00
Jan Prochazka 1731b7e4a3 parentFilter - declarative support 2022-08-07 18:12:10 +02:00
Jan Prochazka 5418bb932c removed commented code 2022-08-07 16:49:16 +02:00
Jan Prochazka 6154b4c780 perspective:removed filterInfos,using only filters 2022-08-07 16:48:27 +02:00
Jan Prochazka 3f9bd100e1 perspective refactor 2022-08-07 16:40:37 +02:00
Jan Prochazka b5c6ddce59 perspective: filter this value, open filtered table 2022-08-07 16:03:20 +02:00
Jan Prochazka 51c72efb34 fixed NTLM auth in SQL server #305 2022-08-07 15:13:10 +02:00
Jan Prochazka 00df20e350 imrpoved editing data with keyboard #331 2022-08-07 12:01:02 +02:00
Jan Prochazka f3a7e3af74 option to skip table save confirmation #329 2022-08-07 11:21:08 +02:00
Jan Prochazka 04c37c2b4f v5.0.10-beta.14 2022-08-07 10:42:39 +02:00
Jan Prochazka 12df0993c0 fixed sqlite3 native module version 2022-08-07 10:42:27 +02:00
Jan Prochazka ac3ec5c11e #324 fixed column type syntax for mysql 2022-08-07 10:17:32 +02:00
Jan Prochazka b565e981e4 v5.0.10-beta.13 2022-08-07 10:05:17 +02:00
Jan Prochazka f7ada698e4 fix 2022-08-07 10:04:32 +02:00
Jan Prochazka bc4c146389 remove msnodesqlv8 from mac+linux build 2022-08-07 10:02:11 +02:00
Jan Prochazka 7c80ca1374 Revert "try to fix mac build"
This reverts commit 1974243ed5.
2022-08-07 09:52:59 +02:00
Jan Prochazka 8c5cc7dcc1 v5.0.10-beta.12 2022-08-07 09:03:18 +02:00
Jan Prochazka 1974243ed5 try to fix mac build 2022-08-07 09:02:51 +02:00
Jan Prochazka 71c9071cb8 default action on connection click: connect #332 2022-08-07 08:44:31 +02:00
Jan Prochazka c28e55132a v5.0.10-beta.11 2022-08-07 08:28:38 +02:00
Jan Prochazka 2b2a4debd4 sqlite prebuild for mac 2022-08-07 08:28:21 +02:00
Jan Prochazka 563a35560b save perspective to file 2022-08-06 17:43:49 +02:00
Jan Prochazka cc019281d4 perspective custom joins supports views 2022-08-06 17:03:48 +02:00
Jan Prochazka 86d7d61cc5 perspectives: custom join over different databases 2022-08-06 16:44:37 +02:00
Jan Prochazka aff1fe0b3d perspectives: prefer not circular lookups 2022-08-06 15:30:49 +02:00
Jan Prochazka 137631b5b5 sort references 2022-08-06 14:37:00 +02:00
Jan Prochazka 090ffa064d perspective: open table ctx menu 2022-08-06 14:05:18 +02:00
Jan Prochazka f77cc1023b perspective column filter 2022-08-06 13:49:05 +02:00
Jan Prochazka c6dbb31748 perspective filters supports lookup 2022-08-06 13:24:51 +02:00
Jan Prochazka ae6c486db5 perspective load fix 2022-08-06 11:37:44 +02:00
Jan Prochazka 9a2c12d558 perspective context menu 2022-08-05 20:55:04 +02:00
Jan Prochazka 1ed01e9839 perspective cell highlight 2022-08-05 20:17:49 +02:00
Jan Prochazka 25d2c129cd perspective ctx menu 2022-08-05 19:55:14 +02:00
Jan Prochazka 7dc7af0cdb v5.0.10-beta.10 2022-08-04 21:32:20 +02:00
Jan Prochazka 80fea3b01b style 2022-08-04 21:32:06 +02:00
Jan Prochazka 97dc92e413 perspectives:add to filter ctx menu 2022-08-04 08:26:35 +02:00
Jan Prochazka 9051ba2ee1 filter list in perspective 2022-08-04 08:16:22 +02:00
Jan Prochazka 7dcbe6c7c1 v5.0.10-beta.9 2022-08-03 20:51:22 +02:00
Jan Prochazka e6fe8a6379 persp: fixed loading 2022-08-03 20:31:29 +02:00
Jan Prochazka b793e4131d perspectives: scroll optimalization 2022-08-03 20:23:36 +02:00
Jan Prochazka b737eaac13 v5.0.10-beta.8 2022-07-31 21:10:14 +02:00
Jan Prochazka cb5cce2ea3 custom joins working on the same DB 2022-07-31 21:09:58 +02:00
Jan Prochazka b05d260caa perspective custom join editing & removing 2022-07-31 20:53:22 +02:00
Jan Prochazka 091e91556d custom join dialog 2022-07-31 20:09:48 +02:00
Jan Prochazka 2b4120435b perspectives support views 2022-07-31 16:56:09 +02:00
Jan Prochazka c8d031e2c4 removed debug code 2022-07-31 16:10:57 +02:00
Jan Prochazka ac07b7e1ba code cleanup 2022-07-31 15:30:06 +02:00
Jan Prochazka bf51f45934 removed perspective intersection observer 2022-07-31 15:28:04 +02:00
Jan Prochazka fe31cfb552 css fix 2022-07-31 12:28:08 +02:00
Jan Prochazka d505be09ca perspectives - sort 2022-07-31 12:24:06 +02:00
Jan Prochazka 44668b8017 perspective sort - divided by table 2022-07-31 12:22:13 +02:00
Jan Prochazka 452dba7f32 perspective sorting 2022-07-31 12:10:56 +02:00
Jan Prochazka 7694864fe7 perspective loading indicator 2022-07-31 10:12:22 +02:00
Jan Prochazka 37d5c6fbf9 cell data formatting in perspectives 2022-07-31 09:40:20 +02:00
Jan Prochazka 802f231e43 ts fix 2022-07-31 08:52:10 +02:00
Jan Prochazka 53c39e6a43 run perspectives test on CI 2022-07-31 08:49:25 +02:00
Jan Prochazka 65f550023a removed obsolete code 2022-07-31 08:47:32 +02:00
Jan Prochazka abe7a20960 perspective - changed display table algorithm 2022-07-31 08:46:24 +02:00
Jan Prochazka d686206fe2 perspective display test WIP 2022-07-31 08:11:04 +02:00
Jan Prochazka 27b2fdb507 Copy as JSON in JSON tab 2022-07-31 08:10:19 +02:00
Jan Prochazka 88f522084d show table name in perspective 2022-07-30 09:21:04 +02:00
Jan Prochazka 8472c8be79 fixed filter parser test (upgraded jestm, ts-jest) 2022-07-30 08:39:35 +02:00
Jan Prochazka 03f8a93dd0 perspective fix 2022-07-30 08:16:07 +02:00
Jan Prochazka 2889f79120 v5.0.10-beta.7 2022-07-30 08:08:12 +02:00
Jan Prochazka 8a312181a3 upgraded all dependencies 2022-07-30 08:07:23 +02:00
Jan Prochazka e7236de078 v5.0.10-beta.6 2022-07-30 07:35:44 +02:00
Jan Prochazka 1fe2269b11 upgraded better-sqlite3 2022-07-30 07:35:34 +02:00
Jan Prochazka 10ea8ca3a6 v5.0.10-beta.5 2022-07-29 21:32:54 +02:00
Jan Prochazka 491d24984d upgraded electron to v17 2022-07-29 21:31:45 +02:00
Jan Prochazka b0279dd315 display fix 2022-07-29 21:16:07 +02:00
Jan Prochazka 9d6b581809 remove commented code 2022-07-29 21:08:50 +02:00
Jan Prochazka 3f748df1ec perspective: fixed some table scenarios 2022-07-29 21:04:09 +02:00
Jan Prochazka 7ca835765c v5.0.10-beta.4 2022-07-28 21:41:01 +02:00
Jan Prochazka a76530155d filter child tables 2022-07-28 21:02:24 +02:00
Jan Prochazka 96b82b690e v5.0.10-beta.3 2022-07-28 20:43:26 +02:00
Jan Prochazka d3a40e52fc perspective defaults - FK columns 2022-07-28 20:43:03 +02:00
Jan Prochazka 513b2ba42f default checked columns 2022-07-28 20:35:39 +02:00
Jan Prochazka d23371f642 ref loading ref column 2022-07-28 20:03:48 +02:00
Jan Prochazka 5ac6e12c3e v5.0.10-beta.2 2022-07-28 19:24:26 +02:00
Jan Prochazka 4468c0ed3b fix perspective refresh 2022-07-28 19:23:17 +02:00
Jan Prochazka 06bd9bcabe perspective - show error, ability to reset filters 2022-07-28 18:57:15 +02:00
Jan Prochazka 66d15abcab last row render fix 2022-07-28 18:46:33 +02:00
Jan Prochazka 3bdb5c0152 perspective filters 2022-07-25 21:42:01 +02:00
Jan Prochazka f504283002 use position:sticky for table header 2022-07-25 20:48:33 +02:00
Jan Prochazka f07c7909ef fix 2022-07-25 20:37:15 +02:00
Jan Prochazka c809f58349 v5.0.10-beta.1 2022-07-24 21:25:06 +02:00
Jan Prochazka 3e91ecd141 perspective filters control 2022-07-24 21:17:52 +02:00
Jan Prochazka 857185a78b Merge branch 'master' into develop 2022-07-24 21:10:19 +02:00
Jan Prochazka c189c12cae changelog 2022-07-24 21:07:02 +02:00
Jan Prochazka 96106e6aac refresh perspective command 2022-07-24 15:56:29 +02:00
Jan Prochazka 088ca231f3 uniqe binding values 2022-07-24 15:34:26 +02:00
Jan Prochazka 5395d1343b nested incomplete loading fix 2022-07-24 15:16:02 +02:00
Jan Prochazka d48c34a4a5 perspctives: nested incremental loading 2022-07-24 14:23:56 +02:00
Jan Prochazka 53ee1d87c2 Merge branch 'master' into develop 2022-07-24 10:17:47 +02:00
Jan Prochazka b5d97c8181 v5.0.9 2022-07-24 10:17:17 +02:00
Jan Prochazka 28e06166e0 perspectives - prepare for nested incremental load 2022-07-21 18:10:43 +02:00
Jan Prochazka 8f1343bc42 perspectives: fixed incremental loading 2022-07-21 17:14:27 +02:00
Jan Prochazka 2080a23b69 incremental loading 2022-07-21 17:05:07 +02:00
Jan Prochazka d71294621b perspective cache - basic design 2022-07-21 15:43:17 +02:00
Jan Prochazka 0f6ec420d2 delete commented code 2022-07-21 12:33:44 +02:00
Jan Prochazka 35152a2796 perspective loader class 2022-07-21 12:33:29 +02:00
Jan Prochazka 1abfab950e perspectives: added data provider layer 2022-07-21 11:26:44 +02:00
Jan Prochazka 6e6d0bb616 Merge branch 'master' into develop 2022-07-21 09:33:26 +02:00
Jan Prochazka 93e264e9ec v5.0.9-beta.1 2022-07-21 09:33:11 +02:00
Jan Prochazka 29257f9bf9 added missing dependency 2022-07-21 09:32:03 +02:00
Jan Prochazka 8dd90ce5e4 fixed problem with SSE #323 2022-07-21 09:31:58 +02:00
Jan Prochazka f2f7421971 new diagram, new query design added to menu 2022-07-21 09:31:52 +02:00
Jan Prochazka 8a10beef52 added missing dependency 2022-07-21 09:31:02 +02:00
Jan Prochazka df33b43e90 fixed problem with SSE #323 2022-07-21 07:49:55 +02:00
Jan Prochazka 153cba3779 new diagram, new query design added to menu 2022-07-21 07:27:28 +02:00
Jan Prochazka 8f110355c4 Merge branch 'master' into develop 2022-07-18 22:46:31 +02:00
Jan Prochazka b570f873fe changelog 2022-07-18 20:51:38 +02:00
Jan Prochazka c07e26c036 v5.0.8 2022-07-18 20:46:40 +02:00
Jan Prochazka 995bc6f16a v5.0.8-beta.4 2022-07-17 19:16:27 +02:00
Jan Prochazka 5b4339889f OR in group filter 2022-07-17 19:15:42 +02:00
Jan Prochazka ae963d7a3b v5.0.8-beta.3 2022-07-17 17:28:17 +02:00
Jan Prochazka c426cd825f configurable editor font #308 2022-07-17 17:26:58 +02:00
Jan Prochazka 62c2b3f5f4 settings could be set from env variables #304 2022-07-17 16:57:56 +02:00
Jan Prochazka ab3584dc23 v5.0.8-beta.2 2022-07-17 10:07:13 +02:00
Jan Prochazka 3a5301af6b permissions for connections 2022-07-17 10:03:17 +02:00
Jan Prochazka 55efdef181 v5.0.8-beta.1 2022-07-17 08:51:42 +02:00
Jan Prochazka e9ea1edd21 fixed adding tables to empty designer 2022-07-17 08:51:02 +02:00
Jan Prochazka d9b91f2122 or conditions in query designer #321 2022-07-17 08:38:27 +02:00
Jan Prochazka 15da5fb95e table control - noCellPadding option 2022-07-17 07:59:16 +02:00
Jan Prochazka d563a40d0f qury designer - improved style 2022-07-16 21:03:08 +02:00
Jan Prochazka a4e5630f89 custom expressions in query designer #306 2022-07-16 20:53:11 +02:00
Jan Prochazka c368ad8d54 support specifying windows domain #305 2022-07-16 11:41:08 +02:00
Jan Prochazka 01d1f08597 changelog 2022-07-16 07:28:43 +02:00
Jan Prochazka 8c934355ab v5.0.7 2022-07-16 07:21:54 +02:00
Jan Prochazka c6e3b52bc6 v5.0.7-beta.5 2022-07-16 07:21:35 +02:00
Jan Prochazka e117caf708 typo 2022-07-16 07:21:23 +02:00
Jan Prochazka 2b4d5c026e Merge branch 'master' into develop 2022-07-14 21:26:07 +02:00
Jan Prochazka 93a736f1f8 v5.0.7-beta.4 2022-07-14 19:22:53 +02:00
Jan Prochazka 1f8ef8e20e fixed changing editor theme #300 2022-07-14 19:22:37 +02:00
Jan Prochazka bef8cdbee4 removed log 2022-07-14 16:50:00 +02:00
Jan Prochazka 763391e73b datagrid: clone rows #309 2022-07-14 16:27:36 +02:00
Jan Prochazka b1cd16b095 v5.0.7-beta.3 2022-07-14 15:25:49 +02:00
Jan Prochazka 2ee1b3105f #315 ssh2 client upgrade 2022-07-14 15:25:22 +02:00
Jan Prochazka 51fa652851 v5.0.7-beta.2 2022-07-14 14:21:07 +02:00
Jan Prochazka 755781bca6 trust server certificate option #305 2022-07-14 14:03:50 +02:00
Jan Prochazka 1a90729f66 date time interval filtering #311 2022-07-14 08:51:31 +02:00
Jan Prochazka 9e520e04b2 refactor: datetime filter parsed extracted 2022-07-14 07:57:06 +02:00
Jan Prochazka ded0c8398c mongo better UX 2022-07-14 07:31:11 +02:00
Jan Prochazka dc31552f9e fixed read mongo query #312 2022-07-14 07:27:38 +02:00
Jan Prochazka e0376a708c ssh tunnel logging #315 2022-07-14 05:38:59 +02:00
Jan Prochazka 1becb89ff0 code format 2022-07-14 05:10:11 +02:00
Jan Prochazka 4d7365828e perspective - styles 2022-07-01 14:54:41 +02:00
Jan Prochazka 29ccb09ba6 perspectives: loading only neccessary columns 2022-07-01 10:24:35 +02:00
Jan Prochazka eadd3feba0 fixed header fixes 2022-07-01 09:55:54 +02:00
Jan Prochazka 93269fe314 perspectives: fixed table header 2022-07-01 08:00:21 +02:00
Jan Prochazka 34ca4c501a fixed table header 2022-06-30 21:46:45 +02:00
Jan Prochazka 34084d0e94 perspective styling 2022-06-30 21:14:56 +02:00
Jan Prochazka 07fc551383 perspective row spans 2022-06-30 21:01:27 +02:00
Jan Prochazka b0eed05a1a perspective rows 2022-06-30 19:13:01 +02:00
Jan Prochazka 8228afd725 perspective rows WIP 2022-06-30 18:10:50 +02:00
Jan Prochazka 301222d118 perspectives: show nested columns 2022-06-30 10:38:03 +02:00
Jan Prochazka 9b741b415a v5.0.7-beta.1 2022-06-30 09:09:54 +02:00
Jan Prochazka cc8438ef66 configurable auto refresh inteval 2022-06-30 08:57:20 +02:00
Jan Prochazka 179bd1f6b1 table data auto refresh 2022-06-30 08:53:01 +02:00
Jan Prochazka 08b7b1870c refresh with ctrl+r #303 2022-06-30 07:40:36 +02:00
Jan Prochazka 2c7da1d3f8 changelog 2022-06-27 20:26:20 +02:00
Jan Prochazka 2a8a2c8652 v5.0.6 2022-06-27 20:22:48 +02:00
Jan Prochazka b6b75f0743 perspectives WIP 2022-06-23 16:50:56 +02:00
Jan Prochazka aca92f3889 perspectives: render simple table 2022-06-23 16:04:05 +02:00
Jan Prochazka 4672540f82 Merge branch 'master' into develop 2022-06-23 15:33:14 +02:00
Jan Prochazka 261cec7ec2 v5.0.6-beta.6 2022-06-23 14:58:41 +02:00
Jan Prochazka de444e8485 changed table ctx menu for redonly connections 2022-06-23 14:58:26 +02:00
Jan Prochazka f4fb92be91 v5.0.6-beta.5 2022-06-23 14:42:28 +02:00
Jan Prochazka 571c928234 handler script errors 2022-06-23 14:32:34 +02:00
Jan Prochazka 2fcc4b1ff0 perspectives WIP 2022-06-23 14:24:06 +02:00
Jan Prochazka c0b0ca22aa Merge branch 'master' of https://github.com/dbgate/dbgate 2022-06-23 14:23:53 +02:00
Jan Prochazka d862762758 runscript WIP 2022-06-23 14:23:46 +02:00
Jan Prochazka 7ca8880c3c Merge branch 'master' into develop 2022-06-23 11:18:36 +02:00
Jan Prochazka 21ccc55e3f v5.0.6-beta.4 2022-06-23 10:44:25 +02:00
Jan Prochazka 8662353071 removed incorrent readonly check 2022-06-23 10:42:12 +02:00
Jan Prochazka faedcfa64d v5.0.6-beta.3 2022-06-23 10:09:05 +02:00
Jan Prochazka 7ad1796db5 filtering by XML columsn in MSSQL 2022-06-23 10:06:09 +02:00
Jan Prochazka 717ec5293b Merge branch 'master' of https://github.com/dbgate/dbgate 2022-06-23 09:57:50 +02:00
Jan Prochazka d437e171fb fixed connection tab styling 2022-06-23 09:57:36 +02:00
Jan Prochazka 97ae7ae0d6 filtering works for complex columns 2022-06-23 09:52:41 +02:00
Jan Prochazka e9a8f3ee84 ability to reset view when grid load error occurs 2022-06-23 09:08:43 +02:00
Jan Prochazka 1fb237417a v5.0.6-alpha.2 2022-06-23 08:49:39 +02:00
Jan Prochazka cd65fa16ed fixes 2022-06-23 08:48:27 +02:00
Jan Prochazka 1e5a740a52 upgraded mongodb driver 2022-06-23 08:32:12 +02:00
Jan Prochazka 42badf17eb perspective - load hiearchic JSON 2022-06-20 22:14:48 +02:00
Jan Prochazka 2ec3c2c24f perspective tre shows dependencies 2022-06-18 08:46:40 +02:00
Jan Prochazka f3ab06d3b8 refactor 2022-06-18 08:00:00 +02:00
Jan Prochazka 2b78a8dcae perspective WIP 2022-06-17 22:30:10 +02:00
Jan Prochazka 389ef98c66 v5.0.6-beta.1 2022-06-16 17:06:06 +02:00
Jan Prochazka 75bf0e53fc perspectives WIP 2022-06-16 17:05:42 +02:00
Jan Prochazka ff4dd18c1b Merge branch 'master' into develop 2022-06-16 13:37:12 +02:00
Jan Prochazka 4c535289a4 connection type label 2022-06-16 13:36:50 +02:00
Jan Prochazka d24886c73b fix 2022-06-16 13:29:51 +02:00
Jan Prochazka 9883a2982a search in columns 2022-06-16 13:07:24 +02:00
Jan Prochazka 24191870e8 Merge branch 'master' into develop 2022-06-13 19:47:06 +02:00
Jan Prochazka b9dae8928e version fixed 2022-06-13 19:36:08 +02:00
Jan Prochazka 7bed880003 v5.0.5 2022-06-13 19:35:49 +02:00
Jan Prochazka e2b95ad372 changelog 2022-06-13 19:35:31 +02:00
Jan Prochazka 18710bc67d v5.0.4 2022-06-13 19:31:31 +02:00
Jan Prochazka 02e8bba999 Merge branch 'master' into develop 2022-06-12 20:20:37 +02:00
Jan Prochazka e770ca3eef v5.0.4-beta.9 2022-06-12 20:09:35 +02:00
Jan Prochazka aaa72426c3 v5.0.4-alpha.8 2022-06-12 20:02:58 +02:00
Jan Prochazka 53e5f1378c shell scripts blocked by default only when listen-api 2022-06-12 20:02:48 +02:00
Jan Prochazka 773abc6dff v5.0.4-alpha.7 2022-06-12 19:46:35 +02:00
Jan Prochazka 8abb311623 v5.0.4-beta.6 2022-06-12 19:45:55 +02:00
Jan Prochazka 2d83fb7dc4 start api => listen api 2022-06-12 19:45:27 +02:00
Jan Prochazka ae69ca9ebd explicit start api 2022-06-12 19:42:51 +02:00
Jan Prochazka 0cb4ec54bc perspective WIP 2022-06-12 19:30:54 +02:00
Jan Prochazka d34cff234c v5.0.4-beta.5 2022-06-12 17:59:05 +02:00
Jan Prochazka 50abead104 map fixes 2022-06-12 17:58:22 +02:00
Jan Prochazka 3b0ed7df8b v5.0.4-beta.4 2022-06-12 11:28:31 +02:00
Jan Prochazka ce925337f1 fix 2022-06-12 11:27:57 +02:00
Jan Prochazka a911f5048f v5.0.4-beta.3 2022-06-12 11:22:56 +02:00
Jan Prochazka 096cbc13d8 fixed filter bool values in postgres 2022-06-12 11:14:39 +02:00
Jan Prochazka a2cf1cd340 v5.0.4-alpha.2 2022-06-12 09:47:22 +02:00
Jan Prochazka 44827ea504 cacth error when reading archive 2022-06-12 09:16:10 +02:00
Jan Prochazka 13b549ca2c Merge branch 'develop' 2022-06-12 08:19:24 +02:00
Jan Prochazka c104122a50 default value support in table yaml files #296 2022-06-12 08:15:35 +02:00
Jan Prochazka 6794b79d0e postgre fix 2022-06-12 07:30:44 +02:00
Jan Prochazka 42200ec04a v5.0.4-beta.1 2022-06-11 22:21:35 +02:00
Jan Prochazka 2944d0fa39 postgis - analyse geo columns, show in map 2022-06-11 22:21:09 +02:00
Jan Prochazka 34496ced0e support for geograpghy view in mssql 2022-06-11 19:19:50 +02:00
Jan Prochazka fa0680a8ee changed export map name 2022-06-11 18:43:19 +02:00
Jan Prochazka f2402cadb0 show tooltip on map 2022-06-11 18:41:17 +02:00
Jan Prochazka ffe82a82fa export map to file 2022-06-11 18:34:57 +02:00
Jan Prochazka 6e1a1edac0 map on standalone tab 2022-06-11 17:57:18 +02:00
Jan Prochazka 427e25b3c0 map invalidate size 2022-06-11 17:24:01 +02:00
Jan Prochazka fca2bf8ddb show popup on map 2022-06-11 17:14:22 +02:00
Jan Prochazka f65c15d2e5 map - show points 2022-06-11 16:06:49 +02:00
Jan Prochazka 343cf84a58 map - show geometry in MySQL 2022-06-11 09:45:23 +02:00
Jan Prochazka e67a94b5d7 changelog 2022-06-10 20:29:41 +02:00
Jan Prochazka cc1916eba3 v5.0.3 2022-06-10 20:28:59 +02:00
Jan Prochazka 0a0ce6ad98 v5.0.3 2022-06-10 20:25:55 +02:00
Jan Prochazka fd21157c2d v5.0.3-beta.5 2022-06-09 15:11:39 +02:00
Jan Prochazka 8b3697e71e v5.0.3-beta.4 2022-06-09 15:10:18 +02:00
Jan Prochazka f3bebcfa8f fix 2022-06-09 15:10:11 +02:00
Jan Prochazka 4c145f1f0a Merge branch 'master' of github.com:dbgate/dbgate 2022-06-09 15:09:20 +02:00
Jan Prochazka cfce4e6ece v5.0.3-beta.4 2022-06-09 15:08:21 +02:00
Jan Prochazka 13d778586e fix mssql import (+integration test) 2022-06-09 14:57:08 +02:00
Jan Prochazka 77b85fa42b changelog 2022-06-09 14:17:45 +02:00
Jan Prochazka fb89c47563 v5.0.3-beta.3 2022-06-09 14:01:38 +02:00
Jan Prochazka 8ffbdfa01d open JSON file 2022-06-09 13:39:48 +02:00
Jan Prochazka 94788454a9 multiple sort criteria #235 2022-06-09 13:12:31 +02:00
Jan Prochazka a92bd1c840 configurable object actions #255 2022-06-09 11:31:31 +02:00
Jan Prochazka 610e9f4e60 settings - default connection action 2022-06-09 10:13:05 +02:00
Jan Prochazka 6e9dace360 fix 2022-06-09 09:37:26 +02:00
Jan Prochazka 148222e239 code style 2022-06-09 09:25:47 +02:00
Jan Prochazka 5e2279cd10 unsaved connection workflow fix 2022-06-09 09:24:05 +02:00
Jan Prochazka b54026b039 connection tabs - improved UX 2022-06-09 09:16:40 +02:00
Jan Prochazka 6f3076fddb #294 fixed statusbar doesn't match active tab 2022-06-09 08:04:09 +02:00
Jan Prochazka 92c336624a v5.0.3-beta.2 2022-06-02 19:18:29 +02:00
Jan Prochazka 07d4b248bf fix 2022-06-02 19:18:15 +02:00
Jan Prochazka 1534099dc4 v5.0.3-beta.1 2022-06-02 17:32:37 +02:00
Jan Prochazka d483869aa6 reorder pinned tables #227 2022-06-02 17:32:07 +02:00
Jan Prochazka 8bb40e991b drag & drop - change order in pinned dbs #227 2022-06-02 17:26:45 +02:00
Jan Prochazka 5c6989bf91 fix 2022-06-02 16:57:23 +02:00
Jan Prochazka 5b503ae802 reset pk name in duplicate table 2022-06-02 16:52:45 +02:00
Jan Prochazka 5feb018e22 upgraded tedious driver 2022-06-02 16:47:46 +02:00
Jan Prochazka 97d259cd1e app object menu from tab 2022-06-02 16:09:20 +02:00
Jan Prochazka fa357cf8ce better UX when define SSH port #291 2022-06-02 15:48:45 +02:00
Jan Prochazka 7a0f5e171e correct handling null values in update keys 2022-06-02 15:44:03 +02:00
Jan Prochazka 24cfb23b39 fix 2022-06-02 15:42:38 +02:00
Jan Prochazka 06b6a5d3ae ability to close file uploader 2022-06-02 15:31:25 +02:00
Jan Prochazka 301ba1df60 upgraded mysql driver #293 2022-06-02 14:56:36 +02:00
481 changed files with 27270 additions and 8967 deletions
+12 -10
View File
@@ -27,6 +27,12 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: yarn adjustPackageJson
run: |
yarn adjustPackageJson
- name: yarn set timeout
run: |
yarn config set network-timeout 100000
- name: yarn install
run: |
yarn install
@@ -48,8 +54,10 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }} # token for electron publish
WIN_CSC_LINK: ${{ secrets.WINCERT_CERTIFICATE }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_PASSWORD }}
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
# WIN_CSC_LINK: ${{ secrets.WINCERT_CERTIFICATE }}
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_PASSWORD }}
CSC_LINK: ${{ secrets.APPLECERT_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
@@ -57,18 +65,12 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_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'
run: |
snapcraft login --with snapcraft.login
snapcraft upload --release=beta app/dist/*.snap
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
- name: Copy artifacts
run: |
+12 -10
View File
@@ -31,6 +31,12 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: yarn adjustPackageJson
run: |
yarn adjustPackageJson
- name: yarn set timeout
run: |
yarn config set network-timeout 100000
- name: yarn install
run: |
# yarn --version
@@ -54,8 +60,10 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }} # token for electron publish
WIN_CSC_LINK: ${{ secrets.WINCERT_CERTIFICATE }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_PASSWORD }}
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
# WIN_CSC_LINK: ${{ secrets.WINCERT_CERTIFICATE }}
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_PASSWORD }}
CSC_LINK: ${{ secrets.APPLECERT_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
@@ -67,18 +75,12 @@ jobs:
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'
run: |
snapcraft login --with snapcraft.login
snapcraft upload --release=stable app/dist/*.snap
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
- name: Copy artifacts
run: |
-58
View File
@@ -1,58 +0,0 @@
name: Docker image BETA
# on: [push]
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-docker.[0-9]+'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04]
steps:
- name: Context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: yarn install
run: |
# yarn --version
# yarn config set network-timeout 300000
yarn install
- name: setCurrentVersion
run: |
yarn setCurrentVersion
- name: Prepare docker image
run: |
yarn run prepare:docker
- name: Build docker image
run: |
docker build ./docker -t dbgate
- name: Push docker image
run: |
docker tag dbgate dbgate/dbgate:beta
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker push dbgate/dbgate:beta
- name: Build alpine docker image
run: |
docker build ./docker -t dbgate -f docker/Dockerfile-alpine
- name: Push alpine docker image
run: |
docker tag dbgate dbgate/dbgate:beta-alpine
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker push dbgate/dbgate:beta-alpine
+58 -24
View File
@@ -1,17 +1,11 @@
name: Docker image
# on: [push]
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
# - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
# on:
# push:
# branches:
# - production
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-docker.[0-9]+'
jobs:
build:
@@ -30,12 +24,43 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
dbgate/dbgate
flavor: |
latest=false
tags: |
type=raw,value=beta,enable=${{ contains(github.ref_name, '-docker.') || contains(github.ref_name, '-beta.') }}
type=match,pattern=\d+.\d+.\d+,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
- name: Docker alpine meta
id: alpmeta
uses: docker/metadata-action@v4
with:
images: |
dbgate/dbgate
flavor: |
latest=false
tags: |
type=raw,value=beta-alpine,enable=${{ contains(github.ref_name, '-docker.') || contains(github.ref_name, '-beta.') }}
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
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: yarn install
run: |
# yarn --version
# yarn config set network-timeout 300000
yarn install
- name: setCurrentVersion
run: |
@@ -43,19 +68,28 @@ jobs:
- name: Prepare docker image
run: |
yarn run prepare:docker
- name: Build docker image
run: |
docker build ./docker -t dbgate
- name: Push docker image
run: |
docker tag dbgate dbgate/dbgate
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker push dbgate/dbgate
- name: Build alpine docker image
run: |
docker build ./docker -t dbgate -f docker/Dockerfile-alpine
- name: Push alpine docker image
run: |
docker tag dbgate dbgate/dbgate:alpine
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker push dbgate/dbgate:alpine
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3
with:
push: true
context: ./docker
tags: ${{ steps.meta.outputs.tags }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: Build and push alpine
uses: docker/build-push-action@v3
with:
push: true
context: ./docker
file: ./docker/Dockerfile-alpine
tags: ${{ steps.alpmeta.outputs.tags }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
+10
View File
@@ -94,6 +94,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 +143,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
+11
View File
@@ -31,6 +31,11 @@ jobs:
run: |
cd packages/filterparser
yarn test:ci
- name: Datalib (perspective) tests
if: always()
run: |
cd packages/datalib
yarn test:ci
- uses: tanmen/jest-reporter@v1
if: always()
with:
@@ -43,6 +48,12 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-file: packages/filterparser/result.json
action-name: Filter parser test results
- uses: tanmen/jest-reporter@v1
if: always()
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-file: packages/datalib/result.json
action-name: Datalib (perspectives) test results
services:
postgres:
+1
View File
@@ -0,0 +1 @@
16.14.2
+20
View File
@@ -0,0 +1,20 @@
{
"terminals": [
{
"splitTerminals": [
{
"name": "lib",
"commands": ["yarn lib"]
},
{
"name": "web",
"commands": ["yarn start:web"]
},
{
"name": "api",
"commands": ["yarn start:api"]
}
]
}
]
}
+4 -1
View File
@@ -1,3 +1,6 @@
{
"jestrunner.jestCommand": "node_modules/.bin/cross-env DEVMODE=1 LOCALTEST=1 node_modules/.bin/jest"
"jestrunner.jestCommand": "node_modules/.bin/cross-env DEVMODE=1 LOCALTEST=1 node_modules/.bin/jest",
"cSpell.words": [
"dbgate"
]
}
+205
View File
@@ -8,6 +8,211 @@ Builds:
- linux - application for linux
- win - application for Windows
### 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
### 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
- CHANGED: OSX Command H shortcut for hiding window #390
- ADDED: Uppercase Autocomplete Suggestions #389
- FIXED: Record view left/right arrows cause start record number to be treated as string #388
- FIXED: MongoDb ObjectId behaviour not consistent in nested objects #387
- FIXED: demo.dbgate.org - beta version crash 5.1.5-beta.3 #386
- ADDED: connect via socket - configurable via environment variables #358
### 5.1.4
- ADDED: Drop database commands #384
- ADDED: Customizable Redis key separator #379
- ADDED: ARM support for docker images
- ADDED: Version tags for docker images
- ADDED: Better SQL command splitting and highlighting
- ADDED: Unsaved marker for SQL files
### 5.1.3
- ADDED: Editing multiline cell values #378 #371 #359
- ADDED: Truncate table #333
- ADDED: Perspectives - show row count
- ADDED: Query - error markers in gutter area
- ADDED: Query - ability to execute query elements from gutter
- FIXED: Correct error line numbers returned from queries
### 5.1.2
- FIXED: MongoDb any export function does not work. #373
- ADDED: Query Designer short order more flexibility #372
- ADDED: Form View move between records #370
- ADDED: Custom SQL conditions in query designer and table filtering #369
- ADDED: Query Designer filter eq to X or IS NULL #368
- FIXED: Query designer, open a saved query lost sort order #363
- ADDED: Query designer reorder columns #362
- ADDED: connect via socket #358
- FIXED: Show affected rows after UPDATE/DELETE/INSERT #361
- ADDED: Perspective cell formatters - JSON, image
- ADDED: Perspectives - cells without joined data are gray
### 5.1.1
- ADDED: Perspective designer
- FIXED: NULL,NOT NULL filter datatime columns #356
- FIXED: Recognize computed columns on SQL server #354
- ADDED: Hotkey for clear filter #352
- FIXED: Change column type on Postgres #350
- ADDED: Ability to open qdesign file #349
- ADDED: Custom editor font size #345
- ADDED: Ability to open perspective files
### 5.1.0
- ADDED: Perspectives (docs: https://dbgate.org/docs/perspectives.html )
- CHANGED: Upgraded SQLite engine version (driver better-sqlite3: 7.6.2)
- CHANGED: Upgraded ElectronJS version (from version 13 to version 17)
- CHANGED: Upgraded all dependencies with current available minor version updates
- CHANGED: By default, connect on click #332˝
- CHANGED: Improved keyboard navigation, when editing table data #331
- ADDED: Option to skip Save changes dialog #329
- FIXED: Unsigned column doesn't work correctly. #324
- FIXED: Connect to MS SQL with domain user now works also under Linux and Mac #305
### 5.0.9
- FIXED: Fixed problem with SSE events on web version
- ADDED: Added menu command "New query designer"
- ADDED: Added menu command "New ER diagram"
### 5.0.8
- ADDED: SQL Server - support using domain logins under Linux and Mac #305
- ADDED: Permissions for connections #318
- ADDED: Ability to change editor front #308
- ADDED: Custom expression in query designer #306
- ADDED: OR conditions in query designer #321
- ADDED: Ability to configure settings view environment variables #304
### 5.0.7
- FIXED: Fixed some problems with SSH tunnel (upgraded SSH client) #315
- FIXED: Fixed MognoDB executing find query #312
- ADDED: Interval filters for date/time columns #311
- ADDED: Ability to clone rows #309
- ADDED: connecting option Trust server certificate for SQL Server #305
- ADDED: Autorefresh, reload table every x second #303
- FIXED(app): Changing editor theme and font size in Editor Themes #300
### 5.0.6
- ADDED: Search in columns
- CHANGED: Upgraded mongodb driver
- ADDED: Ability to reset view, when data load fails
- FIXED: Filtering works for complex types (geography, xml under MSSQL)
- FIXED: Fixed some NPM package problems
### 5.0.5
- ADDED: Visualisation geographics objects on map #288
- ADDED: Support for native SQL as default value inside yaml files #296
- FIXED: Postgres boolean columns don't filter correctly #298
- FIXED: Importing dbgate-api as NPM package now works correctly
- FIXED: Handle error when reading deleted archive
### 5.0.3
- CHANGED: Optimalization of loading DB structure for PostgreSQL, MySQL #273
- CHANGED: Upgraded mysql driver #293
- CHANGED: Better UX when defining SSH port #291
- ADDED: Database object menu from tab
- CHANGED: Ability to close file uploader
- FIXED: Correct handling of NUL values in update keys
- CHANGED: Upgraded MS SQL tedious driver
- ADDED: Change order of pinned tables & databases #227
- FIXED: #294 Statusbar doesn't match active tab
- CHANGED: Improved connection worklflow, disconnecting shws confirmations, when it leads to close any tabs
- ADDED: Configurable object actions #255
- ADDED: Multiple sort criteria #235
- ADDED(app): Open JSON file
### 5.0.2
- FIXED: Cannot use SSH Tunnel after update #291
+11 -4
View File
@@ -22,6 +22,7 @@ DbGate is licensed under MIT license and is completely free.
* MySQL
* PostgreSQL
* SQL Server
* Oracle (experimental)
* MongoDB
* Redis
* SQLite
@@ -66,12 +67,13 @@ 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, query designer on MongoDB databases
## How to contribute
Any contributions are welcome. If you want to contribute without coding, consider following:
@@ -79,7 +81,8 @@ Any contributions are welcome. If you want to contribute without coding, conside
* 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.
* Become a backer on [Open collective](https://opencollective.com/dbgate)
* 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.
Thank you!
@@ -172,4 +175,8 @@ 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.
+15
View File
@@ -0,0 +1,15 @@
const fs = require('fs');
function adjustFile(file) {
const json = JSON.parse(fs.readFileSync(file, { encoding: 'utf-8' }));
if (process.platform != 'win32') {
delete json.optionalDependencies.msnodesqlv8;
}
if (process.arch == 'arm64') {
delete json.optionalDependencies.oracledb;
}
fs.writeFileSync(file, JSON.stringify(json, null, 2), 'utf-8');
}
adjustFile('packages/api/package.json');
adjustFile('app/package.json');
+13 -6
View File
@@ -71,7 +71,13 @@
},
"win": {
"target": [
"nsis",
{
"target": "nsis",
"arch": [
"x64",
"arm64"
]
},
{
"target": "zip",
"arch": [
@@ -107,12 +113,13 @@
"devDependencies": {
"copyfiles": "^2.2.0",
"cross-env": "^6.0.3",
"electron": "13.6.3",
"electron-builder": "22.14.5",
"electron-builder-notarize": "^1.4.0"
"electron": "17.4.10",
"electron-builder": "23.1.0",
"electron-builder-notarize": "^1.5.0"
},
"optionalDependencies": {
"better-sqlite3": "7.5.0",
"msnodesqlv8": "^2.4.4"
"better-sqlite3": "7.6.2",
"msnodesqlv8": "^2.6.0",
"oracledb": "^5.5.0"
}
}
+27
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');
@@ -22,9 +24,25 @@ const configRootPath = path.join(app.getPath('userData'), 'config-root.json');
let initialConfig = {};
let apiLoaded = false;
let mainModule;
// let getLogger;
// let loadLogsContent;
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' }));
} catch (err) {
@@ -154,6 +172,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) {
@@ -166,8 +188,10 @@ ipcMain.on('window-action', async (event, arg) => {
case 'maximize':
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
mainWindow.webContents.send('setIsMaximized', false);
} else {
mainWindow.maximize();
mainWindow.webContents.send('setIsMaximized', true);
}
break;
case 'close':
@@ -327,9 +351,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);
+8
View File
@@ -6,6 +6,9 @@ module.exports = ({ editMenu }) => [
{ command: 'new.sqliteDatabase', hideDisabled: true },
{ divider: true },
{ command: 'new.query', hideDisabled: true },
{ command: 'new.queryDesign', hideDisabled: true },
{ command: 'new.diagram', hideDisabled: true },
{ command: 'new.perspective', hideDisabled: true },
{ command: 'new.freetable', hideDisabled: true },
{ command: 'new.shell', hideDisabled: true },
{ command: 'new.jsonl', hideDisabled: true },
@@ -18,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 },
],
},
{
@@ -66,6 +70,7 @@ module.exports = ({ editMenu }) => [
{ command: 'app.toggleDevTools', hideDisabled: true },
{ command: 'app.toggleFullScreen', hideDisabled: true },
{ command: 'app.minimize', hideDisabled: true },
{ command: 'toggle.sidebar' },
{ divider: true },
{ command: 'theme.changeTheme', hideDisabled: true },
{ command: 'settings.show' },
@@ -81,6 +86,9 @@ 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 },
],
},
{
+484 -510
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,4 +8,4 @@ then
echo "$HOST_IP $HOST_DOMAIN" >> /etc/hosts
fi
node bundle.js
node bundle.js --listen-api
+4 -1
View File
@@ -5,9 +5,12 @@ let fillContent = '';
if (process.platform == 'win32') {
fillContent += `content.msnodesqlv8 = () => require('msnodesqlv8');`;
}
if (process.arch != 'arm64') {
fillContent += `content.oracledb = () => require('oracledb');`;
}
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 = {};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 166 KiB

@@ -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');
})
);
});
@@ -297,4 +297,33 @@ describe('Deploy database', () => {
expect(res.rows[0].val.toString()).toEqual('5');
})
);
test.each(engines.enginesPostgre.map(engine => [engine.label, engine]))(
'Current timestamp default value - %s',
testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(conn, driver, [
[
{
name: 't1.table.yaml',
json: {
name: 't1',
columns: [
{ name: 'id', type: 'int' },
{
name: 'val',
type: 'timestamp',
default: 'current_timestamp',
},
],
primaryKey: ['id'],
},
},
],
]);
await driver.query(conn, `insert into t1 (id) values (1)`);
const res = await driver.query(conn, ` select val from t1 where id = 1`);
expect(res.rows[0].val.toString().substring(0, 2)).toEqual('20');
})
);
});
+24 -24
View File
@@ -1,21 +1,21 @@
version: '3'
services:
postgres:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: Pwd2020Db
ports:
- 15000:5432
# postgres:
# image: postgres
# restart: always
# environment:
# POSTGRES_PASSWORD: Pwd2020Db
# ports:
# - 15000:5432
mariadb:
image: mariadb
command: --default-authentication-plugin=mysql_native_password
restart: always
ports:
- 15004:3306
environment:
- MYSQL_ROOT_PASSWORD=Pwd2020Db
# mariadb:
# image: mariadb
# command: --default-authentication-plugin=mysql_native_password
# restart: always
# ports:
# - 15004:3306
# environment:
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
# mysql:
# image: mysql:8.0.18
@@ -26,15 +26,15 @@ services:
# environment:
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
# mssql:
# image: mcr.microsoft.com/mssql/server
# restart: always
# ports:
# - 15002:1433
# environment:
# - ACCEPT_EULA=Y
# - SA_PASSWORD=Pwd2020Db
# - MSSQL_PID=Express
mssql:
image: mcr.microsoft.com/mssql/server
restart: always
ports:
- 15002:1433
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=Pwd2020Db
- MSSQL_PID=Express
# cockroachdb:
# image: cockroachdb/cockroach
+6 -2
View File
@@ -135,12 +135,16 @@ const filterLocal = [
// filter local testing
'-MySQL',
'-MariaDB',
'PostgreSQL',
'-PostgreSQL',
'-SQL Server',
'-SQLite',
'SQLite',
'-CockroachDB',
];
const enginesPostgre = engines.filter(x => x.label == 'PostgreSQL');
module.exports = process.env.CITEST
? engines.filter(x => !x.skipOnCI)
: engines.filter(x => filterLocal.find(y => x.label == y));
module.exports.enginesPostgre = enginesPostgre;
+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"
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 17.804 17.804" style="enable-background:new 0 0 17.804 17.804;" xml:space="preserve">
<g>
<g id="c98_play">
<path fill='#ccc' d="M2.067,0.043C2.21-0.028,2.372-0.008,2.493,0.085l13.312,8.503c0.094,0.078,0.154,0.191,0.154,0.313
c0,0.12-0.061,0.237-0.154,0.314L2.492,17.717c-0.07,0.057-0.162,0.087-0.25,0.087l-0.176-0.04
c-0.136-0.065-0.222-0.207-0.222-0.361V0.402C1.844,0.25,1.93,0.107,2.067,0.043z"/>
</g>
<g id="Capa_1_78_">
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 733 B

+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 17.804 17.804" style="enable-background:new 0 0 17.804 17.804;" xml:space="preserve">
<g>
<g id="c98_play">
<path fill='#444' d="M2.067,0.043C2.21-0.028,2.372-0.008,2.493,0.085l13.312,8.503c0.094,0.078,0.154,0.191,0.154,0.313
c0,0.12-0.061,0.237-0.154,0.314L2.492,17.717c-0.07,0.057-0.162,0.087-0.25,0.087l-0.176-0.04
c-0.136-0.065-0.222-0.207-0.222-0.361V0.402C1.844,0.25,1.93,0.107,2.067,0.043z"/>
</g>
<g id="Capa_1_78_">
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 733 B

+16 -6
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "5.0.2",
"version": "5.2.3",
"name": "dbgate-all",
"workspaces": [
"packages/*",
@@ -8,10 +8,18 @@
"integration-tests"
],
"scripts": {
"start:api": "yarn workspace dbgate-api start",
"start:app": "cd app && yarn start",
"start:api:portal": "yarn workspace dbgate-api start:portal",
"start:api:singledb": "yarn workspace dbgate-api start:singledb",
"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 | 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",
@@ -32,6 +40,7 @@
"start:app:local": "cd app && yarn start:local",
"setCurrentVersion": "node setCurrentVersion",
"generatePadFile": "node generatePadFile",
"adjustPackageJson": "node adjustPackageJson",
"fillNativeModules": "node fillNativeModules",
"fillNativeModulesElectron": "node fillNativeModules --electron",
"fillPackagedPlugins": "node fillPackagedPlugins",
@@ -50,7 +59,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",
+2
View File
@@ -1,4 +1,6 @@
DEVMODE=1
SHELL_SCRIPTING=1
# PERMISSIONS=~widgets/app,~widgets/plugins
# DISABLE_SHELL=1
# HIDE_APP_EDITOR=1
+1
View File
@@ -0,0 +1 @@
.env
+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
View File
@@ -48,4 +48,15 @@ PASSWORD_relational=relational
ENGINE_relational=mariadb@dbgate-plugin-mysql
READONLY_relational=1
# SETTINGS_dataGrid.showHintColumns=1
# docker run -p 3000:3000 -e CONNECTIONS=mongo -e URL_mongo=mongodb://localhost:27017 -e ENGINE_mongo=mongo@dbgate-plugin-mongo -e LABEL_mongo=mongo dbgate/dbgate:beta
# LOGINS=x,y
# LOGIN_PASSWORD_x=x
# LOGIN_PASSWORD_y=LOGIN_PASSWORD_y
# LOGIN_PERMISSIONS_x=~*
# LOGIN_PERMISSIONS_y=~*
# PERMISSIONS=~*,connections/relational
# PERMISSIONS=~*
+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"}]
+20 -9
View File
@@ -17,6 +17,7 @@
"dbgate"
],
"dependencies": {
"activedirectory2": "^2.1.0",
"async-lock": "^1.2.4",
"axios": "^0.21.1",
"body-parser": "^1.19.0",
@@ -25,15 +26,17 @@
"compare-versions": "^3.6.0",
"cors": "^2.8.5",
"cross-env": "^6.0.3",
"dbgate-query-splitter": "^4.9.0",
"dbgate-query-splitter": "^4.9.3",
"dbgate-sqltree": "^5.0.0-alpha.1",
"dbgate-tools": "^5.0.0-alpha.1",
"debug": "^4.3.4",
"diff": "^5.0.0",
"diff2html": "^3.4.13",
"eslint": "^6.8.0",
"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",
@@ -41,22 +44,29 @@
"is-electron": "^2.2.1",
"js-yaml": "^4.1.0",
"json-stable-stringify": "^1.0.1",
"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",
"node-ssh-forward": "^0.7.2",
"on-finished": "^2.4.1",
"pinomin": "^1.0.1",
"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"
},
"scripts": {
"start": "env-cmd node src/index.js",
"start:portal": "env-cmd -f env/portal/.env node src/index.js",
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js",
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db",
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test",
"start": "env-cmd 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",
"build": "webpack"
},
@@ -72,7 +82,8 @@
"webpack-cli": "^3.3.11"
},
"optionalDependencies": {
"better-sqlite3": "7.5.0",
"msnodesqlv8": "^2.4.4"
"better-sqlite3": "7.6.2",
"msnodesqlv8": "^2.6.0",
"oracledb": "^5.5.0"
}
}
+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;
}
+111 -56
View File
@@ -1,13 +1,17 @@
const fs = require('fs-extra');
const stream = require('stream');
const readline = require('readline');
const path = require('path');
const { formatWithOptions } = require('util');
const { archivedir, clearArchiveLinksCache, resolveArchiveFolder } = require('../utility/directories');
const socket = require('../utility/socket');
const JsonLinesDatastore = require('../utility/JsonLinesDatastore');
const { saveFreeTableData } = require('../utility/freeTableStorage');
const loadFilesRecursive = require('../utility/loadFilesRecursive');
const getJslFileName = require('../utility/getJslFileName');
const { getLogger } = require('dbgate-tools');
const uuidv1 = require('uuid/v1');
const dbgateApi = require('../shell');
const jsldata = require('./jsldata');
const platformInfo = require('../utility/platformInfo');
const logger = getLogger('archive');
module.exports = {
folders_meta: true,
@@ -45,45 +49,53 @@ module.exports = {
files_meta: true,
async files({ folder }) {
const dir = resolveArchiveFolder(folder);
if (!(await fs.exists(dir))) return [];
const files = await loadFilesRecursive(dir); // fs.readdir(dir);
try {
const dir = resolveArchiveFolder(folder);
if (!(await fs.exists(dir))) return [];
const files = await loadFilesRecursive(dir); // fs.readdir(dir);
function fileType(ext, type) {
return files
.filter(name => name.endsWith(ext))
.map(name => ({
name: name.slice(0, -ext.length),
label: path.parse(name.slice(0, -ext.length)).base,
type,
}));
function fileType(ext, type) {
return files
.filter(name => name.endsWith(ext))
.map(name => ({
name: name.slice(0, -ext.length),
label: path.parse(name.slice(0, -ext.length)).base,
type,
}));
}
return [
...fileType('.jsonl', 'jsonl'),
...fileType('.table.yaml', 'table.yaml'),
...fileType('.view.sql', 'view.sql'),
...fileType('.proc.sql', 'proc.sql'),
...fileType('.func.sql', 'func.sql'),
...fileType('.trigger.sql', 'trigger.sql'),
...fileType('.matview.sql', 'matview.sql'),
];
} catch (err) {
logger.error({ err }, 'Error reading archive files');
return [];
}
return [
...fileType('.jsonl', 'jsonl'),
...fileType('.table.yaml', 'table.yaml'),
...fileType('.view.sql', 'view.sql'),
...fileType('.proc.sql', 'proc.sql'),
...fileType('.func.sql', 'func.sql'),
...fileType('.trigger.sql', 'trigger.sql'),
...fileType('.matview.sql', 'matview.sql'),
];
},
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,
@@ -92,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}-${uuidv1()}.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,
@@ -100,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,
@@ -111,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;
},
+150
View File
@@ -0,0 +1,150 @@
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 logger = getLogger('auth');
const tokenSecret = uuidv1();
function shouldAuthorizeApi() {
const logins = getLogins();
return !!process.env.OAUTH_AUTH || !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH);
}
function getTokenLifetime() {
return process.env.TOKEN_LIFETIME || '1d';
}
function unauthorizedResponse(req, res, text) {
// if (req.path == getExpressPath('/config/get-settings')) {
// return res.json({});
// }
// if (req.path == getExpressPath('/connections/list')) {
// return res.json([]);
// }
return res.sendStatus(401).send(text);
}
function authMiddleware(req, res, next) {
const SKIP_AUTH_PATHS = ['/config/get', '/auth/oauth-token', '/auth/login', '/stream'];
if (!shouldAuthorizeApi()) {
return next();
}
let skipAuth = !!SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x));
const authHeader = req.headers.authorization;
if (!authHeader) {
if (skipAuth) {
return next();
}
return unauthorizedResponse(req, res, 'missing authorization header');
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, tokenSecret);
req.user = decoded;
return next();
} catch (err) {
if (skipAuth) {
return next();
}
logger.error({ err }, 'Sending invalid token error');
return unauthorizedResponse(req, res, 'invalid token');
}
}
module.exports = {
oauthToken_meta: true,
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}${scopeParam}`
);
const { access_token, refresh_token } = resp.data;
const payload = jwt.decode(access_token);
logger.info({ payload }, 'User payload returned from 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 &&
!process.env.OAUTH_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
) {
return { error: `Username ${login} not allowed to log in` };
}
if (access_token) {
return {
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
};
}
return { error: 'Token not found' };
},
login_meta: true,
async login(params) {
const { login, password } = params;
if (process.env.AD_URL) {
const adConfig = {
url: process.env.AD_URL,
baseDN: process.env.AD_BASEDN,
username: process.env.AD_USERNAME,
password: process.env.AD_PASSOWRD,
};
const ad = new AD(adConfig);
try {
const res = await ad.authenticate(login, password);
if (!res) {
return { error: 'Login failed' };
}
if (
process.env.AD_ALLOWED_LOGINS &&
!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) {
logger.error({ err }, 'Failed active directory authentization');
return {
error: err.message,
};
}
}
const logins = getLogins();
if (!logins) {
return { error: 'Logins not configured' };
}
const foundLogin = logins.find(x => x.login == login);
if (foundLogin && foundLogin.password == password) {
return {
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
};
}
return { error: 'Invalid credentials' };
},
authMiddleware,
shouldAuthorizeApi,
};
+38 -14
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,18 +28,28 @@ module.exports = {
get_meta: true,
async get(_params, req) {
const logins = getLogins();
const login = logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null;
const permissions = login ? login.permissions : 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.allowShellConnection,
allowShellScripting: platformInfo.allowShellScripting,
isDocker: platformInfo.isDocker,
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,
};
},
@@ -59,13 +69,10 @@ module.exports = {
getSettings_meta: true,
async getSettings() {
try {
return this.fillMissingSettings(
JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' }))
);
} catch (err) {
return this.fillMissingSettings({});
}
const res = await lock.acquire('settings', async () => {
return await this.loadSettings();
});
return res;
},
fillMissingSettings(value) {
@@ -76,15 +83,32 @@ module.exports = {
// res['app.useNativeMenu'] = os.platform() == 'darwin' ? true : false;
res['app.useNativeMenu'] = false;
}
for (const envVar in process.env) {
if (envVar.startsWith('SETTINGS_')) {
const key = envVar.substring('SETTINGS_'.length);
if (!res[key]) {
res[key] = process.env[envVar];
}
}
}
return res;
},
async loadSettings() {
try {
const settingsText = await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' });
return this.fillMissingSettings(JSON.parse(settingsText));
} catch (err) {
return this.fillMissingSettings({});
}
},
updateSettings_meta: true,
async updateSettings(values, req) {
if (!hasPermission(`settings/change`, req)) return false;
const res = await lock.acquire('update', async () => {
const currentValue = await this.getSettings();
const res = await lock.acquire('settings', async () => {
const currentValue = await this.loadSettings();
try {
const updated = {
...currentValue,
+110 -24
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,8 +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 = {};
@@ -48,10 +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}`],
authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined),
defaultDatabase:
process.env[`DATABASE_${id}`] ||
(process.env[`FILE_${id}`] ? getDatabaseFileLabel(process.env[`FILE_${id}`]) : null),
@@ -59,6 +69,7 @@ function getPortalCollections() {
displayName: process.env[`LABEL_${id}`],
isReadOnly: process.env[`READONLY_${id}`],
databases: process.env[`DBCONFIG_${id}`] ? safeJsonParse(process.env[`DBCONFIG_${id}`]) : null,
parent: process.env[`PARENT_${id}`] || undefined,
// SSH tunnel
useSshTunnel: process.env[`USE_SSH_${id}`],
@@ -78,13 +89,13 @@ function getPortalCollections() {
sslKeyFile: process.env[`SSL_KEY_FILE_${id}`],
sslRejectUnauthorized: process.env[`SSL_REJECT_UNAUTHORIZED_${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;
@@ -122,9 +133,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);
@@ -148,12 +160,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() {
@@ -165,21 +196,30 @@ module.exports = {
},
list_meta: true,
async list() {
return portalConnections && !platformInfo.allowShellConnection
? portalConnections.map(maskConnection)
: this.datastore.find();
async list(_params, req) {
if (portalConnections) {
if (platformInfo.allowShellConnection) return portalConnections;
return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, req));
}
return (await this.datastore.find()).filter(x => connectionHasPermission(x, req));
},
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 => {
@@ -193,6 +233,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;
@@ -215,16 +285,26 @@ module.exports = {
},
update_meta: true,
async update({ _id, values }) {
async update({ _id, values }, req) {
if (portalConnections) return;
testConnectionPermission(_id, req);
const res = await this.datastore.patch(_id, values);
socket.emitChanged('connection-list-changed');
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 }) {
async updateDatabase({ conid, database, values }, req) {
if (portalConnections) return;
testConnectionPermission(conid, req);
const conn = await this.datastore.get(conid);
let databases = (conn && conn.databases) || [];
if (databases.find(x => x.name == database)) {
@@ -240,8 +320,9 @@ module.exports = {
},
delete_meta: true,
async delete(connection) {
async delete(connection, req) {
if (portalConnections) return;
testConnectionPermission(connection, req);
const res = await this.datastore.remove(connection._id);
socket.emitChanged('connection-list-changed');
return res;
@@ -249,6 +330,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;
@@ -258,7 +343,8 @@ module.exports = {
},
get_meta: true,
async get({ conid }) {
async get({ conid }, req) {
testConnectionPermission(conid, req);
return this.getCore({ conid, mask: true });
},
@@ -12,6 +12,7 @@ const {
matchPairedObjects,
extendDatabaseInfo,
modelCompareDbDiffOptions,
getLogger,
} = require('dbgate-tools');
const { html, parse } = require('diff2html');
const { handleProcessCommunication } = require('../utility/processComm');
@@ -26,6 +27,11 @@ const generateDeploySql = require('../shell/generateDeploySql');
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 logger = getLogger('databaseConnections');
module.exports = {
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
@@ -41,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];
@@ -71,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() {},
@@ -80,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,
@@ -124,14 +140,20 @@ module.exports = {
const msgid = uuidv1();
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;
},
queryData_meta: true,
async queryData({ conid, database, sql }) {
console.log(`Processing query, conid=${conid}, database=${database}, sql=${sql}`);
async queryData({ conid, database, sql }, req) {
testConnectionPermission(conid, req);
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;
@@ -141,28 +163,32 @@ module.exports = {
},
sqlSelect_meta: true,
async sqlSelect({ conid, database, select }) {
async sqlSelect({ conid, database, select }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'sqlSelect', select });
return res;
},
runScript_meta: true,
async runScript({ conid, database, sql }) {
console.log(`Processing script, conid=${conid}, database=${database}, sql=${sql}`);
async runScript({ conid, database, sql, useTransaction }, req) {
testConnectionPermission(conid, req);
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;
},
collectionData_meta: true,
async collectionData({ conid, database, options }) {
async collectionData({ conid, database, options }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'collectionData', options });
return res.result || null;
},
async loadDataCore(msgtype, { conid, database, ...args }) {
async loadDataCore(msgtype, { conid, database, ...args }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype, ...args });
if (res.errorMessage) {
@@ -176,32 +202,38 @@ module.exports = {
},
loadKeys_meta: true,
async loadKeys({ conid, database, root, filter }) {
async loadKeys({ conid, database, root, filter }, req) {
testConnectionPermission(conid, req);
return this.loadDataCore('loadKeys', { conid, database, root, filter });
},
exportKeys_meta: true,
async exportKeys({ conid, database, options }) {
async exportKeys({ conid, database, options }, req) {
testConnectionPermission(conid, req);
return this.loadDataCore('exportKeys', { conid, database, options });
},
loadKeyInfo_meta: true,
async loadKeyInfo({ conid, database, key }) {
async loadKeyInfo({ conid, database, key }, req) {
testConnectionPermission(conid, req);
return this.loadDataCore('loadKeyInfo', { conid, database, key });
},
loadKeyTableRange_meta: true,
async loadKeyTableRange({ conid, database, key, cursor, count }) {
async loadKeyTableRange({ conid, database, key, cursor, count }, req) {
testConnectionPermission(conid, req);
return this.loadDataCore('loadKeyTableRange', { conid, database, key, cursor, count });
},
loadFieldValues_meta: true,
async loadFieldValues({ conid, database, schemaName, pureName, field, search }) {
async loadFieldValues({ conid, database, schemaName, pureName, field, search }, req) {
testConnectionPermission(conid, req);
return this.loadDataCore('loadFieldValues', { conid, database, schemaName, pureName, field, search });
},
callMethod_meta: true,
async callMethod({ conid, database, method, args }) {
async callMethod({ conid, database, method, args }, req) {
testConnectionPermission(conid, req);
return this.loadDataCore('callMethod', { conid, database, method, args });
// const opened = await this.ensureOpened(conid, database);
@@ -213,7 +245,8 @@ module.exports = {
},
updateCollection_meta: true,
async updateCollection({ conid, database, changeSet }) {
async updateCollection({ conid, database, changeSet }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'updateCollection', changeSet });
if (res.errorMessage) {
@@ -225,7 +258,14 @@ module.exports = {
},
status_meta: true,
async status({ conid, database }) {
async status({ conid, database }, req) {
if (!conid) {
return {
name: 'error',
message: 'No connection',
};
}
testConnectionPermission(conid, req);
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) {
return {
@@ -247,12 +287,14 @@ module.exports = {
},
ping_meta: true,
async ping({ conid, database }) {
async ping({ conid, database }, req) {
testConnectionPermission(conid, req);
let existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) {
existing.subprocess.send({ msgtype: 'ping' });
} else {
// @ts-ignore
existing = await this.ensureOpened(conid, database);
}
@@ -263,7 +305,8 @@ module.exports = {
},
refresh_meta: true,
async refresh({ conid, database, keepOpen }) {
async refresh({ conid, database, keepOpen }, req) {
testConnectionPermission(conid, req);
if (!keepOpen) this.close(conid, database);
await this.ensureOpened(conid, database);
@@ -271,7 +314,8 @@ module.exports = {
},
syncModel_meta: true,
async syncModel({ conid, database, isFullRefresh }) {
async syncModel({ conid, database, isFullRefresh }, req) {
testConnectionPermission(conid, req);
const conn = await this.ensureOpened(conid, database);
conn.subprocess.send({ msgtype: 'syncModel', isFullRefresh });
return { status: 'ok' };
@@ -281,7 +325,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: {
@@ -290,7 +340,7 @@ module.exports = {
},
structure: existing.structure,
};
socket.emitChanged(`database-status-changed-${conid}-${database}`);
socket.emitChanged(`database-status-changed`, { conid, database });
}
},
@@ -301,13 +351,15 @@ module.exports = {
},
disconnect_meta: true,
async disconnect({ conid, database }) {
async disconnect({ conid, database }, req) {
testConnectionPermission(conid, req);
await this.close(conid, database, true);
return { status: 'ok' };
},
structure_meta: true,
async structure({ conid, database }) {
async structure({ conid, database }, req) {
testConnectionPermission(conid, req);
if (conid == '__model') {
const model = await importDbModel(database);
return model;
@@ -324,14 +376,19 @@ module.exports = {
},
serverVersion_meta: true,
async serverVersion({ conid, database }) {
async serverVersion({ conid, database }, req) {
if (!conid) {
return null;
}
testConnectionPermission(conid, req);
if (!conid) return null;
const opened = await this.ensureOpened(conid, database);
return opened.serverVersion || null;
},
sqlPreview_meta: true,
async sqlPreview({ conid, database, objects, options }) {
async sqlPreview({ conid, database, objects, options }, req) {
testConnectionPermission(conid, req);
// wait for structure
await this.structure({ conid, database });
@@ -341,7 +398,8 @@ module.exports = {
},
exportModel_meta: true,
async exportModel({ conid, database }) {
async exportModel({ conid, database }, req) {
testConnectionPermission(conid, req);
const archiveFolder = await archive.getNewArchiveFolder({ database });
await fs.mkdir(path.join(archivedir(), archiveFolder));
const model = await this.structure({ conid, database });
@@ -351,7 +409,8 @@ module.exports = {
},
generateDeploySql_meta: true,
async generateDeploySql({ conid, database, archiveFolder }) {
async generateDeploySql({ conid, database, archiveFolder }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, {
msgtype: 'generateDeploySql',
+14 -7
View File
@@ -8,6 +8,7 @@ const socket = require('../utility/socket');
const scheduler = require('./scheduler');
const getDiagramExport = require('../utility/getDiagramExport');
const apps = require('./apps');
const getMapExport = require('../utility/getMapExport');
function serialize(format, data) {
if (format == 'text') return data;
@@ -48,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;
},
@@ -57,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;
},
@@ -65,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;
@@ -75,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;
},
@@ -111,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;
@@ -128,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();
@@ -187,6 +188,12 @@ module.exports = {
return true;
},
exportMap_meta: true,
async exportMap({ filePath, geoJson }) {
await fs.writeFile(filePath, getMapExport(geoJson));
return true;
},
exportDiagram_meta: true,
async exportDiagram({ filePath, html, css, themeType, themeClassName }) {
await fs.writeFile(filePath, getDiagramExport(html, css, themeType, themeClassName));
+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),
})),
};
},
};
+32 -18
View File
@@ -6,10 +6,17 @@ 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 => {
+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);
},
+112 -18
View File
@@ -1,29 +1,37 @@
const connections = require('./connections');
const socket = require('../utility/socket');
const { fork } = require('child_process');
const uuidv1 = require('uuid/v1');
const _ = require('lodash');
const AsyncLock = require('async-lock');
const { handleProcessCommunication } = require('../utility/processComm');
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);
@@ -32,19 +40,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,
@@ -79,7 +105,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,
@@ -90,19 +122,23 @@ module.exports = {
},
disconnect_meta: true,
async disconnect({ conid }) {
async disconnect({ conid }, req) {
testConnectionPermission(conid, req);
await this.close(conid, true);
return { status: 'ok' };
},
listDatabases_meta: true,
async listDatabases({ conid }) {
async listDatabases({ conid }, req) {
if (!conid) return [];
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
return opened.databases;
},
version_meta: true,
async version({ conid }) {
async version({ conid }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
return opened.version;
},
@@ -116,23 +152,29 @@ module.exports = {
},
ping_meta: true,
async ping({ connections }) {
async ping({ conidArray }) {
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 calling ping');
this.close(conid);
}
})
);
return { status: 'ok' };
},
refresh_meta: true,
async refresh({ conid, keepOpen }) {
async refresh({ conid, keepOpen }, req) {
testConnectionPermission(conid, req);
if (!keepOpen) this.close(conid);
await this.ensureOpened(conid);
@@ -140,10 +182,62 @@ module.exports = {
},
createDatabase_meta: true,
async createDatabase({ conid, name }) {
async createDatabase({ conid, name }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
if (opened.connection.isReadOnly) return false;
opened.subprocess.send({ msgtype: 'createDatabase', name });
return { status: 'ok' };
},
dropDatabase_meta: true,
async dropDatabase({ conid, name }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
if (opened.connection.isReadOnly) return false;
opened.subprocess.send({ msgtype: 'dropDatabase', name });
return { status: 'ok' };
},
sendRequest(conn, message) {
const msgid = uuidv1();
const promise = new Promise((resolve, reject) => {
this.requests[msgid] = [resolve, reject];
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 });
},
};
+68 -9
View File
@@ -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[]} */
@@ -82,13 +87,20 @@ module.exports = {
async create({ conid, database }) {
const sesid = uuidv1();
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,
@@ -103,7 +115,18 @@ module.exports = {
if (handleProcessCommunication(message, subprocess)) return;
this[`handle_${msgtype}`](sesid, message);
});
subprocess.send({ msgtype: 'connect', ...connection, database });
subprocess.on('exit', () => {
this.opened = this.opened.filter(x => x.sesid != sesid);
this.dispatchMessage(sesid, 'Query session closed');
socket.emit(`session-closed-${sesid}`);
});
subprocess.send({
msgtype: 'connect',
...connection,
database,
globalSettings: await config.getSettings(),
});
return _.pick(newOpened, ['conid', 'database', 'sesid']);
},
@@ -114,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 });
@@ -144,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);
@@ -165,6 +213,17 @@ module.exports = {
return { state: 'ok' };
},
ping_meta: true,
async ping({ sesid }) {
const session = this.opened.find(x => x.sesid == sesid);
if (!session) {
throw new Error('Invalid session');
}
session.subprocess.send({ msgtype: 'ping' });
return { state: 'ok' };
},
// runCommand_meta: true,
// async runCommand({ conid, database, sql }) {
// console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`);
+3 -1
View File
@@ -1,6 +1,8 @@
const path = require('path');
const { uploadsdir } = require('../utility/directories');
const uuidv1 = require('uuid/v1');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('uploads');
module.exports = {
upload_meta: {
@@ -15,7 +17,7 @@ module.exports = {
}
const uploadName = uuidv1();
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({
+98 -3
View File
@@ -1,5 +1,96 @@
const shell = require('./shell');
const { setLogger, 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 logger = createLogger({
base: { pid: process.pid },
targets: [
{
type: 'console',
// @ts-ignore
level: process.env.CONSOLE_LOG_LEVEL || process.env.LOG_LEVEL || 'info',
},
{
type: 'stream',
// @ts-ignore
level: process.env.FILE_LOG_LEVEL || process.env.LOG_LEVEL || 'info',
stream: fs.createWriteStream(logsFilePath, { flags: 'a' }),
},
],
});
// 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 },
// },
// ],
// },
// });
setLogger(logger);
}
if (processArgs.listenApi) {
configureLogger();
}
const shell = require('./shell');
const dbgateTools = require('dbgate-tools');
global['DBGATE_TOOLS'] = dbgateTools;
@@ -8,13 +99,17 @@ if (processArgs.startProcess) {
const proc = require('./proc');
const module = proc[processArgs.startProcess];
module.start();
} else if (!processArgs.checkParent && !global['API_PACKAGE']) {
const main = require('./main');
}
if (processArgs.listenApi) {
const main = require('./main');
main.start();
}
module.exports = {
...shell,
getLogger,
configureLogger,
// loadLogsContent,
getMainModule: () => require('./main'),
};
+37 -21
View File
@@ -20,17 +20,22 @@ const jsldata = require('./controllers/jsldata');
const config = require('./controllers/config');
const archive = require('./controllers/archive');
const apps = require('./controllers/apps');
const auth = require('./controllers/auth');
const uploads = require('./controllers/uploads');
const plugins = require('./controllers/plugins');
const files = require('./controllers/files');
const scheduler = require('./controllers/scheduler');
const queryHistory = require('./controllers/queryHistory');
const onFinished = require('on-finished');
const { rundir } = require('./utility/directories');
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);
@@ -40,7 +45,7 @@ function start() {
const server = http.createServer(app);
const logins = getLogins();
if (logins) {
if (logins && process.env.BASIC_AUTH) {
app.use(
basicAuth({
users: _.fromPairs(logins.map(x => [x.login, x.password])),
@@ -52,6 +57,25 @@ 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) {
res.set({
'Cache-Control': 'no-cache',
@@ -63,7 +87,10 @@ function start() {
// Tell the client to retry every 10 seconds if connectivity is lost
res.write('retry: 10000\n\n');
socket.setSseResponse(res);
socket.addSseResponse(res);
onFinished(req, () => {
socket.removeSseResponse(res);
});
});
app.use(bodyParser.json({ limit: '50mb' }));
@@ -84,14 +111,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
@@ -99,35 +122,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);
}
@@ -153,6 +168,7 @@ function useAllControllers(app, electron) {
useController(app, electron, '/scheduler', scheduler);
useController(app, electron, '/query-history', queryHistory);
useController(app, electron, '/apps', apps);
useController(app, electron, '/auth', auth);
}
function setElectronSender(electronSender) {
@@ -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,27 +158,28 @@ function resolveAnalysedPromises() {
afterAnalyseCallbacks = [];
}
async function handleRunScript({ msgid, sql }) {
async function handleRunScript({ msgid, sql, useTransaction }, skipReadonlyCheck = false) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
try {
ensureExecuteCustomScript(driver);
await driver.script(systemConnection, sql);
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
await driver.script(systemConnection, sql, { useTransaction });
process.send({ msgtype: 'response', msgid });
} catch (err) {
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
}
}
async function handleQueryData({ msgid, sql }) {
async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
try {
ensureExecuteCustomScript(driver);
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
// console.log(sql);
const res = await driver.query(systemConnection, sql);
process.send({ msgtype: 'response', msgid, ...res });
} catch (err) {
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
process.send({ msgtype: 'response', msgid, errorMessage: err.message || 'Error executing SQL script' });
}
}
@@ -184,7 +187,7 @@ async function handleSqlSelect({ msgid, select }) {
const driver = requireEngineDriver(storedConnection);
const dmp = driver.createDumper();
dumpSqlSelect(dmp, select);
return handleQueryData({ msgid, sql: dmp.s });
return handleQueryData({ msgid, sql: dmp.s }, true);
}
async function handleDriverDataCore(msgid, callMethod) {
@@ -268,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);
}
@@ -334,19 +337,19 @@ function start() {
setInterval(() => {
const time = new Date().getTime();
if (time - lastPing > 120 * 1000) {
console.log('Database connection not alive, exiting');
if (time - lastPing > 40 * 1000) {
logger.info('Database connection not alive, exiting');
process.exit(0);
}
}, 60 * 1000);
}, 10 * 1000);
process.on('message', async message => {
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,16 +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 { decryptConnection } = require('../utility/crypting');
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);
@@ -75,28 +76,64 @@ 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() {
lastPing = new Date().getTime();
}
async function handleCreateDatabase({ name }) {
async function handleDatabaseOp(op, { name }) {
const driver = requireEngineDriver(storedConnection);
systemConnection = await connectUtility(driver, storedConnection, 'app');
console.log(`RUNNING SCRIPT: CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
if (driver.createDatabase) {
await driver.createDatabase(systemConnection, name);
if (driver[op]) {
await driver[op](systemConnection, name);
} else {
await driver.query(systemConnection, `CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
const dmp = driver.createDumper();
dmp[op](name);
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,
createDatabase: handleCreateDatabase,
serverSummary: handleServerSummary,
summaryCommand: handleSummaryCommand,
createDatabase: props => handleDatabaseOp('createDatabase', props),
dropDatabase: props => handleDatabaseOp('dropDatabase', props),
};
async function handleMessage({ msgtype, ...other }) {
@@ -109,11 +146,11 @@ function start() {
setInterval(() => {
const time = new Date().getTime();
if (time - lastPing > 120 * 1000) {
console.log('Server connection not alive, exiting');
if (time - lastPing > 40 * 1000) {
logger.info('Server connection not alive, exiting');
process.exit(0);
}
}, 60 * 1000);
}, 10 * 1000);
process.on('message', async message => {
if (handleProcessCommunication(message)) return;
+106 -14
View File
@@ -10,11 +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() {
@@ -101,8 +108,9 @@ class TableWriter {
}
class StreamHandler {
constructor(resultIndexHolder, resolve) {
constructor(resultIndexHolder, resolve, startLine) {
this.recordset = this.recordset.bind(this);
this.startLine = startLine;
this.row = this.row.bind(this);
// this.error = this.error.bind(this);
this.done = this.done.bind(this);
@@ -155,14 +163,21 @@ class StreamHandler {
this.resolve();
}
info(info) {
if (info && info.line != null) {
info = {
...info,
line: this.startLine + info.line,
};
}
process.send({ msgtype: 'info', info });
}
}
function handleStream(driver, resultIndexHolder, sql) {
function handleStream(driver, resultIndexHolder, sqlItem) {
return new Promise((resolve, reject) => {
const handler = new StreamHandler(resultIndexHolder, resolve);
driver.stream(systemConnection, sql, handler);
const start = sqlItem.trimStart || sqlItem.start;
const handler = new StreamHandler(resultIndexHolder, resolve, start && start.line);
driver.stream(systemConnection, sqlItem.text, handler);
});
}
@@ -201,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);
@@ -218,20 +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'))) {
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);
@@ -260,10 +316,17 @@ async function handleExecuteReader({ jslid, sql, fileName }) {
});
}
function handlePing() {
lastPing = new Date().getTime();
}
const messageHandlers = {
connect: handleConnect,
executeQuery: handleExecuteQuery,
executeReader: handleExecuteReader,
startProfiler: handleStartProfiler,
stopProfiler: handleStopProfiler,
ping: handlePing,
// cancel: handleCancel,
};
@@ -274,6 +337,35 @@ async function handleMessage({ msgtype, ...other }) {
function start() {
childProcessChecker();
lastPing = new Date().getTime();
setInterval(() => {
const time = new Date().getTime();
if (time - lastPing > 25 * 1000) {
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);
process.on('message', async message => {
if (handleProcessCommunication(message)) return;
try {
+6 -1
View File
@@ -1,8 +1,11 @@
const fs = require('fs-extra');
const platformInfo = require('../utility/platformInfo');
const childProcessChecker = require('../utility/childProcessChecker');
const { SSHConnection } = require('node-ssh-forward');
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,6 +38,8 @@ async function handleStart({ connection, tunnelConfig }) {
tunnelConfig,
});
} catch (err) {
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;
+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;
+6 -3
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,16 +41,16 @@ 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);
const fileStream = fs.createReadStream(downloadedFile, 'utf-8');
const splittedStream = splitQueryStream(fileStream, driver.getQuerySplitterOptions());
const splittedStream = splitQueryStream(fileStream, driver.getQuerySplitterOptions('script'));
const importStream = new ImportStream(pool, driver);
// @ts-ignore
splittedStream.pipe(importStream);
+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;
+7 -3
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,13 +16,15 @@ 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.`);
return queryType == 'json' ? await driver.readJsonQuery(pool, query) : await driver.readQuery(pool, query || sql);
logger.info(`Connected.`);
const reader =
queryType == 'json' ? await driver.readJsonQuery(pool, query) : await driver.readQuery(pool, query || sql);
return reader;
}
module.exports = queryReader;
+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);
}
+29 -9
View File
@@ -2,6 +2,9 @@ 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
@@ -60,7 +70,12 @@ class DatastoreProxy {
const msgid = uuidv1();
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;
}
@@ -69,7 +84,12 @@ class DatastoreProxy {
const msgid = uuidv1();
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;
}
@@ -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 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 uuidv1 = require('uuid/v1');
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(), uuidv1());
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;
+251
View File
@@ -0,0 +1,251 @@
/*
* Copyright 2018 Stocard GmbH.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { Client } = require('ssh2');
const net = require('net');
const fs = require('fs');
const os = require('os');
const path = require('path');
const debug = require('debug');
// interface Options {
// username?: string;
// password?: string;
// privateKey?: string | Buffer;
// agentForward?: boolean;
// bastionHost?: string;
// passphrase?: string;
// endPort?: number;
// endHost: string;
// agentSocket?: string;
// skipAutoPrivateKey?: boolean;
// noReadline?: boolean;
// }
// interface ForwardingOptions {
// fromPort: number;
// toPort: number;
// toHost?: string;
// }
class SSHConnection {
constructor(options) {
this.options = options;
this.debug = debug('ssh');
this.connections = [];
this.isWindows = process.platform === 'win32';
if (!options.username) {
this.options.username = process.env['SSH_USERNAME'] || process.env['USER'];
}
if (!options.endPort) {
this.options.endPort = 22;
}
if (!options.privateKey && !options.agentForward && !options.skipAutoPrivateKey) {
const defaultFilePath = path.join(os.homedir(), '.ssh', 'id_rsa');
if (fs.existsSync(defaultFilePath)) {
this.options.privateKey = fs.readFileSync(defaultFilePath);
}
}
}
async shutdown() {
this.debug('Shutdown connections');
for (const connection of this.connections) {
connection.removeAllListeners();
connection.end();
}
return new Promise(resolve => {
if (this.server) {
this.server.close(resolve);
}
return resolve();
});
}
async tty() {
const connection = await this.establish();
this.debug('Opening tty');
await this.shell(connection);
}
async executeCommand(command) {
const connection = await this.establish();
this.debug('Executing command "%s"', command);
await this.shell(connection, command);
}
async shell(connection, command) {
return new Promise((resolve, reject) => {
connection.shell((err, stream) => {
if (err) {
return reject(err);
}
stream
.on('close', async () => {
stream.end();
process.stdin.unpipe(stream);
process.stdin.destroy();
connection.end();
await this.shutdown();
return resolve();
})
.stderr.on('data', data => {
return reject(data);
});
stream.pipe(process.stdout);
if (command) {
stream.end(`${command}\nexit\n`);
} else {
process.stdin.pipe(stream);
}
});
});
}
async establish() {
let connection;
if (this.options.bastionHost) {
connection = await this.connectViaBastion(this.options.bastionHost);
} else {
connection = await this.connect(this.options.endHost);
}
return connection;
}
async connectViaBastion(bastionHost) {
this.debug('Connecting to bastion host "%s"', bastionHost);
const connectionToBastion = await this.connect(bastionHost);
return new Promise((resolve, reject) => {
connectionToBastion.forwardOut(
'127.0.0.1',
22,
this.options.endHost,
this.options.endPort || 22,
async (err, stream) => {
if (err) {
return reject(err);
}
const connection = await this.connect(this.options.endHost, stream);
return resolve(connection);
}
);
});
}
async connect(host, stream) {
this.debug('Connecting to "%s"', host);
const connection = new Client();
return new Promise(async (resolve, reject) => {
const options = {
host,
port: this.options.endPort,
username: this.options.username,
password: this.options.password,
privateKey: this.options.privateKey,
};
if (this.options.agentForward) {
options['agentForward'] = true;
// see https://github.com/mscdex/ssh2#client for agents on Windows
// guaranteed to give the ssh agent sock if the agent is running (posix)
let agentDefault = process.env['SSH_AUTH_SOCK'];
if (this.isWindows) {
// null or undefined
if (agentDefault == null) {
agentDefault = 'pageant';
}
}
const agentSock = this.options.agentSocket ? this.options.agentSocket : agentDefault;
if (agentSock == null) {
throw new Error('SSH Agent Socket is not provided, or is not set in the SSH_AUTH_SOCK env variable');
}
options['agent'] = agentSock;
}
if (stream) {
options['sock'] = stream;
}
// PPK private keys can be encrypted, but won't contain the word 'encrypted'
// in fact they always contain a `encryption` header, so we can't do a simple check
options['passphrase'] = this.options.passphrase;
const looksEncrypted = this.options.privateKey
? this.options.privateKey.toString().toLowerCase().includes('encrypted')
: false;
if (looksEncrypted && !options['passphrase'] && !this.options.noReadline) {
// options['passphrase'] = await this.getPassphrase();
}
connection.on('ready', () => {
this.connections.push(connection);
return resolve(connection);
});
connection.on('error', error => {
reject(error);
});
try {
connection.connect(options);
} catch (error) {
reject(error);
}
});
}
// private async getPassphrase() {
// return new Promise(resolve => {
// const rl = readline.createInterface({
// input: process.stdin,
// output: process.stdout,
// });
// rl.question('Please type in the passphrase for your private key: ', answer => {
// return resolve(answer);
// });
// });
// }
async forward(options) {
const connection = await this.establish();
return new Promise((resolve, reject) => {
this.server = net
.createServer(socket => {
this.debug(
'Forwarding connection from "localhost:%d" to "%s:%d"',
options.fromPort,
options.toHost,
options.toPort
);
connection.forwardOut(
'localhost',
options.fromPort,
options.toHost || 'localhost',
options.toPort,
(error, stream) => {
if (error) {
return reject(error);
}
socket.pipe(stream);
stream.pipe(socket);
}
);
})
.listen(options.fromPort, 'localhost', () => {
return resolve();
});
});
}
}
module.exports = { SSHConnection };
@@ -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 });
+3 -3
View File
@@ -1,8 +1,5 @@
const { SSHConnection } = require('node-ssh-forward');
const portfinder = require('portfinder');
const fs = require('fs-extra');
const { decryptConnection } = require('./crypting');
const { getSshTunnel } = require('./sshTunnel');
const { getSshTunnelProxy } = require('./sshTunnelProxy');
const platformInfo = require('../utility/platformInfo');
const connections = require('../controllers/connections');
@@ -65,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,
};
+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,
};
+77
View File
@@ -0,0 +1,77 @@
const getMapExport = (geoJson) => {
return `<html>
<meta charset='utf-8'>
<head>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css"
integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"
integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
crossorigin=""></script>
<script>
function createMap() {
map = leaflet.map('map').setView([50, 15], 13);
leaflet
.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '<a href="https://dbgate.org" title="Exported from DbGate">DbGate</a> | © OpenStreetMap',
})
.addTo(map);
const geoJsonObj = leaflet
.geoJSON(${JSON.stringify(geoJson)}, {
style: function () {
return {
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4,
};
},
pointToLayer: (feature, latlng) => {
return leaflet.circleMarker(latlng, {
radius: 7,
weight: 2,
fillColor: '#ff0000',
color: '#ff0000',
opacity: 0.9,
fillOpacity: 0.9,
});
},
onEachFeature: (feature, layer) => {
// does this feature have a property named popupContent?
if (feature.properties && feature.properties.popupContent) {
layer.bindPopup(feature.properties.popupContent);
layer.bindTooltip(feature.properties.popupContent);
}
},
})
.addTo(map);
map.fitBounds(geoJsonObj.getBounds());
}
</script>
<style>
#map {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
</style>
</head>
<body onload='createMap()'>
<div id='map'></div>
</body>
</html>`;
};
module.exports = getMapExport;
+31 -3
View File
@@ -4,12 +4,21 @@ const _ = require('lodash');
const userPermissions = {};
function hasPermission(tested, req) {
if (!req) {
// request object not available, allow all
return true;
}
const { user } = (req && req.auth) || {};
const key = user || '';
const logins = getLogins();
if (!userPermissions[key] && logins) {
const login = logins.find(x => x.login == user);
userPermissions[key] = compilePermissions(login ? login.permissions : null);
if (!userPermissions[key]) {
if (logins) {
const login = logins.find(x => x.login == user);
userPermissions[key] = compilePermissions(login ? login.permissions : null);
} else {
userPermissions[key] = compilePermissions(process.env.PERMISSIONS);
}
}
return testPermission(tested, userPermissions[key]);
}
@@ -50,7 +59,26 @@ function getLogins() {
return loginsCache;
}
function connectionHasPermission(connection, req) {
if (!connection) {
return true;
}
if (_.isString(connection)) {
return hasPermission(`connections/${connection}`, req);
} else {
return hasPermission(`connections/${connection._id}`, req);
}
}
function testConnectionPermission(connection, req) {
if (!connectionHasPermission(connection, req)) {
throw new Error('Connection permission not granted');
}
}
module.exports = {
hasPermission,
getLogins,
connectionHasPermission,
testConnectionPermission,
};
+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: !!process.env.SHELL_CONNECTION || !!isElectron(),
allowShellScripting: !!process.env.SHELL_SCRIPTING || !!isElectron(),
allowShellConnection: !processArgs.listenApiChild || !!process.env.SHELL_CONNECTION || !!isElectron(),
allowShellScripting: !processArgs.listenApiChild || !!process.env.SHELL_SCRIPTING || !!isElectron(),
defaultKeyfile: path.join(os.homedir(), '.ssh/id_rsa'),
};
+9
View File
@@ -11,6 +11,9 @@ 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;
function getPassArgs() {
const res = [];
@@ -20,6 +23,9 @@ function getPassArgs() {
if (global['PLUGINS_DIR']) {
res.push('--plugins-dir', global['PLUGINS_DIR']);
}
if (listenApiChild) {
res.push('listen-api-child');
}
return res;
}
@@ -30,4 +36,7 @@ module.exports = {
getPassArgs,
pluginsDir,
workspaceDir,
listenApi,
listenApiChild,
processDisplayName,
};
@@ -16,7 +16,9 @@ function requireEngineDriver(connection) {
if (engine.includes('@')) {
const [shortName, packageName] = engine.split('@');
const plugin = requirePlugin(packageName);
return plugin.drivers.find(x => x.engine == engine);
if (plugin.drivers) {
return plugin.drivers.find(x => x.engine == engine);
}
}
throw new Error(`Could not find engine driver ${engine}`);
}
@@ -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;
+23 -25
View File
@@ -1,41 +1,39 @@
let sseResponse = null;
const _ = require('lodash');
const stableStringify = require('json-stable-stringify');
const sseResponses = [];
let electronSender = null;
let init = [];
let pingConfigured = false;
module.exports = {
setSseResponse(value) {
sseResponse = value;
setInterval(() => this.emit('ping'), 29 * 1000);
ensurePing() {
if (!pingConfigured) {
setInterval(() => this.emit('ping'), 29 * 1000);
pingConfigured = true;
}
},
addSseResponse(value) {
sseResponses.push(value);
this.ensurePing();
},
removeSseResponse(value) {
_.remove(sseResponses, x => x == value);
},
setElectronSender(value) {
electronSender = value;
this.ensurePing();
},
emit(message, data) {
if (electronSender) {
if (init.length > 0) {
for (const item of init) {
electronSender.send(item.message, item.data == null ? null : item.data);
}
init = [];
}
electronSender.send(message, data == null ? null : data);
} else if (sseResponse) {
if (init.length > 0) {
for (const item of init) {
sseResponse.write(
`event: ${item.message}\ndata: ${JSON.stringify(item.data == null ? null : item.data)}\n\n`
);
}
init = [];
}
sseResponse.write(`event: ${message}\ndata: ${JSON.stringify(data == null ? null : data)}\n\n`);
} else {
init.push([{ message, data }]);
}
for (const res of sseResponses) {
res.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);
},
};
+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}`
);
+7 -1
View File
@@ -1,11 +1,17 @@
const { getLogger } = require('dbgate-tools');
const uuidv1 = require('uuid/v1');
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) {
+24 -10
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 };
}
});
@@ -47,7 +57,6 @@ module.exports = function useController(app, electron, route, controller) {
let method = 'post';
let raw = false;
let rawParams = false;
// if (_.isString(meta)) {
// method = meta;
@@ -55,7 +64,6 @@ module.exports = function useController(app, electron, route, controller) {
if (_.isPlainObject(meta)) {
method = meta.method;
raw = meta.raw;
rawParams = meta.rawParams;
}
if (raw) {
@@ -67,13 +75,19 @@ module.exports = function useController(app, electron, route, controller) {
// controller._init_called = true;
// }
try {
let params = [{ ...req.body, ...req.query }, req];
if (rawParams) params = [req, res];
const data = await controller[key](...params);
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 });
}
}
});
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['js'],
};
+5
View File
@@ -5,6 +5,8 @@
"typings": "lib/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest",
"test:ci": "jest --json --outputFile=result.json --testLocationInResults",
"start": "tsc --watch"
},
"files": [
@@ -12,11 +14,14 @@
],
"dependencies": {
"dbgate-sqltree": "^5.0.0-alpha.1",
"dbgate-tools": "^5.0.0-alpha.1",
"dbgate-filterparser": "^5.0.0-alpha.1"
},
"devDependencies": {
"dbgate-types": "^5.0.0-alpha.1",
"@types/node": "^13.7.0",
"jest": "^28.1.3",
"ts-jest": "^28.0.7",
"typescript": "^4.4.3"
}
}
+72 -28
View File
@@ -1,22 +1,39 @@
import _ from 'lodash';
import { Command, Insert, Update, Delete, UpdateField, Condition, AllowIdentityInsert } from 'dbgate-sqltree';
import { NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
import {
Command,
Insert,
Update,
Delete,
UpdateField,
Condition,
AllowIdentityInsert,
Expression,
} from 'dbgate-sqltree';
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: [],
@@ -29,6 +46,7 @@ export interface ChangeSetRowDefinition {
pureName: string;
schemaName: string;
insertedRowIndex?: number;
existingRowIndex?: number;
condition?: { [column: string]: string };
}
@@ -40,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 [
@@ -57,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];
@@ -65,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];
@@ -110,6 +130,7 @@ export function setChangeSetValue(
schemaName: definition.schemaName,
condition: definition.condition,
insertedRowIndex: definition.insertedRowIndex,
existingRowIndex: definition.existingRowIndex,
fields: {
[definition.uniqueName]: value,
},
@@ -153,6 +174,7 @@ export function setChangeSetRowData(
schemaName: definition.schemaName,
condition: definition.condition,
insertedRowIndex: definition.insertedRowIndex,
existingRowIndex: definition.existingRowIndex,
document,
},
],
@@ -262,27 +284,39 @@ function changeSetInsertToSql(
}
export function extractChangeSetCondition(item: ChangeSetItem, alias?: string): Condition {
function getColumnCondition(columnName: string): Condition {
const value = item.condition[columnName];
const expr: Expression = {
exprType: 'column',
columnName,
source: {
name: {
pureName: item.pureName,
schemaName: item.schemaName,
},
alias,
},
};
if (value == null) {
return {
conditionType: 'isNull',
expr,
};
} else {
return {
conditionType: 'binary',
operator: '=',
left: expr,
right: {
exprType: 'value',
value,
},
};
}
}
return {
conditionType: 'and',
conditions: _.keys(item.condition).map(columnName => ({
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName,
source: {
name: {
pureName: item.pureName,
schemaName: item.schemaName,
},
alias,
},
},
right: {
exprType: 'value',
value: item.condition[columnName],
},
})),
conditions: _.keys(item.condition).map(columnName => getColumnCondition(columnName)),
};
}
@@ -374,6 +408,7 @@ export function deleteChangeSetRows(changeSet: ChangeSet, definition: ChangeSetR
pureName: definition.pureName,
schemaName: definition.schemaName,
condition: definition.condition,
existingRowIndex: definition.existingRowIndex,
},
],
};
@@ -381,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({});
@@ -426,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
);
}
@@ -1,6 +1,6 @@
import _ from 'lodash';
import { GridDisplay, ChangeCacheFunc, ChangeConfigFunc, DisplayColumn } from './GridDisplay';
import { EngineDriver, ViewInfo, ColumnInfo, CollectionInfo } from 'dbgate-types';
import type { EngineDriver, ViewInfo, ColumnInfo, CollectionInfo } from 'dbgate-types';
import { GridConfig, GridCache } from './GridConfig';
function getObjectKeys(obj) {
+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 { 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,
}));
}
}

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