Compare commits
1040 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 51d4bc9a75 | |||
| df3313e647 | |||
| 685c0f7dbc | |||
| 4417edf73b | |||
| faf94c1a24 | |||
| c82a877271 | |||
| 8ba85acd3c | |||
| ba65704d55 | |||
| 68f77d4ed7 | |||
| 008f6be6ac | |||
| 7324cef87a | |||
| 471fcdc131 | |||
| c2abc83f99 | |||
| a23bda7294 | |||
| a2d643305b | |||
| dd36427a80 | |||
| 9b4683ef53 | |||
| a24271f045 | |||
| f74e57bec2 | |||
| 4fb6b49b86 | |||
| 72c380cef5 | |||
| 39cdaf88f4 | |||
| 52c77031c5 | |||
| fdabe1eeaa | |||
| 4429b1d618 | |||
| 5c24774170 | |||
| 792be82acd | |||
| 6e3cd08d8a | |||
| 696d870c2f | |||
| 26471517a9 | |||
| 58233a2fd5 | |||
| 230948c4b4 | |||
| df593074c2 | |||
| 474ecb1b71 | |||
| e8e5781b59 | |||
| a042ff363e | |||
| 63bdf817c6 | |||
| 82eed3b86e | |||
| 21a24f9ba2 | |||
| 550354fe09 | |||
| e14e7efa1a | |||
| b1cf418058 | |||
| bde4127b33 | |||
| e981cb2734 | |||
| 6f4c0edb46 | |||
| 6591e45a6e | |||
| 397a6b54ff | |||
| 51555da376 | |||
| 8e6b1973c7 | |||
| 5a8627c39f | |||
| 26a46d9037 | |||
| b9a974ca27 | |||
| e4ed163723 | |||
| 4d8c62f3f5 | |||
| 4b2e28483b | |||
| 3cd070e211 | |||
| 1e818e7756 | |||
| 91efb7abda | |||
| 5659311ba2 | |||
| 232031ff5b | |||
| 0e242321ed | |||
| 83f3391b24 | |||
| 715c6f7f29 | |||
| 0fc20f7238 | |||
| c824e32f0a | |||
| 10a916bce6 | |||
| 1080147085 | |||
| f0ebe260e2 | |||
| 9ad82caac5 | |||
| 92d13dda31 | |||
| c4f322bda2 | |||
| 504bbeac52 | |||
| f090661eb9 | |||
| a526797013 | |||
| beb1a00874 | |||
| 2ddf10dfda | |||
| cdde770810 | |||
| f2f8b9ef7e | |||
| 1e1c26a16f | |||
| 4b3897c7f0 | |||
| 3e8dabc1e4 | |||
| 81eda4d0d3 | |||
| 090329593e | |||
| aa66367f86 | |||
| f7e43d6608 | |||
| c22bb6905c | |||
| 9a1a0dd0db | |||
| 695c7c2a74 | |||
| edf6819ece | |||
| 9f62a15eeb | |||
| 46df729195 | |||
| f0fc50097b | |||
| aa7d91f2c5 | |||
| 843675a056 | |||
| 129c3ed217 | |||
| 79d1b7893d | |||
| 690940d070 | |||
| 89132ac47a | |||
| 267fe7ca1f | |||
| e4d18bfc43 | |||
| 8bda4a7d2e | |||
| aea2c64703 | |||
| 80a18ec724 | |||
| a28aad9544 | |||
| ce09fcb7fd | |||
| 731c4c046c | |||
| a98d5d29ca | |||
| c75ae033ba | |||
| 067d91bf8c | |||
| 3a24bcebf8 | |||
| d6104c8375 | |||
| 1c5a90226e | |||
| dccfcfe8db | |||
| 069ccf814e | |||
| 032f94e56a | |||
| f95beaefff | |||
| 46afb1f1df | |||
| c40878f1e2 | |||
| 3017fd4ed4 | |||
| 2464935fa5 | |||
| fadd5b138c | |||
| f1212ec956 | |||
| 409278eca4 | |||
| 914d8dfe8a | |||
| 914909f5de | |||
| a403509ee7 | |||
| 866ce9aea7 | |||
| 06a2a4e57d | |||
| 4cb8f7152d | |||
| 773e446fd7 | |||
| b65d211def | |||
| 171f97ee0c | |||
| 424bd3a036 | |||
| 756cf8a099 | |||
| d109464fdd | |||
| 05bb40b780 | |||
| a830fadc7a | |||
| e7acaf3fc7 | |||
| 4f2b3c15e2 | |||
| 54b1cde5c9 | |||
| ffe1c4c7cd | |||
| fee438c6d1 | |||
| 06b48b1c63 | |||
| 9abb1ed19c | |||
| 58f4370bb6 | |||
| 86c02a76d0 | |||
| a2bc636396 | |||
| 00bf1e64a1 | |||
| a45782098a | |||
| df4230ea1d | |||
| 886e0a059e | |||
| f83c4ef799 | |||
| 66d1b4ca49 | |||
| b2f55522a8 | |||
| edc3a7409a | |||
| 09e584326f | |||
| feed0cd8db | |||
| 9d4105335f | |||
| c15261227b | |||
| de567bdd31 | |||
| 6736e8d0cf | |||
| 36ccba7988 | |||
| 75c5d30ad3 | |||
| cd10095dc0 | |||
| a64e42f1c2 | |||
| 3c3f8514da | |||
| 961d11b610 | |||
| c646a83608 | |||
| d1bdebb4ed | |||
| aa4406942f | |||
| ff044ebec8 | |||
| f5d41c89e6 | |||
| d283429f40 | |||
| 15d005be13 | |||
| f404e9956e | |||
| 2dadd1f437 | |||
| 1061d2aba2 | |||
| ff36870763 | |||
| 991176d433 | |||
| 406e3c022c | |||
| 2688c31123 | |||
| 578282c419 | |||
| 9505643a26 | |||
| 2169d1a288 | |||
| 62ebe49ac0 | |||
| a2043b237f | |||
| 7c03d31b84 | |||
| b26be02203 | |||
| a251e92598 | |||
| 1a28922a62 | |||
| 65c3ff8ec9 | |||
| 56fe578884 | |||
| 4dbb3a72d4 | |||
| 5fd7982f06 | |||
| 0ca5114b71 | |||
| d1ae7fe6e9 | |||
| 1417f53c56 | |||
| 7a606cf8ef | |||
| 622773fccd | |||
| 64ceea3779 | |||
| a588d72b26 | |||
| 7ec23ecca4 | |||
| 0c62349802 | |||
| c817bf5911 | |||
| 2d74b831c5 | |||
| 490efb065a | |||
| 6ccaa05bec | |||
| eb04f56662 | |||
| 4e97f54bd4 | |||
| 9fe689625e | |||
| fa24d47c03 | |||
| 1c73920dd5 | |||
| a77492440e | |||
| 7c4a47c4c6 | |||
| a519c78301 | |||
| d024b6f25c | |||
| 0c6e113e3e | |||
| 6ff4acc50d | |||
| fabf333664 | |||
| 29eef5619d | |||
| eb098bb33a | |||
| 36c792f44e | |||
| c7aaf06506 | |||
| 7b6a1543de | |||
| 67e287cfdf | |||
| 7802cde14d | |||
| 6b783027e5 | |||
| 1ab58a491a | |||
| b6c5f26eb4 | |||
| 6a0feb235a | |||
| 1365f2b47c | |||
| 8109dd862e | |||
| fb1c2c61fb | |||
| b514f8ae35 | |||
| 3114a05c3b | |||
| edf0637a35 | |||
| cd1267b464 | |||
| 675ef6e593 | |||
| 60bd3c157e | |||
| aceffd5681 | |||
| 83f01c52f2 | |||
| 5e207a6c16 | |||
| 10d5667c83 | |||
| d1e1b2ce9c | |||
| bb2f1399ba | |||
| 5b6f90abc5 | |||
| 1d24562ead | |||
| fb8174b3e9 | |||
| 4e194539d9 | |||
| b5e37053b8 | |||
| f3dd187df7 | |||
| b5f504f3b1 | |||
| 8df2a8a6df | |||
| dd46604069 | |||
| cc9402dd84 | |||
| be0f68fb7f | |||
| a3db8e2903 | |||
| 87c29faadd | |||
| 9bf610707e | |||
| 28a568901a | |||
| 1ba43af48d | |||
| 356b623eaf | |||
| 85c3d6fe6f | |||
| d9eb0f0976 | |||
| d61a7c54ce | |||
| cd000098f1 | |||
| e9a01a1ffd | |||
| 722789ca01 | |||
| 83ba530112 | |||
| 57fa9335d4 | |||
| 3babe95944 | |||
| aab1229220 | |||
| 7b64587f6a | |||
| 6a5157140e | |||
| 47e0173f84 | |||
| 8fe6cb1f71 | |||
| dc6eff7f9e | |||
| dad9e3ea48 | |||
| 166c2254ec | |||
| 5ab4b9ee13 | |||
| 1c87b1b994 | |||
| 072c340d5f | |||
| 5bc7a8e763 | |||
| 655dec369f | |||
| 9356ef6667 | |||
| b3308dc389 | |||
| 7cbcafb6f7 | |||
| adbb335062 | |||
| bc1c827225 | |||
| 258338cd2e | |||
| cf00af9e30 | |||
| 0f515bb762 | |||
| 5ca3a66f17 | |||
| 4f857ab1f8 | |||
| 5ed97079b1 | |||
| 16408d85f8 | |||
| cc388362d6 | |||
| 079cac6eda | |||
| a43522752c | |||
| dbcc732688 | |||
| 3f525cacc1 | |||
| 2fee308185 | |||
| 331c303e8f | |||
| 7c8d225868 | |||
| dd44798ff4 | |||
| 2dd8749bc6 | |||
| 174d7fde5c | |||
| af3d271361 | |||
| 17e83c700e | |||
| 513fe6184a | |||
| b56f11156d | |||
| 80e8b210be | |||
| d60687485b | |||
| 7a62ef0cc3 | |||
| 0e58e94153 | |||
| 8926e3bc84 | |||
| ef62948b5a | |||
| f014a4e6b4 | |||
| e589a994fa | |||
| 6fdb9cc5c9 | |||
| 11bb8faf91 | |||
| 98b26bb119 | |||
| 268c010a22 | |||
| 6dd3945724 | |||
| ba644a37b7 | |||
| e9322cc1ba | |||
| f266acb807 | |||
| 9f66c5e28a | |||
| 61d93fb9d9 | |||
| c87e38fd17 | |||
| 7eb6357c8d | |||
| 1cf02488b4 | |||
| 5249713a3c | |||
| 1bf8f38793 | |||
| e1f92fef13 | |||
| af01d95348 | |||
| d4f0882054 | |||
| cc0f05168d | |||
| 4d93be61b5 | |||
| dd230b008f | |||
| 16238f8f94 | |||
| 20570c1988 | |||
| 44dadcd256 | |||
| cf07123f51 | |||
| b56134d308 | |||
| f9f879272b | |||
| 3dfae351a6 | |||
| 822482ab4e | |||
| 451f671426 | |||
| b06d747399 | |||
| 37eeaf0cce | |||
| 5f0ee80306 | |||
| d8f25c17f7 | |||
| f6173335da | |||
| 9fdc15b8aa | |||
| 77300f2078 | |||
| 3ab887f8e9 | |||
| 5684eab3e2 | |||
| 9ce743a8d3 | |||
| 680c0057b1 | |||
| e9fffc063b | |||
| a0bc6f314c | |||
| af1bb005e5 | |||
| 34d891e935 | |||
| dcccfe11c8 | |||
| 8823cff3a1 | |||
| 18320352ff | |||
| d3292810f8 | |||
| 7cd493e518 | |||
| 6c4b56a28b | |||
| 0c795e33c3 | |||
| fd2e1e0cae | |||
| 13fd7a0aad | |||
| d5e240a701 | |||
| 2151252032 | |||
| cd175973d9 | |||
| 10789a75a8 | |||
| f775fbad29 | |||
| dbdb50f796 | |||
| 61a2002627 | |||
| 4d8e0d44d1 | |||
| e13808945c | |||
| 3aa7e6c022 | |||
| cb0a9770d2 | |||
| 4a2b33276d | |||
| fb1cbc71f2 | |||
| b8fcbbbc93 | |||
| 6b5d2114bf | |||
| 22b8b30768 | |||
| 175d85a462 | |||
| ed69c55e91 | |||
| 637184a28e | |||
| 242e24b783 | |||
| d407c72f78 | |||
| 380ab2e69e | |||
| 646a83b288 | |||
| eb80eb1afa | |||
| b0f4965fb9 | |||
| 24b5e52666 | |||
| f45c9e38cb | |||
| 78b8fc0531 | |||
| 06d6815df4 | |||
| 4566654acb | |||
| eb3a7f7253 | |||
| c340ac9112 | |||
| 5c1c4e1fa6 | |||
| bbb6c5e5f5 | |||
| 54278f6276 | |||
| a6fa116b5e | |||
| 3792f1001e | |||
| 8d1d6537a4 | |||
| 783f26b500 | |||
| 1eea117062 | |||
| d66fc06403 | |||
| fa13990189 | |||
| 45652cfc33 | |||
| 89219722a9 | |||
| b0d78250e1 | |||
| 0e92d51f3c | |||
| 535737ba72 | |||
| 2213cda1c6 | |||
| b712e3c6ae | |||
| f7f35ee306 | |||
| 973015aed8 | |||
| 2ae50ccbad | |||
| f2d8dfaf18 | |||
| b6afd24172 | |||
| 245ec58505 | |||
| 1d8264c935 | |||
| 0ff4f0d7e9 | |||
| 3bbdc56309 | |||
| 2e37788471 | |||
| 9a2631dc09 | |||
| dbfdaafb86 | |||
| cf3df9cda3 | |||
| 274fcd339b | |||
| 123e00ecbc | |||
| 34a4f9adbf | |||
| 0e819bcc45 | |||
| 570cb2d96b | |||
| c1ba758b01 | |||
| 11daa56335 | |||
| a9257cf4f8 | |||
| 1a2acd764d | |||
| 27b0af6408 | |||
| 3c63738809 | |||
| 9305e767cd | |||
| 2fddf32e54 | |||
| 469fd76f89 | |||
| 1f682d91c9 | |||
| 87c3b39ae9 | |||
| a1032138da | |||
| 9fa6155cd9 | |||
| ea77b4fc1a | |||
| 61dc9da3f0 | |||
| 9d6fe2460f | |||
| e6ac878b74 | |||
| ceea1a9047 | |||
| f7bd12881e | |||
| 4d74626e7f | |||
| a2884a580f | |||
| c8c7df3691 | |||
| 9f8ac81038 | |||
| ae8c5c0cc1 | |||
| 3dc63507ad | |||
| 6ddb8b8bf9 | |||
| 688434d25b | |||
| df2074173b | |||
| b825167687 | |||
| 621181d532 | |||
| c2b6b08105 | |||
| 8489c171f3 | |||
| 592865b16e | |||
| 012d3ec2e1 | |||
| d84adcca5d | |||
| b1ae7d53b9 | |||
| 9a5287725b | |||
| 5ccd724166 | |||
| 5e4c286427 | |||
| 70413b954b | |||
| 97cb9f2752 | |||
| 61287c5480 | |||
| 9c1c008b0d | |||
| 896cc21386 | |||
| a7a8ea053b | |||
| 07b2a3e923 | |||
| 94a91d5fed | |||
| 576fc2062c | |||
| 37a8783751 | |||
| f42d78b2fb | |||
| 522170d5c3 | |||
| 3891e7768d | |||
| 792fa75ccd | |||
| cbd3f1bae9 | |||
| cd92231769 | |||
| ecad1ae01b | |||
| dc576e6ced | |||
| 6cca81f8f1 | |||
| a9f1f19696 | |||
| 390ddac75b | |||
| e2e7c6f06b | |||
| 3a3d0683d5 | |||
| d5534dcf07 | |||
| b0a86f9f4a | |||
| b833a30148 | |||
| d9c1bbaa39 | |||
| 4b74dbbd68 | |||
| 9bcc61551c | |||
| ed71ef312d | |||
| 4fa043b7e5 | |||
| 83725dd349 | |||
| 4e25b71b06 | |||
| 607ae7c872 | |||
| 66ade5823f | |||
| ebfa0a1939 | |||
| 48b1e28ee1 | |||
| 909591404f | |||
| 7a5f2a70ad | |||
| d41b254058 | |||
| 435d06ffb9 | |||
| f4a4eb7f9e | |||
| 9910bbead3 | |||
| cb619a0fe0 | |||
| b0d61f974c | |||
| 8c051ff5f7 | |||
| f713a4b183 | |||
| 6c7e263f0e | |||
| ec3bfb4fae | |||
| 712ec8e6ee | |||
| 4da0b25f44 | |||
| 9b60b7a003 | |||
| 8ed73195c5 | |||
| c69fcd5eff | |||
| a0cefbc1ca | |||
| 5c0c145fd6 | |||
| 310774db3b | |||
| 1dd166b563 | |||
| 0497f541cb | |||
| 42333a97b8 | |||
| 494c3c8e4a | |||
| 69a87bc076 | |||
| bf4eb19ef5 | |||
| 225518df3e | |||
| 0028240552 | |||
| 44be1bdd11 | |||
| 64168577ab | |||
| e0703b1bae | |||
| a240681d6d | |||
| f5906587db | |||
| 51952ecfdd | |||
| dc0001a8cd | |||
| f19835203f | |||
| 2a2debbb88 | |||
| 23cb3a4b12 | |||
| 13d4d34453 | |||
| 2adca64159 | |||
| 18519b5519 | |||
| 4ddea55d23 | |||
| 5858061349 | |||
| d86a5c0cb4 | |||
| c712005e33 | |||
| 7e28e2257e | |||
| d0c7d591c8 | |||
| 17b73a58c8 | |||
| d765591e8c | |||
| be0aeeb2c8 | |||
| 23b345c898 | |||
| 1d85a17533 | |||
| 7a3c46b691 | |||
| d647d30258 | |||
| 8b511a0532 | |||
| ccb52e9b58 | |||
| f60e1190c8 | |||
| da5dd7ac62 | |||
| 08abec7c3e | |||
| b3839def32 | |||
| efe15bf0bb | |||
| f9e167fc7b | |||
| b35e8fcdf4 | |||
| 4bdd988682 | |||
| 94f21472be | |||
| dd33d96ef6 | |||
| 7604889b72 | |||
| 1382461bdc | |||
| 833f029ab5 | |||
| 04d39f6646 | |||
| 4de8a5b038 | |||
| 1dfdeed018 | |||
| 4892e46795 | |||
| 5aff68d313 | |||
| cdd4382266 | |||
| bbd00ac94d | |||
| dba3183c94 | |||
| a2906cca9d | |||
| 140291696b | |||
| 975643fb24 | |||
| bf9a933fb1 | |||
| 643b792069 | |||
| b4d0ccbd8c | |||
| c9bf949d02 | |||
| 074390ac11 | |||
| 45e54475d0 | |||
| f157fc77d4 | |||
| dac1110404 | |||
| da00e1c228 | |||
| 9ed1cdf4b7 | |||
| 18b7792370 | |||
| 53b6b71a29 | |||
| b2204e1d77 | |||
| e7ac7558ca | |||
| c5a7f458ba | |||
| 8ce5e68c0d | |||
| e9256fe20e | |||
| 5913788035 | |||
| 4939b74179 | |||
| 6c9c4be311 | |||
| 1454ddacb8 | |||
| 2b26779ea8 | |||
| 7781ad69cf | |||
| 1a7f06342f | |||
| 2f820d8dac | |||
| 1535dfd407 | |||
| 3fe7d652b2 | |||
| 7fc8b2901b | |||
| a56f59ceba | |||
| 2ac1072357 | |||
| 24c26a6d87 | |||
| 83693e9f2c | |||
| 59efdd735c | |||
| 41afd177ef | |||
| 0137b191b9 | |||
| 054b90c90d | |||
| a46526cbc8 | |||
| 35c42d0a83 | |||
| 6e2ecd0b05 | |||
| a98a4617ae | |||
| 1a716f0bce | |||
| 973f64f4d7 | |||
| a89c6810aa | |||
| 3d45b00a7c | |||
| f93524e24f | |||
| 9aded740ca | |||
| 66f30ff26e | |||
| 4ced94f070 | |||
| fe61e5e631 | |||
| 24b0d278fd | |||
| de5b075ba5 | |||
| 1665c014e1 | |||
| 586a06da91 | |||
| eb1eb18163 | |||
| 1983576b2f | |||
| ffbb91678c | |||
| 0293766bad | |||
| 5eda39cb62 | |||
| b7c8a60c19 | |||
| 51101d91ea | |||
| cc9acf71ce | |||
| d27f8644d8 | |||
| 347448e3c2 | |||
| 0a008a760b | |||
| 462be9e2bd | |||
| f078872c5b | |||
| fdecef7e78 | |||
| 8acafbbd6e | |||
| 5b8d70747f | |||
| c9a9c7d0f7 | |||
| 50eb5012b1 | |||
| 917c2f49a0 | |||
| 5724067974 | |||
| 428de38b41 | |||
| 9e73e16b7f | |||
| 1e91097bf2 | |||
| 61f82be9f3 | |||
| 91e1c83a91 | |||
| e8452704eb | |||
| 357fcbdf47 | |||
| 02abb4f512 | |||
| 14f71e80d3 | |||
| fdcf1c4c9a | |||
| 97e96aaba6 | |||
| 174b0efd2e | |||
| eab5f4fe5e | |||
| a910e91a91 | |||
| 3e83a69ef7 | |||
| e3b833927d | |||
| 6582c7831e | |||
| 0d2169c996 | |||
| e64d013fee | |||
| c1627b8546 | |||
| 2f74eab048 | |||
| f7a269383f | |||
| 5f9156995b | |||
| f886b8c95d | |||
| 2284264a92 | |||
| f405db7685 | |||
| 14110cb6db | |||
| 1e347f6535 | |||
| 0813f4387d | |||
| 894a864110 | |||
| 4e799885b5 | |||
| 650f9a3db9 | |||
| 6b5e33d97e | |||
| 24923db199 | |||
| 80faf0fd68 | |||
| 33b11eef38 | |||
| b6a0fe6713 | |||
| 2b68a6e1de | |||
| e124291267 | |||
| 1a16d7c69e | |||
| 6cb2616d87 | |||
| 395863da3f | |||
| fec2df9d2f | |||
| 9e3a457ef5 | |||
| 728ad21d2f | |||
| d2f18bc048 | |||
| 0ae7939f93 | |||
| 7ac0b907e2 | |||
| 1bd4b77744 | |||
| 5e4ae3208b | |||
| daf7629f5f | |||
| aeceb34d19 | |||
| 2a98918857 | |||
| ce9d583989 | |||
| 7c87baf451 | |||
| f80c6fec99 | |||
| b04af4c5e3 | |||
| fe65193189 | |||
| a75e463ef5 | |||
| 7eb59ad3a0 | |||
| 7a9f8a460f | |||
| 289752c023 | |||
| 98f2c06c21 | |||
| 530b1cade3 | |||
| 65aa8fb4e3 | |||
| 4c0f17a0b2 | |||
| e4371c526b | |||
| e39f0a1f4b | |||
| 842f77d02b | |||
| 2571e6ac7e | |||
| 1599a7ea01 | |||
| cb1d81b586 | |||
| 339588b8a0 | |||
| 1731b7e4a3 | |||
| 5418bb932c | |||
| 6154b4c780 | |||
| 3f9bd100e1 | |||
| b5c6ddce59 | |||
| 51c72efb34 | |||
| 00df20e350 | |||
| f3a7e3af74 | |||
| 04c37c2b4f | |||
| 12df0993c0 | |||
| ac3ec5c11e | |||
| b565e981e4 | |||
| f7ada698e4 | |||
| bc4c146389 | |||
| 7c80ca1374 | |||
| 8c5cc7dcc1 | |||
| 1974243ed5 | |||
| 71c9071cb8 | |||
| c28e55132a | |||
| 2b2a4debd4 | |||
| 563a35560b | |||
| cc019281d4 | |||
| 86d7d61cc5 | |||
| aff1fe0b3d | |||
| 137631b5b5 | |||
| 090ffa064d | |||
| f77cc1023b | |||
| c6dbb31748 | |||
| ae6c486db5 | |||
| 9a2c12d558 | |||
| 1ed01e9839 | |||
| 25d2c129cd | |||
| 7dc7af0cdb | |||
| 80fea3b01b | |||
| 97dc92e413 | |||
| 9051ba2ee1 | |||
| 7dcbe6c7c1 | |||
| e6fe8a6379 | |||
| b793e4131d | |||
| b737eaac13 | |||
| cb5cce2ea3 | |||
| b05d260caa | |||
| 091e91556d | |||
| 2b4120435b | |||
| c8d031e2c4 | |||
| ac07b7e1ba | |||
| bf51f45934 | |||
| fe31cfb552 | |||
| d505be09ca | |||
| 44668b8017 | |||
| 452dba7f32 | |||
| 7694864fe7 | |||
| 37d5c6fbf9 | |||
| 802f231e43 | |||
| 53c39e6a43 | |||
| 65f550023a | |||
| abe7a20960 | |||
| d686206fe2 | |||
| 27b2fdb507 | |||
| 88f522084d | |||
| 8472c8be79 | |||
| 03f8a93dd0 | |||
| 2889f79120 | |||
| 8a312181a3 | |||
| e7236de078 | |||
| 1fe2269b11 | |||
| 10ea8ca3a6 | |||
| 491d24984d | |||
| b0279dd315 | |||
| 9d6b581809 | |||
| 3f748df1ec | |||
| 7ca835765c | |||
| a76530155d | |||
| 96b82b690e | |||
| d3a40e52fc | |||
| 513b2ba42f | |||
| d23371f642 | |||
| 5ac6e12c3e | |||
| 4468c0ed3b | |||
| 06bd9bcabe | |||
| 66d15abcab | |||
| 3bdb5c0152 | |||
| f504283002 | |||
| f07c7909ef | |||
| c809f58349 | |||
| 3e91ecd141 | |||
| 857185a78b | |||
| c189c12cae | |||
| 96106e6aac | |||
| 088ca231f3 | |||
| 5395d1343b | |||
| d48c34a4a5 | |||
| 53ee1d87c2 | |||
| b5d97c8181 | |||
| 28e06166e0 | |||
| 8f1343bc42 | |||
| 2080a23b69 | |||
| d71294621b | |||
| 0f6ec420d2 | |||
| 35152a2796 | |||
| 1abfab950e | |||
| 6e6d0bb616 | |||
| 93e264e9ec | |||
| 29257f9bf9 | |||
| 8dd90ce5e4 | |||
| f2f7421971 | |||
| 8a10beef52 | |||
| df33b43e90 | |||
| 153cba3779 | |||
| 8f110355c4 | |||
| b570f873fe | |||
| c07e26c036 | |||
| 995bc6f16a | |||
| 5b4339889f | |||
| ae963d7a3b | |||
| c426cd825f | |||
| 62c2b3f5f4 | |||
| ab3584dc23 | |||
| 3a5301af6b | |||
| 55efdef181 | |||
| e9ea1edd21 | |||
| d9b91f2122 | |||
| 15da5fb95e | |||
| d563a40d0f | |||
| a4e5630f89 | |||
| c368ad8d54 | |||
| 01d1f08597 | |||
| 8c934355ab | |||
| c6e3b52bc6 | |||
| e117caf708 | |||
| 2b4d5c026e | |||
| 93a736f1f8 | |||
| 1f8ef8e20e | |||
| bef8cdbee4 | |||
| 763391e73b | |||
| b1cd16b095 | |||
| 2ee1b3105f | |||
| 51fa652851 | |||
| 755781bca6 | |||
| 1a90729f66 | |||
| 9e520e04b2 | |||
| ded0c8398c | |||
| dc31552f9e | |||
| e0376a708c | |||
| 1becb89ff0 | |||
| 4d7365828e | |||
| 29ccb09ba6 | |||
| eadd3feba0 | |||
| 93269fe314 | |||
| 34ca4c501a | |||
| 34084d0e94 | |||
| 07fc551383 | |||
| b0eed05a1a | |||
| 8228afd725 | |||
| 301222d118 | |||
| 9b741b415a | |||
| cc8438ef66 | |||
| 179bd1f6b1 | |||
| 08b7b1870c | |||
| 2c7da1d3f8 | |||
| 2a8a2c8652 | |||
| b6b75f0743 | |||
| aca92f3889 | |||
| 4672540f82 | |||
| 261cec7ec2 | |||
| de444e8485 | |||
| f4fb92be91 | |||
| 571c928234 | |||
| 2fcc4b1ff0 | |||
| c0b0ca22aa | |||
| d862762758 | |||
| 7ca8880c3c | |||
| 21ccc55e3f | |||
| 8662353071 | |||
| faedcfa64d | |||
| 7ad1796db5 | |||
| 717ec5293b | |||
| d437e171fb | |||
| 97ae7ae0d6 | |||
| e9a8f3ee84 | |||
| 1fb237417a | |||
| cd65fa16ed | |||
| 1e5a740a52 | |||
| 42badf17eb | |||
| 2ec3c2c24f | |||
| f3ab06d3b8 | |||
| 2b78a8dcae | |||
| 389ef98c66 | |||
| 75bf0e53fc | |||
| ff4dd18c1b | |||
| 4c535289a4 | |||
| d24886c73b | |||
| 9883a2982a | |||
| 24191870e8 | |||
| b9dae8928e | |||
| 7bed880003 | |||
| e2b95ad372 | |||
| 18710bc67d | |||
| 02e8bba999 | |||
| e770ca3eef | |||
| aaa72426c3 | |||
| 53e5f1378c | |||
| 773abc6dff | |||
| 8abb311623 | |||
| 2d83fb7dc4 | |||
| ae69ca9ebd | |||
| 0cb4ec54bc | |||
| d34cff234c | |||
| 50abead104 | |||
| 3b0ed7df8b | |||
| ce925337f1 | |||
| a911f5048f | |||
| 096cbc13d8 | |||
| a2cf1cd340 | |||
| 44827ea504 | |||
| 13b549ca2c | |||
| c104122a50 | |||
| 6794b79d0e | |||
| 42200ec04a | |||
| 2944d0fa39 | |||
| 34496ced0e | |||
| fa0680a8ee | |||
| f2402cadb0 | |||
| ffe82a82fa | |||
| 6e1a1edac0 | |||
| 427e25b3c0 | |||
| fca2bf8ddb | |||
| f65c15d2e5 | |||
| 343cf84a58 | |||
| e67a94b5d7 | |||
| cc1916eba3 | |||
| 0a0ce6ad98 | |||
| fd21157c2d | |||
| 8b3697e71e | |||
| f3bebcfa8f | |||
| 4c145f1f0a | |||
| cfce4e6ece | |||
| 13d778586e | |||
| 77b85fa42b | |||
| fb89c47563 | |||
| 8ffbdfa01d | |||
| 94788454a9 | |||
| a92bd1c840 | |||
| 610e9f4e60 | |||
| 6e9dace360 | |||
| 148222e239 | |||
| 5e2279cd10 | |||
| b54026b039 | |||
| 6f3076fddb | |||
| 92c336624a | |||
| 07d4b248bf | |||
| 1534099dc4 | |||
| d483869aa6 | |||
| 8bb40e991b | |||
| 5c6989bf91 | |||
| 5b503ae802 | |||
| 5feb018e22 | |||
| 97d259cd1e | |||
| fa357cf8ce | |||
| 7a0f5e171e | |||
| 24cfb23b39 | |||
| 06b6a5d3ae | |||
| 301ba1df60 | |||
| 591c105e53 | |||
| ee7198a913 | |||
| 0209780f1c | |||
| 5825e67a14 | |||
| eb0a1221e4 | |||
| ca3f1d720d | |||
| a6f6680788 | |||
| a8047560af | |||
| d63bc714eb | |||
| bce744a7bf | |||
| b2694868a9 | |||
| 3f22960849 | |||
| affb935f63 | |||
| 3df2e1a445 | |||
| 0906bad235 | |||
| 50a91516b6 | |||
| 966a6e02c1 | |||
| e279569466 | |||
| 13017b9c9d | |||
| 3d49fd7719 | |||
| c9987bdc98 | |||
| d9a9f97d3a | |||
| 79b3444121 | |||
| 8e323874b5 | |||
| 5a439f2cac | |||
| 09e3be9ec3 | |||
| 6a35107c5f | |||
| 9ecf021199 | |||
| fb61f263a6 | |||
| 4098c4e504 | |||
| 2d0e595ef7 | |||
| 36cc41ebf3 | |||
| 6ce62b9738 | |||
| 79e22bbe9c | |||
| 6ee5f5d44d |
@@ -12,7 +12,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macOS-10.15, windows-2022, ubuntu-18.04]
|
||||
os: [macos-12, windows-2022, ubuntu-22.04]
|
||||
# os: [macOS-10.15]
|
||||
|
||||
steps:
|
||||
@@ -23,10 +23,16 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js 14.x
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 18.x
|
||||
- name: yarn adjustPackageJson
|
||||
run: |
|
||||
yarn adjustPackageJson
|
||||
- name: yarn set timeout
|
||||
run: |
|
||||
yarn config set network-timeout 100000
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
@@ -40,7 +46,7 @@ jobs:
|
||||
run: |
|
||||
yarn fillPackagedPlugins
|
||||
- name: Install Snapcraft
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
uses: samuelmeuli/action-snapcraft@v1
|
||||
- name: Publish
|
||||
run: |
|
||||
@@ -48,27 +54,24 @@ 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 }}
|
||||
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
|
||||
- name: Save snap login
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
run: 'echo "$SNAPCRAFT_LOGIN" > snapcraft.login'
|
||||
shell: bash
|
||||
env:
|
||||
SNAPCRAFT_LOGIN: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
|
||||
- name: publishSnap
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
snapcraft login --with snapcraft.login
|
||||
snapcraft upload --release=beta app/dist/*.snap
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
@@ -86,6 +89,7 @@ jobs:
|
||||
|
||||
mv app/dist/*.exe artifacts/ || true
|
||||
mv app/dist/*.zip artifacts/ || true
|
||||
mv app/dist/*.tar.gz artifacts/ || true
|
||||
mv app/dist/*.AppImage artifacts/ || true
|
||||
mv app/dist/*.deb artifacts/ || true
|
||||
mv app/dist/*.snap artifacts/ || true
|
||||
|
||||
@@ -16,8 +16,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# os: [ubuntu-18.04, windows-2016]
|
||||
os: [macOS-10.15, windows-2022, ubuntu-18.04]
|
||||
# os: [ubuntu-22.04, windows-2016]
|
||||
os: [macos-12, windows-2022, ubuntu-22.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
@@ -27,10 +27,16 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js 14.x
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 18.x
|
||||
- name: yarn adjustPackageJson
|
||||
run: |
|
||||
yarn adjustPackageJson
|
||||
- name: yarn set timeout
|
||||
run: |
|
||||
yarn config set network-timeout 100000
|
||||
- name: yarn install
|
||||
run: |
|
||||
# yarn --version
|
||||
@@ -46,7 +52,7 @@ jobs:
|
||||
run: |
|
||||
yarn fillPackagedPlugins
|
||||
- name: Install Snapcraft
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
uses: samuelmeuli/action-snapcraft@v1
|
||||
- name: Publish
|
||||
run: |
|
||||
@@ -54,31 +60,28 @@ 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 }}
|
||||
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
|
||||
- name: generatePadFile
|
||||
run: |
|
||||
yarn generatePadFile
|
||||
|
||||
- name: Save snap login
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
run: 'echo "$SNAPCRAFT_LOGIN" > snapcraft.login'
|
||||
shell: bash
|
||||
env:
|
||||
SNAPCRAFT_LOGIN: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
|
||||
- name: publishSnap
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
snapcraft login --with snapcraft.login
|
||||
snapcraft upload --release=stable app/dist/*.snap
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
@@ -96,6 +99,7 @@ jobs:
|
||||
|
||||
mv app/dist/*.exe artifacts/ || true
|
||||
mv app/dist/*.zip artifacts/ || true
|
||||
mv app/dist/*.tar.gz artifacts/ || true
|
||||
mv app/dist/*.AppImage artifacts/ || true
|
||||
mv app/dist/*.deb artifacts/ || true
|
||||
mv app/dist/*.dmg artifacts/ || true
|
||||
@@ -132,12 +136,12 @@ jobs:
|
||||
mv app/dist/dbgate-pad.xml artifacts/ || true
|
||||
|
||||
- name: Copy latest-linux.yml
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
mv app/dist/latest-linux.yml artifacts/latest-linux.yml || true
|
||||
|
||||
- name: Copy latest-mac.yml
|
||||
if: matrix.os == 'macOS-10.15'
|
||||
if: matrix.os == 'macos-12'
|
||||
run: |
|
||||
mv app/dist/latest-mac.yml artifacts/latest-mac.yml || true
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -20,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04]
|
||||
os: [ubuntu-22.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
@@ -30,12 +24,43 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js 14.x
|
||||
|
||||
- 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 18.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 18.x
|
||||
- name: yarn install
|
||||
run: |
|
||||
# yarn --version
|
||||
# yarn config set network-timeout 300000
|
||||
yarn install
|
||||
- name: setCurrentVersion
|
||||
run: |
|
||||
@@ -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
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04]
|
||||
os: [ubuntu-22.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
@@ -30,10 +30,10 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js 14.x
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 18.x
|
||||
|
||||
- name: Configure NPM token
|
||||
env:
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
test-runner:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:14.18
|
||||
container: node:18
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
@@ -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:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
16.14.2
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"terminals": [
|
||||
{
|
||||
"splitTerminals": [
|
||||
{
|
||||
"name": "lib",
|
||||
"commands": ["yarn lib"]
|
||||
},
|
||||
{
|
||||
"name": "web",
|
||||
"commands": ["yarn start:web"]
|
||||
},
|
||||
{
|
||||
"name": "api",
|
||||
"commands": ["yarn start:api"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -8,6 +8,280 @@ Builds:
|
||||
- linux - application for linux
|
||||
- win - application for Windows
|
||||
|
||||
### 5.2.8
|
||||
- FIXED: file menu save and save as not working
|
||||
- FIXED: query editor on import/export screen overlaps with selector
|
||||
- FIXED: Fixed inconsistencies in max/unmaximize window buttons
|
||||
- FIXED: shortcut for select all
|
||||
- FIXED: download with auth header
|
||||
- CHANGED: Upgraded database drivers for mysql, postgres, sqlite, mssql, mongo, redis
|
||||
- CHANGED: Upgraded electron version (now using v30)
|
||||
- ADDED: Vim keyboard bindings for editor
|
||||
- FIXED: Correctly select the save folder for dump
|
||||
- ADDED: enum + set for mysql (#693)
|
||||
- FIXED: localStorageGabageCollector not working
|
||||
- FIXED: Encoding error when opening Unicode query files
|
||||
- ADDED: Add copy/paste to query tab and database list
|
||||
- ADDED: Add copy name to table list
|
||||
- FIXED: Make TabControl scrollable (#730)
|
||||
- ADDED: Add copy to column list
|
||||
- FIXED: Problems with SQLite + glibc in docker containers
|
||||
- ADDED: Button for discard/reset changes (#759)
|
||||
- FIXED: Don't show error dialog when subprocess fails, as DbGate handles this correctly (#751, #746, #542, #272)
|
||||
|
||||
|
||||
### 5.2.7
|
||||
- FIXED: fix body overflow when context menu height great than viewport #592
|
||||
- FIXED: Pass signals in entrypoint.sh #596
|
||||
- FIXED: Remove missing links to jenasoft #625
|
||||
- FIXED: add API headers on upload call #627
|
||||
- FIXED: Disabled shell scripting for NPM distribution by default
|
||||
- FIXED: Fixed data import from files #633
|
||||
- FIXED: Fixed showing GPS positions #575
|
||||
- CHANGED: Improved stability of electron client on Windows and Mac (fewer EPIPE errors)
|
||||
|
||||
### 5.2.6
|
||||
- FIXED: DbGate creates a lot of .tmp.node files in the temp directory #561
|
||||
- FIXED: Typo in datetimeoffset dataType #556
|
||||
- FIXED: SQL export is using the wrong hour formatting #537
|
||||
- FIXED: Missing toolstrip and adds up to 200% zoom to diagram view #524
|
||||
- FIXED: MongoDB password could contain special characters #560
|
||||
|
||||
### 5.2.5
|
||||
- ADDED: Split Windows #394
|
||||
- FIXED: Postgres index asc/desc #514
|
||||
- FIXED: Excel export not working since 5.2.3 #511
|
||||
- ADDED: Include macOS specific app icon #494
|
||||
- FIXED: Resizing window resets window contents #479
|
||||
- FIXED: Solved some minor problems with widget collapsing
|
||||
|
||||
### 5.2.4
|
||||
- FIXED: npm version crash (#508)
|
||||
|
||||
### 5.2.3
|
||||
- ADDED: Search entire table (multi column filter) #491
|
||||
- ADDED: OracleDB - connection to toher than default ports #496
|
||||
- CHANGED: OracleDB - status of support set to experimental
|
||||
- FIXED: OracleDB database URL - fixes: Connect to default Oracle database #489
|
||||
- ADDED: HTML, XML code highlighting for Edit cell value #485
|
||||
- FIXED: Intellisense - incorrect alias after ORDER BY clause #484
|
||||
- FIXED: Typo in SQL-Generator #481
|
||||
- ADDED: Data duplicator #480
|
||||
- FIXED: MongoDB - support for views #476
|
||||
- FIXED: "SQL:CREATE TABLE" generated SQL default value syntax errors #455
|
||||
- FIXED: Crash when right-clicking on tables #452
|
||||
- FIXED: View sort #436
|
||||
- ADDED: Arm64 version for Windows #473
|
||||
- ADDED: Sortable query results and data archive
|
||||
- CHANGED: Use transactions for saving table data
|
||||
- CHANGED: Save table structure uses transactions
|
||||
- ADDED: Table data editing - shows editing mark
|
||||
- ADDED: Editing data archive files
|
||||
- FIXED: Delete cascade options when using more than 2 tables
|
||||
- ADDED: Save to current archive commands
|
||||
- ADDED: Current archive mark is on status bar
|
||||
- FIXED: Changed package used for parsing JSONL files when browsing - fixes backend freezing
|
||||
- FIXED: SSL option for mongodb #504
|
||||
- REMOVED: Data sheet editor
|
||||
- FIXED: Creating SQLite autoincrement column
|
||||
- FIXED: Better error reporting from exports/import/dulicator
|
||||
- CHANGED: Optimalizede OracleDB analysing algorithm
|
||||
- ADDED: Mutli column filter for perspectives
|
||||
- FIXED: Fixed some scenarios using tables from different DBs
|
||||
- FIXED: Sessions with long-running queries are not killed
|
||||
|
||||
|
||||
### 5.2.2
|
||||
- FIXED: Optimalized load DB structure for PostgreSQL #451
|
||||
- ADDED: Auto-closing query connections after configurable (15 minutes default) no-activity interval #468
|
||||
- ADDED: Set application-name connection parameter (for PostgreSQL and MS SQL) for easier identifying of DbGate connections
|
||||
- ADDED: Filters supports binary IDs #467
|
||||
- FIXED: Ctrl+Tab works (switching tabs) #457
|
||||
- FIXED: Format code supports non-standard letters #450
|
||||
- ADDED: New logging system, log to file, ability to reduce logging #360 (using https://www.npmjs.com/package/pinomin)
|
||||
- FIXED: crash on Windows and Mac after system goes in suspend mode #458
|
||||
- ADDED: dbmodel standalone NPM package (https://www.npmjs.com/package/dbmodel) - deploy database via commandline tool
|
||||
|
||||
|
||||
### 5.2.1
|
||||
- FIXED: client_id param in OAuth
|
||||
- ADDED: OAuth scope parameter
|
||||
- FIXED: login page - password was not sent, when submitting by pressing ENTER
|
||||
- FIXED: Used permissions fix
|
||||
- FIXED: Export modal - fixed crash when selecting different database
|
||||
|
||||
### 5.2.0
|
||||
- ADDED: Oracle database support #380
|
||||
- ADDED: OAuth authentification #407
|
||||
- ADDED: Active directory (Windows) authentification #261
|
||||
- ADDED: Ask database credentials when login to DB
|
||||
- ADDED: Login form instead of simple authorization (simple auth is possible with special configuration)
|
||||
- FIXED: MongoDB - connection uri regression
|
||||
- ADDED: MongoDB server summary tab
|
||||
- FIXED: Broken versioned tables in MariaDB #433
|
||||
- CHANGED: Improved editor margin #422
|
||||
- ADDED: Implemented camel case search in all search boxes
|
||||
- ADDED: MonhoDB filter empty array, not empty array
|
||||
- ADDED: Maximize button reflects window state
|
||||
- ADDED: MongoDB - database profiler
|
||||
- CHANGED: Short JSON values are shown directly in grid
|
||||
- FIXED: Fixed filtering nested fields in NDJSON viewer
|
||||
- CHANGED: Improved fuzzy search after Ctrl+P #246
|
||||
- ADDED: MongoDB: Create collection backup
|
||||
- ADDED: Single database mode
|
||||
- ADDED: Perspective designer supports joins from MongoDB nested documents and arrays
|
||||
- FIXED: Perspective designer joins on MongoDB ObjectId fields
|
||||
- ADDED: Filtering columns in designer (query designer, diagram designer, perspective designer)
|
||||
- FIXED: Clone MongoDB rows without _id attribute #404
|
||||
- CHANGED: Improved cell view with GPS latitude, longitude fields
|
||||
- ADDED: SQL: ALTER VIEW and SQL:ALTER PROCEDURE scripts
|
||||
- ADDED: Ctrl+F5 refreshes data grid also with database structure #428
|
||||
- ADDED: Perspective display modes: text, force text #439
|
||||
- FIXED: Fixed file filters #445
|
||||
- ADDED: Rename, remove connection folder, memoize opened state after app restart #425
|
||||
- FIXED: Show SQLServer alter store procedure #435
|
||||
|
||||
|
||||
### 5.1.6
|
||||
- ADDED: Connection folders support #274
|
||||
- ADDED: Keyboard shortcut to hide result window and show/hide the side toolbar #406
|
||||
- ADDED: Ability to show/hide query results #406
|
||||
- FIXED: Double click does not maximize window on MacOS #416
|
||||
- FIXED: Some perspective rendering errors
|
||||
- FIXED: Connection to MongoDB via database URL info SSH tunnel is used
|
||||
- CHANGED: Updated windows code signing certificate
|
||||
- ADDED: Query session cleanup (kill query sessions, if browser tab is closed)
|
||||
- CHANGED: More strict timeouts to kill database and server connections (reduces resource consumption)
|
||||
|
||||
### 5.1.5
|
||||
- ADDED: Support perspectives for MongoDB - MongoDB query designer
|
||||
- ADDED: Show JSON content directly in the overview #395
|
||||
- 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
|
||||
|
||||
### 5.0.1
|
||||
- FIXED(app): Can't Click Sidebar Menu Item #287
|
||||
|
||||
### 5.0.0
|
||||
- CHANGED: Connection workflow, connections are opened on tabs instead of modals
|
||||
- ADDED: Posibility to connect to DB without saving connection
|
||||
- ADDED(mac): Support for SQLite on Mac M1
|
||||
- FIXED(mac): Unable to drag window on MacOS #281 #283
|
||||
- CHANGED: Renamed dbgate-data directory to .dbgate #248
|
||||
- FIXED: Exported SQL has table name undefined #277
|
||||
- ADDED: More data types in table create dialogt #285
|
||||
- ADDED(app): Open previously saved ERD diagrams #278
|
||||
- CHANGED: Better app loading progress UX
|
||||
- FIXED: Removed SSL tab on Redis connection (SSL is not supported for Redis)
|
||||
|
||||
### 4.8.8
|
||||
- CHANGED: New app icon
|
||||
- ADDED: SQL dump, SQL import - also from/to saved queries
|
||||
|
||||
@@ -22,6 +22,7 @@ DbGate is licensed under MIT license and is completely free.
|
||||
* MySQL
|
||||
* PostgreSQL
|
||||
* SQL Server
|
||||
* Oracle (experimental)
|
||||
* MongoDB
|
||||
* Redis
|
||||
* SQLite
|
||||
@@ -66,21 +67,23 @@ DbGate is licensed under MIT license and is completely free.
|
||||
* Mongo JavaScript editor, execute Mongo script (with NodeJs syntax)
|
||||
* Redis tree view, generate script from keys, run Redis script
|
||||
* Runs as application for Windows, Linux and Mac. Or in Docker container on server and in web Browser on client.
|
||||
* Import, export from/to CSV, Excel, JSON, XML
|
||||
* Import, export from/to CSV, Excel, JSON, NDJSON, XML
|
||||
* Free table editor - quick table data editing (cleanup data after import/before export, prototype tables etc.)
|
||||
* Archives - backup your data in JSON files on local filesystem (or on DbGate server, when using web application)
|
||||
* Archives - backup your data in NDJSON files on local filesystem (or on DbGate server, when using web application)
|
||||
* Charts, export chart to HTML page
|
||||
* For detailed info, how to run DbGate in docker container, visit [docker hub](https://hub.docker.com/r/dbgate/dbgate)
|
||||
* Extensible plugin architecture
|
||||
* Perspectives - nested table view over complex relational data, query designer on MongoDB databases
|
||||
|
||||
## How to contribute
|
||||
Any contributions are welcome. If you want to contribute without coding, consider following:
|
||||
|
||||
* Tell your friends about DbGate or share on social networks - when more people will use DbGate, it will grow to be better
|
||||
* Write review on [Slant.co](https://www.slant.co/improve/options/41086/~dbgate-review) or [G2](https://www.g2.com/products/dbgate/reviews)
|
||||
* Create issue, if you find problem in app, or you have idea to new feature. If issue already exists, you could leave comment on it, to prioritise most wanted issues.
|
||||
* Become a backer on [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.
|
||||
* Create issue, if you find problem in app, or you have idea to new feature. If issue already exists, you could leave comment on it, to prioritise most wanted issues
|
||||
* Create some tutorial video on [youtube](https://www.youtube.com/playlist?list=PLCo7KjCVXhr0RfUSjM9wJMsp_ShL1q61A)
|
||||
* Become a backer on [GitHub sponsors](https://github.com/sponsors/dbgate) or [Open collective](https://opencollective.com/dbgate)
|
||||
* Where a small coding is acceptable for you, you could [create plugin](https://dbgate.org/docs/plugin-development.html). Plugins for new themes can be created actually without JS coding
|
||||
|
||||
Thank you!
|
||||
|
||||
@@ -91,7 +94,7 @@ There are many database managers now, so why DbGate?
|
||||
* Many data browsing functions based using foreign keys - master/detail, expand columns, expandable form view
|
||||
|
||||
## Design goals
|
||||
* Application simplicity - DbGate takes the best and only the best from old [DbGate](http://www.jenasoft.com/dbgate), [DatAdmin](http://www.jenasoft.com/datadmin) and [DbMouse](http://www.jenasoft.com/dbmouse) .
|
||||
* Application simplicity - DbGate takes the best and only the best from old DbGate, [DatAdmin](https://www.softpedia.com/get/Internet/Servers/Database-Utils/DatAdmin-Personal.shtml), [DbMouse](https://www.softpedia.com/get/Internet/Servers/Database-Utils/DbMouse.shtml) and [SQL Database Studio](https://en.wikipedia.org/wiki/SQL_Database_Studio)
|
||||
* Minimal dependencies
|
||||
* Frontend - Svelte
|
||||
* Backend - NodeJs, ExpressJs, database connection drivers
|
||||
@@ -172,4 +175,7 @@ cd dbgate-plugin-my-new-plugin # this directory is created by wizard, edit, what
|
||||
yarn plugin # this compiles plugin and copies it into existing DbGate installation
|
||||
```
|
||||
|
||||
After restarting DbGate, you could use your new plugin from DbGate.
|
||||
After restarting DbGate, you could use your new plugin from DbGate.
|
||||
|
||||
## Logging
|
||||
DbGate uses [pinomin logger](https://github.com/dbgate/pinomin). So by default, it produces JSON log messages into console and log files. If you want to see formatted logs, please use [pino-pretty](https://github.com/pinojs/pino-pretty) log formatter.
|
||||
@@ -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');
|
||||
@@ -0,0 +1 @@
|
||||
better-sqlite3_local_prebuilds=../../prebuilds
|
||||
|
After Width: | Height: | Size: 89 KiB |
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>org.dbgate.DbGate</id>
|
||||
|
||||
<name>DbGate</name>
|
||||
<summary>(no)SQL database client</summary>
|
||||
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<developer_name>Jan Prochazka</developer_name>
|
||||
|
||||
<description>
|
||||
<p>DbGate is cross-platform database manager. It's designed to be simple to use and effective, when working with more databases simultaneously. But there are also many advanced features like schema compare, visual query designer, chart visualisation or batch export and import.</p>
|
||||
</description>
|
||||
|
||||
<url type="homepage">https://dbgate.org/</url>
|
||||
<url type="vcs-browser">https://github.com/dbgate/dbgate</url>
|
||||
<url type="contact">https://dbgate.org/about/</url>
|
||||
<url type="donation">https://github.com/sponsors/dbgate</url>
|
||||
<url type="bugtracker">https://github.com/dbgate/dbgate/issues</url>
|
||||
|
||||
<launchable type="desktop-id">org.dbgate.DbGate.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>
|
||||
https://github.com/dbgate/dbgate/raw/c2abc83f994a56945c27fccea3df84b48005961f/img/screenshot1.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>
|
||||
https://github.com/dbgate/dbgate/raw/c2abc83f994a56945c27fccea3df84b48005961f/img/screenshot2.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<content_rating type="oars-1.1"/>
|
||||
|
||||
<releases>
|
||||
<release version="5.2.7" date="2024-05-13"/>
|
||||
</releases>
|
||||
</component>
|
||||
@@ -19,9 +19,10 @@
|
||||
"appId": "org.dbgate",
|
||||
"productName": "DbGate",
|
||||
"afterSign": "electron-builder-notarize",
|
||||
"asarUnpack": "**/*.node",
|
||||
"mac": {
|
||||
"category": "database",
|
||||
"icon": "icon512.png",
|
||||
"icon": "icon512-mac.png",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "entitlements.mac.plist",
|
||||
"entitlementsInherit": "entitlements.mac.plist",
|
||||
@@ -47,7 +48,8 @@
|
||||
"armv7l",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tar.gz"
|
||||
],
|
||||
"icon": "icons/",
|
||||
"category": "Development",
|
||||
@@ -71,7 +73,13 @@
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
@@ -107,12 +115,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": "30.0.2",
|
||||
"electron-builder": "23.1.0",
|
||||
"electron-builder-notarize": "^1.5.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"better-sqlite3": "7.5.0",
|
||||
"msnodesqlv8": "^2.4.4"
|
||||
"better-sqlite3": "9.6.0",
|
||||
"msnodesqlv8": "^4.2.1",
|
||||
"oracledb": "^5.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const electron = require('electron');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
// const unhandled = require('electron-unhandled');
|
||||
// const { openNewGitHubIssue, debugInfo } = require('electron-util');
|
||||
const { Menu, ipcMain } = require('electron');
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
const log = require('electron-log');
|
||||
@@ -15,6 +17,7 @@ const path = require('path');
|
||||
const url = require('url');
|
||||
const mainMenuDefinition = require('./mainMenuDefinition');
|
||||
const { settings } = require('cluster');
|
||||
let disableAutoUpgrade = false;
|
||||
|
||||
// require('@electron/remote/main').initialize();
|
||||
|
||||
@@ -22,16 +25,46 @@ const configRootPath = path.join(app.getPath('userData'), 'config-root.json');
|
||||
let initialConfig = {};
|
||||
let apiLoaded = false;
|
||||
let mainModule;
|
||||
// let getLogger;
|
||||
// let loadLogsContent;
|
||||
|
||||
process.on('uncaughtException', function (error) {
|
||||
console.error('uncaughtException', error);
|
||||
});
|
||||
|
||||
const isMac = () => os.platform() == 'darwin';
|
||||
|
||||
// unhandled({
|
||||
// showDialog: true,
|
||||
// reportButton: error => {
|
||||
// openNewGitHubIssue({
|
||||
// user: 'dbgate',
|
||||
// repo: 'dbgate',
|
||||
// body: `PLEASE DELETE SENSITIVE INFO BEFORE POSTING ISSUE!!!\n\n\`\`\`\n${
|
||||
// error.stack
|
||||
// }\n\`\`\`\n\n---\n\n${debugInfo()}\n\n\`\`\`\n${loadLogsContent ? loadLogsContent(50) : ''}\n\`\`\``,
|
||||
// });
|
||||
// },
|
||||
// logger: error => (getLogger ? getLogger('electron').fatal(error) : console.error(error)),
|
||||
// });
|
||||
|
||||
try {
|
||||
initialConfig = JSON.parse(fs.readFileSync(configRootPath, { encoding: 'utf-8' }));
|
||||
disableAutoUpgrade = initialConfig['disableAutoUpgrade'] || false;
|
||||
} catch (err) {
|
||||
console.log('Error loading config-root:', err.message);
|
||||
initialConfig = {};
|
||||
}
|
||||
|
||||
if (process.argv.includes('--disable-auto-upgrade')) {
|
||||
console.log('Disabling auto-upgrade');
|
||||
disableAutoUpgrade = true;
|
||||
}
|
||||
if (process.argv.includes('--enable-auto-upgrade')) {
|
||||
console.log('Enabling auto-upgrade');
|
||||
disableAutoUpgrade = false;
|
||||
}
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow;
|
||||
@@ -154,6 +187,10 @@ ipcMain.on('app-started', async (event, arg) => {
|
||||
mainWindow.webContents.send('run-command', runCommandOnLoad);
|
||||
runCommandOnLoad = null;
|
||||
}
|
||||
|
||||
if (initialConfig['winIsMaximized']) {
|
||||
mainWindow.webContents.send('setIsMaximized', true);
|
||||
}
|
||||
});
|
||||
ipcMain.on('window-action', async (event, arg) => {
|
||||
if (!mainWindow) {
|
||||
@@ -164,11 +201,7 @@ ipcMain.on('window-action', async (event, arg) => {
|
||||
mainWindow.minimize();
|
||||
break;
|
||||
case 'maximize':
|
||||
if (mainWindow.isMaximized()) {
|
||||
mainWindow.unmaximize();
|
||||
} else {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
mainWindow.isMaximized() ? mainWindow.unmaximize() : mainWindow.maximize();
|
||||
break;
|
||||
case 'close':
|
||||
mainWindow.close();
|
||||
@@ -211,6 +244,9 @@ ipcMain.on('window-action', async (event, arg) => {
|
||||
case 'paste':
|
||||
mainWindow.webContents.paste();
|
||||
break;
|
||||
case 'selectAll':
|
||||
mainWindow.webContents.selectAll();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -296,6 +332,7 @@ function createWindow() {
|
||||
JSON.stringify({
|
||||
winBounds: mainWindow.getBounds(),
|
||||
winIsMaximized: mainWindow.isMaximized(),
|
||||
disableAutoUpgrade,
|
||||
}),
|
||||
'utf-8'
|
||||
);
|
||||
@@ -308,6 +345,14 @@ function createWindow() {
|
||||
mainWindow.setIcon(path.resolve(__dirname, '../icon.png'));
|
||||
}
|
||||
// mainWindow.webContents.toggleDevTools();
|
||||
|
||||
mainWindow.on('maximize', () => {
|
||||
mainWindow.webContents.send('setIsMaximized', true);
|
||||
});
|
||||
|
||||
mainWindow.on('unmaximize', () => {
|
||||
mainWindow.webContents.send('setIsMaximized', false);
|
||||
});
|
||||
}
|
||||
|
||||
if (!apiLoaded) {
|
||||
@@ -327,9 +372,12 @@ function createWindow() {
|
||||
// path.join(__dirname, process.env.DEVMODE ? '../../packages/api/src/index' : '../packages/api/dist/bundle.js')
|
||||
// )
|
||||
// );
|
||||
api.configureLogger();
|
||||
const main = api.getMainModule();
|
||||
main.useAllControllers(null, electron);
|
||||
mainModule = main;
|
||||
// getLogger = api.getLogger;
|
||||
// loadLogsContent = api.loadLogsContent;
|
||||
apiLoaded = true;
|
||||
}
|
||||
mainModule.setElectronSender(mainWindow.webContents);
|
||||
@@ -347,7 +395,10 @@ function createWindow() {
|
||||
}
|
||||
|
||||
function onAppReady() {
|
||||
if (!process.env.DEVMODE) {
|
||||
if (disableAutoUpgrade) {
|
||||
console.log('Auto-upgrade is disabled, run dbgate --enable-auto-upgrade to enable');
|
||||
}
|
||||
if (!process.env.DEVMODE && !disableAutoUpgrade) {
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
}
|
||||
createWindow();
|
||||
|
||||
@@ -6,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 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -44,6 +48,7 @@ module.exports = ({ editMenu }) => [
|
||||
{ command: 'edit.cut' },
|
||||
{ command: 'edit.copy' },
|
||||
{ command: 'edit.paste' },
|
||||
{ command: 'edit.selectAll' },
|
||||
],
|
||||
}
|
||||
: null,
|
||||
@@ -66,6 +71,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 +87,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 },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
FROM node:14
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
gnupg \
|
||||
iputils-ping \
|
||||
iproute2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
unixodbc \
|
||||
gcc \
|
||||
g++ \
|
||||
make
|
||||
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource-archive-keyring.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/nodesource-archive-keyring.gpg] https://deb.nodesource.com/node_18.x jammy main" | tee /etc/apt/sources.list.d/nodesource.list \
|
||||
&& echo "deb-src [signed-by=/usr/share/keyrings/nodesource-archive-keyring.gpg] https://deb.nodesource.com/node_18.x jammy main" | tee -a /etc/apt/sources.list.d/nodesource.list \
|
||||
&& apt-get update && apt-get install -y nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& npm install -g yarn
|
||||
|
||||
WORKDIR /home/dbgate-docker
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:14-alpine
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /home/dbgate-docker
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@ then
|
||||
echo "$HOST_IP $HOST_DOMAIN" >> /etc/hosts
|
||||
fi
|
||||
|
||||
node bundle.js
|
||||
exec node bundle.js --listen-api
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
|
||||
|
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');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
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
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0.18
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
restart: always
|
||||
ports:
|
||||
- 15001: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
|
||||
# command: --default-authentication-plugin=mysql_native_password
|
||||
# restart: always
|
||||
# ports:
|
||||
# - 15001:3306
|
||||
# environment:
|
||||
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
|
||||
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server
|
||||
@@ -27,11 +36,11 @@ services:
|
||||
- SA_PASSWORD=Pwd2020Db
|
||||
- MSSQL_PID=Express
|
||||
|
||||
cockroachdb:
|
||||
image: cockroachdb/cockroach
|
||||
ports:
|
||||
- 15003:26257
|
||||
command: start-single-node --insecure
|
||||
# cockroachdb:
|
||||
# image: cockroachdb/cockroach
|
||||
# ports:
|
||||
# - 15003:26257
|
||||
# command: start-single-node --insecure
|
||||
|
||||
# mongodb:
|
||||
# image: mongo:4.0.12
|
||||
|
||||
@@ -31,6 +31,23 @@ const engines = [
|
||||
objects: [views],
|
||||
dbSnapshotBySeconds: true,
|
||||
},
|
||||
{
|
||||
label: 'MariaDB',
|
||||
connection: {
|
||||
engine: 'mariadb@dbgate-plugin-mysql',
|
||||
password: 'Pwd2020Db',
|
||||
user: 'root',
|
||||
server: 'mysql',
|
||||
port: 3306,
|
||||
},
|
||||
local: {
|
||||
server: 'localhost',
|
||||
port: 15004,
|
||||
},
|
||||
skipOnCI: true,
|
||||
objects: [views],
|
||||
dbSnapshotBySeconds: true,
|
||||
},
|
||||
{
|
||||
label: 'PostgreSQL',
|
||||
connection: {
|
||||
@@ -117,12 +134,17 @@ const engines = [
|
||||
const filterLocal = [
|
||||
// filter local testing
|
||||
'-MySQL',
|
||||
'-MariaDB',
|
||||
'-PostgreSQL',
|
||||
'-SQL Server',
|
||||
'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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -104,3 +104,6 @@ magick icon.png -resize 16x16! ../app/icons/16x16.png
|
||||
magick icon.png -resize 192x192! ../packages/web/public/logo192.png
|
||||
magick icon.png -resize 512x512! ../packages/web/public/logo512.png
|
||||
magick icon.png -define icon:auto-resize="256,128,96,64,48,32,16" ../packages/web/public/favicon.ico
|
||||
|
||||
convert icon.png -resize 800x800 -background transparent -gravity center -extent 1000x1000 iconmac.png
|
||||
magick composite iconmac.png macbg.png -resize 600x600! ../app/icon512-mac.png
|
||||
|
||||
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 177 KiB |
@@ -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 |
@@ -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 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "5.0.0-beta.3",
|
||||
"version": "5.2.9-beta.2",
|
||||
"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",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
DEVMODE=1
|
||||
SHELL_SCRIPTING=1
|
||||
|
||||
# PERMISSIONS=~widgets/app,~widgets/plugins
|
||||
# DISABLE_SHELL=1
|
||||
# HIDE_APP_EDITOR=1
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.env
|
||||
@@ -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
|
||||
@@ -1,6 +1,6 @@
|
||||
DEVMODE=1
|
||||
|
||||
CONNECTIONS=mysql,postgres,mongo,mongo2,mysqlssh,sqlite
|
||||
CONNECTIONS=mysql,postgres,mongo,mongo2,mysqlssh,sqlite,relational
|
||||
|
||||
LABEL_mysql=MySql localhost
|
||||
SERVER_mysql=localhost
|
||||
@@ -41,4 +41,22 @@ LABEL_sqlite=sqlite
|
||||
FILE_sqlite=/home/jena/.dbgate/files/sqlite/feeds.sqlite
|
||||
ENGINE_sqlite=sqlite@dbgate-plugin-sqlite
|
||||
|
||||
LABEL_relational=Relational dataset repo
|
||||
SERVER_relational=relational.fit.cvut.cz
|
||||
USER_relational=guest
|
||||
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=~*
|
||||
|
||||
@@ -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"}]
|
||||
|
||||
|
||||
@@ -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,18 @@
|
||||
"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",
|
||||
"dbgate-datalib": "^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 +45,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"
|
||||
},
|
||||
@@ -68,11 +79,12 @@
|
||||
"node-loader": "^1.0.2",
|
||||
"nodemon": "^2.0.2",
|
||||
"typescript": "^4.4.3",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"better-sqlite3": "7.5.0",
|
||||
"msnodesqlv8": "^2.4.4"
|
||||
"better-sqlite3": "9.6.0",
|
||||
"msnodesqlv8": "^4.2.1",
|
||||
"oracledb": "^5.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
|
||||
@@ -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 && foundLogin.password == password) {
|
||||
return {
|
||||
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
||||
};
|
||||
}
|
||||
return { error: 'Invalid credentials' };
|
||||
},
|
||||
|
||||
authMiddleware,
|
||||
shouldAuthorizeApi,
|
||||
};
|
||||
@@ -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) {
|
||||
@@ -73,17 +80,35 @@ module.exports = {
|
||||
...value,
|
||||
};
|
||||
if (value['app.useNativeMenu'] !== true && value['app.useNativeMenu'] !== false) {
|
||||
res['app.useNativeMenu'] = os.platform() == 'darwin' ? true : false;
|
||||
// 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,
|
||||
|
||||
@@ -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}`],
|
||||
@@ -77,14 +88,15 @@ function getPortalCollections() {
|
||||
sslCertFilePassword: process.env[`SSL_CERT_FILE_PASSWORD_${id}`],
|
||||
sslKeyFile: process.env[`SSL_KEY_FILE_${id}`],
|
||||
sslRejectUnauthorized: process.env[`SSL_REJECT_UNAUTHORIZED_${id}`],
|
||||
trustServerCertificate: process.env[`SSL_TRUST_CERTIFICATE_${id}`],
|
||||
}));
|
||||
console.log('Using connections from ENV variables:');
|
||||
console.log(JSON.stringify(connections.map(pickSafeConnectionInfo), undefined, 2));
|
||||
|
||||
logger.info({ connections: connections.map(pickSafeConnectionInfo) }, 'Using connections from ENV variables');
|
||||
const noengine = connections.filter(x => !x.engine);
|
||||
if (noengine.length > 0) {
|
||||
console.log(
|
||||
'Warning: Invalid CONNECTIONS configutation, missing ENGINE for connection ID:',
|
||||
noengine.map(x => x._id)
|
||||
logger.warn(
|
||||
{ connections: noengine.map(x => x._id) },
|
||||
'Invalid CONNECTIONS configutation, missing ENGINE for connection ID'
|
||||
);
|
||||
}
|
||||
return connections;
|
||||
@@ -122,9 +134,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 +161,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 +197,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 +234,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 +286,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 +321,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 +331,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 +344,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,24 @@ 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' });
|
||||
try {
|
||||
existing.subprocess.send({ msgtype: 'ping' });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error pinging DB connection');
|
||||
this.close(conid, database);
|
||||
|
||||
return {
|
||||
status: 'error',
|
||||
message: 'Ping failed',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore
|
||||
existing = await this.ensureOpened(conid, database);
|
||||
}
|
||||
|
||||
@@ -263,7 +315,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,9 +324,10 @@ module.exports = {
|
||||
},
|
||||
|
||||
syncModel_meta: true,
|
||||
async syncModel({ conid, database }) {
|
||||
async syncModel({ conid, database, isFullRefresh }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
const conn = await this.ensureOpened(conid, database);
|
||||
conn.subprocess.send({ msgtype: 'syncModel' });
|
||||
conn.subprocess.send({ msgtype: 'syncModel', isFullRefresh });
|
||||
return { status: 'ok' };
|
||||
},
|
||||
|
||||
@@ -281,7 +335,13 @@ module.exports = {
|
||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||
if (existing) {
|
||||
existing.disconnected = true;
|
||||
if (kill) existing.subprocess.kill();
|
||||
if (kill) {
|
||||
try {
|
||||
existing.subprocess.kill();
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error killing subprocess');
|
||||
}
|
||||
}
|
||||
this.opened = this.opened.filter(x => x.conid != conid || x.database != database);
|
||||
this.closed[`${conid}/${database}`] = {
|
||||
status: {
|
||||
@@ -290,7 +350,7 @@ module.exports = {
|
||||
},
|
||||
structure: existing.structure,
|
||||
};
|
||||
socket.emitChanged(`database-status-changed-${conid}-${database}`);
|
||||
socket.emitChanged(`database-status-changed`, { conid, database });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -301,13 +361,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 +386,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 +408,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 +419,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',
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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),
|
||||
})),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,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);
|
||||
},
|
||||
|
||||
@@ -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,30 @@ module.exports = {
|
||||
},
|
||||
|
||||
ping_meta: true,
|
||||
async ping({ connections }) {
|
||||
async ping({ conidArray, strmid }) {
|
||||
await Promise.all(
|
||||
_.uniq(connections).map(async conid => {
|
||||
_.uniq(conidArray).map(async conid => {
|
||||
const last = this.lastPinged[conid];
|
||||
if (last && new Date().getTime() - last < 30 * 1000) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.lastPinged[conid] = new Date().getTime();
|
||||
const opened = await this.ensureOpened(conid);
|
||||
opened.subprocess.send({ msgtype: 'ping' });
|
||||
try {
|
||||
opened.subprocess.send({ msgtype: 'ping' });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error pinging server connection');
|
||||
this.close(conid);
|
||||
}
|
||||
})
|
||||
);
|
||||
socket.setStreamIdFilter(strmid, { conid: conidArray });
|
||||
return { status: 'ok' };
|
||||
},
|
||||
|
||||
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 +183,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 });
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,26 @@ 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');
|
||||
}
|
||||
try {
|
||||
session.subprocess.send({ msgtype: 'ping' });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error pinging session');
|
||||
|
||||
return {
|
||||
status: 'error',
|
||||
message: 'Ping failed',
|
||||
};
|
||||
}
|
||||
|
||||
return { state: 'ok' };
|
||||
},
|
||||
|
||||
// runCommand_meta: true,
|
||||
// async runCommand({ conid, database, sql }) {
|
||||
// console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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'),
|
||||
};
|
||||
|
||||
@@ -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,10 +45,10 @@ 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])),
|
||||
users: _.fromPairs(logins.filter(x => x.password).map(x => [x.login, x.password])),
|
||||
challenge: true,
|
||||
realm: 'DbGate Web App',
|
||||
})
|
||||
@@ -52,7 +57,27 @@ function start() {
|
||||
|
||||
app.use(cors());
|
||||
|
||||
if (platformInfo.isDocker) {
|
||||
// server static files inside docker container
|
||||
app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
|
||||
} else if (platformInfo.isNpmDist) {
|
||||
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
|
||||
} else if (process.env.DEVWEB) {
|
||||
// console.log('__dirname', __dirname);
|
||||
// console.log(path.join(__dirname, '../../web/public/build'));
|
||||
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../web/public')));
|
||||
} else {
|
||||
app.get(getExpressPath('/'), (req, res) => {
|
||||
res.send('DbGate API');
|
||||
});
|
||||
}
|
||||
|
||||
if (auth.shouldAuthorizeApi()) {
|
||||
app.use(auth.authMiddleware);
|
||||
}
|
||||
|
||||
app.get(getExpressPath('/stream'), async function (req, res) {
|
||||
const strmid = req.query.strmid;
|
||||
res.set({
|
||||
'Cache-Control': 'no-cache',
|
||||
'Content-Type': 'text/event-stream',
|
||||
@@ -63,7 +88,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, strmid);
|
||||
onFinished(req, () => {
|
||||
socket.removeSseResponse(strmid);
|
||||
});
|
||||
});
|
||||
|
||||
app.use(bodyParser.json({ limit: '50mb' }));
|
||||
@@ -84,14 +112,10 @@ function start() {
|
||||
app.use(getExpressPath('/runners/data'), express.static(rundir()));
|
||||
|
||||
if (platformInfo.isDocker) {
|
||||
// server static files inside docker container
|
||||
app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
console.log('DbGate API listening on port (docker build)', port);
|
||||
logger.info(`DbGate API listening on port ${port} (docker build)`);
|
||||
server.listen(port);
|
||||
} else if (platformInfo.isNpmDist) {
|
||||
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
|
||||
getPort({
|
||||
port: parseInt(
|
||||
// @ts-ignore
|
||||
@@ -99,35 +123,27 @@ function start() {
|
||||
),
|
||||
}).then(port => {
|
||||
server.listen(port, () => {
|
||||
console.log(`DbGate API listening on port ${port} (NPM build)`);
|
||||
logger.info(`DbGate API listening on port ${port} (NPM build)`);
|
||||
});
|
||||
});
|
||||
} else if (process.env.DEVWEB) {
|
||||
console.log('__dirname', __dirname);
|
||||
console.log(path.join(__dirname, '../../web/public/build'));
|
||||
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../web/public')));
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
console.log('DbGate API & web listening on port (dev web build)', port);
|
||||
logger.info(`DbGate API & web listening on port ${port} (dev web build)`);
|
||||
server.listen(port);
|
||||
} else {
|
||||
app.get(getExpressPath('/'), (req, res) => {
|
||||
res.send('DbGate API');
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
console.log('DbGate API listening on port (dev API build)', port);
|
||||
logger.info(`DbGate API listening on port ${port} (dev API build)`);
|
||||
server.listen(port);
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
console.log('\nShutting down DbGate API server');
|
||||
logger.info('\nShutting down DbGate API server');
|
||||
server.close(() => {
|
||||
console.log('Server shut down, terminating');
|
||||
logger.info('Server shut down, terminating');
|
||||
process.exit(0);
|
||||
});
|
||||
setTimeout(() => {
|
||||
console.log('Server close timeout, terminating');
|
||||
logger.info('Server close timeout, terminating');
|
||||
process.exit(0);
|
||||
}, 1000);
|
||||
}
|
||||
@@ -153,6 +169,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,12 +9,15 @@ 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 = [];
|
||||
let afterAnalyseCallbacks = [];
|
||||
let analysedStructure = null;
|
||||
let lastPing = null;
|
||||
let lastStatusString = null;
|
||||
let lastStatus = null;
|
||||
let analysedTime = 0;
|
||||
let serverVersion;
|
||||
@@ -78,21 +81,24 @@ async function handleIncrementalRefresh(forceSend) {
|
||||
resolveAnalysedPromises();
|
||||
}
|
||||
|
||||
function handleSyncModel() {
|
||||
function handleSyncModel({ isFullRefresh }) {
|
||||
if (loadingModel) return;
|
||||
handleIncrementalRefresh();
|
||||
if (isFullRefresh) handleFullRefresh();
|
||||
else handleIncrementalRefresh();
|
||||
}
|
||||
|
||||
function setStatus(status) {
|
||||
const statusString = stableStringify(status);
|
||||
if (lastStatus != statusString) {
|
||||
process.send({ msgtype: 'status', status: { ...status, counter: getStatusCounter() } });
|
||||
lastStatus = statusString;
|
||||
const newStatus = { ...lastStatus, ...status };
|
||||
const statusString = stableStringify(newStatus);
|
||||
if (lastStatusString != statusString) {
|
||||
process.send({ msgtype: 'status', status: { ...newStatus, counter: getStatusCounter() } });
|
||||
lastStatusString = statusString;
|
||||
lastStatus = newStatus;
|
||||
}
|
||||
}
|
||||
|
||||
function setStatusName(name) {
|
||||
setStatus({ name });
|
||||
setStatus({ name, message: null });
|
||||
}
|
||||
|
||||
async function readVersion() {
|
||||
@@ -109,6 +115,7 @@ async function handleConnect({ connection, structure, globalSettings }) {
|
||||
if (!structure) setStatusName('pending');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection, 'app'));
|
||||
systemConnection.feedback = feedback => setStatus({ feedback });
|
||||
await checkedAsyncCall(readVersion());
|
||||
if (structure) {
|
||||
analysedStructure = structure;
|
||||
@@ -151,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' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,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) {
|
||||
@@ -263,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);
|
||||
}
|
||||
@@ -329,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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
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 }) {
|
||||
constructor({ fileName, dataName }) {
|
||||
super({ objectMode: true });
|
||||
this.wasHeader = false;
|
||||
this.tableName = path.parse(fileName).name;
|
||||
this.dataName = dataName;
|
||||
this.driver = driverBase;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
@@ -28,7 +30,7 @@ class SqlizeStream extends stream.Transform {
|
||||
const dmp = this.driver.createDumper();
|
||||
dmp.put(
|
||||
'^insert ^into %f (%,i) ^values (%,v);\n',
|
||||
{ pureName: this.tableName },
|
||||
{ pureName: this.dataName || this.tableName },
|
||||
Object.keys(chunk),
|
||||
Object.values(chunk)
|
||||
);
|
||||
@@ -38,9 +40,9 @@ class SqlizeStream extends stream.Transform {
|
||||
}
|
||||
}
|
||||
|
||||
async function sqlDataWriter({ fileName, driver, encoding = 'utf-8' }) {
|
||||
console.log(`Writing file ${fileName}`);
|
||||
const stringify = new SqlizeStream({ fileName });
|
||||
async function sqlDataWriter({ fileName, dataName, driver, encoding = 'utf-8' }) {
|
||||
logger.info(`Writing file ${fileName}`);
|
||||
const stringify = new SqlizeStream({ fileName, dataName });
|
||||
const fileStream = fs.createWriteStream(fileName, encoding);
|
||||
stringify.pipe(fileStream);
|
||||
stringify['finisher'] = fileStream;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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,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 });
|
||||
|
||||
@@ -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');
|
||||
@@ -55,7 +52,7 @@ async function connectUtility(driver, storedConnection, connectionMode, addition
|
||||
throw new Error(tunnel.message);
|
||||
}
|
||||
|
||||
connection.server = '127.0.0.1';
|
||||
connection.server = 'localhost';
|
||||
connection.port = tunnel.localPort;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -30,7 +39,7 @@ function getLogins() {
|
||||
permissions: process.env.PERMISSIONS,
|
||||
});
|
||||
}
|
||||
if (process.env.LOGINS) {
|
||||
if (process.env.LOGINS || process.env.OAUTH_PERMISSIONS) {
|
||||
const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim()));
|
||||
for (const login of logins) {
|
||||
const password = process.env[`LOGIN_PASSWORD_${login}`];
|
||||
@@ -42,6 +51,13 @@ function getLogins() {
|
||||
permissions,
|
||||
});
|
||||
}
|
||||
if (process.env.OAUTH_PERMISSIONS) {
|
||||
res.push({
|
||||
login,
|
||||
password: null,
|
||||
permissions,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +66,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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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 && !isNpmDist) || !!process.env.SHELL_CONNECTION || !!isElectron(),
|
||||
allowShellScripting: (!processArgs.listenApiChild && !isNpmDist) || !!process.env.SHELL_SCRIPTING || !!isElectron(),
|
||||
defaultKeyfile: path.join(os.homedir(), '.ssh/id_rsa'),
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -1,41 +1,60 @@
|
||||
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, strmid) {
|
||||
sseResponses[strmid] = {
|
||||
response: value,
|
||||
filter: {},
|
||||
};
|
||||
this.ensurePing();
|
||||
},
|
||||
removeSseResponse(strmid) {
|
||||
delete sseResponses[strmid];
|
||||
},
|
||||
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`
|
||||
);
|
||||
}
|
||||
for (const strmid in sseResponses) {
|
||||
let skipThisStream = false;
|
||||
for (const key in sseResponses[strmid].filter) {
|
||||
if (data && data[key]) {
|
||||
if (!sseResponses[strmid].filter[key].includes(data[key])) {
|
||||
skipThisStream = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
init = [];
|
||||
}
|
||||
sseResponse.write(`event: ${message}\ndata: ${JSON.stringify(data == null ? null : data)}\n\n`);
|
||||
} else {
|
||||
init.push([{ message, data }]);
|
||||
if (skipThisStream) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sseResponses[strmid].response.write(
|
||||
`event: ${message}\ndata: ${stableStringify(data == null ? null : data)}\n\n`
|
||||
);
|
||||
}
|
||||
},
|
||||
emitChanged(key) {
|
||||
emitChanged(key, params = undefined) {
|
||||
// console.log('EMIT CHANGED', key);
|
||||
this.emit('changed-cache', key);
|
||||
this.emit('changed-cache', { key, ...params });
|
||||
// this.emit(key);
|
||||
},
|
||||
setStreamIdFilter(strmid, filter) {
|
||||
sseResponses[strmid].filter = filter;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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}`
|
||||
);
|
||||
|
||||
|
||||