Compare commits
863 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8489c171f3 | |||
| 592865b16e | |||
| 012d3ec2e1 | |||
| d84adcca5d | |||
| b1ae7d53b9 | |||
| 9a5287725b | |||
| 5ccd724166 | |||
| 5e4c286427 | |||
| 70413b954b | |||
| 07b2a3e923 | |||
| 94a91d5fed | |||
| 576fc2062c | |||
| 37a8783751 | |||
| f42d78b2fb | |||
| 792fa75ccd | |||
| cbd3f1bae9 | |||
| cd92231769 | |||
| ecad1ae01b | |||
| dc576e6ced | |||
| 6cca81f8f1 | |||
| a9f1f19696 | |||
| 390ddac75b | |||
| e2e7c6f06b | |||
| 3a3d0683d5 | |||
| d5534dcf07 | |||
| b0a86f9f4a | |||
| b833a30148 | |||
| d9c1bbaa39 | |||
| 4b74dbbd68 | |||
| 9bcc61551c | |||
| ed71ef312d | |||
| 4fa043b7e5 | |||
| 83725dd349 | |||
| 4e25b71b06 | |||
| 607ae7c872 | |||
| 66ade5823f | |||
| ebfa0a1939 | |||
| 909591404f | |||
| 7a5f2a70ad | |||
| d41b254058 | |||
| 435d06ffb9 | |||
| f4a4eb7f9e | |||
| 9910bbead3 | |||
| cb619a0fe0 | |||
| b0d61f974c | |||
| 8c051ff5f7 | |||
| f713a4b183 | |||
| 6c7e263f0e | |||
| ec3bfb4fae | |||
| 712ec8e6ee | |||
| 4da0b25f44 | |||
| 9b60b7a003 | |||
| 8ed73195c5 | |||
| c69fcd5eff | |||
| 310774db3b | |||
| 1dd166b563 | |||
| 0497f541cb | |||
| 42333a97b8 | |||
| 494c3c8e4a | |||
| 69a87bc076 | |||
| bf4eb19ef5 | |||
| 225518df3e | |||
| 0028240552 | |||
| 44be1bdd11 | |||
| e0703b1bae | |||
| a240681d6d | |||
| f5906587db | |||
| 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 | |||
| 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 | |||
| e4835b505a | |||
| d6d7ad99df | |||
| ddf186ee6b | |||
| 5a54af71a8 | |||
| 03950ef57e | |||
| bd7cc1580a | |||
| 140e05bc85 | |||
| 07060218b1 | |||
| 23546468bc | |||
| fa0a243418 | |||
| 9ab1ddc182 | |||
| bd79e96035 | |||
| 0a1fe0df10 | |||
| 5b9c27023c | |||
| ce7c734265 | |||
| 32e4e36258 | |||
| 2a3b4fe4d8 | |||
| 1aa1d09c8b | |||
| 00c9ac61c4 | |||
| 1d013f96fb | |||
| b3a2197820 | |||
| 8e5584e90f | |||
| 566082d40f | |||
| 96b2c7280d | |||
| 2f8282cbce | |||
| e59eb4b8e6 | |||
| a799f9b9c9 | |||
| c925ce9652 | |||
| ce4124caef | |||
| eeb52b9a41 | |||
| babe6d6d1b | |||
| e6d0faf273 | |||
| 10b803d901 | |||
| 39cb665b32 | |||
| 4c9ae46577 | |||
| 9e25a45090 | |||
| 51bd5c228b | |||
| 97c2dcbaf6 | |||
| cd8698fac9 | |||
| bc49435fbf | |||
| 4e32646ab8 | |||
| d79c9e159a | |||
| 3eab120aaf | |||
| 5b48e0251a | |||
| a97788eefd | |||
| 4e27a1ab0f | |||
| a0059698b0 | |||
| 4bf3296042 | |||
| 31edd48257 | |||
| 8e9a14665b | |||
| 793bc6b494 | |||
| 864b9c9333 | |||
| fb4dd06578 | |||
| 3c8a0ebd22 | |||
| 3a20f24f7c | |||
| aa8854da93 | |||
| 82ae8e23e0 | |||
| 4d7887a379 | |||
| 30b054dbec | |||
| df743e8ade | |||
| 7cc70dc5f8 | |||
| 10491868a0 | |||
| 8f945dc13f | |||
| d041f88a47 | |||
| e5bbf5eca3 | |||
| 66da21804b | |||
| 5315a549b0 | |||
| 82304f13e7 | |||
| 5bcd5d57ba | |||
| b6464dc119 | |||
| 4182132dde | |||
| 32228d542a | |||
| 72f5710dfd | |||
| 539c383b21 | |||
| c3289d09c0 | |||
| 233fed30cb | |||
| 63521002cc | |||
| 21642e0a3e | |||
| 4945b71c58 | |||
| ed0d63d135 | |||
| 2c25669bc7 | |||
| 0c94145b18 | |||
| 95fb5d51c5 | |||
| a75e931fd5 | |||
| c7c667bbe0 | |||
| 97eff2b113 | |||
| 0122b0accc | |||
| 6c718981d6 | |||
| f78d37159e | |||
| bf1881595c | |||
| 4e8c92eb36 | |||
| 34a0cb095b | |||
| f606d0c41c | |||
| 8002d734bc | |||
| be6d572ce2 | |||
| c3baa88d9e | |||
| 5ef06abd1a | |||
| e86a34ab53 | |||
| c2b715e3f7 | |||
| 666f1a3159 | |||
| 1ee66047d4 | |||
| 8032bf272b | |||
| d52cfadfc4 | |||
| 374c820567 | |||
| cc639df566 | |||
| 5a42e8e963 | |||
| 0187bb74ee | |||
| bf4b7beadb | |||
| b254c90f33 | |||
| 89fdfbe8c1 | |||
| a37c81be88 | |||
| abe9694d05 | |||
| c55c1f3aaf | |||
| ce6b750a1c | |||
| f83d45356f | |||
| 589ff1ad38 | |||
| ba97f50273 | |||
| e52fbd5034 | |||
| e29d3a6143 | |||
| 7313fa16f6 | |||
| 18437d1be7 | |||
| 2b39c85918 | |||
| 5135b985c9 | |||
| 05619faa7a | |||
| 12a638af3b | |||
| 38e05bf8e0 | |||
| 6d92de6930 | |||
| 90f8d349fc | |||
| 5379e86d6e | |||
| 85e449953f | |||
| 30e52723dd | |||
| 467918bcbb | |||
| 73d17504c1 | |||
| 7a3007deb2 | |||
| 2a46ff78bb | |||
| fa193f0e57 | |||
| f702513bb9 | |||
| 72462376b1 | |||
| a46ef7f0d0 | |||
| 9f5013c6da | |||
| 5ea6c56752 | |||
| cbb38b8edc | |||
| 045f6c6a47 | |||
| 334ab504cf | |||
| 66db28010c | |||
| 807392fc57 | |||
| 1a32d88312 | |||
| 1a76cc0979 | |||
| bcdaf84739 | |||
| 39296a852e | |||
| 1a035ca168 | |||
| c0b365602b | |||
| 5aac142e4c | |||
| 25380ee0a8 | |||
| 0b3b18ceda | |||
| 9cecebe8bc | |||
| c0227ecce1 | |||
| 1d37950b37 | |||
| fe277f5ffa | |||
| 1b52a2a0fc | |||
| a062073a5f | |||
| 44e52dfa9c | |||
| 0323264bbd | |||
| aacedba450 | |||
| aac768b158 | |||
| d4a7ae13e1 | |||
| b8206a7a02 | |||
| 4c2f6c9b65 | |||
| d58d46feac | |||
| 760edc7eca | |||
| 7c0f33383f | |||
| a20a34680d | |||
| 0e8166577f | |||
| 11c82b1aac | |||
| 61f24c3408 | |||
| e25657bd43 | |||
| 4bd7cd26d0 | |||
| e06894372f | |||
| 1f0ae98c88 | |||
| c0fdcf2fd1 | |||
| 8d31130737 | |||
| 9e3991556a | |||
| fc08353225 | |||
| 25556f0d3e | |||
| 0fb3817af6 | |||
| d8e840f127 | |||
| 4270722557 | |||
| b3cfff0ae8 | |||
| 2f5f0ab54c | |||
| 4c856c5e36 | |||
| 5c8ae85c54 | |||
| 5b39576e61 | |||
| 735c48902f | |||
| 613ac3f0e5 | |||
| 1aecda6d9f | |||
| 38dfad4dfc | |||
| 25ae5bf048 | |||
| f3bfe58c58 | |||
| ff714b1f8a | |||
| 93552585f7 | |||
| ec7641dbd6 | |||
| e2308f501b | |||
| 45277e34c9 | |||
| ba77b32e9a | |||
| c05ef42bf9 | |||
| b0e0197346 | |||
| 4c411b048d | |||
| a3bc1e577a | |||
| ef533671a2 | |||
| 77612cc6fb | |||
| 26881a3e39 | |||
| a1b7ad18af | |||
| 487d4afd70 | |||
| 39f7508136 | |||
| 153bc6ddde | |||
| 73d4c43571 | |||
| a73168b7e1 | |||
| f10f863940 | |||
| 5df0204450 | |||
| 2bec053809 | |||
| 6fb582249c | |||
| 1a81952ce7 | |||
| 04f25c4535 | |||
| 8824262395 | |||
| ff661ec89b | |||
| 0221c30716 | |||
| 913896d8bc | |||
| 9b449527fd | |||
| 6dff481dbf | |||
| 649626975c | |||
| c135a068a2 | |||
| 728a2c6a9f | |||
| 6e041e9eed | |||
| 6c8eccd369 | |||
| bf1a89ee21 | |||
| d888feeaf8 | |||
| e181318e24 | |||
| a9038984d8 | |||
| 4acce560ad | |||
| 29591a613a | |||
| 8f1d76fd2a | |||
| 7d196c7c62 | |||
| 267e687e2b | |||
| 34658e134f | |||
| 4efcef192a | |||
| 9c7a130ee4 | |||
| 0d7bfd5f90 | |||
| 5c4794deae | |||
| 074a75075d | |||
| 4ce2ed06e1 | |||
| 61c5ac6a47 | |||
| 4a0bd14dfe | |||
| 85bdbd503b | |||
| aec9c796b2 | |||
| 6c317b0cf7 | |||
| 170f213024 | |||
| 050c15994b | |||
| 5e73c75bb5 | |||
| 09bb8f0874 | |||
| 651dd09b15 | |||
| 900fdc56f4 | |||
| 5bda092a51 | |||
| dc34898cd8 | |||
| 6590830344 | |||
| cf734a26ee | |||
| 32c06fdf4d | |||
| cbe0ea7c9b | |||
| b6cc77c7fe | |||
| 25015f35d5 | |||
| ae719157c0 | |||
| 772a72dfd8 | |||
| 3ccb00854c | |||
| cf047cb7b5 | |||
| 84725f0586 | |||
| 34dae68a62 | |||
| 750a37a27f | |||
| cd7864b889 | |||
| 7efd4be401 | |||
| 0629123cc1 | |||
| 6138cfc2da | |||
| 730fd38b9e | |||
| 3d72df424f | |||
| 98a9859216 | |||
| 0b6042c3cb | |||
| 35792a024a | |||
| ddff3d2b89 | |||
| c26bc6d0e9 | |||
| 8c3708fc8c | |||
| 008a09cc81 | |||
| 7497e2684c | |||
| c4b0b185e6 | |||
| 8b91c69f5f | |||
| f57624ef24 | |||
| 64c8d4bdca | |||
| 9b396e7431 | |||
| 78faa94d77 | |||
| 7953186e03 | |||
| ccfbe2cccb | |||
| 74ad345ba5 | |||
| 750e929d1e | |||
| 5eba93559d | |||
| 51942be0a6 | |||
| 425841bb38 | |||
| 2202ae5aab | |||
| e8d24e177b | |||
| d7a2bf3ac0 | |||
| 8692283cb8 |
@@ -28,9 +28,3 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Install source [e.g. installer/SNAP/Docker/NPM]
|
||||
- Type - Web/Application
|
||||
- Database engine: [e.g. MySQL/PostgreSQL/SQL Server]
|
||||
|
||||
**Additional info**
|
||||
You could provide additional context, to better understand your case.
|
||||
- How looks your typical DbGate usage?
|
||||
- What other DB software do you use?
|
||||
- Anything else you think might be helpful
|
||||
|
||||
@@ -15,9 +15,3 @@ A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional info**
|
||||
You could provide additional context, to better understand your case.
|
||||
- How looks your typical DbGate usage?
|
||||
- What other DB software do you use?
|
||||
- Anything else you think might be helpful
|
||||
|
||||
@@ -16,9 +16,3 @@ App Version [help -> about]:
|
||||
|
||||
**Screenshot [if appropriate]**:
|
||||
A screenshot of the app if that helps
|
||||
|
||||
**Additional info**
|
||||
You could provide additional context, to better understand your case.
|
||||
- How looks your typical DbGate usage?
|
||||
- What other DB software do you use?
|
||||
- Anything else you think might be helpful
|
||||
|
||||
@@ -10,9 +10,10 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# os: [ubuntu-18.04, windows-2016]
|
||||
os: [macOS-10.15, windows-2019, ubuntu-18.04]
|
||||
os: [macOS-10.15, windows-2022, ubuntu-18.04]
|
||||
# os: [macOS-10.15]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
@@ -26,6 +27,9 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
- name: yarn adjustPackageJson
|
||||
run: |
|
||||
yarn adjustPackageJson
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
@@ -46,8 +50,17 @@ jobs:
|
||||
yarn run build:app
|
||||
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_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
|
||||
- name: Save snap login
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
|
||||
@@ -14,9 +14,10 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# os: [ubuntu-18.04, windows-2016]
|
||||
os: [macOS-10.15, windows-2019, ubuntu-18.04]
|
||||
os: [macOS-10.15, windows-2022, ubuntu-18.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
@@ -30,6 +31,9 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
- name: yarn adjustPackageJson
|
||||
run: |
|
||||
yarn adjustPackageJson
|
||||
- name: yarn install
|
||||
run: |
|
||||
# yarn --version
|
||||
@@ -52,8 +56,17 @@ jobs:
|
||||
yarn run build:app
|
||||
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_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
|
||||
- name: generatePadFile
|
||||
run: |
|
||||
@@ -83,8 +96,8 @@ jobs:
|
||||
cp app/dist/*.exe artifacts/dbgate-latest.exe || true
|
||||
cp app/dist/*win_x64.zip artifacts/dbgate-windows-latest.zip || true
|
||||
cp app/dist/*win_arm64.zip artifacts/dbgate-windows-latest-arm64.zip || true
|
||||
cp app/dist/*-mac_x64.dmg artifacts/dbgate-latest.dmg || true
|
||||
cp app/dist/*-mac_arm64.dmg artifacts/dbgate-latest-arm64.dmg || true
|
||||
cp app/dist/*-mac_universal.dmg artifacts/dbgate-latest.dmg || true
|
||||
cp app/dist/*-mac_x64.dmg artifacts/dbgate-latest-x64.dmg || true
|
||||
|
||||
mv app/dist/*.exe artifacts/ || true
|
||||
mv app/dist/*.zip artifacts/ || true
|
||||
@@ -118,7 +131,7 @@ jobs:
|
||||
# mv app/dist/latest.yml artifacts/latest.yml || true
|
||||
|
||||
- name: Copy latest.yml (windows)
|
||||
if: matrix.os == 'windows-2019'
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
mv app/dist/latest.yml artifacts/latest.yml || true
|
||||
mv app/dist/dbgate-pad.xml artifacts/ || 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:
|
||||
@@ -30,12 +24,43 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
dbgate/dbgate
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=raw,value=beta,enable=${{ contains(github.ref_name, '-docker.') || contains(github.ref_name, '-beta.') }}
|
||||
|
||||
type=match,pattern=\d+.\d+.\d+,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
|
||||
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
|
||||
|
||||
- name: Docker alpine meta
|
||||
id: alpmeta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
dbgate/dbgate
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=raw,value=beta-alpine,enable=${{ contains(github.ref_name, '-docker.') || contains(github.ref_name, '-beta.') }}
|
||||
|
||||
type=match,pattern=\d+.\d+.\d+,suffix=-alpine,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
|
||||
type=raw,value=alpine,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
|
||||
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
- name: yarn install
|
||||
run: |
|
||||
# yarn --version
|
||||
# yarn config set network-timeout 300000
|
||||
yarn install
|
||||
- name: setCurrentVersion
|
||||
run: |
|
||||
@@ -43,19 +68,28 @@ jobs:
|
||||
- name: Prepare docker image
|
||||
run: |
|
||||
yarn run prepare:docker
|
||||
- name: Build docker image
|
||||
run: |
|
||||
docker build ./docker -t dbgate
|
||||
- name: Push docker image
|
||||
run: |
|
||||
docker tag dbgate dbgate/dbgate
|
||||
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
|
||||
docker push dbgate/dbgate
|
||||
- name: Build alpine docker image
|
||||
run: |
|
||||
docker build ./docker -t dbgate -f docker/Dockerfile-alpine
|
||||
- name: Push alpine docker image
|
||||
run: |
|
||||
docker tag dbgate dbgate/dbgate:alpine
|
||||
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
|
||||
docker push dbgate/dbgate:alpine
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
context: ./docker
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
- name: Build and push alpine
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
context: ./docker
|
||||
file: ./docker/Dockerfile-alpine
|
||||
tags: ${{ steps.alpmeta.outputs.tags }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
@@ -79,21 +79,21 @@ jobs:
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish query-splitter
|
||||
working-directory: packages/query-splitter
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish web
|
||||
working-directory: packages/web
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate
|
||||
|
||||
- name: Publish dbgate (obsolete)
|
||||
working-directory: packages/dbgate
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate-serve
|
||||
working-directory: packages/serve
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate-plugin-csv
|
||||
working-directory: plugins/dbgate-plugin-csv
|
||||
run: |
|
||||
@@ -133,3 +133,8 @@ jobs:
|
||||
working-directory: plugins/dbgate-plugin-sqlite
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate-plugin-redis
|
||||
working-directory: plugins/dbgate-plugin-redis
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
@@ -31,10 +31,10 @@ jobs:
|
||||
run: |
|
||||
cd packages/filterparser
|
||||
yarn test:ci
|
||||
- name: Query spliiter tests
|
||||
- name: Datalib (perspective) tests
|
||||
if: always()
|
||||
run: |
|
||||
cd packages/query-splitter
|
||||
cd packages/datalib
|
||||
yarn test:ci
|
||||
- uses: tanmen/jest-reporter@v1
|
||||
if: always()
|
||||
@@ -52,8 +52,8 @@ jobs:
|
||||
if: always()
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
result-file: packages/query-splitter/result.json
|
||||
action-name: Query splitter test results
|
||||
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"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,227 @@
|
||||
# ChangeLog
|
||||
|
||||
Builds:
|
||||
- docker - build
|
||||
- npm - npm package dbgate-serve
|
||||
- app - classic electron app
|
||||
- mac - application for macOS
|
||||
- linux - application for linux
|
||||
- win - application for Windows
|
||||
|
||||
### 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
|
||||
- FIXED(mac): Fixed crash when reopening main window
|
||||
- FIXED: MySQL dump now handles correctly dependand views
|
||||
- FIXED(app): Browse tabs with Ctrl+Tab
|
||||
- ADDED(app): Browse tabs in reverse order with Ctrl+Shift+Tab #245
|
||||
|
||||
### 4.8.7
|
||||
- ADDED: MySQL dump/backup database
|
||||
- ADDED: Import SQL dump from file or from URL
|
||||
- FIXED(mac): Fixed Cmd+C, Cmd+V, Cmd+X - shortcuts for copy/cut/paste #270
|
||||
- FIXED(mac): Some minor issues on macOS
|
||||
- FIXED: Analysing MS SQL nvarchar(max)
|
||||
- ADDED: Support for dockerhost network name under docker #271
|
||||
|
||||
### 4.8.4
|
||||
- FIXED(mac): Fixed build for macOS arm64 #259
|
||||
- FIXED(mac): Fixed opening SQLite files on macOS #243
|
||||
- FIXED(mac): Fixed opening PEM certificates on macOS #206
|
||||
- FIXED(mac): Fixed handling Command key on macOS
|
||||
- FIXED(mac): Fixed system menu on macOS
|
||||
- FIXED(mac): Fixed reopening main window on macOS
|
||||
- CHANGED: Shortcut for net query is now Ctrl+T or Command+T on macOS, former it was Ctrl+Q
|
||||
- FIXED: Fixed misplaced tab close icon #260
|
||||
- ADDED: Added menu command "Tools/Change to recent database"
|
||||
|
||||
### 4.8.3
|
||||
- FIXED: filters in query result and NDJSON/archive viewer
|
||||
- ADDED: Added select values from query result and NDJSON/archive viewer
|
||||
- ADDED: tab navigation in datagrid #254
|
||||
- ADDED: Keyboard shortcuts added to help menu #254
|
||||
- ADDED: API logging (run enableApiLog() in developers console to enable logging)
|
||||
- ADDED: SSH reconnect + moved SSH forward into separate fork #253
|
||||
- ADDED: Data type + reference link in column manager
|
||||
- FIXED(win,linux,mac): Unable to change theme after installing plugin #244
|
||||
|
||||
### 4.8.2
|
||||
- ADDED: implemented missing redis search key logic
|
||||
|
||||
### 4.8.1
|
||||
- FIXED: fixed crash after disconnecting from all DBs
|
||||
|
||||
### 4.8.0
|
||||
- ADDED: Redis support (support stream type), removed experimental status
|
||||
- ADDED: Redis readonly support
|
||||
- ADDED: Explicit NDJSON support, when opening NDJSON/JSON lines file, table data are immediately shown, without neccesarity to import
|
||||
- ADDED(win,linux,mac): Opening developer tools when crashing without reload app
|
||||
### 4.7.4
|
||||
- ADDED: Experimental Redis support (full support is planned to version 4.8.0)
|
||||
- ADDED: Read-only connections
|
||||
- FIXED: MongoDB filters
|
||||
- ADDED: MongoDB column value selection
|
||||
- ADDED: App related queries
|
||||
- ADDED: Fuzzy search #246
|
||||
- ADDED(docker, npm): New permissions
|
||||
- FIXED(npm): NPM build no longer allocates additonal ports
|
||||
- CHANGED(npm): renamed NPM package dbgate => dbgate-serve
|
||||
- CHANGED(docker): custom JavaScripts and connections defined in scripts are now prohibited by default, use SHELL_CONNECTION and SHELL_SCRIPTING environment variables for allowing this
|
||||
- ADDED(docker, npm): Better documentation of environment variables configuration, https://dbgate.org/docs/env-variables.html
|
||||
- ADDED(docker): support for multiple users with different permissions
|
||||
- ADDED(docker): logout operation
|
||||
|
||||
### 4.7.3
|
||||
- CHANGED: Export menu redesign, quick export menu merged with old export menu
|
||||
- REMOVED: Quick export menu
|
||||
- ADDED: Export column mapping
|
||||
- ADDED: Export invoked from data grid respects columns choosed in column manager
|
||||
- ADDED: Quick export (now merged in export menu) is now possible also in web app
|
||||
- FIXED: Virtual foreign key editor fixes
|
||||
- FIXED: Tabs panel style fix
|
||||
- ADDED: Find by schema in databases widget
|
||||
- FIXED: Column manager selection fix
|
||||
- FIXED: NPM dist - fixed error when loading plugins
|
||||
- CHANGED: NPN dist is now executed by dbgate-serve command
|
||||
- ADDED: NPM dist accepts .env configuration
|
||||
|
||||
### 4.7.2
|
||||
- CHANGED: documentation URL - https://dbgate.org/docs/
|
||||
- CHANGED: Close button available for all tab groups - #238
|
||||
- ADDED: Search function for the Keyboard Shortcuts overview - #239
|
||||
- ADDED: Editor font size settings - #229
|
||||
- ADDED: Rename MongoDB collection - #223
|
||||
- FIXED: bug in cache subsystem
|
||||
|
||||
### 4.7.1
|
||||
- FIXED: Fixed connecting to MS SQL server running in docker container from DbGate running in docker container #236
|
||||
- FIXED: Fixed export MongoDB collections into Excel and CSV #240
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://www.npmjs.com/package/dbgate)
|
||||
[](https://www.npmjs.com/package/dbgate-serve)
|
||||

|
||||
[](https://snapcraft.io/dbgate)
|
||||
[](https://snapcraft.io/dbgate)
|
||||
@@ -16,13 +16,14 @@ DbGate is licensed under MIT license and is completely free.
|
||||
|
||||
* Try it online - [demo.dbgate.org](https://demo.dbgate.org) - online demo application
|
||||
* **Download** application for Windows, Linux or Mac from [dbgate.org](https://dbgate.org/download/)
|
||||
* Run web version as [NPM package](https://www.npmjs.com/package/dbgate) or as [docker image](https://hub.docker.com/r/dbgate/dbgate)
|
||||
* Run web version as [NPM package](https://www.npmjs.com/package/dbgate-serve) or as [docker image](https://hub.docker.com/r/dbgate/dbgate)
|
||||
|
||||
## Supported databases
|
||||
* MySQL
|
||||
* PostgreSQL
|
||||
* SQL Server
|
||||
* MongoDB
|
||||
* Redis
|
||||
* SQLite
|
||||
* Amazon Redshift
|
||||
* CockroachDB
|
||||
@@ -63,6 +64,7 @@ DbGate is licensed under MIT license and is completely free.
|
||||
* SQL code completion
|
||||
* Add SQL LEFT/INNER/RIGHT join utility
|
||||
* 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
|
||||
* Free table editor - quick table data editing (cleanup data after import/before export, prototype tables etc.)
|
||||
@@ -70,6 +72,7 @@ DbGate is licensed under MIT license and is completely free.
|
||||
* 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
|
||||
|
||||
## How to contribute
|
||||
Any contributions are welcome. If you want to contribute without coding, consider following:
|
||||
@@ -77,7 +80,8 @@ Any contributions are welcome. If you want to contribute without coding, conside
|
||||
* Tell your friends about DbGate or share on social networks - when more people will use DbGate, it will grow to be better
|
||||
* Write review on [Slant.co](https://www.slant.co/improve/options/41086/~dbgate-review) or [G2](https://www.g2.com/products/dbgate/reviews)
|
||||
* Create issue, if you find problem in app, or you have idea to new feature. If issue already exists, you could leave comment on it, to prioritise most wanted issues.
|
||||
* Become a backer on [Open collective](https://opencollective.com/dbgate)
|
||||
* Create some tutorial video on [youtube](https://www.youtube.com/playlist?list=PLCo7KjCVXhr0RfUSjM9wJMsp_ShL1q61A)
|
||||
* Become a backer on [GitHub sponsors](https://github.com/sponsors/dbgate) or [Open collective](https://opencollective.com/dbgate)
|
||||
* Where a small coding is acceptable for you, you could [create plugin](https://dbgate.org/docs/plugin-development.html). Plugins for new themes can be created actually without JS coding.
|
||||
|
||||
Thank you!
|
||||
@@ -91,8 +95,8 @@ There are many database managers now, so why DbGate?
|
||||
## 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) .
|
||||
* Minimal dependencies
|
||||
* Frontend - Svelte, socket.io
|
||||
* Backend - NodeJs, ExpressJs, socket.io, database connection drivers
|
||||
* Frontend - Svelte
|
||||
* Backend - NodeJs, ExpressJs, database connection drivers
|
||||
* JavaScript + TypeScript
|
||||
* App - electron
|
||||
* Platform independent - runs as web application in single docker container on server, or as application using Electron platform on Linux, Windows and Mac
|
||||
@@ -124,11 +128,11 @@ yarn # install NPM packages
|
||||
And than run following 3 commands concurrently in 3 terminals:
|
||||
```
|
||||
yarn start:api # run API on port 3000
|
||||
yarn start:web # run web on port 5000
|
||||
yarn start:web # run web on port 5001
|
||||
yarn lib # watch typescript libraries and plugins modifications
|
||||
```
|
||||
This runs API on port 3000 and web application on port 5000
|
||||
Open http://localhost:5000 in your browser
|
||||
This runs API on port 3000 and web application on port 5001
|
||||
Open http://localhost:5001 in your browser
|
||||
|
||||
If you want to run electron app:
|
||||
```sh
|
||||
@@ -139,13 +143,13 @@ yarn # install NPM packages for electron
|
||||
|
||||
And than run following 3 commands concurrently in 3 terminals:
|
||||
```
|
||||
yarn start:web # run web on port 5000 (only static JS and HTML files)
|
||||
yarn start:web # run web on port 5001 (only static JS and HTML files)
|
||||
yarn lib # watch typescript libraries and plugins modifications
|
||||
yarn start:app # run electron app
|
||||
```
|
||||
|
||||
## How to run built electron app locally
|
||||
This mode is very similar to production run of electron app. Electron doesn't use localhost:5000.
|
||||
This mode is very similar to production run of electron app. Electron doesn't use localhost:5001.
|
||||
|
||||
```sh
|
||||
cd app
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
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;
|
||||
}
|
||||
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
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.disable-library-validation</key><true/>
|
||||
<key>com.apple.security.cs.allow-jit</key><true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key><true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key><true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key><true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dbgate",
|
||||
"version": "4.1.1",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"private": true,
|
||||
"author": "Jan Prochazka <jenasoft.database@gmail.com>",
|
||||
"description": "Opensource database administration tool",
|
||||
@@ -15,18 +15,23 @@
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
},
|
||||
"build": {
|
||||
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
||||
"artifactName": "dbgate-${version}-${os}_${arch}.${ext}",
|
||||
"appId": "org.dbgate",
|
||||
"productName": "DbGate",
|
||||
"afterSign": "electron-builder-notarize",
|
||||
"mac": {
|
||||
"category": "database",
|
||||
"icon": "icon512.png",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "entitlements.mac.plist",
|
||||
"entitlementsInherit": "entitlements.mac.plist",
|
||||
"publish": [
|
||||
"github"
|
||||
],
|
||||
"target": {
|
||||
"target": "default",
|
||||
"arch": [
|
||||
"arm64",
|
||||
"universal",
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
@@ -89,7 +94,7 @@
|
||||
},
|
||||
"homepage": "./",
|
||||
"scripts": {
|
||||
"start": "cross-env ELECTRON_START_URL=http://localhost:5000 DEVMODE=1 electron .",
|
||||
"start": "cross-env ELECTRON_START_URL=http://localhost:5001 DEVMODE=1 electron .",
|
||||
"start:local": "cross-env electron .",
|
||||
"dist": "electron-builder",
|
||||
"build": "cd ../packages/api && yarn build && cd ../web && yarn build && cd ../../app && yarn dist",
|
||||
@@ -102,11 +107,12 @@
|
||||
"devDependencies": {
|
||||
"copyfiles": "^2.2.0",
|
||||
"cross-env": "^6.0.3",
|
||||
"electron": "13.6.3",
|
||||
"electron-builder": "22.14.5"
|
||||
"electron": "17.4.10",
|
||||
"electron-builder": "23.1.0",
|
||||
"electron-builder-notarize": "^1.5.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"better-sqlite3": "7.4.5",
|
||||
"msnodesqlv8": "^2.4.4"
|
||||
"better-sqlite3": "7.6.2",
|
||||
"msnodesqlv8": "^2.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ const { settings } = require('cluster');
|
||||
|
||||
const configRootPath = path.join(app.getPath('userData'), 'config-root.json');
|
||||
let initialConfig = {};
|
||||
let apiLoaded = false;
|
||||
let mainModule;
|
||||
|
||||
const isMac = () => os.platform() == 'darwin';
|
||||
|
||||
try {
|
||||
initialConfig = JSON.parse(fs.readFileSync(configRootPath, { encoding: 'utf-8' }));
|
||||
@@ -28,19 +32,11 @@ try {
|
||||
initialConfig = {};
|
||||
}
|
||||
|
||||
// let settingsJson = {};
|
||||
// try {
|
||||
// const datadir = path.join(os.homedir(), 'dbgate-data');
|
||||
// settingsJson = JSON.parse(fs.readFileSync(path.join(datadir, 'settings.json'), { encoding: 'utf-8' }));
|
||||
// } catch (err) {
|
||||
// console.log('Error loading settings.json:', err.message);
|
||||
// settingsJson = {};
|
||||
// }
|
||||
|
||||
// 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;
|
||||
let mainMenu;
|
||||
let runCommandOnLoad = null;
|
||||
|
||||
log.transports.file.level = 'debug';
|
||||
autoUpdater.logger = log;
|
||||
@@ -49,30 +45,72 @@ autoUpdater.logger = log;
|
||||
|
||||
let commands = {};
|
||||
|
||||
function commandItem(id) {
|
||||
function formatKeyText(keyText) {
|
||||
if (!keyText) {
|
||||
return keyText;
|
||||
}
|
||||
if (os.platform() == 'darwin') {
|
||||
return keyText.replace('CtrlOrCommand+', 'Command+');
|
||||
}
|
||||
return keyText.replace('CtrlOrCommand+', 'Ctrl+');
|
||||
}
|
||||
|
||||
function commandItem(item) {
|
||||
const id = item.command;
|
||||
const command = commands[id];
|
||||
if (item.skipInApp) {
|
||||
return { skip: true };
|
||||
}
|
||||
return {
|
||||
id,
|
||||
label: command ? command.menuName || command.toolbarName || command.name : id,
|
||||
accelerator: command ? command.keyText : undefined,
|
||||
accelerator: formatKeyText(command ? command.keyText : undefined),
|
||||
enabled: command ? command.enabled : false,
|
||||
click() {
|
||||
mainWindow.webContents.send('run-command', id);
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('run-command', id);
|
||||
} else {
|
||||
runCommandOnLoad = id;
|
||||
createWindow();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildMenu() {
|
||||
const template = _cloneDeepWith(mainMenuDefinition, item => {
|
||||
let template = _cloneDeepWith(mainMenuDefinition({ editMenu: true }), item => {
|
||||
if (item.divider) {
|
||||
return { type: 'separator' };
|
||||
}
|
||||
|
||||
if (item.command) {
|
||||
return commandItem(item.command);
|
||||
return commandItem(item);
|
||||
}
|
||||
});
|
||||
|
||||
template = _cloneDeepWith(template, item => {
|
||||
if (Array.isArray(item) && item.find(x => x.skip)) {
|
||||
return item.filter(x => x && !x.skip);
|
||||
}
|
||||
});
|
||||
|
||||
if (isMac()) {
|
||||
template = [
|
||||
{
|
||||
label: 'DbGate',
|
||||
submenu: [
|
||||
commandItem({ command: 'about.show' }),
|
||||
{ role: 'services' },
|
||||
{ role: 'hide' },
|
||||
{ role: 'hideOthers' },
|
||||
{ role: 'unhide' },
|
||||
{ role: 'quit' },
|
||||
],
|
||||
},
|
||||
...template,
|
||||
];
|
||||
}
|
||||
|
||||
return Menu.buildFromTemplate(template);
|
||||
}
|
||||
|
||||
@@ -86,15 +124,21 @@ ipcMain.on('update-commands', async (event, arg) => {
|
||||
// rebuild menu
|
||||
if (menu.label != command.text || menu.accelerator != command.keyText) {
|
||||
mainMenu = buildMenu();
|
||||
mainWindow.setMenu(mainMenu);
|
||||
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
// mainWindow.setMenu(mainMenu);
|
||||
return;
|
||||
}
|
||||
|
||||
menu.enabled = command.enabled;
|
||||
}
|
||||
});
|
||||
ipcMain.on('close-window', async (event, arg) => {
|
||||
mainWindow.close();
|
||||
ipcMain.on('quit-app', async (event, arg) => {
|
||||
if (isMac()) {
|
||||
app.quit();
|
||||
} else {
|
||||
mainWindow.close();
|
||||
}
|
||||
});
|
||||
ipcMain.on('set-title', async (event, arg) => {
|
||||
mainWindow.setTitle(arg);
|
||||
@@ -102,7 +146,19 @@ ipcMain.on('set-title', async (event, arg) => {
|
||||
ipcMain.on('open-link', async (event, arg) => {
|
||||
electron.shell.openExternal(arg);
|
||||
});
|
||||
ipcMain.on('open-dev-tools', () => {
|
||||
mainWindow.webContents.openDevTools();
|
||||
});
|
||||
ipcMain.on('app-started', async (event, arg) => {
|
||||
if (runCommandOnLoad) {
|
||||
mainWindow.webContents.send('run-command', runCommandOnLoad);
|
||||
runCommandOnLoad = null;
|
||||
}
|
||||
});
|
||||
ipcMain.on('window-action', async (event, arg) => {
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
switch (arg) {
|
||||
case 'minimize':
|
||||
mainWindow.minimize();
|
||||
@@ -138,6 +194,23 @@ ipcMain.on('window-action', async (event, arg) => {
|
||||
case 'zoomreset':
|
||||
mainWindow.webContents.zoomLevel = 0;
|
||||
break;
|
||||
|
||||
// edit
|
||||
case 'undo':
|
||||
mainWindow.webContents.undo();
|
||||
break;
|
||||
case 'redo':
|
||||
mainWindow.webContents.redo();
|
||||
break;
|
||||
case 'cut':
|
||||
mainWindow.webContents.cut();
|
||||
break;
|
||||
case 'copy':
|
||||
mainWindow.webContents.copy();
|
||||
break;
|
||||
case 'paste':
|
||||
mainWindow.webContents.paste();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -161,7 +234,8 @@ function fillMissingSettings(value) {
|
||||
...value,
|
||||
};
|
||||
if (value['app.useNativeMenu'] !== true && value['app.useNativeMenu'] !== false) {
|
||||
res['app.useNativeMenu'] = os.platform() == 'darwin' ? true : false;
|
||||
res['app.useNativeMenu'] = false;
|
||||
// res['app.useNativeMenu'] = os.platform() == 'darwin' ? true : false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -169,7 +243,7 @@ function fillMissingSettings(value) {
|
||||
function createWindow() {
|
||||
let settingsJson = {};
|
||||
try {
|
||||
const datadir = path.join(os.homedir(), 'dbgate-data');
|
||||
const datadir = path.join(os.homedir(), '.dbgate');
|
||||
settingsJson = fillMissingSettings(
|
||||
JSON.parse(fs.readFileSync(path.join(datadir, 'settings.json'), { encoding: 'utf-8' }))
|
||||
);
|
||||
@@ -236,25 +310,29 @@ function createWindow() {
|
||||
// mainWindow.webContents.toggleDevTools();
|
||||
}
|
||||
|
||||
const apiPackage = path.join(
|
||||
__dirname,
|
||||
process.env.DEVMODE ? '../../packages/api/src/index' : '../packages/api/dist/bundle.js'
|
||||
);
|
||||
if (!apiLoaded) {
|
||||
const apiPackage = path.join(
|
||||
__dirname,
|
||||
process.env.DEVMODE ? '../../packages/api/src/index' : '../packages/api/dist/bundle.js'
|
||||
);
|
||||
|
||||
global.API_PACKAGE = apiPackage;
|
||||
global.NATIVE_MODULES = path.join(__dirname, 'nativeModules');
|
||||
global.API_PACKAGE = apiPackage;
|
||||
global.NATIVE_MODULES = path.join(__dirname, 'nativeModules');
|
||||
|
||||
// console.log('global.API_PACKAGE', global.API_PACKAGE);
|
||||
const api = require(apiPackage);
|
||||
// console.log(
|
||||
// 'REQUIRED',
|
||||
// path.resolve(
|
||||
// path.join(__dirname, process.env.DEVMODE ? '../../packages/api/src/index' : '../packages/api/dist/bundle.js')
|
||||
// )
|
||||
// );
|
||||
const main = api.getMainModule();
|
||||
main.initializeElectronSender(mainWindow.webContents);
|
||||
main.useAllControllers(null, electron);
|
||||
// console.log('global.API_PACKAGE', global.API_PACKAGE);
|
||||
const api = require(apiPackage);
|
||||
// console.log(
|
||||
// 'REQUIRED',
|
||||
// path.resolve(
|
||||
// path.join(__dirname, process.env.DEVMODE ? '../../packages/api/src/index' : '../packages/api/dist/bundle.js')
|
||||
// )
|
||||
// );
|
||||
const main = api.getMainModule();
|
||||
main.useAllControllers(null, electron);
|
||||
mainModule = main;
|
||||
apiLoaded = true;
|
||||
}
|
||||
mainModule.setElectronSender(mainWindow.webContents);
|
||||
|
||||
loadMainWindow();
|
||||
|
||||
@@ -264,6 +342,7 @@ function createWindow() {
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
mainWindow = null;
|
||||
mainModule.setElectronSender(null);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -283,7 +362,7 @@ app.on('ready', onAppReady);
|
||||
app.on('window-all-closed', function () {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
if (!isMac()) {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = [
|
||||
module.exports = ({ editMenu }) => [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
@@ -6,6 +6,9 @@ module.exports = [
|
||||
{ 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 },
|
||||
@@ -17,6 +20,7 @@ module.exports = [
|
||||
{ command: 'group.saveAs', hideDisabled: true },
|
||||
{ divider: true },
|
||||
{ command: 'file.exit', hideDisabled: true },
|
||||
{ command: 'app.logout', hideDisabled: true, skipInApp: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -33,6 +37,20 @@ module.exports = [
|
||||
],
|
||||
},
|
||||
|
||||
editMenu
|
||||
? {
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ command: 'edit.undo' },
|
||||
{ command: 'edit.redo' },
|
||||
{ divider: true },
|
||||
{ command: 'edit.cut' },
|
||||
{ command: 'edit.copy' },
|
||||
{ command: 'edit.paste' },
|
||||
],
|
||||
}
|
||||
: null,
|
||||
|
||||
// {
|
||||
// label: 'Edit',
|
||||
// submenu: [
|
||||
@@ -51,6 +69,7 @@ module.exports = [
|
||||
{ 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' },
|
||||
@@ -61,6 +80,7 @@ module.exports = [
|
||||
submenu: [
|
||||
{ command: 'database.search', hideDisabled: true },
|
||||
{ command: 'commandPalette.show', hideDisabled: true },
|
||||
{ command: 'database.switch', hideDisabled: true },
|
||||
{ divider: true },
|
||||
{ command: 'sql.generator', hideDisabled: true },
|
||||
{ command: 'file.import', hideDisabled: true },
|
||||
@@ -75,6 +95,7 @@ module.exports = [
|
||||
{ command: 'app.openIssue', hideDisabled: true },
|
||||
{ command: 'app.openSponsoring', hideDisabled: true },
|
||||
{ divider: true },
|
||||
{ command: 'settings.commands', hideDisabled: true },
|
||||
{ command: 'tabs.changelog', hideDisabled: true },
|
||||
{ command: 'about.show', hideDisabled: true },
|
||||
],
|
||||
|
||||
@@ -5,6 +5,7 @@ services:
|
||||
dbgate:
|
||||
build: docker
|
||||
# image: dbgate/dbgate:beta-alpine
|
||||
# image: dbgate/dbgate:alpine
|
||||
# image: dbgate/dbgate:beta
|
||||
restart: always
|
||||
ports:
|
||||
@@ -13,10 +14,10 @@ services:
|
||||
# - /home/jena/dbgate-data:/root/dbgate-data
|
||||
|
||||
volumes:
|
||||
- dbgate-data:/root/dbgate-data
|
||||
- dbgate-data:/root/.dbgate
|
||||
|
||||
environment:
|
||||
WEB_ROOT: /dbgate
|
||||
# environment:
|
||||
# WEB_ROOT: /dbgate
|
||||
|
||||
# CONNECTIONS: mssql
|
||||
# LABEL_mssql: MS Sql
|
||||
@@ -25,11 +26,11 @@ services:
|
||||
# PORT_mssql: 1433
|
||||
# PASSWORD_mssql: Pwd2020Db
|
||||
# ENGINE_mssql: mssql@dbgate-plugin-mssql
|
||||
proxy:
|
||||
# image: nginx
|
||||
build: test/nginx
|
||||
ports:
|
||||
- 8082:80
|
||||
# proxy:
|
||||
# # image: nginx
|
||||
# build: test/nginx
|
||||
# ports:
|
||||
# - 8082:80
|
||||
|
||||
# volumes:
|
||||
# - /home/jena/test/chinook:/mnt/sqt
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
FROM node:14
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
iputils-ping \
|
||||
iproute2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /home/dbgate-docker
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN ["chmod", "+x", "/home/dbgate-docker/entrypoint.sh"]
|
||||
|
||||
WORKDIR /home/dbgate-docker
|
||||
EXPOSE 3000
|
||||
VOLUME /root/dbgate-data
|
||||
CMD node bundle.js
|
||||
VOLUME /root/.dbgate
|
||||
|
||||
CMD ["/home/dbgate-docker/entrypoint.sh"]
|
||||
|
||||
@@ -2,9 +2,16 @@ FROM node:14-alpine
|
||||
|
||||
WORKDIR /home/dbgate-docker
|
||||
|
||||
RUN apk --no-cache upgrade \
|
||||
&& apk --no-cache add \
|
||||
iputils
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN ["chmod", "+x", "/home/dbgate-docker/entrypoint.sh"]
|
||||
|
||||
WORKDIR /home/dbgate-docker
|
||||
EXPOSE 3000
|
||||
VOLUME /root/dbgate-data
|
||||
CMD node bundle.js
|
||||
VOLUME /root/.dbgate
|
||||
|
||||
CMD ["/home/dbgate-docker/entrypoint.sh"]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
HOST_DOMAIN="dockerhost"
|
||||
ping -q -c1 $HOST_DOMAIN > /dev/null 2>&1
|
||||
if [ $? != 0 ]
|
||||
then
|
||||
HOST_IP=$(ip route | awk 'NR==1 {print $3}')
|
||||
echo "$HOST_IP $HOST_DOMAIN" >> /etc/hosts
|
||||
fi
|
||||
|
||||
node bundle.js --listen-api
|
||||
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 166 KiB |
@@ -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');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ const txMatch = (tname, vcolname, nextcol) =>
|
||||
expect.objectContaining({
|
||||
columnName: 'id',
|
||||
notNull: true,
|
||||
dataType: expect.stringContaining('int'),
|
||||
dataType: expect.stringMatching(/int/i),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
columnName: vcolname,
|
||||
|
||||
@@ -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',
|
||||
'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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dbgate-integration-tests",
|
||||
"version": "4.1.1",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# magick icon.svg -resize 1024x1024 -transparent white -background transparent app/icon1024.png
|
||||
|
||||
# magick app/icon1024.png -resize 512x512 app/icon512.png
|
||||
# magick app/icon1024.png -resize 256x256 app/icon.png
|
||||
# magick app/icon1024.png -resize 32x32 app/icon32.png
|
||||
# magick icon.svg -define icon:auto-resize="256,128,96,64,48,32,16" -transparent white -background transparent app/icon.ico
|
||||
|
||||
# # magick icon.svg -resize 512x512 -transparent white -background transparent app/icon512.png
|
||||
# # magick icon.svg -resize 256x256 -transparent white -background transparent app/icon.png
|
||||
# # magick icon.svg -resize 32x32 -transparent white -background transparent app/icon32.png
|
||||
# # magick icon.svg -define icon:auto-resize="256,128,96,64,48,32,16" -transparent white -background transparent app/icon.ico
|
||||
|
||||
# magick app/icon1024.png -resize 512x512 app/icons/512x512.png
|
||||
# magick app/icon1024.png -resize 256x256 app/icons/256x256.png
|
||||
# magick app/icon1024.png -resize 128x128 app/icons/128x128.png
|
||||
# magick app/icon1024.png -resize 64x64 app/icons/64x64.png
|
||||
# magick app/icon1024.png -resize 48x48 app/icons/48x48.png
|
||||
# magick app/icon1024.png -resize 32x32 app/icons/32x32.png
|
||||
# magick app/icon1024.png -resize 16x16 app/icons/16x16.png
|
||||
|
||||
# # magick icon.svg -resize 16x16 -transparent white -background transparent app/icons/16x16.png
|
||||
# # magick icon.svg -resize 32x32 -transparent white -background transparent app/icons/32x32.png
|
||||
# # magick icon.svg -resize 48x48 -transparent white -background transparent app/icons/48x48.png
|
||||
# # magick icon.svg -resize 64x64 -transparent white -background transparent app/icons/64x64.png
|
||||
# # magick icon.svg -resize 128x128 -transparent white -background transparent app/icons/128x128.png
|
||||
# # magick icon.svg -resize 256x256 -transparent white -background transparent app/icons/256x256.png
|
||||
# # magick icon.svg -resize 512x512 -transparent white -background transparent app/icons/512x512.png
|
||||
|
||||
# magick icon.svg -resize 1024x1024 icon.png
|
||||
# magick icon.svg -resize 1024x1024 -transparent white -background transparent icon.png
|
||||
|
||||
STROKE_WIDTH=30
|
||||
LEFT=150
|
||||
RIGHT=850
|
||||
|
||||
|
||||
|
||||
magick \
|
||||
\( \
|
||||
-size 1000x1000 -define gradient:direction=east 'gradient:#0050b3-#1890ff' \
|
||||
\( +clone -fill Black -colorize 100 \
|
||||
-fill White -stroke White -draw "arc $LEFT,750 $RIGHT,950 0,360" -draw "rectangle $LEFT,150 $RIGHT,850" \
|
||||
\) \
|
||||
-alpha off \
|
||||
-compose CopyOpacity -composite \
|
||||
\) \
|
||||
\( \
|
||||
-size 1000x1000 -define gradient:direction=east 'gradient:#096dd9-#40a9ff' \
|
||||
\( +clone -fill Black -colorize 100 \
|
||||
-fill White -draw "arc $LEFT,50 $RIGHT,250 0,360" \
|
||||
\) \
|
||||
-alpha off \
|
||||
-compose CopyOpacity -composite \
|
||||
\) \
|
||||
-compose Over -composite \
|
||||
-strokewidth $STROKE_WIDTH -stroke '#0050b3' -fill transparent \
|
||||
-draw "arc $LEFT,225 $RIGHT,425 0,180" \
|
||||
-draw "arc $LEFT,400 $RIGHT,600 0,180" \
|
||||
-draw "arc $LEFT,575 $RIGHT,775 0,180" \
|
||||
-draw "arc $LEFT,750 $RIGHT,950 0,180" \
|
||||
-draw "arc $LEFT,50 $RIGHT,250 0,360" \
|
||||
-draw "line $LEFT,150 $LEFT,850" \
|
||||
-draw "line $RIGHT,150 $RIGHT,850" \
|
||||
-fill '#fafafa' -stroke '#8c8c8c' -strokewidth 3 \
|
||||
-pointsize 800 -font './Mcbungus-Regular.ttf' \
|
||||
-gravity center \
|
||||
-draw 'text 0,100 "G"' \
|
||||
icon.png
|
||||
|
||||
|
||||
# magick \
|
||||
# \( \
|
||||
# -size 300x300 gradient:red-blue \
|
||||
# \( +clone -fill Black -colorize 100 \
|
||||
# -fill White -draw "polygon 50,50 250,50 200,200" \
|
||||
# \) \
|
||||
# -alpha off \
|
||||
# -compose CopyOpacity -composite \
|
||||
# \) \
|
||||
# \( \
|
||||
# -size 300x300 'gradient:#f80-#08f' \
|
||||
# \( +clone -fill Black -colorize 100 \
|
||||
# -fill White -draw "polygon 50,150 250,150 200,300" \
|
||||
# \) \
|
||||
# -alpha off \
|
||||
# -compose CopyOpacity -composite \
|
||||
# \) \
|
||||
# -compose Over -composite \
|
||||
# icon.png
|
||||
|
||||
magick icon.png -resize 512x512! ../app/icon512.png
|
||||
magick icon.png -resize 256x256! ../app/icon.png
|
||||
magick icon.png -resize 32x32! ../app/icon32.png
|
||||
magick icon.png -define icon:auto-resize="256,128,96,64,48,32,16" ../app/icon.ico
|
||||
|
||||
magick icon.png -resize 512x512! ../app/icons/512x512.png
|
||||
magick icon.png -resize 256x256! ../app/icons/256x256.png
|
||||
magick icon.png -resize 128x128! ../app/icons/128x128.png
|
||||
magick icon.png -resize 64x64! ../app/icons/64x64.png
|
||||
magick icon.png -resize 48x48! ../app/icons/48x48.png
|
||||
magick icon.png -resize 32x32! ../app/icons/32x32.png
|
||||
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
|
||||
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 134 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": "4.7.2",
|
||||
"version": "5.1.6-beta.7",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
@@ -10,20 +10,23 @@
|
||||
"scripts": {
|
||||
"start:api": "yarn workspace dbgate-api start",
|
||||
"start:app": "cd app && yarn start",
|
||||
"start:api:debug": "cross-env DEBUG=* yarn workspace dbgate-api start",
|
||||
"start:app:debug": "cd app && cross-env DEBUG=* yarn start",
|
||||
"start:api:debug:ssh": "cross-env DEBUG=ssh yarn workspace dbgate-api start",
|
||||
"start:app:debug:ssh": "cd app && cross-env DEBUG=ssh yarn start",
|
||||
"start:api:portal": "yarn workspace dbgate-api start:portal",
|
||||
"start:api:covid": "yarn workspace dbgate-api start:covid",
|
||||
"start:api:singledb": "yarn workspace dbgate-api start:singledb",
|
||||
"start:api:auth": "yarn workspace dbgate-api start:auth",
|
||||
"start:web": "yarn workspace dbgate-web dev",
|
||||
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
||||
"start:tools": "yarn workspace dbgate-tools start",
|
||||
"start:datalib": "yarn workspace dbgate-datalib start",
|
||||
"start:filterparser": "yarn workspace dbgate-filterparser start",
|
||||
"start:querysplitter": "yarn workspace dbgate-query-splitter start",
|
||||
"build:sqltree": "yarn workspace dbgate-sqltree build",
|
||||
"build:datalib": "yarn workspace dbgate-datalib build",
|
||||
"build:filterparser": "yarn workspace dbgate-filterparser build",
|
||||
"build:querysplitter": "yarn workspace dbgate-query-splitter build",
|
||||
"build:tools": "yarn workspace dbgate-tools build",
|
||||
"build:lib": "yarn build:querysplitter && yarn build:tools && yarn build:sqltree && yarn build:filterparser && yarn build:datalib",
|
||||
"build:lib": "yarn build:sqltree && yarn build:tools && yarn build:filterparser && yarn build:datalib",
|
||||
"build:app": "yarn plugins:copydist && cd app && yarn install && yarn build",
|
||||
"build:api": "yarn workspace dbgate-api build",
|
||||
"build:web:docker": "yarn workspace dbgate-web build",
|
||||
@@ -34,6 +37,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",
|
||||
@@ -43,16 +47,16 @@
|
||||
"install:sqlite:docker": "cd docker && yarn init --yes && yarn add better-sqlite3 && cd ..",
|
||||
"prepare:docker": "yarn plugins:copydist && yarn build:web:docker && yarn build:api && yarn copy:docker:build && yarn install:sqlite:docker",
|
||||
"start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"",
|
||||
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn start:querysplitter\" \"yarn build:plugins:frontend:watch\"",
|
||||
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn build:plugins:frontend:watch\"",
|
||||
"ts:api": "yarn workspace dbgate-api ts",
|
||||
"ts:web": "yarn workspace dbgate-web ts",
|
||||
"ts": "yarn ts:api && yarn ts:web",
|
||||
"postinstall": "yarn resetPackagedPlugins && yarn build:lib && patch-package && yarn fillNativeModules && yarn build:plugins:frontend"
|
||||
"postinstall": "yarn resetPackagedPlugins && yarn build:lib && patch-package && yarn fillNativeModules && yarn build:plugins:frontend",
|
||||
"dbgate-serve": "node packages/dbgate/bin/dbgate-serve.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"concurrently": "^5.1.0",
|
||||
"patch-package": "^6.2.1",
|
||||
"socket.io": "^2.3.0"
|
||||
"patch-package": "^6.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copyfiles": "^2.2.0",
|
||||
|
||||
@@ -1 +1,17 @@
|
||||
DEVMODE=1
|
||||
SHELL_SCRIPTING=1
|
||||
|
||||
# PERMISSIONS=~widgets/app,~widgets/plugins
|
||||
# DISABLE_SHELL=1
|
||||
# HIDE_APP_EDITOR=1
|
||||
|
||||
|
||||
# DEVWEB=1
|
||||
# LOGINS=admin,test
|
||||
|
||||
# LOGIN_PASSWORD_admin=admin
|
||||
# LOGIN_PERMISSIONS_admin=*
|
||||
|
||||
# LOGIN_PASSWORD_test=test
|
||||
# LOGIN_PERMISSIONS_test=~*, widgets/database
|
||||
# WORKSPACE_DIR=/home/jena/dbgate-data-2
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.env
|
||||
@@ -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
|
||||
@@ -38,7 +38,25 @@ SSH_LOGIN_mysqlssh=root
|
||||
SSH_PASSWORD_mysqlssh=xxx
|
||||
|
||||
LABEL_sqlite=sqlite
|
||||
FILE_sqlite=/home/jena/dbgate-data/files/sqlite/feeds.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=~*
|
||||
@@ -8,6 +8,8 @@ USER_mysql=root
|
||||
PASSWORD_mysql=test
|
||||
PORT_mysql=3307
|
||||
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
||||
DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]
|
||||
|
||||
|
||||
SINGLE_CONNECTION=mysql
|
||||
SINGLE_DATABASE=Chinook
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "dbgate-api",
|
||||
"main": "src/index.js",
|
||||
"version": "4.1.1",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -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,9 +26,10 @@
|
||||
"compare-versions": "^3.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^6.0.3",
|
||||
"dbgate-query-splitter": "^4.1.1",
|
||||
"dbgate-sqltree": "^4.1.1",
|
||||
"dbgate-tools": "^4.1.1",
|
||||
"dbgate-query-splitter": "^4.9.2",
|
||||
"dbgate-sqltree": "^5.0.0-alpha.1",
|
||||
"dbgate-tools": "^5.0.0-alpha.1",
|
||||
"debug": "^4.3.4",
|
||||
"diff": "^5.0.0",
|
||||
"diff2html": "^3.4.13",
|
||||
"eslint": "^6.8.0",
|
||||
@@ -41,29 +43,32 @@
|
||||
"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",
|
||||
"ncp": "^2.0.0",
|
||||
"node-cron": "^2.0.3",
|
||||
"node-ssh-forward": "^0.7.2",
|
||||
"on-finished": "^2.4.1",
|
||||
"portfinder": "^1.0.28",
|
||||
"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 node src/index.js",
|
||||
"start:singledb": "env-cmd -f .env-singledb 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: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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"dbgate-types": "^4.1.1",
|
||||
"dbgate-types": "^5.0.0-alpha.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"node-loader": "^1.0.2",
|
||||
"nodemon": "^2.0.2",
|
||||
@@ -72,7 +77,7 @@
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"better-sqlite3": "7.4.5",
|
||||
"msnodesqlv8": "^2.4.4"
|
||||
"better-sqlite3": "7.6.2",
|
||||
"msnodesqlv8": "^2.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,4 +265,16 @@ module.exports = {
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
createConfigFile_meta: true,
|
||||
async createConfigFile({ appFolder, fileName, content }) {
|
||||
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('used-apps-changed');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
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');
|
||||
|
||||
@@ -45,29 +42,34 @@ 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) {
|
||||
console.log('Error reading archive files', err.message);
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
const axios = require('axios');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const getExpressPath = require('../utility/getExpressPath');
|
||||
const uuidv1 = require('uuid/v1');
|
||||
const { getLogins } = require('../utility/hasPermission');
|
||||
const AD = require('activedirectory2').promiseWrapper;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
console.log('Sending invalid token error', err.message);
|
||||
|
||||
return unauthorizedResponse(req, res, 'invalid token');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
oauthToken_meta: true,
|
||||
async oauthToken(params) {
|
||||
const { redirectUri, code } = params;
|
||||
|
||||
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}`
|
||||
);
|
||||
|
||||
const { access_token, refresh_token } = resp.data;
|
||||
|
||||
const payload = jwt.decode(access_token);
|
||||
|
||||
console.log('User payload returned from OAUTH:', payload);
|
||||
|
||||
const login = 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) {
|
||||
console.log('Failed active directory authentization', err.message);
|
||||
return {
|
||||
error: err.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const logins = getLogins();
|
||||
if (!logins) {
|
||||
return { error: 'Logins not configured' };
|
||||
}
|
||||
if (logins.find(x => x.login == login)?.password == password) {
|
||||
return {
|
||||
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
||||
};
|
||||
}
|
||||
return { error: 'Invalid credentials' };
|
||||
},
|
||||
|
||||
authMiddleware,
|
||||
shouldAuthorizeApi,
|
||||
};
|
||||
@@ -3,7 +3,7 @@ const os = require('os');
|
||||
const path = require('path');
|
||||
const axios = require('axios');
|
||||
const { datadir } = require('../utility/directories');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
const { hasPermission, getLogins } = require('../utility/hasPermission');
|
||||
const socket = require('../utility/socket');
|
||||
const _ = require('lodash');
|
||||
const AsyncLock = require('async-lock');
|
||||
@@ -26,17 +26,35 @@ module.exports = {
|
||||
// },
|
||||
|
||||
get_meta: true,
|
||||
async get() {
|
||||
const permissions = process.env.PERMISSIONS ? process.env.PERMISSIONS.split(',') : null;
|
||||
async get(_params, req) {
|
||||
const logins = getLogins();
|
||||
const login = req.user ? req.user.login : logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null;
|
||||
const permissions = login ? login.permissions : process.env.PERMISSIONS;
|
||||
|
||||
return {
|
||||
runAsPortal: !!connections.portalConnections,
|
||||
singleDatabase: connections.singleDatabase,
|
||||
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
|
||||
allowShellConnection: platformInfo.allowShellConnection,
|
||||
allowShellScripting: platformInfo.allowShellScripting,
|
||||
isDocker: platformInfo.isDocker,
|
||||
permissions,
|
||||
login,
|
||||
oauth: process.env.OAUTH_AUTH,
|
||||
oauthLogout: process.env.OAUTH_LOGOUT,
|
||||
isLoginForm: !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH),
|
||||
...currentVersion,
|
||||
};
|
||||
},
|
||||
|
||||
logout_meta: {
|
||||
method: 'get',
|
||||
raw: true,
|
||||
},
|
||||
logout(req, res) {
|
||||
res.status(401).send('Logged out<br><a href="../..">Back to DbGate</a>');
|
||||
},
|
||||
|
||||
platformInfo_meta: true,
|
||||
async platformInfo() {
|
||||
return platformInfo;
|
||||
@@ -44,13 +62,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) {
|
||||
@@ -58,17 +73,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;
|
||||
},
|
||||
|
||||
updateSettings_meta: true,
|
||||
async updateSettings(values) {
|
||||
if (!hasPermission(`settings/change`)) return false;
|
||||
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({});
|
||||
}
|
||||
},
|
||||
|
||||
const res = await lock.acquire('update', async () => {
|
||||
const currentValue = await this.getSettings();
|
||||
updateSettings_meta: true,
|
||||
async updateSettings(values, req) {
|
||||
if (!hasPermission(`settings/change`, req)) return false;
|
||||
|
||||
const res = await lock.acquire('settings', async () => {
|
||||
const currentValue = await this.loadSettings();
|
||||
try {
|
||||
const updated = {
|
||||
...currentValue,
|
||||
|
||||
@@ -5,12 +5,15 @@ const fs = require('fs-extra');
|
||||
|
||||
const { datadir, filesdir } = require('../utility/directories');
|
||||
const socket = require('../utility/socket');
|
||||
const { encryptConnection } = require('../utility/crypting');
|
||||
const { encryptConnection, maskConnection } = require('../utility/crypting');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const { pickSafeConnectionInfo } = require('../utility/crypting');
|
||||
const JsonLinesDatabase = require('../utility/JsonLinesDatabase');
|
||||
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { safeJsonParse } = require('dbgate-tools');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
||||
|
||||
function getNamedArgs() {
|
||||
const res = {};
|
||||
@@ -50,11 +53,16 @@ function getPortalCollections() {
|
||||
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),
|
||||
singleDatabase: !!process.env[`DATABASE_${id}`] || !!process.env[`FILE_${id}`],
|
||||
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}`],
|
||||
@@ -161,8 +169,12 @@ module.exports = {
|
||||
},
|
||||
|
||||
list_meta: true,
|
||||
async list() {
|
||||
return portalConnections || 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,
|
||||
@@ -199,6 +211,9 @@ module.exports = {
|
||||
}
|
||||
socket.emitChanged('connection-list-changed');
|
||||
socket.emitChanged('used-apps-changed');
|
||||
if (this._closeAll) {
|
||||
this._closeAll(connection._id);
|
||||
}
|
||||
// for (const db of connection.databases || []) {
|
||||
// socket.emitChanged(`db-apps-changed-${connection._id}-${db.name}`);
|
||||
// }
|
||||
@@ -206,16 +221,18 @@ 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;
|
||||
},
|
||||
|
||||
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)) {
|
||||
@@ -231,20 +248,30 @@ 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;
|
||||
},
|
||||
|
||||
get_meta: true,
|
||||
async get({ conid }) {
|
||||
if (portalConnections) return portalConnections.find(x => x._id == conid) || null;
|
||||
async getCore({ conid, mask = false }) {
|
||||
if (!conid) return null;
|
||||
if (portalConnections) {
|
||||
const res = portalConnections.find(x => x._id == conid) || null;
|
||||
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
||||
}
|
||||
const res = await this.datastore.get(conid);
|
||||
return res || null;
|
||||
},
|
||||
|
||||
get_meta: true,
|
||||
async get({ conid }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
return this.getCore({ conid, mask: true });
|
||||
},
|
||||
|
||||
newSqliteDatabase_meta: true,
|
||||
async newSqliteDatabase({ file }) {
|
||||
const sqliteDir = path.join(filesdir(), 'sqlite');
|
||||
|
||||
@@ -26,6 +26,7 @@ const generateDeploySql = require('../shell/generateDeploySql');
|
||||
const { createTwoFilesPatch } = require('diff');
|
||||
const diff2htmlPage = require('../utility/diff2htmlPage');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { testConnectionPermission } = require('../utility/hasPermission');
|
||||
|
||||
module.exports = {
|
||||
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
||||
@@ -33,6 +34,10 @@ module.exports = {
|
||||
closed: {},
|
||||
requests: {},
|
||||
|
||||
async _init() {
|
||||
connections._closeAll = conid => this.closeAll(conid);
|
||||
},
|
||||
|
||||
handle_structure(conid, database, { structure }) {
|
||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||
if (!existing) return;
|
||||
@@ -75,7 +80,7 @@ module.exports = {
|
||||
async ensureOpened(conid, database) {
|
||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||
if (existing) return existing;
|
||||
const connection = await connections.get({ conid });
|
||||
const connection = await connections.getCore({ conid });
|
||||
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
||||
'--is-forked-api',
|
||||
'--start-process',
|
||||
@@ -126,7 +131,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
queryData_meta: true,
|
||||
async queryData({ conid, database, sql }) {
|
||||
async queryData({ conid, database, sql }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
console.log(`Processing query, conid=${conid}, database=${database}, sql=${sql}`);
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
// if (opened && opened.status && opened.status.name == 'error') {
|
||||
@@ -136,8 +142,17 @@ module.exports = {
|
||||
return res;
|
||||
},
|
||||
|
||||
sqlSelect_meta: true,
|
||||
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 }) {
|
||||
async runScript({ conid, database, sql }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
console.log(`Processing script, conid=${conid}, database=${database}, sql=${sql}`);
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
const res = await this.sendRequest(opened, { msgtype: 'runScript', sql });
|
||||
@@ -145,21 +160,92 @@ module.exports = {
|
||||
},
|
||||
|
||||
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;
|
||||
return res.result || null;
|
||||
},
|
||||
|
||||
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) {
|
||||
console.error(res.errorMessage);
|
||||
|
||||
return {
|
||||
errorMessage: res.errorMessage,
|
||||
};
|
||||
}
|
||||
return res.result || null;
|
||||
},
|
||||
|
||||
loadKeys_meta: true,
|
||||
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 }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
return this.loadDataCore('exportKeys', { conid, database, options });
|
||||
},
|
||||
|
||||
loadKeyInfo_meta: true,
|
||||
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 }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
return this.loadDataCore('loadKeyTableRange', { conid, database, key, cursor, count });
|
||||
},
|
||||
|
||||
loadFieldValues_meta: true,
|
||||
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 }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
return this.loadDataCore('callMethod', { conid, database, method, args });
|
||||
|
||||
// const opened = await this.ensureOpened(conid, database);
|
||||
// const res = await this.sendRequest(opened, { msgtype: 'callMethod', method, args });
|
||||
// if (res.errorMessage) {
|
||||
// console.error(res.errorMessage);
|
||||
// }
|
||||
// return res.result || null;
|
||||
},
|
||||
|
||||
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 });
|
||||
return res.result;
|
||||
if (res.errorMessage) {
|
||||
return {
|
||||
errorMessage: res.errorMessage,
|
||||
};
|
||||
}
|
||||
return res.result || null;
|
||||
},
|
||||
|
||||
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 {
|
||||
@@ -181,7 +267,8 @@ 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) {
|
||||
@@ -197,7 +284,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);
|
||||
@@ -205,9 +293,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' };
|
||||
},
|
||||
|
||||
@@ -228,14 +317,22 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
closeAll(conid, kill = true) {
|
||||
for (const existing of this.opened.filter(x => x.conid == conid)) {
|
||||
this.close(conid, existing.database, kill);
|
||||
}
|
||||
},
|
||||
|
||||
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;
|
||||
@@ -252,14 +349,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 });
|
||||
|
||||
@@ -269,7 +371,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 });
|
||||
@@ -279,7 +382,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',
|
||||
@@ -325,8 +429,8 @@ module.exports = {
|
||||
const targetDb = generateDbPairingId(
|
||||
extendDatabaseInfo(await this.structure({ conid: targetConid, database: targetDatabase }))
|
||||
);
|
||||
// const sourceConnection = await connections.get({conid:sourceConid})
|
||||
const connection = await connections.get({ conid: targetConid });
|
||||
// const sourceConnection = await connections.getCore({conid:sourceConid})
|
||||
const connection = await connections.getCore({ conid: targetConid });
|
||||
const driver = requireEngineDriver(connection);
|
||||
const targetDbPaired = matchPairedObjects(sourceDb, targetDb, dbDiffOptions);
|
||||
const diffRows = computeDbDiffRows(sourceDb, targetDbPaired, dbDiffOptions, driver);
|
||||
|
||||
@@ -3,11 +3,12 @@ const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { filesdir, archivedir, resolveArchiveFolder, uploadsdir, appdir } = require('../utility/directories');
|
||||
const getChartExport = require('../utility/getChartExport');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
const { hasPermission } = require('../utility/hasPermission');
|
||||
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;
|
||||
@@ -23,8 +24,8 @@ function deserialize(format, text) {
|
||||
|
||||
module.exports = {
|
||||
list_meta: true,
|
||||
async list({ folder }) {
|
||||
if (!hasPermission(`files/${folder}/read`)) return [];
|
||||
async list({ folder }, req) {
|
||||
if (!hasPermission(`files/${folder}/read`, req)) return [];
|
||||
const dir = path.join(filesdir(), folder);
|
||||
if (!(await fs.exists(dir))) return [];
|
||||
const files = (await fs.readdir(dir)).map(file => ({ folder, file }));
|
||||
@@ -32,11 +33,11 @@ module.exports = {
|
||||
},
|
||||
|
||||
listAll_meta: true,
|
||||
async listAll() {
|
||||
async listAll(_params, req) {
|
||||
const folders = await fs.readdir(filesdir());
|
||||
const res = [];
|
||||
for (const folder of folders) {
|
||||
if (!hasPermission(`files/${folder}/read`)) continue;
|
||||
if (!hasPermission(`files/${folder}/read`, req)) continue;
|
||||
const dir = path.join(filesdir(), folder);
|
||||
const files = (await fs.readdir(dir)).map(file => ({ folder, file }));
|
||||
res.push(...files);
|
||||
@@ -45,31 +46,43 @@ module.exports = {
|
||||
},
|
||||
|
||||
delete_meta: true,
|
||||
async delete({ folder, file }) {
|
||||
if (!hasPermission(`files/${folder}/write`)) return;
|
||||
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(`all-files-changed`);
|
||||
return true;
|
||||
},
|
||||
|
||||
rename_meta: true,
|
||||
async rename({ folder, file, newFile }) {
|
||||
if (!hasPermission(`files/${folder}/write`)) return;
|
||||
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(`all-files-changed`);
|
||||
return true;
|
||||
},
|
||||
|
||||
refresh_meta: true,
|
||||
async refresh({ folders }, req) {
|
||||
for (const folder of folders) {
|
||||
socket.emitChanged(`files-changed-${folder}`);
|
||||
socket.emitChanged(`all-files-changed`);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
copy_meta: true,
|
||||
async copy({ folder, file, newFile }) {
|
||||
if (!hasPermission(`files/${folder}/write`)) return;
|
||||
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(`all-files-changed`);
|
||||
return true;
|
||||
},
|
||||
|
||||
load_meta: true,
|
||||
async load({ folder, file, format }) {
|
||||
async load({ folder, file, format }, req) {
|
||||
if (folder.startsWith('archive:')) {
|
||||
const text = await fs.readFile(path.join(resolveArchiveFolder(folder.substring('archive:'.length)), file), {
|
||||
encoding: 'utf-8',
|
||||
@@ -81,27 +94,36 @@ module.exports = {
|
||||
});
|
||||
return deserialize(format, text);
|
||||
} else {
|
||||
if (!hasPermission(`files/${folder}/read`)) return null;
|
||||
if (!hasPermission(`files/${folder}/read`, req)) return null;
|
||||
const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' });
|
||||
return deserialize(format, text);
|
||||
}
|
||||
},
|
||||
|
||||
loadFrom_meta: true,
|
||||
async loadFrom({ filePath, format }, req) {
|
||||
const text = await fs.readFile(filePath, { encoding: 'utf-8' });
|
||||
return deserialize(format, text);
|
||||
},
|
||||
|
||||
save_meta: true,
|
||||
async save({ folder, file, data, format }) {
|
||||
async save({ folder, file, data, format }, req) {
|
||||
if (folder.startsWith('archive:')) {
|
||||
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)}`);
|
||||
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('used-apps-changed');
|
||||
apps.emitChangedDbApp(folder);
|
||||
return true;
|
||||
} else {
|
||||
if (!hasPermission(`files/${folder}/write`)) return false;
|
||||
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
||||
const dir = path.join(filesdir(), folder);
|
||||
if (!(await fs.exists(dir))) {
|
||||
await fs.mkdir(dir);
|
||||
@@ -122,8 +144,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
favorites_meta: true,
|
||||
async favorites() {
|
||||
if (!hasPermission(`files/favorites/read`)) return [];
|
||||
async favorites(_params, req) {
|
||||
if (!hasPermission(`files/favorites/read`, req)) return [];
|
||||
const dir = path.join(filesdir(), 'favorites');
|
||||
if (!(await fs.exists(dir))) return [];
|
||||
const files = await fs.readdir(dir);
|
||||
@@ -141,8 +163,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
generateUploadsFile_meta: true,
|
||||
async generateUploadsFile() {
|
||||
const fileName = `${uuidv1()}.html`;
|
||||
async generateUploadsFile({ extension }) {
|
||||
const fileName = `${uuidv1()}.${extension || 'html'}`;
|
||||
return {
|
||||
fileName,
|
||||
filePath: path.join(uploadsdir(), fileName),
|
||||
@@ -166,9 +188,35 @@ 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));
|
||||
return true;
|
||||
},
|
||||
|
||||
getFileRealPath_meta: true,
|
||||
async getFileRealPath({ folder, file }, req) {
|
||||
if (folder.startsWith('archive:')) {
|
||||
if (!hasPermission(`archive/write`, req)) return false;
|
||||
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
|
||||
return path.join(dir, file);
|
||||
} else if (folder.startsWith('app:')) {
|
||||
if (!hasPermission(`apps/write`, req)) return false;
|
||||
const app = folder.substring('app:'.length);
|
||||
return path.join(appdir(), app, file);
|
||||
} else {
|
||||
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
||||
const dir = path.join(filesdir(), folder);
|
||||
if (!(await fs.exists(dir))) {
|
||||
await fs.mkdir(dir);
|
||||
}
|
||||
return path.join(dir, file);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const { filterName } = require('dbgate-tools');
|
||||
const fs = require('fs');
|
||||
const lineReader = require('line-reader');
|
||||
const _ = require('lodash');
|
||||
@@ -111,18 +112,22 @@ module.exports = {
|
||||
getInfo_meta: true,
|
||||
async getInfo({ jslid }) {
|
||||
const file = getJslFileName(jslid);
|
||||
const firstLine = await readFirstLine(file);
|
||||
if (firstLine) {
|
||||
const parsed = JSON.parse(firstLine);
|
||||
if (parsed.__isStreamHeader) {
|
||||
return parsed;
|
||||
try {
|
||||
const firstLine = await readFirstLine(file);
|
||||
if (firstLine) {
|
||||
const parsed = JSON.parse(firstLine);
|
||||
if (parsed.__isStreamHeader) {
|
||||
return parsed;
|
||||
}
|
||||
return {
|
||||
__isStreamHeader: true,
|
||||
__isDynamicStructure: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
__isStreamHeader: true,
|
||||
__isDynamicStructure: true,
|
||||
};
|
||||
return null;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getRows_meta: true,
|
||||
@@ -144,6 +149,19 @@ module.exports = {
|
||||
return {};
|
||||
},
|
||||
|
||||
loadFieldValues_meta: true,
|
||||
async loadFieldValues({ jslid, field, search }) {
|
||||
const datastore = await this.ensureDatastore(jslid);
|
||||
const res = new Set();
|
||||
await datastore.enumRows(row => {
|
||||
if (!filterName(search, row[field])) return true;
|
||||
res.add(row[field]);
|
||||
return res.size < 100;
|
||||
});
|
||||
// @ts-ignore
|
||||
return [...res].map(value => ({ value }));
|
||||
},
|
||||
|
||||
async notifyChangedStats(stats) {
|
||||
// console.log('SENDING STATS', JSON.stringify(stats));
|
||||
const datastore = this.datastores[stats.jslid];
|
||||
|
||||
@@ -7,7 +7,7 @@ const socket = require('../utility/socket');
|
||||
const compareVersions = require('compare-versions');
|
||||
const requirePlugin = require('../shell/requirePlugin');
|
||||
const downloadPackage = require('../utility/downloadPackage');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
const { hasPermission } = require('../utility/hasPermission');
|
||||
const _ = require('lodash');
|
||||
const packagedPluginsContent = require('../packagedPluginsContent');
|
||||
|
||||
@@ -73,7 +73,7 @@ module.exports = {
|
||||
const res = [];
|
||||
for (const packageName of _.union(files1, files2)) {
|
||||
if (packageName == 'dist') continue;
|
||||
// if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
|
||||
if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
|
||||
try {
|
||||
if (packagedContent && packagedContent[packageName]) {
|
||||
const manifest = {
|
||||
@@ -89,6 +89,12 @@ module.exports = {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
.then(x => JSON.parse(x));
|
||||
if (!manifest.keywords) {
|
||||
continue;
|
||||
}
|
||||
if (!manifest.keywords.includes('dbgateplugin')) {
|
||||
continue;
|
||||
}
|
||||
const readmeFile = path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'README.md');
|
||||
// @ts-ignore
|
||||
if (await fs.exists(readmeFile)) {
|
||||
@@ -109,8 +115,8 @@ module.exports = {
|
||||
// },
|
||||
|
||||
install_meta: true,
|
||||
async install({ packageName }) {
|
||||
if (!hasPermission(`plugins/install`)) return;
|
||||
async install({ packageName }, req) {
|
||||
if (!hasPermission(`plugins/install`, req)) return;
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
// @ts-ignore
|
||||
if (!(await fs.exists(dir))) {
|
||||
@@ -119,21 +125,23 @@ module.exports = {
|
||||
socket.emitChanged(`installed-plugins-changed`);
|
||||
// this.removedPlugins = this.removedPlugins.filter(x => x != packageName);
|
||||
// await this.saveRemovePlugins();
|
||||
return true;
|
||||
},
|
||||
|
||||
uninstall_meta: true,
|
||||
async uninstall({ packageName }) {
|
||||
if (!hasPermission(`plugins/install`)) return;
|
||||
async uninstall({ packageName }, req) {
|
||||
if (!hasPermission(`plugins/install`, req)) return;
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
await fs.rmdir(dir, { recursive: true });
|
||||
socket.emitChanged(`installed-plugins-changed`);
|
||||
// this.removedPlugins.push(packageName);
|
||||
await this.saveRemovePlugins();
|
||||
// await this.saveRemovePlugins();
|
||||
return true;
|
||||
},
|
||||
|
||||
upgrade_meta: true,
|
||||
async upgrade({ packageName }) {
|
||||
if (!hasPermission(`plugins/install`)) return;
|
||||
async upgrade({ packageName }, req) {
|
||||
if (!hasPermission(`plugins/install`, req)) return;
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
// @ts-ignore
|
||||
if (await fs.exists(dir)) {
|
||||
@@ -142,6 +150,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
socket.emitChanged(`installed-plugins-changed`);
|
||||
return true;
|
||||
},
|
||||
|
||||
command_meta: true,
|
||||
@@ -153,6 +162,7 @@ module.exports = {
|
||||
authTypes_meta: true,
|
||||
async authTypes({ engine }) {
|
||||
const packageName = extractPackageName(engine);
|
||||
if (!packageName) return null;
|
||||
const content = requirePlugin(packageName);
|
||||
const driver = content.drivers.find(x => x.engine == engine);
|
||||
if (!driver || !driver.getAuthTypes) return null;
|
||||
|
||||
@@ -6,9 +6,10 @@ 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 } = require('dbgate-tools');
|
||||
const { extractShellApiPlugins, extractShellApiFunctionName, jsonScriptToJavascript } = require('dbgate-tools');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
|
||||
function extractPlugins(script) {
|
||||
const requireRegex = /\s*\/\/\s*@require\s+([^\s]+)\s*\n/g;
|
||||
@@ -103,6 +104,7 @@ module.exports = {
|
||||
scriptFile,
|
||||
[
|
||||
'--checkParent', // ...process.argv.slice(3)
|
||||
'--is-forked-api',
|
||||
...processArgs.getPassArgs(),
|
||||
],
|
||||
{
|
||||
@@ -110,7 +112,7 @@ module.exports = {
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
||||
env: {
|
||||
...process.env,
|
||||
DBGATE_API: global['API_PACKAGE'] || global['dbgateApiModulePath'] || process.argv[1],
|
||||
DBGATE_API: global['API_PACKAGE'] || process.argv[1],
|
||||
..._.fromPairs(pluginNames.map(name => [`PLUGIN_${_.camelCase(name)}`, getPluginBackendPath(name)])),
|
||||
},
|
||||
}
|
||||
@@ -150,6 +152,16 @@ module.exports = {
|
||||
start_meta: true,
|
||||
async start({ script }) {
|
||||
const runid = uuidv1();
|
||||
|
||||
if (script.type == 'json') {
|
||||
const js = jsonScriptToJavascript(script);
|
||||
return this.startCore(runid, scriptTemplate(js, false));
|
||||
}
|
||||
|
||||
if (!platformInfo.allowShellScripting) {
|
||||
return { errorMessage: 'Shell scripting is not allowed' };
|
||||
}
|
||||
|
||||
return this.startCore(runid, scriptTemplate(script, false));
|
||||
},
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const cron = require('node-cron');
|
||||
const runners = require('./runners');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
const { hasPermission } = require('../utility/hasPermission');
|
||||
|
||||
const scheduleRegex = /\s*\/\/\s*@schedule\s+([^\n]+)\n/;
|
||||
|
||||
@@ -26,8 +26,8 @@ module.exports = {
|
||||
this.tasks.push(task);
|
||||
},
|
||||
|
||||
async reload() {
|
||||
if (!hasPermission('files/shell/read')) return;
|
||||
async reload(_params, req) {
|
||||
if (!hasPermission('files/shell/read', req)) return;
|
||||
const shellDir = path.join(filesdir(), 'shell');
|
||||
await this.unload();
|
||||
if (!(await fs.exists(shellDir))) return;
|
||||
|
||||
@@ -7,6 +7,7 @@ const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const lock = new AsyncLock();
|
||||
const config = require('./config');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { testConnectionPermission } = require('../utility/hasPermission');
|
||||
|
||||
module.exports = {
|
||||
opened: [],
|
||||
@@ -37,7 +38,7 @@ module.exports = {
|
||||
const res = await lock.acquire(conid, async () => {
|
||||
const existing = this.opened.find(x => x.conid == conid);
|
||||
if (existing) return existing;
|
||||
const connection = await connections.get({ conid });
|
||||
const connection = await connections.getCore({ conid });
|
||||
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
||||
'--is-forked-api',
|
||||
'--start-process',
|
||||
@@ -90,19 +91,22 @@ 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) {
|
||||
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;
|
||||
},
|
||||
@@ -132,7 +136,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
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,9 +145,20 @@ 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' };
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,8 +4,10 @@ const connections = require('./connections');
|
||||
const socket = require('../utility/socket');
|
||||
const { fork } = require('child_process');
|
||||
const jsldata = require('./jsldata');
|
||||
const path = require('path');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { appdir } = require('../utility/directories');
|
||||
|
||||
module.exports = {
|
||||
/** @type {import('dbgate-types').OpenedSession[]} */
|
||||
@@ -46,9 +48,18 @@ module.exports = {
|
||||
this.dispatchMessage(sesid, info);
|
||||
},
|
||||
|
||||
handle_done(sesid) {
|
||||
handle_done(sesid, props) {
|
||||
socket.emit(`session-done-${sesid}`);
|
||||
this.dispatchMessage(sesid, 'Query execution finished');
|
||||
if (!props.skipFinishedMessage) {
|
||||
this.dispatchMessage(sesid, 'Query execution finished');
|
||||
}
|
||||
const session = this.opened.find(x => x.sesid == sesid);
|
||||
if (session.loadingReader_jslid) {
|
||||
socket.emit(`session-jslid-done-${session.loadingReader_jslid}`);
|
||||
}
|
||||
if (session.killOnDone) {
|
||||
this.kill({ sesid });
|
||||
}
|
||||
},
|
||||
|
||||
handle_recordset(sesid, props) {
|
||||
@@ -60,12 +71,17 @@ module.exports = {
|
||||
jsldata.notifyChangedStats(stats);
|
||||
},
|
||||
|
||||
handle_initializeFile(sesid, props) {
|
||||
const { jslid } = props;
|
||||
socket.emit(`session-initialize-file-${jslid}`);
|
||||
},
|
||||
|
||||
handle_ping() {},
|
||||
|
||||
create_meta: true,
|
||||
async create({ conid, database }) {
|
||||
const sesid = uuidv1();
|
||||
const connection = await connections.get({ conid });
|
||||
const connection = await connections.getCore({ conid });
|
||||
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
||||
'--is-forked-api',
|
||||
'--start-process',
|
||||
@@ -87,6 +103,12 @@ module.exports = {
|
||||
if (handleProcessCommunication(message, subprocess)) return;
|
||||
this[`handle_${msgtype}`](sesid, message);
|
||||
});
|
||||
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 });
|
||||
return _.pick(newOpened, ['conid', 'database', 'sesid']);
|
||||
},
|
||||
@@ -105,6 +127,29 @@ module.exports = {
|
||||
return { state: 'ok' };
|
||||
},
|
||||
|
||||
executeReader_meta: true,
|
||||
async executeReader({ conid, database, sql, queryName, appFolder }) {
|
||||
const { sesid } = await this.create({ conid, database });
|
||||
const session = this.opened.find(x => x.sesid == sesid);
|
||||
session.killOnDone = true;
|
||||
const jslid = uuidv1();
|
||||
session.loadingReader_jslid = jslid;
|
||||
const fileName = queryName && appFolder ? path.join(appdir(), appFolder, `${queryName}.query.sql`) : null;
|
||||
|
||||
session.subprocess.send({ msgtype: 'executeReader', sql, fileName, jslid });
|
||||
|
||||
return { jslid };
|
||||
},
|
||||
|
||||
stopLoadingReader_meta: true,
|
||||
async stopLoadingReader({ jslid }) {
|
||||
const session = this.opened.find(x => x.loadingReader_jslid == jslid);
|
||||
if (session) {
|
||||
this.kill({ sesid: session.sesid });
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// cancel_meta: true,
|
||||
// async cancel({ sesid }) {
|
||||
// const session = this.opened.find((x) => x.sesid == sesid);
|
||||
@@ -126,6 +171,17 @@ module.exports = {
|
||||
return { state: 'ok' };
|
||||
},
|
||||
|
||||
ping_meta: true,
|
||||
async ping({ sesid }) {
|
||||
const session = this.opened.find(x => x.sesid == sesid);
|
||||
if (!session) {
|
||||
throw new Error('Invalid session');
|
||||
}
|
||||
session.subprocess.send({ msgtype: 'ping' });
|
||||
|
||||
return { state: 'ok' };
|
||||
},
|
||||
|
||||
// runCommand_meta: true,
|
||||
// async runCommand({ conid, database, sql }) {
|
||||
// console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
module.exports = {
|
||||
version: '4.1.1',
|
||||
version: '5.0.0-alpha.1',
|
||||
buildTime: '2021-04-17T07:22:49.702Z'
|
||||
};
|
||||
|
||||
@@ -8,9 +8,10 @@ if (processArgs.startProcess) {
|
||||
const proc = require('./proc');
|
||||
const module = proc[processArgs.startProcess];
|
||||
module.start();
|
||||
} else if (!processArgs.checkParent && !global['API_PACKAGE'] && !global['dbgateApiModulePath']) {
|
||||
const main = require('./main');
|
||||
}
|
||||
|
||||
if (processArgs.listenApi) {
|
||||
const main = require('./main');
|
||||
main.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,15 +20,19 @@ 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');
|
||||
|
||||
function start() {
|
||||
// console.log('process.argv', process.argv);
|
||||
@@ -37,12 +41,11 @@ function start() {
|
||||
|
||||
const server = http.createServer(app);
|
||||
|
||||
if (process.env.LOGIN && process.env.PASSWORD) {
|
||||
const logins = getLogins();
|
||||
if (logins && process.env.BASIC_AUTH) {
|
||||
app.use(
|
||||
basicAuth({
|
||||
users: {
|
||||
[process.env.LOGIN]: process.env.PASSWORD,
|
||||
},
|
||||
users: _.fromPairs(logins.map(x => [x.login, x.password])),
|
||||
challenge: true,
|
||||
realm: 'DbGate Web App',
|
||||
})
|
||||
@@ -51,6 +54,10 @@ function start() {
|
||||
|
||||
app.use(cors());
|
||||
|
||||
if (auth.shouldAuthorizeApi()) {
|
||||
app.use(auth.authMiddleware);
|
||||
}
|
||||
|
||||
app.get(getExpressPath('/stream'), async function (req, res) {
|
||||
res.set({
|
||||
'Cache-Control': 'no-cache',
|
||||
@@ -62,7 +69,10 @@ function start() {
|
||||
|
||||
// Tell the client to retry every 10 seconds if connectivity is lost
|
||||
res.write('retry: 10000\n\n');
|
||||
socket.setSseResponse(res);
|
||||
socket.addSseResponse(res);
|
||||
onFinished(req, () => {
|
||||
socket.removeSseResponse(res);
|
||||
});
|
||||
});
|
||||
|
||||
app.use(bodyParser.json({ limit: '50mb' }));
|
||||
@@ -85,24 +95,37 @@ function start() {
|
||||
if (platformInfo.isDocker) {
|
||||
// server static files inside docker container
|
||||
app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
|
||||
} else {
|
||||
if (!platformInfo.isNpmDist) {
|
||||
app.get(getExpressPath('/'), (req, res) => {
|
||||
res.send('DbGate API');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (platformInfo.isNpmDist) {
|
||||
const port = process.env.PORT || 3000;
|
||||
console.log('DbGate API listening on port (docker build)', port);
|
||||
server.listen(port);
|
||||
} else if (platformInfo.isNpmDist) {
|
||||
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
|
||||
getPort({ port: 5000 }).then(port => {
|
||||
getPort({
|
||||
port: parseInt(
|
||||
// @ts-ignore
|
||||
process.env.PORT || 3000
|
||||
),
|
||||
}).then(port => {
|
||||
server.listen(port, () => {
|
||||
console.log(`DbGate API listening on port ${port}`);
|
||||
console.log(`DbGate API listening on port ${port} (NPM build)`);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
} 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 listening on port', port);
|
||||
console.log('DbGate API & web listening on port (dev web build)', port);
|
||||
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);
|
||||
server.listen(port);
|
||||
}
|
||||
|
||||
@@ -139,10 +162,11 @@ 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 initializeElectronSender(electronSender) {
|
||||
function setElectronSender(electronSender) {
|
||||
socket.setElectronSender(electronSender);
|
||||
}
|
||||
|
||||
module.exports = { start, useAllControllers, initializeElectronSender, configController: config };
|
||||
module.exports = { start, useAllControllers, setElectronSender, configController: config };
|
||||
|
||||
@@ -20,7 +20,7 @@ function start() {
|
||||
if (handleProcessCommunication(connection)) return;
|
||||
try {
|
||||
const driver = requireEngineDriver(connection);
|
||||
const conn = await connectUtility(driver, connection);
|
||||
const conn = await connectUtility(driver, connection, 'app');
|
||||
const res = await driver.getVersion(conn);
|
||||
process.send({ msgtype: 'connected', ...res });
|
||||
} catch (e) {
|
||||
|
||||
@@ -7,6 +7,7 @@ const connectUtility = require('../utility/connectUtility');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const { SqlGenerator } = require('dbgate-tools');
|
||||
const generateDeploySql = require('../shell/generateDeploySql');
|
||||
const { dumpSqlSelect } = require('dbgate-sqltree');
|
||||
|
||||
let systemConnection;
|
||||
let storedConnection;
|
||||
@@ -14,6 +15,7 @@ let afterConnectCallbacks = [];
|
||||
let afterAnalyseCallbacks = [];
|
||||
let analysedStructure = null;
|
||||
let lastPing = null;
|
||||
let lastStatusString = null;
|
||||
let lastStatus = null;
|
||||
let analysedTime = 0;
|
||||
let serverVersion;
|
||||
@@ -77,21 +79,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() {
|
||||
@@ -107,7 +112,8 @@ async function handleConnect({ connection, structure, globalSettings }) {
|
||||
|
||||
if (!structure) setStatusName('pending');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection));
|
||||
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection, 'app'));
|
||||
systemConnection.feedback = feedback => setStatus({ feedback });
|
||||
await checkedAsyncCall(readVersion());
|
||||
if (structure) {
|
||||
analysedStructure = structure;
|
||||
@@ -150,10 +156,11 @@ function resolveAnalysedPromises() {
|
||||
afterAnalyseCallbacks = [];
|
||||
}
|
||||
|
||||
async function handleRunScript({ msgid, sql }) {
|
||||
async function handleRunScript({ msgid, sql }, skipReadonlyCheck = false) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
||||
await driver.script(systemConnection, sql);
|
||||
process.send({ msgtype: 'response', msgid });
|
||||
} catch (err) {
|
||||
@@ -161,25 +168,80 @@ async function handleRunScript({ msgid, sql }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleQueryData({ msgid, sql }) {
|
||||
async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
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 || 'Error executing SQL script' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSqlSelect({ msgid, select }) {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const dmp = driver.createDumper();
|
||||
dumpSqlSelect(dmp, select);
|
||||
return handleQueryData({ msgid, sql: dmp.s }, true);
|
||||
}
|
||||
|
||||
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 handleCollectionData({ msgid, options }) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
const result = await driver.readCollection(systemConnection, options);
|
||||
process.send({ msgtype: 'response', msgid, result });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
return handleDriverDataCore(msgid, driver => driver.readCollection(systemConnection, options));
|
||||
}
|
||||
|
||||
async function handleLoadKeys({ msgid, root, filter }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeys(systemConnection, root, filter));
|
||||
}
|
||||
|
||||
async function handleExportKeys({ msgid, options }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.exportKeys(systemConnection, options));
|
||||
}
|
||||
|
||||
async function handleLoadKeyInfo({ msgid, key }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyInfo(systemConnection, key));
|
||||
}
|
||||
|
||||
async function handleCallMethod({ msgid, method, args }) {
|
||||
return handleDriverDataCore(msgid, driver => {
|
||||
if (storedConnection.isReadOnly) {
|
||||
throw new Error('Connection is read only, cannot call custom methods');
|
||||
}
|
||||
|
||||
ensureExecuteCustomScript(driver);
|
||||
return driver.callMethod(systemConnection, method, args);
|
||||
});
|
||||
}
|
||||
|
||||
async function handleLoadKeyTableRange({ msgid, key, cursor, count }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyTableRange(systemConnection, key, cursor, count));
|
||||
}
|
||||
|
||||
async function handleLoadFieldValues({ msgid, schemaName, pureName, field, search }) {
|
||||
return handleDriverDataCore(msgid, driver =>
|
||||
driver.loadFieldValues(systemConnection, { schemaName, pureName }, field, search)
|
||||
);
|
||||
}
|
||||
|
||||
function ensureExecuteCustomScript(driver) {
|
||||
if (driver.readOnlySessions) {
|
||||
return;
|
||||
}
|
||||
if (storedConnection.isReadOnly) {
|
||||
throw new Error('Connection is read only');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +249,7 @@ async function handleUpdateCollection({ msgid, changeSet }) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
ensureExecuteCustomScript(driver);
|
||||
const result = await driver.updateCollection(systemConnection, changeSet);
|
||||
process.send({ msgtype: 'response', msgid, result });
|
||||
} catch (err) {
|
||||
@@ -248,10 +311,17 @@ const messageHandlers = {
|
||||
runScript: handleRunScript,
|
||||
updateCollection: handleUpdateCollection,
|
||||
collectionData: handleCollectionData,
|
||||
loadKeys: handleLoadKeys,
|
||||
loadKeyInfo: handleLoadKeyInfo,
|
||||
callMethod: handleCallMethod,
|
||||
loadKeyTableRange: handleLoadKeyTableRange,
|
||||
sqlPreview: handleSqlPreview,
|
||||
ping: handlePing,
|
||||
syncModel: handleSyncModel,
|
||||
generateDeploySql: handleGenerateDeploySql,
|
||||
loadFieldValues: handleLoadFieldValues,
|
||||
sqlSelect: handleSqlSelect,
|
||||
exportKeys: handleExportKeys,
|
||||
// runCommand: handleRunCommand,
|
||||
};
|
||||
|
||||
@@ -265,11 +335,11 @@ function start() {
|
||||
|
||||
setInterval(() => {
|
||||
const time = new Date().getTime();
|
||||
if (time - lastPing > 120 * 1000) {
|
||||
if (time - lastPing > 40 * 1000) {
|
||||
console.log('Database connection not alive, exiting');
|
||||
process.exit(0);
|
||||
}
|
||||
}, 60 * 1000);
|
||||
}, 10 * 1000);
|
||||
|
||||
process.on('message', async message => {
|
||||
if (handleProcessCommunication(message)) return;
|
||||
|
||||
@@ -3,6 +3,7 @@ const databaseConnectionProcess = require('./databaseConnectionProcess');
|
||||
const serverConnectionProcess = require('./serverConnectionProcess');
|
||||
const sessionProcess = require('./sessionProcess');
|
||||
const jslDatastoreProcess = require('./jslDatastoreProcess');
|
||||
const sshForwardProcess = require('./sshForwardProcess');
|
||||
|
||||
module.exports = {
|
||||
connectProcess,
|
||||
@@ -10,4 +11,5 @@ module.exports = {
|
||||
serverConnectionProcess,
|
||||
sessionProcess,
|
||||
jslDatastoreProcess,
|
||||
sshForwardProcess,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ const stableStringify = require('json-stable-stringify');
|
||||
const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
|
||||
@@ -58,11 +57,14 @@ async function handleConnect(connection) {
|
||||
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
systemConnection = await connectUtility(driver, storedConnection);
|
||||
systemConnection = await connectUtility(driver, storedConnection, 'app');
|
||||
readVersion();
|
||||
handleRefresh();
|
||||
if (extractBoolSettingsValue(globalSettings, 'connection.autoRefresh', false)) {
|
||||
setInterval(handleRefresh, extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 5, 3600) * 1000);
|
||||
setInterval(
|
||||
handleRefresh,
|
||||
extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 5, 3600) * 1000
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus({
|
||||
@@ -78,14 +80,16 @@ function handlePing() {
|
||||
lastPing = new Date().getTime();
|
||||
}
|
||||
|
||||
async function handleCreateDatabase({ name }) {
|
||||
async function handleDatabaseOp(op, { name }) {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await connectUtility(driver, storedConnection);
|
||||
console.log(`RUNNING SCRIPT: CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
||||
if (driver.createDatabase) {
|
||||
await driver.createDatabase(systemConnection, name);
|
||||
systemConnection = await connectUtility(driver, storedConnection, 'app');
|
||||
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);
|
||||
console.log(`RUNNING SCRIPT: ${dmp.s}`);
|
||||
await driver.query(systemConnection, dmp.s);
|
||||
}
|
||||
await handleRefresh();
|
||||
}
|
||||
@@ -93,7 +97,8 @@ async function handleCreateDatabase({ name }) {
|
||||
const messageHandlers = {
|
||||
connect: handleConnect,
|
||||
ping: handlePing,
|
||||
createDatabase: handleCreateDatabase,
|
||||
createDatabase: props => handleDatabaseOp('createDatabase', props),
|
||||
dropDatabase: props => handleDatabaseOp('dropDatabase', props),
|
||||
};
|
||||
|
||||
async function handleMessage({ msgtype, ...other }) {
|
||||
@@ -106,11 +111,11 @@ function start() {
|
||||
|
||||
setInterval(() => {
|
||||
const time = new Date().getTime();
|
||||
if (time - lastPing > 120 * 1000) {
|
||||
if (time - lastPing > 40 * 1000) {
|
||||
console.log('Server connection not alive, exiting');
|
||||
process.exit(0);
|
||||
}
|
||||
}, 60 * 1000);
|
||||
}, 10 * 1000);
|
||||
|
||||
process.on('message', async message => {
|
||||
if (handleProcessCommunication(message)) return;
|
||||
|
||||
@@ -15,13 +15,18 @@ let systemConnection;
|
||||
let storedConnection;
|
||||
let afterConnectCallbacks = [];
|
||||
// let currentHandlers = [];
|
||||
let lastPing = null;
|
||||
|
||||
class TableWriter {
|
||||
constructor(structure, resultIndex) {
|
||||
this.jslid = uuidv1();
|
||||
this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
|
||||
constructor() {
|
||||
this.currentRowCount = 0;
|
||||
this.currentChangeIndex = 1;
|
||||
this.initializedFile = false;
|
||||
}
|
||||
|
||||
initializeFromQuery(structure, resultIndex) {
|
||||
this.jslid = uuidv1();
|
||||
this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
|
||||
fs.writeFileSync(
|
||||
this.currentFile,
|
||||
JSON.stringify({
|
||||
@@ -32,13 +37,21 @@ class TableWriter {
|
||||
this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
|
||||
this.writeCurrentStats(false, false);
|
||||
this.resultIndex = resultIndex;
|
||||
this.initializedFile = true;
|
||||
process.send({ msgtype: 'recordset', jslid: this.jslid, resultIndex });
|
||||
}
|
||||
|
||||
initializeFromReader(jslid) {
|
||||
this.jslid = jslid;
|
||||
this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
|
||||
this.writeCurrentStats(false, false);
|
||||
}
|
||||
|
||||
row(row) {
|
||||
// console.log('ACCEPT ROW', row);
|
||||
this.currentStream.write(JSON.stringify(row) + '\n');
|
||||
this.currentRowCount += 1;
|
||||
|
||||
if (!this.plannedStats) {
|
||||
this.plannedStats = true;
|
||||
process.nextTick(() => {
|
||||
@@ -49,6 +62,21 @@ class TableWriter {
|
||||
}
|
||||
}
|
||||
|
||||
rowFromReader(row) {
|
||||
if (!this.initializedFile) {
|
||||
process.send({ msgtype: 'initializeFile', jslid: this.jslid });
|
||||
this.initializedFile = true;
|
||||
|
||||
fs.writeFileSync(this.currentFile, JSON.stringify(row) + '\n');
|
||||
this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
|
||||
this.writeCurrentStats(false, false);
|
||||
this.initializedFile = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.row(row);
|
||||
}
|
||||
|
||||
writeCurrentStats(isFinished = false, emitEvent = false) {
|
||||
const stats = {
|
||||
rowCount: this.currentRowCount,
|
||||
@@ -63,18 +91,20 @@ class TableWriter {
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
close(afterClose) {
|
||||
if (this.currentStream) {
|
||||
this.currentStream.end(() => {
|
||||
this.writeCurrentStats(true, true);
|
||||
if (afterClose) afterClose();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -98,7 +128,11 @@ class StreamHandler {
|
||||
|
||||
recordset(columns) {
|
||||
this.closeCurrentWriter();
|
||||
this.currentWriter = new TableWriter(Array.isArray(columns) ? { columns } : columns, this.resultIndexHolder.value);
|
||||
this.currentWriter = new TableWriter();
|
||||
this.currentWriter.initializeFromQuery(
|
||||
Array.isArray(columns) ? { columns } : columns,
|
||||
this.resultIndexHolder.value
|
||||
);
|
||||
this.resultIndexHolder.value += 1;
|
||||
|
||||
// this.writeCurrentStats();
|
||||
@@ -110,7 +144,6 @@ class StreamHandler {
|
||||
// }, 500);
|
||||
}
|
||||
row(row) {
|
||||
// console.log('ACCEPT ROW', row);
|
||||
if (this.currentWriter) this.currentWriter.row(row);
|
||||
else if (row.message) process.send({ msgtype: 'info', info: { message: row.message } });
|
||||
// this.onRow(this.jslid);
|
||||
@@ -124,22 +157,40 @@ 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);
|
||||
});
|
||||
}
|
||||
|
||||
function allowExecuteCustomScript(driver) {
|
||||
if (driver.readOnlySessions) {
|
||||
return true;
|
||||
}
|
||||
if (storedConnection.isReadOnly) {
|
||||
return false;
|
||||
// throw new Error('Connection is read only');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleConnect(connection) {
|
||||
storedConnection = connection;
|
||||
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await connectUtility(driver, storedConnection);
|
||||
systemConnection = await connectUtility(driver, storedConnection, 'app');
|
||||
for (const [resolve] of afterConnectCallbacks) {
|
||||
resolve();
|
||||
}
|
||||
@@ -163,10 +214,26 @@ async function handleExecuteQuery({ sql }) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
|
||||
if (!allowExecuteCustomScript(driver)) {
|
||||
process.send({
|
||||
msgtype: 'info',
|
||||
info: {
|
||||
message: 'Connection without read-only sessions is read only',
|
||||
severity: 'error',
|
||||
},
|
||||
});
|
||||
process.send({ msgtype: 'done', skipFinishedMessage: true });
|
||||
return;
|
||||
//process.send({ msgtype: 'error', error: e.message });
|
||||
}
|
||||
|
||||
const resultIndexHolder = {
|
||||
value: 0,
|
||||
};
|
||||
for (const sqlItem of splitQuery(sql, driver.getQuerySplitterOptions('stream'))) {
|
||||
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);
|
||||
@@ -176,9 +243,44 @@ async function handleExecuteQuery({ sql }) {
|
||||
process.send({ msgtype: 'done' });
|
||||
}
|
||||
|
||||
async function handleExecuteReader({ jslid, sql, fileName }) {
|
||||
await waitConnected();
|
||||
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
|
||||
if (fileName) {
|
||||
sql = fs.readFileSync(fileName, 'utf-8');
|
||||
} else {
|
||||
if (!allowExecuteCustomScript(driver)) {
|
||||
process.send({ msgtype: 'done' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const writer = new TableWriter();
|
||||
writer.initializeFromReader(jslid);
|
||||
|
||||
const reader = await driver.readQuery(systemConnection, sql);
|
||||
|
||||
reader.on('data', data => {
|
||||
writer.rowFromReader(data);
|
||||
});
|
||||
reader.on('end', () => {
|
||||
writer.close(() => {
|
||||
process.send({ msgtype: 'done' });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handlePing() {
|
||||
lastPing = new Date().getTime();
|
||||
}
|
||||
|
||||
const messageHandlers = {
|
||||
connect: handleConnect,
|
||||
executeQuery: handleExecuteQuery,
|
||||
executeReader: handleExecuteReader,
|
||||
ping: handlePing,
|
||||
// cancel: handleCancel,
|
||||
};
|
||||
|
||||
@@ -189,6 +291,17 @@ async function handleMessage({ msgtype, ...other }) {
|
||||
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
|
||||
lastPing = new Date().getTime();
|
||||
|
||||
setInterval(() => {
|
||||
const time = new Date().getTime();
|
||||
if (time - lastPing > 25 * 1000) {
|
||||
console.log('Session not alive, exiting');
|
||||
process.exit(0);
|
||||
}
|
||||
}, 10 * 1000);
|
||||
|
||||
process.on('message', async message => {
|
||||
if (handleProcessCommunication(message)) return;
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
const fs = require('fs-extra');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const { SSHConnection } = require('../utility/SSHConnection');
|
||||
|
||||
async function getSshConnection(connection) {
|
||||
const sshConfig = {
|
||||
endHost: connection.sshHost || '',
|
||||
endPort: connection.sshPort || 22,
|
||||
bastionHost: connection.sshBastionHost || '',
|
||||
agentForward: connection.sshMode == 'agent',
|
||||
passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyfilePassword : undefined,
|
||||
username: connection.sshLogin,
|
||||
password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
|
||||
agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,
|
||||
privateKey:
|
||||
connection.sshMode == 'keyFile' && connection.sshKeyfile ? await fs.readFile(connection.sshKeyfile) : undefined,
|
||||
skipAutoPrivateKey: true,
|
||||
noReadline: true,
|
||||
};
|
||||
|
||||
const sshConn = new SSHConnection(sshConfig);
|
||||
return sshConn;
|
||||
}
|
||||
|
||||
async function handleStart({ connection, tunnelConfig }) {
|
||||
try {
|
||||
const sshConn = await getSshConnection(connection);
|
||||
await sshConn.forward(tunnelConfig);
|
||||
|
||||
process.send({
|
||||
msgtype: 'connected',
|
||||
connection,
|
||||
tunnelConfig,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Error creating SSH tunnel connection:', err.message);
|
||||
|
||||
process.send({
|
||||
msgtype: 'error',
|
||||
connection,
|
||||
tunnelConfig,
|
||||
errorMessage: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const messageHandlers = {
|
||||
connect: handleStart,
|
||||
};
|
||||
|
||||
async function handleMessage({ msgtype, ...other }) {
|
||||
const handler = messageHandlers[msgtype];
|
||||
await handler(other);
|
||||
}
|
||||
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
process.on('message', async message => {
|
||||
if (handleProcessCommunication(message)) return;
|
||||
try {
|
||||
await handleMessage(message);
|
||||
} catch (e) {
|
||||
console.error('sshForwardProcess - unhandled error', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { start };
|
||||
@@ -1,18 +1,47 @@
|
||||
const EnsureStreamHeaderStream = require('../utility/EnsureStreamHeaderStream');
|
||||
const Stream = require('stream');
|
||||
const ColumnMapTransformStream = require('../utility/ColumnMapTransformStream');
|
||||
|
||||
function copyStream(input, output, options) {
|
||||
const { columns } = options || {};
|
||||
|
||||
const transforms = [];
|
||||
if (columns) {
|
||||
transforms.push(new ColumnMapTransformStream(columns));
|
||||
}
|
||||
if (output.requireFixedStructure) {
|
||||
transforms.push(new EnsureStreamHeaderStream());
|
||||
}
|
||||
|
||||
// return new Promise((resolve, reject) => {
|
||||
// Stream.pipeline(input, ...transforms, output, err => {
|
||||
// if (err) {
|
||||
// reject(err);
|
||||
// } else {
|
||||
// resolve();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
function copyStream(input, output) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const finisher = output['finisher'] || output;
|
||||
finisher.on('finish', resolve);
|
||||
finisher.on('error', reject);
|
||||
|
||||
if (output.requireFixedStructure) {
|
||||
const ensureHeader = new EnsureStreamHeaderStream();
|
||||
input.pipe(ensureHeader);
|
||||
ensureHeader.pipe(output);
|
||||
} else {
|
||||
input.pipe(output);
|
||||
let lastStream = input;
|
||||
for (const tran of transforms) {
|
||||
lastStream.pipe(tran);
|
||||
lastStream = tran;
|
||||
}
|
||||
lastStream.pipe(output);
|
||||
|
||||
// if (output.requireFixedStructure) {
|
||||
// const ensureHeader = new EnsureStreamHeaderStream();
|
||||
// input.pipe(ensureHeader);
|
||||
// ensureHeader.pipe(output);
|
||||
// } else {
|
||||
// input.pipe(output);
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
function doDump(dumper) {
|
||||
return new Promise((resolve, reject) => {
|
||||
dumper.once('end', () => {
|
||||
resolve(true);
|
||||
});
|
||||
dumper.once('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
dumper.run();
|
||||
});
|
||||
}
|
||||
|
||||
async function dumpDatabase({
|
||||
connection = undefined,
|
||||
systemConnection = undefined,
|
||||
driver = undefined,
|
||||
outputFile,
|
||||
databaseName,
|
||||
schemaName,
|
||||
}) {
|
||||
console.log(`Dumping database`);
|
||||
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
|
||||
console.log(`Connected.`);
|
||||
|
||||
const dumper = await driver.createBackupDumper(pool, {
|
||||
outputFile,
|
||||
databaseName,
|
||||
schemaName,
|
||||
});
|
||||
await doDump(dumper);
|
||||
}
|
||||
|
||||
module.exports = dumpDatabase;
|
||||
@@ -5,7 +5,7 @@ async function executeQuery({ connection = undefined, systemConnection = undefin
|
||||
console.log(`Execute query ${sql}`);
|
||||
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
const pool = systemConnection || (await connectUtility(driver, connection));
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'script'));
|
||||
console.log(`Connected.`);
|
||||
|
||||
await driver.script(pool, sql);
|
||||
|
||||
@@ -21,7 +21,7 @@ async function generateDeploySql({
|
||||
}) {
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
|
||||
const pool = systemConnection || (await connectUtility(driver, connection));
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'read'));
|
||||
if (!analysedStructure) {
|
||||
analysedStructure = await driver.analyseFull(pool);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
const fs = require('fs');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const { splitQueryStream } = require('dbgate-query-splitter/lib/splitQueryStream');
|
||||
const download = require('./download');
|
||||
const stream = require('stream');
|
||||
|
||||
class ImportStream extends stream.Transform {
|
||||
constructor(pool, driver) {
|
||||
super({ objectMode: true });
|
||||
this.pool = pool;
|
||||
this.driver = driver;
|
||||
}
|
||||
async _transform(chunk, encoding, cb) {
|
||||
try {
|
||||
await this.driver.script(this.pool, chunk);
|
||||
} catch (err) {
|
||||
this.emit('error', err.message);
|
||||
}
|
||||
cb();
|
||||
}
|
||||
_flush(cb) {
|
||||
this.push('finish');
|
||||
cb();
|
||||
this.emit('end');
|
||||
}
|
||||
}
|
||||
|
||||
function awaitStreamEnd(stream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.once('end', () => {
|
||||
resolve(true);
|
||||
});
|
||||
stream.once('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function importDatabase({ connection = undefined, systemConnection = undefined, driver = undefined, inputFile }) {
|
||||
console.log(`Importing database`);
|
||||
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
console.log(`Connected.`);
|
||||
|
||||
const downloadedFile = await download(inputFile);
|
||||
|
||||
const fileStream = fs.createReadStream(downloadedFile, 'utf-8');
|
||||
const splittedStream = splitQueryStream(fileStream, driver.getQuerySplitterOptions('script'));
|
||||
const importStream = new ImportStream(pool, driver);
|
||||
// @ts-ignore
|
||||
splittedStream.pipe(importStream);
|
||||
await awaitStreamEnd(importStream);
|
||||
}
|
||||
|
||||
module.exports = importDatabase;
|
||||
@@ -21,6 +21,8 @@ const executeQuery = require('./executeQuery');
|
||||
const loadFile = require('./loadFile');
|
||||
const deployDb = require('./deployDb');
|
||||
const initializeApiEnvironment = require('./initializeApiEnvironment');
|
||||
const dumpDatabase = require('./dumpDatabase');
|
||||
const importDatabase = require('./importDatabase');
|
||||
|
||||
const dbgateApi = {
|
||||
queryReader,
|
||||
@@ -45,6 +47,8 @@ const dbgateApi = {
|
||||
loadFile,
|
||||
deployDb,
|
||||
initializeApiEnvironment,
|
||||
dumpDatabase,
|
||||
importDatabase,
|
||||
};
|
||||
|
||||
requirePlugin.initializeDbgateApi(dbgateApi);
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
async function queryReader({ connection, sql }) {
|
||||
console.log(`Reading query ${sql}`);
|
||||
async function queryReader({
|
||||
connection,
|
||||
query,
|
||||
queryType,
|
||||
// obsolete; use query instead
|
||||
sql,
|
||||
}) {
|
||||
// if (sql && json) {
|
||||
// throw new Error('Only one of sql or json could be set');
|
||||
// }
|
||||
// if (!sql && !json) {
|
||||
// throw new Error('One of sql or json must be set');
|
||||
// }
|
||||
console.log(`Reading query ${query || sql}`);
|
||||
// else console.log(`Reading query ${JSON.stringify(json)}`);
|
||||
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await connectUtility(driver, connection);
|
||||
const pool = await connectUtility(driver, connection, queryType == 'json' ? 'read' : 'script');
|
||||
console.log(`Connected.`);
|
||||
return await driver.readQuery(pool, sql);
|
||||
const reader =
|
||||
queryType == 'json' ? await driver.readJsonQuery(pool, query) : await driver.readQuery(pool, query || sql);
|
||||
return reader;
|
||||
}
|
||||
|
||||
module.exports = queryReader;
|
||||
|
||||
@@ -5,10 +5,11 @@ const { driverBase } = require('dbgate-tools');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
|
||||
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 +29,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 +39,9 @@ class SqlizeStream extends stream.Transform {
|
||||
}
|
||||
}
|
||||
|
||||
async function sqlDataWriter({ fileName, driver, encoding = 'utf-8' }) {
|
||||
async function sqlDataWriter({ fileName, dataName, driver, encoding = 'utf-8' }) {
|
||||
console.log(`Writing file ${fileName}`);
|
||||
const stringify = new SqlizeStream({ fileName });
|
||||
const stringify = new SqlizeStream({ fileName, dataName });
|
||||
const fileStream = fs.createWriteStream(fileName, encoding);
|
||||
stringify.pipe(fileStream);
|
||||
stringify['finisher'] = fileStream;
|
||||
|
||||
@@ -4,12 +4,12 @@ const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
async function tableReader({ connection, pureName, schemaName }) {
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await connectUtility(driver, connection);
|
||||
const pool = await connectUtility(driver, connection, 'read');
|
||||
console.log(`Connected.`);
|
||||
|
||||
const fullName = { pureName, schemaName };
|
||||
|
||||
if (driver.dialect.nosql) {
|
||||
if (driver.databaseEngineTypes.includes('document')) {
|
||||
// @ts-ignore
|
||||
console.log(`Reading collection ${fullNameToString(fullName)}`);
|
||||
// @ts-ignore
|
||||
|
||||
@@ -8,7 +8,7 @@ async function tableWriter({ connection, schemaName, pureName, driver, systemCon
|
||||
if (!driver) {
|
||||
driver = requireEngineDriver(connection);
|
||||
}
|
||||
const pool = systemConnection || (await connectUtility(driver, connection));
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
|
||||
console.log(`Connected.`);
|
||||
return await driver.writeTable(pool, { schemaName, pureName }, options);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
const stream = require('stream');
|
||||
const { transformRowUsingColumnMap } = require('dbgate-tools');
|
||||
|
||||
class ColumnMapTransformStream extends stream.Transform {
|
||||
constructor(columns) {
|
||||
super({ objectMode: true });
|
||||
this.columns = columns;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
if (chunk.__isStreamHeader) {
|
||||
// skip stream header
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
this.push(transformRowUsingColumnMap(chunk, this.columns));
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ColumnMapTransformStream;
|
||||
@@ -159,10 +159,23 @@ class JsonLinesDatastore {
|
||||
}
|
||||
}
|
||||
|
||||
async enumRows(eachRow) {
|
||||
await lock.acquire('reader', async () => {
|
||||
await this._ensureReader(0, null);
|
||||
for (;;) {
|
||||
const line = await this._readLine(true);
|
||||
if (line == null) break;
|
||||
const shouldContinue = eachRow(line);
|
||||
if (!shouldContinue) break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getRows(offset, limit, filter) {
|
||||
const res = [];
|
||||
await lock.acquire('reader', async () => {
|
||||
await this._ensureReader(offset, filter);
|
||||
// console.log(JSON.stringify(this.currentFilter, undefined, 2));
|
||||
for (let i = 0; i < limit; i += 1) {
|
||||
const line = await this._readLine(true);
|
||||
if (line == null) break;
|
||||
|
||||
@@ -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,47 @@
|
||||
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');
|
||||
|
||||
async function loadConnection(driver, storedConnection, connectionMode) {
|
||||
const { allowShellConnection } = platformInfo;
|
||||
|
||||
if (connectionMode == 'app') {
|
||||
return storedConnection;
|
||||
}
|
||||
|
||||
if (storedConnection._id || !allowShellConnection) {
|
||||
if (!storedConnection._id) {
|
||||
throw new Error('Missing connection _id');
|
||||
}
|
||||
|
||||
await connections._init();
|
||||
const loaded = await connections.getCore({ conid: storedConnection._id });
|
||||
const loadedWithDb = {
|
||||
...loaded,
|
||||
database: storedConnection.database,
|
||||
};
|
||||
|
||||
if (loaded.isReadOnly) {
|
||||
if (connectionMode == 'read') return loadedWithDb;
|
||||
if (connectionMode == 'write') throw new Error('Cannot write readonly connection');
|
||||
if (connectionMode == 'script') {
|
||||
if (driver.readOnlySessions) return loadedWithDb;
|
||||
throw new Error('Cannot write readonly connection');
|
||||
}
|
||||
}
|
||||
return loadedWithDb;
|
||||
}
|
||||
return storedConnection;
|
||||
}
|
||||
|
||||
async function connectUtility(driver, storedConnection, connectionMode, additionalOptions = null) {
|
||||
const connectionLoaded = await loadConnection(driver, storedConnection, connectionMode);
|
||||
|
||||
async function connectUtility(driver, storedConnection) {
|
||||
const connection = {
|
||||
database: storedConnection.defaultDatabase,
|
||||
...decryptConnection(storedConnection),
|
||||
database: connectionLoaded.defaultDatabase,
|
||||
...decryptConnection(connectionLoaded),
|
||||
};
|
||||
|
||||
if (!connection.port && driver.defaultPort) connection.port = driver.defaultPort.toString();
|
||||
@@ -57,7 +90,7 @@ async function connectUtility(driver, storedConnection) {
|
||||
}
|
||||
}
|
||||
|
||||
const conn = await driver.connect(connection);
|
||||
const conn = await driver.connect({ ...connection, ...additionalOptions });
|
||||
return conn;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ function encryptPasswordField(connection, field) {
|
||||
[field]: 'crypt:' + getEncryptor().encrypt(connection[field]),
|
||||
};
|
||||
}
|
||||
return connection;
|
||||
return connection;
|
||||
}
|
||||
|
||||
function decryptPasswordField(connection, field) {
|
||||
@@ -75,6 +75,11 @@ function encryptConnection(connection) {
|
||||
return connection;
|
||||
}
|
||||
|
||||
function maskConnection(connection) {
|
||||
if (!connection) return connection;
|
||||
return _.omit(connection, ['password', 'sshPassword', 'sshKeyfilePassword']);
|
||||
}
|
||||
|
||||
function decryptConnection(connection) {
|
||||
connection = decryptPasswordField(connection, 'password');
|
||||
connection = decryptPasswordField(connection, 'sshPassword');
|
||||
@@ -95,5 +100,6 @@ module.exports = {
|
||||
loadEncryptionKey,
|
||||
encryptConnection,
|
||||
decryptConnection,
|
||||
maskConnection,
|
||||
pickSafeConnectionInfo,
|
||||
};
|
||||
|
||||
@@ -3,11 +3,13 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const cleanDirectory = require('./cleanDirectory');
|
||||
const platformInfo = require('./platformInfo');
|
||||
const processArgs = require('./processArgs');
|
||||
const consoleObjectWriter = require('../shell/consoleObjectWriter');
|
||||
|
||||
const createDirectories = {};
|
||||
const ensureDirectory = (dir, clean) => {
|
||||
if (!createDirectories[dir]) {
|
||||
if (clean && fs.existsSync(dir)) {
|
||||
if (clean && fs.existsSync(dir) && !platformInfo.isForkedApi) {
|
||||
console.log(`Cleaning directory ${dir}`);
|
||||
cleanDirectory(dir);
|
||||
}
|
||||
@@ -19,8 +21,18 @@ const ensureDirectory = (dir, clean) => {
|
||||
}
|
||||
};
|
||||
|
||||
function datadirCore() {
|
||||
if (process.env.WORKSPACE_DIR) {
|
||||
return process.env.WORKSPACE_DIR;
|
||||
}
|
||||
if (processArgs.workspaceDir) {
|
||||
return processArgs.workspaceDir;
|
||||
}
|
||||
return path.join(os.homedir(), '.dbgate');
|
||||
}
|
||||
|
||||
function datadir() {
|
||||
const dir = path.join(os.homedir(), 'dbgate-data');
|
||||
const dir = datadirCore();
|
||||
ensureDirectory(dir);
|
||||
|
||||
return dir;
|
||||
@@ -54,7 +66,10 @@ function packagedPluginsDir() {
|
||||
}
|
||||
if (platformInfo.isNpmDist) {
|
||||
// node_modules
|
||||
return global['dbgateApiPackagedPluginsPath'];
|
||||
return global['PLUGINS_DIR'];
|
||||
}
|
||||
if (processArgs.pluginsDir) {
|
||||
return processArgs.pluginsDir;
|
||||
}
|
||||
if (platformInfo.isElectronBundle) {
|
||||
return path.resolve(__dirname, '../../plugins');
|
||||
@@ -98,6 +113,27 @@ function clearArchiveLinksCache() {
|
||||
archiveLinksCache = {};
|
||||
}
|
||||
|
||||
function migrateDataDir() {
|
||||
if (process.env.WORKSPACE_DIR) {
|
||||
return;
|
||||
}
|
||||
if (processArgs.workspaceDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const oldDir = path.join(os.homedir(), 'dbgate-data');
|
||||
const newDir = path.join(os.homedir(), '.dbgate');
|
||||
if (fs.existsSync(oldDir) && !fs.existsSync(newDir)) {
|
||||
fs.renameSync(oldDir, newDir);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error migrating data dir:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
migrateDataDir();
|
||||
|
||||
module.exports = {
|
||||
datadir,
|
||||
jsldir,
|
||||
|
||||
@@ -3,7 +3,7 @@ const fs = require('fs-extra');
|
||||
async function saveFreeTableData(file, data) {
|
||||
const { structure, rows } = data;
|
||||
const fileStream = fs.createWriteStream(file);
|
||||
await fileStream.write(JSON.stringify(structure) + '\n');
|
||||
await fileStream.write(JSON.stringify({ __isStreamHeader: true, ...structure }) + '\n');
|
||||
for (const row of rows) {
|
||||
await fileStream.write(JSON.stringify(row) + '\n');
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ function getJslFileName(jslid) {
|
||||
if (archiveMatch) {
|
||||
return path.join(resolveArchiveFolder(archiveMatch[1]), `${archiveMatch[2]}.jsonl`);
|
||||
}
|
||||
const fileMatch = jslid.match(/^file:\/\/(.*)$/);
|
||||
if (fileMatch) {
|
||||
return fileMatch[1];
|
||||
}
|
||||
return path.join(jsldir(), `${jslid}.jsonl`);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -1,12 +1,84 @@
|
||||
const { compilePermissions, testPermission } = require('dbgate-tools');
|
||||
const _ = require('lodash');
|
||||
|
||||
let compiled = undefined;
|
||||
const userPermissions = {};
|
||||
|
||||
function hasPermission(tested) {
|
||||
if (compiled === undefined) {
|
||||
compiled = compilePermissions(process.env.PERMISSIONS);
|
||||
function hasPermission(tested, req) {
|
||||
if (!req) {
|
||||
// request object not available, allow all
|
||||
return true;
|
||||
}
|
||||
return testPermission(tested, compiled);
|
||||
const { user } = (req && req.auth) || {};
|
||||
const key = user || '';
|
||||
const logins = getLogins();
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
module.exports = hasPermission;
|
||||
let loginsCache = null;
|
||||
let loginsLoaded = false;
|
||||
|
||||
function getLogins() {
|
||||
if (loginsLoaded) {
|
||||
return loginsCache;
|
||||
}
|
||||
|
||||
const res = [];
|
||||
if (process.env.LOGIN && process.env.PASSWORD) {
|
||||
res.push({
|
||||
login: process.env.LOGIN,
|
||||
password: process.env.PASSWORD,
|
||||
permissions: process.env.PERMISSIONS,
|
||||
});
|
||||
}
|
||||
if (process.env.LOGINS) {
|
||||
const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim()));
|
||||
for (const login of logins) {
|
||||
const password = process.env[`LOGIN_PASSWORD_${login}`];
|
||||
const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
|
||||
if (password) {
|
||||
res.push({
|
||||
login,
|
||||
password,
|
||||
permissions,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loginsCache = res.length > 0 ? res : null;
|
||||
loginsLoaded = true;
|
||||
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,
|
||||
};
|
||||
|
||||