Compare commits
845 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c00fb361a | |||
| 903e0bc568 | |||
| d12a23b73f | |||
| 6e34f84b14 | |||
| c74fa04b7f | |||
| 758b05393c | |||
| 219066d8d7 | |||
| 449dd4730f | |||
| 73ffe9ce41 | |||
| c21c24f088 | |||
| f35f548ecd | |||
| 69d5283caf | |||
| 43950fc398 | |||
| d2e3b80517 | |||
| 3728d8ecc1 | |||
| dcca524726 | |||
| 9ec5fc29aa | |||
| 1d0f3a08f4 | |||
| 3d8ffcd0f7 | |||
| 8c28871b76 | |||
| df53f827c5 | |||
| 83adcd73a9 | |||
| 8e6890b4d6 | |||
| bd107e849b | |||
| 5893f53b43 | |||
| 1894ed7721 | |||
| 96b715de8e | |||
| b26890f5b3 | |||
| 5ae29eabaa | |||
| d9e4aeb518 | |||
| 6b7edbd552 | |||
| 12f19299a8 | |||
| 0008e5a83b | |||
| 0bd1aac0ef | |||
| 5145344987 | |||
| cc980fbc0c | |||
| 878caff378 | |||
| 5ce82d6794 | |||
| d456f91921 | |||
| 3be77fc634 | |||
| a4e68733ed | |||
| eaae3f824b | |||
| 8d3b9685a1 | |||
| 3fa354a815 | |||
| 512722695e | |||
| 9ed424a5d9 | |||
| a36ef67305 | |||
| be340580d4 | |||
| fbe207dac3 | |||
| f59837f7c3 | |||
| d0d0913c70 | |||
| 701c25d07a | |||
| d38b4d7b74 | |||
| 8fd9324048 | |||
| 6004cd17bf | |||
| 746e694d7e | |||
| ead419003b | |||
| 6141db8f34 | |||
| 6993cbeb9f | |||
| 96f2c6e2aa | |||
| 65f507bc75 | |||
| 05d6484d27 | |||
| 41bc08a07e | |||
| 98058f600e | |||
| 41b302b0b9 | |||
| fbe334e7d7 | |||
| 9a155491cb | |||
| ab8ec07f2f | |||
| 3e1c886b17 | |||
| 21c3d16db5 | |||
| 0e181cdc82 | |||
| e168be6d97 | |||
| f65be99017 | |||
| e201d4c896 | |||
| a8035d01c6 | |||
| 054275f143 | |||
| e652456d54 | |||
| 1e6a7d72ab | |||
| 965054a707 | |||
| 9a26dc090e | |||
| 30b0d4cced | |||
| f973536f7f | |||
| 490840b71d | |||
| 2ad93c114e | |||
| cec2106cfe | |||
| 9200e6fc63 | |||
| 5907975c02 | |||
| fe68887cdd | |||
| 24df6edbf1 | |||
| 710bd270d7 | |||
| 147e014205 | |||
| 65a7f5f1c6 | |||
| cfc3a4217d | |||
| 35be854997 | |||
| 58af890abe | |||
| ada878c939 | |||
| 08435fbe26 | |||
| 00a643e70a | |||
| cc759a8427 | |||
| bb392207ea | |||
| a5b9ff0c3a | |||
| 146afce934 | |||
| de0909248d | |||
| d5b3a56129 | |||
| fbed850acc | |||
| 25fb467c02 | |||
| 8493022f75 | |||
| 621c1ed95a | |||
| 4992e284fb | |||
| e4fb040ddf | |||
| 2bfa49cc2e | |||
| 3b9d617e37 | |||
| fdf8025a02 | |||
| 423dfb6ace | |||
| 0a4aede3a8 | |||
| 872705d58d | |||
| ca5776e6f3 | |||
| d4998b5d55 | |||
| e93f5e3e87 | |||
| d29bb90c5a | |||
| 1230e5c9e7 | |||
| dc3d23c27b | |||
| 6623061c2c | |||
| 1ecb853309 | |||
| 2a6c52800b | |||
| 320ddfda2e | |||
| 40febc8ef2 | |||
| 56f6519b3e | |||
| f219abf082 | |||
| 742a04d149 | |||
| 26caacc12e | |||
| 1497518867 | |||
| 1a4a69f365 | |||
| 78520e09c3 | |||
| f0207ff161 | |||
| dd45f1c032 | |||
| ddf1c8e385 | |||
| 948efbaa76 | |||
| ccd1a4319d | |||
| 22be1f1b72 | |||
| 7095862601 | |||
| fa98e0570f | |||
| 4316d3eade | |||
| f8cd0b5f52 | |||
| a8b3f69acc | |||
| 78cb36ea0e | |||
| b4d58f0609 | |||
| 18abc214a6 | |||
| 5e3857fd3d | |||
| e35b36643c | |||
| 16fa339025 | |||
| 051b0e0fd3 | |||
| 62d3212f88 | |||
| fd96665e97 | |||
| 8f6637773b | |||
| d7f829b3e1 | |||
| 3fdb43762b | |||
| 7ae02a62fe | |||
| 11cb33fe25 | |||
| a09202d1fa | |||
| fcccccaaae | |||
| 9f80578bab | |||
| 32e3665b7a | |||
| e9c10b306c | |||
| dabadcc00e | |||
| 9cc594d633 | |||
| 8350eeb751 | |||
| 7b61bafab7 | |||
| 6407d795ed | |||
| 9cf235af39 | |||
| 18e5365104 | |||
| c03eff8da2 | |||
| 28f79cd6c9 | |||
| fc2786b07f | |||
| 620ad13427 | |||
| 0776442a5f | |||
| 4a207395ca | |||
| 2df983a1cf | |||
| 03e17aea22 | |||
| aefa481c43 | |||
| 553c256d31 | |||
| b6023afb29 | |||
| 0df1e3a47f | |||
| 78a08c5a0b | |||
| 55a880c9ac | |||
| 61341b8380 | |||
| a32b567eb1 | |||
| 25462d3290 | |||
| a9207b392b | |||
| c0f3c3bd2b | |||
| 8621fbda79 | |||
| 84de986efd | |||
| 0f3ab11532 | |||
| 6b4a81e471 | |||
| 14a18a42b7 | |||
| 2c28eddc2b | |||
| 1b22ea661c | |||
| efc3c7532e | |||
| a3a807f22c | |||
| fac5d3c07b | |||
| df5ba25010 | |||
| d66db8ca40 | |||
| 0722d7ceb9 | |||
| 06a23951c9 | |||
| 727d4876f5 | |||
| f5a43786c2 | |||
| 30967af8ec | |||
| ccd892708b | |||
| 8cf3e38b27 | |||
| 4685f42045 | |||
| e6232189e7 | |||
| 6e12d06343 | |||
| d02b6d90cc | |||
| d10e9d7098 | |||
| 57b0cca6ab | |||
| fc565fd818 | |||
| 4e0c439c6f | |||
| 39220ba408 | |||
| 7fbb9f9df3 | |||
| 6c3ca9c364 | |||
| 7b648cddfd | |||
| a9e1d7641d | |||
| 02823c4158 | |||
| d58789cc25 | |||
| 434a0cba9f | |||
| ca8695a1d3 | |||
| 7f141605fa | |||
| 23f9b5f2fc | |||
| 1abbe43785 | |||
| 6361737cf4 | |||
| a884f968e1 | |||
| ce611c4773 | |||
| ba75c7ddaa | |||
| ff5dccc2b4 | |||
| 9b8994fe43 | |||
| 34969d9980 | |||
| da11e90333 | |||
| 282d06404e | |||
| 64e60c106b | |||
| 1b3fd20755 | |||
| ce5a2b1f0a | |||
| d68d5d5c51 | |||
| 5a3460efb7 | |||
| edf5ddf027 | |||
| 982714fa4c | |||
| 90ee525be7 | |||
| 600323e027 | |||
| 46a8b59196 | |||
| f96ae1a1de | |||
| 8894ec3019 | |||
| 6f914a8d6b | |||
| 9f06b91399 | |||
| 9d7f12952d | |||
| bc4e6ab1de | |||
| 2300e1baee | |||
| 1b00e0f254 | |||
| 6534e99103 | |||
| ac98895e15 | |||
| 4e0961ae5a | |||
| 7669b77069 | |||
| 529d5b0b7b | |||
| 6edc482aad | |||
| 8fce81a264 | |||
| ea2479beda | |||
| 40e7ee91d7 | |||
| 813942edbd | |||
| b70747de6f | |||
| 1c58f9aa5a | |||
| 93aa2a4e6e | |||
| 0504e0423a | |||
| c1c16ab54e | |||
| 76dc856dbf | |||
| 227fdf10dd | |||
| 19c744b17d | |||
| 3ce74d04d0 | |||
| 87b8fc6a1b | |||
| 9012badfb8 | |||
| 3b6e5d8ed1 | |||
| 1148724613 | |||
| f526695aae | |||
| e8850eeac2 | |||
| 777834d790 | |||
| dca9246450 | |||
| 767f7ab40e | |||
| 1b810ec74f | |||
| f59b9e1b5f | |||
| 398dbe802e | |||
| 8b5fa0fe76 | |||
| 99042a47f3 | |||
| 46e600abe9 | |||
| 051dd8b58f | |||
| 067b02dba1 | |||
| 22a0874188 | |||
| 0e25809158 | |||
| 305d877896 | |||
| a932a5251a | |||
| 7b58fccb9f | |||
| 859fef62d4 | |||
| 0647a3a233 | |||
| aedf55dba0 | |||
| e9a422b657 | |||
| 23df6bdfbf | |||
| 1b5fee233e | |||
| 63457d2de4 | |||
| 732c944e98 | |||
| 86c4db8f22 | |||
| 8c0c9981de | |||
| e5dcf78ceb | |||
| 92bce26e22 | |||
| a72c038435 | |||
| 6742cdf373 | |||
| ea72cef7f9 | |||
| 565ad83399 | |||
| 43f795c9e4 | |||
| 1589cfb639 | |||
| a9b9931aa8 | |||
| 1cd577cc65 | |||
| 13d8db3daa | |||
| 40c4a01bc0 | |||
| 4301c70ba7 | |||
| d5e9e556ab | |||
| bdf9e04963 | |||
| b95285365d | |||
| abf445e969 | |||
| e988e3a303 | |||
| dca548b8a0 | |||
| 56ecfdb4eb | |||
| 7640851aa9 | |||
| d9301160e1 | |||
| 3656d7f631 | |||
| 9f89b07777 | |||
| 199dbff7b1 | |||
| 88b8cb48fc | |||
| e8b3232966 | |||
| 5de7537c71 | |||
| 4706313239 | |||
| d32819da4e | |||
| b6becae396 | |||
| d310c5746e | |||
| e2f4e9f30a | |||
| 44011afd14 | |||
| cebaa71ce1 | |||
| 0ed9105a05 | |||
| 69ecbe5ad7 | |||
| a218761e99 | |||
| 71d167d5fb | |||
| aabdea8627 | |||
| f220a1384c | |||
| e438ade08e | |||
| ed1d537f60 | |||
| d59bc05f12 | |||
| 4608301f1c | |||
| a865320e3a | |||
| bc8c01900b | |||
| 9704eefc21 | |||
| 52cd52d83c | |||
| 4a29371907 | |||
| 1e5e4e3189 | |||
| 041f7da59b | |||
| 4dae3447d6 | |||
| 7391af6f08 | |||
| 8a640c8219 | |||
| 2857582f46 | |||
| 1d80f03c38 | |||
| d7c20048fe | |||
| cbbdb77a6e | |||
| 2ff995aa95 | |||
| 21705a0e96 | |||
| c03da3be54 | |||
| 69f48ed11a | |||
| caa0c342a4 | |||
| 01b4388b3c | |||
| b870f98ec2 | |||
| a5249102f2 | |||
| 5aa0c57a74 | |||
| 053b076af0 | |||
| 247309e11b | |||
| c9fe08e7b7 | |||
| 468d4dd9b0 | |||
| 6056ba6475 | |||
| 4f03a6fb58 | |||
| d8aa4bc5e4 | |||
| 06e46e0f1e | |||
| 731295f708 | |||
| 9399040cd3 | |||
| 9f9fde5811 | |||
| cbc46a8229 | |||
| fb11997430 | |||
| b6fbc46b58 | |||
| 21de2513e7 | |||
| 51bb2d2bc2 | |||
| 8d9043e590 | |||
| 59c3e8817c | |||
| 3132b3035a | |||
| 7ebf5ca16a | |||
| d96f132dc0 | |||
| b26ff08a3c | |||
| 44678cf5f1 | |||
| 5084ee761e | |||
| 91f50ae949 | |||
| 15f04edcf1 | |||
| 01945675ed | |||
| 185c16c3e2 | |||
| d25814b84b | |||
| a09a3fd012 | |||
| 871fd83148 | |||
| dd8bc493e7 | |||
| 44d3baf51a | |||
| c85c0558b9 | |||
| 7f11699fac | |||
| 525e48ae59 | |||
| a6369a7dde | |||
| d5ea99603f | |||
| 083432cbfe | |||
| dbbce4160d | |||
| 885aac69c5 | |||
| b3c301fc2a | |||
| 01deb4d285 | |||
| aeddaa4566 | |||
| eb314ef507 | |||
| 620c49cf76 | |||
| 6d73dfdb40 | |||
| 232cdb8cb1 | |||
| fd53174099 | |||
| 9bf240d83b | |||
| 421567ff14 | |||
| ce05008fce | |||
| a250cb9fe2 | |||
| 012f8bc14e | |||
| 11dce549bd | |||
| 5b567ac941 | |||
| 5b103c78e5 | |||
| bc96f9c5e5 | |||
| d97d82b225 | |||
| e9b6002f63 | |||
| 704999a05f | |||
| ba99e3b9b7 | |||
| 9adeaed1b9 | |||
| 10bd2e930f | |||
| 07396c9824 | |||
| bf34b13b7f | |||
| 0bab0ed748 | |||
| 8754a208b1 | |||
| 19100c7999 | |||
| d98ec77abf | |||
| 34c2decd91 | |||
| 09fb4ea89f | |||
| d6bb32aead | |||
| 3a21191fba | |||
| ad4e0ba9aa | |||
| baf598099f | |||
| 7d017d83d6 | |||
| 7911780a16 | |||
| 1dc6b40a68 | |||
| dd9e6e63ad | |||
| 30633e7820 | |||
| acfc67ed0a | |||
| a99a8ef382 | |||
| 7aec713e6c | |||
| 60c4dd3875 | |||
| 7d8ba8d42c | |||
| 7ff7842441 | |||
| bcf497b460 | |||
| bf51a60986 | |||
| 41809d1ca8 | |||
| acb1445270 | |||
| 86530df37e | |||
| b64b513b14 | |||
| 285feb4f8b | |||
| c6f4395578 | |||
| 4981854c7f | |||
| 65f6ff35e0 | |||
| d892cc5763 | |||
| d122ece8e9 | |||
| a363e508b6 | |||
| e481606d0e | |||
| a1e2c9fd08 | |||
| f5931abdeb | |||
| 4c9e05b08f | |||
| 9c34727e88 | |||
| 939cd94ebb | |||
| 4a33415b06 | |||
| 082e659c7b | |||
| a8059c49e9 | |||
| f7b14b2ee8 | |||
| 581a294af1 | |||
| 40e8ba38f0 | |||
| 65f4ec0f43 | |||
| 8748ba1226 | |||
| 2ad8ee18a0 | |||
| 8b9dc5a6bf | |||
| a1a6570ee3 | |||
| 6c68f7da2e | |||
| ccd5f6c2e5 | |||
| 73b20ae809 | |||
| a4dd6cc928 | |||
| 6f37bde55d | |||
| 8f1f3cea18 | |||
| f715e21306 | |||
| d9b478cf1f | |||
| 36a768eb60 | |||
| 0ebe6e545d | |||
| 5fb295e044 | |||
| be7e11a4f6 | |||
| d13bf9ac74 | |||
| e909b6e643 | |||
| 9555a93ddc | |||
| 1d9ad35019 | |||
| 78aee78d9c | |||
| 4b96d5879c | |||
| 20396a8c7d | |||
| 8510b12841 | |||
| 345f9541fe | |||
| c1c864ced7 | |||
| 7a440a32d1 | |||
| ef1431f89b | |||
| 57cf0ec074 | |||
| d795507ddd | |||
| c3aafb2979 | |||
| 93d4472932 | |||
| 69934be88c | |||
| 8638bfb30b | |||
| 5b3d6a3957 | |||
| 94dd0644d0 | |||
| f089329e12 | |||
| 8554d374c9 | |||
| 424ec1c90d | |||
| ce2f1b4170 | |||
| ce1146a9ef | |||
| f065821587 | |||
| 18c518f385 | |||
| 6ba1953acb | |||
| 324ee4641f | |||
| 9ee4490498 | |||
| 0fa32c9572 | |||
| b3e5563e15 | |||
| 55038dee51 | |||
| b54eaf2964 | |||
| 98e97a0f9b | |||
| f15b4a4f4b | |||
| bd7641f502 | |||
| 2d04ec2308 | |||
| ba0ab68f50 | |||
| 825fe21bd9 | |||
| 072ca73259 | |||
| b333a136e8 | |||
| e34cf7fd77 | |||
| 1c7099b3f0 | |||
| d74ee441ac | |||
| 7bad90009e | |||
| 424bc588f6 | |||
| 852e1e1687 | |||
| 649c06b641 | |||
| 6b4df0bd65 | |||
| e67324b05c | |||
| d688f5e080 | |||
| c3f9d688f1 | |||
| 7affc6e987 | |||
| 9f26608681 | |||
| d34b102e52 | |||
| 077f95049e | |||
| b570f2f77d | |||
| b4e7e394c3 | |||
| e1f87161a8 | |||
| 57bf3709f3 | |||
| 9d258d33cf | |||
| 62e322c451 | |||
| 9a04ee2d1f | |||
| 5852fac71a | |||
| f315a378dc | |||
| dcee792aaa | |||
| d0df897f93 | |||
| 915e3ecc94 | |||
| 76dadd1f8b | |||
| 73fdcae916 | |||
| 941162a05f | |||
| 22b8ec6144 | |||
| a0c7f3f896 | |||
| 692be297b3 | |||
| db3eabcd2f | |||
| fee78bb488 | |||
| dda6f43b8a | |||
| b5fad74ea0 | |||
| ef42106a16 | |||
| bba1922120 | |||
| f386c326e2 | |||
| 3b26f6f5ea | |||
| 52701e1173 | |||
| 3c7d24916c | |||
| 4fac0cb535 | |||
| 00f6ef7603 | |||
| 556b9fe20c | |||
| 3dcd0975f7 | |||
| db7e88e302 | |||
| 9c5fb2823c | |||
| d12b2ae2db | |||
| 449d68122b | |||
| 0227ae1d96 | |||
| 265e58e5cb | |||
| 9054f30aef | |||
| 36887abf88 | |||
| 4ca5fcf472 | |||
| c4b01dea22 | |||
| 6d4cc4a6b8 | |||
| 4229e9921c | |||
| 92b6a7e335 | |||
| cb8731b915 | |||
| 0c80b7af1d | |||
| 4ce9c46215 | |||
| bbf402368f | |||
| 37d1dc7c6d | |||
| a677dc3981 | |||
| 77163cc1b2 | |||
| 5d41a84fec | |||
| 890de53b0a | |||
| a1f2b5b696 | |||
| 6eda037544 | |||
| eb5b8b42dc | |||
| 4a5022d14d | |||
| 54c6f9c4f8 | |||
| dee60e9958 | |||
| bbefa38355 | |||
| 6681f2e5c8 | |||
| 1728756dc4 | |||
| 1f0860e45d | |||
| 9eb91a3ae9 | |||
| ad50ea5aee | |||
| 73045fd7fc | |||
| 11aeccc822 | |||
| 310a8c1c63 | |||
| 23153e5b86 | |||
| 130d8a1ba0 | |||
| 8d9ecf3352 | |||
| 6080cfa351 | |||
| 4e04b2075f | |||
| 9f415826fd | |||
| 54d92b8bf7 | |||
| f1e8b91f61 | |||
| a1bd1a0fa1 | |||
| b142cd5039 | |||
| b548856c29 | |||
| a0df52000a | |||
| e98a1a9767 | |||
| ad2eaff60e | |||
| 3df7b74f65 | |||
| 67c1b2cb71 | |||
| 6c0e84a31d | |||
| c49a440211 | |||
| caedf6a8e7 | |||
| 203330d1b8 | |||
| c8d66384c7 | |||
| 74447d2690 | |||
| b66ddedc86 | |||
| 8df97de8c6 | |||
| cd5cae33ce | |||
| 608ce53e7d | |||
| d2ae6c2353 | |||
| 7eda1136ea | |||
| a756fa9e9b | |||
| afb5e5ac5d | |||
| efa1acddd4 | |||
| e00db115ad | |||
| 366f247910 | |||
| 2a6368af60 | |||
| 5420630453 | |||
| 4e39eb89fd | |||
| a783944700 | |||
| 8a987db177 | |||
| 834a7d0f55 | |||
| 051bcb7819 | |||
| 126587ba82 | |||
| 860ebcbe6a | |||
| 25f395ed63 | |||
| 2da361a1f2 | |||
| 4e363dc77a | |||
| 23e20b9b83 | |||
| e70a6ffbb9 | |||
| cab236123f | |||
| cab7e0d8a3 | |||
| 2f425f8119 | |||
| 017e46fa0f | |||
| 9efcd9060e | |||
| abdd5d3e0e | |||
| cf40346e1a | |||
| b6d80fb443 | |||
| f6e4f1aefc | |||
| dbf66b8e89 | |||
| 53ad3902ac | |||
| cae2bfbdc2 | |||
| 58d6142460 | |||
| 2ca4838ac7 | |||
| 3787f90283 | |||
| 9064375e25 | |||
| 033036bd1a | |||
| 5d74d80829 | |||
| 88231e3d35 | |||
| 1aa683aeab | |||
| c2326bc5cc | |||
| 55db3ae517 | |||
| 4b0dbf0183 | |||
| 2725e001a5 | |||
| 02a0f65e4b | |||
| 9fd964022e | |||
| ec7dabc1c7 | |||
| 95eeb9ce93 | |||
| d137cdf881 | |||
| a926a3e8a8 | |||
| e8b3516d34 | |||
| 54e5e0cb7e | |||
| baa4620523 | |||
| fcd1532a4d | |||
| 66b768b176 | |||
| eeae8c92d0 | |||
| d35bfbb0fd | |||
| 4516b0c57c | |||
| 49243822af | |||
| 16521d5434 | |||
| 1afa2e87ec | |||
| 18ec929501 | |||
| 7d6636bb54 | |||
| 3c7e6b59f0 | |||
| daa8a60da2 | |||
| f231d51d0b | |||
| 308f315ed5 | |||
| a572374ad7 | |||
| 1cf315634c | |||
| b0d2bdbad9 | |||
| 255fb0cac0 | |||
| c3be0018fe | |||
| 37e2269387 | |||
| 5dbe2ce2e4 | |||
| 1008ec4fa1 | |||
| d36d1cf1da | |||
| 21d7438bbe | |||
| caf1c37171 | |||
| 0a748ac78a | |||
| 76c4002a04 | |||
| 201a07f717 | |||
| 5b2eb51511 | |||
| 36ab5800a3 | |||
| a79486275e | |||
| 6dc70a8f3b | |||
| 8e990e4e0a | |||
| f11becfcc8 | |||
| 8d04374764 | |||
| 87ae95aa4f | |||
| 0fa1ec44b1 | |||
| b4e4f26361 | |||
| 2afaf1f36d | |||
| f236213356 | |||
| efd0be5e2c | |||
| 6612f48d0a | |||
| f1679f1614 | |||
| 8b7dca00af | |||
| 59fa26b0fb | |||
| 7a92222050 | |||
| be2775e12e | |||
| 6c3f8b9b84 | |||
| f02157857c | |||
| 470b0d6be7 | |||
| 2b1b304c6e | |||
| 5460a64951 | |||
| 62faf616c5 | |||
| 3f2f4c7c6b | |||
| 5e49a33e8f | |||
| 5fb7d53018 | |||
| 424a3c2b53 | |||
| 6e629b984b | |||
| c73609211a | |||
| e5477351f8 | |||
| d89f8d99a3 | |||
| da472dff19 | |||
| 2dc501dcbd | |||
| 052b705c3c | |||
| 24c8fca971 | |||
| 86edabee4d | |||
| d6f162a8ca | |||
| 9e05a4eab7 | |||
| 32d9490856 | |||
| 91d9f66eb8 | |||
| 86986d8f34 | |||
| 03ef9f109f | |||
| 67a8228886 | |||
| 8db6da2de9 | |||
| 544b8180b2 | |||
| 2515b032d0 | |||
| 6086b0e797 | |||
| 2760e25c0f | |||
| 76aa0b4a70 | |||
| 0e23687c7f | |||
| 028b820d48 | |||
| 2c81458954 | |||
| ebe1883f8e | |||
| 030e468829 | |||
| 68724bcb4f | |||
| 6186bb54e4 | |||
| a4e822dec2 | |||
| 5744cb7318 | |||
| 2f6a66dbd7 | |||
| 91d3980e3b | |||
| 3ddf72a24d | |||
| a6f4b2896a | |||
| c79ddbf948 | |||
| de99b8ecce | |||
| 8b0bcde7ec | |||
| d862f1f5b4 | |||
| 1c4f6315a6 | |||
| 44eaac6685 | |||
| a89576965d | |||
| 774f14327c | |||
| 6bd9391160 | |||
| a82fb0c2cb | |||
| 110e683318 | |||
| 781ee77280 | |||
| adc9894fde | |||
| c7bf5f2abc | |||
| 601e868afc | |||
| 25b1259c4c | |||
| 1a8a111c79 | |||
| 497b3016c0 | |||
| fe9bd52b04 | |||
| 0705c77333 | |||
| b66e77a2d8 | |||
| 4b4c8d8052 | |||
| 4ee56782ba | |||
| 104997d77c | |||
| 8e07b3c96d | |||
| 4e618540f8 | |||
| 49941a34b9 | |||
| 771b797a23 | |||
| d09915bf6e | |||
| 264c94ff34 | |||
| a90df99331 | |||
| 78f0d61627 | |||
| 8c106b3435 | |||
| 42555c7231 | |||
| ab035a2afe | |||
| 3a30eed3cd | |||
| 4cb390374b | |||
| 50179dd7eb | |||
| 2956c3360c | |||
| c634bdbd34 | |||
| 1892c0cd80 | |||
| 63b395982c | |||
| d50c8539b2 |
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 13
|
||||
},
|
||||
"rules": {
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
node_modules/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
webadmin/dist/
|
||||
installer/src/certs/server.key
|
||||
|
||||
# vim swap files
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
run_tests:
|
||||
stage: test
|
||||
image: cloudron/base:4.2.0@sha256:46da2fffb36353ef714f97ae8e962bd2c212ca091108d768ba473078319a47f4
|
||||
services:
|
||||
- name: mysql:8.0
|
||||
alias: mysql
|
||||
variables:
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
MYSQL_DATABASE: box
|
||||
BOX_ENV: ci
|
||||
DATABASE_URL: mysql://root:password@mysql/box
|
||||
script:
|
||||
- echo "Running tests..."
|
||||
- mysql -hmysql -uroot -ppassword -e "ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'password';"
|
||||
- mysql -hmysql -uroot -ppassword -e "CREATE DATABASE IF NOT EXISTS box"
|
||||
- npm install
|
||||
- node_modules/.bin/db-migrate up
|
||||
- ln -s /usr/local/node-18.18.0/bin/node /usr/bin/node
|
||||
- node_modules/.bin/mocha --no-timeouts --bail src/test/tokens-test.js
|
||||
- echo "Done!"
|
||||
|
||||
stages:
|
||||
- test
|
||||
|
||||
@@ -2755,4 +2755,130 @@
|
||||
|
||||
[7.7.1]
|
||||
* postgresql: fix bug in loading of contrib extensions
|
||||
* dashboard: use native slider element for app memory and cpu
|
||||
|
||||
[7.7.2]
|
||||
* docker: use unix domain socket based logging instead of udp
|
||||
* dashboard: use native slider element for app memory and cpu
|
||||
* filemanager: fix empty folder content layout
|
||||
* dashboard: preserve app link paths
|
||||
* backups: deleted apps must also be displayed in contents
|
||||
* filemanager: make uploads cancellable
|
||||
* Fix crash on systemds with no swap
|
||||
|
||||
[8.0.0]
|
||||
* mongodb: optionally start mongodb based on AVX support
|
||||
* dashboard: font and color improvements
|
||||
* docker: prune volumes on infra change
|
||||
* oidc: initial login of admin and normal user now gets an OIDC session
|
||||
* branding: default background image for the dashboard
|
||||
* dashboard: list view for apps
|
||||
* import: fix crash when using mountpoint provider
|
||||
* dashboard: set '/' as keyboard shortcut
|
||||
* app: memory limit is redefined to be just RAM and unlimited swap
|
||||
* dashboard: rework filter UI
|
||||
* cpu: rework cpu shares into cpu quota
|
||||
* cifs: enable seal encryption by default
|
||||
* updatechecker: fix bug where release info was not refreshed
|
||||
* ovh: storage location domain has changed. add rbx region
|
||||
* domains: add deSEC integration
|
||||
* notfound: better message when navigating by IP address
|
||||
* IPv6 only server installation
|
||||
* Initial Ubuntu 24.04 (Noble Numbat) support
|
||||
* syslog: handle potential multiline syslog input
|
||||
* user directory: fixes to mandatory 2fa setting when cloudron connector is used
|
||||
* notification: do not send login notification for external users
|
||||
* dashboard: pending checklist indicator
|
||||
* cloudron-support: add --recreate-docker and --recreate-container
|
||||
* filemanager: add dark mode
|
||||
* proxyauth: now uses oidc instead of ldap auth
|
||||
* dashboard: add admin notes
|
||||
* Use systemd-resolved as the system resolver. unbound is now only for mail server and recursive DNS lookups
|
||||
|
||||
[8.0.1]
|
||||
* nfs: disable rpcbind service. we only support nfsv4 mounting
|
||||
* dashboard: only show postinstall if notes are not just empty
|
||||
* ami: disable route53
|
||||
* mailer: add html version of test mail
|
||||
* sshfs: server side copying
|
||||
* backups: rewrite tgz backups using tar-stream
|
||||
* backups: fix issue with s3 backend where files missing in remote was not detected correctly
|
||||
* provision: redirect to correct task (setup/restore/activation)
|
||||
|
||||
[8.0.2]
|
||||
* tgz: fix unhandled promise error handler
|
||||
* tgz: add underflow/overflow proxy stream to ensure size of a changing file
|
||||
* backups: give task a low oomScoreAdjust to not get killed
|
||||
* Fix issue with uploads via File Manager where temp files were not cleaned up
|
||||
* addons: fix crash when importing database of an app with no addons
|
||||
* sshfs: if remote copy fails, fallback to sshfs based copy
|
||||
* frontend: reduce DOM node creation on very fast logstreams and cap to 1k loglines
|
||||
|
||||
[8.0.3]
|
||||
* logs: fix recursion when displaying box logs
|
||||
* frontend: fix clear view in logs viewer
|
||||
* dashboard: support links/markdown in checklist items
|
||||
|
||||
[8.0.4]
|
||||
* ami: IMDv2 support
|
||||
* ionos: add contract-owned eu-central-3
|
||||
* dashboard: remove mailbox import/export feature
|
||||
* backupcleaner: do not remove the backup in progress
|
||||
* backups: make noop upload work again
|
||||
* volumes: `/mnt/volumes` is reserved
|
||||
* apps: do not log app logs to output
|
||||
* sftp: restore mode and owner
|
||||
* dashboard: also render checklist items in apps.html
|
||||
|
||||
[8.0.5]
|
||||
* cpu quota: fix rounding error
|
||||
* frontend: fix translation resolver to actually fallback to english
|
||||
* i18n: fix crash if language file is missing
|
||||
* memory: fix slider UI where max was incorrectly set
|
||||
* digitalocean: add LON1 Spaces region
|
||||
* exoscale: add sos AT-VIE-2 region
|
||||
* i18n: remove use of "Cloudron"
|
||||
* tz: add note in backup and update UI
|
||||
* backups: do not overflow the schedule timings
|
||||
* checklist: new checklist items on update are acknowledged
|
||||
* backups: automatically trigger a remount if mount is not active
|
||||
* logs: rework the syslog parser
|
||||
* docker: use system dns for app containers
|
||||
* logs: show error message in UI when log rotated
|
||||
* unbound: prefer ip4 for dns queries (only on ubuntu 24 and above)
|
||||
* apps: allow operators to update apps
|
||||
|
||||
[8.0.6]
|
||||
* Fix AdGuard resolving dashboard to docker bridge IP
|
||||
|
||||
[8.1.0]
|
||||
* backups: add hetzner object storage
|
||||
* registry: cloudron container registry
|
||||
* gandi: add PAT token support
|
||||
* OpenID: add groups claim support
|
||||
* OpenID: enable refresh token support (dokuwiki)
|
||||
* filemanger: fix various regressions
|
||||
* dashboard: mobile and dark-mode fixes
|
||||
* syslog: fix multiline timestamps
|
||||
* porkbun: use new API endpoint
|
||||
* fix "happy eyeballs" quirk in nodejs
|
||||
* Update nodejs to 20.18.0
|
||||
|
||||
[8.2.0]
|
||||
* rsync: show better error message with too many empty dirs, symlinks or executables
|
||||
* mail: update Solr to 8.11.4
|
||||
* mail: update Haraka to 3.0.5
|
||||
* Add sqlite3 addon
|
||||
* docker: update docker to 27.3.1
|
||||
* du: add exclude file to skip filesystem usage checks
|
||||
* mail: attachment search
|
||||
* oidc: use cloudron name as provider name
|
||||
* groups: add eventlog
|
||||
* resources: allow mounting devices into apps
|
||||
* remove global lock
|
||||
* hetzner: add helsinki object storage location
|
||||
* backups: implement app archive
|
||||
* notifications: per user email notification config
|
||||
* postgres: enable vector extension
|
||||
* docker: fallback to downloading images from quay if dockerhub does not work
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
const constants = require('./src/constants.js'),
|
||||
fs = require('fs'),
|
||||
ldapServer = require('./src/ldapserver.js'),
|
||||
net = require('net'),
|
||||
oidc = require('./src/oidc.js'),
|
||||
paths = require('./src/paths.js'),
|
||||
proxyAuth = require('./src/proxyauth.js'),
|
||||
@@ -25,9 +26,17 @@ async function setupLogging() {
|
||||
};
|
||||
}
|
||||
|
||||
// happy eyeballs workaround. when there is no ipv6, nodejs timesout prematurely since the default for ipv4 is just 250ms
|
||||
// https://github.com/nodejs/node/issues/54359
|
||||
async function setupNetworking() {
|
||||
net.setDefaultAutoSelectFamilyAttemptTimeout(2500);
|
||||
}
|
||||
|
||||
// this is also used as the 'uncaughtException' handler which can only have synchronous functions
|
||||
function exitSync(status) {
|
||||
if (status.error) fs.write(logFd, status.error.stack + '\n', function () {});
|
||||
const ts = new Date().toISOString();
|
||||
const msg = status.error.stack.replace(/\n/g, `\n${ts} `); // prefix each line with ts
|
||||
if (status.error) fs.write(logFd, `${ts} ${msg}\n`, function () {});
|
||||
fs.fsyncSync(logFd);
|
||||
fs.closeSync(logFd);
|
||||
process.exit(status.code);
|
||||
@@ -35,6 +44,7 @@ function exitSync(status) {
|
||||
|
||||
async function startServers() {
|
||||
await setupLogging();
|
||||
await setupNetworking();
|
||||
await server.start(); // do this first since it also inits the database
|
||||
await proxyAuth.start();
|
||||
await ldapServer.start();
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# following files are skipped when exporting using git archive
|
||||
test export-ignore
|
||||
docs export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
dist/
|
||||
node_modules/
|
||||
|
||||
# will get generated on build
|
||||
public/theme.css
|
||||
public/theme.css.map
|
||||
|
||||
# vim swap files
|
||||
*.swp
|
||||
|
||||
# these are not done yet
|
||||
src/translation/ja.json
|
||||
src/translation/pl.json
|
||||
src/translation/si.json
|
||||
src/translation/gl.json
|
||||
public/translation/ja.json
|
||||
public/translation/pl.json
|
||||
public/translation/si.json
|
||||
public/translation/gl.json
|
||||
public/translation/hr.json
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"unused": true,
|
||||
"esversion": 6,
|
||||
"globalstrict": false,
|
||||
"predef": [
|
||||
"$",
|
||||
"angular",
|
||||
"async",
|
||||
"describe",
|
||||
"it",
|
||||
"before",
|
||||
"after",
|
||||
"require",
|
||||
"monaco",
|
||||
"Mimer",
|
||||
"ISTATES"
|
||||
]
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
The Cloudron Subscription license
|
||||
Copyright (c) 2022 Cloudron UG
|
||||
|
||||
With regard to the Cloudron Software:
|
||||
|
||||
This software and associated documentation files (the "Software") may only be
|
||||
used in production, if you (and any entity that you represent) have agreed to,
|
||||
and are in compliance with, the Cloudron Subscription Terms of Service, available
|
||||
at https://cloudron.io/legal/terms.html (the “Subscription Terms”), or other
|
||||
agreement governing the use of the Software, as agreed by you and Cloudron,
|
||||
and otherwise have a valid Cloudron Subscription. Subject to the foregoing sentence,
|
||||
you are free to modify this Software and publish patches to the Software. You agree
|
||||
that Subscription and/or its licensors (as applicable) retain all right, title and
|
||||
interest in and to all such modifications and/or patches, and all such modifications
|
||||
and/or patches may only be used, copied, modified, displayed, distributed, or otherwise
|
||||
exploited with a valid Cloudron subscription. Notwithstanding the foregoing, you may copy
|
||||
and modify the Software for development and testing purposes, without requiring a
|
||||
subscription. You agree that Cloudron and/or its licensors (as applicable) retain
|
||||
all right, title and interest in and to all such modifications. You are not
|
||||
granted any other rights beyond what is expressly stated herein. Subject to the
|
||||
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
|
||||
and/or sell the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
For all third party components incorporated into the Cloudron Software, those
|
||||
components are licensed under the original license provided by the owner of the
|
||||
applicable component.
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Cloudron Dashboard
|
||||
|
||||
This is the front end code of Cloudron. The backend code is [here](https://git.cloudron.io/cloudron/box).
|
||||
|
||||
## Developing
|
||||
|
||||
* `npm install`
|
||||
* `gulp develop --api-origin=https://my.example.com`
|
||||
|
||||
## License
|
||||
|
||||
Please note that the Cloudron code is under a source-available license. This is not the same as an
|
||||
open source license but ensures the code is available for inspection (and hacking!).
|
||||
|
||||
## Contributions
|
||||
|
||||
Just to give a heads-up, we are a bit restrictive in merging changes. We are a small team and
|
||||
would like to keep our maintenance burden low. It might be best to first discuss features in the [forum](https://forum.cloudron.io),
|
||||
which also helps to determine how many other people will use it to justify maintenance for a feature.
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
<title>Cloudron Setup</title>
|
||||
<meta name="description" content="Cloudron Setup">
|
||||
<title>Cloudron Setup</title>
|
||||
<meta name="description" content="Cloudron Setup">
|
||||
|
||||
<link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
<link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link type="text/css" rel="stylesheet" href="/theme.css">
|
||||
<!-- contains all thing already using import statement -->
|
||||
<script type="module" src="./src/modules.js"></script>
|
||||
|
||||
<!-- Fontawesome -->
|
||||
<link type="text/css" rel="stylesheet" href="/3rdparty/fontawesome/css/all.css?<%= revision %>"/>
|
||||
<!-- Theme CSS -->
|
||||
<link type="text/css" rel="stylesheet" href="./src/theme.scss">
|
||||
|
||||
<!-- jQuery-->
|
||||
<script type="text/javascript" src="/3rdparty/js/jquery.min.js"></script>
|
||||
<!-- jQuery-->
|
||||
<script type="text/javascript" src="/js/jquery.min.js"></script>
|
||||
|
||||
<!-- async -->
|
||||
<script type="text/javascript" src="/3rdparty/js/async-3.2.0.min.js"></script>
|
||||
<!-- async -->
|
||||
<script type="text/javascript" src="/js/async-3.2.0.min.js"></script>
|
||||
|
||||
<!-- Bootstrap Core JavaScript -->
|
||||
<script type="text/javascript" src="/3rdparty/js/bootstrap.min.js"></script>
|
||||
<!-- Angularjs scripts -->
|
||||
<script type="text/javascript" src="/js/angular.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-loader.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-cookies.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-md5.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-ui-notification.js"></script>
|
||||
|
||||
<!-- Angularjs scripts -->
|
||||
<script type="text/javascript" src="/3rdparty/js/angular.min.js"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-loader.min.js"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-cookies.min.js"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-md5.min.js"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-ui-notification.js"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/autofill-event.js"></script>
|
||||
<!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ -->
|
||||
<script type="text/javascript" src="/js/ui-bootstrap-tpls-1.3.3.min.js"></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"></script>
|
||||
<!-- Angular translate https://angular-translate.github.io/ -->
|
||||
<script type="text/javascript" src="/js/angular-translate.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-translate-loader-static-files.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-translate-storage-cookie.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-translate-storage-local.min.js"></script>
|
||||
|
||||
<!-- Angular translate https://angular-translate.github.io/ -->
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-translate.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-translate-loader-static-files.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-cookie.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-local.min.js?<%= revision %>"></script>
|
||||
<!-- Showdown (markdown converter) -->
|
||||
<script type="text/javascript" src="/js/showdown-1.9.1.min.js"></script>
|
||||
|
||||
<!-- Showdown (markdown converter) -->
|
||||
<script type="text/javascript" src="/3rdparty/js/showdown-1.9.1.min.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Setup Application -->
|
||||
<script type="text/javascript" src="/js/setup.js"></script>
|
||||
<!-- Setup Application -->
|
||||
<script type="text/javascript" src="/js/activation.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/js/client.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/js/utils.js?%VITE_CACHE_ID%"></script>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -138,7 +136,7 @@
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<a class="btn btn-success" href="/">Proceed to Dashboard</a>
|
||||
<a class="btn btn-success" ng-href="firstTimeLoginUrl">Proceed to Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,7 +23,7 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-family: "Roboto","Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-family: "Noto Sans", Helvetica, Arial, sans-serif;
|
||||
line-height: 1.846;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<script>
|
||||
|
||||
var tmp = window.location.hash.slice(1).split('&');
|
||||
|
||||
tmp.forEach(function (pair) {
|
||||
if (pair.indexOf('access_token=') === 0) localStorage.token = pair.split('=')[1];
|
||||
});
|
||||
|
||||
var redirectTo = '/';
|
||||
if (localStorage.getItem('redirectToHash')) {
|
||||
redirectTo += localStorage.getItem('redirectToHash');
|
||||
localStorage.removeItem('redirectToHash');
|
||||
}
|
||||
window.location.href = redirectTo;
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
echo "=> Create timezones.js"
|
||||
./scripts/createTimezones.cjs ./public/js/timezones.js
|
||||
|
||||
echo "=> Build theme.css for oidc views"
|
||||
./node_modules/.bin/sass --quiet --pkg-importer=node ./src/theme.scss ./public/theme.css
|
||||
|
||||
export VITE_CACHE_ID=$(date +%s)
|
||||
|
||||
echo "=> Build the dashboard apps"
|
||||
./node_modules/.bin/vite build
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
echo "=> Set API origin"
|
||||
export VITE_API_ORIGIN="${DASHBOARD_DEVELOPMENT_ORIGIN}"
|
||||
|
||||
# only really used for prod builds to bust cache
|
||||
export VITE_CACHE_ID="develop"
|
||||
|
||||
echo "=> Run vite locally"
|
||||
npm run dev
|
||||
@@ -0,0 +1,22 @@
|
||||
import globals from 'globals';
|
||||
import js from '@eslint/js';
|
||||
import pluginVue from 'eslint-plugin-vue';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
{
|
||||
files: ["**/*.js"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
ecmaVersion: 13,
|
||||
sourceType: 'module'
|
||||
},
|
||||
rules: {
|
||||
semi: "error",
|
||||
"prefer-const": "error"
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -5,6 +5,13 @@
|
||||
<link rel="icon" href="/api/v1/cloudron/avatar" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>File Manager</title>
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
@@ -1,220 +0,0 @@
|
||||
/* jslint node:true */
|
||||
|
||||
'use strict';
|
||||
|
||||
const argv = require('yargs').argv,
|
||||
autoprefixer = require('gulp-autoprefixer'),
|
||||
concat = require('gulp-concat'),
|
||||
cssnano = require('gulp-cssnano'),
|
||||
ejs = require('gulp-ejs'),
|
||||
execSync = require('child_process').execSync,
|
||||
fs = require('fs'),
|
||||
gulp = require('gulp'),
|
||||
sass = require('gulp-sass')(require('sass')),
|
||||
serve = require('gulp-serve'),
|
||||
sourcemaps = require('gulp-sourcemaps');
|
||||
|
||||
if (argv.help || argv.h) {
|
||||
console.log('Supported arguments for "gulp develop":');
|
||||
console.log(' --api-origin <cloudron api uri>');
|
||||
console.log(' --revision <revision>');
|
||||
console.log(' --appstore-console-origin <appstore console uri>');
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const revision = argv.revision || '';
|
||||
|
||||
let apiOrigin = '';
|
||||
if (argv.apiOrigin) {
|
||||
if (argv.apiOrigin.indexOf('https://') === 0) apiOrigin = argv.apiOrigin;
|
||||
else apiOrigin = 'https://' + argv.apiOrigin;
|
||||
}
|
||||
|
||||
var appstore = {
|
||||
consoleOrigin: argv.appstoreConsoleOrigin || ''
|
||||
};
|
||||
|
||||
console.log();
|
||||
console.log('Cloudron API: %s', apiOrigin || 'default');
|
||||
console.log('Building for revision: %s', revision);
|
||||
console.log();
|
||||
console.log('Overriding appstore origin:');
|
||||
console.log(' Console: %s', appstore.consoleOrigin || 'no');
|
||||
console.log();
|
||||
|
||||
gulp.task('fontawesome', function () {
|
||||
return gulp.src('node_modules/@fortawesome/fontawesome-free/**/*')
|
||||
.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('moment', function () {
|
||||
return gulp.src('node_modules/moment/min/*')
|
||||
.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',
|
||||
'node_modules/chart.js/dist/chart.umd.js'
|
||||
]).pipe(gulp.dest('dist/3rdparty/'));
|
||||
});
|
||||
|
||||
gulp.task('3rdparty', gulp.series(['3rdparty-copy', 'moment', 'bootstrap', 'fontawesome']));
|
||||
|
||||
// --------------
|
||||
// JavaScript
|
||||
// --------------
|
||||
|
||||
gulp.task('js-index', function () {
|
||||
return gulp.src([
|
||||
'src/js/index.js',
|
||||
'src/js/client.js',
|
||||
'src/js/utils.js',
|
||||
'src/views/*.js'
|
||||
])
|
||||
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('index.js', { newLine: ';' }))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-passwordreset', function () {
|
||||
return gulp.src(['src/js/passwordreset.js', 'src/js/utils.js'])
|
||||
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('passwordreset.js', { newLine: ';' }))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-setupaccount', function () {
|
||||
return gulp.src(['src/js/setupaccount.js', 'src/js/utils.js'])
|
||||
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('setupaccount.js', { newLine: ';' }))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-setup', function () {
|
||||
return gulp.src(['src/js/setup.js', 'src/js/client.js', 'src/js/utils.js'])
|
||||
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('setup.js', { newLine: ';' }))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-setupdns', function () {
|
||||
return gulp.src(['src/js/setupdns.js', 'src/js/client.js', 'src/js/utils.js'])
|
||||
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('setupdns.js', { newLine: ';' }))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js-restore', function () {
|
||||
return gulp.src(['src/js/restore.js', 'src/js/client.js', 'src/js/utils.js'])
|
||||
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat('restore.js', { newLine: ';' }))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest('dist/js'));
|
||||
});
|
||||
|
||||
gulp.task('js', gulp.series([ 'js-index', 'js-passwordreset', 'js-setupaccount', 'js-setup', 'js-setupdns', 'js-restore' ]));
|
||||
|
||||
// --------------
|
||||
// HTML
|
||||
// --------------
|
||||
|
||||
gulp.task('html-views', function () {
|
||||
return gulp.src('src/views/**/*.html').pipe(gulp.dest('dist/views'));
|
||||
});
|
||||
|
||||
gulp.task('html-templates', function () {
|
||||
return gulp.src('src/templates/**/*').pipe(gulp.dest('dist/templates'));
|
||||
});
|
||||
|
||||
gulp.task('html-raw', function () {
|
||||
return gulp.src('src/*.html').pipe(ejs({ apiOrigin: apiOrigin, 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('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('dist'));
|
||||
});
|
||||
|
||||
gulp.task('images', function () {
|
||||
return gulp.src('src/img/**')
|
||||
.pipe(gulp.dest('dist/img'));
|
||||
});
|
||||
|
||||
gulp.task('translation', function () {
|
||||
return gulp.src('src/translation/**')
|
||||
.pipe(gulp.dest('dist/translation'));
|
||||
});
|
||||
|
||||
gulp.task('timezones', function (done) {
|
||||
execSync('./scripts/createTimezones.js ./dist/js/timezones.js');
|
||||
done();
|
||||
});
|
||||
|
||||
// --------------
|
||||
// Utilities
|
||||
// --------------
|
||||
|
||||
gulp.task('clean', function (done) {
|
||||
fs.rm('dist', { recursive: true, force: true }, done);
|
||||
});
|
||||
|
||||
gulp.task('default', gulp.series(['clean', 'html', 'js', 'timezones', '3rdparty', 'translation', 'images', 'css']));
|
||||
|
||||
gulp.task('watch', function (done) {
|
||||
gulp.watch(['src/*.scss'], gulp.series(['css']));
|
||||
gulp.watch(['src/img/*'], gulp.series(['images']));
|
||||
gulp.watch(['src/translation/*'], gulp.series(['translation']));
|
||||
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(['scripts/createTimezones.js', 'src/js/utils.js'], gulp.series(['timezones']));
|
||||
gulp.watch(['src/js/setup.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-setup']));
|
||||
gulp.watch(['src/js/setupdns.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-setupdns']));
|
||||
gulp.watch(['src/js/restore.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-restore']));
|
||||
gulp.watch(['src/js/passwordreset.js', 'src/js/utils.js'], gulp.series(['js-passwordreset']));
|
||||
gulp.watch(['src/js/setupaccount.js', 'src/js/utils.js'], gulp.series(['js-setupaccount']));
|
||||
gulp.watch(['src/js/index.js', 'src/js/client.js', 'src/views/*.js', 'src/js/utils.js'], gulp.series(['js-index']));
|
||||
gulp.watch(['src/3rdparty/**/*'], gulp.series(['3rdparty']));
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('serve', serve({ root: 'dist', port: 4000, hostname: '0.0.0.0' }));
|
||||
|
||||
gulp.task('develop', gulp.series(['default', 'watch', 'serve']));
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
<!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 Dashboard</title>
|
||||
<meta name="description" content="Cloudron Dashboard">
|
||||
|
||||
<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">
|
||||
|
||||
<!-- contains all thing already using import statement -->
|
||||
<script type="module" src="./src/modules.js"></script>
|
||||
|
||||
<!-- jQuery-->
|
||||
<script type="text/javascript" src="/js/jquery.min.js"></script>
|
||||
|
||||
<!-- CSS -->
|
||||
<link type="text/css" rel="stylesheet" href="/slick.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="/angular-ui-notification.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="./src/theme.scss">
|
||||
|
||||
<!-- async -->
|
||||
<script type="text/javascript" src="/js/async-3.2.0.min.js"></script>
|
||||
|
||||
<!-- Slick carousel -->
|
||||
<script type="text/javascript" src="/js/slick.js"></script>
|
||||
|
||||
<!-- Angularjs scripts -->
|
||||
<script type="text/javascript" src="/js/angular.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-loader.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-route.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-cookies.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-animate.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-base64.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-md5.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-sanitize.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-slick.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-ui-notification.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-fittext.min.js"></script>
|
||||
|
||||
<!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ -->
|
||||
<script type="text/javascript" src="/js/ui-bootstrap-tpls-1.3.3.min.js"></script>
|
||||
|
||||
<!-- Angular translate https://angular-translate.github.io/ -->
|
||||
<script type="text/javascript" src="/js/angular-translate.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-translate-loader-static-files.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-translate-storage-cookie.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-translate-storage-local.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/js/clipboard.min.js"></script>
|
||||
|
||||
<!-- Showdown (markdown converter) -->
|
||||
<script type="text/javascript" src="/js/showdown-1.9.1.min.js"></script>
|
||||
|
||||
<!-- Anugular Multiselect https://github.com/sebastianha/angular-bootstrap-multiselect -->
|
||||
<script type="text/javascript" src="/js/angular-bootstrap-multiselect.js"></script>
|
||||
|
||||
<!-- timezone list -->
|
||||
<script type="text/javascript" src="/js/timezones.js?%VITE_CACHE_ID%"></script>
|
||||
|
||||
<!-- Main Application -->
|
||||
<script type="text/javascript" src="/js/index.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/js/client.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/js/utils.js?%VITE_CACHE_ID%"></script>
|
||||
|
||||
<script type="text/javascript" src="/views/app.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/apps.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/appstore.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/backups.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/branding.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/domains.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/email.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/emails-eventlog.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/emails.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/emails-queue.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/eventlog.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/network.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/notifications.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/profile.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/services.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/settings.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/support.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/system.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/user-directory.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/users.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/views/volumes.js?%VITE_CACHE_ID%"></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://docs.cloudron.io/troubleshooting/" target="_blank"><i class="fa fa-circle-notch fa-spin"></i> {{ 'main.offline' | tr }}</a>
|
||||
|
||||
<!-- Modal reboot server -->
|
||||
<div class="modal fade" id="rebootModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'main.rebootDialog.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-bold">{{ 'main.rebootDialog.warning' | tr }}</p>
|
||||
<p>{{ 'main.rebootDialog.description' | tr }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="reboot.submit()" ng-disabled="reboot.busy"><i class="fa fa-circle-notch fa-spin" ng-show="reboot.busy"></i> {{ 'main.rebootDialog.rebootAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mainContentContainer" 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="user.isAtLeastOwner && (subscription.plan.id === 'free' || subscription.plan.id === 'expired')">
|
||||
<a ng-click="openSubscriptionSetup()" style="cursor: pointer">
|
||||
<span class="badge" ng-class="{'badge-danger': subscription.plan.id !== 'free', 'badge-success': subscription.plan.id === 'free' }">
|
||||
{{ subscription.plan.id === 'free' ? ('settings.appstoreAccount.subscriptionSetupAction' | tr) : ('settings.appstoreAccount.subscriptionReactivateAction' | tr) }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="!user.isAtLeastOwner && subscription.plan.id === 'expired'">
|
||||
<a>
|
||||
<span class="badge badge-danger">Subscription Expired</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-class="{ active: isActive('/apps')}" href="#/apps" ng-click="closeNavbar()"><i class="fa fa-grip fa-fw"></i> {{ 'apps.title' | tr }}</a>
|
||||
</li>
|
||||
<li ng-show="user.isAtLeastAdmin">
|
||||
<a ng-class="{ active: isActive('/appstore')}" href="#/appstore" ng-click="closeNavbar()"><i class="fa fa-cloud-download-alt fa-fw"></i> {{ 'appstore.title' | tr }}</a>
|
||||
</li>
|
||||
<li ng-show="user.isAtLeastUserManager">
|
||||
<a ng-class="{ active: isActive('/users')}" href="#/users" ng-click="closeNavbar()"><i class="fa fa-users fa-fw"></i> {{ 'main.navbar.users' | tr }}</a>
|
||||
</li>
|
||||
<li ng-show="user.isAtLeastAdmin">
|
||||
<a href="#/notifications" ng-click="closeNavbar()">
|
||||
<i class="fas fa-bell" ng-show="notificationCount"></i>
|
||||
<i class="far fa-bell" ng-hide="notificationCount"></i>
|
||||
<span class="badge badge-danger" ng-show="notificationCount">{{ notificationCount === 100 ? '100+' : notificationCount }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><img ng-src="{{user.avatarUrl}}" style="width: 24px; height: 24px;"/> {{user.username}} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="#/profile" ng-click="closeNavbar()"><i class="fa fa-user fa-fw"></i> {{ 'profile.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastMailManager" class="divider"></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/backups" ng-click="closeNavbar()"><i class="fa fa-archive fa-fw"></i> {{ 'backups.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/branding" ng-click="closeNavbar()"><i class="fa fa-passport fa-fw"></i> {{ 'branding.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/domains" ng-click="closeNavbar()"><i class="fa fa-globe fa-fw"></i> {{ 'domains.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastMailManager"><a href="#/email" ng-click="closeNavbar()"><i class="fa fa-envelope fa-fw"></i> {{ 'emails.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/eventlog" ng-click="closeNavbar()"><i class="fa fa-list-alt fa-fw"></i> {{ 'eventlog.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/network" ng-click="closeNavbar()"><i class="fas fa-network-wired fa-fw"></i> {{ 'network.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/services" ng-click="closeNavbar()"><i class="fa fa-cogs fa-fw"></i> {{ 'services.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/settings" ng-click="closeNavbar()"><i class="fa fa-wrench fa-fw"></i> {{ 'settings.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/user-directory" ng-click="closeNavbar()"><i class="fa fa-users-gear fa-fw"></i> {{ 'users.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/volumes" ng-click="closeNavbar()"><i class="fa fa-hdd fa-fw"></i> {{ 'volumes.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin" class="divider"></li>
|
||||
<li ng-show="user.isAtLeastOwner"><a href="#/support" ng-click="closeNavbar()"><i class="fa fa-comment fa-fw"></i> {{ 'support.title' | tr }}</a></li>
|
||||
<li ng-show="user.isAtLeastAdmin"><a href="#/system" ng-click="closeNavbar()"><i class="fa fa-chart-area fa-fw"></i> {{ 'system.title' | tr }}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="" ng-click="logout($event)"><i class="fa fa-sign-out-alt fa-fw"></i> {{ 'main.logout' | tr }}</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.footer | markdown2html"></span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,6 +5,13 @@
|
||||
<link rel="icon" href="/api/v1/cloudron/avatar" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Logs</title>
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
@@ -23,7 +23,7 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-family: "Roboto","Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-family: "Noto Sans", Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.846;
|
||||
}
|
||||
@@ -51,9 +51,19 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', (event) => {
|
||||
document.getElementById('message').innerHTML =
|
||||
'You are seeing this page because the DNS record of <b>' + window.location.hostname + '</b> is set to this server\'s IP'
|
||||
// https://stackoverflow.com/questions/37437890/check-if-url-has-domain-name-and-not-an-ip
|
||||
const containsLetter = /[a-zA-z]/.test(window.location.hostname); // ignore technicality that IP can contain letters ! http://192.168.0x1.0x1 or http://0xc0.0xa8.1.1
|
||||
const isIPv6 = location.hostname.startsWith('[') && location.hostname.endsWith(']');
|
||||
|
||||
let message;
|
||||
if (!containsLetter || isIPv6) { // ipv4 or ipv6
|
||||
message = 'You cannot view Cloudron dashboard by IP address. Instead, navigate to the domain you configured during setup i.e <b>https://my.domain.example</b> .'
|
||||
+ '<br>If you do not remember your domain, SSH into your server and run <code>cloudron-support --owner-login</code> .'
|
||||
} else { // hostname
|
||||
message = 'You are seeing this page because the DNS record of <b>' + window.location.hostname + '</b> is set to this server\'s IP'
|
||||
+ ' but Cloudron has no app configured for this domain.';
|
||||
}
|
||||
document.getElementById('message').innerHTML = message;
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
@@ -1,36 +1,33 @@
|
||||
{
|
||||
"name": "dashboard",
|
||||
"version": "1.0.0",
|
||||
"description": "[Cloudron](https://cloudron.io) is the best way to run apps on your server.",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"update-translations": "curl https://translate.cloudron.io/api/components/cloudron/dashboard/file/ -o lang.zip && unzip -jo lang.zip -d ./src/translation/ && rm lang.zip"
|
||||
"update-translations": "curl https://translate.cloudron.io/api/components/cloudron/dashboard/file/ -o lang.zip && unzip -jo lang.zip -d ./public/translation/ && rm lang.zip",
|
||||
"dev": "vite --strictPort --port 4000",
|
||||
"build": "vite build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "ssh://git@git.cloudron.io:6000/cloudron/dashboard.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@eslint/js": "^9.16.0",
|
||||
"@fontsource/noto-sans": "^5.1.0",
|
||||
"@fortawesome/fontawesome-free": "^6.7.1",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@xterm/addon-attach": "^0.11.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"anser": "^2.3.0",
|
||||
"bootstrap-sass": "^3.4.3",
|
||||
"chart.js": "^4.3.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-autoprefixer": "^8.0.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-cssnano": "^2.1.3",
|
||||
"gulp-ejs": "^5.1.0",
|
||||
"gulp-sass": "^5.1.0",
|
||||
"gulp-serve": "^1.4.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"sass": "^1.63.3",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"env": {
|
||||
"browser": true
|
||||
}
|
||||
"chart.js": "^4.4.7",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"filesize": "^10.1.6",
|
||||
"jquery": "^3.7.1",
|
||||
"marked": "^15.0.3",
|
||||
"moment": "^2.30.1",
|
||||
"pankow": "^2.4.2",
|
||||
"pankow-viewers": "^1.0.11",
|
||||
"sass": "^1.82.0",
|
||||
"vite": "^6.0.3",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^10.0.5",
|
||||
"vue-router": "^4.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src <%= apiOrigin %> 'unsafe-inline' 'unsafe-eval' 'self'; img-src <%= apiOrigin %> 'self'" />
|
||||
<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 status (because angular has not loaded yet, we see template string for a flash) -->
|
||||
<title>Cloudron Password Reset</title>
|
||||
<meta name="description" content="Cloudron Password Reset">
|
||||
<!-- this gets changed once we get the status (because angular has not loaded yet, we see template string for a flash) -->
|
||||
<title>Cloudron Password Reset</title>
|
||||
<meta name="description" content="Cloudron Password Reset">
|
||||
|
||||
<link id="favicon" href="<%= apiOrigin %>/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
<link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link type="text/css" rel="stylesheet" href="/theme.css?<%= revision %>">
|
||||
<!-- contains all thing already using import statement -->
|
||||
<script type="module" src="./src/modules.js"></script>
|
||||
|
||||
<!-- Fontawesome -->
|
||||
<link type="text/css" rel="stylesheet" href="/3rdparty/fontawesome/css/all.css?<%= revision %>"/>
|
||||
<!-- Theme CSS -->
|
||||
<link type="text/css" rel="stylesheet" href="./src/theme.scss">
|
||||
|
||||
<!-- jQuery-->
|
||||
<script type="text/javascript" src="/3rdparty/js/jquery.min.js?<%= revision %>"></script>
|
||||
<!-- jQuery-->
|
||||
<script type="text/javascript" src="/js/jquery.min.js"></script>
|
||||
|
||||
<!-- async -->
|
||||
<script type="text/javascript" src="/3rdparty/js/async-3.2.0.min.js?<%= revision %>"></script>
|
||||
<!-- async -->
|
||||
<script type="text/javascript" src="/js/async-3.2.0.min.js"></script>
|
||||
|
||||
<!-- Bootstrap Core JavaScript -->
|
||||
<script type="text/javascript" src="/3rdparty/js/bootstrap.min.js?<%= revision %>"></script>
|
||||
<!-- Showdown (markdown converter) -->
|
||||
<script type="text/javascript" src="/js/showdown-1.9.1.min.js"></script>
|
||||
|
||||
<!-- Showdown (markdown converter) -->
|
||||
<script type="text/javascript" src="/3rdparty/js/showdown-1.9.1.min.js?<%= revision %>"></script>
|
||||
<!-- Angularjs scripts -->
|
||||
<script type="text/javascript" src="/js/angular.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-loader.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-cookies.min.js"></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-md5.min.js"></script> -->
|
||||
<!-- <script type="text/javascript" src="/3rdparty/js/angular-ui-notification.js"></script> -->
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-cookies.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/autofill-event.js?<%= revision %>"></script>
|
||||
<!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ -->
|
||||
<script type="text/javascript" src="/js/ui-bootstrap-tpls-1.3.3.min.js"></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>
|
||||
<!-- Angular translate https://angular-translate.github.io/ -->
|
||||
<script type="text/javascript" src="/js/angular-translate.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-translate-loader-static-files.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-translate-storage-cookie.min.js"></script>
|
||||
<script type="text/javascript" src="/js/angular-translate-storage-local.min.js"></script>
|
||||
|
||||
<!-- Angular translate https://angular-translate.github.io/ -->
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-translate.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-translate-loader-static-files.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-cookie.min.js?<%= revision %>"></script>
|
||||
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-local.min.js?<%= revision %>"></script>
|
||||
|
||||
<!-- Setup Application -->
|
||||
<script type="text/javascript" src="/js/passwordreset.js?<%= revision %>"></script>
|
||||
<!-- Setup Application -->
|
||||
<script type="text/javascript" src="/js/passwordreset.js?%VITE_CACHE_ID%"></script>
|
||||
<script type="text/javascript" src="/js/utils.js?%VITE_CACHE_ID%"></script>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -59,7 +53,7 @@
|
||||
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="text-align: center;">
|
||||
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/>
|
||||
<img width="128" height="128" style="margin-top: -84px" src="/api/v1/cloudron/avatar"/>
|
||||
<br/>
|
||||
<h2>{{ 'passwordReset.title' | tr }}</h2>
|
||||
</div>
|
||||
@@ -87,7 +81,7 @@
|
||||
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="text-align: center;">
|
||||
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/>
|
||||
<img width="128" height="128" style="margin-top: -84px" src="/api/v1/cloudron/avatar"/>
|
||||
<br/>
|
||||
<h2 ng-hide="error">{{ 'passwordReset.emailSent.title' | tr }}</h2>
|
||||
<h4 ng-show="error" class="has-error">{{ error }}</h4>
|
||||
@@ -102,7 +96,7 @@
|
||||
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="text-align: center;">
|
||||
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/>
|
||||
<img width="128" height="128" style="margin-top: -84px" src="/api/v1/cloudron/avatar"/>
|
||||
<br/>
|
||||
<h2>{{ 'passwordReset.newPassword.title' | tr }}</h2>
|
||||
</div>
|
||||
@@ -150,7 +144,7 @@
|
||||
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="text-align: center;">
|
||||
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/>
|
||||
<img width="128" height="128" style="margin-top: -84px" src="/api/v1/cloudron/avatar"/>
|
||||
<br/>
|
||||
<h2>{{ 'passwordReset.success.title' | tr }}</h2>
|
||||
<br/>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 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 |
@@ -1,19 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular */
|
||||
/* global $ */
|
||||
/* global $, angular, redirectIfNeeded */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', '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; }, {});
|
||||
const 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.view = '';
|
||||
$scope.initialized = false;
|
||||
$scope.setupToken = '';
|
||||
$scope.firstTimeLoginUrl = '';
|
||||
|
||||
$scope.owner = {
|
||||
error: null,
|
||||
@@ -36,7 +36,7 @@ app.controller('SetupController', ['$scope', 'Client', function ($scope, Client)
|
||||
setupToken: $scope.setupToken
|
||||
};
|
||||
|
||||
Client.createAdmin(data, function (error) {
|
||||
Client.createAdmin(data, function (error, autoLoginToken) {
|
||||
if (error && error.statusCode === 400) {
|
||||
$scope.owner.busy = false;
|
||||
|
||||
@@ -59,38 +59,16 @@ app.controller('SetupController', ['$scope', 'Client', function ($scope, Client)
|
||||
return;
|
||||
}
|
||||
|
||||
// set token to autologin on first oidc flow
|
||||
localStorage.cloudronFirstTimeToken = autoLoginToken;
|
||||
|
||||
$scope.firstTimeLoginUrl = '/openid/auth?client_id=cid-webadmin&scope=openid email profile&response_type=code token&redirect_uri=' + window.location.origin + '/authcallback.html';
|
||||
|
||||
setView('finished');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function redirectIfNeeded(status) {
|
||||
if ('develop' in search || localStorage.getItem('develop')) {
|
||||
console.warn('Cloudron develop mode on. To disable run localStorage.removeItem(\'develop\')');
|
||||
localStorage.setItem('develop', true);
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are here from https://ip/setup.html ,go to https://admin/setup.html
|
||||
if (status.adminFqdn && status.adminFqdn !== window.location.hostname) {
|
||||
window.location.href = 'https://' + status.adminFqdn + '/setup.html';
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we don't have a domain yet, first go to domain setup
|
||||
if (!status.adminFqdn) {
|
||||
window.location.href = '/setupdns.html';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (status.activated) {
|
||||
window.location.href = '/';
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function setView(view) {
|
||||
if (view === 'finished') {
|
||||
$scope.view = 'finished';
|
||||
@@ -103,7 +81,8 @@ app.controller('SetupController', ['$scope', 'Client', function ($scope, Client)
|
||||
Client.getProvisionStatus(function (error, status) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
if (redirectIfNeeded(status)) return;
|
||||
if (redirectIfNeeded(status, 'activation')) return; // redirected to some other view...
|
||||
|
||||
setView(search.view);
|
||||
|
||||
$scope.setupToken = search.setupToken;
|
||||
@@ -1,26 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
/* global $ */
|
||||
/* global angular */
|
||||
/* global EventSource */
|
||||
/* global async */
|
||||
/* global $, angular, async */
|
||||
|
||||
// keep in sync with box/src/notfications.js
|
||||
const NOTIFICATION_TYPES = {
|
||||
ALERT_CLOUDRON_INSTALLED: 'cloudronInstalled',
|
||||
ALERT_CLOUDRON_UPDATED: 'cloudronUpdated',
|
||||
ALERT_CLOUDRON_UPDATE_FAILED: 'cloudronUpdateFailed',
|
||||
ALERT_CERTIFICATE_RENEWAL_FAILED: 'certificateRenewalFailed',
|
||||
ALERT_BACKUP_CONFIG: 'backupConfig',
|
||||
ALERT_DISK_SPACE: 'diskSpace',
|
||||
ALERT_MAIL_STATUS: 'mailStatus',
|
||||
ALERT_REBOOT: 'reboot',
|
||||
ALERT_BOX_UPDATE: 'boxUpdate',
|
||||
ALERT_UPDATE_UBUNTU: 'ubuntuUpdate',
|
||||
ALERT_MANUAL_APP_UPDATE: 'manualAppUpdate',
|
||||
ALERT_APP_OOM: 'appOutOfMemory',
|
||||
ALERT_APP_UPDATED: 'appUpdated',
|
||||
ALERT_BACKUP_FAILED: 'backupFailed',
|
||||
CLOUDRON_INSTALLED: 'cloudronInstalled',
|
||||
CLOUDRON_UPDATED: 'cloudronUpdated',
|
||||
CLOUDRON_UPDATE_FAILED: 'cloudronUpdateFailed',
|
||||
CERTIFICATE_RENEWAL_FAILED: 'certificateRenewalFailed',
|
||||
BACKUP_CONFIG: 'backupConfig',
|
||||
DISK_SPACE: 'diskSpace',
|
||||
MAIL_STATUS: 'mailStatus',
|
||||
REBOOT: 'reboot',
|
||||
BOX_UPDATE: 'boxUpdate',
|
||||
UPDATE_UBUNTU: 'ubuntuUpdate',
|
||||
MANUAL_APP_UPDATE: 'manualAppUpdate',
|
||||
APP_OOM: 'appOutOfMemory',
|
||||
APP_UPDATED: 'appUpdated',
|
||||
BACKUP_FAILED: 'backupFailed',
|
||||
};
|
||||
|
||||
// keep in sync with box/src/apps.js
|
||||
@@ -167,10 +164,17 @@ const REGIONS_WASABI = [
|
||||
{ name: 'Virginia (US East 2)', value: 'https://s3.us-east-2.wasabisys.com' }
|
||||
];
|
||||
|
||||
const REGIONS_HETZNER = [
|
||||
{ name: 'Falkenstein (FSN1)', value: 'https://fsn1.your-objectstorage.com' },
|
||||
{ name: 'Helsinki (HEL1)', value: 'https://hel1.your-objectstorage.com' },
|
||||
{ name: 'Nuremberg (NBG1)', value: 'https://nbg1.your-objectstorage.com' }
|
||||
];
|
||||
|
||||
// https://docs.digitalocean.com/products/platform/availability-matrix/
|
||||
const REGIONS_DIGITALOCEAN = [
|
||||
{ name: 'AMS3', value: 'https://ams3.digitaloceanspaces.com' },
|
||||
{ name: 'FRA1', value: 'https://fra1.digitaloceanspaces.com' },
|
||||
{ name: 'LON1', value: 'https://lon1.digitaloceanspaces.com' },
|
||||
{ name: 'NYC3', value: 'https://nyc3.digitaloceanspaces.com' },
|
||||
{ name: 'SFO2', value: 'https://sfo2.digitaloceanspaces.com' },
|
||||
{ name: 'SFO3', value: 'https://sfo3.digitaloceanspaces.com' },
|
||||
@@ -181,6 +185,7 @@ const REGIONS_DIGITALOCEAN = [
|
||||
// https://www.exoscale.com/datacenters/
|
||||
const REGIONS_EXOSCALE = [
|
||||
{ name: 'Vienna (AT-VIE-1)', value: 'https://sos-at-vie-1.exo.io' },
|
||||
{ name: 'Vienna (AT-VIE-2)', value: 'https://sos-at-vie-2.exo.io' },
|
||||
{ name: 'Sofia (BG-SOF-1)', value: 'https://sos-bg-sof-1.exo.io' },
|
||||
{ name: 'Zurich (CH-DK-2)', value: 'https://sos-ch-dk-2.exo.io' },
|
||||
{ name: 'Geneva (CH-GVA-2)', value: 'https://sos-ch-gva-2.exo.io' },
|
||||
@@ -218,13 +223,14 @@ const REGIONS_LINODE = [
|
||||
|
||||
// note: ovh also has a storage endpoint but that only supports path style access (https://docs.ovh.com/au/en/storage/object-storage/s3/location/)
|
||||
const REGIONS_OVH = [
|
||||
{ name: 'Beauharnois (BHS)', value: 'https://s3.bhs.cloud.ovh.net', region: 'bhs' }, // default
|
||||
{ name: 'Frankfurt (DE)', value: 'https://s3.de.cloud.ovh.net', region: 'de' },
|
||||
{ name: 'Gravelines (GRA)', value: 'https://s3.gra.cloud.ovh.net', region: 'gra' },
|
||||
{ name: 'Strasbourg (SBG)', value: 'https://s3.sbg.cloud.ovh.net', region: 'sbg' },
|
||||
{ name: 'London (UK)', value: 'https://s3.uk.cloud.ovh.net', region: 'uk' },
|
||||
{ name: 'Sydney (SYD)', value: 'https://s3.syd.cloud.ovh.net', region: 'syd' },
|
||||
{ name: 'Warsaw (WAW)', value: 'https://s3.waw.cloud.ovh.net', region: 'waw' },
|
||||
{ name: 'Beauharnois (BHS)', value: 'https://s3.bhs.io.cloud.ovh.net', region: 'bhs' }, // default
|
||||
{ name: 'Frankfurt (DE)', value: 'https://s3.de.io.cloud.ovh.net', region: 'de' },
|
||||
{ name: 'Gravelines (GRA)', value: 'https://s3.gra.io.cloud.ovh.net', region: 'gra' },
|
||||
{ name: 'Roubaix (RBX)', value: 'https://s3.rbx.io.cloud.ovh.net', region: 'rbx' },
|
||||
{ name: 'Strasbourg (SBG)', value: 'https://s3.sbg.io.cloud.ovh.net', region: 'sbg' },
|
||||
{ name: 'London (UK)', value: 'https://s3.uk.io.cloud.ovh.net', region: 'uk' },
|
||||
{ name: 'Sydney (SYD)', value: 'https://s3.syd.io.cloud.ovh.net', region: 'syd' },
|
||||
{ name: 'Warsaw (WAW)', value: 'https://s3.waw.io.cloud.ovh.net', region: 'waw' },
|
||||
];
|
||||
|
||||
const ENDPOINTS_OVH = [
|
||||
@@ -239,9 +245,10 @@ const ENDPOINTS_OVH = [
|
||||
|
||||
// https://docs.ionos.com/cloud/managed-services/s3-object-storage/endpoints
|
||||
const REGIONS_IONOS = [
|
||||
{ name: 'Frankfurt (DE)', value: 'https://s3-de-central.profitbricks.com', region: 's3-de-central' }, // default
|
||||
{ name: 'Berlin (eu-central-2)', value: 'https://s3-eu-central-2.ionoscloud.com', region: 'eu-central-2' }, // default
|
||||
{ name: 'Logrono (eu-south-2)', value: 'https://s3-eu-south-2.ionoscloud.com', region: 'eu-south-2' }, // default
|
||||
{ name: 'Berlin (eu-central-3)', value: 'https://s3.eu-central-3.ionoscloud.com', region: 'de' }, // default. contract-owned
|
||||
{ name: 'Frankfurt (DE)', value: 'https://s3.eu-central-1.ionoscloud.com', region: 'de' },
|
||||
{ name: 'Berlin (eu-central-2)', value: 'https://s3-eu-central-2.ionoscloud.com', region: 'eu-central-2' },
|
||||
{ name: 'Logrono (eu-south-2)', value: 'https://s3-eu-south-2.ionoscloud.com', region: 'eu-south-2' },
|
||||
];
|
||||
|
||||
// this is not used anywhere because upcloud needs endpoint URL. we detect region from the URL (https://upcloud.com/data-centres)
|
||||
@@ -288,6 +295,7 @@ const STORAGE_PROVIDERS = [
|
||||
{ name: 'Filesystem', value: 'filesystem' },
|
||||
{ name: 'Filesystem (Mountpoint)', value: 'mountpoint' }, // legacy
|
||||
{ name: 'Google Cloud Storage', value: 'gcs' },
|
||||
{ name: 'Hetzner Object Storage', value: 'hetzner-objectstorage' },
|
||||
{ name: 'IDrive e2', value: 'idrive-e2' },
|
||||
{ name: 'IONOS (Profitbricks)', value: 'ionos-objectstorage' },
|
||||
{ name: 'Linode Object Storage', value: 'linode-objectstorage' },
|
||||
@@ -327,7 +335,7 @@ function prettyBinarySize(size, fallback) {
|
||||
|
||||
// we can also use KB here (JEDEC)
|
||||
var i = Math.floor(Math.log(size) / Math.log(1024));
|
||||
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'KiB', 'MiB', 'GiB', 'TiB'][i];
|
||||
return (size / Math.pow(1024, i)).toFixed(3) * 1 + ' ' + ['B', 'KiB', 'MiB', 'GiB', 'TiB'][i];
|
||||
}
|
||||
|
||||
// decimal units (SI) 1000 based
|
||||
@@ -365,6 +373,8 @@ angular.module('Application').filter('trKeyFromPeriod', function () {
|
||||
angular.module('Application').filter('prettyDate', function ($translate) {
|
||||
// http://ejohn.org/files/pretty.js
|
||||
return function prettyDate(utc) {
|
||||
if (utc === null) return $translate.instant('main.prettyDate.never', {});
|
||||
|
||||
var date = new Date(utc), // this converts utc into browser timezone and not cloudron timezone!
|
||||
diff = (((new Date()).getTime() - date.getTime()) / 1000) + 30, // add 30seconds for clock skew
|
||||
day_diff = Math.floor(diff / 86400);
|
||||
@@ -417,7 +427,7 @@ angular.module('Application').filter('markdown2html', function () {
|
||||
angular.module('Application').config(['$translateProvider', function ($translateProvider) {
|
||||
$translateProvider.useStaticFilesLoader({
|
||||
prefix: 'translation/',
|
||||
suffix: '.json?' + '<%= revision %>'
|
||||
suffix: '.json'
|
||||
});
|
||||
$translateProvider.useLocalStorage();
|
||||
$translateProvider.preferredLanguage('en');
|
||||
@@ -448,6 +458,73 @@ function translateFilterFactory($parse, $translate) {
|
||||
translateFilterFactory.displayName = 'translateFilterFactory';
|
||||
angular.module('Application').filter('tr', translateFilterFactory);
|
||||
|
||||
// checks provision status and redirects to correct view
|
||||
// {
|
||||
// setup: { active, message, errorMessage }
|
||||
// restore { active, message, errorMessage }
|
||||
// activated
|
||||
// adminFqn
|
||||
// }
|
||||
// returns true if redirected . currentView is one of dashboard/restore/setup/activation
|
||||
function redirectIfNeeded(status, currentView) {
|
||||
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; }, {});
|
||||
|
||||
if ('develop' in search || localStorage.getItem('develop')) {
|
||||
console.warn('Cloudron develop mode on. To disable run localStorage.removeItem(\'develop\')');
|
||||
localStorage.setItem('develop', true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status.activated) {
|
||||
console.log('Already activated');
|
||||
if (currentView === 'dashboard') {
|
||||
// support local development with localhost check
|
||||
if (window.location.hostname !== status.adminFqdn && window.location.hostname !== 'localhost' && !window.location.hostname.startsWith('192.')) {
|
||||
// user is accessing by IP or by the old admin location (pre-migration)
|
||||
window.location.href = '/setup.html' + window.location.search;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
window.location.href = 'https://' + status.adminFqdn + '/';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (status.setup.active) {
|
||||
console.log('Setup is active');
|
||||
if (currentView === 'setup') return false;
|
||||
window.location.href = '/setup.html' + window.location.search;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (status.restore.active) {
|
||||
console.log('Restore is active');
|
||||
if (currentView === 'restore') return;
|
||||
window.location.href = '/restore.html' + window.location.search;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (status.adminFqdn) {
|
||||
console.log('adminFqdn is set');
|
||||
// if we are here from https://ip/activation.html ,go to https://admin/activation.html
|
||||
if (status.adminFqdn !== window.location.hostname) {
|
||||
window.location.href = 'https://' + status.adminFqdn + '/activation.html' + (window.location.search);
|
||||
return true;
|
||||
}
|
||||
if (currentView === 'activation') return false;
|
||||
window.location.href = 'https://' + status.adminFqdn + '/activation.html' + (window.location.search);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (currentView === 'dashboard') {
|
||||
window.location.href = '/setup.html' + window.location.search;
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we are here, proceed with current view
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------
|
||||
// Cloudron REST API wrapper
|
||||
@@ -543,21 +620,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
.error(defaultErrorHandler(callback));
|
||||
}
|
||||
|
||||
function head(url, config, callback) {
|
||||
if (arguments.length !== 3) {
|
||||
console.error('HEAD', arguments);
|
||||
throw('Wrong number of arguments');
|
||||
}
|
||||
|
||||
config = config || {};
|
||||
config.headers = config.headers || {};
|
||||
config.headers.Authorization = 'Bearer ' + token;
|
||||
|
||||
return $http.head(client.apiOrigin + url, config)
|
||||
.success(defaultSuccessHandler(callback))
|
||||
.error(defaultErrorHandler(callback));
|
||||
}
|
||||
|
||||
function post(url, data, config, callback) {
|
||||
if (arguments.length !== 4) {
|
||||
console.error('POST', arguments);
|
||||
@@ -619,7 +681,8 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
source: null,
|
||||
avatarUrl: null,
|
||||
avatarType: null,
|
||||
hasBackgroundImage: false
|
||||
hasBackgroundImage: false,
|
||||
notificationConfig: []
|
||||
};
|
||||
this._config = {
|
||||
consoleServerOrigin: null,
|
||||
@@ -635,8 +698,9 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
this._installedAppsById = {};
|
||||
this._appTags = [];
|
||||
// window.location fallback for websocket connections which do not have relative uris
|
||||
this.apiOrigin = '<%= apiOrigin %>' || window.location.origin;
|
||||
this.apiOrigin = window.cloudronApiOrigin || window.location.origin;
|
||||
this.avatar = '';
|
||||
this.background = '';
|
||||
this._availableLanguages = ['en'];
|
||||
this._appstoreAppCache = [];
|
||||
|
||||
@@ -660,7 +724,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
// this happens mostly if the box crashes
|
||||
if (message === 'Empty message or object') {
|
||||
message = 'Got empty response. Click to check the server logs.';
|
||||
action = action || '/frontend/logs.html?id=box';
|
||||
action = action || '/logs.html?id=box';
|
||||
}
|
||||
|
||||
this.notify('Cloudron Error', message, true, 'error', action);
|
||||
@@ -670,7 +734,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
Client.prototype.initError = function (error, initFunction) {
|
||||
console.error('Application startup error', error);
|
||||
|
||||
$timeout(initFunction, 5000); // we will try to re-init the app
|
||||
// $timeout(initFunction, 5000); // we will try to re-init the app
|
||||
};
|
||||
|
||||
Client.prototype.clearNotifications = function () {
|
||||
@@ -750,6 +814,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
this._userInfo.avatarUrl = userInfo.avatarUrl + '?ts=' + Date.now(); // we add the timestamp to avoid caching
|
||||
this._userInfo.avatarType = userInfo.avatarType;
|
||||
this._userInfo.hasBackgroundImage = userInfo.hasBackgroundImage;
|
||||
this._userInfo.notificationConfig = userInfo.notificationConfig;
|
||||
this._userInfo.isAtLeastOwner = [ ROLES.OWNER ].indexOf(userInfo.role) !== -1;
|
||||
this._userInfo.isAtLeastAdmin = [ ROLES.OWNER, ROLES.ADMIN ].indexOf(userInfo.role) !== -1;
|
||||
this._userInfo.isAtLeastMailManager = [ ROLES.OWNER, ROLES.ADMIN, ROLES.MAIL_MANAGER ].indexOf(userInfo.role) !== -1;
|
||||
@@ -761,9 +826,9 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
|
||||
angular.copy(config, this._config);
|
||||
|
||||
<% if (appstore.consoleOrigin) { -%>
|
||||
this._config.consoleServerOrigin = '<%= appstore.consoleOrigin %>';
|
||||
<% } -%>
|
||||
// <% if (appstore.consoleOrigin) { -%>
|
||||
// this._config.consoleServerOrigin = '<%= appstore.consoleOrigin %>';
|
||||
// <% } -%>
|
||||
|
||||
this._configListener.forEach(function (callback) {
|
||||
callback(that._config);
|
||||
@@ -825,6 +890,31 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.hasCloudronBackground = function (callback) {
|
||||
get('/api/v1/branding/cloudron_background', null, function (error, data, status) {
|
||||
if (error && error.statusCode !== 404) callback(error);
|
||||
else if (error) callback(null, false);
|
||||
else callback(null, status === 200);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.changeCloudronBackground = function (background, callback) {
|
||||
var fd = new FormData();
|
||||
if (background) fd.append('background', background);
|
||||
|
||||
var config = {
|
||||
headers: { 'Content-Type': undefined },
|
||||
transformRequest: angular.identity
|
||||
};
|
||||
|
||||
post('/api/v1/branding/cloudron_background', fd, config, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.changeCloudronAvatar = function (avatarFile, callback) {
|
||||
var fd = new FormData();
|
||||
fd.append('avatar', avatarFile);
|
||||
@@ -853,22 +943,23 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.installApp = function (id, manifest, title, config, callback) {
|
||||
Client.prototype.installApp = function (id, manifest, config, callback) {
|
||||
var data = {
|
||||
appStoreId: id + '@' + manifest.version,
|
||||
subdomain: config.subdomain,
|
||||
domain: config.domain,
|
||||
secondaryDomains: config.secondaryDomains,
|
||||
portBindings: config.portBindings,
|
||||
ports: config.ports,
|
||||
accessRestriction: config.accessRestriction,
|
||||
cert: config.cert,
|
||||
key: config.key,
|
||||
sso: config.sso,
|
||||
overwriteDns: config.overwriteDns,
|
||||
upstreamUri: config.upstreamUri
|
||||
upstreamUri: config.upstreamUri,
|
||||
backupId: config.backupId // when restoring from archive
|
||||
};
|
||||
|
||||
post('/api/v1/apps/install', data, null, function (error, data, status) {
|
||||
post('/api/v1/apps', data, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
|
||||
@@ -881,7 +972,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
subdomain: config.subdomain,
|
||||
domain: config.domain,
|
||||
secondaryDomains: config.secondaryDomains,
|
||||
portBindings: config.portBindings,
|
||||
ports: config.ports,
|
||||
backupId: config.backupId,
|
||||
overwriteDns: !!config.overwriteDns
|
||||
};
|
||||
@@ -916,6 +1007,17 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.archiveApp = function (appId, backupId, callback) {
|
||||
var data = { backupId: backupId };
|
||||
|
||||
post('/api/v1/apps/' + appId + '/archive', data, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.uninstallApp = function (appId, callback) {
|
||||
var data = {};
|
||||
|
||||
@@ -954,6 +1056,15 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.ackAppChecklistItem = function (appId, key, acknowledged, callback) {
|
||||
put('/api/v1/apps/' + appId + '/checklist/' + key, { done: acknowledged }, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.updateApp = function (id, manifest, options, callback) {
|
||||
var data = {
|
||||
appStoreId: manifest.id + '@' + manifest.version,
|
||||
@@ -1039,7 +1150,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
const storageConfig = Object.assign({}, backupConfig);
|
||||
delete storageConfig.limits;
|
||||
|
||||
post('/api/v1/backups/config/storage', backupConfig, null, function (error, data, status) {
|
||||
post('/api/v1/backups/config/storage', storageConfig, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
@@ -1292,8 +1403,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
};
|
||||
|
||||
Client.prototype.getUpdateInfo = function (callback) {
|
||||
if (!this._userInfo.isAtLeastAdmin) return callback(new Error('Not allowed'));
|
||||
|
||||
get('/api/v1/updater/updates', null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
@@ -1392,6 +1501,41 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.listArchives = function (callback) {
|
||||
var config = {
|
||||
params: {
|
||||
page: 1,
|
||||
per_page: 100
|
||||
}
|
||||
};
|
||||
|
||||
get('/api/v1/archives', config, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null, data.archives);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.deleteArchive = function (id, callback) {
|
||||
del('/api/v1/archives/' + id, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.unarchiveApp = function (archiveId, data, callback) {
|
||||
post('/api/v1/archives/' + archiveId + '/unarchive', data, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Client.prototype.getBackups = function (callback) {
|
||||
var page = 1;
|
||||
var perPage = 100;
|
||||
@@ -1485,16 +1629,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.restore = function (backupConfig, remotePath, version, ipv4Config, skipDnsSetup, setupToken, callback) {
|
||||
var data = {
|
||||
backupConfig: backupConfig,
|
||||
remotePath: remotePath,
|
||||
version: version,
|
||||
ipv4Config: ipv4Config,
|
||||
skipDnsSetup: skipDnsSetup,
|
||||
setupToken: setupToken
|
||||
};
|
||||
|
||||
Client.prototype.restore = function (data, callback) {
|
||||
post('/api/v1/provision/restore', data, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status));
|
||||
@@ -1830,15 +1965,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.getAppLimits = function (appId, callback) {
|
||||
get('/api/v1/apps/' + appId + '/limits', null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null, data.limits);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.getAppWithTask = function (appId, callback) {
|
||||
var that = this;
|
||||
|
||||
@@ -1888,6 +2014,15 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.detectIp = function (callback) {
|
||||
post('/api/v1/provision/detect_ip', {}, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.setup = function (data, callback) {
|
||||
post('/api/v1/provision/setup', data, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
@@ -1913,10 +2048,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
if (error) return callback(error);
|
||||
if (status !== 201) return callback(new ClientError(status, result));
|
||||
|
||||
that.setToken(result.token);
|
||||
that.setUserInfo({ username: data.username, email: data.email, admin: true, twoFactorAuthenticationEnabled: false, source: '', avatarUrl: null });
|
||||
|
||||
callback(null, result.activated);
|
||||
callback(null, result.token);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2082,15 +2214,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.disks = function (callback) {
|
||||
get('/api/v1/system/disks', null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.diskUsage = function (callback) {
|
||||
get('/api/v1/system/disk_usage', null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
@@ -2216,7 +2339,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
// amend properties to mimick full app
|
||||
data.applinks.forEach(function (applink) {
|
||||
applink.type = APP_TYPES.LINK;
|
||||
applink.fqdn = new URL(applink.upstreamUri).hostname;
|
||||
applink.fqdn = applink.upstreamUri;
|
||||
applink.manifest = { addons: {}};
|
||||
applink.installationState = ISTATES.INSTALLED;
|
||||
applink.runState = RSTATES.RUNNING;
|
||||
@@ -2241,7 +2364,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
Client.prototype.updateApplink = function (id, data, callback) {
|
||||
post('/api/v1/applinks/' + id, data, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 202) return callback(new ClientError(status, data));
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
@@ -2336,6 +2459,15 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.setNotificationConfig = function (notificationConfig, callback) {
|
||||
post('/api/v1/profile/notification_config', { notificationConfig }, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.setProfileEmail = function (email, password, callback) {
|
||||
post('/api/v1/profile/email', { email: email, password: password }, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
@@ -2557,9 +2689,10 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
this.config(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
that.getUpdateInfo(function (error, info) { // note: non-admin users may get access denied for this
|
||||
if (!error) result.update = info.update; // attach update information to config object
|
||||
that.getUpdateInfo(function (error, info) {
|
||||
if (error) return callback(error);
|
||||
|
||||
result.update = info.update;
|
||||
that.setConfig(result);
|
||||
callback(null);
|
||||
});
|
||||
@@ -2727,8 +2860,10 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
Client.prototype.login = function () {
|
||||
this.setToken(null);
|
||||
|
||||
localStorage.setItem('redirectToHash', window.location.hash);
|
||||
|
||||
// start oidc flow
|
||||
window.location.href = this.apiOrigin + '/openid/auth?client_id=' + ('<%= apiOrigin %>' ? TOKEN_TYPES.ID_DEVELOPMENT : TOKEN_TYPES.ID_WEBADMIN) + '&scope=openid email profile&response_type=code token&redirect_uri=' + window.location.origin + '/authcallback.html';
|
||||
window.location.href = this.apiOrigin + '/openid/auth?client_id=' + (window.cloudronApiOrigin ? TOKEN_TYPES.ID_DEVELOPMENT : TOKEN_TYPES.ID_WEBADMIN) + '&scope=openid email profile&response_type=code token&redirect_uri=' + window.location.origin + '/authcallback.html';
|
||||
};
|
||||
|
||||
Client.prototype.logout = function () {
|
||||
@@ -3030,18 +3165,18 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.getSolrConfig = function (callback) {
|
||||
Client.prototype.getFtsConfig = function (callback) {
|
||||
var config = {};
|
||||
|
||||
get('/api/v1/mailserver/solr_config', config, function (error, data, status) {
|
||||
get('/api/v1/mailserver/fts_config', config, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
callback(null, data);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.setSolrConfig = function (enabled, callback) {
|
||||
post('/api/v1/mailserver/solr_config', { enabled: enabled }, null, function (error, data, status) {
|
||||
Client.prototype.setFtsConfig = function (state, callback) {
|
||||
post('/api/v1/mailserver/fts_config', { enable: state }, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
@@ -3060,7 +3195,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
};
|
||||
|
||||
Client.prototype.setSpamAcl = function (acl, callback) {
|
||||
post('/api/v1/mailserver/spam_acl', { whitelist: acl.whitelist, blacklist: acl.blacklist }, null, function (error, data, status) {
|
||||
post('/api/v1/mailserver/spam_acl', { allowlist: acl.allowlist, blocklist: acl.blocklist }, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
@@ -3438,8 +3573,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
mountOptions: mountOptions
|
||||
};
|
||||
|
||||
console.log('---update', data)
|
||||
|
||||
post('/api/v1/volumes/' + volumeId, data, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
@@ -3482,8 +3615,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
// basically the user has not setup appstore account yet
|
||||
if (!subscription.plan) return window.location.href = '/#/appstore';
|
||||
|
||||
if (subscription.plan.id === 'free') window.open(this.getConfig().consoleServerOrigin + '/#/subscription_setup/' + subscription.cloudronId + '?email=' + subscription.emailEncoded, '_blank');
|
||||
else window.open(this.getConfig().consoleServerOrigin + '/#/cloudron/' + subscription.cloudronId + '?email=' + subscription.emailEncoded, '_blank');
|
||||
window.open(this.getConfig().consoleServerOrigin + '/#/cloudron/' + subscription.cloudronId + '?email=' + subscription.emailEncoded, '_blank');
|
||||
};
|
||||
|
||||
Client.prototype.getAppstoreAppByIdAndVersion = function (appId, version, callback) {
|
||||
@@ -3593,10 +3725,18 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
var ACTION_APP_STOP = 'app.stop';
|
||||
var ACTION_APP_RESTART = 'app.restart';
|
||||
|
||||
var ACTION_ARCHIVES_ADD = 'archives.add';
|
||||
var ACTION_ARCHIVES_DEL = 'archives.del';
|
||||
|
||||
var ACTION_BACKUP_FINISH = 'backup.finish';
|
||||
var ACTION_BACKUP_START = 'backup.start';
|
||||
var ACTION_BACKUP_CLEANUP_START = 'backup.cleanup.start';
|
||||
var ACTION_BACKUP_CLEANUP_FINISH = 'backup.cleanup.finish';
|
||||
|
||||
var ACTION_BRANDING_AVATAR = 'branding.avatar';
|
||||
var ACTION_BRANDING_NAME = 'branding.name';
|
||||
var ACTION_BRANDING_FOOTER = 'branding.footer';
|
||||
|
||||
var ACTION_CERTIFICATE_NEW = 'certificate.new';
|
||||
var ACTION_CERTIFICATE_RENEWAL = 'certificate.renew';
|
||||
var ACTION_CERTIFICATE_CLEANUP = 'certificate.cleanup';
|
||||
@@ -3611,6 +3751,11 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
|
||||
var ACTION_EXTERNAL_LDAP_CONFIGURE = 'externalldap.configure';
|
||||
|
||||
var ACTION_GROUP_ADD = 'group.add';
|
||||
var ACTION_GROUP_UPDATE = 'group.update';
|
||||
var ACTION_GROUP_REMOVE = 'group.remove';
|
||||
var ACTION_GROUP_MEMBERSHIP = 'group.membership';
|
||||
|
||||
var ACTION_INSTALL_FINISH = 'cloudron.install.finish';
|
||||
|
||||
var ACTION_START = 'cloudron.start';
|
||||
@@ -3627,6 +3772,8 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
var ACTION_USER_UPDATE = 'user.update';
|
||||
var ACTION_USER_TRANSFER = 'user.transfer';
|
||||
|
||||
var ACTION_USER_DIRECTORY_PROFILE_CONFIG_UPDATE = 'userdirectory.profileconfig.update';
|
||||
|
||||
var ACTION_MAIL_LOCATION = 'mail.location';
|
||||
var ACTION_MAIL_ENABLED = 'mail.enabled';
|
||||
var ACTION_MAIL_DISABLED = 'mail.disabled';
|
||||
@@ -3658,6 +3805,11 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
return pre + (app.label || app.fqdn || app.subdomain) + ' (' + app.manifest.title + ') ';
|
||||
}
|
||||
|
||||
function eventBy() {
|
||||
if (eventLog.source && eventLog.source.username) return ' by ' + eventLog.source.username;
|
||||
return '';
|
||||
}
|
||||
|
||||
switch (eventLog.action) {
|
||||
case ACTION_ACTIVATE:
|
||||
return 'Cloudron was activated';
|
||||
@@ -3672,24 +3824,22 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
if (!data.app) return '';
|
||||
app = data.app;
|
||||
|
||||
var q = function (x) {
|
||||
return '"' + x + '"';
|
||||
};
|
||||
|
||||
if ('accessRestriction' in data) { // since it can be null
|
||||
return 'Access restriction ' + appName('of', app) + ' was changed';
|
||||
} else if ('operators' in data) {
|
||||
return 'Operators ' + appName('of', app) + ' was changed';
|
||||
} else if (data.label) {
|
||||
return 'Label ' + appName('of', app) + ' was set to ' + q(data.label);
|
||||
return `Label ${appName('of', app)} was set to ${data.label}`;
|
||||
} else if (data.tags) {
|
||||
return 'Tags ' + appName('of', app) + ' was set to ' + q(data.tags.join(','));
|
||||
return `Tags ${appName('of', app)} was set to ${data.tags.join(', ')}`;
|
||||
} else if (data.icon) {
|
||||
return 'Icon ' + appName('of', app) + ' was changed';
|
||||
} else if (data.memoryLimit) {
|
||||
return 'Memory limit ' + appName('of', app) + ' was set to ' + data.memoryLimit;
|
||||
} else if (data.cpuShares) {
|
||||
return 'Memory limit ' + appName('of', app) + ' was set to ' + prettyBinarySize(data.memoryLimit);
|
||||
} else if (data.cpuShares) { // replaced by cpuQuota in 8.0
|
||||
return 'CPU shares ' + appName('of', app) + ' was set to ' + Math.round((data.cpuShares * 100)/1024) + '%';
|
||||
} else if (data.cpuQuota) {
|
||||
return 'CPU quota ' + appName('of', app) + ' was set to ' + data.cpuQuota + '%';
|
||||
} else if (data.env) {
|
||||
return 'Env vars ' + appName('of', app) + ' was changed';
|
||||
} else if ('debugMode' in data) { // since it can be null
|
||||
@@ -3699,9 +3849,9 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
return appName('', app, 'App') + ' was taken out of repair mode';
|
||||
}
|
||||
} else if ('enableBackup' in data) {
|
||||
return 'Automatic backups ' + appName('of', app) + ' were ' + (data.enableBackup ? 'enabled' : 'disabled');
|
||||
return 'Automatic backups ' + appName('of', app) + ' was ' + (data.enableBackup ? 'enabled' : 'disabled');
|
||||
} else if ('enableAutomaticUpdate' in data) {
|
||||
return 'Automatic updates ' + appName('of', app) + ' were ' + (data.enableAutomaticUpdate ? 'enabled' : 'disabled');
|
||||
return 'Automatic updates ' + appName('of', app) + ' was ' + (data.enableAutomaticUpdate ? 'enabled' : 'disabled');
|
||||
} else if ('reverseProxyConfig' in data) {
|
||||
return 'Reverse proxy configuration ' + appName('of', app) + ' was updated';
|
||||
} else if ('upstreamUri' in data) {
|
||||
@@ -3736,11 +3886,11 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
} else {
|
||||
return 'Icon ' + appName('of', app) + ' was reset';
|
||||
}
|
||||
} else if (('mailboxName' in data) && data.mailboxName !== data.app.mailboxName) {
|
||||
} else if ('mailboxName' in data) {
|
||||
if (data.mailboxName) {
|
||||
return 'Mailbox ' + appName('of', app) + ' was set to ' + q(data.mailboxName);
|
||||
return `Mailbox ${appName('of', app)} was set to ${data.mailboxDisplayName || '' } ${data.mailboxName}@${data.mailboxDomain}`;
|
||||
} else {
|
||||
return 'Mailbox ' + appName('of', app) + ' was reset';
|
||||
return 'Mailbox ' + appName('of', app) + ' was disabled';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3749,7 +3899,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
|
||||
case ACTION_APP_INSTALL:
|
||||
if (!data.app) return '';
|
||||
return data.app.manifest.title + ' (package v' + data.app.manifest.version + ') was installed ' + appName('at', data.app);
|
||||
return data.app.manifest.title + ' (package v' + data.app.manifest.version + ') was installed ' + appName('at', data.app) + eventBy();
|
||||
|
||||
case ACTION_APP_RESTORE:
|
||||
if (!data.app) return '';
|
||||
@@ -3833,6 +3983,12 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
if (!data.app) return '';
|
||||
return appName('', data.app, 'App') + ' was restarted';
|
||||
|
||||
case ACTION_ARCHIVES_ADD:
|
||||
return 'Backup ' + data.backupId + ' added to archive';
|
||||
|
||||
case ACTION_ARCHIVES_DEL:
|
||||
return 'Backup ' + data.backupId + ' deleted from archive';
|
||||
|
||||
case ACTION_BACKUP_START:
|
||||
return 'Backup started';
|
||||
|
||||
@@ -3849,6 +4005,15 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
case ACTION_BACKUP_CLEANUP_FINISH:
|
||||
return data.errorMessage ? 'Backup cleaner errored: ' + data.errorMessage : 'Backup cleaner removed ' + (data.removedBoxBackupPaths ? data.removedBoxBackupPaths.length : '0') + ' backups';
|
||||
|
||||
case ACTION_BRANDING_AVATAR:
|
||||
return 'Cloudron Avatar Changed';
|
||||
|
||||
case ACTION_BRANDING_NAME:
|
||||
return 'Cloudron Name set to ' + data.name;
|
||||
|
||||
case ACTION_BRANDING_FOOTER:
|
||||
return 'Cloudron Footer set to ' + data.footer;
|
||||
|
||||
case ACTION_CERTIFICATE_NEW:
|
||||
return 'Certificate install for ' + data.domain + (errorMessage ? ' failed' : ' succeeded');
|
||||
|
||||
@@ -3884,6 +4049,18 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
return 'External Directory set to ' + data.config.url + ' (' + data.config.provider + ')';
|
||||
}
|
||||
|
||||
case ACTION_GROUP_ADD:
|
||||
return 'Group ' + data.name + ' was added';
|
||||
|
||||
case ACTION_GROUP_UPDATE:
|
||||
return 'Group name changed from ' + data.oldName + ' to ' + data.group.name;
|
||||
|
||||
case ACTION_GROUP_REMOVE:
|
||||
return 'Group ' + data.group.name + ' was removed';
|
||||
|
||||
case ACTION_GROUP_MEMBERSHIP:
|
||||
return 'Group membership of ' + data.group.name + ' changed. Now was ' + data.userIds.length + ' member(s).';
|
||||
|
||||
case ACTION_INSTALL_FINISH:
|
||||
return 'Cloudron version ' + data.version + ' installed';
|
||||
|
||||
@@ -3955,9 +4132,11 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
case ACTION_USER_LOGIN:
|
||||
if (data.mailboxId) {
|
||||
return 'User ' + (data.user ? data.user.username : data.userId) + ' logged in to mailbox ' + data.mailboxId;
|
||||
} else {
|
||||
} else if (data.appId) {
|
||||
app = this.getCachedAppSync(data.appId);
|
||||
return 'User ' + (data.user ? data.user.username : data.userId) + ' logged in to ' + (app ? app.fqdn : data.appId);
|
||||
} else { // can happen with directoryserver
|
||||
return 'User ' + (data.user ? data.user.username : data.userId) + ' authenticated';
|
||||
}
|
||||
|
||||
case ACTION_USER_LOGIN_GHOST:
|
||||
@@ -3966,6 +4145,9 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
case ACTION_USER_LOGOUT:
|
||||
return 'User ' + (data.user ? data.user.username : data.userId) + ' logged out';
|
||||
|
||||
case ACTION_USER_DIRECTORY_PROFILE_CONFIG_UPDATE:
|
||||
return 'User directory profile config updated. Mandatory 2FA: ' + (data.config.mandatory2FA) + ' Lock profiles: ' + (data.config.lockUserProfiles);
|
||||
|
||||
case ACTION_DYNDNS_UPDATE: {
|
||||
details = data.errorMessage ? 'Error updating DNS. ' : 'Updated DNS. ';
|
||||
if (data.fromIpv4 !== data.toIpv4) details += 'From IPv4 ' + data.fromIpv4 + ' to ' + data.toIpv4 + '. ';
|
||||
@@ -4001,8 +4183,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
if (source.appId) {
|
||||
var app = this.getCachedAppSync(source.appId);
|
||||
line += ' - ' + (app ? app.fqdn : source.appId);
|
||||
} else if (source.ip) {
|
||||
line += ' - ' + source.ip;
|
||||
}
|
||||
|
||||
return line;
|
||||
@@ -1,8 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular:false */
|
||||
/* global $:false */
|
||||
/* global async */
|
||||
/* global $, async, angular, redirectIfNeeded */
|
||||
/* global ERROR,ISTATES,HSTATES,RSTATES,APP_TYPES,NOTIFICATION_TYPES */
|
||||
|
||||
// deal with accessToken in the query, this is passed for example on password reset and account setup upon invite
|
||||
@@ -19,7 +17,7 @@ if (search.accessToken) {
|
||||
}
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'ngFitText', 'ngRoute', 'ngAnimate', 'ngSanitize', 'angular-md5', 'base64', 'slick', 'ui-notification', 'ui.bootstrap', 'ui.bootstrap-slider', 'ngTld', 'ui.multiselect']);
|
||||
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'ngFitText', 'ngRoute', 'ngAnimate', 'ngSanitize', 'angular-md5', 'base64', 'slick', 'ui-notification', 'ui.bootstrap', 'ui.multiselect']);
|
||||
|
||||
app.config(['NotificationProvider', function (NotificationProvider) {
|
||||
NotificationProvider.setOptions({
|
||||
@@ -51,9 +49,9 @@ app.config(['$routeProvider', function ($routeProvider) {
|
||||
}).when('/users', {
|
||||
controller: 'UsersController',
|
||||
templateUrl: 'views/users.html?<%= revision %>'
|
||||
}).when('/usersettings', {
|
||||
}).when('/user-directory', {
|
||||
controller: 'UserSettingsController',
|
||||
templateUrl: 'views/user-settings.html?<%= revision %>'
|
||||
templateUrl: 'views/user-directory.html?<%= revision %>'
|
||||
}).when('/app/:appId/:view?', {
|
||||
controller: 'AppController',
|
||||
templateUrl: 'views/app.html?<%= revision %>'
|
||||
@@ -97,7 +95,7 @@ app.config(['$routeProvider', function ($routeProvider) {
|
||||
controller: 'NotificationsController',
|
||||
templateUrl: 'views/notifications.html?<%= revision %>'
|
||||
}).when('/oidc', {
|
||||
redirectTo: '/usersettings'
|
||||
redirectTo: '/user-directory'
|
||||
}).when('/settings', {
|
||||
controller: 'SettingsController',
|
||||
templateUrl: 'views/settings.html?<%= revision %>'
|
||||
@@ -122,16 +120,16 @@ app.config(['$routeProvider', function ($routeProvider) {
|
||||
app.filter('notificationTypeToColor', function () {
|
||||
return function (n) {
|
||||
switch (n.type) {
|
||||
case NOTIFICATION_TYPES.ALERT_REBOOT:
|
||||
case NOTIFICATION_TYPES.ALERT_APP_OOM:
|
||||
case NOTIFICATION_TYPES.ALERT_MAIL_STATUS:
|
||||
case NOTIFICATION_TYPES.ALERT_CERTIFICATE_RENEWAL_FAILED:
|
||||
case NOTIFICATION_TYPES.ALERT_DISK_SPACE:
|
||||
case NOTIFICATION_TYPES.ALERT_BACKUP_CONFIG:
|
||||
case NOTIFICATION_TYPES.ALERT_BACKUP_FAILED:
|
||||
case NOTIFICATION_TYPES.REBOOT:
|
||||
case NOTIFICATION_TYPES.APP_OOM:
|
||||
case NOTIFICATION_TYPES.MAIL_STATUS:
|
||||
case NOTIFICATION_TYPES.CERTIFICATE_RENEWAL_FAILED:
|
||||
case NOTIFICATION_TYPES.DISK_SPACE:
|
||||
case NOTIFICATION_TYPES.BACKUP_CONFIG:
|
||||
case NOTIFICATION_TYPES.BACKUP_FAILED:
|
||||
return '#ff4c4c';
|
||||
case NOTIFICATION_TYPES.ALERT_BOX_UPDATE:
|
||||
case NOTIFICATION_TYPES.ALERT_MANUAL_APP_UPDATE:
|
||||
case NOTIFICATION_TYPES.BOX_UPDATE:
|
||||
case NOTIFICATION_TYPES.MANUAL_APP_UPDATE:
|
||||
return '#f0ad4e';
|
||||
default:
|
||||
return '#2196f3';
|
||||
@@ -305,6 +303,36 @@ app.filter('installationActive', function () {
|
||||
};
|
||||
});
|
||||
|
||||
// color indicator in app list
|
||||
app.filter('installationStateClass', function () {
|
||||
const ERROR_CLASS = 'status-error';
|
||||
const BUSY_CLASS = 'status-starting fa-beat-fade';
|
||||
const INACTIVE_CLASS = 'status-inactive';
|
||||
const ACTIVE_CLASS = 'status-active';
|
||||
|
||||
return function(app) {
|
||||
if (!app) return '';
|
||||
|
||||
switch (app.installationState) {
|
||||
case ISTATES.ERROR: return ERROR_CLASS;
|
||||
case ISTATES.INSTALLED: {
|
||||
if (app.debugMode) {
|
||||
return INACTIVE_CLASS;
|
||||
} else {
|
||||
if (app.runState === RSTATES.RUNNING) {
|
||||
if (!app.health) return BUSY_CLASS; // no data yet
|
||||
if (app.type === APP_TYPES.LINK || app.health === HSTATES.HEALTHY) return ACTIVE_CLASS;
|
||||
return ERROR_CLASS; // dead/exit/unhealthy
|
||||
} else {
|
||||
return INACTIVE_CLASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
default: return BUSY_CLASS;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// this appears in the app grid
|
||||
app.filter('installationStateLabel', function () {
|
||||
return function(app) {
|
||||
@@ -399,7 +427,7 @@ app.filter('errorSuggestion', function () {
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('readyToUpdate', function () {
|
||||
app.filter('canUpdate', function () {
|
||||
return function (apps) {
|
||||
return apps.every(function (app) {
|
||||
return (app.installationState === ISTATES.ERROR) || (app.installationState === ISTATES.INSTALLED);
|
||||
@@ -643,6 +671,10 @@ app.controller('MainController', ['$scope', '$route', '$timeout', '$location', '
|
||||
$scope.hideNavBarActions = $location.path() === '/logs';
|
||||
$scope.backgroundImageUrl = '';
|
||||
|
||||
$scope.closeNavbar = function () {
|
||||
$('.navbar-collapse').collapse('hide');
|
||||
};
|
||||
|
||||
$scope.reboot = {
|
||||
busy: false,
|
||||
|
||||
@@ -706,7 +738,10 @@ app.controller('MainController', ['$scope', '$route', '$timeout', '$location', '
|
||||
};
|
||||
|
||||
function redirectOnMandatory2FA() {
|
||||
if (Client.getConfig().mandatory2FA && !Client.getUserInfo().twoFactorAuthenticationEnabled) {
|
||||
if (Client.getConfig().mandatory2FA) {
|
||||
if (Client.getUserInfo().twoFactorAuthenticationEnabled) return; // user already has 2fa
|
||||
if (Client.getUserInfo().source && $scope.config.external2FA) return; // 2fa is external
|
||||
|
||||
$location.path('/profile').search({ setup2fa: true });
|
||||
}
|
||||
}
|
||||
@@ -745,35 +780,12 @@ app.controller('MainController', ['$scope', '$route', '$timeout', '$location', '
|
||||
});
|
||||
}
|
||||
|
||||
function redirectIfNeeded(status) {
|
||||
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' + window.location.search;
|
||||
} else if (status.adminFqdn) {
|
||||
window.location.href = 'https://' + status.adminFqdn + '/setup.html' + (window.location.search);
|
||||
} else {
|
||||
window.location.href = '/setupdns.html' + window.location.search;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// support local development with localhost check
|
||||
if (window.location.hostname !== status.adminFqdn && window.location.hostname !== 'localhost' && !window.location.hostname.startsWith('192.')) {
|
||||
// user is accessing by IP or by the old admin location (pre-migration)
|
||||
window.location.href = '/setupdns.html' + window.location.search;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// this loads the very first thing when accessing via IP or domain
|
||||
function init() {
|
||||
Client.getProvisionStatus(function (error, status) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
if (redirectIfNeeded(status)) return;
|
||||
if (redirectIfNeeded(status, 'dashboard')) return; // we got redirected...
|
||||
|
||||
// check version and force reload if needed
|
||||
if (!localStorage.version) {
|
||||
@@ -59,7 +59,7 @@ app.filter('tr', translateFilterFactory);
|
||||
|
||||
app.controller('PasswordResetController', ['$scope', '$translate', '$http', function ($scope, $translate, $http) {
|
||||
// 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.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
const search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
$scope.initialized = false;
|
||||
$scope.mode = '';
|
||||
@@ -72,7 +72,8 @@ app.controller('PasswordResetController', ['$scope', '$translate', '$http', func
|
||||
$scope.passwordResetIdentifier = '';
|
||||
$scope.newPassword = '';
|
||||
$scope.newPasswordRepeat = '';
|
||||
var API_ORIGIN = '<%= apiOrigin %>' || window.location.origin;
|
||||
|
||||
const API_ORIGIN = window.cloudronApiOrigin || window.location.origin;
|
||||
|
||||
$scope.onPasswordReset = function () {
|
||||
$scope.busy = true;
|
||||
@@ -1,17 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
/* global $, angular, tld, SECRET_PLACEHOLDER, STORAGE_PROVIDERS, BACKUP_FORMATS */
|
||||
/* global REGIONS_S3, REGIONS_WASABI, REGIONS_DIGITALOCEAN, REGIONS_EXOSCALE, REGIONS_SCALEWAY, REGIONS_LINODE, REGIONS_OVH, REGIONS_IONOS, REGIONS_UPCLOUD, REGIONS_VULTR, REGIONS_CONTABO */
|
||||
/* global $, angular, SECRET_PLACEHOLDER, STORAGE_PROVIDERS, BACKUP_FORMATS, redirectIfNeeded */
|
||||
/* global REGIONS_S3, REGIONS_WASABI, REGIONS_DIGITALOCEAN, REGIONS_EXOSCALE, REGIONS_SCALEWAY, REGIONS_LINODE, REGIONS_OVH, REGIONS_IONOS, REGIONS_UPCLOUD, REGIONS_VULTR, REGIONS_CONTABO, REGIONS_HETZNER */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']);
|
||||
|
||||
app.filter('zoneName', function () {
|
||||
return function (domain) {
|
||||
return tld.getDomain(domain);
|
||||
};
|
||||
});
|
||||
|
||||
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; }, {});
|
||||
|
||||
@@ -51,7 +45,7 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
|
||||
password: '',
|
||||
diskPath: '',
|
||||
user: '',
|
||||
seal: false,
|
||||
seal: true,
|
||||
port: 22,
|
||||
privateKey: ''
|
||||
};
|
||||
@@ -61,30 +55,29 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
|
||||
$scope.mountOptions.diskPath = '/dev/disk/by-uuid/' + newValue.uuid;
|
||||
});
|
||||
|
||||
$scope.sysinfo = {
|
||||
$scope.ipv4Config = {
|
||||
provider: 'generic',
|
||||
ipv4: '',
|
||||
ip: '',
|
||||
ifname: ''
|
||||
};
|
||||
|
||||
$scope.sysinfoProvider = [
|
||||
$scope.ipv6Config = {
|
||||
provider: 'generic',
|
||||
ip: '',
|
||||
ifname: ''
|
||||
};
|
||||
|
||||
$scope.ipProviders = [
|
||||
{ name: 'Disabled', value: 'noop' },
|
||||
{ name: 'Public IP', value: 'generic' },
|
||||
{ name: 'Static IP Address', value: 'fixed' },
|
||||
{ name: 'Network Interface', value: 'network-interface' }
|
||||
];
|
||||
|
||||
$scope.prettySysinfoProviderName = function (provider) {
|
||||
switch (provider) {
|
||||
case 'generic': return 'Public IP';
|
||||
case 'fixed': return 'Static IP Address';
|
||||
case 'network-interface': return 'Network Interface';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.s3Regions = REGIONS_S3;
|
||||
$scope.wasabiRegions = REGIONS_WASABI;
|
||||
$scope.doSpacesRegions = REGIONS_DIGITALOCEAN;
|
||||
$scope.hetznerRegions = REGIONS_HETZNER;
|
||||
$scope.exoscaleSosRegions = REGIONS_EXOSCALE;
|
||||
$scope.scalewayRegions = REGIONS_SCALEWAY;
|
||||
$scope.linodeRegions = REGIONS_LINODE;
|
||||
@@ -100,7 +93,7 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
|
||||
|
||||
$scope.s3like = function (provider) {
|
||||
return provider === 's3' || provider === 'minio' || provider === 's3-v4-compat' || provider === 'exoscale-sos'
|
||||
|| provider === 'digitalocean-spaces' || provider === 'wasabi' || provider === 'scaleway-objectstorage'
|
||||
|| provider === 'digitalocean-spaces' || provider === 'wasabi' || provider === 'scaleway-objectstorage' || provider === 'hetzner-objectstorage'
|
||||
|| provider === 'linode-objectstorage' || provider === 'ovh-objectstorage' || provider === 'backblaze-b2' || provider === 'cloudflare-r2'
|
||||
|| provider === 'ionos-objectstorage' || provider === 'vultr-objectstorage' || provider === 'upcloud-objectstorage' || provider === 'idrive-e2'
|
||||
|| provider === 'contabo-objectstorage';
|
||||
@@ -170,6 +163,9 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'digitalocean-spaces') {
|
||||
backupConfig.region = 'us-east-1';
|
||||
} else if (backupConfig.provider === 'hetzner-objectstorage') {
|
||||
backupConfig.region = 'us-east-1';
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
}
|
||||
} else if (backupConfig.provider === 'gcs') {
|
||||
backupConfig.bucket = $scope.bucket;
|
||||
@@ -239,16 +235,17 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
|
||||
return;
|
||||
}
|
||||
|
||||
var sysinfoConfig = {
|
||||
provider: $scope.sysinfo.provider
|
||||
var data = {
|
||||
backupConfig: backupConfig,
|
||||
remotePath: $scope.remotePath.replace(/\.tar\.gz(\.enc)?$/, ''),
|
||||
version: version ? version[1] : '',
|
||||
ipv4Config: $scope.ipv4Config,
|
||||
ipv6Config: $scope.ipv6Config,
|
||||
skipDnsSetup: $scope.skipDnsSetup,
|
||||
setupToken: $scope.setupToken
|
||||
};
|
||||
if ($scope.sysinfo.provider === 'fixed') {
|
||||
sysinfoConfig.ip = $scope.sysinfo.ipv4;
|
||||
} else if ($scope.sysinfo.provider === 'network-interface') {
|
||||
sysinfoConfig.ifname = $scope.sysinfo.ifname;
|
||||
}
|
||||
|
||||
Client.restore(backupConfig, $scope.remotePath.replace(/\.tar\.gz(\.enc)?$/, ''), version ? version[1] : '', sysinfoConfig, $scope.skipDnsSetup, $scope.setupToken, function (error) {
|
||||
Client.restore(data, function (error) {
|
||||
$scope.busy = false;
|
||||
|
||||
if (error) {
|
||||
@@ -303,7 +300,7 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
|
||||
$scope.busy = false;
|
||||
$scope.error.generic = status.restore.errorMessage;
|
||||
} else { // restore worked, redirect to admin page
|
||||
window.location.href = '/';
|
||||
window.location.href = 'https://' + status.adminFqdn + '/';
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -362,14 +359,11 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
|
||||
Client.getProvisionStatus(function (error, status) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
if (redirectIfNeeded(status, 'restore')) return; // redirected to some other view...
|
||||
|
||||
if (status.restore.active) return waitForRestore();
|
||||
|
||||
if (status.restore.errorMessage) $scope.error.generic = status.restore.errorMessage;
|
||||
|
||||
if (status.activated) {
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
if (status.restore.errorMessage) $scope.error.generic = status.restore.errorMessage; // any previous restore error
|
||||
|
||||
Client.getProvisionBlockDevices(function (error, result) {
|
||||
if (error) {
|
||||
@@ -388,7 +382,13 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
|
||||
|
||||
$scope.instanceId = search.instanceId;
|
||||
$scope.setupToken = search.setupToken;
|
||||
$scope.initialized = true;
|
||||
|
||||
Client.detectIp(function (error, ip) { // this is never supposed to error
|
||||
if (!error) $scope.ipv4Config.provider = ip.ipv4 ? 'generic' : 'noop';
|
||||
if (!error) $scope.ipv6Config.provider = ip.ipv6 ? 'generic' : 'noop';
|
||||
|
||||
$scope.initialized = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,16 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
/* global $, tld, angular, Clipboard, ENDPOINTS_OVH */
|
||||
/* global $, angular, Clipboard, ENDPOINTS_OVH, redirectIfNeeded */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']);
|
||||
|
||||
app.filter('zoneName', function () {
|
||||
return function (domain) {
|
||||
return tld.getDomain(domain);
|
||||
};
|
||||
});
|
||||
|
||||
app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', function ($scope, $http, $timeout, 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; }, {});
|
||||
|
||||
@@ -19,12 +13,11 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
$scope.provider = '';
|
||||
$scope.showDNSSetup = false;
|
||||
$scope.instanceId = '';
|
||||
$scope.isDomain = false;
|
||||
$scope.isSubdomain = false;
|
||||
$scope.advancedVisible = false;
|
||||
$scope.clipboardDone = false;
|
||||
$scope.search = window.location.search;
|
||||
$scope.setupToken = '';
|
||||
$scope.taskMinutesActive = null;
|
||||
|
||||
$scope.tlsProvider = [
|
||||
{ name: 'Let\'s Encrypt Prod', value: 'letsencrypt-prod' },
|
||||
@@ -34,27 +27,25 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
{ name: 'Self-Signed', value: 'fallback' }, // this is not 'Custom' because we don't allow user to upload certs during setup phase
|
||||
];
|
||||
|
||||
$scope.sysinfo = {
|
||||
$scope.ipv4Config = {
|
||||
provider: 'generic',
|
||||
ipv4: '',
|
||||
ip: '',
|
||||
ifname: ''
|
||||
};
|
||||
|
||||
$scope.sysinfoProvider = [
|
||||
$scope.ipv6Config = {
|
||||
provider: 'generic',
|
||||
ip: '',
|
||||
ifname: ''
|
||||
};
|
||||
|
||||
$scope.ipProviders = [
|
||||
{ name: 'Disabled', value: 'noop' },
|
||||
{ name: 'Public IP', value: 'generic' },
|
||||
{ name: 'Static IP Address', value: 'fixed' },
|
||||
{ name: 'Network Interface', value: 'network-interface' }
|
||||
];
|
||||
|
||||
$scope.prettySysinfoProviderName = function (provider) {
|
||||
switch (provider) {
|
||||
case 'generic': return 'Public IP';
|
||||
case 'fixed': return 'Static IP Address';
|
||||
case 'network-interface': return 'Network Interface';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.ovhEndpoints = ENDPOINTS_OVH;
|
||||
|
||||
$scope.needsPort80 = function (dnsProvider, tlsProvider) {
|
||||
@@ -65,24 +56,12 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
// If we migrate the api origin we have to poll the new location
|
||||
if (search.admin_fqdn) Client.apiOrigin = 'https://' + search.admin_fqdn;
|
||||
|
||||
$scope.$watch('dnsCredentials.domain', function (newVal) {
|
||||
if (!newVal) {
|
||||
$scope.isDomain = false;
|
||||
$scope.isSubdomain = false;
|
||||
} else if (!tld.getDomain(newVal) || newVal[newVal.length-1] === '.') {
|
||||
$scope.isDomain = false;
|
||||
$scope.isSubdomain = false;
|
||||
} else {
|
||||
$scope.isDomain = true;
|
||||
$scope.isSubdomain = tld.getDomain(newVal) !== newVal;
|
||||
}
|
||||
});
|
||||
|
||||
// keep in sync with domains.js
|
||||
$scope.dnsProvider = [
|
||||
{ name: 'AWS Route53', value: 'route53' },
|
||||
{ name: 'Bunny', value: 'bunny' },
|
||||
{ name: 'Cloudflare', value: 'cloudflare' },
|
||||
{ name: 'deSEC', value: 'desec' },
|
||||
{ name: 'DigitalOcean', value: 'digitalocean' },
|
||||
{ name: 'DNSimple', value: 'dnsimple' },
|
||||
{ name: 'Gandi LiveDNS', value: 'gandi' },
|
||||
@@ -119,6 +98,7 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
dnsimpleAccessToken: '',
|
||||
hetznerToken: '',
|
||||
vultrToken: '',
|
||||
deSecToken: '',
|
||||
nameComUsername: '',
|
||||
nameComToken: '',
|
||||
namecheapUsername: '',
|
||||
@@ -219,6 +199,8 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
config.token = $scope.dnsCredentials.hetznerToken;
|
||||
} else if (provider === 'vultr') {
|
||||
config.token = $scope.dnsCredentials.vultrToken;
|
||||
} else if (provider === 'desec') {
|
||||
config.token = $scope.dnsCredentials.deSecToken;
|
||||
} else if (provider === 'namecom') {
|
||||
config.username = $scope.dnsCredentials.nameComUsername;
|
||||
config.token = $scope.dnsCredentials.nameComToken;
|
||||
@@ -248,15 +230,6 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
tlsConfig.wildcard = true;
|
||||
}
|
||||
|
||||
var sysinfoConfig = {
|
||||
provider: $scope.sysinfo.provider
|
||||
};
|
||||
if ($scope.sysinfo.provider === 'fixed') {
|
||||
sysinfoConfig.ip = $scope.sysinfo.ipv4;
|
||||
} else if ($scope.sysinfo.provider === 'network-interface') {
|
||||
sysinfoConfig.ifname = $scope.sysinfo.ifname;
|
||||
}
|
||||
|
||||
var data = {
|
||||
domainConfig: {
|
||||
domain: $scope.dnsCredentials.domain,
|
||||
@@ -265,7 +238,8 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
config: config,
|
||||
tlsConfig: tlsConfig
|
||||
},
|
||||
ipv4Config: sysinfoConfig,
|
||||
ipv4Config: $scope.ipv4Config,
|
||||
ipv6Config: $scope.ipv6Config,
|
||||
providerToken: $scope.instanceId,
|
||||
setupToken: $scope.setupToken
|
||||
};
|
||||
@@ -299,28 +273,30 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
$scope.state = 'initialized';
|
||||
$scope.dnsCredentials.busy = false;
|
||||
} else { // proceed to activation
|
||||
window.location.href = 'https://' + status.adminFqdn + '/setup.html' + (window.location.search);
|
||||
window.location.href = 'https://' + status.adminFqdn + '/activation.html' + (window.location.search);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.message = status.setup.message;
|
||||
if (!error) {
|
||||
$scope.message = status.setup.message;
|
||||
$scope.taskMinutesActive = (new Date() - new Date(status.setup.startTime)) / 60000;
|
||||
}
|
||||
|
||||
setTimeout(waitForDnsSetup, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
function init() {
|
||||
Client.getProvisionStatus(function (error, status) {
|
||||
if (error) {
|
||||
// During domain migration, the box code restarts and can result in getStatus() failing temporarily
|
||||
console.error(error);
|
||||
$scope.state = 'waitingForBox';
|
||||
return $timeout(initialize, 3000);
|
||||
}
|
||||
$scope.state = 'waitingForBox';
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
// domain is currently like a lock flag
|
||||
if (status.adminFqdn) return waitForDnsSetup();
|
||||
if (redirectIfNeeded(status, 'setup')) return; // redirected to some other view...
|
||||
|
||||
if (status.setup.active) return waitForDnsSetup();
|
||||
|
||||
$scope.error.setup = status.setup.errorMessage; // show any previous error
|
||||
|
||||
if (status.provider === 'digitalocean' || status.provider === 'digitalocean-mp') {
|
||||
$scope.dnsCredentials.provider = 'digitalocean';
|
||||
@@ -331,15 +307,22 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
} else if (status.provider === 'gce') {
|
||||
$scope.dnsCredentials.provider = 'gcdns';
|
||||
} else if (status.provider === 'ami') {
|
||||
$scope.dnsCredentials.provider = 'route53';
|
||||
// aws marketplace made a policy change that they one cannot provide route53 IAM credentials
|
||||
$scope.dnsCredentials.provider = 'wildcard';
|
||||
}
|
||||
|
||||
$scope.instanceId = search.instanceId;
|
||||
$scope.setupToken = search.setupToken;
|
||||
$scope.provider = status.provider;
|
||||
$scope.state = 'initialized';
|
||||
|
||||
setTimeout(function () { $("[autofocus]:first").focus(); }, 100);
|
||||
Client.detectIp(function (error, ip) { // this is never supposed to error
|
||||
if (!error) $scope.ipv4Config.provider = ip.ipv4 ? 'generic' : 'noop';
|
||||
if (!error) $scope.ipv6Config.provider = ip.ipv6 ? 'generic' : 'noop';
|
||||
|
||||
$scope.state = 'initialized';
|
||||
|
||||
setTimeout(function () { $("[autofocus]:first").focus(); }, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -349,5 +332,5 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
|
||||
$timeout(function () { $scope.clipboardDone = false; }, 5000);
|
||||
});
|
||||
|
||||
initialize();
|
||||
init();
|
||||
}]);
|
||||
@@ -62,15 +62,16 @@ app.filter('tr', translateFilterFactory);
|
||||
|
||||
app.controller('SetupAccountController', ['$scope', '$translate', '$http', function ($scope, $translate, $http) {
|
||||
// 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.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
const search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
var API_ORIGIN = '<%= apiOrigin %>' || window.location.origin;
|
||||
const API_ORIGIN = window.cloudronApiOrigin || window.location.origin;
|
||||
|
||||
$scope.initialized = false;
|
||||
$scope.busy = false;
|
||||
$scope.error = null;
|
||||
$scope.view = 'setup';
|
||||
$scope.branding = null;
|
||||
$scope.dashboardUrl = '';
|
||||
|
||||
$scope.profileLocked = !!search.profileLocked;
|
||||
$scope.existingUsername = !!search.username;
|
||||
@@ -122,8 +123,10 @@ app.controller('SetupAccountController', ['$scope', '$translate', '$http', funct
|
||||
$http.post(API_ORIGIN + '/api/v1/auth/setup_account', data).success(function (data, status) {
|
||||
if (status !== 201) return error(data, status);
|
||||
|
||||
// set token to autologin
|
||||
localStorage.token = data.accessToken;
|
||||
// set token to autologin on first oidc flow
|
||||
localStorage.cloudronFirstTimeToken = data.accessToken;
|
||||
|
||||
$scope.dashboardUrl = '/openid/auth?client_id=cid-webadmin&scope=openid email profile&response_type=code token&redirect_uri=' + window.location.origin + '/authcallback.html';
|
||||
|
||||
$scope.view = 'done';
|
||||
}).error(error);
|
||||
@@ -1,5 +1,7 @@
|
||||
/* This file contains helpers which should not be part of client.js */
|
||||
|
||||
/* global angular */
|
||||
|
||||
angular.module('Application').directive('passwordReveal', function () {
|
||||
var svgEye = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye" class="svg-inline--fa fa-eye fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"></path></svg>';
|
||||
var svgEyeSlash = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye-slash" class="svg-inline--fa fa-eye-slash fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"></path></svg>';
|
||||
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |