Compare commits
1124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8f50fc117 | |||
| cfdb7b32fc | |||
| cc833f0b73 | |||
| 47282afa22 | |||
| 417640cfbe | |||
| 8df1690d5d | |||
| 461d1bcd5b | |||
| 8b5e164291 | |||
| 2644f56755 | |||
| 437e6baeba | |||
| 6788d37b6f | |||
| b90550d6ba | |||
| c068deb47e | |||
| 2ce5b28048 | |||
| 57f1751309 | |||
| 2d8dc36f28 | |||
| 077a717525 | |||
| 8795493462 | |||
| af532bae8f | |||
| c07b3e2d3c | |||
| 6b1a9fa837 | |||
| 24888dfad5 | |||
| 4d4c8638ca | |||
| 9fd983abfb | |||
| 637839ee14 | |||
| 9b2578665f | |||
| ee05e109c8 | |||
| a905e32cde | |||
| fb5cec0d38 | |||
| 1a4f490fb5 | |||
| 4518c2c4c0 | |||
| 17771ccecd | |||
| f3c2a3c025 | |||
| 2aa919b444 | |||
| 408987ee30 | |||
| fe04ad9940 | |||
| 1b03e750a2 | |||
| 3ff781139e | |||
| 2ea3ba492e | |||
| 89d3228077 | |||
| 7946f5ee81 | |||
| 44f62eac9a | |||
| 47725e57b0 | |||
| aac2aaa999 | |||
| 6ac1160bf2 | |||
| 70fae41042 | |||
| 07f5bfe3dc | |||
| 93a88a22b9 | |||
| 792faa1176 | |||
| 66900d594f | |||
| 9555f3c853 | |||
| 9ed2fa734a | |||
| db83508920 | |||
| d72a6585d4 | |||
| 74fc8c9cf7 | |||
| f3440f3c01 | |||
| b01799c606 | |||
| 3cbb4e3f43 | |||
| 36299acbfb | |||
| acc20af2d9 | |||
| 077ce5b521 | |||
| 1efe82dda2 | |||
| f283618209 | |||
| fe6baf8dba | |||
| b742dc51fb | |||
| c8ea649afc | |||
| a27e94f694 | |||
| 25f9e7829f | |||
| 85be7acab2 | |||
| 36c23227e5 | |||
| 0b6f68e190 | |||
| 6c90fc2764 | |||
| 4fd1e55ae8 | |||
| 9672f7e3da | |||
| 50d29f8ef0 | |||
| 6ffa00026e | |||
| c4677505ac | |||
| bac6d7cf3c | |||
| 85ea91e0e3 | |||
| 09b09086ce | |||
| 8fbfa86a7f | |||
| 6224e942dc | |||
| ab5edbdd41 | |||
| 7825d10f18 | |||
| 8c1988e480 | |||
| 8403b811d8 | |||
| 1c797505ae | |||
| 466086b509 | |||
| 221f7247e6 | |||
| 624bc88f74 | |||
| d6ca4458e4 | |||
| 1fe3e60468 | |||
| aa65b2b97c | |||
| 6dea2475c7 | |||
| a666cb00eb | |||
| f0fac9165c | |||
| e51eb8a9c1 | |||
| 4822984e34 | |||
| 4a558a7f65 | |||
| 48d4935c7d | |||
| 506accfe9b | |||
| c15aba47f5 | |||
| fdafa8adf6 | |||
| 23e15581f3 | |||
| 1621f866a8 | |||
| f03fe33b1f | |||
| 6ec2a5ea35 | |||
| 0ae4d323f7 | |||
| 930404e482 | |||
| 92257afdab | |||
| 300ff09a47 | |||
| 14dd1103eb | |||
| ff07eb1de0 | |||
| b81f45bf47 | |||
| eb3232e049 | |||
| 752f653f82 | |||
| ed90dbe7b7 | |||
| e1e0f2944b | |||
| 2269f15b66 | |||
| 5c0a53e02a | |||
| 8810439ffc | |||
| 9d61270937 | |||
| b602a9d15d | |||
| bb0ab03ad9 | |||
| d674dcaeef | |||
| a738ddb917 | |||
| 935c92b507 | |||
| 10d1a2d8e4 | |||
| cf6d64646a | |||
| c570e8b6fe | |||
| 849b9e0c80 | |||
| 8f8aa31304 | |||
| a1fe79c876 | |||
| 7c9654a541 | |||
| a86df7cdbf | |||
| 3d5cdd659b | |||
| 7a2a5d3846 | |||
| 62fb0acb3c | |||
| c4dfe8a723 | |||
| 4af4df9288 | |||
| 25b0e18ceb | |||
| c4aec8dfa6 | |||
| 4056a3da43 | |||
| 2027f8052b | |||
| 96bb293c1f | |||
| fd73b28d66 | |||
| aafa698776 | |||
| a99d31535c | |||
| fda8791d5a | |||
| 9e2ac31a08 | |||
| 758b32a61c | |||
| 62b392e555 | |||
| a4c99fd361 | |||
| 8823656d70 | |||
| b82f5da112 | |||
| 729f51b779 | |||
| 1c1171e8a7 | |||
| 44df319ff6 | |||
| 84dec337f0 | |||
| 2e60a9d43c | |||
| 4474766526 | |||
| ddc1d8117d | |||
| 609bae4f1a | |||
| ff16a4334f | |||
| 739e308c0e | |||
| 6b29f57e1d | |||
| 21981829fd | |||
| b6e00a3107 | |||
| 8b8b137cad | |||
| 1ba1286df0 | |||
| 0417a82f83 | |||
| 4cc01a2152 | |||
| c7d434a091 | |||
| 3e1e704a7f | |||
| 12a9dcaa76 | |||
| fc2dd148c5 | |||
| ede6f36913 | |||
| f85143fb7b | |||
| bbf3043fc3 | |||
| bbd73d361a | |||
| 7d44c87aff | |||
| 7e81041b87 | |||
| e30698459b | |||
| 42399469a7 | |||
| 8ccc7bb734 | |||
| 38a7c222a8 | |||
| 9ea21606e5 | |||
| c809119d57 | |||
| b8ca009e69 | |||
| 1e37d7da7d | |||
| 9df90e4edc | |||
| 4576e93deb | |||
| ea5e0b28da | |||
| 19c8a01969 | |||
| ebab88e7aa | |||
| b4248acd9a | |||
| c303174f0b | |||
| 91cf6465df | |||
| 426d2aab09 | |||
| 8c44e558a8 | |||
| 6a08e08d7c | |||
| 2796ad12fe | |||
| 45d40297bf | |||
| 57ac37c210 | |||
| 8eee0b809c | |||
| 5387054000 | |||
| e08f072d95 | |||
| 44db2ca02a | |||
| dc8564d18e | |||
| 3b8bc9fdab | |||
| c5d65fa030 | |||
| 0252b08c8f | |||
| 8f29b7a91f | |||
| 19e1bbdc1c | |||
| cd2baf105f | |||
| 3366acde58 | |||
| 775f6eff0b | |||
| 52d501dae8 | |||
| eb24baf2c1 | |||
| 7fd0ef51b5 | |||
| 173acc5226 | |||
| 6643b825ee | |||
| a56f20584f | |||
| 22664bea62 | |||
| f80bf65076 | |||
| 28634c59c8 | |||
| 993377a40b | |||
| 9633733bc4 | |||
| 6b4893b854 | |||
| 09f7c35dac | |||
| 0fc4169b0b | |||
| 78746be0f5 | |||
| 2287a550d7 | |||
| d6eb6d3318 | |||
| db2d36eaa1 | |||
| 151d20341e | |||
| 2c51bc17f1 | |||
| 0448ad49ed | |||
| 2d4129f8f7 | |||
| c42aa7c806 | |||
| debeb8dfd8 | |||
| 2227e1dd4b | |||
| 1d7e73c162 | |||
| 3f451856a0 | |||
| fdd0483c9f | |||
| c1a49a52e8 | |||
| d8394392c9 | |||
| eb7a037f94 | |||
| eb905aab86 | |||
| 55892097d7 | |||
| 6bfcda9fdc | |||
| 02dcbb9a52 | |||
| 0c6a6e4173 | |||
| e044251df4 | |||
| 26d27a3f6a | |||
| c5b9fccedb | |||
| 0153e5212c | |||
| 92835a5270 | |||
| 5f41c78305 | |||
| 1726b89dea | |||
| 2506e69cdc | |||
| 88bc30bbea | |||
| 2835d1bd87 | |||
| e590896f01 | |||
| dfb0836446 | |||
| 88fdd1f562 | |||
| ae07c7934e | |||
| 9515a060ab | |||
| c550416c9d | |||
| 712883373a | |||
| 50930ee609 | |||
| 78bffad99f | |||
| b3760a961d | |||
| 813d92ce32 | |||
| b02570e679 | |||
| b7d1979d0d | |||
| 6e6846835a | |||
| d899935b56 | |||
| 2a07c063ab | |||
| 3ab9d77930 | |||
| 5537507646 | |||
| 215dd03751 | |||
| 3fe73ba198 | |||
| 6bc7edea67 | |||
| c44e69c396 | |||
| f6ad697755 | |||
| 2abca93333 | |||
| 788e7c40e9 | |||
| dca43f3e57 | |||
| f95a98d3ee | |||
| 11fe3dc492 | |||
| 4277244150 | |||
| 8458bcf10e | |||
| 6c8c7751fd | |||
| d6096d04d9 | |||
| bffe6327a0 | |||
| 28845d6f33 | |||
| 6ec7da9071 | |||
| 5dbe564afb | |||
| 4794791167 | |||
| 7b2ae2c457 | |||
| f0093c5e4f | |||
| 96117216ee | |||
| 9982557909 | |||
| 530331f9ee | |||
| 23018abdf6 | |||
| 23b72620a1 | |||
| a80c21d77f | |||
| 765307ddef | |||
| 9a859629bc | |||
| cc7b203f93 | |||
| 8744eadca0 | |||
| 76eaee5b1a | |||
| 7adde2a880 | |||
| c02eced029 | |||
| ad5ca50273 | |||
| 767756ba9b | |||
| c3cf5ff84c | |||
| a82c790855 | |||
| 4b22e3e0a8 | |||
| 2039a143ac | |||
| 84473dc10d | |||
| 5695da1d86 | |||
| 30583cce21 | |||
| 27c7c0438f | |||
| b67d5eec3d | |||
| 7c2ea6288c | |||
| 9a1d71face | |||
| 8e346bf676 | |||
| 53a00a8d76 | |||
| ea1e556197 | |||
| 402d75bfe0 | |||
| a444b61edf | |||
| ce41af14db | |||
| d52d606088 | |||
| 475311f63a | |||
| 5509089c49 | |||
| 3698220b8f | |||
| b22dba00a2 | |||
| 3d8ec5531c | |||
| 7df0ae0ba3 | |||
| 05d37cc6c6 | |||
| df03f783f8 | |||
| cd9263711f | |||
| 48c3372c33 | |||
| 5d1ff97bf3 | |||
| 1decfe8063 | |||
| a3d0ffb7de | |||
| 59a54f8683 | |||
| 83e2bd6ade | |||
| a59aca10ec | |||
| 9ac6e65087 | |||
| deb8e117ad | |||
| 9c3cae5eca | |||
| 1fbbeba5bc | |||
| 8317972078 | |||
| 0a9947dbb9 | |||
| 51521926e7 | |||
| 8e08ac2ce1 | |||
| fec82d127e | |||
| ceb0770ea0 | |||
| 34eadebe00 | |||
| e7f614cdf3 | |||
| 18507f79b1 | |||
| ee1c7dbf03 | |||
| 868af95ff2 | |||
| 01f59d39e0 | |||
| 0226a5603d | |||
| 1629be3788 | |||
| 480bc630da | |||
| 165cc279de | |||
| 2ec5a2acff | |||
| 6914e83dde | |||
| 263762c0bc | |||
| f8b8a574a6 | |||
| 79c80b351d | |||
| 2c86fb17fc | |||
| e205ffafdf | |||
| 2680b415c6 | |||
| 62d8b35545 | |||
| 2b578efdd6 | |||
| a7f37df34d | |||
| 3edb119422 | |||
| 07d4d5051a | |||
| 0b8e5a75f1 | |||
| f263c73df7 | |||
| f89f201764 | |||
| 9f8dcdf8ea | |||
| f3baf31dcd | |||
| a9400785ca | |||
| 7c76ad2088 | |||
| 6a5839d8cd | |||
| 744f39623f | |||
| 97e57c74e4 | |||
| ff0d6b658b | |||
| 1c946a438d | |||
| 9b047a1927 | |||
| 3fc6141d57 | |||
| daf7e2313b | |||
| 75642d785e | |||
| 2621b5c047 | |||
| 57cb9a1d0b | |||
| 6318ae046c | |||
| 57a41cde9d | |||
| ac86b7a954 | |||
| 0bc500e34f | |||
| a60e065e43 | |||
| 5563b6a786 | |||
| 0345c52aba | |||
| 05c858df9e | |||
| 0b4ef21762 | |||
| 64a58921a8 | |||
| b96098b909 | |||
| b1dbb2c408 | |||
| 3b1a08c67e | |||
| 07d37e133f | |||
| 161eb8bef9 | |||
| eb518c673c | |||
| 6f32a0d6de | |||
| b0684ce29c | |||
| b2c8a4d8ef | |||
| d09ac5bcc6 | |||
| c25c3e9daa | |||
| c1cb5c36a1 | |||
| 20118f941e | |||
| f40eee4577 | |||
| bee05afc87 | |||
| efdc533849 | |||
| 1f7c6d59c1 | |||
| 981622f414 | |||
| 347c8a8716 | |||
| 4542564709 | |||
| cb889ce06d | |||
| db54a305b0 | |||
| 8ccf17543a | |||
| 72e99885aa | |||
| 18d2a9cab6 | |||
| 9c57702afc | |||
| b708eb94d2 | |||
| 82c5531d04 | |||
| e6f49b2d3b | |||
| 8ac97e2c8f | |||
| db7174b0f3 | |||
| a47911048c | |||
| 5a2bdbf966 | |||
| aa562228ef | |||
| 98a70aedf2 | |||
| 9b9da5664b | |||
| 2f2314d2f8 | |||
| 715ebf0747 | |||
| bb0443b967 | |||
| 2cf0b528f0 | |||
| 6a95d481f0 | |||
| d281b21832 | |||
| 1d5cf43e68 | |||
| 6d6b2300a8 | |||
| 640ee55772 | |||
| 7ec12f487b | |||
| 63b42d64b1 | |||
| 667506172a | |||
| 518bb74fbf | |||
| 9038538718 | |||
| 5234f50453 | |||
| 6eabf73ece | |||
| 651d01564d | |||
| 52cdec8d3c | |||
| 998c9bdeb7 | |||
| 318ee89e89 | |||
| 031d7a1f18 | |||
| 7424a226c9 | |||
| 30a1997fd9 | |||
| 778ea0b720 | |||
| 353517f9c6 | |||
| e651b2ee13 | |||
| 018b3a876f | |||
| 1b9586011e | |||
| cb856ce2bb | |||
| 8d3c1c9f9e | |||
| 1ec0f67b29 | |||
| 093491c5b4 | |||
| 56191d0cd9 | |||
| 7342268eb8 | |||
| 3a09cbf42b | |||
| b268368e3d | |||
| 59c8211c41 | |||
| 14560fff0a | |||
| adf3172ebb | |||
| 4ead9cbf6a | |||
| 0863dc785f | |||
| 342538358d | |||
| a8b79055ef | |||
| ec3be4c36a | |||
| 0a2ef0e041 | |||
| e7b623ea16 | |||
| 87777017a0 | |||
| bf2c7a18d1 | |||
| b5505bcd87 | |||
| bdf9fbae71 | |||
| 04c1afc9ce | |||
| 458c51bdaa | |||
| 90a736ba43 | |||
| 661ce4fc1d | |||
| b764f1c861 | |||
| 182949d8d2 | |||
| a879bdeb47 | |||
| 9c66a4ef4e | |||
| d2d75b8e41 | |||
| e36c15f770 | |||
| 8dc6da2b7a | |||
| d3ae252740 | |||
| 29f48bcba6 | |||
| e6fe5adca7 | |||
| 82a96ec91d | |||
| db02cbb575 | |||
| 749dd20704 | |||
| b9db6040f4 | |||
| c9628c0f75 | |||
| 979af88a40 | |||
| 98b4cd330f | |||
| 5ab390c3db | |||
| 71eaf9966f | |||
| 9653d07ae2 | |||
| f1663d0fbf | |||
| 38cb2201a9 | |||
| fa04bea64b | |||
| 2bc66af55d | |||
| db5892d0ae | |||
| 59c7c1e302 | |||
| 48f63ec761 | |||
| 4051e34e20 | |||
| 428bd43d60 | |||
| 67415ff715 | |||
| fbc494abc9 | |||
| 0816af3cf1 | |||
| bb575fff5b | |||
| cbe632839c | |||
| 7c972758af | |||
| 236f66f56f | |||
| a485df2f79 | |||
| 54b9154457 | |||
| 37aabcee4f | |||
| b2d18560be | |||
| 1429aa1edc | |||
| 5d4f942d46 | |||
| 30ea7e854d | |||
| 907f82338e | |||
| dcb0160b64 | |||
| fccd7fa438 | |||
| c39711a87e | |||
| a8de003cf0 | |||
| 6db54fc3b5 | |||
| d058536011 | |||
| 02ad4ba98d | |||
| a68a76112c | |||
| 975c545081 | |||
| fcfee9082b | |||
| e6ad14f8d4 | |||
| 1670f15732 | |||
| 5cd696792b | |||
| fbc399f5fa | |||
| 3d6413ae05 | |||
| 97120a6b04 | |||
| 226162ee57 | |||
| a888ec265f | |||
| 6fb7555f01 | |||
| a8d0e25866 | |||
| 970f7fe69b | |||
| c507df902e | |||
| 7fa5ef8165 | |||
| 92cb768c4b | |||
| 8ec406c2e0 | |||
| 9473c108f0 | |||
| 14c43d9f7e | |||
| ce9a03a5a8 | |||
| 04e8b14fc4 | |||
| 43b747676c | |||
| bd40cf9947 | |||
| 203b31d81f | |||
| 0430fb2772 | |||
| d3746d6859 | |||
| d8dfa89f87 | |||
| cbdb90d06b | |||
| 63e040ea79 | |||
| fd1a0f3b0a | |||
| abaf8a676c | |||
| 0b96fc4701 | |||
| 400e210d37 | |||
| ea0c697ad3 | |||
| edf8c32a0f | |||
| ccef5da7d9 | |||
| ddf213aec4 | |||
| 8bd9237951 | |||
| ae488312a1 | |||
| 1ed45656e4 | |||
| 07edcc5f94 | |||
| 1783059fd4 | |||
| aab766e8ff | |||
| 158514f334 | |||
| 77d29c3728 | |||
| 3c3383ac03 | |||
| 6e46240fd7 | |||
| d01c46bfee | |||
| 1e5007ec8b | |||
| deed95e9a9 | |||
| 082323511a | |||
| c07224cab5 | |||
| 1604a96f41 | |||
| 50963f00c0 | |||
| 699db93b18 | |||
| 85e467581c | |||
| 42e4588e9c | |||
| 93c194cff7 | |||
| 00450dc048 | |||
| c319fd5862 | |||
| 5048b5b585 | |||
| e7f24084af | |||
| c57b9b4fa3 | |||
| ac5b7a4469 | |||
| 884faa0e27 | |||
| 50b4b7bb92 | |||
| cf259ace47 | |||
| 270389a18c | |||
| a340eea769 | |||
| 22589e7103 | |||
| 2b6423d3b7 | |||
| 50bf193fd1 | |||
| c2ba059ced | |||
| 856ed0c765 | |||
| a73681ce8b | |||
| 1426ed952b | |||
| d6bf6eb0a0 | |||
| 97b24079f7 | |||
| 707f84839e | |||
| 643d2f3fad | |||
| 92660e037d | |||
| 2e6a0411fb | |||
| 5d57a5fabb | |||
| cb90ad803b | |||
| 937e8ce1ed | |||
| c1976d5b13 | |||
| 8070e88564 | |||
| 15c0c691ff | |||
| f68912b466 | |||
| dfa4e20a8f | |||
| ee1a194305 | |||
| 0fa88855e5 | |||
| eda3d5c143 | |||
| b450efe5c2 | |||
| ca76626d55 | |||
| ed887953b6 | |||
| 04debe3ea3 | |||
| 4312096dd2 | |||
| 94b079fa7b | |||
| 0373d86349 | |||
| 0f5c290785 | |||
| c79f43bb27 | |||
| 184ad3bc4e | |||
| aa0a4ae3e9 | |||
| ff9c4b407f | |||
| c3b01d477e | |||
| 3c0641745b | |||
| 7186a0c41b | |||
| 4c3bc7450e | |||
| 02f04e2d33 | |||
| 97b6e4c672 | |||
| 2fd1caa2aa | |||
| cb25217c48 | |||
| ab70bc663d | |||
| 0cfe931cd1 | |||
| 29bddb5fcb | |||
| cb7d160346 | |||
| 507c8b8786 | |||
| 60107147c2 | |||
| be2afec86b | |||
| d316d216db | |||
| dd53d0d575 | |||
| 0f6c0a2ccd | |||
| 937a165711 | |||
| 22c402ca3d | |||
| eddbd4fddc | |||
| 0e43ca31a3 | |||
| 9c90a20b4d | |||
| 764e7e7d1f | |||
| 0e8cb00233 | |||
| 0a1a011338 | |||
| 3dfcd9324d | |||
| 3e4ac4a0ca | |||
| be1795d50d | |||
| 0b0b06baa9 | |||
| b789cd2af0 | |||
| 0871403c0a | |||
| 53a34d9352 | |||
| fe23551b04 | |||
| 484b6477d3 | |||
| 8ebe04c2ff | |||
| 672d6b0856 | |||
| 0c066fafa2 | |||
| 6c574ead94 | |||
| 31a62313bb | |||
| 4dacf7064f | |||
| e900e4de77 | |||
| 4ce6939b79 | |||
| 8430fd1473 | |||
| ac7c54e273 | |||
| 6c9a3b530d | |||
| 2f2c70d1df | |||
| a78c991330 | |||
| 8f9349ec53 | |||
| bc6be6a9ad | |||
| a9b7c2795a | |||
| cd81cc8cb8 | |||
| 473b35d807 | |||
| 0c04d5bfc8 | |||
| eed460f435 | |||
| d742982973 | |||
| c8263077a2 | |||
| eae01bdbd9 | |||
| 1ebafbbc20 | |||
| a525bb0257 | |||
| cf5cf9e42f | |||
| 7969dff043 | |||
| d73f7304b3 | |||
| 4400b0117a | |||
| 739c91b1c6 | |||
| 510115ade9 | |||
| 8c2d79b75e | |||
| 1a31fb78e5 | |||
| 97f4d5e3ac | |||
| d0b17f7e7b | |||
| eb74aaff3b | |||
| 9108b665a8 | |||
| e449147ed4 | |||
| 53e82876dd | |||
| dd4a4518b3 | |||
| a9e46c64b1 | |||
| fb85770fd3 | |||
| 9e9e651714 | |||
| 314da7ace8 | |||
| 54103ca120 | |||
| be86a3022f | |||
| 91ecab08da | |||
| cae445556e | |||
| 8c2af87857 | |||
| 2d44e356d3 | |||
| dec1931f07 | |||
| 46473c3756 | |||
| cd893edfcf | |||
| 84302c1739 | |||
| d6f6b4bfe5 | |||
| 8c6531b6fb | |||
| f4993a7e58 | |||
| cc812c2177 | |||
| e11dc028d1 | |||
| e314910a76 | |||
| ee9140c365 | |||
| ce4ccc21dd | |||
| 6108fcf17b | |||
| cd3fb77033 | |||
| 0697274311 | |||
| 11f5aaaf3b | |||
| 8f0b66bd98 | |||
| 3be660dcd9 | |||
| 3bb82d5e68 | |||
| 3f9f1480d3 | |||
| 948c446362 | |||
| 25f888e0d8 | |||
| 98661de24e | |||
| a833ceb737 | |||
| b41d0379f0 | |||
| df6da7dd1c | |||
| 24ca5bc990 | |||
| e3e62b8407 | |||
| 0c98e6f4ca | |||
| 6034121695 | |||
| afe837e30a | |||
| f3cf640e21 | |||
| 8d98cefcca | |||
| bdf57a5c0a | |||
| 37f108d9f7 | |||
| 091663afe0 | |||
| a77918bef9 | |||
| f167714ea1 | |||
| 1cab172169 | |||
| 35c3df5a18 | |||
| b9a6f46543 | |||
| 12b1909c7a | |||
| 5bd57b6dbd | |||
| 961220be3f | |||
| 4db703aeb1 | |||
| cec1cc7086 | |||
| 2bacbe6701 | |||
| 3c65d88c65 | |||
| 726a1c37cc | |||
| 63f2bbb253 | |||
| 7f11cc0daf | |||
| f32884b3b2 | |||
| 97465c1bd8 | |||
| ce0a1ce38a | |||
| f5060a0d4f | |||
| bb34c8a242 | |||
| 34fd733bb7 | |||
| 19b65460ff | |||
| edf277fcaf | |||
| 9db334c2a4 | |||
| 1039d9c95e | |||
| 37c8b2b57f | |||
| 461fb0144e | |||
| 60a9c60f40 | |||
| 869a6b5a51 | |||
| 133e101f83 | |||
| 6ecadb2308 | |||
| 0d3ff81d6c | |||
| e938886629 | |||
| aa32055aa8 | |||
| 59481c37bc | |||
| 0b868dad2d | |||
| 3c063a2263 | |||
| f9750e237a | |||
| 908bb75fdc | |||
| a273827166 | |||
| 0cb96f4b03 | |||
| af7764253d | |||
| af9652f7c8 | |||
| e741ca9216 | |||
| e3950c2fb0 | |||
| 3eae49139c | |||
| 97f17916f9 | |||
| ee0a25962b | |||
| 55fb3b3b55 | |||
| a58d9d1497 | |||
| 801dbc9705 | |||
| e3897c4c34 | |||
| 04dd8914cd | |||
| c2651fd8f8 | |||
| 27f760fdbf | |||
| a74cf0b064 | |||
| e09d2db7e6 | |||
| 28f183f450 | |||
| 5126b605f2 | |||
| aaebdda9d6 | |||
| af29a3f498 | |||
| 55b6773d88 | |||
| 239ec86c4a | |||
| 13adca00d6 | |||
| a76631ee3d | |||
| 7ac99f16cd | |||
| 3b6ca1c59d | |||
| 3f55064c47 | |||
| 917bc2a88c | |||
| 85dfa1ccad | |||
| 606828da1d | |||
| a182d78566 | |||
| 0f294531d3 | |||
| 9189532b83 | |||
| c031253bd4 | |||
| 11ae5d4832 | |||
| 3251dc3d73 | |||
| a671e6acf7 | |||
| 5ce658125c | |||
| 1d88a935a5 | |||
| 8da07a16b9 | |||
| 7ce045ae51 | |||
| a3e253436e | |||
| 08955ce5a4 | |||
| 57a4fa2d38 | |||
| 548e652ba2 | |||
| e92cfae4d9 | |||
| ed1320c937 | |||
| abccffd05f | |||
| c1106aa32e | |||
| a94f5daac9 | |||
| 9d9f16e948 | |||
| 0b5bd0b4cd | |||
| caa59dd9a9 | |||
| 423958dd0e | |||
| 6a90cf5102 | |||
| 2d820a3005 | |||
| 7fff55a1ed | |||
| 52d1d47030 | |||
| 7df1d388f0 | |||
| a34f5f13da | |||
| bcc1e5f79c | |||
| 44d32ea281 | |||
| 63e16c9bb8 | |||
| 528be0e4c0 | |||
| 0660a924b7 | |||
| 6f29d5f3f6 | |||
| 9903004af5 | |||
| 0c2b250901 | |||
| 00f8e96dd2 | |||
| 267fa79164 | |||
| 1528aa9d0c | |||
| 0cd6f7f2e7 | |||
| f05967d871 | |||
| 26087e1580 | |||
| 9275c4fbfd | |||
| a2e03ccf7a | |||
| 6605a38eab | |||
| 0a09d89684 | |||
| c0d4100dd1 | |||
| 791f5af3e0 | |||
| ed57e701bc | |||
| c678a9b6d7 | |||
| 07b428f051 | |||
| a1ab8b6aa8 | |||
| a07848164c | |||
| 1b1d4ee431 | |||
| f8e5668c5c | |||
| d8719626d9 | |||
| 3a06797de0 | |||
| b9d7149dbb | |||
| 72bbb4ec68 | |||
| d9ec1be9b6 | |||
| ecddb6977a | |||
| 77220038a1 | |||
| 016f194271 | |||
| e34fecee5e | |||
| 7448dc5ec5 | |||
| bfd25a08c2 | |||
| 8861e61bdf | |||
| 049c2fca8a | |||
| 63df9df913 | |||
| 05b6740e07 | |||
| 46aac0288c | |||
| 4ec0fbd33c | |||
| 7a24d5fdfa | |||
| 3f082ccace | |||
| a37fc3093a | |||
| 4541940a76 | |||
| 3017fe0c95 | |||
| d3bf9a2478 | |||
| 7107672358 | |||
| 8519d6665e | |||
| 774c9e435e | |||
| e1f35a8d9f | |||
| 67136e418c | |||
| 924cc997aa | |||
| 12eda5f507 | |||
| 96bb979abf | |||
| f74ad0323c | |||
| 62fd73f1b1 | |||
| 44f027eb04 | |||
| c9cf6d610b | |||
| 16d4d28046 | |||
| 2280008029 | |||
| a36439314d | |||
| 290b44fbb7 | |||
| 404c280595 | |||
| b0f8370a31 | |||
| 6abcf4ec3c | |||
| db6d7bcefb | |||
| 0e1913b0b4 | |||
| cc6b097dc5 | |||
| c4f7a0c857 | |||
| 34187d76b6 | |||
| 87e7e9fa07 | |||
| 8643fbb65c | |||
| 6a2846afeb | |||
| 7bfa23e2b1 | |||
| bcd55972cd | |||
| d75e1d04b3 | |||
| 02ef77398a | |||
| b0b19053a7 | |||
| 5cc298555a | |||
| f2a0dcca31 | |||
| c274e60868 | |||
| bde6ef8797 | |||
| df15f63424 | |||
| 1bd4a0aa8e | |||
| f068ce4e85 | |||
| 20093c581c | |||
| 814d7bafa8 | |||
| e07fac0335 | |||
| 39730c71ce | |||
| 8565130166 | |||
| 9acde7fe86 | |||
| 63e43e8d20 | |||
| 10a3af8e5e | |||
| 14536febaf | |||
| 75b597418c | |||
| 435730470b | |||
| 689ddf6875 | |||
| fa550f57b3 | |||
| b9b84b661a | |||
| b7573f449f | |||
| 69f6895bd6 | |||
| 72a1e0d5ca | |||
| 4eb80eedc0 | |||
| 32e6931b46 | |||
| f60258ed71 | |||
| 32454ba64a | |||
| de212f49c2 | |||
| c308bd90cb | |||
| 593bde9d92 | |||
| a16bd7030a | |||
| 4d248bce39 | |||
| e236264848 | |||
| 20e9877fe9 | |||
| b0c4021d17 | |||
| 01bfd84853 | |||
| a0dbcc9bb3 | |||
| bbe351161f | |||
| 968f515679 | |||
| cbb5cb3702 | |||
| 82ed1881ea | |||
| 4d13d309d3 | |||
| 75eae0d8ec | |||
| c329541708 | |||
| 10b8e93713 | |||
| 963b1d60b5 | |||
| 158271de14 | |||
| baba63889d | |||
| d52273a516 | |||
| 1b7556443f | |||
| 9575a1158a | |||
| 8ebcc2f8af | |||
| 8d6de76fa0 | |||
| 0ad813cc8d | |||
| 63ae9a90cf | |||
| 551912145e | |||
| 611f54c237 | |||
| 60c9f49b44 | |||
| 9f66003755 | |||
| 862e1d94be | |||
| 8196f76847 | |||
| 09f1bb4653 | |||
| f626a1f0b7 | |||
| fd609d3e19 | |||
| 977e83cc22 | |||
| 59b3cabf7e | |||
| 4d85c36c16 | |||
| b762f80812 | |||
| 4e0791eb22 | |||
| 392e6d1c98 | |||
| 5a49a555ad | |||
| d59cb63188 | |||
| 1d0f87f408 | |||
| a26264e8ce | |||
| ed716d7569 | |||
| f85fca1720 | |||
| ed2539cbfc | |||
| 5405338d20 | |||
| f8ad2fdc11 | |||
| a618f2b523 | |||
| 8b5a88ba5e | |||
| db9e3b44a1 | |||
| 634408d3a3 | |||
| 529a668db3 | |||
| c0f01da1cd | |||
| 4cbab59fdb | |||
| ec9c9fb0f5 | |||
| 286d634756 | |||
| ca2457bfcb | |||
| 459cafdf56 | |||
| b9d6c8f8bb | |||
| 2da019556b | |||
| cbd28bc12f | |||
| 4332f60cc4 | |||
| 950179ee1c | |||
| 803eb4760e | |||
| 32a41e6c1c | |||
| de195c461b | |||
| 5003a8ea4d | |||
| caa41b0022 | |||
| c7151d2b8d | |||
| 0929ae1a4c | |||
| 0c79c42c10 | |||
| 028b24db03 | |||
| bce3d3f664 | |||
| 828d6f6cc8 | |||
| 0a026cc143 | |||
| 3bc9a87933 | |||
| 769f9adc9d | |||
| b5f53d921e | |||
| 105e9e7825 | |||
| c8cf050156 | |||
| b7baafbbe6 | |||
| 85dde71ec3 | |||
| 2970b086a3 | |||
| 5910709008 | |||
| 2b6ce4f813 | |||
| 451c697fb7 | |||
| 09149318b1 | |||
| d2d8eb9485 | |||
| 91265613a9 | |||
| 31c414bbe1 | |||
| e2a3654ed7 | |||
| 96d7283534 | |||
| 256a7e322b | |||
| e5b78337ac | |||
| 67ba5aa1c5 | |||
| 848a617f98 | |||
| 1fc7efef0d | |||
| 576f6eafbb | |||
| 2caf73b5e3 | |||
| 56abb68e0c | |||
| 7aaac5a48a | |||
| 8326587886 | |||
| 466b3f4784 | |||
| bccdf548a8 | |||
| fa4b1b3d5b | |||
| 9d47fd198f | |||
| 5966ee6800 | |||
| 2d20e3c13d | |||
| 2172f8532d | |||
| 9dc4318152 | |||
| e1a92e7127 | |||
| 767b31caa2 | |||
| c2232936e0 | |||
| 4f1bbfd9e3 | |||
| caf57e37dc | |||
| 64b8e4ad6c | |||
| c9d3907124 | |||
| bf6bea800b | |||
| 26f1673d47 | |||
| 08153454a2 | |||
| efc26ab587 | |||
| e24e0a7e87 | |||
| 23bc267c46 | |||
| 35cc592d61 | |||
| 512f6a1166 | |||
| 3160ffec3f | |||
| c543d4517f | |||
| d7334b991b |
@@ -1,9 +1,6 @@
|
||||
dist/
|
||||
node_modules/
|
||||
coverage/
|
||||
webadmin/dist/
|
||||
installer/src/certs/server.key
|
||||
|
||||
# vim swap files
|
||||
*.swp
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"unused": true,
|
||||
"globalstrict": true,
|
||||
"predef": [ "angular", "$" ],
|
||||
"esnext": true
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
The Cloudron Subscription license
|
||||
Copyright (c) 2018 Cloudron UG
|
||||
Copyright (c) 2019 Cloudron UG
|
||||
|
||||
With regard to the Cloudron Software:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Cloudron
|
||||
# Cloudron Dashboard
|
||||
|
||||
[Cloudron](https://cloudron.io) is the best way to run apps on your server.
|
||||
|
||||
@@ -29,7 +29,7 @@ anyone to effortlessly host web applications on their server on their own terms.
|
||||
* Trivially migrate to another server keeping your apps and data (for example, switch your
|
||||
infrastructure provider or move to a bigger server).
|
||||
|
||||
* Comprehensive [REST API](https://cloudron.io/documentation/developer/api/).
|
||||
* Comprehensive [REST API](https://cloudron.io/developer/api/).
|
||||
|
||||
* [CLI](https://cloudron.io/documentation/cli/) to configure apps.
|
||||
|
||||
@@ -37,7 +37,7 @@ anyone to effortlessly host web applications on their server on their own terms.
|
||||
|
||||
## Demo
|
||||
|
||||
Try our demo at https://my-demo.cloudron.me (username: cloudron password: cloudron).
|
||||
Try our demo at https://my.demo.cloudron.io (username: cloudron password: cloudron).
|
||||
|
||||
## Installing
|
||||
|
||||
@@ -59,6 +59,6 @@ the containers in the Cloudron.
|
||||
|
||||
## Community
|
||||
|
||||
* [Chat](https://chat.cloudron.io/)
|
||||
* [Forum](https://forum.cloudron.io/)
|
||||
* [Support](mailto:support@cloudron.io)
|
||||
|
||||
|
||||
@@ -11,238 +11,209 @@ var argv = require('yargs').argv,
|
||||
rimraf = require('rimraf'),
|
||||
sass = require('gulp-sass'),
|
||||
serve = require('gulp-serve'),
|
||||
sourcemaps = require('gulp-sourcemaps'),
|
||||
uglify = require('gulp-uglify'),
|
||||
url = require('url');
|
||||
|
||||
gulp.task('3rdparty', function () {
|
||||
gulp.src([
|
||||
'webadmin/src/3rdparty/**/*.js',
|
||||
'webadmin/src/3rdparty/**/*.map',
|
||||
'webadmin/src/3rdparty/**/*.css',
|
||||
'webadmin/src/3rdparty/**/*.otf',
|
||||
'webadmin/src/3rdparty/**/*.eot',
|
||||
'webadmin/src/3rdparty/**/*.svg',
|
||||
'webadmin/src/3rdparty/**/*.gif',
|
||||
'webadmin/src/3rdparty/**/*.ttf',
|
||||
'webadmin/src/3rdparty/**/*.woff',
|
||||
'webadmin/src/3rdparty/**/*.woff2'
|
||||
])
|
||||
.pipe(gulp.dest('webadmin/dist/3rdparty/'));
|
||||
|
||||
gulp.src('node_modules/bootstrap-sass/assets/javascripts/bootstrap.min.js')
|
||||
.pipe(gulp.dest('webadmin/dist/3rdparty/js'));
|
||||
});
|
||||
|
||||
|
||||
// --------------
|
||||
// JavaScript
|
||||
// --------------
|
||||
sourcemaps = require('gulp-sourcemaps');
|
||||
|
||||
if (argv.help || argv.h) {
|
||||
console.log('Supported arguments for "gulp develop":');
|
||||
console.log(' --client-id <clientId>');
|
||||
console.log(' --client-secret <clientSecret>');
|
||||
console.log(' --api-origin <cloudron api uri>');
|
||||
console.log(' --revision <revision>');
|
||||
console.log(' --appstore-web-origin <appstore web uri>');
|
||||
console.log(' --appstore-api-origin <appstore api uri>');
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
gulp.task('js', ['js-index', 'js-logs', 'js-terminal', 'js-setup', 'js-setupdns', 'js-restore', 'js-update'], function () {});
|
||||
|
||||
var oauth = {
|
||||
clientId: argv.clientId || 'cid-webadmin',
|
||||
clientSecret: argv.clientSecret || 'unused',
|
||||
apiOrigin: argv.apiOrigin || '',
|
||||
apiOriginHostname: argv.apiOrigin ? url.parse(argv.apiOrigin).hostname : ''
|
||||
};
|
||||
|
||||
var revision = argv.revision || '';
|
||||
|
||||
var appstore = {
|
||||
webOrigin: argv.appstoreWebOrigin || '',
|
||||
apiOrigin: argv.appstoreApiOrigin || ''
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log('Using OAuth credentials:');
|
||||
console.log(' ClientId: %s', oauth.clientId);
|
||||
console.log(' ClientSecret: %s', oauth.clientSecret);
|
||||
console.log(' Cloudron API: %s', oauth.apiOrigin || 'default');
|
||||
console.log(' Cloudron Host: %s', oauth.apiOriginHostname);
|
||||
console.log();
|
||||
console.log('Building for revision: %s', revision);
|
||||
console.log();
|
||||
console.log('Overriding appstore origin:');
|
||||
console.log(' Website: %s', appstore.webOrigin || 'no');
|
||||
console.log(' Api: %s', appstore.apiOrigin || 'no');
|
||||
console.log();
|
||||
|
||||
gulp.task('fontawesome', function () {
|
||||
return gulp.src([
|
||||
'node_modules/@fortawesome/fontawesome-free/*css*/all.min.css',
|
||||
'node_modules/@fortawesome/fontawesome-free/*webfonts*/*.eot',
|
||||
'node_modules/@fortawesome/fontawesome-free/*webfonts*/*.svg',
|
||||
'node_modules/@fortawesome/fontawesome-free/*webfonts*/*.ttf',
|
||||
'node_modules/@fortawesome/fontawesome-free/*webfonts*/*.woff',
|
||||
'node_modules/@fortawesome/fontawesome-free/*webfonts*/*.woff2'
|
||||
]).pipe(gulp.dest('dist/3rdparty/fontawesome/'));
|
||||
});
|
||||
|
||||
gulp.task('bootstrap', function () {
|
||||
return gulp.src('node_modules/bootstrap-sass/assets/javascripts/bootstrap.min.js')
|
||||
.pipe(gulp.dest('dist/3rdparty/js'));
|
||||
});
|
||||
|
||||
gulp.task('3rdparty-copy', function () {
|
||||
return gulp.src([
|
||||
'src/3rdparty/**/*.js',
|
||||
'src/3rdparty/**/*.map',
|
||||
'src/3rdparty/**/*.css',
|
||||
'src/3rdparty/**/*.otf',
|
||||
'src/3rdparty/**/*.eot',
|
||||
'src/3rdparty/**/*.svg',
|
||||
'src/3rdparty/**/*.gif',
|
||||
'src/3rdparty/**/*.ttf'
|
||||
])
|
||||
.pipe(gulp.dest('dist/3rdparty/'));
|
||||
});
|
||||
|
||||
gulp.task('3rdparty', gulp.series(['3rdparty-copy', 'bootstrap', 'fontawesome']));
|
||||
|
||||
// --------------
|
||||
// JavaScript
|
||||
// --------------
|
||||
|
||||
gulp.task('js-index', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src([
|
||||
'webadmin/src/js/index.js',
|
||||
'webadmin/src/js/client.js',
|
||||
'webadmin/src/js/appstore.js',
|
||||
'webadmin/src/js/main.js',
|
||||
'webadmin/src/views/*.js'
|
||||
return gulp.src([
|
||||
'src/js/index.js',
|
||||
'src/js/client.js',
|
||||
'src/js/main.js',
|
||||
'src/views/*.js'
|
||||
])
|
||||
.pipe(ejs({ oauth: oauth }, {}, { ext: '.js' }))
|
||||
.pipe(ejs({ oauth: oauth, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('index.js', { newLine: ';' }))
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-logs', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src(['webadmin/src/js/logs.js', 'webadmin/src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth }, {}, { ext: '.js' }))
|
||||
return gulp.src(['src/js/logs.js', 'src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('logs.js', { newLine: ';' }))
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-terminal', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src(['webadmin/src/js/terminal.js', 'webadmin/src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth }, {}, { ext: '.js' }))
|
||||
return gulp.src(['src/js/terminal.js', 'src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('terminal.js', { newLine: ';' }))
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-setup', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src(['webadmin/src/js/setup.js', 'webadmin/src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth }, {}, { ext: '.js' }))
|
||||
return gulp.src(['src/js/setup.js', 'src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('setup.js', { newLine: ';' }))
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-setupdns', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src(['webadmin/src/js/setupdns.js', 'webadmin/src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth }, {}, { ext: '.js' }))
|
||||
return gulp.src(['src/js/setupdns.js', 'src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('setupdns.js', { newLine: ';' }))
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-restore', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src(['webadmin/src/js/restore.js', 'webadmin/src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth }, {}, { ext: '.js' }))
|
||||
return gulp.src(['src/js/restore.js', 'src/js/client.js'])
|
||||
.pipe(ejs({ oauth: oauth, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('restore.js', { newLine: ';' }))
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
});
|
||||
|
||||
|
||||
gulp.task('js-update', function () {
|
||||
// needs special treatment for error handling
|
||||
var uglifyer = uglify();
|
||||
uglifyer.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
gulp.src(['webadmin/src/js/update.js'])
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(uglifyer)
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist/js'));
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js', gulp.series([ 'js-index', 'js-logs', 'js-terminal', 'js-setup', 'js-setupdns', 'js-restore' ]));
|
||||
|
||||
// --------------
|
||||
// HTML
|
||||
// --------------
|
||||
|
||||
gulp.task('html', ['html-views', 'html-templates'], function () {
|
||||
return gulp.src('webadmin/src/*.html').pipe(ejs({ apiOriginHostname: oauth.apiOriginHostname }, {}, { ext: '.html' })).pipe(gulp.dest('webadmin/dist'));
|
||||
});
|
||||
|
||||
gulp.task('html-views', function () {
|
||||
return gulp.src('webadmin/src/views/**/*.html').pipe(gulp.dest('webadmin/dist/views'));
|
||||
return gulp.src('src/views/**/*.html').pipe(gulp.dest('dist/views'));
|
||||
});
|
||||
|
||||
gulp.task('html-templates', function () {
|
||||
return gulp.src('webadmin/src/templates/**/*.html').pipe(gulp.dest('webadmin/dist/templates'));
|
||||
return gulp.src('src/templates/**/*.html').pipe(gulp.dest('dist/templates'));
|
||||
});
|
||||
|
||||
gulp.task('html-raw', function () {
|
||||
return gulp.src('src/*.html').pipe(ejs({ revision: revision }, {}, { ext: '.html' })).pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('html', gulp.series(['html-views', 'html-templates', 'html-raw']));
|
||||
|
||||
// --------------
|
||||
// CSS
|
||||
// --------------
|
||||
|
||||
gulp.task('css', function () {
|
||||
return gulp.src('webadmin/src/*.scss')
|
||||
return gulp.src('src/*.scss')
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(sass({ includePaths: ['node_modules/bootstrap-sass/assets/stylesheets/'] }).on('error', sass.logError))
|
||||
.pipe(autoprefixer())
|
||||
.pipe(cssnano())
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('webadmin/dist'));
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('images', function () {
|
||||
return gulp.src('webadmin/src/img/**')
|
||||
.pipe(gulp.dest('webadmin/dist/img'));
|
||||
return gulp.src('src/img/**')
|
||||
.pipe(gulp.dest('dist/img'));
|
||||
});
|
||||
|
||||
|
||||
// --------------
|
||||
// Utilities
|
||||
// --------------
|
||||
|
||||
gulp.task('watch', ['default'], function () {
|
||||
gulp.watch(['webadmin/src/*.scss'], ['css']);
|
||||
gulp.watch(['webadmin/src/img/*'], ['images']);
|
||||
gulp.watch(['webadmin/src/**/*.html'], ['html']);
|
||||
gulp.watch(['webadmin/src/views/*.html'], ['html-views']);
|
||||
gulp.watch(['webadmin/src/templates/*.html'], ['html-templates']);
|
||||
gulp.watch(['webadmin/src/js/update.js'], ['js-update']);
|
||||
gulp.watch(['webadmin/src/js/setup.js', 'webadmin/src/js/client.js'], ['js-setup']);
|
||||
gulp.watch(['webadmin/src/js/setupdns.js', 'webadmin/src/js/client.js'], ['js-setupdns']);
|
||||
gulp.watch(['webadmin/src/js/restore.js', 'webadmin/src/js/client.js'], ['js-restore']);
|
||||
gulp.watch(['webadmin/src/js/logs.js', 'webadmin/src/js/client.js'], ['js-logs']);
|
||||
gulp.watch(['webadmin/src/js/terminal.js', 'webadmin/src/js/client.js'], ['js-terminal']);
|
||||
gulp.watch(['webadmin/src/js/index.js', 'webadmin/src/js/client.js', 'webadmin/src/js/appstore.js', 'webadmin/src/js/main.js', 'webadmin/src/views/*.js'], ['js-index']);
|
||||
gulp.watch(['webadmin/src/3rdparty/**/*'], ['3rdparty']);
|
||||
gulp.task('clean', function (done) {
|
||||
rimraf.sync('dist');
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('clean', function () {
|
||||
rimraf.sync('webadmin/dist');
|
||||
gulp.task('default', gulp.series(['clean', 'html', 'js', '3rdparty', 'images', 'css']));
|
||||
|
||||
gulp.task('watch', function (done) {
|
||||
gulp.watch(['src/*.scss'], gulp.series(['css']));
|
||||
gulp.watch(['src/img/*'], gulp.series(['images']));
|
||||
gulp.watch(['src/**/*.html'], gulp.series(['html']));
|
||||
gulp.watch(['src/views/*.html'], gulp.series(['html-views']));
|
||||
gulp.watch(['src/templates/*.html'], gulp.series(['html-templates']));
|
||||
gulp.watch(['src/js/setup.js', 'src/js/client.js'], gulp.series(['js-setup']));
|
||||
gulp.watch(['src/js/setupdns.js', 'src/js/client.js'], gulp.series(['js-setupdns']));
|
||||
gulp.watch(['src/js/restore.js', 'src/js/client.js'], gulp.series(['js-restore']));
|
||||
gulp.watch(['src/js/logs.js', 'src/js/client.js'], gulp.series(['js-logs']));
|
||||
gulp.watch(['src/js/terminal.js', 'src/js/client.js'], gulp.series(['js-terminal']));
|
||||
gulp.watch(['src/js/index.js', 'src/js/client.js', 'src/js/main.js', 'src/views/*.js'], gulp.series(['js-index']));
|
||||
gulp.watch(['src/3rdparty/**/*'], gulp.series(['3rdparty']));
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('default', ['clean', 'html', 'js', '3rdparty', 'images', 'css'], function () {});
|
||||
gulp.task('serve', serve({ root: 'dist', port: 4000 }));
|
||||
|
||||
gulp.task('develop', gulp.series(['default', 'watch', 'serve']));
|
||||
|
||||
gulp.task('develop', ['watch'], serve({ root: 'webadmin/dist', port: 4000 }));
|
||||
|
||||
@@ -1,111 +1,33 @@
|
||||
{
|
||||
"name": "cloudron",
|
||||
"description": "Main code for a cloudron",
|
||||
"name": "dashboard",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "Cloudron authors"
|
||||
"description": "[Cloudron](https://cloudron.io) is the best way to run apps on your server.",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.cloudron.io/cloudron/box.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0 <=4.1.1"
|
||||
"url": "ssh://git@git.cloudron.io:6000/cloudron/dashboard.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"@google-cloud/dns": "^0.7.0",
|
||||
"@google-cloud/storage": "^1.2.1",
|
||||
"@sindresorhus/df": "^2.1.0",
|
||||
"async": "^2.6.0",
|
||||
"aws-sdk": "^2.151.0",
|
||||
"body-parser": "^1.18.2",
|
||||
"cloudron-manifestformat": "^2.10.0",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
"connect-lastmile": "^1.0.2",
|
||||
"connect-timeout": "^1.9.0",
|
||||
"cookie-parser": "^1.3.5",
|
||||
"cookie-session": "^1.3.2",
|
||||
"cron": "^1.3.0",
|
||||
"csurf": "^1.6.6",
|
||||
"db-migrate": "^0.10.0-beta.24",
|
||||
"db-migrate-mysql": "^1.1.10",
|
||||
"debug": "^3.1.0",
|
||||
"dockerode": "^2.5.3",
|
||||
"ejs": "^2.5.7",
|
||||
"ejs-cli": "^2.0.0",
|
||||
"express": "^4.16.2",
|
||||
"express-session": "^1.15.6",
|
||||
"hat": "0.0.3",
|
||||
"json": "^9.0.3",
|
||||
"ldapjs": "^1.0.0",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"mime": "^2.0.3",
|
||||
"moment-timezone": "^0.5.14",
|
||||
"morgan": "^1.9.0",
|
||||
"multiparty": "^4.1.2",
|
||||
"mysql": "^2.15.0",
|
||||
"nodemailer": "^4.4.0",
|
||||
"nodemailer-smtp-transport": "^2.7.4",
|
||||
"oauth2orize": "^1.11.0",
|
||||
"once": "^1.3.2",
|
||||
"parse-links": "^0.1.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-http": "^0.3.0",
|
||||
"passport-http-bearer": "^1.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-oauth2-client-password": "^0.1.2",
|
||||
"password-generator": "^2.2.0",
|
||||
"progress-stream": "^2.0.0",
|
||||
"proxy-middleware": "^0.15.0",
|
||||
"recursive-readdir": "^2.2.1",
|
||||
"request": "^2.83.0",
|
||||
"s3-block-read-stream": "^0.2.0",
|
||||
"safetydance": "^0.7.1",
|
||||
"semver": "^5.4.1",
|
||||
"showdown": "^1.8.2",
|
||||
"split": "^1.0.0",
|
||||
"superagent": "^3.8.1",
|
||||
"supererror": "^0.7.1",
|
||||
"tar-fs": "^1.16.0",
|
||||
"tar-stream": "^1.5.5",
|
||||
"tldjs": "^2.2.0",
|
||||
"underscore": "^1.7.0",
|
||||
"uuid": "^3.1.0",
|
||||
"valid-url": "^1.0.9",
|
||||
"validator": "^9.1.1",
|
||||
"ws": "^3.3.1"
|
||||
"@fortawesome/fontawesome-free": "^5.5.0",
|
||||
"bootstrap-sass": "^3.4.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-autoprefixer": "^5.0.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-cssnano": "^2.1.3",
|
||||
"gulp-ejs": "^3.3.0",
|
||||
"gulp-sass": "^4.0.2",
|
||||
"gulp-serve": "^1.4.0",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"rimraf": "^2.6.2",
|
||||
"yargs": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bootstrap-sass": "^3.3.3",
|
||||
"expect.js": "*",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-autoprefixer": "^4.0.0",
|
||||
"gulp-concat": "^2.4.3",
|
||||
"gulp-cssnano": "^2.1.0",
|
||||
"gulp-ejs": "^3.1.0",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-serve": "^1.0.0",
|
||||
"gulp-sourcemaps": "^2.6.1",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
"hock": "^1.3.2",
|
||||
"istanbul": "*",
|
||||
"js2xmlparser": "^3.0.0",
|
||||
"mocha": "*",
|
||||
"mock-aws-s3": "git+https://github.com/cloudron-io/mock-aws-s3.git",
|
||||
"nock": "^9.0.14",
|
||||
"node-sass": "^4.6.1",
|
||||
"readdirp": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
|
||||
"yargs": "^10.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"migrate_local": "DATABASE_URL=mysql://root:@localhost/box node_modules/.bin/db-migrate up",
|
||||
"migrate_test": "BOX_ENV=test DATABASE_URL=mysql://root:@localhost/boxtest node_modules/.bin/db-migrate up",
|
||||
"test": "npm run migrate_test && src/test/setupTest && BOX_ENV=test ./node_modules/istanbul/lib/cli.js test $1 ./node_modules/mocha/bin/_mocha -- --exit -R spec ./src/test ./src/routes/test/[^a]*",
|
||||
"test_all": "npm run migrate_test && src/test/setupTest && BOX_ENV=test ./node_modules/istanbul/lib/cli.js test $1 ./node_modules/mocha/bin/_mocha -- --exit -R spec ./src/test ./src/routes/test",
|
||||
"postmerge": "/bin/true",
|
||||
"precommit": "/bin/true",
|
||||
"prepush": "npm test",
|
||||
"webadmin": "node_modules/.bin/gulp"
|
||||
"eslintConfig": {
|
||||
"env": {
|
||||
"browser": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* angular-ui-notification - Angular.js service providing simple notifications using Bootstrap 3 styles with css transitions for animating
|
||||
* @author Alex_Crack
|
||||
* @version v0.3.6
|
||||
* @link https://github.com/alexcrack/angular-ui-notification
|
||||
* @license MIT
|
||||
*/
|
||||
.ui-notification
|
||||
{
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
|
||||
width: 300px;
|
||||
|
||||
-webkit-transition: all ease .5s;
|
||||
-o-transition: all ease .5s;
|
||||
transition: all ease .5s;
|
||||
|
||||
color: #fff;
|
||||
border-radius: 0;
|
||||
background: #337ab7;
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, .3);
|
||||
}
|
||||
.ui-notification.clickable
|
||||
{
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-notification.clickable:hover
|
||||
{
|
||||
opacity: .7;
|
||||
}
|
||||
.ui-notification.killed
|
||||
{
|
||||
-webkit-transition: opacity ease 1s;
|
||||
-o-transition: opacity ease 1s;
|
||||
transition: opacity ease 1s;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
.ui-notification > h3
|
||||
{
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
|
||||
display: block;
|
||||
|
||||
margin: 10px 10px 0 10px;
|
||||
padding: 0 0 5px 0;
|
||||
|
||||
text-align: left;
|
||||
|
||||
border-bottom: 1px solid rgba(255, 255, 255, .3);
|
||||
}
|
||||
.ui-notification a
|
||||
{
|
||||
color: #fff;
|
||||
}
|
||||
.ui-notification a:hover
|
||||
{
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ui-notification > .message
|
||||
{
|
||||
margin: 10px 10px 10px 10px;
|
||||
}
|
||||
.ui-notification.warning
|
||||
{
|
||||
color: #fff;
|
||||
background: #f0ad4e;
|
||||
}
|
||||
.ui-notification.error
|
||||
{
|
||||
color: #fff;
|
||||
background: #d9534f;
|
||||
}
|
||||
.ui-notification.success
|
||||
{
|
||||
color: #fff;
|
||||
background: #5cb85c;
|
||||
}
|
||||
.ui-notification.info
|
||||
{
|
||||
color: #fff;
|
||||
background: #5bc0de;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* angular-ui-notification - Angular.js service providing simple notifications using Bootstrap 3 styles with css transitions for animating
|
||||
* @author Alex_Crack
|
||||
* @version v0.3.6
|
||||
* @link https://github.com/alexcrack/angular-ui-notification
|
||||
* @license MIT
|
||||
*/
|
||||
angular.module('ui-notification', []);
|
||||
|
||||
angular.module('ui-notification').provider('Notification', function () {
|
||||
|
||||
this.options = {
|
||||
delay: 5000,
|
||||
startTop: 10,
|
||||
startRight: 10,
|
||||
verticalSpacing: 10,
|
||||
horizontalSpacing: 10,
|
||||
positionX: 'right',
|
||||
positionY: 'top',
|
||||
replaceMessage: false,
|
||||
templateUrl: 'angular-ui-notification.html',
|
||||
onClose: undefined,
|
||||
onClick: undefined,
|
||||
closeOnClick: true,
|
||||
maxCount: 0, // 0 - Infinite
|
||||
container: 'body',
|
||||
priority: 10
|
||||
};
|
||||
|
||||
this.setOptions = function (options) {
|
||||
if (!angular.isObject(options)) throw new Error("Options should be an object!");
|
||||
this.options = angular.extend({}, this.options, options);
|
||||
};
|
||||
|
||||
this.$get = ["$timeout", "$http", "$compile", "$templateCache", "$rootScope", "$injector", "$sce", "$q", "$window", function ($timeout, $http, $compile, $templateCache, $rootScope, $injector, $sce, $q, $window) {
|
||||
var options = this.options;
|
||||
|
||||
var startTop = options.startTop;
|
||||
var startRight = options.startRight;
|
||||
var verticalSpacing = options.verticalSpacing;
|
||||
var horizontalSpacing = options.horizontalSpacing;
|
||||
var delay = options.delay;
|
||||
|
||||
var messageElements = [];
|
||||
var isResizeBound = false;
|
||||
|
||||
var notify = function (args, t) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (typeof args !== 'object' || args === null) {
|
||||
args = {message: args};
|
||||
}
|
||||
|
||||
args.scope = args.scope ? args.scope : $rootScope;
|
||||
args.template = args.templateUrl ? args.templateUrl : options.templateUrl;
|
||||
args.delay = !angular.isUndefined(args.delay) ? args.delay : delay;
|
||||
args.type = t || args.type || options.type || '';
|
||||
args.positionY = args.positionY ? args.positionY : options.positionY;
|
||||
args.positionX = args.positionX ? args.positionX : options.positionX;
|
||||
args.replaceMessage = args.replaceMessage ? args.replaceMessage : options.replaceMessage;
|
||||
args.onClose = args.onClose ? args.onClose : options.onClose;
|
||||
args.onClick = args.onClick ? args.onClick : options.onClick;
|
||||
args.closeOnClick = (args.closeOnClick !== null && args.closeOnClick !== undefined) ? args.closeOnClick : options.closeOnClick;
|
||||
args.container = args.container ? args.container : options.container;
|
||||
args.priority = args.priority ? args.priority : options.priority;
|
||||
|
||||
var template = $templateCache.get(args.template);
|
||||
|
||||
if (template) {
|
||||
processNotificationTemplate(template);
|
||||
} else {
|
||||
// load it via $http only if it isn't default template and template isn't exist in template cache
|
||||
// cache:true means cache it for later access.
|
||||
$http.get(args.template, {cache: true})
|
||||
.then(function (response) {
|
||||
processNotificationTemplate(response.data);
|
||||
})
|
||||
.catch(function (data) {
|
||||
throw new Error('Template (' + args.template + ') could not be loaded. ' + data);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function processNotificationTemplate(template) {
|
||||
|
||||
var scope = args.scope.$new();
|
||||
scope.message = $sce.trustAsHtml(args.message);
|
||||
scope.title = $sce.trustAsHtml(args.title);
|
||||
scope.t = args.type.substr(0, 1);
|
||||
scope.delay = args.delay;
|
||||
scope.onClose = args.onClose;
|
||||
scope.onClick = args.onClick;
|
||||
|
||||
var priorityCompareTop = function (a, b) {
|
||||
return a._priority - b._priority;
|
||||
};
|
||||
|
||||
var priorityCompareBtm = function (a, b) {
|
||||
return b._priority - a._priority;
|
||||
};
|
||||
|
||||
var reposite = function () {
|
||||
var j = 0;
|
||||
var k = 0;
|
||||
var lastTop = startTop;
|
||||
var lastRight = startRight;
|
||||
var lastPosition = [];
|
||||
|
||||
if (args.positionY === 'top') {
|
||||
messageElements.sort(priorityCompareTop);
|
||||
} else if (args.positionY === 'bottom') {
|
||||
messageElements.sort(priorityCompareBtm);
|
||||
}
|
||||
|
||||
for (var i = messageElements.length - 1; i >= 0; i--) {
|
||||
var element = messageElements[i];
|
||||
if (args.replaceMessage && i < messageElements.length - 1) {
|
||||
element.addClass('killed');
|
||||
continue;
|
||||
}
|
||||
var elHeight = parseInt(element[0].offsetHeight);
|
||||
var elWidth = parseInt(element[0].offsetWidth);
|
||||
var position = lastPosition[element._positionY + element._positionX];
|
||||
|
||||
if ((top + elHeight) > window.innerHeight) {
|
||||
position = startTop;
|
||||
k++;
|
||||
j = 0;
|
||||
}
|
||||
|
||||
var top = (lastTop = position ? (j === 0 ? position : position + verticalSpacing) : startTop);
|
||||
var right = lastRight + (k * (horizontalSpacing + elWidth));
|
||||
|
||||
element.css(element._positionY, top + 'px');
|
||||
if (element._positionX === 'center') {
|
||||
element.css('left', parseInt(window.innerWidth / 2 - elWidth / 2) + 'px');
|
||||
} else {
|
||||
element.css(element._positionX, right + 'px');
|
||||
}
|
||||
|
||||
lastPosition[element._positionY + element._positionX] = top + elHeight;
|
||||
|
||||
if (options.maxCount > 0 && messageElements.length > options.maxCount && i === 0) {
|
||||
element.scope().kill(true);
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
};
|
||||
|
||||
var templateElement = $compile(template)(scope);
|
||||
templateElement._positionY = args.positionY;
|
||||
templateElement._positionX = args.positionX;
|
||||
templateElement._priority = args.priority;
|
||||
templateElement.addClass(args.type);
|
||||
|
||||
var closeEvent = function (e) {
|
||||
e = e.originalEvent || e;
|
||||
if (e.type === 'click' || e.propertyName === 'opacity' && e.elapsedTime >= 1) {
|
||||
|
||||
if (scope.onClose) {
|
||||
scope.$apply(scope.onClose(templateElement));
|
||||
}
|
||||
|
||||
if (e.type === 'click')
|
||||
if (scope.onClick) {
|
||||
scope.$apply(scope.onClick(templateElement));
|
||||
}
|
||||
|
||||
templateElement.remove();
|
||||
messageElements.splice(messageElements.indexOf(templateElement), 1);
|
||||
scope.$destroy();
|
||||
reposite();
|
||||
}
|
||||
};
|
||||
|
||||
if (args.closeOnClick) {
|
||||
templateElement.addClass('clickable');
|
||||
templateElement.bind('click', closeEvent);
|
||||
}
|
||||
|
||||
templateElement.bind('webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd', closeEvent);
|
||||
|
||||
if (angular.isNumber(args.delay)) {
|
||||
$timeout(function () {
|
||||
templateElement.addClass('killed');
|
||||
}, args.delay);
|
||||
}
|
||||
|
||||
setCssTransitions('none');
|
||||
|
||||
angular.element(document.querySelector(args.container)).append(templateElement);
|
||||
var offset = -(parseInt(templateElement[0].offsetHeight) + 50);
|
||||
templateElement.css(templateElement._positionY, offset + "px");
|
||||
messageElements.push(templateElement);
|
||||
|
||||
if (args.positionX == 'center') {
|
||||
var elWidth = parseInt(templateElement[0].offsetWidth);
|
||||
templateElement.css('left', parseInt(window.innerWidth / 2 - elWidth / 2) + 'px');
|
||||
}
|
||||
|
||||
$timeout(function () {
|
||||
setCssTransitions('');
|
||||
});
|
||||
|
||||
function setCssTransitions(value) {
|
||||
['-webkit-transition', '-o-transition', 'transition'].forEach(function (prefix) {
|
||||
templateElement.css(prefix, value);
|
||||
});
|
||||
}
|
||||
|
||||
scope._templateElement = templateElement;
|
||||
|
||||
scope.kill = function (isHard) {
|
||||
if (isHard) {
|
||||
if (scope.onClose) {
|
||||
scope.$apply(scope.onClose(scope._templateElement));
|
||||
}
|
||||
|
||||
messageElements.splice(messageElements.indexOf(scope._templateElement), 1);
|
||||
scope._templateElement.remove();
|
||||
scope.$destroy();
|
||||
$timeout(reposite);
|
||||
} else {
|
||||
scope._templateElement.addClass('killed');
|
||||
}
|
||||
};
|
||||
|
||||
$timeout(reposite);
|
||||
|
||||
if (!isResizeBound) {
|
||||
angular.element($window).bind('resize', function (e) {
|
||||
$timeout(reposite);
|
||||
});
|
||||
isResizeBound = true;
|
||||
}
|
||||
|
||||
deferred.resolve(scope);
|
||||
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
notify.primary = function (args) {
|
||||
return this(args, 'primary');
|
||||
};
|
||||
notify.error = function (args) {
|
||||
return this(args, 'error');
|
||||
};
|
||||
notify.success = function (args) {
|
||||
return this(args, 'success');
|
||||
};
|
||||
notify.info = function (args) {
|
||||
return this(args, 'info');
|
||||
};
|
||||
notify.warning = function (args) {
|
||||
return this(args, 'warning');
|
||||
};
|
||||
|
||||
notify.clearAll = function () {
|
||||
angular.forEach(messageElements, function (element) {
|
||||
element.addClass('killed');
|
||||
});
|
||||
};
|
||||
|
||||
return notify;
|
||||
}];
|
||||
});
|
||||
|
||||
angular.module("ui-notification").run(["$templateCache", function($templateCache) {$templateCache.put("angular-ui-notification.html","<div class=\"ui-notification\"><h3 ng-show=\"title\" ng-bind-html=\"title\"></h3><div class=\"message\" ng-bind-html=\"message\"></div></div>");}]);
|
||||
@@ -67,8 +67,7 @@
|
||||
|
||||
<footer class="text-center">
|
||||
<span class="text-muted"><a href="https://cloudron.io" target="_blank">Cloudron</a></span>
|
||||
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter</a></span>
|
||||
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
|
||||
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="546.13336"
|
||||
height="546.13336"
|
||||
viewBox="0 0 512.00001 512.00001"
|
||||
id="svg4519"
|
||||
version="1.1"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
sodipodi:docname="appicon_fallback.svg"
|
||||
inkscape:export-filename="/home/nebulon/projects/yellowtent/dashboard/src/img/appicon_fallback.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4521" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="89.894291"
|
||||
inkscape:cy="162.5294"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g4496"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="2880"
|
||||
inkscape:window-height="1565"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="55"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata4524">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-540.36216)">
|
||||
<g
|
||||
id="g4467"
|
||||
transform="matrix(20.50952,0,0,20.859456,-526.58031,-94.042799)">
|
||||
<g
|
||||
inkscape:export-ydpi="67.349998"
|
||||
inkscape:export-xdpi="67.349998"
|
||||
transform="matrix(0.59473169,0,0,0.59473169,31.04719,102.48374)"
|
||||
id="g4382">
|
||||
<g
|
||||
id="g4496">
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="opacity:1;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:1.10000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4162"
|
||||
sodipodi:sides="6"
|
||||
sodipodi:cx="12.46875"
|
||||
sodipodi:cy="-99.893143"
|
||||
sodipodi:r1="19.266006"
|
||||
sodipodi:r2="16.307295"
|
||||
sodipodi:arg1="-0.52224059"
|
||||
sodipodi:arg2="0.0013581913"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0.12490573"
|
||||
inkscape:randomized="0"
|
||||
d="m 29.166669,-109.50348 c 1.200386,2.08567 1.17988,17.183595 -0.02617,19.265993 -1.206046,2.082397 -14.291486,9.613601 -16.697919,9.610333 -2.406432,-0.0033 -15.4713664,-7.56999 -16.671752,-9.655655 -1.2003857,-2.085666 -1.1798799,-17.183591 0.026167,-19.265991 1.2060467,-2.0824 14.2914862,-9.6136 16.6979192,-9.61033 2.406432,0.003 15.471366,7.56999 16.671752,9.65565 z"
|
||||
transform="rotate(-30,10.993604,-99.259973)"
|
||||
inkscape:export-xdpi="67.349998"
|
||||
inkscape:export-ydpi="67.349998" />
|
||||
<rect
|
||||
inkscape:transform-center-x="0.66390665"
|
||||
ry="3.9522502"
|
||||
y="-107.69034"
|
||||
x="4.8100815"
|
||||
height="14.288903"
|
||||
width="14.288903"
|
||||
id="rect4168-1-1"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3.75875854;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
inkscape:transform-center-y="3.7035412e-06" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,185 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="Application" ng-controller="MainController">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
|
||||
|
||||
<!-- this gets changed once we get the config (because angular has not loaded yet, we see template string for a flash) -->
|
||||
<title> Cloudron </title>
|
||||
|
||||
<link id="favicon" type="image/png" rel="icon" href="/api/v1/cloudron/avatar">
|
||||
<link rel="apple-touch-icon" href="/api/v1/cloudron/avatar">
|
||||
<link rel="icon" href="/api/v1/cloudron/avatar">
|
||||
|
||||
<!-- CSS -->
|
||||
<link type="text/css" rel="stylesheet" href="/3rdparty/slick.css?<%= revision %>"/>
|
||||
<link type="text/css" rel="stylesheet" href="/3rdparty/angular-ui-notification.css?<%= revision %>"/>
|
||||
<link type="text/css" rel="stylesheet" href="/3rdparty/bootstrap-slider/bootstrap-slider.min.css?<%= revision %>"/>
|
||||
<link type="text/css" rel="stylesheet" href="/theme.css?<%= revision %>">
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<link type="text/css" rel="stylesheet" href="/3rdparty/fontawesome/css/all.min.css?<%= revision %>">
|
||||
|
||||
<!-- jQuery-->
|
||||
<script type="text/javascript" src="/3rdparty/js/jquery.min.js?<%= revision %>"></script>
|
||||
|
||||
<!-- toBlob() polyfill-->
|
||||
<script type="text/javascript" src="/3rdparty/js/canvas-to-blob.min.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Bootstrap Core JavaScript -->
|
||||
<script type="text/javascript" src="/3rdparty/js/bootstrap.min.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Slick carousel -->
|
||||
<script type="text/javascript" src="/3rdparty/js/slick.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Angularjs scripts -->
|
||||
<script type="text/javascript" src="/3rdparty/js/angular.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-loader.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-route.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-animate.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-base64.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-md5.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-sanitize.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-slick.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-ui-notification.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-fittext.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/autofill-event.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Angular directives for tldjs -->
|
||||
<script type="text/javascript" src="/3rdparty/js/tld.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-tld.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ -->
|
||||
<script type="text/javascript" src="/3rdparty/js/ui-bootstrap-tpls-1.3.3.min.js?<%= revision %>"></script>
|
||||
|
||||
<script type="text/javascript" src="/3rdparty/js/Chart.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/ansi_up.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/clipboard.min.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Showdown (markdown converter) -->
|
||||
<script type="text/javascript" src="/3rdparty/js/showdown-1.6.4.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/showdown-target-blank.min.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Bootstrap slider -->
|
||||
<script type="text/javascript" src="/3rdparty/bootstrap-slider/bootstrap-slider.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/bootstrap-slider/slider.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Anugular Multiselect https://github.com/sebastianha/angular-bootstrap-multiselect -->
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-bootstrap-multiselect.js?<%= revision %>"></script>
|
||||
|
||||
<!-- colors -->
|
||||
<script type="text/javascript" src="/3rdparty/js/colors.js?<%= revision %>"></script>
|
||||
|
||||
<!-- moment -->
|
||||
<script type="text/javascript" src="/3rdparty/js/moment.min.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Main Application -->
|
||||
<script type="text/javascript" src="/js/index.js?<%= revision %>"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="text/ng-template" id="notification.html">
|
||||
<div class="ui-notification">
|
||||
<h3 ng-show="title" ng-bind-html="title"></h3>
|
||||
<div class="message">
|
||||
<a href="{{action}}" ng-show="action" ng-bind-html="message"></a>
|
||||
<span ng-hide="action" ng-bind-html="message"></span>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<a class="offline-banner animateMe" ng-show="client.offline" ng-cloak href="https://cloudron.io/documentation/troubleshooting/" target="_blank"><i class="fa fa-circle-notch fa-spin"></i> Cloudron is offline. Reconnecting...</a>
|
||||
|
||||
<!-- Modal setup subscription -->
|
||||
<div class="modal fade" id="setupSubscriptionModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">Setup Subscription</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<iframe ng-show="subscription.plan.id === 'free'" ng-src="{{ config.webServerOrigin + '/helper/setup_subscription_info.html?email=' + subscription.emailEncoded + '&cloudronId=' + subscription.cloudronId }}" style="width: 100%; height: 100px; border: none;"></iframe>
|
||||
<iframe ng-show="subscription.plan.id === 'expired'" ng-src="{{ config.webServerOrigin + '/helper/subscription_expired.html?email=' + subscription.emailEncoded + '&cloudronId=' + subscription.cloudronId }}" style="width: 100%; height: 100px; border: none;"></iframe>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="animateMe ng-hide layout-root" ng-show="initialized">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-default navbar-static-top shadow" role="navigation" style="margin-bottom: 0">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand navbar-brand-icon" href="#/"><img ng-src="{{ client.avatar }}" width="40" height="40"/></a>
|
||||
<a class="navbar-brand" href="#/">{{ config.cloudronName || 'Cloudron' }}</a>
|
||||
</div>
|
||||
<!-- /.navbar-header -->
|
||||
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav navbar-right" ng-hide="hideNavBarActions">
|
||||
<li ng-show="subscription.plan.id === 'free'">
|
||||
<a ng-href="" ng-click="showSubscriptionModal()" style="cursor: pointer">
|
||||
<span class="badge badge-success">Setup Subscription</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="subscription.plan.id === 'expired'">
|
||||
<a ng-href="" ng-click="showSubscriptionModal()" style="cursor: pointer">
|
||||
<span class="badge badge-danger">Subscription expired</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-class="{ active: isActive('/apps')}" href="#/apps"><i class="fa fa-cloud-download-alt fa-fw"></i> My Apps</a>
|
||||
</li>
|
||||
<li ng-show="user.admin">
|
||||
<a ng-class="{ active: isActive('/appstore')}" href="#/appstore"><i class="fa fa-th fa-fw"></i> App Store</a>
|
||||
</li>
|
||||
<li ng-show="user.admin">
|
||||
<a ng-class="{ active: isActive('/users')}" href="#/users"><i class="fa fa-users fa-fw"></i> Users</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><img ng-src="{{user.gravatar}}" style="margin-top: -4px;"/> {{user.username}} <span class="badge badge-danger" ng-show="notifications.length">{{ notifications.length }}</span> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="#/account"><i class="fa fa-user fa-fw"></i> Account</a></li>
|
||||
<!-- <li ng-show="user.admin"><a href="#/tokens"><i class="fa fa-key fa-fw"></i> API Access</a></li> -->
|
||||
<li ng-show="user.admin"><a href="#/backups"><i class="fa fa-archive fa-fw"></i> Backups</a></li>
|
||||
<li ng-show="user.admin"><a href="#/domains"><i class="fa fa-globe fa-fw"></i> Domains & Certs</a></li>
|
||||
<li ng-show="user.admin"><a href="#/email"><i class="fa fa-envelope fa-fw"></i> Email</a></li>
|
||||
<li ng-show="user.admin"><a href="#/activity"><i class="fa fa-list-alt fa-fw"></i> Event Log</a></li>
|
||||
<li ng-show="user.admin"><a href="#/graphs"><i class="fa fa-chart-bar fa-fw"></i> Graphs</a></li>
|
||||
<li><a href="#/notifications"><i class="fa fa-bell fa-fw"></i> Notifications <span class="badge badge-danger pull-right" ng-show="notifications.length">{{ notifications.length }}</span></a></li>
|
||||
<li ng-show="user.admin"><a href="#/settings"><i class="fa fa-wrench fa-fw"></i> Settings</a></li>
|
||||
<li ng-show="user.admin" class="divider"></li>
|
||||
<li ng-show="user.admin"><a href="#/support"><i class="fa fa-comment fa-fw"></i> Support</a></li>
|
||||
<li ng-show="user.admin"><a href="#/system"><i class="fa fa-cogs fa-fw"></i> System</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="" ng-click="logout($event)"><i class="fa fa-sign-out-alt fa-fw"></i> Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div ng-view id="ng-view" class="layout-content"></div>
|
||||
|
||||
<footer class="text-center ng-cloak">
|
||||
<span class="text-muted" ng-bind-html="config.uiSpec.footer.body | markdown2html"></span>
|
||||
<span class="version">v{{config.version}}</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
/* global angular:false */
|
||||
/* global showdown:false */
|
||||
/* global moment:false */
|
||||
/* global $:false */
|
||||
/* global ERROR,ISTATES,HSTATES,RSTATES */
|
||||
|
||||
// deal with accessToken in the query, this is passed for example on password reset and account setup upon invite
|
||||
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
@@ -16,6 +19,64 @@ if (search.accessToken) {
|
||||
window.location.search = encodeURIComponent(Object.keys(search).map(function (key) { return key + '=' + search[key]; }).join('&'));
|
||||
}
|
||||
|
||||
// poor man's async in the global namespace
|
||||
function asyncForEachParallel(items, handler, callback) {
|
||||
var alreadyDone = 0;
|
||||
var errored = false;
|
||||
|
||||
if (items.length === 0) return callback();
|
||||
|
||||
function done(error) {
|
||||
// do nothing if already called back due to error
|
||||
if (errored) return;
|
||||
|
||||
if (error) {
|
||||
errored = true;
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
++alreadyDone;
|
||||
|
||||
// we are done
|
||||
if (alreadyDone === items.length) callback();
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
handler(items[i], done);
|
||||
}
|
||||
}
|
||||
|
||||
function asyncForEach(items, handler, callback) {
|
||||
var cur = 0;
|
||||
|
||||
if (items.length === 0) return callback();
|
||||
|
||||
(function iterator() {
|
||||
handler(items[cur], function (error) {
|
||||
if (error) return callback(error);
|
||||
if (cur >= items.length-1) return callback();
|
||||
++cur;
|
||||
|
||||
iterator();
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
function asyncSeries(funcs, callback) {
|
||||
var cur = 0;
|
||||
|
||||
if (funcs.length === 0) return callback();
|
||||
|
||||
(function iterator() {
|
||||
funcs[cur](function (error) {
|
||||
if (error) return callback(error);
|
||||
if (cur >= funcs.length-1) return callback();
|
||||
++cur;
|
||||
|
||||
iterator();
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['ngFitText', 'ngRoute', 'ngAnimate', 'ngSanitize', 'angular-md5', 'base64', 'slick', 'ui-notification', 'ui.bootstrap', 'ui.bootstrap-slider', 'ngTld', 'ui.multiselect']);
|
||||
@@ -25,76 +86,85 @@ app.config(['NotificationProvider', function (NotificationProvider) {
|
||||
delay: 5000,
|
||||
startTop: 60,
|
||||
positionX: 'left',
|
||||
maxCount: 3,
|
||||
templateUrl: 'notification.html'
|
||||
});
|
||||
}]);
|
||||
|
||||
// configure resourceUrlWhitelist https://code.angularjs.org/1.5.8/docs/api/ng/provider/$sceDelegateProvider#resourceUrlWhitelist
|
||||
app.config(function ($sceDelegateProvider) {
|
||||
$sceDelegateProvider.resourceUrlWhitelist([
|
||||
// Allow same origin resource loads.
|
||||
'self',
|
||||
// Allow loading from our assets domain.
|
||||
'https://cloudron.io/**',
|
||||
'https://staging.cloudron.io/**',
|
||||
'https://dev.cloudron.io/**',
|
||||
// Allow local development against the appstore pages
|
||||
'http://localhost:5000/**'
|
||||
]);
|
||||
});
|
||||
|
||||
// setup all major application routes
|
||||
app.config(['$routeProvider', function ($routeProvider) {
|
||||
$routeProvider.when('/', {
|
||||
redirectTo: '/apps'
|
||||
}).when('/users', {
|
||||
controller: 'UsersController',
|
||||
templateUrl: 'views/users.html'
|
||||
templateUrl: 'views/users.html?<%= revision %>'
|
||||
}).when('/app/:appId/:view?', {
|
||||
controller: 'AppController',
|
||||
templateUrl: 'views/app.html?<%= revision %>'
|
||||
}).when('/appstore', {
|
||||
controller: 'AppStoreController',
|
||||
templateUrl: 'views/appstore.html'
|
||||
templateUrl: 'views/appstore.html?<%= revision %>'
|
||||
}).when('/appstore/:appId', {
|
||||
controller: 'AppStoreController',
|
||||
templateUrl: 'views/appstore.html'
|
||||
templateUrl: 'views/appstore.html?<%= revision %>'
|
||||
}).when('/apps', {
|
||||
controller: 'AppsController',
|
||||
templateUrl: 'views/apps.html'
|
||||
templateUrl: 'views/apps.html?<%= revision %>'
|
||||
}).when('/account', {
|
||||
controller: 'AccountController',
|
||||
templateUrl: 'views/account.html'
|
||||
templateUrl: 'views/account.html?<%= revision %>'
|
||||
}).when('/backups', {
|
||||
controller: 'BackupsController',
|
||||
templateUrl: 'views/backups.html?<%= revision %>'
|
||||
}).when('/graphs', {
|
||||
controller: 'GraphsController',
|
||||
templateUrl: 'views/graphs.html'
|
||||
templateUrl: 'views/graphs.html?<%= revision %>'
|
||||
}).when('/domains', {
|
||||
controller: 'DomainsController',
|
||||
templateUrl: 'views/domains.html'
|
||||
templateUrl: 'views/domains.html?<%= revision %>'
|
||||
}).when('/email', {
|
||||
controller: 'EmailController',
|
||||
templateUrl: 'views/email.html'
|
||||
templateUrl: 'views/email.html?<%= revision %>'
|
||||
}).when('/email/:domain', {
|
||||
controller: 'EmailController',
|
||||
templateUrl: 'views/email.html?<%= revision %>'
|
||||
}).when('/notifications', {
|
||||
controller: 'NotificationsController',
|
||||
templateUrl: 'views/notifications.html?<%= revision %>'
|
||||
}).when('/settings', {
|
||||
controller: 'SettingsController',
|
||||
templateUrl: 'views/settings.html'
|
||||
templateUrl: 'views/settings.html?<%= revision %>'
|
||||
}).when('/activity', {
|
||||
controller: 'ActivityController',
|
||||
templateUrl: 'views/activity.html'
|
||||
templateUrl: 'views/activity.html?<%= revision %>'
|
||||
}).when('/support', {
|
||||
controller: 'SupportController',
|
||||
templateUrl: 'views/support.html'
|
||||
templateUrl: 'views/support.html?<%= revision %>'
|
||||
}).when('/system', {
|
||||
controller: 'SystemController',
|
||||
templateUrl: 'views/system.html?<%= revision %>'
|
||||
}).when('/tokens', {
|
||||
controller: 'TokensController',
|
||||
templateUrl: 'views/tokens.html'
|
||||
templateUrl: 'views/tokens.html?<%= revision %>'
|
||||
}).otherwise({ redirectTo: '/'});
|
||||
}]);
|
||||
|
||||
// keep in sync with appdb.js
|
||||
var ISTATES = {
|
||||
PENDING_INSTALL: 'pending_install',
|
||||
PENDING_CLONE: 'pending_clone',
|
||||
PENDING_CONFIGURE: 'pending_configure',
|
||||
PENDING_UNINSTALL: 'pending_uninstall',
|
||||
PENDING_RESTORE: 'pending_restore',
|
||||
PENDING_UPDATE: 'pending_update',
|
||||
PENDING_FORCE_UPDATE: 'pending_force_update',
|
||||
PENDING_BACKUP: 'pending_backup',
|
||||
ERROR: 'error',
|
||||
INSTALLED: 'installed'
|
||||
};
|
||||
var HSTATES = {
|
||||
HEALTHY: 'healthy',
|
||||
UNHEALTHY: 'unhealthy',
|
||||
ERROR: 'error',
|
||||
DEAD: 'dead'
|
||||
};
|
||||
|
||||
app.filter('installError', function () {
|
||||
return function (app) {
|
||||
if (!app) return false;
|
||||
if (app.installationState === ISTATES.ERROR) return true;
|
||||
if (app.installationState === ISTATES.INSTALLED) {
|
||||
// app.health can also be null to indicate insufficient data
|
||||
@@ -105,30 +175,103 @@ app.filter('installError', function () {
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('activeTask', function () {
|
||||
return function (app) {
|
||||
if (!app) return false;
|
||||
return app.taskId !== null;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('installSuccess', function () {
|
||||
return function (app) {
|
||||
if (!app) return false;
|
||||
return app.installationState === ISTATES.INSTALLED;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('appIsInstalledAndHealthy', function () {
|
||||
return function (app) {
|
||||
if (!app) return false;
|
||||
return (app.installationState === ISTATES.INSTALLED && app.health === HSTATES.HEALTHY && app.runState === RSTATES.RUNNING);
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('applicationLink', function() {
|
||||
return function(app) {
|
||||
if (!app) return '';
|
||||
|
||||
if (app.installationState === ISTATES.INSTALLED && app.health === HSTATES.HEALTHY && app.runState === RSTATES.RUNNING && !app.pendingPostInstallConfirmation) {
|
||||
return 'https://' + app.fqdn;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('activeOAuthClients', function () {
|
||||
return function (clients, user) {
|
||||
return clients.filter(function (c) { return user.admin || (c.activeTokens && c.activeTokens.length > 0); });
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyAppMessage', function () {
|
||||
return function (message) {
|
||||
if (message === 'ETRYAGAIN') return 'The DNS record for this location is not setup correctly. Please verify your DNS settings and repair this app.';
|
||||
if (message === 'DNS Record already exists') return 'The DNS record for this location already exists. Manually remove the DNS record and then click on repair.';
|
||||
// this appears when an item in app grid is clicked
|
||||
app.filter('prettyAppErrorMessage', function () {
|
||||
return function (app) {
|
||||
if (!app) return '';
|
||||
|
||||
if (app.installationState === ISTATES.INSTALLED) {
|
||||
// app.health can also be null to indicate insufficient data
|
||||
if (app.health === HSTATES.UNHEALTHY) return 'The app is not responding to health checks. Check the logs for any error messages.';
|
||||
}
|
||||
|
||||
if (app.error.reason === 'Access Denied') {
|
||||
if (app.error.domain) return 'The DNS record for this location is not setup correctly. Please verify your DNS settings and repair this app.';
|
||||
} else if (app.error.reason === 'Already Exists') {
|
||||
if (app.error.domain) return 'The DNS record for this location already exists. Cloudron does not remove existing DNS records. Manually remove the DNS record and then click on repair.';
|
||||
}
|
||||
|
||||
return app.error.message;
|
||||
};
|
||||
});
|
||||
|
||||
// this appears as tool tip in app grid
|
||||
app.filter('appProgressMessage', function () {
|
||||
return function (app) {
|
||||
var message = app.message || (app.error ? app.error.message : '');
|
||||
return message;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('shortAppMessage', function () {
|
||||
return function (message) {
|
||||
if (message === 'ETRYAGAIN') return 'DNS record not setup correctly';
|
||||
return message;
|
||||
app.filter('selectedTagFilter', function () {
|
||||
return function selectedTagFilter(apps, selectedTags) {
|
||||
return apps.filter(function (app) {
|
||||
if (selectedTags.length === 0) return true;
|
||||
if (!app.tags) return false;
|
||||
|
||||
for (var i = 0; i < selectedTags.length; i++) {
|
||||
if (app.tags.indexOf(selectedTags[i]) === -1) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('selectedDomainFilter', function () {
|
||||
return function selectedDomainFilter(apps, selectedDomain) {
|
||||
return apps.filter(function (app) {
|
||||
if (!selectedDomain) return true;
|
||||
if (selectedDomain._alldomains) return true; // magic domain for single select, see apps.js ALL_DOMAINS_DOMAIN
|
||||
|
||||
if (selectedDomain.domain === app.domain) return true;
|
||||
return !!app.alternateDomains.find(function (ad) { return ad.domain === selectedDomain.domain; });
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyDomains', function () {
|
||||
return function prettyDomains(domains) {
|
||||
return domains.map(function (d) { return d.domain; }).join(', ');
|
||||
};
|
||||
});
|
||||
|
||||
@@ -139,51 +282,111 @@ app.filter('prettyMemory', function () {
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyDiskSize', function () {
|
||||
return function (size) {
|
||||
if (!size) return 'not available yet';
|
||||
var i = Math.floor(Math.log(size) / Math.log(1024));
|
||||
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; };
|
||||
});
|
||||
|
||||
app.filter('installationActive', function () {
|
||||
return function(app) {
|
||||
return function (app) {
|
||||
if (app.installationState === ISTATES.ERROR) return false;
|
||||
if (app.installationState === ISTATES.INSTALLED) return false;
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('installationStateLabel', function() {
|
||||
// for better DNS errors
|
||||
function detailedError(app) {
|
||||
if (app.installationProgress === 'ETRYAGAIN') return 'DNS Error';
|
||||
return 'Error';
|
||||
}
|
||||
// this appears in the app grid
|
||||
app.filter('installationStateLabel', function () {
|
||||
return function(app, user) {
|
||||
if (!app) return '';
|
||||
|
||||
return function(app) {
|
||||
var waiting = app.progress === 0 ? ' (Pending)' : '';
|
||||
var waiting = app.progress === 0 ? ' (Queued)' : '';
|
||||
|
||||
switch (app.installationState) {
|
||||
case ISTATES.PENDING_INSTALL:
|
||||
case ISTATES.PENDING_CLONE:
|
||||
return 'Installing' + waiting;
|
||||
case ISTATES.PENDING_CONFIGURE: return 'Configuring' + waiting;
|
||||
case ISTATES.PENDING_CLONE:
|
||||
return 'Cloning' + waiting;
|
||||
case ISTATES.PENDING_LOCATION_CHANGE:
|
||||
case ISTATES.PENDING_CONFIGURE:
|
||||
case ISTATES.PENDING_RECREATE_CONTAINER:
|
||||
case ISTATES.PENDING_DEBUG:
|
||||
return 'Configuring' + waiting;
|
||||
case ISTATES.PENDING_RESIZE:
|
||||
return 'Resizing' + waiting;
|
||||
case ISTATES.PENDING_DATA_DIR_MIGRATION:
|
||||
return 'Migrating data' + waiting;
|
||||
case ISTATES.PENDING_UNINSTALL: return 'Uninstalling' + waiting;
|
||||
case ISTATES.PENDING_RESTORE: return 'Restoring' + waiting;
|
||||
case ISTATES.PENDING_UPDATE: return 'Updating' + waiting;
|
||||
case ISTATES.PENDING_FORCE_UPDATE: return 'Updating' + waiting;
|
||||
case ISTATES.PENDING_BACKUP: return 'Backing up' + waiting;
|
||||
case ISTATES.ERROR: return detailedError(app);
|
||||
case ISTATES.PENDING_START: return 'Starting' + waiting;
|
||||
case ISTATES.PENDING_STOP: return 'Stopping' + waiting;
|
||||
case ISTATES.ERROR: {
|
||||
if (app.error && app.error.message === 'ETRYAGAIN') return 'DNS Error';
|
||||
return 'Error';
|
||||
}
|
||||
case ISTATES.INSTALLED: {
|
||||
if (app.runState === 'running') {
|
||||
if (app.debugMode) {
|
||||
return 'Paused';
|
||||
} else if (app.runState === 'running') {
|
||||
if (!app.health) return 'Starting...'; // no data yet
|
||||
if (app.health === HSTATES.HEALTHY) return 'Running';
|
||||
return 'Not responding'; // dead/exit/unhealthy
|
||||
} else if (app.runState === 'pending_start') return 'Starting...';
|
||||
else if (app.runState === 'pending_stop') return 'Stopping...';
|
||||
else if (app.runState === 'stopped') return 'Stopped';
|
||||
} else if (app.runState === 'stopped') return 'Stopped';
|
||||
else return app.runState;
|
||||
break;
|
||||
}
|
||||
default: return app.installationState;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('taskName', function () {
|
||||
return function(app) {
|
||||
if (!app) return '';
|
||||
|
||||
switch (app.installationState) {
|
||||
case ISTATES.PENDING_INSTALL: return 'install';
|
||||
case ISTATES.PENDING_CLONE: return 'clone';
|
||||
case ISTATES.PENDING_LOCATION_CHANGE: return 'location change';
|
||||
case ISTATES.PENDING_CONFIGURE: return 'configure';
|
||||
case ISTATES.PENDING_RECREATE_CONTAINER: return 'create container';
|
||||
case ISTATES.PENDING_DEBUG: return 'debug';
|
||||
case ISTATES.PENDING_RESIZE: return 'resize';
|
||||
case ISTATES.PENDING_DATA_DIR_MIGRATION: return 'data migration';
|
||||
case ISTATES.PENDING_UNINSTALL: return 'uninstall';
|
||||
case ISTATES.PENDING_RESTORE: return 'restore';
|
||||
case ISTATES.PENDING_UPDATE: return 'update';
|
||||
case ISTATES.PENDING_BACKUP: return 'backup';
|
||||
case ISTATES.PENDING_START: return 'start app';
|
||||
case ISTATES.PENDING_STOP: return 'stop app';
|
||||
default: return app.installationState;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('errorSuggestion', function () {
|
||||
return function (error) {
|
||||
if (!error) return '';
|
||||
|
||||
switch (error.reason) {
|
||||
case ERROR.ACCESS_DENIED:
|
||||
if (error.domain) return 'Check the DNS credentials of ' + error.domain.domain + ' in the Domains & Certs view';
|
||||
return '';
|
||||
case ERROR.COLLECTD_ERROR: return 'Check if collectd is running on the server';
|
||||
case ERROR.DATABASE_ERROR: return 'Check if MySQL database is running on the server';
|
||||
case ERROR.DOCKER_ERROR: return 'Check if docker is running on the server';
|
||||
case ERROR.DNS_ERROR: return 'Check if the DNS service of the domain is running';
|
||||
case ERROR.LOGROTATE_ERROR: return 'Check if logrotate is running on the server';
|
||||
case ERROR.NETWORK_ERROR: return 'Check if there are any network issues on the server';
|
||||
case ERROR.REVERSEPROXY_ERROR: return 'Check if nginx is running on the server';
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('readyToUpdate', function () {
|
||||
return function (apps) {
|
||||
return apps.every(function (app) {
|
||||
@@ -200,25 +403,6 @@ app.filter('inProgressApps', function () {
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('ignoreAdminGroup', function () {
|
||||
return function (groups) {
|
||||
return groups.filter(function (group) {
|
||||
if (group.id) return group.id !== 'admin';
|
||||
return group !== 'admin';
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('applicationLink', function() {
|
||||
return function(app) {
|
||||
if (app.installationState === ISTATES.INSTALLED && app.health === HSTATES.HEALTHY) {
|
||||
return 'https://' + app.fqdn;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyHref', function () {
|
||||
return function (input) {
|
||||
if (!input) return input;
|
||||
@@ -252,9 +436,21 @@ app.filter('prettyDate', function () {
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyLongDate', function () {
|
||||
return function prettyLongDate(time) {
|
||||
return moment(time).format('MMMM Do YYYY, h:mm:ss a');
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyShortDate', function () {
|
||||
return function prettyShortDate(time) {
|
||||
return moment(time).format('MMMM Do YYYY');
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('markdown2html', function () {
|
||||
var converter = new showdown.Converter({
|
||||
extensions: ['targetblank'],
|
||||
extensions: [],
|
||||
simplifiedAutoLink: true,
|
||||
strikethrough: true,
|
||||
tables: true
|
||||
@@ -284,58 +480,6 @@ app.filter('postInstallMessage', function () {
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// keep this in sync with eventlog.js and CLI tool
|
||||
var ACTION_ACTIVATE = 'cloudron.activate';
|
||||
var ACTION_APP_CONFIGURE = 'app.configure';
|
||||
var ACTION_APP_INSTALL = 'app.install';
|
||||
var ACTION_APP_RESTORE = 'app.restore';
|
||||
var ACTION_APP_UNINSTALL = 'app.uninstall';
|
||||
var ACTION_APP_UPDATE = 'app.update';
|
||||
var ACTION_APP_LOGIN = 'app.login';
|
||||
var ACTION_BACKUP_FINISH = 'backup.finish';
|
||||
var ACTION_BACKUP_START = 'backup.start';
|
||||
var ACTION_BACKUP_CLEANUP = 'backup.cleanup';
|
||||
var ACTION_CERTIFICATE_RENEWAL = 'certificate.renew';
|
||||
var ACTION_CLI_MODE = 'settings.climode';
|
||||
var ACTION_START = 'cloudron.start';
|
||||
var ACTION_UPDATE = 'cloudron.update';
|
||||
var ACTION_USER_ADD = 'user.add';
|
||||
var ACTION_USER_LOGIN = 'user.login';
|
||||
var ACTION_USER_REMOVE = 'user.remove';
|
||||
var ACTION_USER_UPDATE = 'user.update';
|
||||
|
||||
app.filter('eventLogDetails', function() {
|
||||
// NOTE: if you change this, the CLI tool (cloudron machine eventlog) probably needs fixing as well
|
||||
return function(eventLog) {
|
||||
var source = eventLog.source;
|
||||
var data = eventLog.data;
|
||||
var errorMessage = data.errorMessage;
|
||||
|
||||
switch (eventLog.action) {
|
||||
case ACTION_ACTIVATE: return 'Cloudron activated';
|
||||
case ACTION_APP_CONFIGURE: return 'App ' + data.appId + ' was configured';
|
||||
case ACTION_APP_INSTALL: return 'App ' + data.manifest.id + '@' + data.manifest.version + ' installed at ' + data.location + ' with id ' + data.appId;
|
||||
case ACTION_APP_RESTORE: return 'App ' + data.appId + ' restored';
|
||||
case ACTION_APP_UNINSTALL: return 'App ' + data.appId + ' uninstalled';
|
||||
case ACTION_APP_UPDATE: return 'App ' + data.appId + ' updated to version ' + data.toManifest.id + '@' + data.toManifest.version;
|
||||
case ACTION_APP_LOGIN: return 'App ' + data.appId + ' logged in';
|
||||
case ACTION_BACKUP_START: return 'Backup started';
|
||||
case ACTION_BACKUP_FINISH: return 'Backup finished. ' + (errorMessage ? ('error: ' + errorMessage) : ('id: ' + data.filename));
|
||||
case ACTION_BACKUP_CLEANUP: return 'Backup ' + data.backup.id + ' removed';
|
||||
case ACTION_CERTIFICATE_RENEWAL: return 'Certificate renewal for ' + data.domain + (errorMessage ? ' failed' : 'succeeded');
|
||||
case ACTION_CLI_MODE: return 'CLI mode was ' + (data.enabled ? 'enabled' : 'disabled');
|
||||
case ACTION_START: return 'Cloudron started with version ' + data.version;
|
||||
case ACTION_UPDATE: return 'Updating to version ' + data.boxUpdateInfo.version;
|
||||
case ACTION_USER_ADD: return 'User ' + data.email + ' added with id ' + data.userId;
|
||||
case ACTION_USER_LOGIN: return 'User ' + data.userId + ' logged in';
|
||||
case ACTION_USER_REMOVE: return 'User ' + data.userId + ' removed';
|
||||
case ACTION_USER_UPDATE: return 'User ' + data.userId + ' updated';
|
||||
default: return eventLog.action;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// custom directive for dynamic names in forms
|
||||
// See http://stackoverflow.com/questions/23616578/issue-registering-form-control-with-interpolated-name#answer-23617401
|
||||
app.directive('laterName', function () { // (2)
|
||||
@@ -375,7 +519,7 @@ app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $loc
|
||||
app.directive('ngClickSelect', function () {
|
||||
return {
|
||||
restrict: 'AC',
|
||||
link: function (scope, element, attrs) {
|
||||
link: function (scope, element/*, attrs */) {
|
||||
element.bind('click', function () {
|
||||
var selection = window.getSelection();
|
||||
var range = document.createRange();
|
||||
@@ -0,0 +1,187 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular */
|
||||
/* global moment */
|
||||
/* global $ */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['angular-md5', 'ui-notification']);
|
||||
|
||||
app.controller('LogsController', ['$scope', 'Client', function ($scope, Client) {
|
||||
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
$scope.initialized = false;
|
||||
$scope.client = Client;
|
||||
$scope.selected = '';
|
||||
$scope.activeEventSource = null;
|
||||
$scope.lines = 100;
|
||||
$scope.selectedAppInfo = null;
|
||||
$scope.selectedTaskInfo = null;
|
||||
|
||||
function ab2str(buf) {
|
||||
return String.fromCharCode.apply(null, new Uint16Array(buf));
|
||||
}
|
||||
|
||||
$scope.clear = function () {
|
||||
var logViewer = $('.logs-container');
|
||||
logViewer.empty();
|
||||
};
|
||||
|
||||
// https://github.com/janl/mustache.js/blob/master/mustache.js#L60
|
||||
var entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/',
|
||||
'`': '`',
|
||||
'=': '='
|
||||
};
|
||||
|
||||
function escapeHtml(string) {
|
||||
return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
|
||||
return entityMap[s];
|
||||
});
|
||||
}
|
||||
|
||||
function showLogs() {
|
||||
if (!$scope.selected) return;
|
||||
|
||||
var func;
|
||||
if ($scope.selected.type === 'platform') func = Client.getPlatformLogs;
|
||||
else if ($scope.selected.type === 'service') func = Client.getServiceLogs;
|
||||
else if ($scope.selected.type === 'task') func = Client.getTaskLogs;
|
||||
else if ($scope.selected.type === 'app') func = Client.getAppLogs;
|
||||
|
||||
func($scope.selected.value, true /* follow */, $scope.lines, function handleLogs(error, result) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.activeEventSource = result;
|
||||
result.onmessage = function handleMessage(message) {
|
||||
var data;
|
||||
|
||||
try {
|
||||
data = JSON.parse(message.data);
|
||||
} catch (e) {
|
||||
return console.error(e);
|
||||
}
|
||||
|
||||
// check if we want to auto scroll (this is before the appending, as that skews the check)
|
||||
var tmp = $('.logs-container');
|
||||
var autoScroll = tmp[0].scrollTop > (tmp[0].scrollHeight - tmp.innerHeight() - 24);
|
||||
|
||||
var logLine = $('<div class="log-line">');
|
||||
var timeString = moment.utc(data.realtimeTimestamp/1000).format('MMM DD HH:mm:ss');
|
||||
logLine.html('<span class="time">' + timeString + ' </span>' + window.ansiToHTML(escapeHtml(typeof data.message === 'string' ? data.message : ab2str(data.message))));
|
||||
tmp.append(logLine);
|
||||
|
||||
if (autoScroll) tmp[0].lastChild.scrollIntoView({ behavior: 'instant', block: 'end' });
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function select(ids, callback) {
|
||||
if (ids.id) {
|
||||
var BUILT_IN_LOGS = [
|
||||
{ name: 'Box', type: 'platform', value: 'box', url: Client.makeURL('/api/v1/cloudron/logs/box') },
|
||||
{ name: 'Graphite', type: 'service', value: 'graphite', url: Client.makeURL('/api/v1/services/graphite/logs') },
|
||||
{ name: 'MongoDB', type: 'service', value: 'mongodb', url: Client.makeURL('/api/v1/services/mongodb/logs') },
|
||||
{ name: 'MySQL', type: 'service', value: 'mysql', url: Client.makeURL('/api/v1/services/mysql/logs') },
|
||||
{ name: 'PostgreSQL', type: 'service', value: 'postgresql', url: Client.makeURL('/api/v1/services/postgresql/logs') },
|
||||
{ name: 'Mail', type: 'service', value: 'mail', url: Client.makeURL('/api/v1/services/mail/logs') },
|
||||
{ name: 'Docker', type: 'service', value: 'docker', url: Client.makeURL('/api/v1/services/docker/logs') },
|
||||
{ name: 'Unbound', type: 'service', value: 'unbound', url: Client.makeURL('/api/v1/services/unbound/logs') },
|
||||
{ name: 'SFTP', type: 'service', value: 'sftp', url: Client.makeURL('/api/v1/services/sftp/logs') },
|
||||
];
|
||||
|
||||
$scope.selected = BUILT_IN_LOGS.find(function (e) { return e.value === ids.id; });
|
||||
callback();
|
||||
} else if (ids.crashId) {
|
||||
$scope.selected = {
|
||||
type: 'platform',
|
||||
value: 'crash-' + ids.crashId,
|
||||
name: 'Crash',
|
||||
url: Client.makeURL('/api/v1/cloudron/logs/crash-' + ids.crashId)
|
||||
};
|
||||
|
||||
callback();
|
||||
} else if (ids.appId) {
|
||||
Client.getApp(ids.appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
$scope.selectedAppInfo = app;
|
||||
|
||||
$scope.selected = {
|
||||
type: 'app',
|
||||
value: app.id,
|
||||
name: app.fqdn + ' (' + app.manifest.title + ')',
|
||||
url: Client.makeURL('/api/v1/apps/' + app.id + '/logs'),
|
||||
addons: app.manifest.addons
|
||||
};
|
||||
|
||||
callback();
|
||||
});
|
||||
} else if (ids.taskId) {
|
||||
Client.getTask(ids.taskId, function (error, task) {
|
||||
if (error) return callback(error);
|
||||
|
||||
$scope.selectedTaskInfo = task;
|
||||
|
||||
$scope.selected = {
|
||||
type: 'task',
|
||||
value: task.id,
|
||||
name: task.type,
|
||||
url: Client.makeURL('/api/v1/tasks/' + task.id + '/logs')
|
||||
};
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
Client.getStatus(function (error, status) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
if (!status.activated) {
|
||||
console.log('Not activated yet, redirecting', status);
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
// check version and force reload if needed
|
||||
if (!localStorage.version) {
|
||||
localStorage.version = status.version;
|
||||
} else if (localStorage.version !== status.version) {
|
||||
localStorage.version = status.version;
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
console.log('Running log version ', localStorage.version);
|
||||
|
||||
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
|
||||
Client.refreshUserInfo(function (error) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
Client.refreshConfig(function (error) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
select({ id: search.id, taskId: search.taskId, appId: search.appId, crashId: search.crashId }, function (error) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
// now mark the Client to be ready
|
||||
Client.setReady();
|
||||
|
||||
$scope.initialized = true;
|
||||
|
||||
showLogs();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
}]);
|
||||
@@ -0,0 +1,172 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular */
|
||||
/* global $ */
|
||||
|
||||
angular.module('Application').controller('MainController', ['$scope', '$route', '$timeout', '$location', 'Client', function ($scope, $route, $timeout, $location, Client) {
|
||||
$scope.initialized = false; // used to animate the UI
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.installedApps = Client.getInstalledApps();
|
||||
$scope.config = {};
|
||||
$scope.status = {};
|
||||
$scope.client = Client;
|
||||
$scope.subscription = {};
|
||||
$scope.notifications = [];
|
||||
|
||||
$scope.hideNavBarActions = $location.path() === '/logs';
|
||||
|
||||
$scope.isActive = function (url) {
|
||||
if (!$route.current) return false;
|
||||
return $route.current.$$route.originalPath.indexOf(url) === 0;
|
||||
};
|
||||
|
||||
$scope.logout = function (event) {
|
||||
event.stopPropagation();
|
||||
$scope.initialized = false;
|
||||
Client.logout();
|
||||
};
|
||||
|
||||
$scope.waitingForPlanSelection = false;
|
||||
$('#setupSubscriptionModal').on('hide.bs.modal', function () {
|
||||
$scope.waitingForPlanSelection = false;
|
||||
|
||||
// check for updates to stay in sync
|
||||
Client.checkForUpdates(function (error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
Client.refreshConfig();
|
||||
});
|
||||
});
|
||||
|
||||
$scope.showSubscriptionModal = function () {
|
||||
$('#setupSubscriptionModal').modal('show');
|
||||
|
||||
function checkPlan() {
|
||||
if (!$scope.waitingForPlanSelection) return;
|
||||
|
||||
Client.getSubscription(function (error, subscription) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
// check again to give more immediate feedback once a subscription was setup
|
||||
if (subscription.plan.id === 'free' || subscription.plan.id === 'expired') {
|
||||
$timeout(checkPlan, 5000);
|
||||
} else {
|
||||
$scope.waitingForPlanSelection = false;
|
||||
$('#setupSubscriptionModal').modal('hide');
|
||||
$scope.subscription = subscription;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.waitingForPlanSelection = true;
|
||||
|
||||
checkPlan();
|
||||
};
|
||||
|
||||
function refreshNotifications() {
|
||||
Client.getNotifications(false, 1, 20, function (error, results) {
|
||||
if (error) console.error(error);
|
||||
else $scope.notifications = results;
|
||||
|
||||
$timeout(refreshNotifications, 60 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: this function is exported and called from the settings.js
|
||||
$scope.updateSubscriptionStatus = function () {
|
||||
if (!Client.getUserInfo().admin) return;
|
||||
|
||||
Client.getSubscription(function (error, subscription) {
|
||||
if (error && error.statusCode === 412) return; // ignore if not yet registered
|
||||
if (error) console.error(error);
|
||||
|
||||
$scope.subscription = subscription;
|
||||
});
|
||||
};
|
||||
|
||||
// update state of acknowledged notification
|
||||
$scope.notificationAcknowledged = function (notificationId) {
|
||||
// remove notification from list
|
||||
$scope.notifications = $scope.notifications.filter(function (n) { return n.id !== notificationId; });
|
||||
};
|
||||
|
||||
function init() {
|
||||
Client.getStatus(function (error, status) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
// WARNING if anything about the routing is changed here test these use-cases:
|
||||
//
|
||||
// 1. Caas
|
||||
// 3. selfhosted restore
|
||||
// 4. local development with gulp develop
|
||||
|
||||
if (!status.activated) {
|
||||
console.log('Not activated yet, redirecting', status);
|
||||
if (status.restore.active || status.restore.errorMessage) { // show the error message in restore page
|
||||
window.location.href = '/restore.html';
|
||||
} else {
|
||||
window.location.href = status.adminFqdn ? '/setup.html' : '/setupdns.html';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// support local development with localhost check
|
||||
if (window.location.hostname !== status.adminFqdn && window.location.hostname !== 'localhost') {
|
||||
// user is accessing by IP or by the old admin location (pre-migration)
|
||||
window.location.href = '/setupdns.html';
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.status = status;
|
||||
|
||||
// check version and force reload if needed
|
||||
if (!localStorage.version) {
|
||||
localStorage.version = status.version;
|
||||
} else if (localStorage.version !== status.version) {
|
||||
localStorage.version = status.version;
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
console.log('Running dashboard version ', localStorage.version);
|
||||
|
||||
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
|
||||
Client.refreshUserInfo(function (error) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
Client.refreshConfig(function (error) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
Client.refreshInstalledApps(function (error) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
// now mark the Client to be ready
|
||||
Client.setReady();
|
||||
|
||||
$scope.config = Client.getConfig();
|
||||
|
||||
$scope.initialized = true;
|
||||
|
||||
refreshNotifications();
|
||||
|
||||
$scope.updateSubscriptionStatus();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Client.onConfig(function (config) {
|
||||
if (config.cloudronName) {
|
||||
document.title = config.cloudronName;
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
// setup all the dialog focus handling
|
||||
['updateModal'].forEach(function (id) {
|
||||
$('#' + id).on('shown.bs.modal', function () {
|
||||
$(this).find('[autofocus]:first').focus();
|
||||
});
|
||||
});
|
||||
}]);
|
||||
@@ -1,6 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular */
|
||||
/* global tld */
|
||||
/* global $ */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['angular-md5', 'ui-notification']);
|
||||
@@ -11,11 +13,13 @@ app.filter('zoneName', function () {
|
||||
};
|
||||
});
|
||||
|
||||
app.controller('RestoreController', ['$scope', '$http', 'Client', function ($scope, $http, Client) {
|
||||
app.controller('RestoreController', ['$scope', 'Client', function ($scope, Client) {
|
||||
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
$scope.client = Client;
|
||||
$scope.busy = false;
|
||||
$scope.error = {};
|
||||
$scope.message = ''; // progress
|
||||
$scope.provider = '';
|
||||
$scope.bucket = '';
|
||||
$scope.prefix = '';
|
||||
@@ -33,6 +37,7 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
// List is from http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
|
||||
$scope.s3Regions = [
|
||||
{ name: 'Asia Pacific (Mumbai)', value: 'ap-south-1' },
|
||||
{ name: 'Asia Pacific (Osaka-Local)', value: 'ap-northeast-3' },
|
||||
{ name: 'Asia Pacific (Seoul)', value: 'ap-northeast-2' },
|
||||
{ name: 'Asia Pacific (Singapore)', value: 'ap-southeast-1' },
|
||||
{ name: 'Asia Pacific (Sydney)', value: 'ap-southeast-2' },
|
||||
@@ -41,6 +46,8 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
{ name: 'EU (Frankfurt)', value: 'eu-central-1' },
|
||||
{ name: 'EU (Ireland)', value: 'eu-west-1' },
|
||||
{ name: 'EU (London)', value: 'eu-west-2' },
|
||||
{ name: 'EU (Paris)', value: 'eu-west-3' },
|
||||
{ name: 'EU (Stockholm)', value: 'eu-north-1' },
|
||||
{ name: 'South America (São Paulo)', value: 'sa-east-1' },
|
||||
{ name: 'US East (N. Virginia)', value: 'us-east-1' },
|
||||
{ name: 'US East (Ohio)', value: 'us-east-2' },
|
||||
@@ -50,10 +57,31 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
|
||||
$scope.doSpacesRegions = [
|
||||
{ name: 'AMS3', value: 'https://ams3.digitaloceanspaces.com' },
|
||||
{ name: 'FRA1', value: 'https://fra1.digitaloceanspaces.com' },
|
||||
{ name: 'NYC3', value: 'https://nyc3.digitaloceanspaces.com' },
|
||||
{ name: 'SFO2', value: 'https://sfo2.digitaloceanspaces.com' },
|
||||
{ name: 'SGP1', value: 'https://sgp1.digitaloceanspaces.com' }
|
||||
];
|
||||
|
||||
$scope.exoscaleSosRegions = [
|
||||
{ name: 'AT-VIE-1', value: 'https://sos-at-vie-1.exo.io' },
|
||||
{ name: 'CH-DK-2', value: 'https://sos-ch-dk-2.exo.io' },
|
||||
{ name: 'CH-GVA-2', value: 'https://sos-ch-gva-2.exo.io' },
|
||||
{ name: 'DE-FRA-1', value: 'https://sos-de-fra-1.exo.io' },
|
||||
];
|
||||
|
||||
// https://www.scaleway.com/docs/object-storage-feature/
|
||||
$scope.scalewayRegions = [
|
||||
{ name: 'FR-PAR', value: 'https://s3.fr-par.scw.cloud', region: 'fr-par' }, // default
|
||||
{ name: 'NL-AMS', value: 'https://s3.nl-ams.scw.cloud', region: 'nl-ams' }
|
||||
];
|
||||
|
||||
$scope.wasabiRegions = [
|
||||
{ name: 'EU Central 1', value: 'https://s3.eu-central-1.wasabisys.com' },
|
||||
{ name: 'US East 1', value: 'https://s3.wasabisys.com' },
|
||||
{ name: 'US West 1', value: 'https://s3.us-west-1.wasabisys.com' }
|
||||
];
|
||||
|
||||
$scope.storageProvider = [
|
||||
{ name: 'Amazon S3', value: 's3' },
|
||||
{ name: 'DigitalOcean Spaces', value: 'digitalocean-spaces' },
|
||||
@@ -61,7 +89,9 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
{ name: 'Filesystem', value: 'filesystem' },
|
||||
{ name: 'Google Cloud Storage', value: 'gcs' },
|
||||
{ name: 'Minio', value: 'minio' },
|
||||
{ name: 'Scaleway Object Storage', value: 'scaleway-objectstorage' },
|
||||
{ name: 'S3 API Compatible (v4)', value: 's3-v4-compat' },
|
||||
{ name: 'Wasabi', value: 'wasabi' }
|
||||
];
|
||||
|
||||
$scope.formats = [
|
||||
@@ -70,7 +100,8 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
];
|
||||
|
||||
$scope.s3like = function (provider) {
|
||||
return provider === 's3' || provider === 'minio' || provider === 's3-v4-compat' || provider === 'exoscale-sos' || provider === 'digitalocean-spaces';
|
||||
return provider === 's3' || provider === 'minio' || provider === 's3-v4-compat' || provider === 'exoscale-sos'
|
||||
|| provider === 'digitalocean-spaces' || provider === 'wasabi' || provider === 'scaleway-objectstorage';
|
||||
};
|
||||
|
||||
$scope.restore = function () {
|
||||
@@ -94,13 +125,19 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
|
||||
if (backupConfig.provider === 's3') {
|
||||
if ($scope.region) backupConfig.region = $scope.region;
|
||||
delete backupConfig.endpoint;
|
||||
} else if (backupConfig.provider === 'minio' || backupConfig.provider === 's3-v4-compat') {
|
||||
backupConfig.region = 'us-east-1';
|
||||
backupConfig.acceptSelfSignedCerts = $scope.acceptSelfSignedCerts;
|
||||
} else if (backupConfig.provider === 'exoscale-sos') {
|
||||
backupConfig.endpoint = 'https://sos-ch-dk-2.exo.io';
|
||||
backupConfig.region = 'us-east-1';
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'wasabi') {
|
||||
backupConfig.region = 'us-east-1';
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'scaleway-objectstorage') {
|
||||
backupConfig.region = $scope.scalewayRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'digitalocean-spaces') {
|
||||
backupConfig.region = 'us-east-1';
|
||||
}
|
||||
@@ -147,7 +184,7 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
$scope.busy = false;
|
||||
|
||||
if (error) {
|
||||
if (error.statusCode === 402) {
|
||||
if (error.statusCode === 424) {
|
||||
$scope.error.generic = error.message;
|
||||
|
||||
if (error.message.indexOf('AWS Access Key Id') !== -1) {
|
||||
@@ -169,12 +206,12 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
$scope.error.generic = 'Unknown region';
|
||||
$scope.error.region = true;
|
||||
$scope.configureBackupForm.region.$setPristine();
|
||||
$('#inputConfigureBackupRegion').focus();
|
||||
$('#inputConfigureBackupDORegion').focus();
|
||||
} else if (error.message.toLowerCase() === 'wrong region') {
|
||||
$scope.error.generic = 'Wrong S3 Region';
|
||||
$scope.error.region = true;
|
||||
$scope.configureBackupForm.region.$setPristine();
|
||||
$('#inputConfigureBackupRegion').focus();
|
||||
$('#inputConfigureBackupS3Region').focus();
|
||||
} else {
|
||||
$('#inputConfigureBackupBucket').focus();
|
||||
}
|
||||
@@ -193,10 +230,18 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
$scope.busy = true;
|
||||
|
||||
Client.getStatus(function (error, status) {
|
||||
if (!error && !status.webadminStatus.restoring) {
|
||||
window.location.href = '/';
|
||||
if (!error && !status.restore.active) { // restore finished
|
||||
if (status.restore.errorMessage) {
|
||||
$scope.busy = false;
|
||||
$scope.error.generic = status.restore.errorMessage;
|
||||
} else { // restore worked, redirect to admin page
|
||||
window.location.href = '/';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.message = status.restore.message;
|
||||
|
||||
setTimeout(waitForRestore, 5000);
|
||||
});
|
||||
}
|
||||
@@ -219,20 +264,23 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
|
||||
|
||||
document.getElementById('gcsKeyFileInput').onchange = readFileLocally($scope.gcsKey, 'content', 'keyFileName');
|
||||
|
||||
Client.getStatus(function (error, status) {
|
||||
if (error) {
|
||||
window.location.href = '/error.html';
|
||||
return;
|
||||
}
|
||||
function init() {
|
||||
Client.getStatus(function (error, status) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
if (status.restoring) return waitForRestore();
|
||||
if (status.restore.active) return waitForRestore();
|
||||
|
||||
if (status.activated) {
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
if (status.restore.errorMessage) $scope.error.generic = status.restore.errorMessage;
|
||||
|
||||
$scope.instanceId = search.instanceId;
|
||||
$scope.initialized = true;
|
||||
});
|
||||
if (status.activated) {
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.instanceId = search.instanceId;
|
||||
$scope.initialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
}]);
|
||||
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular */
|
||||
/* global $ */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['angular-md5', 'ui-notification', 'ui.bootstrap']);
|
||||
|
||||
app.controller('SetupController', ['$scope', 'Client', function ($scope, Client) {
|
||||
// Stupid angular location provider either wants html5 location mode or not, do the query parsing on my own
|
||||
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
$scope.client = Client;
|
||||
$scope.initialized = false;
|
||||
$scope.busy = false;
|
||||
$scope.account = {
|
||||
email: '',
|
||||
displayName: '',
|
||||
requireEmail: false,
|
||||
username: '',
|
||||
password: ''
|
||||
};
|
||||
$scope.error = null;
|
||||
$scope.provider = '';
|
||||
$scope.apiServerOrigin = '';
|
||||
|
||||
$scope.activateCloudron = function () {
|
||||
$scope.busy = true;
|
||||
$scope.error = null;
|
||||
|
||||
Client.createAdmin($scope.account.username, $scope.account.password, $scope.account.email, $scope.account.displayName, function (error) {
|
||||
if (error && error.statusCode === 400) {
|
||||
$scope.busy = false;
|
||||
$scope.error = { username: error.message };
|
||||
$scope.account.username = '';
|
||||
$scope.setupForm.username.$setPristine();
|
||||
setTimeout(function () { $('#inputUsername').focus(); }, 200);
|
||||
return;
|
||||
} else if (error) {
|
||||
$scope.busy = false;
|
||||
console.error('Internal error', error);
|
||||
$scope.error = { generic: error.message };
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = '/';
|
||||
});
|
||||
};
|
||||
|
||||
function init() {
|
||||
Client.getStatus(function (error, status) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
// if we are here from the ip first go to the real domain if already setup
|
||||
if (status.adminFqdn && status.adminFqdn !== window.location.hostname) {
|
||||
window.location.href = 'https://' + status.adminFqdn + '/setup.html';
|
||||
return;
|
||||
}
|
||||
|
||||
// if we don't have a domain yet, first go to domain setup
|
||||
if (!status.adminFqdn) {
|
||||
window.location.href = '/setupdns.html';
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.activated) {
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.account.email = search.email || $scope.account.email;
|
||||
$scope.account.displayName = search.displayName || $scope.account.displayName;
|
||||
$scope.account.requireEmail = !search.email;
|
||||
$scope.provider = status.provider;
|
||||
$scope.apiServerOrigin = status.apiServerOrigin;
|
||||
|
||||
$scope.initialized = true;
|
||||
|
||||
// Ensure we have a good autofocus
|
||||
setTimeout(function () {
|
||||
$(document).find("[autofocus]:first").focus();
|
||||
}, 250);
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
}]);
|
||||
@@ -1,9 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
/* global tld */
|
||||
/* global tld:false */
|
||||
/* global angular:false */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['angular-md5', 'ui-notification']);
|
||||
var app = angular.module('Application', ['angular-md5', 'ui-notification', 'ui.bootstrap']);
|
||||
|
||||
app.filter('zoneName', function () {
|
||||
return function (domain) {
|
||||
@@ -15,15 +16,24 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
$scope.state = null; // 'initialized', 'waitingForDnsSetup', 'waitingForBox'
|
||||
$scope.error = null;
|
||||
$scope.error = {};
|
||||
$scope.provider = '';
|
||||
$scope.showDNSSetup = false;
|
||||
$scope.instanceId = '';
|
||||
$scope.explicitZone = search.zone || '';
|
||||
$scope.isDomain = false;
|
||||
$scope.isSubdomain = false;
|
||||
$scope.tlsConfig = {
|
||||
provider: search.env === 'dev' ? 'letsencrypt-staging' : 'letsencrypt-prod'
|
||||
$scope.hyphenatedSubdomains = false;
|
||||
$scope.tlsProvider = [
|
||||
{ name: 'Let\'s Encrypt Prod', value: 'letsencrypt-prod' },
|
||||
{ name: 'Let\'s Encrypt Prod - Wildcard', value: 'letsencrypt-prod-wildcard' },
|
||||
{ name: 'Let\'s Encrypt Staging', value: 'letsencrypt-staging' },
|
||||
{ name: 'Let\'s Encrypt Staging - Wildcard', value: 'letsencrypt-staging-wildcard' },
|
||||
{ name: 'Self-Signed', value: 'fallback' }, // this is not 'Custom' because we don't allow user to upload certs during setup phase
|
||||
];
|
||||
|
||||
$scope.needsPort80 = function (dnsProvider, tlsProvider) {
|
||||
return ((dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') &&
|
||||
(tlsProvider === 'letsencrypt-prod' || tlsProvider === 'letsencrypt-staging'));
|
||||
};
|
||||
|
||||
// If we migrate the api origin we have to poll the new location
|
||||
@@ -45,24 +55,53 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
// keep in sync with domains.js
|
||||
$scope.dnsProvider = [
|
||||
{ name: 'AWS Route53', value: 'route53' },
|
||||
{ name: 'Cloudflare (DNS only)', value: 'cloudflare' },
|
||||
{ name: 'Digital Ocean', value: 'digitalocean' },
|
||||
{ name: 'Cloudflare', value: 'cloudflare' },
|
||||
{ name: 'DigitalOcean', value: 'digitalocean' },
|
||||
{ name: 'Gandi LiveDNS', value: 'gandi' },
|
||||
{ name: 'GoDaddy', value: 'godaddy' },
|
||||
{ name: 'Google Cloud DNS', value: 'gcdns' },
|
||||
{ name: 'Name.com', value: 'namecom' },
|
||||
{ name: 'Namecheap', value: 'namecheap' },
|
||||
{ name: 'Wildcard', value: 'wildcard' },
|
||||
{ name: 'Manual (not recommended)', value: 'manual' },
|
||||
{ name: 'No-op (only for development)', value: 'noop' }
|
||||
];
|
||||
$scope.dnsCredentials = {
|
||||
error: null,
|
||||
busy: false,
|
||||
advancedVisible: false,
|
||||
domain: '',
|
||||
accessKeyId: '',
|
||||
secretAccessKey: '',
|
||||
gcdnsKey: { keyFileName: '', content: '' },
|
||||
digitalOceanToken: '',
|
||||
provider: 'route53'
|
||||
gandiApiKey: '',
|
||||
cloudflareEmail: '',
|
||||
cloudflareToken: '',
|
||||
godaddyApiKey: '',
|
||||
godaddyApiSecret: '',
|
||||
nameComUsername: '',
|
||||
nameComToken: '',
|
||||
namecheapUsername: '',
|
||||
namecheapApiKey: '',
|
||||
provider: 'route53',
|
||||
zoneName: '',
|
||||
tlsConfig: {
|
||||
provider: 'letsencrypt-prod-wildcard'
|
||||
},
|
||||
hyphenatedSubdomains: false
|
||||
};
|
||||
|
||||
$scope.setDefaultTlsProvider = function () {
|
||||
var dnsProvider = $scope.dnsCredentials.provider;
|
||||
// wildcard LE won't work without automated DNS
|
||||
if (dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') {
|
||||
$scope.dnsCredentials.tlsConfig.provider = 'letsencrypt-prod';
|
||||
} else {
|
||||
$scope.dnsCredentials.tlsConfig.provider = 'letsencrypt-prod-wildcard';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function readFileLocally(obj, file, fileName) {
|
||||
return function (event) {
|
||||
$scope.$apply(function () {
|
||||
@@ -83,25 +122,19 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
|
||||
$scope.setDnsCredentials = function () {
|
||||
$scope.dnsCredentials.busy = true;
|
||||
$scope.dnsCredentials.error = null;
|
||||
$scope.error = null;
|
||||
$scope.error = {};
|
||||
|
||||
var provider = $scope.dnsCredentials.provider;
|
||||
|
||||
var data = {
|
||||
providerToken: $scope.instanceId
|
||||
providerToken: $scope.instanceId,
|
||||
hyphenatedSubdomains: $scope.dnsCredentials.hyphenatedSubdomains
|
||||
};
|
||||
|
||||
// special case the wildcard provider
|
||||
if (provider === 'wildcard') {
|
||||
provider = 'manual';
|
||||
data.wildcard = true;
|
||||
}
|
||||
|
||||
if (provider === 'route53') {
|
||||
data.accessKeyId = $scope.dnsCredentials.accessKeyId;
|
||||
data.secretAccessKey = $scope.dnsCredentials.secretAccessKey;
|
||||
} else if (provider === 'gcdns'){
|
||||
} else if (provider === 'gcdns') {
|
||||
try {
|
||||
var serviceAccountKey = JSON.parse($scope.dnsCredentials.gcdnsKey.content);
|
||||
data.projectId = serviceAccountKey.project_id;
|
||||
@@ -114,25 +147,45 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
throw 'fields_missing';
|
||||
}
|
||||
} catch(e) {
|
||||
$scope.dnsCredentials.error = 'Cannot parse Google Service Account Key';
|
||||
$scope.error.dnsCredentials = 'Cannot parse Google Service Account Key';
|
||||
$scope.dnsCredentials.busy = false;
|
||||
return;
|
||||
}
|
||||
} else if (provider === 'digitalocean') {
|
||||
data.token = $scope.dnsCredentials.digitalOceanToken;
|
||||
} else if (provider === 'gandi') {
|
||||
data.token = $scope.dnsCredentials.gandiApiKey;
|
||||
} else if (provider === 'godaddy') {
|
||||
data.apiKey = $scope.dnsCredentials.godaddyApiKey;
|
||||
data.apiSecret = $scope.dnsCredentials.godaddyApiSecret;
|
||||
} else if (provider === 'cloudflare') {
|
||||
data.email = $scope.dnsCredentials.cloudflareEmail;
|
||||
data.token = $scope.dnsCredentials.cloudflareToken;
|
||||
} else if (provider === 'namecom') {
|
||||
data.username = $scope.dnsCredentials.nameComUsername;
|
||||
data.token = $scope.dnsCredentials.nameComToken;
|
||||
} else if (provider === 'namecheap') {
|
||||
data.token = $scope.dnsCredentials.namecheapApiKey;
|
||||
data.username = $scope.dnsCredentials.namecheapUsername;
|
||||
}
|
||||
|
||||
Client.setupDnsConfig($scope.dnsCredentials.domain, $scope.explicitZone, provider, data, $scope.tlsConfig, function (error) {
|
||||
if (error && error.statusCode === 403) {
|
||||
var tlsConfig = {
|
||||
provider: $scope.dnsCredentials.tlsConfig.provider,
|
||||
wildcard: false
|
||||
};
|
||||
if ($scope.dnsCredentials.tlsConfig.provider.indexOf('-wildcard') !== -1) {
|
||||
tlsConfig.provider = tlsConfig.provider.replace('-wildcard', '');
|
||||
tlsConfig.wildcard = true;
|
||||
}
|
||||
|
||||
Client.setup($scope.dnsCredentials.domain, $scope.dnsCredentials.zoneName, provider, data, tlsConfig, function (error) {
|
||||
if (error && error.statusCode === 401) {
|
||||
$scope.dnsCredentials.busy = false;
|
||||
$scope.error = 'Wrong instance id provided.';
|
||||
$scope.error.ami = 'Wrong instance id provided.';
|
||||
return;
|
||||
} else if (error) {
|
||||
$scope.dnsCredentials.busy = false;
|
||||
$scope.dnsCredentials.error = error.message;
|
||||
$scope.error.dnsCredentials = error.message;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -144,12 +197,19 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
$scope.state = 'waitingForDnsSetup';
|
||||
|
||||
Client.getStatus(function (error, status) {
|
||||
// webadminStatus.dns is intentionally not tested. it can be false if dns creds are invalid
|
||||
// runConfigurationChecks() in main.js will pick the .dns and show a notification
|
||||
if (!error && status.adminFqdn && status.webadminStatus.tls) {
|
||||
window.location.href = 'https://' + status.adminFqdn + '/setup.html';
|
||||
if (!error && !status.setup.active) {
|
||||
if (!status.adminFqdn || status.setup.errorMessage) { // setup reset or errored. start over
|
||||
$scope.error.setup = status.setup.errorMessage;
|
||||
$scope.state = 'initialized';
|
||||
$scope.dnsCredentials.busy = false;
|
||||
} else { // proceed to activation
|
||||
window.location.href = 'https://' + status.adminFqdn + '/setup.html';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.message = status.setup.message;
|
||||
|
||||
setTimeout(waitForDnsSetup, 5000);
|
||||
});
|
||||
}
|
||||
@@ -166,8 +226,8 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
// domain is currently like a lock flag
|
||||
if (status.adminFqdn) return waitForDnsSetup();
|
||||
|
||||
if (status.provider === 'digitalocean') $scope.dnsCredentials.provider = 'digitalocean';
|
||||
if (status.provider === 'gcp') $scope.dnsCredentials.provider = 'gcdns';
|
||||
if (status.provider === 'digitalocean' || status.provider === 'digitalocean-mp') $scope.dnsCredentials.provider = 'digitalocean';
|
||||
if (status.provider === 'gce') $scope.dnsCredentials.provider = 'gcdns';
|
||||
if (status.provider === 'ami') {
|
||||
// remove route53 on ami
|
||||
$scope.dnsProvider.shift();
|
||||
@@ -1,6 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular */
|
||||
/* global $ */
|
||||
/* global Terminal */
|
||||
/* global RSTATES,ISTATES */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['angular-md5', 'ui-notification']);
|
||||
@@ -18,6 +21,7 @@ app.controller('TerminalController', ['$scope', '$timeout', '$location', 'Client
|
||||
$scope.restartAppBusy = false;
|
||||
$scope.appBusy = false;
|
||||
$scope.selectedAppInfo = null;
|
||||
$scope.schedulerTasks = [];
|
||||
|
||||
$scope.downloadFile = {
|
||||
error: '',
|
||||
@@ -27,7 +31,7 @@ app.controller('TerminalController', ['$scope', '$timeout', '$location', 'Client
|
||||
downloadUrl: function () {
|
||||
if (!$scope.downloadFile.filePath) return '';
|
||||
|
||||
var filePath = $scope.downloadFile.filePath.replace(/\/*\//g, '/');
|
||||
var filePath = encodeURIComponent($scope.downloadFile.filePath);
|
||||
|
||||
return Client.apiOrigin + '/api/v1/apps/' + $scope.selected.value + '/download?file=' + filePath + '&access_token=' + Client.getToken();
|
||||
},
|
||||
@@ -99,19 +103,6 @@ app.controller('TerminalController', ['$scope', '$timeout', '$location', 'Client
|
||||
fileUpload.click();
|
||||
};
|
||||
|
||||
$scope.populateDropdown = function () {
|
||||
Client.getInstalledApps().forEach(function (app) {
|
||||
$scope.apps.push({
|
||||
type: 'app',
|
||||
value: app.id,
|
||||
name: app.fqdn + ' (' + app.manifest.title + ')',
|
||||
addons: app.manifest.addons
|
||||
});
|
||||
});
|
||||
|
||||
// $scope.selected = $scope.apps[0];
|
||||
};
|
||||
|
||||
$scope.usesAddon = function (addon) {
|
||||
if (!$scope.selected || !$scope.selected.addons) return false;
|
||||
return !!Object.keys($scope.selected.addons).find(function (a) { return a === addon; });
|
||||
@@ -132,96 +123,108 @@ app.controller('TerminalController', ['$scope', '$timeout', '$location', 'Client
|
||||
|
||||
$scope.restartApp = function () {
|
||||
$scope.restartAppBusy = true;
|
||||
$scope.appBusy = true;
|
||||
|
||||
var appId = $scope.selected.value;
|
||||
|
||||
function waitUntilStopped(callback) {
|
||||
Client.refreshInstalledApps(function (error) {
|
||||
refreshApp(appId, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
Client.getApp(appId, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
if (result.runState === 'stopped') return callback();
|
||||
setTimeout(waitUntilStopped.bind(null, callback), 2000);
|
||||
});
|
||||
if (result.installationState === ISTATES.INSTALLED && result.runState === RSTATES.STOPPED) return callback();
|
||||
setTimeout(waitUntilStopped.bind(null, callback), 2000);
|
||||
});
|
||||
}
|
||||
|
||||
Client.stopApp(appId, function (error) {
|
||||
if (error) return console.error('Failed to stop app.', error);
|
||||
if (error) console.error('Failed to stop app.', error);
|
||||
|
||||
waitUntilStopped(function (error) {
|
||||
if (error) return console.error('Failed to get app status.', error);
|
||||
if (error) console.error('Failed to get app status.', error);
|
||||
|
||||
Client.startApp(appId, function (error) {
|
||||
if (error) console.error('Failed to start app.', error);
|
||||
|
||||
$scope.restartAppBusy = false;
|
||||
$scope.appBusy = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.repairApp = function () {
|
||||
$('#repairAppModal').modal('show');
|
||||
$scope.pauseApp = function () {
|
||||
$('#pauseAppModal').modal('show');
|
||||
};
|
||||
|
||||
$scope.repairAppBegin = function () {
|
||||
$scope.pauseAppBegin = function () {
|
||||
$scope.appBusy = true;
|
||||
|
||||
function waitUntilInRepairState() {
|
||||
Client.refreshInstalledApps(function (error) {
|
||||
if (error) return console.error('Failed to refresh app status.', error);
|
||||
function waitUntilInPauseState() {
|
||||
refreshApp($scope.selected.value, function (error, result) {
|
||||
if (error) return console.error('Failed to get app status.', error);
|
||||
|
||||
Client.getApp($scope.selected.value, function (error, result) {
|
||||
if (error) return console.error('Failed to get app status.', error);
|
||||
|
||||
if (result.installationState === 'installed') $scope.appBusy = false;
|
||||
else setTimeout(waitUntilInRepairState, 2000);
|
||||
});
|
||||
if (result.installationState === 'installed') $scope.appBusy = false;
|
||||
else setTimeout(waitUntilInPauseState, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
Client.debugApp($scope.selected.value, true, function (error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
Client.refreshInstalledApps(function (error) {
|
||||
if (error) console.error(error);
|
||||
$('#pauseAppModal').modal('hide');
|
||||
|
||||
$('#repairAppModal').modal('hide');
|
||||
|
||||
waitUntilInRepairState();
|
||||
});
|
||||
waitUntilInPauseState();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.repairAppDone = function () {
|
||||
$scope.pauseAppDone = function () {
|
||||
$scope.appBusy = true;
|
||||
|
||||
function waitUntilInNormalState() {
|
||||
Client.refreshInstalledApps(function (error) {
|
||||
if (error) return console.error('Failed to refresh app status.', error);
|
||||
refreshApp($scope.selected.value, function (error, result) {
|
||||
if (error) return console.error('Failed to get app status.', error);
|
||||
|
||||
Client.getApp($scope.selected.value, function (error, result) {
|
||||
if (error) return console.error('Failed to get app status.', error);
|
||||
|
||||
if (result.installationState === 'installed') $scope.appBusy = false;
|
||||
else setTimeout(waitUntilInNormalState, 2000);
|
||||
});
|
||||
if (result.installationState === 'installed') $scope.appBusy = false;
|
||||
else setTimeout(waitUntilInNormalState, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
Client.debugApp($scope.selected.value, false, function (error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
Client.refreshInstalledApps(function (error) {
|
||||
if (error) console.error(error);
|
||||
|
||||
waitUntilInNormalState();
|
||||
});
|
||||
waitUntilInNormalState();
|
||||
});
|
||||
};
|
||||
|
||||
function createTerminalSocket() {
|
||||
try {
|
||||
// websocket cannot use relative urls
|
||||
var url = Client.apiOrigin.replace('https', 'wss') + '/api/v1/apps/' + $scope.selected.value + '/execws?tty=true&rows=' + $scope.terminal.rows + '&columns=' + $scope.terminal.cols + '&access_token=' + Client.getToken();
|
||||
$scope.terminalSocket = new WebSocket(url);
|
||||
$scope.terminal.attach($scope.terminalSocket);
|
||||
|
||||
$scope.terminalSocket.onclose = function () {
|
||||
// retry in one second
|
||||
$scope.terminalReconnectTimeout = setTimeout(function () {
|
||||
showTerminal(true);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshApp(id, callback) {
|
||||
Client.getApp(id, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
$scope.selectedAppInfo = result;
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
}
|
||||
|
||||
function showTerminal(retry) {
|
||||
reset();
|
||||
|
||||
@@ -229,56 +232,72 @@ app.controller('TerminalController', ['$scope', '$timeout', '$location', 'Client
|
||||
|
||||
var appId = $scope.selected.value;
|
||||
|
||||
Client.getApp(appId, function (error, result) {
|
||||
refreshApp(appId, function (error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
// we expect this to be called _after_ a reconfigure was issued
|
||||
if (result.installationState === 'pending_configure') {
|
||||
$scope.appBusy = true;
|
||||
} else if (result.installationState === 'installed') {
|
||||
$scope.appBusy = false;
|
||||
}
|
||||
var result = $scope.selectedAppInfo;
|
||||
|
||||
$scope.selectedAppInfo = result;
|
||||
$scope.schedulerTasks = result.manifest.addons.scheduler ? Object.keys(result.manifest.addons.scheduler).map(function (k) { return { name: k, command: result.manifest.addons.scheduler[k].command }; }) : [];
|
||||
|
||||
$scope.terminal = new Terminal();
|
||||
$scope.terminal.open(document.querySelector('#terminalContainer'));
|
||||
$scope.terminal.fit();
|
||||
$scope.terminal.open(document.querySelector('#terminalContainer'), true);
|
||||
|
||||
try {
|
||||
// websocket cannot use relative urls
|
||||
var url = Client.apiOrigin.replace('https', 'wss') + '/api/v1/apps/' + $scope.selected.value + '/execws?tty=true&rows=' + $scope.terminal.rows + '&columns=' + $scope.terminal.cols + '&access_token=' + Client.getToken();
|
||||
$scope.terminalSocket = new WebSocket(url);
|
||||
$scope.terminal.attach($scope.terminalSocket);
|
||||
window.terminal = $scope.terminal;
|
||||
|
||||
$scope.terminalSocket.onclose = function () {
|
||||
// retry in one second
|
||||
$scope.terminalReconnectTimeout = setTimeout(function () {
|
||||
showTerminal(true);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// Let the browser handle paste
|
||||
$scope.terminal.attachCustomKeyEventHandler(function (e) {
|
||||
if (e.key === 'v' && (e.ctrlKey || e.metaKey)) return false;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
// Let the browser handle paste
|
||||
$scope.terminal.attachCustomKeyEventHandler(function (e) {
|
||||
if (e.key === 'v' && (e.ctrlKey || e.metaKey)) return false;
|
||||
});
|
||||
|
||||
if (retry) $scope.terminal.writeln('Reconnecting...');
|
||||
else $scope.terminal.writeln('Connecting...');
|
||||
|
||||
// we have to give it some time to setup the terminal to make it fit, there is no event unfortunately
|
||||
setTimeout(function () {
|
||||
if (!$scope.terminal) return;
|
||||
$scope.terminal.fit();
|
||||
|
||||
// this is here so that the text wraps correctly after the fit!
|
||||
var YELLOW = '\u001b[33m'; // https://gist.github.com/dainkaplan/4651352
|
||||
var NC = '\u001b[0m';
|
||||
$scope.terminal.writeln(YELLOW + 'If you resize the browser window, press Ctrl+D to start a new session with the current size.' + NC);
|
||||
|
||||
createTerminalSocket(); // create exec container after we fit() since we cannot resize exec container post-creation
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.terminalInject = function (addon) {
|
||||
$scope.terminalInject = function (addon, extra) {
|
||||
if (!$scope.terminalSocket) return;
|
||||
|
||||
var cmd;
|
||||
if (addon === 'mysql') cmd = 'mysql --user=${MYSQL_USERNAME} --password=${MYSQL_PASSWORD} --host=${MYSQL_HOST} ${MYSQL_DATABASE}';
|
||||
else if (addon === 'postgresql') cmd = 'PGPASSWORD=${POSTGRESQL_PASSWORD} psql -h ${POSTGRESQL_HOST} -p ${POSTGRESQL_PORT} -U ${POSTGRESQL_USERNAME} -d ${POSTGRESQL_DATABASE}';
|
||||
else if (addon === 'mongodb') cmd = 'mongo -u "${MONGODB_USERNAME}" -p "${MONGODB_PASSWORD}" ${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DATABASE}';
|
||||
else if (addon === 'redis') cmd = 'redis-cli -h "${REDIS_HOST}" -p "${REDIS_PORT}" -a "${REDIS_PASSWORD}"';
|
||||
var cmd, manifestVersion = $scope.selected.manifest.manifestVersion;
|
||||
if (addon === 'mysql') {
|
||||
if (manifestVersion === 1) {
|
||||
cmd = 'mysql --user=${MYSQL_USERNAME} --password=${MYSQL_PASSWORD} --host=${MYSQL_HOST} ${MYSQL_DATABASE}';
|
||||
} else {
|
||||
cmd = 'mysql --user=${CLOUDRON_MYSQL_USERNAME} --password=${CLOUDRON_MYSQL_PASSWORD} --host=${CLOUDRON_MYSQL_HOST} ${CLOUDRON_MYSQL_DATABASE}';
|
||||
}
|
||||
} else if (addon === 'postgresql') {
|
||||
if (manifestVersion === 1) {
|
||||
cmd = 'PGPASSWORD=${POSTGRESQL_PASSWORD} psql -h ${POSTGRESQL_HOST} -p ${POSTGRESQL_PORT} -U ${POSTGRESQL_USERNAME} -d ${POSTGRESQL_DATABASE}';
|
||||
} else {
|
||||
cmd = 'PGPASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} psql -h ${CLOUDRON_POSTGRESQL_HOST} -p ${CLOUDRON_POSTGRESQL_PORT} -U ${CLOUDRON_POSTGRESQL_USERNAME} -d ${CLOUDRON_POSTGRESQL_DATABASE}';
|
||||
}
|
||||
} else if (addon === 'mongodb') {
|
||||
if (manifestVersion === 1) {
|
||||
cmd = 'mongo -u "${MONGODB_USERNAME}" -p "${MONGODB_PASSWORD}" ${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DATABASE}';
|
||||
} else {
|
||||
cmd = 'mongo -u "${CLOUDRON_MONGODB_USERNAME}" -p "${CLOUDRON_MONGODB_PASSWORD}" ${CLOUDRON_MONGODB_HOST}:${CLOUDRON_MONGODB_PORT}/${CLOUDRON_MONGODB_DATABASE}';
|
||||
}
|
||||
} else if (addon === 'redis') {
|
||||
if (manifestVersion === 1) {
|
||||
cmd = 'redis-cli -h "${REDIS_HOST}" -p "${REDIS_PORT}" -a "${REDIS_PASSWORD}"';
|
||||
} else {
|
||||
cmd = 'redis-cli -h "${CLOUDRON_REDIS_HOST}" -p "${CLOUDRON_REDIS_PORT}" -a "${CLOUDRON_REDIS_PASSWORD}"';
|
||||
}
|
||||
} else if (addon === 'scheduler' && extra) {
|
||||
cmd = extra.command;
|
||||
}
|
||||
|
||||
if (!cmd) return;
|
||||
|
||||
@@ -288,29 +307,6 @@ app.controller('TerminalController', ['$scope', '$timeout', '$location', 'Client
|
||||
$scope.terminal.focus();
|
||||
};
|
||||
|
||||
Client.onReady($scope.populateDropdown);
|
||||
|
||||
// Client.onApps(function () {
|
||||
// console.log('onapps')
|
||||
// if ($scope.$$destroyed) return;
|
||||
// if ($scope.selected.type !== 'app') return $scope.appBusy = false;
|
||||
|
||||
// var appId = $scope.selected.value;
|
||||
|
||||
// Client.getApp(appId, function (error, result) {
|
||||
// if (error) return console.error(error);
|
||||
|
||||
// // we expect this to be called _after_ a reconfigure was issued
|
||||
// if (result.installationState === 'pending_configure') {
|
||||
// $scope.appBusy = true;
|
||||
// } else if (result.installationState === 'installed') {
|
||||
// $scope.appBusy = false;
|
||||
// }
|
||||
|
||||
// $scope.selectedAppInfo = result;
|
||||
// });
|
||||
// });
|
||||
|
||||
// terminal right click handling
|
||||
$scope.terminalClear = function () {
|
||||
if (!$scope.terminal) return;
|
||||
@@ -350,6 +346,10 @@ app.controller('TerminalController', ['$scope', '$timeout', '$location', 'Client
|
||||
return false;
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function (e) {
|
||||
if ($scope.terminal) $scope.terminal.fit();
|
||||
});
|
||||
|
||||
Client.getStatus(function (error, status) {
|
||||
if (error) return $scope.error(error);
|
||||
|
||||
@@ -360,39 +360,39 @@ app.controller('TerminalController', ['$scope', '$timeout', '$location', 'Client
|
||||
return;
|
||||
}
|
||||
|
||||
Client.refreshConfig(function (error) {
|
||||
// check version and force reload if needed
|
||||
if (!localStorage.version) {
|
||||
localStorage.version = status.version;
|
||||
} else if (localStorage.version !== status.version) {
|
||||
localStorage.version = status.version;
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
console.log('Running terminal version ', localStorage.version);
|
||||
|
||||
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
|
||||
Client.refreshUserInfo(function (error) {
|
||||
if (error) return $scope.error(error);
|
||||
|
||||
// check version and force reload if needed
|
||||
if (!localStorage.version) {
|
||||
localStorage.version = Client.getConfig().version;
|
||||
} else if (localStorage.version !== Client.getConfig().version) {
|
||||
localStorage.version = Client.getConfig().version;
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
Client.refreshInstalledApps(function (error) {
|
||||
Client.refreshConfig(function (error) {
|
||||
if (error) return $scope.error(error);
|
||||
|
||||
Client.getInstalledApps().forEach(function (app) {
|
||||
$scope.apps.push({
|
||||
refreshApp(search.id, function (error, app) {
|
||||
$scope.selected = {
|
||||
type: 'app',
|
||||
value: app.id,
|
||||
name: app.fqdn + ' (' + app.manifest.title + ')',
|
||||
addons: app.manifest.addons
|
||||
});
|
||||
addons: app.manifest.addons,
|
||||
manifest: app.manifest
|
||||
};
|
||||
|
||||
// now mark the Client to be ready
|
||||
Client.setReady();
|
||||
|
||||
$scope.initialized = true;
|
||||
|
||||
showTerminal();
|
||||
});
|
||||
|
||||
// activate pre-selected log from query otherwise choose the first one
|
||||
$scope.selected = $scope.apps.find(function (e) { return e.value === search.id; });
|
||||
if (!$scope.selected) $scope.selected = $scope.apps[0];
|
||||
|
||||
// now mark the Client to be ready
|
||||
Client.setReady();
|
||||
|
||||
$scope.initialized = true;
|
||||
|
||||
showTerminal();
|
||||
});
|
||||
});
|
||||
});
|
||||