Compare commits
2109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 800a7e26e9 | |||
| 1bc9dc30f6 | |||
| 7d538ee1b8 | |||
| ac5f4cca19 | |||
| 54a5d5b9aa | |||
| 5c4ec5afc0 | |||
| 5bd6001f95 | |||
| 0fb8914b67 | |||
| 1f6ac49686 | |||
| 42887fb1d9 | |||
| f14a7808cb | |||
| a781a46f13 | |||
| 6941a12314 | |||
| f0e70a97bc | |||
| c59e3ef4ae | |||
| 2bfdc7c1ac | |||
| d831e7d765 | |||
| fe8ef5b922 | |||
| 2c150eee33 | |||
| a4d6bafe1a | |||
| 78017b8adb | |||
| ea822f66ca | |||
| a55adf12db | |||
| 84c016490c | |||
| bb7056d614 | |||
| 462b490d05 | |||
| 084050bb2f | |||
| 8d2ea7e736 | |||
| fe8d5b0d3e | |||
| de724319aa | |||
| ac91b417c3 | |||
| 229863d7ff | |||
| 8dcb3f2f85 | |||
| 15c8f84960 | |||
| f37dd03e4b | |||
| 82c97f7e1c | |||
| 91078f7a7e | |||
| d2775956e0 | |||
| 00b52fa3af | |||
| 1ac0ed3c18 | |||
| 6ec8246b46 | |||
| f5978a524d | |||
| 72030ee8fc | |||
| d6a4dd6965 | |||
| 8aa5dc85af | |||
| 5c7f99c0ee | |||
| 847cb91759 | |||
| 9e92d08261 | |||
| bf8e03aa0c | |||
| fcd05f3bb4 | |||
| a14dfc171d | |||
| b8b445eb24 | |||
| fbf4a53a1b | |||
| 0c7e810bd3 | |||
| 0502779a29 | |||
| 576d9ca894 | |||
| d8771509cd | |||
| b139749198 | |||
| bdcb5c502c | |||
| dc72df1dbd | |||
| 8be834d0c8 | |||
| c995454f69 | |||
| 854e0ebe3f | |||
| f01d2631dd | |||
| 60f8cdf3b4 | |||
| 8e5bf14623 | |||
| b063ebd6d7 | |||
| eb7d7a2d1b | |||
| f9ee088592 | |||
| 1f32d4b4dd | |||
| d3b4c2f394 | |||
| 41c00eda74 | |||
| 155af33b0c | |||
| b289146aeb | |||
| d2e32a4fd0 | |||
| 6631c95166 | |||
| 7adabcc203 | |||
| de35a935a6 | |||
| d3d668d930 | |||
| 1f60c6dd21 | |||
| 1431700642 | |||
| 12a1de56fd | |||
| 4267208f28 | |||
| 25d6ec157f | |||
| b2fc7df06d | |||
| c875371db9 | |||
| dd7818b960 | |||
| 0dd6446f3e | |||
| 579dc4959b | |||
| 27c3170c80 | |||
| 7b275008b5 | |||
| 2cfea229fd | |||
| e349193745 | |||
| 45d68c4da8 | |||
| 36d443fa36 | |||
| 8549663ed0 | |||
| 73b2feae59 | |||
| 0871b416a7 | |||
| 767bfba670 | |||
| 019cbeb617 | |||
| a7d8a8fd1f | |||
| f7e073c857 | |||
| 2fdb3668e2 | |||
| f1aee1d9a4 | |||
| 057f75ca5f | |||
| 314654f319 | |||
| 961959d361 | |||
| d603ea50e2 | |||
| 3b7bc6beba | |||
| e437168e87 | |||
| cf7d735c56 | |||
| b2a41cc4d5 | |||
| 5bc3cb6353 | |||
| e6024ac85b | |||
| 4f6fac2336 | |||
| 12feb68bf0 | |||
| d5d325bf4e | |||
| 47ae1443d1 | |||
| 63b51d2fca | |||
| 3547be3401 | |||
| a038028c3b | |||
| 40cc12e568 | |||
| 4a1a915add | |||
| 3096b67b76 | |||
| 3acceca32f | |||
| e8e05159d8 | |||
| ae0c7390f2 | |||
| 11f2acd702 | |||
| 1759e257c4 | |||
| 3e86c59607 | |||
| dfe5b6b1f2 | |||
| b0026eafb5 | |||
| 67519fb203 | |||
| 7f4a9d6016 | |||
| 317f6e77d4 | |||
| 5fcfd32f6c | |||
| 0fd056dff9 | |||
| 10cf8f1d0e | |||
| 67d95cda76 | |||
| 3ac66f9dd4 | |||
| 43e426ab9f | |||
| a5224258c3 | |||
| ab3a2911c3 | |||
| 1cdcab6047 | |||
| 2ad8ed5550 | |||
| 9226baa63c | |||
| b790d085bb | |||
| 933d5c9139 | |||
| 2c5fd7effa | |||
| f5a5bebae6 | |||
| f2316ec84e | |||
| 84165e5342 | |||
| f65dab114e | |||
| 4ad7504be6 | |||
| e9318d7f11 | |||
| 01d7d41c17 | |||
| f1fb5f2530 | |||
| ea28c10a39 | |||
| 6c84681f35 | |||
| edb3aea880 | |||
| f898925bc0 | |||
| f3be7aa763 | |||
| 71c52a87ec | |||
| 8c786f38ab | |||
| 6bc5826c86 | |||
| 90e36c4552 | |||
| f83bd2e3b7 | |||
| 367eb1b1e1 | |||
| 91e21d69db | |||
| 5bcfea161f | |||
| a9e0ee81ce | |||
| c37c513067 | |||
| c0972b3e14 | |||
| 1b91ae1ab3 | |||
| 4a462597fd | |||
| 505c71855e | |||
| 2ba0d716d1 | |||
| 35ab03c392 | |||
| ddb7551b92 | |||
| fdcc5d68a2 | |||
| ac7c32ad4c | |||
| e93898d2ec | |||
| 22fe27da9c | |||
| e31164140c | |||
| 7d53eeb7f5 | |||
| d3731ff339 | |||
| 02ba2fe59b | |||
| 55091cfe8d | |||
| f6e14a5420 | |||
| b38371400c | |||
| 0458fee326 | |||
| 15256ebbdf | |||
| 773435fb7f | |||
| 1a674a30ac | |||
| 491617e41c | |||
| 24fc480f7b | |||
| 677f11ba1a | |||
| aa7e307205 | |||
| 6ac914904e | |||
| 68dd1fbedb | |||
| 6869380500 | |||
| 3a0febe760 | |||
| 46d195557b | |||
| 548418ecfe | |||
| 28c0d63f89 | |||
| b3eb6ccde3 | |||
| 0e09df797b | |||
| 7fae0751b9 | |||
| e6cdd1ed7a | |||
| 9d8c079d37 | |||
| 9bb58b1649 | |||
| d732adf34b | |||
| 8718e7efd2 | |||
| b21d29098b | |||
| d010330b58 | |||
| 4562024e72 | |||
| 34043d5c97 | |||
| a468b6ff39 | |||
| 534aa7423e | |||
| be416fd335 | |||
| 43962c4a5a | |||
| 1b33b2c48c | |||
| efa4c09306 | |||
| 181b9cdee6 | |||
| a1c3cd272b | |||
| c67b4a4e51 | |||
| 72c97ba224 | |||
| ca96e40397 | |||
| 758daee0c9 | |||
| 37b6a2568d | |||
| 4ee2fd8b14 | |||
| 7706be3e2f | |||
| 16c2316183 | |||
| ce22100b02 | |||
| d2cc38c8ec | |||
| c0a31ccb55 | |||
| 8ddc9e3138 | |||
| 7a24c23bdd | |||
| 32f4b72d68 | |||
| 282df5c2e6 | |||
| 6713ba3798 | |||
| 355edda058 | |||
| b1141f8cbb | |||
| eaad527e32 | |||
| 810922de5e | |||
| da3414e3bc | |||
| 99a0c78fe4 | |||
| 444d3eeb7c | |||
| a3c00c5f75 | |||
| dfb465ef77 | |||
| b20107ad2a | |||
| 6977556984 | |||
| 676f25962d | |||
| 02fe971f0b | |||
| 0b310f849a | |||
| c9c00d2b9f | |||
| 910087e37a | |||
| 9d99794242 | |||
| 3218d7c64d | |||
| 3364e5c876 | |||
| a6355e1945 | |||
| a3a8e67ce2 | |||
| ac388e1daf | |||
| 625a55ddeb | |||
| 6c1b07a7f1 | |||
| b511b0c39a | |||
| 5b43de944d | |||
| e255f4158f | |||
| e0740f89d9 | |||
| a4605238aa | |||
| a4e680ee01 | |||
| edf7d80678 | |||
| 92e6e99bb0 | |||
| 73ed60059f | |||
| 78d8e0b5aa | |||
| 832eca2d9c | |||
| f69bdd1ac4 | |||
| 01f10baa18 | |||
| 23178b24c6 | |||
| 295aafddf0 | |||
| 398b978a5f | |||
| f59999f35f | |||
| e078bf2d2f | |||
| bab1da1ea5 | |||
| 593c9f133d | |||
| 8071eb2d93 | |||
| f44ff12a5f | |||
| 70953333d2 | |||
| e453be1c04 | |||
| 6ac2f8d9c3 | |||
| 06ca87e041 | |||
| 15d1521564 | |||
| 4e1396e7a7 | |||
| ab0f8fc081 | |||
| 0f29652b96 | |||
| 53de61330f | |||
| 2c52190966 | |||
| df66a0ff38 | |||
| 33ced1c16e | |||
| 0b3d3ceb48 | |||
| d385edf348 | |||
| 54cd9c7afa | |||
| 1ccb24018f | |||
| 6bd8e81eae | |||
| edcffd3417 | |||
| 1e599731b8 | |||
| f7c85d8aa2 | |||
| 75e6c8fad6 | |||
| f16170e0b5 | |||
| 74c1e71fe5 | |||
| 471dd08016 | |||
| f8e15acf89 | |||
| 703621d84e | |||
| 0893bf4aa0 | |||
| e44e9f4cf0 | |||
| 6cdbebf0d3 | |||
| 7b85106792 | |||
| cf2a90845b | |||
| 647209cf6d | |||
| 5c0d53b52c | |||
| 56c2b8798c | |||
| d4262e9fdc | |||
| 1902e8206d | |||
| 5918952afb | |||
| 55cd323159 | |||
| 249734f52a | |||
| 8530a62421 | |||
| 65f1260839 | |||
| fc8a679367 | |||
| cafacf4748 | |||
| 5c6ceb2307 | |||
| e05fef18a0 | |||
| 9c8aab6e29 | |||
| 38267a26c1 | |||
| 9e99dae864 | |||
| f9747fd1eb | |||
| bf47368f25 | |||
| 7b82950495 | |||
| 5dd5a20fc1 | |||
| 906e3f8cea | |||
| 44ee3c92b7 | |||
| ae55912176 | |||
| 703b47d916 | |||
| a1816896ca | |||
| e522b4c7f3 | |||
| daa4f7dfb1 | |||
| 39e6463d19 | |||
| f30bc948c3 | |||
| 2b0422b2cb | |||
| 70149846df | |||
| a8f88d7c4d | |||
| 73ddf48d12 | |||
| a9a65fe1f1 | |||
| 70eb5c1053 | |||
| defcf7d220 | |||
| 1f78253b38 | |||
| 40fcdb0fc0 | |||
| 9a3a78c96c | |||
| 7701b41af4 | |||
| 9304d02d78 | |||
| 932b9c0966 | |||
| edb33e1f2c | |||
| fc83306d72 | |||
| 5bdfe23435 | |||
| 503b17acd6 | |||
| ef8c088127 | |||
| 151f15fa15 | |||
| 35db862d12 | |||
| 7948e04ca7 | |||
| b6b9c2a843 | |||
| 95109ef9b0 | |||
| 7fd49be682 | |||
| f16662bcc0 | |||
| 4962597f58 | |||
| 19597bb40b | |||
| 80bc526401 | |||
| 67f1b968bb | |||
| ae0ed07447 | |||
| f5f415e851 | |||
| 8a20c1812f | |||
| 98d4d99c1b | |||
| a38ef2b6f5 | |||
| 5723717576 | |||
| b8f097ff94 | |||
| dcab038020 | |||
| 234c192429 | |||
| 15af5396f8 | |||
| 3fc6da34e8 | |||
| b1dce01b49 | |||
| 885dc48a50 | |||
| 30962c4c7b | |||
| b993cfd294 | |||
| c8c5862b47 | |||
| 0dfd8b9f53 | |||
| 0d3450ceed | |||
| 073ece0527 | |||
| 8020bbd806 | |||
| 1ebd4dbda6 | |||
| beebbf54ad | |||
| f8e5a65f95 | |||
| 379066c782 | |||
| e96ada8c2e | |||
| 0a7d903dd6 | |||
| 0afc671763 | |||
| dd183cbca0 | |||
| cc17373166 | |||
| 19df2e0baa | |||
| 045b4db246 | |||
| 59ccf845b5 | |||
| 814a48c55f | |||
| 4c6cc4abd6 | |||
| 34f47fcfd7 | |||
| 87e64da0d9 | |||
| 730de02874 | |||
| e0fef1da42 | |||
| 3bfa7b1cdd | |||
| fcb59091d2 | |||
| 9146e2a318 | |||
| aefe69c4b5 | |||
| d1ee3d5688 | |||
| f92cdf36f5 | |||
| 8668ff8939 | |||
| 50d37ad752 | |||
| c6afe4fd4e | |||
| 03be4a22d4 | |||
| 85f18f5991 | |||
| 0d237a8f55 | |||
| 7522ac1eaf | |||
| 4704c2a178 | |||
| da818ff577 | |||
| 2660f673db | |||
| fec1a1c0ee | |||
| 537d0203e1 | |||
| 7e00fbfe2d | |||
| b54cfd76dd | |||
| 74783eee0e | |||
| 3994d5bf75 | |||
| 08f7685ca3 | |||
| d08d4962a5 | |||
| 42c9166df2 | |||
| 4d70a0c5bd | |||
| 1202be4686 | |||
| 14f93a91e3 | |||
| 49de124fc0 | |||
| cc939de641 | |||
| fd6c77c73e | |||
| 5c213d31cf | |||
| d8ab1c3a67 | |||
| 5157789774 | |||
| 69d92ba0a8 | |||
| 4686e38a5e | |||
| cab3f60e06 | |||
| f59907d9d7 | |||
| 305441ea28 | |||
| 807094c829 | |||
| cae1b3f47a | |||
| 4c3a8e1fd7 | |||
| 0d5c1b99df | |||
| 656faacc76 | |||
| b8919f8c11 | |||
| b0c042e9dc | |||
| 455116c86f | |||
| 4202452e70 | |||
| 7df7fd635b | |||
| b4fa2d7ce6 | |||
| 8ac3089e0b | |||
| e2f1f3029f | |||
| dd5c438597 | |||
| 60982cc276 | |||
| d23ea29bef | |||
| 5246ea3537 | |||
| 5ad217bd18 | |||
| 5d1c6d35f7 | |||
| 1d49af144c | |||
| 9bdbd49de0 | |||
| e0821d264e | |||
| 5cf94ae35b | |||
| c5fbb73fea | |||
| c30b8942e7 | |||
| e21c1bbc59 | |||
| 87178985f9 | |||
| 0a23332766 | |||
| 9a752ac3a2 | |||
| aaed5a47d7 | |||
| 6e19a7e862 | |||
| 5cd9fe860f | |||
| 498e77c46e | |||
| da69644771 | |||
| 7c0f4ad255 | |||
| 76ec0b6d74 | |||
| c3e604ba49 | |||
| 15848d399c | |||
| 0c791ec52c | |||
| 6938b6e9ac | |||
| a9fe3e1b12 | |||
| c23234d1d8 | |||
| 3855beccce | |||
| 41932c9127 | |||
| 91840904b6 | |||
| 9ba4ac9956 | |||
| 753092db30 | |||
| f0b07f1155 | |||
| 3429060a7b | |||
| 8a6016376c | |||
| 51d0658bdb | |||
| 1cac2f6170 | |||
| 88e209d65b | |||
| 67103e7113 | |||
| 496ada3647 | |||
| 405302e2f0 | |||
| 0530a58530 | |||
| 8ccae822fe | |||
| e45af9b611 | |||
| e8a9bd83d3 | |||
| 8d32c853d5 | |||
| 9137fb2b31 | |||
| d2eeb19f06 | |||
| 43cc91aca2 | |||
| 045187fe2d | |||
| 834e908edd | |||
| 98ba60a51c | |||
| 9bcca0a791 | |||
| 1a588b34fa | |||
| b3398a09ba | |||
| 8fed7034bf | |||
| cfe933c368 | |||
| 79968af8ed | |||
| bd58e02a18 | |||
| d5e82110c3 | |||
| da6be5c490 | |||
| c5b7264f1a | |||
| f8015c156e | |||
| 23012fbb5c | |||
| f3c0e8a835 | |||
| 808428e947 | |||
| 60d3eba712 | |||
| 817b0f8167 | |||
| 116673630a | |||
| 82c8fa2640 | |||
| 14b51c0c74 | |||
| 3c99ccc67b | |||
| c69bc77a7a | |||
| 7ff07804d0 | |||
| 2f4766af9d | |||
| c2e5f19226 | |||
| ac1bd650ce | |||
| c845b63578 | |||
| 4c278a8da5 | |||
| 7b48bc1ef6 | |||
| 2179f199b7 | |||
| 60a3ea0cea | |||
| acaf7cd934 | |||
| 38a2fdff39 | |||
| 14907849cc | |||
| 45ba89c89a | |||
| 9d235fa838 | |||
| 5ea481409f | |||
| 603c2fdc68 | |||
| e384995b63 | |||
| 92444c05aa | |||
| 5831bb49f1 | |||
| ba14d1e846 | |||
| c8e34ac522 | |||
| d41c20f06c | |||
| 30b09856a5 | |||
| 3307c0c183 | |||
| fb1af04b55 | |||
| fda439cb38 | |||
| eb8044e3d4 | |||
| 842177a0aa | |||
| 6913fe1f08 | |||
| aaf2c7f58a | |||
| 8c9ed4907b | |||
| d7c1a8f7ae | |||
| f3855b6548 | |||
| 82096b67a5 | |||
| a560c9c1ee | |||
| 2a089eab08 | |||
| ab4607bf0e | |||
| c9e839f1fd | |||
| 33c75076da | |||
| 8d9fe13490 | |||
| 1752579f9d | |||
| a525104dac | |||
| 375fd7a6aa | |||
| cd79191d7a | |||
| 904994ae24 | |||
| 0a1870d862 | |||
| d1ab79a9e7 | |||
| b37769b935 | |||
| ac7001b96e | |||
| 8c0b88d69a | |||
| 4381809959 | |||
| 54d3cd86b9 | |||
| 1720feeeee | |||
| f7b40d5f92 | |||
| 31df40a841 | |||
| cdda8649fc | |||
| c44863a9bb | |||
| 1e4df539b7 | |||
| 823599fd78 | |||
| 94852460c1 | |||
| 900a789b69 | |||
| 013c181aaa | |||
| 3b38440385 | |||
| 074977c58e | |||
| 3625eb01e2 | |||
| bbc5217c81 | |||
| a5515ad08b | |||
| 12e4441227 | |||
| fdc4ddf316 | |||
| eb2ebead28 | |||
| a8ed7eb914 | |||
| fd509a9099 | |||
| 7bf4b23ee5 | |||
| 937dd2be5e | |||
| 5f9789f99f | |||
| d6d70163d5 | |||
| f4c830e671 | |||
| 950bdb5b76 | |||
| 3068ed75ee | |||
| 42945e1b42 | |||
| faef230d85 | |||
| a7f0dfdff7 | |||
| da28516a98 | |||
| 44e27bf9ab | |||
| 72fdc707ee | |||
| 4d8b6c5ea7 | |||
| fafa299ae1 | |||
| f779957145 | |||
| ce6a26976c | |||
| b53a6da24a | |||
| 438fdaa1b1 | |||
| eb10d0d8d0 | |||
| e1a7056ef8 | |||
| 20fa49f8e4 | |||
| 4ff90029b1 | |||
| 20b75b4065 | |||
| 832a25601d | |||
| 19682ec21b | |||
| b71e2957d3 | |||
| e7fb444bb3 | |||
| fd2705a49d | |||
| 935c9a50ab | |||
| 08c2e989fa | |||
| 942e595444 | |||
| f2225ff6b6 | |||
| 12e073e8cf | |||
| d3b77c3be1 | |||
| 2607ad2e24 | |||
| 67f0801453 | |||
| eebd36ccc8 | |||
| 55d401c098 | |||
| 0c1c5ae9e5 | |||
| d7b5d1f947 | |||
| 80f253e67c | |||
| 39cad02e0d | |||
| 0c79dcdf1b | |||
| cb1bc6cfdf | |||
| a7094de40f | |||
| b5c9f034ca | |||
| d875ed5cf5 | |||
| 53e4b347de | |||
| fafdf88442 | |||
| 0911de205f | |||
| 26fc03ee19 | |||
| f406de3089 | |||
| b62f7a7497 | |||
| 59b428f24b | |||
| 54e8395789 | |||
| 2fabfbe8f6 | |||
| 28ac9e153e | |||
| dadbf1de90 | |||
| 2e16dd983f | |||
| 847f57855c | |||
| 2d412c019c | |||
| 47fc9561ab | |||
| 25fa999259 | |||
| f40145412b | |||
| cec15dbfc0 | |||
| 69d0790484 | |||
| 537a904a49 | |||
| 939643c307 | |||
| 2ce208854f | |||
| 6883b91f8a | |||
| dccdef99c1 | |||
| 76ab99bca6 | |||
| fed0c7a330 | |||
| f179d92c9c | |||
| 907c6dec47 | |||
| 3ea9192f79 | |||
| 6a5cd698a4 | |||
| 179dd0e4d8 | |||
| 4c5763b125 | |||
| 37ed1f750e | |||
| 9528e26487 | |||
| 8143ef1057 | |||
| 2056015fa2 | |||
| 1d98c76c90 | |||
| bddc65e072 | |||
| 1b7a06aaf1 | |||
| fbf12dc764 | |||
| e390a56f05 | |||
| 063c272aea | |||
| c00fd0480c | |||
| ca732f9b8b | |||
| d3a0fe64e7 | |||
| 90256ba68a | |||
| 8b30762f03 | |||
| 77436877ef | |||
| 840e1c5c5b | |||
| ee179a0bd8 | |||
| 2a3ab0ecb2 | |||
| 841c7730e7 | |||
| bf8f8d32a9 | |||
| 8ba56f8b66 | |||
| dc5e7ab98d | |||
| ca7effe0c0 | |||
| 7e543a64e4 | |||
| 98ea5a7b70 | |||
| 7883839871 | |||
| f8d74bbb6d | |||
| 387c399078 | |||
| c184f1e42e | |||
| 5c7d19ce13 | |||
| 3d4b3edc3a | |||
| a7ffc8a172 | |||
| 618d2b993a | |||
| 2458b6b388 | |||
| be933b8b78 | |||
| 38dcb7bd3e | |||
| a001126704 | |||
| fe513b1a07 | |||
| 0825d0511a | |||
| c1ec42a812 | |||
| ae747aa426 | |||
| 70cf212178 | |||
| 5992658164 | |||
| 6e79927bc0 | |||
| 8dffd6181d | |||
| c4ee21bdb0 | |||
| 1d1197e18a | |||
| cc1ff369a7 | |||
| c89030beee | |||
| 4fa65099f9 | |||
| 2a90a0a278 | |||
| 36ab052dda | |||
| fddbe6a574 | |||
| a887be432b | |||
| cf86645bb5 | |||
| 705311f01f | |||
| 81429bfa85 | |||
| 1016d41d7a | |||
| 0ff760fe4a | |||
| f48b1d066e | |||
| 90c82ab1e7 | |||
| 1a0d1f7d79 | |||
| 132132307f | |||
| 2a6e6a671e | |||
| c073f2a15b | |||
| cf1e1aac77 | |||
| 19893601f2 | |||
| 034d35b8a7 | |||
| 7ef9e7eb51 | |||
| 28e23dfdab | |||
| 2c80c4a7d5 | |||
| 5c1147bfa4 | |||
| ee79744735 | |||
| 410d4a47ed | |||
| ead6d9c7d3 | |||
| 4c74908789 | |||
| 547d8ae113 | |||
| 4fcaae8053 | |||
| c935744f4c | |||
| a01e1bad0f | |||
| 3cabbc1328 | |||
| 7192439b2c | |||
| ff6cbf6628 | |||
| 53e9925880 | |||
| 9dfe6242b9 | |||
| ea419509f1 | |||
| a1a683ec56 | |||
| dbe3b6a427 | |||
| e8bd4d05b5 | |||
| 1fb0ed9545 | |||
| ea464bdc7d | |||
| d1c2d0b907 | |||
| 175b2914b6 | |||
| a2c86daef6 | |||
| b40248a1d5 | |||
| ae3a34287a | |||
| b971f2ab22 | |||
| cbc73f5c9a | |||
| 373ef5b7e1 | |||
| 89388940ed | |||
| b2c94adabf | |||
| 3904f50c1b | |||
| d81fa897ad | |||
| a0792aa469 | |||
| 5be03c7ab5 | |||
| fc4da4408c | |||
| 83ab701d02 | |||
| 59aaabecc7 | |||
| 3f07bb5c3a | |||
| 94dd1eb0c2 | |||
| 07ab079715 | |||
| d559df3ee3 | |||
| 69cd766f88 | |||
| 276db17f0c | |||
| cfce39c1de | |||
| ef5631bff4 | |||
| 62017b3ff5 | |||
| 3aafbd2ccb | |||
| 100bea981d | |||
| 931311f11f | |||
| 5e456f378b | |||
| bf315258c5 | |||
| 9780db6fa0 | |||
| 6d0a24cc95 | |||
| 1a3d6e86a8 | |||
| 4f5efef922 | |||
| ff269d414e | |||
| c3e746aa74 | |||
| 511047874e | |||
| 47e40fc762 | |||
| 27c113d8ae | |||
| c082f2a1b1 | |||
| 12f7a3fca0 | |||
| 88d66dee6e | |||
| 3460e6d513 | |||
| 1831caea08 | |||
| b88885582d | |||
| 66650d6dd9 | |||
| 0b65f07960 | |||
| b14e6a7860 | |||
| 68a8c964ea | |||
| f03ac0133f | |||
| 6c1045c545 | |||
| 0cdfd5e62b | |||
| a839638478 | |||
| 1063dbea02 | |||
| c173db69a9 | |||
| bbbab1b2e5 | |||
| c9b6b5b8e1 | |||
| 2386124089 | |||
| 9e907f10b1 | |||
| 2ec6e47086 | |||
| 504e18d9a7 | |||
| b021ff4e21 | |||
| 32c50a9793 | |||
| 44ae48baf5 | |||
| cc3dc1636b | |||
| 545ac689b1 | |||
| e7799fef1c | |||
| d857ca46e0 | |||
| 0e3cc97ee6 | |||
| 66107cf7a4 | |||
| 1e8df5c9d0 | |||
| 4f608bdc5f | |||
| 3da1bae826 | |||
| 48559d3358 | |||
| 5d8871a044 | |||
| a684fadf43 | |||
| 8c858c6b1e | |||
| 7e0803c4b4 | |||
| 697b42c70f | |||
| cb52dfdd0f | |||
| db203a5ad8 | |||
| 3f4df35a3e | |||
| 4661576c8a | |||
| b03bb5a3d7 | |||
| f7d4f9d94e | |||
| c10593e4ac | |||
| 47580c6976 | |||
| 0fa281083e | |||
| 0aca6c2588 | |||
| e181abe797 | |||
| 7818219763 | |||
| 25d20266b8 | |||
| 330505faaf | |||
| f7dd10bc08 | |||
| 20cc82e210 | |||
| 585885e6a2 | |||
| 149af003d4 | |||
| 50a5b9861a | |||
| 17ce6f7291 | |||
| 7047ee9391 | |||
| 5e1c32b606 | |||
| 38032f0b77 | |||
| 6364ea8e57 | |||
| c796e724aa | |||
| 63053f46a8 | |||
| 8bf8c278f0 | |||
| aa0c186c8c | |||
| 5539f74bea | |||
| 11a6cf8236 | |||
| ce63dbde1b | |||
| 989d843fcb | |||
| 32d07e7959 | |||
| 50b585c1dd | |||
| e03beba9bc | |||
| e4ceedcac6 | |||
| 80edfffc3f | |||
| 7a734ebf29 | |||
| 197e159b48 | |||
| 696632e551 | |||
| b1752de36f | |||
| 5967907f86 | |||
| ed288317c4 | |||
| 3754021ae8 | |||
| 7214ce2ede | |||
| d1dcbd97b7 | |||
| 30c0690e90 | |||
| 4b79881472 | |||
| b42be9899e | |||
| 54c2e670e1 | |||
| e35dbba57d | |||
| e632f07708 | |||
| 7f0201b552 | |||
| 70de7ffe69 | |||
| 7a421205e9 | |||
| 3dcc9ad844 | |||
| 5548ed1b57 | |||
| 32bc082a9f | |||
| 4eed77e123 | |||
| 8564105ee3 | |||
| 4a537dde1a | |||
| 2f4d89f32a | |||
| 10d74d3b34 | |||
| e1ba78ff61 | |||
| 31ee456962 | |||
| 717607807a | |||
| 56e4af58d3 | |||
| 622aecfd6d | |||
| be9adb64bb | |||
| bb257f2bf2 | |||
| 76f365f7e8 | |||
| 499336e752 | |||
| e53ec3e43c | |||
| 8b5fe714e1 | |||
| 27c5721e2b | |||
| f3534288f1 | |||
| 87a5829f9e | |||
| 11e9f0eaf2 | |||
| 6f653a603b | |||
| a7bba61b6a | |||
| 15ac81c1cd | |||
| 6b839c8cdc | |||
| 15c25329b6 | |||
| aca85b63ae | |||
| 26637c0f1c | |||
| 56da5493b3 | |||
| abcc9b374c | |||
| 53fc6af1fb | |||
| 93db7ec71e | |||
| f15909c814 | |||
| 7adae569f5 | |||
| a7bf22cafe | |||
| fc9da4756c | |||
| 5c7db14253 | |||
| b42a51cc56 | |||
| 9eee7b2582 | |||
| bf8a5e6a11 | |||
| a7e507a137 | |||
| 04de621e37 | |||
| dc7f5e3dbc | |||
| c3f45471c7 | |||
| c47d5565c2 | |||
| fd4f3147f9 | |||
| 22e23e1e65 | |||
| a470b2cd4e | |||
| 8e8b635769 | |||
| 83b056738f | |||
| af13c580b3 | |||
| e35197c968 | |||
| 58992030dc | |||
| 677dfde22e | |||
| e1433cbf2a | |||
| 93e48fabdf | |||
| fb424d28b9 | |||
| 8b9017224f | |||
| 3b50a732b5 | |||
| 5852fe54a7 | |||
| 2fe84293b3 | |||
| 66d1de0821 | |||
| 148c0d9213 | |||
| 871a6f23e2 | |||
| b043e6bf52 | |||
| 4bbae73be1 | |||
| daddbadc06 | |||
| 4029b94ff2 | |||
| b74d1cb54a | |||
| 6a689e2ae4 | |||
| cf5059b006 | |||
| 337c5412b7 | |||
| 01b1f7529e | |||
| 820aeee659 | |||
| 4db5855b74 | |||
| 579f5d7c8a | |||
| 313256adff | |||
| f2a2e86db7 | |||
| 75206dd8ac | |||
| 8435063c3f | |||
| 8700e1ef65 | |||
| bba48f455e | |||
| 6ac0cd421a | |||
| 1a471a7c20 | |||
| e851abb5a5 | |||
| 85eb88a4a6 | |||
| fd61a0bfbf | |||
| 2513ade3c4 | |||
| 5b2ad5f87f | |||
| b4b731eaa4 | |||
| 2b4a354a8c | |||
| a1d9c5d86c | |||
| 744cc51140 | |||
| c73e24c3a2 | |||
| e3bec5039b | |||
| 85d9c91e5e | |||
| ca236da385 | |||
| c904a92c22 | |||
| 0952b6d68f | |||
| d09f932834 | |||
| ac4c5c6e28 | |||
| 0e3bcbe10d | |||
| 3b64176c0d | |||
| 782ecbd750 | |||
| 6b2e98b9be | |||
| 4cf1739604 | |||
| 369474a0bc | |||
| 24975d670e | |||
| 61ac1c4ae3 | |||
| 8b881ab0b4 | |||
| b291b04d62 | |||
| 5a5ce4e665 | |||
| 2317c17b4b | |||
| fb02bdb445 | |||
| 53d85ac7a2 | |||
| f25534ff0f | |||
| 463369f283 | |||
| 9377701ad1 | |||
| a98fce2160 | |||
| ccda1b4523 | |||
| ac6f80c274 | |||
| 5fd86b781b | |||
| 0d28292cfb | |||
| 8bf9d38711 | |||
| 4caf052199 | |||
| 82ea2cd86d | |||
| 89c0d5763c | |||
| 813409a8fb | |||
| 03b7445cb9 | |||
| 9f35801f64 | |||
| 526f5efb78 | |||
| b3071603d0 | |||
| 6f358dd8ea | |||
| a6a715b8c2 | |||
| 1ebbfe5d92 | |||
| 9e27921867 | |||
| f1057bb4a3 | |||
| 48434453e3 | |||
| 6e8597e3f7 | |||
| bb98fe824e | |||
| 70b57b078d | |||
| 8410e541b6 | |||
| 4bad752f30 | |||
| 68d677f028 | |||
| bee3150193 | |||
| 4025ef2070 | |||
| e64cc325d7 | |||
| 5c9d323fe9 | |||
| 5d55e558fe | |||
| 687f126f4f | |||
| 2bad06a234 | |||
| 61fbb76eeb | |||
| 38f8048309 | |||
| 540a0761ef | |||
| 051563522a | |||
| d86045c64e | |||
| 1a2e2f4d51 | |||
| f2fbe6dfee | |||
| 5dcecb55c2 | |||
| 3bd41013c0 | |||
| aa35099a4d | |||
| 6a45d83082 | |||
| 25397b94b3 | |||
| d1df48ebb1 | |||
| 4a7aa032c2 | |||
| 846986987d | |||
| b2d380afcc | |||
| 6135e83653 | |||
| f090bbc1c7 | |||
| a10d0e45c9 | |||
| 6d5671dd0e | |||
| 8a251fe39e | |||
| 24d93ea87d | |||
| 93aa4f086e | |||
| a357f5a1b8 | |||
| 39c0af46b0 | |||
| 4e4048be4e | |||
| b5e2c628cd | |||
| 6b8b69d324 | |||
| 36993703f6 | |||
| 713f1239c6 | |||
| cb3dffc7fc | |||
| c0065765d4 | |||
| 37621f8548 | |||
| 8e18a5fb4c | |||
| a0d96d5a74 | |||
| 0c8620e944 | |||
| 7fbc883ec0 | |||
| 6561a40f2c | |||
| 0427d08ede | |||
| a7aec70bc1 | |||
| ad6918d71e | |||
| c7d36ac06e | |||
| 9428cf0d06 | |||
| ec17e58eed | |||
| 2723f2150b | |||
| 9ced45c714 | |||
| 42e7233aaa | |||
| 4e54e4c22a | |||
| 565859b2aa | |||
| fe9ef2e85d | |||
| 0a5fedf089 | |||
| 45a9235738 | |||
| fd43c75811 | |||
| 4271f42685 | |||
| 016bfcddad | |||
| 26d18031f2 | |||
| 4307ff5bf0 | |||
| 648d6b2662 | |||
| 6f4a3701e7 | |||
| 2486aee24d | |||
| 19c9d7d59d | |||
| abd640d36b | |||
| a085e9ed54 | |||
| f790ef5a2b | |||
| d6ec588c11 | |||
| ed031c6dc1 | |||
| 0a871d57cc | |||
| f1653d4643 | |||
| 58bc040953 | |||
| c040206d3a | |||
| e0af49f638 | |||
| 7d1f218523 | |||
| c779701d26 | |||
| dc7b425dc5 | |||
| 9be00cd546 | |||
| 2caf8d6a4e | |||
| 27c4069187 | |||
| 4b2c0b0771 | |||
| 7e1216a3d3 | |||
| a02e3d2ebd | |||
| 84297ff473 | |||
| 1ffad1ebaf | |||
| 2db99e7807 | |||
| d7fdfd6d71 | |||
| 241053e1a8 | |||
| 6da8396c76 | |||
| 79e0a1b94c | |||
| 4d94700375 | |||
| 1672217dd9 | |||
| 313c90ff85 | |||
| 227da8dce4 | |||
| 84111996b4 | |||
| b23699f0c1 | |||
| 0c0610c6c5 | |||
| fb39aa32bb | |||
| ca25c6075b | |||
| a9f474b24d | |||
| 1354da01e6 | |||
| 6151a1ca7f | |||
| bbe1350f7d | |||
| d9c104613c | |||
| 4770b32287 | |||
| 89cfe1ef57 | |||
| 72d29030d9 | |||
| 5cf98922fb | |||
| 6a2f2b4efe | |||
| 69cb8c5a0a | |||
| 1ba9513a4e | |||
| 1095e47e6f | |||
| 0ebda97b03 | |||
| 2d5ef36a7f | |||
| 39cbfb84ae | |||
| 73a56830b0 | |||
| d924f73ceb | |||
| c44926c2cd | |||
| 11a852d49c | |||
| 2b30f5591c | |||
| d3661fa9d3 | |||
| a1c66e3808 | |||
| f3fc393617 | |||
| 3f6909866f | |||
| 3adcfd6b4a | |||
| d42be69735 | |||
| 9ed3328496 | |||
| 3e5bd096cf | |||
| d5e4d8637c | |||
| c5883ebe8f | |||
| e6d8115e52 | |||
| a9d70fe27e | |||
| 3fb314a1cb | |||
| e898639168 | |||
| ef9a17a28a | |||
| 16141a7104 | |||
| e496f4f3e2 | |||
| 5d42439bf4 | |||
| b60681e9bd | |||
| 25684bf4f6 | |||
| 8d57ef2be4 | |||
| 6143f792f3 | |||
| f0c5b603ee | |||
| b61b45177a | |||
| afd19a7307 | |||
| 155a348802 | |||
| 733de44d03 | |||
| 228532b03e | |||
| 54bd10a1db | |||
| 674f158877 | |||
| a07d7ffdec | |||
| aadc85cda0 | |||
| 43f6d79554 | |||
| fd1aea420e | |||
| dbdad6fce8 | |||
| f48231e309 | |||
| d24189e8aa | |||
| 5baa08eb0c | |||
| 95eb310a7f | |||
| d112d6308c | |||
| 00da650524 | |||
| bb3aae46c5 | |||
| 1ca8eeeb50 | |||
| 5d4eef50e4 | |||
| 2b2123f14d | |||
| 8b91b815ba | |||
| 5824c8ffd0 | |||
| 8788867572 | |||
| f2193ff45c | |||
| 1091142614 | |||
| ef22387440 | |||
| 61ff04119e | |||
| bb7252042e | |||
| c95e267cdd | |||
| 059547e37c | |||
| 6dc0e4f5c3 | |||
| 8fc9685a1b | |||
| 7744568eb8 | |||
| 30b6d1cd1d | |||
| fe9585500d | |||
| 1ddba4a76f | |||
| 53b83909ed | |||
| af75060001 | |||
| 315210fe29 | |||
| 284c646638 | |||
| 39df3d7bad | |||
| 8f75f7332d | |||
| c3edf44cb4 | |||
| f933716bf5 | |||
| b37c0fbb95 | |||
| 3d057781de | |||
| 043180a0fa | |||
| f345212468 | |||
| a6c367ba24 | |||
| 1a7eb9f6d8 | |||
| a22fffc8b5 | |||
| 7e036cc65f | |||
| 7a083e7ce4 | |||
| c6aef98361 | |||
| 639b7d9374 | |||
| 23f7c1522e | |||
| 2e4bc5e218 | |||
| a556237963 | |||
| e8488eb406 | |||
| 5363842c4d | |||
| 4c5d783699 | |||
| 64d067d5a1 | |||
| 1845a65085 | |||
| 86bad5cb3e | |||
| ad4c88b535 | |||
| 55dde26aae | |||
| a93c85ebc9 | |||
| cd45046724 | |||
| 804be6d5e4 | |||
| 344782099f | |||
| 4ffff84540 | |||
| dffa3b7986 | |||
| 0f7bc9be52 | |||
| 05fc8ed5db | |||
| 74a8779c49 | |||
| 40631a753f | |||
| 5cf266c5be | |||
| 0f3eb42332 | |||
| 1fa8395847 | |||
| 1b6e283ac1 | |||
| 7b9504c5b4 | |||
| 2b52e21ccf | |||
| c49050ea69 | |||
| 53037c96cf | |||
| 000e5fa105 | |||
| 3ccad9ada9 | |||
| 73bd3e513c | |||
| 2c2a24c31b | |||
| 28a1c254d9 | |||
| 203ad6b565 | |||
| 0969bb9824 | |||
| c6ae7729d1 | |||
| af719dd8c2 | |||
| f87e257233 | |||
| 83d7535d84 | |||
| 811cc9c028 | |||
| f14fbfe087 | |||
| 446099b1f9 | |||
| bad927e283 | |||
| ccbb3dca9f | |||
| d47b947acf | |||
| 6332db8e86 | |||
| d829d43f2f | |||
| 8944698df1 | |||
| ccf5f2f60e | |||
| d381f579d3 | |||
| 995c0a7afc | |||
| ebba516603 | |||
| 9289d80e32 | |||
| d366e10cb9 | |||
| 7c30c2f945 | |||
| 4ae9821185 | |||
| 2283c90da1 | |||
| a32d2c6f4d | |||
| 208edfcebd | |||
| 6ec869505e | |||
| 4135040007 | |||
| 67c3883cb4 | |||
| 30b3be8194 | |||
| 594a2e759a | |||
| 46e6a048d4 | |||
| 426fe6e3c1 | |||
| 4bfaf1728d | |||
| 83aaa9e8f0 | |||
| 6adf26f4d9 | |||
| ac9ffa3f0f | |||
| 7a94216b3a | |||
| fd361ef5cb | |||
| 314e20ea18 | |||
| 48bf21a537 | |||
| f6de9918b5 | |||
| c37ecab029 | |||
| a50d7e2c60 | |||
| 38ff515d68 | |||
| ff2cb31f0f | |||
| 85fc467845 | |||
| 1289a6e14f | |||
| a88d523559 | |||
| 48e2a79d8f | |||
| 56c671149e | |||
| d37621044c | |||
| f3087773e3 | |||
| 94681fd47d | |||
| c2678efc06 | |||
| e682a77858 | |||
| 89e3ce06fa | |||
| d1afc9c10a | |||
| f3ac263238 | |||
| 2d3c1b7702 | |||
| e028eadf60 | |||
| 1cd94b4f7a | |||
| fd70f456e9 | |||
| f03b23497a | |||
| 1842afa7c6 | |||
| 41901be95f | |||
| 93261c63b5 | |||
| 95dfe5361d | |||
| e5b27af055 | |||
| b2c48af53f | |||
| b0f0a34672 | |||
| 9c477710b9 | |||
| 3c01673741 | |||
| 28743efb60 | |||
| eb201d3474 | |||
| b54d34127e | |||
| fcfc9572f1 | |||
| c0f0084e56 | |||
| 7e3162d287 | |||
| c231184c28 | |||
| 08294f5f39 | |||
| 048afdc232 | |||
| 8779de448d | |||
| 3770f07720 | |||
| f53180a960 | |||
| 6b6c1b98be | |||
| 65405da8d1 | |||
| 3df54eac21 | |||
| 68c1cd83a7 | |||
| be3696ff8c | |||
| 9666deca6d | |||
| 87d25336e7 | |||
| 11f4b3d869 | |||
| 2ed7f152b7 | |||
| 6e21f19bd5 | |||
| 8e205207b0 | |||
| 892ff38a3f | |||
| 3428b95672 | |||
| 64cb966683 | |||
| 8bcff36745 | |||
| d985a66eff | |||
| 16f855f173 | |||
| 31500076d1 | |||
| d955f0e3d8 | |||
| 4615418000 | |||
| 9ad0799b48 | |||
| 60b1dcf5ee | |||
| d0c9e17090 | |||
| e829c2031c | |||
| 85ea71d9aa | |||
| e14dd36a0a | |||
| cf210c5a5a | |||
| ce571bad81 | |||
| 55b0fb69ac | |||
| e04f3e317e | |||
| 8f2b6cf44e | |||
| ee0ac7cbed | |||
| 87fd1a5f38 | |||
| c0ef2eb559 | |||
| b7cc592fb1 | |||
| 1d2c3a748b | |||
| 405529e8f6 | |||
| e17959ed65 | |||
| b599f95564 | |||
| 16c8721d6c | |||
| 6c115804e8 | |||
| 43c69d4288 | |||
| b8dcfcf900 | |||
| f77aeded6f | |||
| faf6ea83fa | |||
| cb7e54acaf | |||
| 87f9837939 | |||
| 380447dd41 | |||
| a8667836b3 | |||
| 31d0d6c7c9 | |||
| 77cd519be2 | |||
| 361902d59d | |||
| 5accc2a923 | |||
| 53eb1649d5 | |||
| 4cbda25000 | |||
| 5a10a8dd46 | |||
| ea4c140ad0 | |||
| a67b4a5059 | |||
| 95bdbc590c | |||
| d249c77b18 | |||
| e07914e9b3 | |||
| d831624d43 | |||
| 827791574d | |||
| b458a254a9 | |||
| 1bbdabc42f | |||
| 91fd93c724 | |||
| 186a336232 | |||
| 27c697c1d2 | |||
| 974fd19b40 | |||
| d384442fb3 | |||
| 46dbd016f4 | |||
| 0cf0c7a27c | |||
| 5489237f11 | |||
| aa16370fc5 | |||
| b75554ddcb | |||
| a31281df40 | |||
| 51d9e7fcb1 | |||
| e2b15c9b4f | |||
| 6bbb968128 | |||
| 91ba2a9282 | |||
| 21d7dd873e | |||
| 3f0f189a3a | |||
| dc49dd0a94 | |||
| a7c6e36ec3 | |||
| c8976daf96 | |||
| 7aca2a1b36 | |||
| 63f9e26b2e | |||
| 547783e0e0 | |||
| 86109127b8 | |||
| 6df195aa94 | |||
| cbdea7965d | |||
| 0a74546a98 | |||
| fde6e5f1fb | |||
| 06ea69ae4a | |||
| 4728885437 | |||
| a7e26055b2 | |||
| 45e6ef09a8 | |||
| d9f07c4de4 | |||
| 98cc0b9dfc | |||
| f84076d80c | |||
| 5f85810345 | |||
| 52ad526b87 | |||
| 8446773037 | |||
| be10718dfc | |||
| 8939f0dad7 | |||
| 3c1419a22f | |||
| 211c52ccc1 | |||
| 99b73f91b8 | |||
| 9da96b9e92 | |||
| 7f87af5a08 | |||
| e2659c87f4 | |||
| a8cb0a0e9c | |||
| 34583c834d | |||
| 1d61d4dad4 | |||
| 434bfdde67 | |||
| d5a8731850 | |||
| d3738f2f31 | |||
| 570a8ee97d | |||
| 9168af4850 | |||
| d8a9ef8187 | |||
| 475239bd02 | |||
| ea3042e1d7 | |||
| 39d631b056 | |||
| 6e06e05f12 | |||
| 4d8f8f19d4 | |||
| 384e341452 | |||
| aa46285b8f | |||
| fae00992c8 | |||
| 47477808e4 | |||
| be16f93ba0 | |||
| a153f0d87d | |||
| 179dd6339f | |||
| 4c662a30ef | |||
| 28372af5a9 | |||
| 1f93cce2c5 | |||
| 62cc534153 | |||
| 4df1ec867c | |||
| ff195ad272 | |||
| 00ca21243b | |||
| 4d9c73ab1f | |||
| ddf98937d6 | |||
| 5acd7c3e99 | |||
| 0f35017955 | |||
| 2235524f97 | |||
| 17a8b38484 | |||
| 7651b5a11d | |||
| 775a066a9a | |||
| 5cdbfc0af7 | |||
| 4a8e9fef99 | |||
| 05f68d0b1a | |||
| d82a685d7f | |||
| 105bcf23be | |||
| a26ceeb76d | |||
| 9f94545283 | |||
| daac71ac9a | |||
| 2db34c4d7e | |||
| 5212f6d0b7 | |||
| 3ec502d862 | |||
| 1d5bca7962 | |||
| 4b9b4c1427 | |||
| dfc4667910 | |||
| 5be2eb1c9a | |||
| 5e1e6657d1 | |||
| 2a2019b363 | |||
| a8de386af6 | |||
| 63354b00eb | |||
| 822460e5ee | |||
| dbf6eeb144 | |||
| 47295ec417 | |||
| 0e60b46db0 | |||
| 8d7175875a | |||
| 024d057e03 | |||
| ec20fb453b | |||
| 06768833cc | |||
| 51d1efead6 | |||
| 04ff495066 | |||
| df408505e0 | |||
| 7674e23580 | |||
| 689cf3171d | |||
| ac23dda8db | |||
| da7934555c | |||
| 0b9b0e7f13 | |||
| a430b66a8d | |||
| 11e4542746 | |||
| 7238b89437 | |||
| e2dcc3c0ea | |||
| 0e944f7d8a | |||
| b391f4bc12 | |||
| 65f066d391 | |||
| cbe81a8f0a | |||
| 9957739dc4 | |||
| d0eb84ec51 | |||
| df0e211d01 | |||
| a3eef29b26 | |||
| 3614812680 | |||
| 8cdae41051 | |||
| b59833e8f0 | |||
| a4586b8bf1 | |||
| 3fbdfbe7a4 | |||
| 73a5fb1648 | |||
| 71fc7c4ab6 | |||
| d57b39da6e | |||
| 4bf13f3c70 | |||
| 4297665c93 | |||
| 7131ae2d91 | |||
| 103dc9704a | |||
| 7efa79637c | |||
| f33666b848 | |||
| 2acd6c8b40 | |||
| 7329c9d0bb | |||
| 4a7cc06d05 | |||
| b2bb29911c | |||
| 028a723a31 | |||
| 05705be7c3 | |||
| f89c0bb07c | |||
| ca402cb604 | |||
| 47b9f15b0b | |||
| 2104df5a83 | |||
| 5527926508 | |||
| f82f3fa858 | |||
| 69d4514716 | |||
| 0a49fcb22a | |||
| 89d50ed5f1 | |||
| 9224d1e017 | |||
| 9b2c954f8e | |||
| d718c88353 | |||
| b2e1d4cc61 | |||
| e3b81d0b36 | |||
| cbfe19aebf | |||
| 15269713cc | |||
| cbba373d7d | |||
| 2a6544b794 | |||
| 2eedfad833 | |||
| 55b946e784 | |||
| 1839e1ac42 | |||
| 67bc03bcc1 | |||
| f0f04ce3ff | |||
| 16caeb5400 | |||
| 9a6995343b | |||
| ec07334d14 | |||
| c859321cfd | |||
| d68db98d62 | |||
| b89f55f9b9 | |||
| 58f42f1441 | |||
| 3df0e6fda1 | |||
| 79093bf61c | |||
| 78e2c223f2 | |||
| deea1249c4 | |||
| a513fbf592 | |||
| d6eb675b89 | |||
| 10df195630 | |||
| 6dfcec6e8b | |||
| 8ffd091e53 | |||
| 8c7f1de80c | |||
| 830db36928 | |||
| ae68cc95bb | |||
| a123a8301f | |||
| 0ce1c120f9 | |||
| 67728851a3 | |||
| 785db03650 | |||
| 2fe02a2cc3 | |||
| 7a747774fd | |||
| 8a88a8a9ef | |||
| 42456bac0f | |||
| 0cffd76296 | |||
| d8373bc488 | |||
| 5953fd71d3 | |||
| de6837adb0 | |||
| 23dd575ce1 | |||
| 1f8cdbaf62 | |||
| e312c3147a | |||
| 128e7fccdb | |||
| 3a323551eb | |||
| b9d9797734 | |||
| 26d94c0519 | |||
| 6befc2b7a2 | |||
| 9db671b83a | |||
| c4205c1c11 | |||
| 93d47bb0c2 | |||
| 218ed1ce13 | |||
| bb12da6777 | |||
| c4452c99ae | |||
| 5ef0ad9a3e | |||
| 113a3eb5cf | |||
| eec386cbf4 | |||
| b3e8b41e0e | |||
| 1db275707b | |||
| 64b730a22e | |||
| 8522775569 | |||
| e81b77cbcd | |||
| 8498b79493 | |||
| 7c1160de92 | |||
| c517b6db3b | |||
| 30f3a6f450 | |||
| 2ed9d68c08 | |||
| d45c2eb5b6 | |||
| fc1c1ea690 | |||
| 18e17f2932 | |||
| e70ea6f202 | |||
| c4026c8e78 | |||
| a58b2efaf9 | |||
| e917ae4198 | |||
| 3832ac3965 | |||
| bba9de7b76 | |||
| d2de2c7093 | |||
| 53aed4c7f8 | |||
| e8bd839281 | |||
| 7d07e34d6b | |||
| f6f97e69eb | |||
| 0fe5d9d628 | |||
| e665857aa8 | |||
| 00f6b878b3 | |||
| 7cddf8ae6a | |||
| 3383a84fa6 | |||
| 8e120bca77 | |||
| 66d235a873 | |||
| dca9266697 | |||
| b3131169ad | |||
| e83bcf0fd9 | |||
| 9137244fcf | |||
| 3a32aab066 | |||
| 5fd4e2f008 | |||
| 0bc460db56 | |||
| 5a34244854 | |||
| 9676cabcb8 | |||
| 44b02961ac | |||
| 66f0b49ca7 | |||
| 24ed0fd479 | |||
| 8f6656d657 | |||
| 19e08942ed | |||
| 0865b9bbc5 | |||
| 00a5dbf42c | |||
| acae9ee602 | |||
| 93e0787911 | |||
| 5c354db1b2 | |||
| a5ca8781ff | |||
| ba1636033e | |||
| d8c063f59b | |||
| 30b99abfb1 | |||
| 8a51582d8a | |||
| 72635c8711 | |||
| 7bec653b55 | |||
| 9d30b6e29a | |||
| 77d4a9f711 | |||
| 8eec3cddb5 | |||
| d633029d08 | |||
| 15a27e234c | |||
| 174846f110 | |||
| 2aed64d1c1 | |||
| e36f24dc63 | |||
| 74f4dacbe4 | |||
| 4d7a5f03ab | |||
| 40dcbd1229 | |||
| 30686477f1 | |||
| 7beddf3019 | |||
| 8e8737c4df | |||
| 454afd5d1b | |||
| 5d88e86462 | |||
| 02666b7da4 | |||
| 5342dae5b3 | |||
| 2b0fd17fbf | |||
| f0848e23c7 | |||
| 3dc6e274f0 | |||
| 2c6d9e699f | |||
| 27ecde2f17 | |||
| 05a685fb24 | |||
| 6b272e8a6f | |||
| 7d9f57299c | |||
| 263f68554e | |||
| c7a924e83b | |||
| 74a71f4d8e | |||
| 9b9fb325bd | |||
| b7b738c92b | |||
| b8a539a67a | |||
| 23f7a540fb | |||
| 3d487be59e | |||
| 6ef6caaca4 | |||
| 87a222748f | |||
| ef0a9a4a1e | |||
| 6c674e86e5 | |||
| 55e6e80433 | |||
| 86bbad5b3d | |||
| e02d2c8af2 | |||
| 6907c6dfe4 | |||
| 1a51a355f2 | |||
| 92227a3b7f | |||
| ab0d5c8967 | |||
| e13db20c85 | |||
| e4b4a7b0c5 | |||
| 1fa23bdf16 | |||
| f8905a0490 | |||
| 5fe6757f8a | |||
| f3ac71ed3e | |||
| 8aef6ebfbc | |||
| 31d593a529 | |||
| d5b47ecdce | |||
| f5020f8dc0 | |||
| def774c9ef | |||
| 2dea8309a7 | |||
| d153a734cb | |||
| a220667f1b | |||
| 62b648c70f | |||
| 9f8bde7078 | |||
| c593b4180a | |||
| 3e2177b402 | |||
| d9bf09bb9b | |||
| 21c1d3c191 | |||
| 7da590ca76 | |||
| 97a298375a | |||
| 7d35c9a8eb | |||
| d77aaded39 | |||
| fae1b4dec1 | |||
| e4c47de90a | |||
| 6a5de6606c | |||
| 8132920ed8 | |||
| 26e8eb8c11 | |||
| 3647f3130f | |||
| f52a330b16 | |||
| a669144d16 | |||
| 78e835dd5f | |||
| da8a7041d1 | |||
| 3aa103e114 | |||
| 6aca198428 | |||
| 085ed59334 | |||
| 3f3bb4d3b7 | |||
| 148e3dc65b | |||
| 063b102461 | |||
| 558620cdfa | |||
| 7b62c9e8a4 | |||
| b44d65d265 | |||
| 1197693591 | |||
| 81f91f3324 | |||
| f78f6634fa | |||
| 28eee609de | |||
| a3e1843e8b | |||
| c9b8607c9f | |||
| fe94d75b1b | |||
| 5aadf88ae3 | |||
| 4e038142da | |||
| d75e95a23d | |||
| a019227ddc | |||
| d3b10a6711 | |||
| faa14fa91d | |||
| 85dced9cd1 | |||
| e45df6fa6d | |||
| 8bbf9b50b4 | |||
| 12794a499d | |||
| 3d31f73453 | |||
| 99f30fe09a | |||
| 11c64f3035 | |||
| aa65af10b6 | |||
| 1afc8d20a0 | |||
| 42b4d7ce5f | |||
| c1c53af855 | |||
| 740c88c506 | |||
| 270d27be73 | |||
| dc98c2c1fe | |||
| e6079ee275 | |||
| 26b88a55e0 | |||
| b5721fe6f3 | |||
| ec99cae3d9 | |||
| cceae6770c | |||
| 4cfa658fde | |||
| 8e58349bfa | |||
| 68a08b1f62 | |||
| 20a30b303b | |||
| 373f294fe3 | |||
| 5459a540e7 | |||
| 221181092e | |||
| 326e01d548 | |||
| c7572f0624 | |||
| b94ce542c3 | |||
| 6a303ae50a | |||
| 59721a3f1a | |||
| bd5ecf358a | |||
| 2617fcdcfd | |||
| dd5e4adc73 | |||
| c46c41db5a | |||
| aafd3c8d35 | |||
| cb6d531300 | |||
| e7b11a7ceb | |||
| 54c6c2e6cd | |||
| c7ef32bf3c | |||
| 824816cb94 | |||
| a138425298 | |||
| 9888aa8c08 | |||
| da0dcf65b3 | |||
| c9d875e3fa | |||
| bd44bb4534 | |||
| fda0a918f0 | |||
| 31599c5da9 | |||
| 7e1fbb3017 | |||
| 08b2f6f998 | |||
| daeaa800d8 | |||
| 9c4baf9cad | |||
| a1f01d2def | |||
| dde6e0859b | |||
| 7322006455 | |||
| c193a86a4c | |||
| 58fcca58fc | |||
| ffc477091f | |||
| 46620e2da5 | |||
| c43ba46c97 | |||
| a2fdefb6a6 | |||
| 0aea814a23 | |||
| 5c605be45c | |||
| 1e34f3ef38 | |||
| 569cd4dd42 | |||
| 203ab05d12 | |||
| 471c68474a | |||
| 76cc9614df | |||
| e53a35eebf | |||
| 96f60caa77 | |||
| e608257c1a | |||
| 05f31f40eb | |||
| 0b73633a66 | |||
| 2f5c4413e1 | |||
| c6e93b9870 | |||
| f64297c256 | |||
| edb9bac503 | |||
| fa64191082 | |||
| 4a5c0e7ff0 | |||
| aa54e47029 | |||
| 7bcb6dd7cb | |||
| 9651db67ce | |||
| 3ebb41c5ad | |||
| 23d91c7b37 | |||
| b644418b11 | |||
| 86c4045073 | |||
| 42f493b2c3 | |||
| 5a766c583c | |||
| 776e65bc5e | |||
| 55939f6320 | |||
| a1ea6b21d8 | |||
| c61357ec6d | |||
| d84b4e4f7d | |||
| 19bae2af96 | |||
| daff760280 | |||
| e161c26c35 | |||
| b99f634939 | |||
| a93b3dab1b | |||
| 5a16a50b3f | |||
| 21888829cc | |||
| dffef6f839 | |||
| 4ef7195d38 | |||
| 627d6f8a28 | |||
| 178ebf3ed0 | |||
| 979e91a2eb | |||
| 4dec688e0b | |||
| 1247dbe999 | |||
| f6f6318e02 | |||
| 6c7341b9f3 | |||
| 71e9caef9c | |||
| dfba9d3650 | |||
| 45cfe1a817 | |||
| 68066cdd48 | |||
| d1da77d6bc | |||
| 6cd97d2cb9 | |||
| dcae882009 | |||
| ee45bc713e | |||
| 5936e00acc | |||
| d12e8cbe97 | |||
| 60140087a5 | |||
| 74f4849144 | |||
| b8c36b034b | |||
| d51183be51 | |||
| fd3bb37c48 | |||
| f990095ddf | |||
| c86b99e69c | |||
| 70b15d128a | |||
| 42ce3cb405 | |||
| 98fb9c89de | |||
| 2bc2b632c6 | |||
| 2b4c6514c3 | |||
| f3e2f10478 | |||
| b43fa38350 | |||
| 0d4165421c | |||
| b02e8a9502 | |||
| b625e40eb0 | |||
| 47729e3e43 | |||
| ac0426d97d | |||
| 0619274d00 | |||
| 24b9e713f2 | |||
| b3736617c4 | |||
| 63ee99fde9 | |||
| 837d5803c8 | |||
| 2d1f24ca0f | |||
| 84a1f40115 | |||
| abb40b3ad7 | |||
| dfd54b7b54 | |||
| c82cde5e16 | |||
| 5ea4c90292 | |||
| fa3bdb5464 | |||
| 12abf494d8 | |||
| f5e8a60a8f | |||
| c53adedbe0 | |||
| 3b6831ea93 | |||
| 2f19af6a3e | |||
| 0f5a4c10ef | |||
| dfcaacee80 | |||
| 2ac3c1fe6e | |||
| d8c70c2498 | |||
| 16c15548c1 | |||
| d0fbf68d90 | |||
| 230599417e | |||
| 4c235f0427 | |||
| 4045a16e27 | |||
| 7f91e85df8 | |||
| dfb3285e18 | |||
| 0280059c13 | |||
| 5fdc3eca7e | |||
| 95cee1676a | |||
| 70580cc6ae | |||
| f56eb0d791 | |||
| 4534a729c7 | |||
| 62a6c243fe | |||
| 5feb5ee015 | |||
| 7907d05847 | |||
| 1dfa95d457 | |||
| b6b52ffdb7 | |||
| 97cb73cb73 | |||
| 3f3226391a | |||
| 91c873c25f | |||
| b38e28e50e | |||
| 65fc3eb362 | |||
| 3955fbdc64 | |||
| 441b72158d | |||
| 8e1457d605 | |||
| da4215afbd | |||
| 6e2007aeca | |||
| 4ba01c6cf1 | |||
| 1dd5f63a11 | |||
| 467921dbf6 | |||
| 99b2140838 | |||
| 63b5e28537 | |||
| 109eefa7e3 | |||
| 380791211f | |||
| bffd7c8d24 | |||
| aaab06f21d | |||
| 4c87b62b5e | |||
| e34e479c33 | |||
| 665f7c849b | |||
| 88b9eed559 | |||
| cb162a8f1e | |||
| db5d3aa9bd | |||
| 9db1ad32f9 | |||
| 630286665b | |||
| 8b08c1d113 | |||
| 6fcfa6cac0 | |||
| b1da8bbc4c | |||
| 7d56e71f77 | |||
| 68e02817a0 | |||
| 58bb2d5f93 | |||
| ee263914aa | |||
| a887fc8153 | |||
| 12d9e6b618 | |||
| f1b9ce61e1 | |||
| 3f2c7542f8 | |||
| 132c1a9a8b | |||
| 801c0ac47c | |||
| 24550236be | |||
| 3f46d6d16e | |||
| 5d6cf5789a | |||
| fca66222df | |||
| 2e3120cd46 | |||
| fa2b03b7fa | |||
| b141b3c95e | |||
| 83a2f07ba9 | |||
| ec2dd67d89 | |||
| 6da071c88d | |||
| 98ba214dfb | |||
| 5b86c96adf | |||
| a43f9d78c4 | |||
| bc8b32ebab | |||
| 363efd3836 | |||
| c1817505f8 | |||
| 27fac748cd | |||
| 56739945fa | |||
| 8a5e7accaf | |||
| 8c8ae57103 | |||
| c3ab43a9fc | |||
| a2d1d83457 | |||
| ce01adc5b7 | |||
| f0598c9fec | |||
| 19c0d325ca | |||
| 97d0cc2fb4 | |||
| df3a80e17e | |||
| 3eacdff1d6 | |||
| cd63d2622c | |||
| 2bf91ea453 | |||
| 7e8cbc542b | |||
| 55e0d734df | |||
| a42c7e4735 | |||
| dd264617d7 | |||
| 2167b1fc6b | |||
| 9b3f1c3f9c | |||
| e51014a5bc | |||
| 58e5f96eda | |||
| 74679b4b41 | |||
| 2a0f727cba | |||
| 9937c01cde | |||
| ebf3e4fea7 | |||
| 8e3839203b | |||
| a53bc720cb | |||
| ab62f61256 | |||
| a2b737e6a0 | |||
| 893e07ea16 | |||
| 78cfb9b7f0 | |||
| fae42bbca3 | |||
| d95b13664e | |||
| f7fe38fa25 | |||
| 38ed49d814 | |||
| fb08369aec | |||
| a071cef46a | |||
| 7438576bb1 | |||
| 571d510423 | |||
| 8dd4b58227 | |||
| d9402bc24d | |||
| 0513ed16bb |
@@ -2925,3 +2925,89 @@
|
||||
[8.3.2]
|
||||
* Bring back immich vectors hook in postgres addon
|
||||
|
||||
[9.0.0]
|
||||
* UI redesign
|
||||
* notifications: email notification when server reboot is required
|
||||
* notifications: email notification when cloudron update failed
|
||||
* notifications: email notification for low disk space (90%)
|
||||
* node: update to 22.20.0
|
||||
* docker: update to 28.1.1
|
||||
* s3: automatically abort old multipart uploads
|
||||
* notifications: validate domains configs
|
||||
* ldap: automatically detect pagination support
|
||||
* ubuntu: alert for 20.04 support being deprecated
|
||||
* domains: vanity nameservers
|
||||
* token: access can by restricted by ip range(s)
|
||||
* sendmail: requiresValidCertificate option for using mail server domain
|
||||
* mail: update haraka to 3.1.1
|
||||
* sshfs: implement rm via ssh
|
||||
* multiple docker registries
|
||||
* mail: rename delivered -> sent and received -> saved in event log
|
||||
* graphs: replace collectd with custom collector
|
||||
* graphs: live graphs
|
||||
* graphs: add system disk and network graph
|
||||
* profile: drop gravatar support
|
||||
* login: suppress notification of impersonated users
|
||||
* mongodb: reduce verbosity of logs
|
||||
* redis: disable by default when optional
|
||||
* apps: fix issue where operations on stopped apps errored
|
||||
* eventlog: Fix incorrect eventlog that the update crashed
|
||||
* database: change charset to utf8mb4. this allows emojis everywhere!
|
||||
* mail: add brevo as relay provider
|
||||
* mail: add rbl6 check
|
||||
* eventlog: mail server change log
|
||||
* profile: avatar cannot be changed when profile is locked
|
||||
* app backup: no more part alters app state. runs completely in background
|
||||
* system: disk usage is not collected in background. new disk ui, computes space on demand
|
||||
* backups: multiple backup targets
|
||||
* port bindings: add `enabledByDefault` property in manifest
|
||||
* backups: store integrity information and perform validation
|
||||
* reverse proxy: remove OCSP support. this is being deprecated in favor of CRLs
|
||||
* sqlite: fix issue where dump was also logged when backing up
|
||||
* backups: remove noop backend
|
||||
|
||||
[9.0.1]
|
||||
* redis: update to 8.2.2
|
||||
* Split the ubuntu version and cloudron version
|
||||
* Restructure sidebar menu items
|
||||
* eventlog: fix display of backup (sites) events
|
||||
* app archive: fix download config
|
||||
* graphs: fix performance issue when selecting apps
|
||||
* Support overwrite DNS in app install dialog
|
||||
* encryption: do not allow password and hint to be the same
|
||||
* Add better grouping to mailbox owner select
|
||||
* eventlog: display task log link when available
|
||||
* add ephemeral port warning
|
||||
* rsync: fix integrity computation
|
||||
|
||||
[9.0.2]
|
||||
* backupsite: only owner can add a site
|
||||
* remove max-height from the users view and groups view tables
|
||||
* backups: fix listing when stats is null
|
||||
* graphs: fix detection of rootfs block device
|
||||
* sidebar: ldap/openid/directory should not be visible to non-admins
|
||||
* sidebar: email domains, eventlog, settings is only for admins
|
||||
* reload dashboard on Cloudron version change
|
||||
* Always start with a fresh domains list for the apps filter
|
||||
* sysinfo: fallback to product family if product vendor is empty
|
||||
* archive: display the site name of latest backup
|
||||
* graphs: fix flickering of disk graph item
|
||||
* graphs: fix issue with live graph time calculation
|
||||
|
||||
[9.0.3]
|
||||
* Fix submit state for login form
|
||||
* Avoid flickering of SystemUpdate view when update is busy
|
||||
* backuptask: fix crash when accessing stats of old backups
|
||||
* backup sites: fix listing when status call errors
|
||||
* backups: display mail backup stats
|
||||
* Add missing autocomplete attributes on forms
|
||||
* Refresh backup site status and task in the background
|
||||
* Hide non-owner actions for backup sites
|
||||
* Move app start/stop back to the main toolbar
|
||||
* Fix styling in public page
|
||||
* network: fix ip caching bug
|
||||
* Change default footer to not have the forum link
|
||||
* Fix troubleshooting tool
|
||||
* Give domains list a larger max-height
|
||||
* Make app error compatible with previous releases
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
'use strict';
|
||||
|
||||
const constants = require('./src/constants.js'),
|
||||
fs = require('fs'),
|
||||
fs = require('node:fs'),
|
||||
ldapServer = require('./src/ldapserver.js'),
|
||||
net = require('net'),
|
||||
oidc = require('./src/oidc.js'),
|
||||
net = require('node:net'),
|
||||
oidcServer = require('./src/oidcserver.js'),
|
||||
paths = require('./src/paths.js'),
|
||||
proxyAuth = require('./src/proxyauth.js'),
|
||||
safe = require('safetydance'),
|
||||
@@ -35,6 +35,7 @@ async function setupNetworking() {
|
||||
// this is also used as the 'uncaughtException' handler which can only have synchronous functions
|
||||
function exitSync(status) {
|
||||
const ts = new Date().toISOString();
|
||||
if (status.message) fs.write(logFd, `${ts} ${status.message}\n`, function () {});
|
||||
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);
|
||||
@@ -55,7 +56,7 @@ async function startServers() {
|
||||
|
||||
async function main() {
|
||||
const [error] = await safe(startServers());
|
||||
if (error) return exitSync({ error: new Error(`Error starting server: ${JSON.stringify(error)}`), code: 1 });
|
||||
if (error) return exitSync({ error, code: 1, message: 'Error starting servers' });
|
||||
|
||||
// require this here so that logging handler is already setup
|
||||
const debug = require('debug')('box:box');
|
||||
@@ -73,8 +74,12 @@ async function main() {
|
||||
await server.stop();
|
||||
await directoryServer.stop();
|
||||
await ldapServer.stop();
|
||||
await oidc.stop();
|
||||
setTimeout(process.exit.bind(process), 3000);
|
||||
await oidcServer.stop();
|
||||
|
||||
setTimeout(() => {
|
||||
debug('Shutdown complete');
|
||||
process.exit();
|
||||
}, 2000); // need to wait for the task processes to die
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async function () {
|
||||
@@ -84,11 +89,15 @@ async function main() {
|
||||
await server.stop();
|
||||
await directoryServer.stop();
|
||||
await ldapServer.stop();
|
||||
await oidc.stop();
|
||||
setTimeout(process.exit.bind(process), 3000);
|
||||
await oidcServer.stop();
|
||||
|
||||
setTimeout(() => {
|
||||
debug('Shutdown complete');
|
||||
process.exit();
|
||||
}, 2000); // need to wait for the task processes to die
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (error) => exitSync({ error, code: 1 }));
|
||||
process.on('uncaughtException', (error) => exitSync({ error, code: 1, message: 'From uncaughtException handler.' }));
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
dist/
|
||||
node_modules/
|
||||
|
||||
# will get generated on build
|
||||
public/theme.css
|
||||
public/theme.css.map
|
||||
|
||||
# vim swap files
|
||||
*.swp
|
||||
|
||||
dist/
|
||||
|
||||
# these are not done yet
|
||||
public/translation/ja.json
|
||||
public/translation/pl.json
|
||||
|
||||
@@ -1,153 +1,10 @@
|
||||
<!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" />
|
||||
|
||||
<title>Cloudron Setup</title>
|
||||
<meta name="description" content="Cloudron Setup">
|
||||
|
||||
<link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
|
||||
<!-- contains all thing already using import statement -->
|
||||
<script type="module" src="./src/modules.js"></script>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link type="text/css" rel="stylesheet" href="./src/theme.scss">
|
||||
|
||||
<!-- jQuery-->
|
||||
<script type="text/javascript" src="/js/jquery.min.js"></script>
|
||||
|
||||
<!-- async -->
|
||||
<script type="text/javascript" src="/js/async-3.2.0.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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Showdown (markdown converter) -->
|
||||
<script type="text/javascript" src="/js/showdown-1.9.1.min.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>
|
||||
|
||||
<body class="setup" ng-app="Application" ng-controller="SetupController">
|
||||
|
||||
<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> Cloudron is offline. Reconnecting...</a>
|
||||
|
||||
<div class="main-container" ng-show="initialized">
|
||||
<div class="row" ng-show="view === 'owner'">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="card" style="max-width: none; padding: 20px;">
|
||||
<form role="form" name="ownerForm" ng-submit="owner.submit()" novalidate>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<h1>Welcome to Cloudron</h1>
|
||||
<h3>Set up Admin Account</h3>
|
||||
<p class="has-error text-center" ng-show="owner.error.generic">{{ owner.error.generic }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="form-group" ng-class="{ 'has-error': ownerForm.displayName.$dirty && ownerForm.displayName.$invalid }">
|
||||
<label class="control-label" for="inputDisplayName">Full Name</label>
|
||||
<input type="text" class="form-control" ng-model="owner.displayName" id="inputDisplayName" name="displayName" placeholder="Full Name" required autocomplete="off" ng-disabled="owner.busy" autofocus>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (ownerForm.email.$dirty && ownerForm.email.$invalid) || (!ownerForm.email.$dirty && owner.error.email) }">
|
||||
<label class="control-label" for="inputEmail">Email <sup><a ng-href="https://docs.cloudron.io/installation/#admin-account" class="help" target="_blank" tabIndex="-1"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<input type="email" class="form-control" ng-model="owner.email" id="inputEmail" name="email" placeholder="Email" required autocomplete="off" ng-disabled="owner.busy">
|
||||
<small>A valid email is required for Let's Encrypt certificates. This email is local to your Cloudron. </small>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (ownerForm.username.$dirty && ownerForm.username.$invalid) || (!ownerForm.username.$dirty && owner.error.username) }">
|
||||
<label class="control-label" for="inputUsername">Username</label>
|
||||
<input type="text" class="form-control" ng-model="owner.username" id="inputUsername" name="username" placeholder="Username" ng-maxlength="512" ng-minlength="1" required autocomplete="off" ng-disabled="owner.busy">
|
||||
<small>{{ owner.error.username }}</small>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 0;" ng-class="{ 'has-error': ownerForm.password.$dirty && ownerForm.password.$invalid }">
|
||||
<label class="control-label" for="inputPassword">Password</label>
|
||||
<input type="password" class="form-control" ng-model="owner.password" id="inputPassword" name="password" placeholder="Password" ng-pattern="/^.{8,}$/" required autocomplete="off" ng-disabled="owner.busy" password-reveal>
|
||||
<small><span ng-show="ownerForm.password.$dirty && ownerForm.password.$invalid">Password must be at least 8 characters</span> </small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<button type="submit" class="btn btn-success" ng-disabled="ownerForm.$invalid || owner.busy"><i class="fa fa-circle-notch fa-spin" ng-show="owner.busy"></i> Create Admin</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="view === 'finished'">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="card" style="max-width: none; padding: 20px 40px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<h1>Cloudron is ready to use</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
Before you start:
|
||||
<ul class="fa-ul">
|
||||
<li><i class="fa-li fa fa-users"></i>
|
||||
<b>User management</b>: Cloudron has a central user directory. When installing an app,
|
||||
you can set it up to authenticate against this directory.
|
||||
</li>
|
||||
<br/>
|
||||
<li><i class="fa-li fa fa-envelope-open"></i>
|
||||
<b>Email Configuration</b>: Apps are configured to send email based on the settings in the Email view.
|
||||
This saves you the trouble of having to configure mail settings inside each app.
|
||||
</li>
|
||||
<br/>
|
||||
<li><i class="fa-li fa fa-archive"></i>
|
||||
<b>Backups</b>: Store your backups on storage services completely independent from your server.
|
||||
You can use backups to seamlessly migrate your setup to another server.
|
||||
</li>
|
||||
<br/>
|
||||
<li><i class="fa-li fa fa-birthday-cake"></i>
|
||||
<b>Updates</b>: The Cloudron team tracks upstream releases and publishes app updates after testing.
|
||||
Your apps are kept fresh & secure.
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<a class="btn btn-success" ng-href="firstTimeLoginUrl">Proceed to Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="text-center">
|
||||
<span class="text-muted">©2022 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
|
||||
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Cloudron Admin Setup</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" style="overflow: hidden; height: 100%;"></div>
|
||||
<script type="module" src="/src/activation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<script>
|
||||
|
||||
var tmp = window.location.hash.slice(1).split('&');
|
||||
const tmp = window.location.hash.slice(1).split('&');
|
||||
|
||||
// FIXME: implicit flow (response_type=code token) results in access_token query param. this is not secure
|
||||
tmp.forEach(function (pair) {
|
||||
if (pair.indexOf('access_token=') === 0) localStorage.token = pair.split('=')[1];
|
||||
});
|
||||
|
||||
var redirectTo = '/';
|
||||
let redirectTo = '/';
|
||||
if (localStorage.getItem('redirectToHash')) {
|
||||
redirectTo += localStorage.getItem('redirectToHash');
|
||||
localStorage.removeItem('redirectToHash');
|
||||
}
|
||||
window.location.href = redirectTo;
|
||||
|
||||
window.location.replace(redirectTo); // this removes us from history
|
||||
|
||||
</script>
|
||||
|
||||
@@ -5,10 +5,7 @@ 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
|
||||
npm run build
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import globals from 'globals';
|
||||
import js from '@eslint/js';
|
||||
import js from '@eslint/js';
|
||||
import pluginVue from 'eslint-plugin-vue';
|
||||
import globals from 'globals';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
{
|
||||
files: ["**/*.js"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
ecmaVersion: 13,
|
||||
sourceType: 'module'
|
||||
globals: globals.browser,
|
||||
},
|
||||
rules: {
|
||||
semi: "error",
|
||||
"prefer-const": "error"
|
||||
"semi": "error",
|
||||
"prefer-const": "error",
|
||||
"vue/no-reserved-component-names": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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,211 +1,10 @@
|
||||
<!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 -->
|
||||
<!-- for now we need this in a static non transformed index.js file -->
|
||||
<script> window.VITE_CACHE_ID = '%VITE_CACHE_ID%' </script>
|
||||
<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 lang="en">
|
||||
<head>
|
||||
<title><%= name %> Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" style="overflow: hidden; height: 100%;"></div>
|
||||
<script type="module" src="/src/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
function injectMetaTags(templates) {
|
||||
let config;
|
||||
let template = fs.readFileSync(path.resolve('./src/meta-tags-header.html'), 'utf8');
|
||||
|
||||
return {
|
||||
name: 'inject-meta-tags',
|
||||
configResolved(resolvedConfig) {
|
||||
config = resolvedConfig;
|
||||
|
||||
// remove all ejs content in dev mode
|
||||
if (!config.isProduction) template = template.replace(/<%[^]*?%>/g, '');
|
||||
},
|
||||
transformIndexHtml: {
|
||||
handler: function transform(html, ctx) {
|
||||
if (templates.indexOf(ctx.filename) === -1) return html;
|
||||
|
||||
html = html.replace(/<head(.*)>/i, `$&\n${template}`);
|
||||
|
||||
return html;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default injectMetaTags;
|
||||
@@ -1,17 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><%= name %> OpenID Error</title>
|
||||
|
||||
<script>
|
||||
window.cloudron = {};
|
||||
window.cloudron.name = '<%= name %>';
|
||||
window.cloudron.iconUrl = '<%- iconUrl %>';
|
||||
window.cloudron.errorMessage = `<%- errorMessage %>`;
|
||||
window.cloudron.footer = `<%- footer -%>`;
|
||||
window.cloudron.language = `<%= language %>`;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/oidcerror.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><%= name %> OpenID Access Denied</title>
|
||||
|
||||
<script>
|
||||
window.cloudron = {};
|
||||
window.cloudron.name = '<%= name %>';
|
||||
window.cloudron.iconUrl = '<%- iconUrl %>';
|
||||
window.cloudron.submitUrl = '<%- submitUrl %>';
|
||||
window.cloudron.footer = `<%- footer -%>`;
|
||||
window.cloudron.language = `<%= language %>`;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/oidcinteractionabort.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Authorize <%= name %></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<form id="submitForm" method="post" action="<%- submitUrl %>">
|
||||
<!-- <button type="submit"></button> -->
|
||||
</form>
|
||||
|
||||
<script>
|
||||
|
||||
document.getElementById('submitForm').submit();
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><%= name %> Login</title>
|
||||
|
||||
<script>
|
||||
window.cloudron = {};
|
||||
window.cloudron.name = '<%= name %>';
|
||||
window.cloudron.note = '<%- note %>';
|
||||
window.cloudron.submitUrl = '<%- submitUrl %>';
|
||||
window.cloudron.iconUrl = '<%- iconUrl %>';
|
||||
window.cloudron.footer = `<%- footer -%>`;
|
||||
window.cloudron.language = `<%= language %>`;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/login.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,31 +3,30 @@
|
||||
"scripts": {
|
||||
"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"
|
||||
"build": "rm -rf ./dist/* && vite build --config ./vite.config.mjs && vite build --config ./vite.proxyauth.config.mjs"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@eslint/js": "^9.16.0",
|
||||
"@fontsource/noto-sans": "^5.1.0",
|
||||
"@fortawesome/fontawesome-free": "^6.7.1",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@cloudron/pankow": "^3.5.3",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@fortawesome/fontawesome-free": "^7.1.0",
|
||||
"@vitejs/plugin-vue": "^6.0.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.4.7",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"filesize": "^10.1.6",
|
||||
"jquery": "^3.7.1",
|
||||
"marked": "^15.0.3",
|
||||
"anser": "^2.3.2",
|
||||
"async": "^3.2.6",
|
||||
"chart.js": "^4.5.1",
|
||||
"chartjs-plugin-annotation": "^3.1.0",
|
||||
"eslint": "^9.38.0",
|
||||
"eslint-plugin-vue": "^10.5.1",
|
||||
"marked": "^16.4.1",
|
||||
"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"
|
||||
"moment-timezone": "^0.6.0",
|
||||
"vite": "^7.1.10",
|
||||
"vite-plugin-singlefile": "^2.3.0",
|
||||
"vue": "^3.5.22",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-router": "^4.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,165 +1,20 @@
|
||||
<!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" />
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><%= name %> Password Reset</title>
|
||||
|
||||
<!-- 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">
|
||||
<script>
|
||||
window.cloudron = {};
|
||||
window.cloudron.name = '<%= name %>';
|
||||
window.cloudron.footer = `<%- footer -%>`;
|
||||
window.cloudron.language = `<%= language %>`;
|
||||
</script>
|
||||
|
||||
<link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
</head>
|
||||
|
||||
<!-- contains all thing already using import statement -->
|
||||
<script type="module" src="./src/modules.js"></script>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/passwordreset.js"></script>
|
||||
</body>
|
||||
|
||||
<!-- Theme CSS -->
|
||||
<link type="text/css" rel="stylesheet" href="./src/theme.scss">
|
||||
|
||||
<!-- jQuery-->
|
||||
<script type="text/javascript" src="/js/jquery.min.js"></script>
|
||||
|
||||
<!-- async -->
|
||||
<script type="text/javascript" src="/js/async-3.2.0.min.js"></script>
|
||||
|
||||
<!-- Showdown (markdown converter) -->
|
||||
<script type="text/javascript" src="/js/showdown-1.9.1.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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<body ng-app="Application" ng-controller="PasswordResetController">
|
||||
|
||||
<div class="layout-root ng-cloak" ng-show="initialized">
|
||||
|
||||
<div class="layout-content" ng-show="mode === 'passwordReset'">
|
||||
<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="/api/v1/cloudron/avatar"/>
|
||||
<br/>
|
||||
<h2>{{ 'passwordReset.title' | tr }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form name="passwordResetForm" ng-submit="onPasswordReset()">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputPasswordResetIdentifier">{{ 'passwordReset.usernameOrEmail' | tr }}</label>
|
||||
<input type="text" class="form-control" id="inputPasswordResetIdentifier" name="passwordResetIdentifier" ng-model="passwordResetIdentifier" ng-disabled="busy" autofocus required>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="card-form-bottom-bar">
|
||||
<a href="/" class="hand">{{ 'passwordReset.backToLoginAction' | tr }}</a>
|
||||
<button class="btn btn-primary btn-outline" type="submit" ng-disabled="busy || passwordResetForm.$invalid"><i class="fa fa-circle-notch fa-spin" ng-show="busy"></i> {{ 'passwordReset.resetAction' | tr }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-content" ng-show="mode === 'passwordResetDone'">
|
||||
<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="/api/v1/cloudron/avatar"/>
|
||||
<br/>
|
||||
<h2 ng-hide="error">{{ 'passwordReset.emailSent.title' | tr }}</h2>
|
||||
<h4 ng-show="error" class="has-error">{{ error }}</h4>
|
||||
<br/>
|
||||
<a href="/" class="btn btn-primary">{{ 'passwordReset.backToLoginAction' | tr }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-content" ng-show="mode === 'newPassword'">
|
||||
<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="/api/v1/cloudron/avatar"/>
|
||||
<br/>
|
||||
<h2>{{ 'passwordReset.newPassword.title' | tr }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4 class="has-error" ng-show="error">{{ error }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form name="newPasswordForm" ng-submit="onNewPassword()">
|
||||
<input type="password" style="display: none;"/>
|
||||
<div class="form-group" ng-class="{ 'has-error': newPasswordForm.newPassword.$dirty && newPasswordForm.newPassword.$invalid }">
|
||||
<label class="control-label" for="inputNewPassword">{{ 'passwordReset.newPassword.password' | tr }}</label>
|
||||
<div class="control-label" ng-show="newPasswordForm.newPassword.$dirty && newPasswordForm.newPassword.$invalid">
|
||||
<small ng-show="newPasswordForm.newPassword.$dirty && newPasswordForm.newPassword.$invalid">{{ 'passwordReset.newPassword.errorLength' | tr }}</small>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="inputNewPassword" ng-model="newPassword" name="newPassword" ng-minlength="8" ng-maxlength="256" autofocus required password-reveal>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': newPasswordForm.newPasswordRepeat.$dirty && (newPassword !== newPasswordRepeat) }">
|
||||
<label class="control-label" for="inputNewPasswordRepeat">{{ 'passwordReset.newPassword.passwordRepeat' | tr }}</label>
|
||||
<div class="control-label" ng-show="newPasswordForm.newPasswordRepeat.$dirty && (newPassword !== newPasswordRepeat)">
|
||||
<small ng-show="newPasswordForm.newPasswordRepeat.$dirty && (newPassword !== newPasswordRepeat)">{{ 'passwordReset.newPassword.errorMismatch' | tr }}</small>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="inputNewPasswordRepeat" ng-model="newPasswordRepeat" name="newPasswordRepeat" required password-reveal>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputPasswordResetTotpToken">{{ 'login.2faToken' | tr }}</label>
|
||||
<input type="text" class="form-control" name="passwordResetTotpToken" id="inputPasswordResetTotpToken" ng-model="totpToken" ng-disabled="busy" value="">
|
||||
</div>
|
||||
<div class="card-form-bottom-bar">
|
||||
<a href="/" class="hand">{{ 'passwordReset.backToLoginAction' | tr }}</a>
|
||||
<button class="btn btn-primary btn-outline" type="submit" ng-disabled="busy || newPasswordForm.$invalid || newPassword !== newPasswordRepeat"><i class="fa fa-circle-notch fa-spin" ng-show="busy"></i> {{ 'passwordReset.passwordChanged.submitAction' | tr }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-content" ng-show="mode === 'newPasswordDone'">
|
||||
<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="/api/v1/cloudron/avatar"/>
|
||||
<br/>
|
||||
<h2>{{ 'passwordReset.success.title' | tr }}</h2>
|
||||
<br/>
|
||||
<a href="/" class="btn btn-primary">{{ 'passwordReset.success.openDashboardAction' | tr }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="text-center">
|
||||
<span class="text-muted" ng-bind-html="branding.footer | markdown2html"></span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><%= name %> Login</title>
|
||||
|
||||
<script>
|
||||
window.cloudron = {};
|
||||
window.cloudron.name = '<%= name %>';
|
||||
window.cloudron.iconUrl = '<%- iconUrl %>';
|
||||
window.cloudron.loginUrl = '<%- loginUrl %>';
|
||||
window.cloudron.language = `<%= language %>`;
|
||||
window.cloudron.apiOrigin = `<%= apiOrigin %>`;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/proxyauth.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* angular-ui-notification - Angular.js service providing simple notifications using Bootstrap 3 styles with css transitions for animating
|
||||
* @author Alex_Crack
|
||||
* @version v0.3.6
|
||||
* @link https://github.com/alexcrack/angular-ui-notification
|
||||
* @license MIT
|
||||
*/
|
||||
.ui-notification
|
||||
{
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
|
||||
width: 300px;
|
||||
|
||||
-webkit-transition: all ease .5s;
|
||||
-o-transition: all ease .5s;
|
||||
transition: all ease .5s;
|
||||
|
||||
color: #fff;
|
||||
border-radius: 0;
|
||||
background: #337ab7;
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, .3);
|
||||
}
|
||||
.ui-notification.clickable
|
||||
{
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-notification.clickable:hover
|
||||
{
|
||||
opacity: .7;
|
||||
}
|
||||
.ui-notification.killed
|
||||
{
|
||||
-webkit-transition: opacity ease 1s;
|
||||
-o-transition: opacity ease 1s;
|
||||
transition: opacity ease 1s;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
.ui-notification > h3
|
||||
{
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
|
||||
display: block;
|
||||
|
||||
margin: 10px 10px 0 10px;
|
||||
padding: 0 0 5px 0;
|
||||
|
||||
text-align: left;
|
||||
|
||||
border-bottom: 1px solid rgba(255, 255, 255, .3);
|
||||
}
|
||||
.ui-notification a
|
||||
{
|
||||
color: #fff;
|
||||
}
|
||||
.ui-notification a:hover
|
||||
{
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ui-notification > .message
|
||||
{
|
||||
margin: 10px 10px 10px 10px;
|
||||
}
|
||||
.ui-notification.warning
|
||||
{
|
||||
color: #fff;
|
||||
background: #f0ad4e;
|
||||
}
|
||||
.ui-notification.error
|
||||
{
|
||||
color: #fff;
|
||||
background: #d9534f;
|
||||
}
|
||||
.ui-notification.success
|
||||
{
|
||||
color: #fff;
|
||||
background: #5cb85c;
|
||||
}
|
||||
.ui-notification.info
|
||||
{
|
||||
color: #fff;
|
||||
background: #5bc0de;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="546.13336"
|
||||
height="546.13336"
|
||||
viewBox="0 0 512.00001 512.00001"
|
||||
id="svg4519"
|
||||
version="1.1"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
sodipodi:docname="app_down.svg"
|
||||
inkscape:export-filename="/home/nebulon/Cloudron/Assets/logo_115.png"
|
||||
inkscape:export-xdpi="20.214842"
|
||||
inkscape:export-ydpi="20.214842"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
|
||||
id="defs4521" /><sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="250.01276"
|
||||
inkscape:cy="238.90108"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g4496"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1014"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" /><metadata
|
||||
id="metadata4524"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-540.36216)"><g
|
||||
id="g4467"
|
||||
transform="matrix(20.50952,0,0,20.859456,-526.58031,-94.042799)"><g
|
||||
inkscape:export-ydpi="67.349998"
|
||||
inkscape:export-xdpi="67.349998"
|
||||
transform="matrix(0.59473169,0,0,0.59473169,31.04719,102.48374)"
|
||||
id="g4382"><g
|
||||
id="g4496"><path
|
||||
sodipodi:type="star"
|
||||
style="opacity:1;fill:#7c7c7c;fill-opacity:1;stroke:none;stroke-width:1.10000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4162"
|
||||
sodipodi:sides="6"
|
||||
sodipodi:cx="12.46875"
|
||||
sodipodi:cy="-99.893143"
|
||||
sodipodi:r1="19.266006"
|
||||
sodipodi:r2="16.307295"
|
||||
sodipodi:arg1="-0.52224059"
|
||||
sodipodi:arg2="0.0013581913"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0.12490573"
|
||||
inkscape:randomized="0"
|
||||
d="m 29.166669,-109.50348 c 1.200386,2.08567 1.17988,17.183595 -0.02617,19.265993 -1.206046,2.082397 -14.291486,9.613601 -16.697919,9.610333 -2.406432,-0.0033 -15.4713664,-7.56999 -16.671752,-9.655655 -1.2003857,-2.085666 -1.1798799,-17.183591 0.026167,-19.265991 1.2060467,-2.0824 14.2914862,-9.6136 16.6979192,-9.61033 2.406432,0.003 15.471366,7.56999 16.671752,9.65565 z"
|
||||
transform="rotate(-30,10.993604,-99.259973)"
|
||||
inkscape:export-xdpi="67.349998"
|
||||
inkscape:export-ydpi="67.349998" /><rect
|
||||
inkscape:transform-center-x="0.40624986"
|
||||
ry="2.4183984"
|
||||
y="-104.9176"
|
||||
x="2.2207832"
|
||||
height="8.7434387"
|
||||
width="8.7434387"
|
||||
id="rect4168-1-1"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.29999995;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><g
|
||||
transform="translate(0,0.14463441)"
|
||||
id="g4491"><rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.29999995;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4168-1-1-7"
|
||||
width="8.7434387"
|
||||
height="8.7434387"
|
||||
x="9.0890703"
|
||||
y="-98.734459"
|
||||
ry="2.4183986" /><rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.29999995;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4168-1-1-7-2"
|
||||
width="8.7434387"
|
||||
height="8.7434387"
|
||||
x="9.0890703"
|
||||
y="-111.39002"
|
||||
ry="2.4183986" /></g><ellipse
|
||||
style="fill:#ca3636;stroke-width:1.36965;fill-opacity:1"
|
||||
id="path1"
|
||||
cx="23.01207"
|
||||
cy="-89.902901"
|
||||
rx="9.6073742"
|
||||
ry="9.4462013" /><path
|
||||
d="m 26.439777,-95.584412 c 0.161271,-0.368194 0.04101,-0.798203 -0.289743,-1.034712 -0.330744,-0.236504 -0.781758,-0.215002 -1.090634,0.04838 l -6.997553,6.020141 c -0.273341,0.236508 -0.371745,0.615454 -0.243273,0.948711 0.12847,0.333256 0.45648,0.559013 0.820025,0.559013 h 3.047762 l -2.101999,4.821487 c -0.161272,0.368197 -0.041,0.798206 0.289742,1.034712 0.330744,0.236506 0.781758,0.215005 1.090634,-0.04837 l 6.997553,-6.020141 c 0.273342,-0.236505 0.371745,-0.615454 0.243274,-0.948711 -0.12847,-0.333256 -0.453747,-0.556325 -0.820026,-0.556325 h -3.047762 z"
|
||||
id="path1-7"
|
||||
style="fill:#ffffff;stroke-width:0.027104" /></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="546.13336"
|
||||
height="546.13336"
|
||||
viewBox="0 0 512.00001 512.00001"
|
||||
id="svg4519"
|
||||
version="1.1"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
sodipodi:docname="not_found.svg"
|
||||
inkscape:export-filename="/home/nebulon/Cloudron/Assets/logo_115.png"
|
||||
inkscape:export-xdpi="20.214842"
|
||||
inkscape:export-ydpi="20.214842"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
|
||||
id="defs4521" /><sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="250.01276"
|
||||
inkscape:cy="239.91123"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g4496"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1014"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" /><metadata
|
||||
id="metadata4524"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-540.36216)"><g
|
||||
id="g4467"
|
||||
transform="matrix(20.50952,0,0,20.859456,-526.58031,-94.042799)"><g
|
||||
inkscape:export-ydpi="67.349998"
|
||||
inkscape:export-xdpi="67.349998"
|
||||
transform="matrix(0.59473169,0,0,0.59473169,31.04719,102.48374)"
|
||||
id="g4382"><g
|
||||
id="g4496"><path
|
||||
sodipodi:type="star"
|
||||
style="opacity:1;fill:#7c7c7c;fill-opacity:1;stroke:none;stroke-width:1.10000002;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4162"
|
||||
sodipodi:sides="6"
|
||||
sodipodi:cx="12.46875"
|
||||
sodipodi:cy="-99.893143"
|
||||
sodipodi:r1="19.266006"
|
||||
sodipodi:r2="16.307295"
|
||||
sodipodi:arg1="-0.52224059"
|
||||
sodipodi:arg2="0.0013581913"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0.12490573"
|
||||
inkscape:randomized="0"
|
||||
d="m 29.166669,-109.50348 c 1.200386,2.08567 1.17988,17.183595 -0.02617,19.265993 -1.206046,2.082397 -14.291486,9.613601 -16.697919,9.610333 -2.406432,-0.0033 -15.4713664,-7.56999 -16.671752,-9.655655 -1.2003857,-2.085666 -1.1798799,-17.183591 0.026167,-19.265991 1.2060467,-2.0824 14.2914862,-9.6136 16.6979192,-9.61033 2.406432,0.003 15.471366,7.56999 16.671752,9.65565 z"
|
||||
transform="rotate(-30,10.993604,-99.259973)"
|
||||
inkscape:export-xdpi="67.349998"
|
||||
inkscape:export-ydpi="67.349998" /><rect
|
||||
inkscape:transform-center-x="0.40624986"
|
||||
ry="2.4183984"
|
||||
y="-104.9176"
|
||||
x="2.2207832"
|
||||
height="8.7434387"
|
||||
width="8.7434387"
|
||||
id="rect4168-1-1"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.29999995;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><g
|
||||
transform="translate(0,0.14463441)"
|
||||
id="g4491"><rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.29999995;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4168-1-1-7"
|
||||
width="8.7434387"
|
||||
height="8.7434387"
|
||||
x="9.0890703"
|
||||
y="-98.734459"
|
||||
ry="2.4183986" /><rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.29999995;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4168-1-1-7-2"
|
||||
width="8.7434387"
|
||||
height="8.7434387"
|
||||
x="9.0890703"
|
||||
y="-111.39002"
|
||||
ry="2.4183986" /></g><ellipse
|
||||
style="fill:#1a76bf;stroke-width:1.36965;fill-opacity:1"
|
||||
id="path1"
|
||||
cx="23.01207"
|
||||
cy="-89.902901"
|
||||
rx="9.6073742"
|
||||
ry="9.4462013" /><path
|
||||
d="m 22.28174,-94.476029 h -3.651649 c -0.403963,0 -0.730329,0.320891 -0.730329,0.718076 v 1.436157 c 0,0.397187 0.326366,0.718079 0.730329,0.718079 h 8.613327 c 0.09585,0 0.189429,-0.03815 0.257897,-0.105469 l 1.095495,-1.077115 c 0.141502,-0.139127 0.141502,-0.368016 0,-0.507142 l -1.095495,-1.07712 c -0.06847,-0.06732 -0.162041,-0.105466 -0.257897,-0.105466 h -3.501017 c 0,-0.397187 -0.326367,-0.718075 -0.730331,-0.718075 -0.403964,0 -0.73033,0.320888 -0.73033,0.718075 z m 5.842639,5.026544 c 0,-0.397185 -0.326366,-0.718078 -0.730331,-0.718078 h -3.651647 v -0.718076 H 22.28174 v 0.718076 h -3.501017 c -0.09585,0 -0.18943,0.03815 -0.257898,0.105468 l -1.095496,1.077117 c -0.141501,0.139127 -0.141501,0.368014 0,0.507141 l 1.095496,1.077118 c 0.06847,0.06732 0.162042,0.105467 0.257898,0.105467 h 8.613325 c 0.403965,0 0.730331,-0.320892 0.730331,-0.718078 z m -4.381978,5.026544 v -2.154233 H 22.28174 v 2.154233 c 0,0.397187 0.326366,0.718078 0.73033,0.718078 0.403964,0 0.730331,-0.320891 0.730331,-0.718078 z"
|
||||
id="path1-3"
|
||||
style="fill:#ffffff;stroke-width:0.0226306" /></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
@@ -1,99 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* 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
|
||||
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,
|
||||
busy: false,
|
||||
|
||||
email: '',
|
||||
displayName: '',
|
||||
username: '',
|
||||
password: '',
|
||||
|
||||
submit: function () {
|
||||
$scope.owner.busy = true;
|
||||
$scope.owner.error = null;
|
||||
|
||||
var data = {
|
||||
username: $scope.owner.username,
|
||||
password: $scope.owner.password,
|
||||
email: $scope.owner.email,
|
||||
displayName: $scope.owner.displayName,
|
||||
setupToken: $scope.setupToken
|
||||
};
|
||||
|
||||
Client.createAdmin(data, function (error, autoLoginToken) {
|
||||
if (error && error.statusCode === 400) {
|
||||
$scope.owner.busy = false;
|
||||
|
||||
if (error.message === 'Invalid email') {
|
||||
$scope.owner.error = { email: error.message };
|
||||
$scope.owner.email = '';
|
||||
$scope.ownerForm.email.$setPristine();
|
||||
setTimeout(function () { $('#inputEmail').focus(); }, 200);
|
||||
} else {
|
||||
$scope.owner.error = { username: error.message };
|
||||
$scope.owner.username = '';
|
||||
$scope.ownerForm.username.$setPristine();
|
||||
setTimeout(function () { $('#inputUsername').focus(); }, 200);
|
||||
}
|
||||
return;
|
||||
} else if (error) {
|
||||
$scope.owner.busy = false;
|
||||
console.error('Internal error', error);
|
||||
$scope.owner.error = { generic: error.message };
|
||||
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 setView(view) {
|
||||
if (view === 'finished') {
|
||||
$scope.view = 'finished';
|
||||
} else {
|
||||
$scope.view = 'owner';
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
Client.getProvisionStatus(function (error, status) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
if (redirectIfNeeded(status, 'activation')) return; // redirected to some other view...
|
||||
|
||||
setView(search.view);
|
||||
|
||||
$scope.setupToken = search.setupToken;
|
||||
$scope.initialized = true;
|
||||
|
||||
// Ensure we have a good autofocus
|
||||
setTimeout(function () {
|
||||
$(document).find("[autofocus]:first").focus();
|
||||
}, 250);
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
}]);
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
AngularJS v1.5.8
|
||||
(c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(R,B){'use strict';function Da(a,b,c){if(!a)throw Ma("areq",b||"?",c||"required");return a}function Ea(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;Y(a)&&(a=a.join(" "));Y(b)&&(b=b.join(" "));return a+" "+b}function Na(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function Z(a,b,c){var d="";a=Y(a)?a:a&&G(a)&&a.length?a.split(/\s+/):[];s(a,function(a,l){a&&0<a.length&&(d+=0<l?" ":"",d+=c?b+a:a+b)});return d}function Oa(a){if(a instanceof F)switch(a.length){case 0:return a;
|
||||
case 1:if(1===a[0].nodeType)return a;break;default:return F(ta(a))}if(1===a.nodeType)return F(a)}function ta(a){if(!a[0])return a;for(var b=0;b<a.length;b++){var c=a[b];if(1==c.nodeType)return c}}function Pa(a,b,c){s(b,function(b){a.addClass(b,c)})}function Qa(a,b,c){s(b,function(b){a.removeClass(b,c)})}function V(a){return function(b,c){c.addClass&&(Pa(a,b,c.addClass),c.addClass=null);c.removeClass&&(Qa(a,b,c.removeClass),c.removeClass=null)}}function oa(a){a=a||{};if(!a.$$prepared){var b=a.domOperation||
|
||||
P;a.domOperation=function(){a.$$domOperationFired=!0;b();b=P};a.$$prepared=!0}return a}function ha(a,b){Fa(a,b);Ga(a,b)}function Fa(a,b){b.from&&(a.css(b.from),b.from=null)}function Ga(a,b){b.to&&(a.css(b.to),b.to=null)}function W(a,b,c){var d=b.options||{};c=c.options||{};var e=(d.addClass||"")+" "+(c.addClass||""),l=(d.removeClass||"")+" "+(c.removeClass||"");a=Ra(a.attr("class"),e,l);c.preparationClasses&&(d.preparationClasses=$(c.preparationClasses,d.preparationClasses),delete c.preparationClasses);
|
||||
e=d.domOperation!==P?d.domOperation:null;ua(d,c);e&&(d.domOperation=e);d.addClass=a.addClass?a.addClass:null;d.removeClass=a.removeClass?a.removeClass:null;b.addClass=d.addClass;b.removeClass=d.removeClass;return d}function Ra(a,b,c){function d(a){G(a)&&(a=a.split(" "));var b={};s(a,function(a){a.length&&(b[a]=!0)});return b}var e={};a=d(a);b=d(b);s(b,function(a,b){e[b]=1});c=d(c);s(c,function(a,b){e[b]=1===e[b]?null:-1});var l={addClass:"",removeClass:""};s(e,function(b,c){var d,e;1===b?(d="addClass",
|
||||
e=!a[c]||a[c+"-remove"]):-1===b&&(d="removeClass",e=a[c]||a[c+"-add"]);e&&(l[d].length&&(l[d]+=" "),l[d]+=c)});return l}function y(a){return a instanceof F?a[0]:a}function Sa(a,b,c){var d="";b&&(d=Z(b,"ng-",!0));c.addClass&&(d=$(d,Z(c.addClass,"-add")));c.removeClass&&(d=$(d,Z(c.removeClass,"-remove")));d.length&&(c.preparationClasses=d,a.addClass(d))}function pa(a,b){var c=b?"-"+b+"s":"";la(a,[ma,c]);return[ma,c]}function va(a,b){var c=b?"paused":"",d=aa+"PlayState";la(a,[d,c]);return[d,c]}function la(a,
|
||||
b){a.style[b[0]]=b[1]}function $(a,b){return a?b?a+" "+b:a:b}function Ha(a,b,c){var d=Object.create(null),e=a.getComputedStyle(b)||{};s(c,function(a,b){var c=e[a];if(c){var g=c.charAt(0);if("-"===g||"+"===g||0<=g)c=Ta(c);0===c&&(c=null);d[b]=c}});return d}function Ta(a){var b=0;a=a.split(/\s*,\s*/);s(a,function(a){"s"==a.charAt(a.length-1)&&(a=a.substring(0,a.length-1));a=parseFloat(a)||0;b=b?Math.max(a,b):a});return b}function wa(a){return 0===a||null!=a}function Ia(a,b){var c=S,d=a+"s";b?c+="Duration":
|
||||
d+=" linear all";return[c,d]}function Ja(){var a=Object.create(null);return{flush:function(){a=Object.create(null)},count:function(b){return(b=a[b])?b.total:0},get:function(b){return(b=a[b])&&b.value},put:function(b,c){a[b]?a[b].total++:a[b]={total:1,value:c}}}}function Ka(a,b,c){s(c,function(c){a[c]=xa(a[c])?a[c]:b.style.getPropertyValue(c)})}var S,ya,aa,za;void 0===R.ontransitionend&&void 0!==R.onwebkittransitionend?(S="WebkitTransition",ya="webkitTransitionEnd transitionend"):(S="transition",ya=
|
||||
"transitionend");void 0===R.onanimationend&&void 0!==R.onwebkitanimationend?(aa="WebkitAnimation",za="webkitAnimationEnd animationend"):(aa="animation",za="animationend");var qa=aa+"Delay",Aa=aa+"Duration",ma=S+"Delay",La=S+"Duration",Ma=B.$$minErr("ng"),Ua={transitionDuration:La,transitionDelay:ma,transitionProperty:S+"Property",animationDuration:Aa,animationDelay:qa,animationIterationCount:aa+"IterationCount"},Va={transitionDuration:La,transitionDelay:ma,animationDuration:Aa,animationDelay:qa},
|
||||
Ba,ua,s,Y,xa,ea,Ca,ba,G,J,F,P;B.module("ngAnimate",[],function(){P=B.noop;Ba=B.copy;ua=B.extend;F=B.element;s=B.forEach;Y=B.isArray;G=B.isString;ba=B.isObject;J=B.isUndefined;xa=B.isDefined;Ca=B.isFunction;ea=B.isElement}).directive("ngAnimateSwap",["$animate","$rootScope",function(a,b){return{restrict:"A",transclude:"element",terminal:!0,priority:600,link:function(b,d,e,l,n){var I,g;b.$watchCollection(e.ngAnimateSwap||e["for"],function(e){I&&a.leave(I);g&&(g.$destroy(),g=null);if(e||0===e)g=b.$new(),
|
||||
n(g,function(b){I=b;a.enter(b,null,d)})})}}}]).directive("ngAnimateChildren",["$interpolate",function(a){return{link:function(b,c,d){function e(a){c.data("$$ngAnimateChildren","on"===a||"true"===a)}var l=d.ngAnimateChildren;G(l)&&0===l.length?c.data("$$ngAnimateChildren",!0):(e(a(l)(b)),d.$observe("ngAnimateChildren",e))}}}]).factory("$$rAFScheduler",["$$rAF",function(a){function b(a){d=d.concat(a);c()}function c(){if(d.length){for(var b=d.shift(),n=0;n<b.length;n++)b[n]();e||a(function(){e||c()})}}
|
||||
var d,e;d=b.queue=[];b.waitUntilQuiet=function(b){e&&e();e=a(function(){e=null;b();c()})};return b}]).provider("$$animateQueue",["$animateProvider",function(a){function b(a){if(!a)return null;a=a.split(" ");var b=Object.create(null);s(a,function(a){b[a]=!0});return b}function c(a,c){if(a&&c){var d=b(c);return a.split(" ").some(function(a){return d[a]})}}function d(a,b,c,d){return l[a].some(function(a){return a(b,c,d)})}function e(a,b){var c=0<(a.addClass||"").length,d=0<(a.removeClass||"").length;
|
||||
return b?c&&d:c||d}var l=this.rules={skip:[],cancel:[],join:[]};l.join.push(function(a,b,c){return!b.structural&&e(b)});l.skip.push(function(a,b,c){return!b.structural&&!e(b)});l.skip.push(function(a,b,c){return"leave"==c.event&&b.structural});l.skip.push(function(a,b,c){return c.structural&&2===c.state&&!b.structural});l.cancel.push(function(a,b,c){return c.structural&&b.structural});l.cancel.push(function(a,b,c){return 2===c.state&&b.structural});l.cancel.push(function(a,b,d){if(d.structural)return!1;
|
||||
a=b.addClass;b=b.removeClass;var e=d.addClass;d=d.removeClass;return J(a)&&J(b)||J(e)&&J(d)?!1:c(a,d)||c(b,e)});this.$get=["$$rAF","$rootScope","$rootElement","$document","$$HashMap","$$animation","$$AnimateRunner","$templateRequest","$$jqLite","$$forceReflow",function(b,c,g,l,C,Wa,Q,t,H,T){function O(){var a=!1;return function(b){a?b():c.$$postDigest(function(){a=!0;b()})}}function x(a,b,c){var f=y(b),d=y(a),N=[];(a=h[c])&&s(a,function(a){w.call(a.node,f)?N.push(a.callback):"leave"===c&&w.call(a.node,
|
||||
d)&&N.push(a.callback)});return N}function r(a,b,c){var f=ta(b);return a.filter(function(a){return!(a.node===f&&(!c||a.callback===c))})}function p(a,h,v){function r(c,f,d,h){sa(function(){var c=x(T,a,f);c.length?b(function(){s(c,function(b){b(a,d,h)});"close"!==d||a[0].parentNode||ra.off(a)}):"close"!==d||a[0].parentNode||ra.off(a)});c.progress(f,d,h)}function k(b){var c=a,f=m;f.preparationClasses&&(c.removeClass(f.preparationClasses),f.preparationClasses=null);f.activeClasses&&(c.removeClass(f.activeClasses),
|
||||
f.activeClasses=null);E(a,m);ha(a,m);m.domOperation();A.complete(!b)}var m=Ba(v),p,T;if(a=Oa(a))p=y(a),T=a.parent();var m=oa(m),A=new Q,sa=O();Y(m.addClass)&&(m.addClass=m.addClass.join(" "));m.addClass&&!G(m.addClass)&&(m.addClass=null);Y(m.removeClass)&&(m.removeClass=m.removeClass.join(" "));m.removeClass&&!G(m.removeClass)&&(m.removeClass=null);m.from&&!ba(m.from)&&(m.from=null);m.to&&!ba(m.to)&&(m.to=null);if(!p)return k(),A;v=[p.className,m.addClass,m.removeClass].join(" ");if(!Xa(v))return k(),
|
||||
A;var g=0<=["enter","move","leave"].indexOf(h),w=l[0].hidden,t=!f||w||N.get(p);v=!t&&z.get(p)||{};var H=!!v.state;t||H&&1==v.state||(t=!M(a,T,h));if(t)return w&&r(A,h,"start"),k(),w&&r(A,h,"close"),A;g&&K(a);w={structural:g,element:a,event:h,addClass:m.addClass,removeClass:m.removeClass,close:k,options:m,runner:A};if(H){if(d("skip",a,w,v)){if(2===v.state)return k(),A;W(a,v,w);return v.runner}if(d("cancel",a,w,v))if(2===v.state)v.runner.end();else if(v.structural)v.close();else return W(a,v,w),v.runner;
|
||||
else if(d("join",a,w,v))if(2===v.state)W(a,w,{});else return Sa(a,g?h:null,m),h=w.event=v.event,m=W(a,v,w),v.runner}else W(a,w,{});(H=w.structural)||(H="animate"===w.event&&0<Object.keys(w.options.to||{}).length||e(w));if(!H)return k(),ka(a),A;var C=(v.counter||0)+1;w.counter=C;L(a,1,w);c.$$postDigest(function(){var b=z.get(p),c=!b,b=b||{},f=0<(a.parent()||[]).length&&("animate"===b.event||b.structural||e(b));if(c||b.counter!==C||!f){c&&(E(a,m),ha(a,m));if(c||g&&b.event!==h)m.domOperation(),A.end();
|
||||
f||ka(a)}else h=!b.structural&&e(b,!0)?"setClass":b.event,L(a,2),b=Wa(a,h,b.options),A.setHost(b),r(A,h,"start",{}),b.done(function(b){k(!b);(b=z.get(p))&&b.counter===C&&ka(y(a));r(A,h,"close",{})})});return A}function K(a){a=y(a).querySelectorAll("[data-ng-animate]");s(a,function(a){var b=parseInt(a.getAttribute("data-ng-animate")),c=z.get(a);if(c)switch(b){case 2:c.runner.end();case 1:z.remove(a)}})}function ka(a){a=y(a);a.removeAttribute("data-ng-animate");z.remove(a)}function k(a,b){return y(a)===
|
||||
y(b)}function M(a,b,c){c=F(l[0].body);var f=k(a,c)||"HTML"===a[0].nodeName,d=k(a,g),h=!1,r,e=N.get(y(a));(a=F.data(a[0],"$ngAnimatePin"))&&(b=a);for(b=y(b);b;){d||(d=k(b,g));if(1!==b.nodeType)break;a=z.get(b)||{};if(!h){var p=N.get(b);if(!0===p&&!1!==e){e=!0;break}else!1===p&&(e=!1);h=a.structural}if(J(r)||!0===r)a=F.data(b,"$$ngAnimateChildren"),xa(a)&&(r=a);if(h&&!1===r)break;f||(f=k(b,c));if(f&&d)break;if(!d&&(a=F.data(b,"$ngAnimatePin"))){b=y(a);continue}b=b.parentNode}return(!h||r)&&!0!==e&&
|
||||
d&&f}function L(a,b,c){c=c||{};c.state=b;a=y(a);a.setAttribute("data-ng-animate",b);c=(b=z.get(a))?ua(b,c):c;z.put(a,c)}var z=new C,N=new C,f=null,A=c.$watch(function(){return 0===t.totalPendingRequests},function(a){a&&(A(),c.$$postDigest(function(){c.$$postDigest(function(){null===f&&(f=!0)})}))}),h=Object.create(null),sa=a.classNameFilter(),Xa=sa?function(a){return sa.test(a)}:function(){return!0},E=V(H),w=R.Node.prototype.contains||function(a){return this===a||!!(this.compareDocumentPosition(a)&
|
||||
16)},ra={on:function(a,b,c){var f=ta(b);h[a]=h[a]||[];h[a].push({node:f,callback:c});F(b).on("$destroy",function(){z.get(f)||ra.off(a,b,c)})},off:function(a,b,c){if(1!==arguments.length||G(arguments[0])){var f=h[a];f&&(h[a]=1===arguments.length?null:r(f,b,c))}else for(f in b=arguments[0],h)h[f]=r(h[f],b)},pin:function(a,b){Da(ea(a),"element","not an element");Da(ea(b),"parentElement","not an element");a.data("$ngAnimatePin",b)},push:function(a,b,c,f){c=c||{};c.domOperation=f;return p(a,b,c)},enabled:function(a,
|
||||
b){var c=arguments.length;if(0===c)b=!!f;else if(ea(a)){var d=y(a);1===c?b=!N.get(d):N.put(d,!b)}else b=f=!!a;return b}};return ra}]}]).provider("$$animation",["$animateProvider",function(a){var b=this.drivers=[];this.$get=["$$jqLite","$rootScope","$injector","$$AnimateRunner","$$HashMap","$$rAFScheduler",function(a,d,e,l,n,I){function g(a){function b(a){if(a.processed)return a;a.processed=!0;var d=a.domNode,p=d.parentNode;e.put(d,a);for(var K;p;){if(K=e.get(p)){K.processed||(K=b(K));break}p=p.parentNode}(K||
|
||||
c).children.push(a);return a}var c={children:[]},d,e=new n;for(d=0;d<a.length;d++){var g=a[d];e.put(g.domNode,a[d]={domNode:g.domNode,fn:g.fn,children:[]})}for(d=0;d<a.length;d++)b(a[d]);return function(a){var b=[],c=[],d;for(d=0;d<a.children.length;d++)c.push(a.children[d]);a=c.length;var e=0,k=[];for(d=0;d<c.length;d++){var g=c[d];0>=a&&(a=e,e=0,b.push(k),k=[]);k.push(g.fn);g.children.forEach(function(a){e++;c.push(a)});a--}k.length&&b.push(k);return b}(c)}var u=[],C=V(a);return function(n,Q,t){function H(a){a=
|
||||
a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];s(a,function(a){var c=a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function T(a){var b=[],c={};s(a,function(a,d){var h=y(a.element),e=0<=["enter","move"].indexOf(a.event),h=a.structural?H(h):[];if(h.length){var k=e?"to":"from";s(h,function(a){var b=a.getAttribute("ng-animate-ref");c[b]=c[b]||{};c[b][k]={animationID:d,element:F(a)}})}else b.push(a)});var d={},e={};s(c,function(c,k){var r=c.from,
|
||||
p=c.to;if(r&&p){var z=a[r.animationID],g=a[p.animationID],A=r.animationID.toString();if(!e[A]){var n=e[A]={structural:!0,beforeStart:function(){z.beforeStart();g.beforeStart()},close:function(){z.close();g.close()},classes:O(z.classes,g.classes),from:z,to:g,anchors:[]};n.classes.length?b.push(n):(b.push(z),b.push(g))}e[A].anchors.push({out:r.element,"in":p.element})}else r=r?r.animationID:p.animationID,p=r.toString(),d[p]||(d[p]=!0,b.push(a[r]))});return b}function O(a,b){a=a.split(" ");b=b.split(" ");
|
||||
for(var c=[],d=0;d<a.length;d++){var e=a[d];if("ng-"!==e.substring(0,3))for(var r=0;r<b.length;r++)if(e===b[r]){c.push(e);break}}return c.join(" ")}function x(a){for(var c=b.length-1;0<=c;c--){var d=e.get(b[c])(a);if(d)return d}}function r(a,b){function c(a){(a=a.data("$$animationRunner"))&&a.setHost(b)}a.from&&a.to?(c(a.from.element),c(a.to.element)):c(a.element)}function p(){var a=n.data("$$animationRunner");!a||"leave"===Q&&t.$$domOperationFired||a.end()}function K(b){n.off("$destroy",p);n.removeData("$$animationRunner");
|
||||
C(n,t);ha(n,t);t.domOperation();L&&a.removeClass(n,L);n.removeClass("ng-animate");k.complete(!b)}t=oa(t);var ka=0<=["enter","move","leave"].indexOf(Q),k=new l({end:function(){K()},cancel:function(){K(!0)}});if(!b.length)return K(),k;n.data("$$animationRunner",k);var M=Ea(n.attr("class"),Ea(t.addClass,t.removeClass)),L=t.tempClasses;L&&(M+=" "+L,t.tempClasses=null);var z;ka&&(z="ng-"+Q+"-prepare",a.addClass(n,z));u.push({element:n,classes:M,event:Q,structural:ka,options:t,beforeStart:function(){n.addClass("ng-animate");
|
||||
L&&a.addClass(n,L);z&&(a.removeClass(n,z),z=null)},close:K});n.on("$destroy",p);if(1<u.length)return k;d.$$postDigest(function(){var a=[];s(u,function(b){b.element.data("$$animationRunner")?a.push(b):b.close()});u.length=0;var b=T(a),c=[];s(b,function(a){c.push({domNode:y(a.from?a.from.element:a.element),fn:function(){a.beforeStart();var b,c=a.close;if((a.anchors?a.from.element||a.to.element:a.element).data("$$animationRunner")){var d=x(a);d&&(b=d.start)}b?(b=b(),b.done(function(a){c(!a)}),r(a,b)):
|
||||
c()}})});I(g(c))});return k}}]}]).provider("$animateCss",["$animateProvider",function(a){var b=Ja(),c=Ja();this.$get=["$window","$$jqLite","$$AnimateRunner","$timeout","$$forceReflow","$sniffer","$$rAFScheduler","$$animateQueue",function(a,e,l,n,I,g,u,C){function B(a,b){var c=a.parentNode;return(c.$$ngAnimateParentKey||(c.$$ngAnimateParentKey=++O))+"-"+a.getAttribute("class")+"-"+b}function Q(r,p,g,n){var k;0<b.count(g)&&(k=c.get(g),k||(p=Z(p,"-stagger"),e.addClass(r,p),k=Ha(a,r,n),k.animationDuration=
|
||||
Math.max(k.animationDuration,0),k.transitionDuration=Math.max(k.transitionDuration,0),e.removeClass(r,p),c.put(g,k)));return k||{}}function t(a){x.push(a);u.waitUntilQuiet(function(){b.flush();c.flush();for(var a=I(),d=0;d<x.length;d++)x[d](a);x.length=0})}function H(c,e,g){e=b.get(g);e||(e=Ha(a,c,Ua),"infinite"===e.animationIterationCount&&(e.animationIterationCount=1));b.put(g,e);c=e;g=c.animationDelay;e=c.transitionDelay;c.maxDelay=g&&e?Math.max(g,e):g||e;c.maxDuration=Math.max(c.animationDuration*
|
||||
c.animationIterationCount,c.transitionDuration);return c}var T=V(e),O=0,x=[];return function(a,c){function d(){k()}function u(){k(!0)}function k(b){if(!(w||F&&O)){w=!0;O=!1;f.$$skipPreparationClasses||e.removeClass(a,ga);e.removeClass(a,ea);va(h,!1);pa(h,!1);s(x,function(a){h.style[a[0]]=""});T(a,f);ha(a,f);Object.keys(A).length&&s(A,function(a,b){a?h.style.setProperty(b,a):h.style.removeProperty(b)});if(f.onDone)f.onDone();fa&&fa.length&&a.off(fa.join(" "),z);var c=a.data("$$animateCss");c&&(n.cancel(c[0].timer),
|
||||
a.removeData("$$animateCss"));G&&G.complete(!b)}}function M(a){q.blockTransition&&pa(h,a);q.blockKeyframeAnimation&&va(h,!!a)}function L(){G=new l({end:d,cancel:u});t(P);k();return{$$willAnimate:!1,start:function(){return G},end:d}}function z(a){a.stopPropagation();var b=a.originalEvent||a;a=b.$manualTimeStamp||Date.now();b=parseFloat(b.elapsedTime.toFixed(3));Math.max(a-W,0)>=R&&b>=m&&(F=!0,k())}function N(){function b(){if(!w){M(!1);s(x,function(a){h.style[a[0]]=a[1]});T(a,f);e.addClass(a,ea);if(q.recalculateTimingStyles){na=
|
||||
h.className+" "+ga;ia=B(h,na);D=H(h,na,ia);ca=D.maxDelay;J=Math.max(ca,0);m=D.maxDuration;if(0===m){k();return}q.hasTransitions=0<D.transitionDuration;q.hasAnimations=0<D.animationDuration}q.applyAnimationDelay&&(ca="boolean"!==typeof f.delay&&wa(f.delay)?parseFloat(f.delay):ca,J=Math.max(ca,0),D.animationDelay=ca,da=[qa,ca+"s"],x.push(da),h.style[da[0]]=da[1]);R=1E3*J;V=1E3*m;if(f.easing){var d,g=f.easing;q.hasTransitions&&(d=S+"TimingFunction",x.push([d,g]),h.style[d]=g);q.hasAnimations&&(d=aa+
|
||||
"TimingFunction",x.push([d,g]),h.style[d]=g)}D.transitionDuration&&fa.push(ya);D.animationDuration&&fa.push(za);W=Date.now();var p=R+1.5*V;d=W+p;var g=a.data("$$animateCss")||[],N=!0;if(g.length){var l=g[0];(N=d>l.expectedEndTime)?n.cancel(l.timer):g.push(k)}N&&(p=n(c,p,!1),g[0]={timer:p,expectedEndTime:d},g.push(k),a.data("$$animateCss",g));if(fa.length)a.on(fa.join(" "),z);f.to&&(f.cleanupStyles&&Ka(A,h,Object.keys(f.to)),Ga(a,f))}}function c(){var b=a.data("$$animateCss");if(b){for(var d=1;d<b.length;d++)b[d]();
|
||||
a.removeData("$$animateCss")}}if(!w)if(h.parentNode){var d=function(a){if(F)O&&a&&(O=!1,k());else if(O=!a,D.animationDuration)if(a=va(h,O),O)x.push(a);else{var b=x,c=b.indexOf(a);0<=a&&b.splice(c,1)}},g=0<ba&&(D.transitionDuration&&0===X.transitionDuration||D.animationDuration&&0===X.animationDuration)&&Math.max(X.animationDelay,X.transitionDelay);g?n(b,Math.floor(g*ba*1E3),!1):b();v.resume=function(){d(!0)};v.pause=function(){d(!1)}}else k()}var f=c||{};f.$$prepared||(f=oa(Ba(f)));var A={},h=y(a);
|
||||
if(!h||!h.parentNode||!C.enabled())return L();var x=[],I=a.attr("class"),E=Na(f),w,O,F,G,v,J,R,m,V,W,fa=[];if(0===f.duration||!g.animations&&!g.transitions)return L();var ja=f.event&&Y(f.event)?f.event.join(" "):f.event,$="",U="";ja&&f.structural?$=Z(ja,"ng-",!0):ja&&($=ja);f.addClass&&(U+=Z(f.addClass,"-add"));f.removeClass&&(U.length&&(U+=" "),U+=Z(f.removeClass,"-remove"));f.applyClassesEarly&&U.length&&T(a,f);var ga=[$,U].join(" ").trim(),na=I+" "+ga,ea=Z(ga,"-active"),I=E.to&&0<Object.keys(E.to).length;
|
||||
if(!(0<(f.keyframeStyle||"").length||I||ga))return L();var ia,X;0<f.stagger?(E=parseFloat(f.stagger),X={transitionDelay:E,animationDelay:E,transitionDuration:0,animationDuration:0}):(ia=B(h,na),X=Q(h,ga,ia,Va));f.$$skipPreparationClasses||e.addClass(a,ga);f.transitionStyle&&(E=[S,f.transitionStyle],la(h,E),x.push(E));0<=f.duration&&(E=0<h.style[S].length,E=Ia(f.duration,E),la(h,E),x.push(E));f.keyframeStyle&&(E=[aa,f.keyframeStyle],la(h,E),x.push(E));var ba=X?0<=f.staggerIndex?f.staggerIndex:b.count(ia):
|
||||
0;(ja=0===ba)&&!f.skipBlocking&&pa(h,9999);var D=H(h,na,ia),ca=D.maxDelay;J=Math.max(ca,0);m=D.maxDuration;var q={};q.hasTransitions=0<D.transitionDuration;q.hasAnimations=0<D.animationDuration;q.hasTransitionAll=q.hasTransitions&&"all"==D.transitionProperty;q.applyTransitionDuration=I&&(q.hasTransitions&&!q.hasTransitionAll||q.hasAnimations&&!q.hasTransitions);q.applyAnimationDuration=f.duration&&q.hasAnimations;q.applyTransitionDelay=wa(f.delay)&&(q.applyTransitionDuration||q.hasTransitions);q.applyAnimationDelay=
|
||||
wa(f.delay)&&q.hasAnimations;q.recalculateTimingStyles=0<U.length;if(q.applyTransitionDuration||q.applyAnimationDuration)m=f.duration?parseFloat(f.duration):m,q.applyTransitionDuration&&(q.hasTransitions=!0,D.transitionDuration=m,E=0<h.style[S+"Property"].length,x.push(Ia(m,E))),q.applyAnimationDuration&&(q.hasAnimations=!0,D.animationDuration=m,x.push([Aa,m+"s"]));if(0===m&&!q.recalculateTimingStyles)return L();if(null!=f.delay){var da;"boolean"!==typeof f.delay&&(da=parseFloat(f.delay),J=Math.max(da,
|
||||
0));q.applyTransitionDelay&&x.push([ma,da+"s"]);q.applyAnimationDelay&&x.push([qa,da+"s"])}null==f.duration&&0<D.transitionDuration&&(q.recalculateTimingStyles=q.recalculateTimingStyles||ja);R=1E3*J;V=1E3*m;f.skipBlocking||(q.blockTransition=0<D.transitionDuration,q.blockKeyframeAnimation=0<D.animationDuration&&0<X.animationDelay&&0===X.animationDuration);f.from&&(f.cleanupStyles&&Ka(A,h,Object.keys(f.from)),Fa(a,f));q.blockTransition||q.blockKeyframeAnimation?M(m):f.skipBlocking||pa(h,!1);return{$$willAnimate:!0,
|
||||
end:d,start:function(){if(!w)return v={end:d,cancel:u,resume:null,pause:null},G=new l(v),t(N),G}}}}]}]).provider("$$animateCssDriver",["$$animationProvider",function(a){a.drivers.push("$$animateCssDriver");this.$get=["$animateCss","$rootScope","$$AnimateRunner","$rootElement","$sniffer","$$jqLite","$document",function(a,c,d,e,l,n,I){function g(a){return a.replace(/\bng-\S+\b/g,"")}function u(a,b){G(a)&&(a=a.split(" "));G(b)&&(b=b.split(" "));return a.filter(function(a){return-1===b.indexOf(a)}).join(" ")}
|
||||
function C(c,e,n){function l(a){var b={},c=y(a).getBoundingClientRect();s(["width","height","top","left"],function(a){var d=c[a];switch(a){case "top":d+=t.scrollTop;break;case "left":d+=t.scrollLeft}b[a]=Math.floor(d)+"px"});return b}function p(){var c=g(n.attr("class")||""),d=u(c,k),c=u(k,c),d=a(C,{to:l(n),addClass:"ng-anchor-in "+d,removeClass:"ng-anchor-out "+c,delay:!0});return d.$$willAnimate?d:null}function I(){C.remove();e.removeClass("ng-animate-shim");n.removeClass("ng-animate-shim")}var C=
|
||||
F(y(e).cloneNode(!0)),k=g(C.attr("class")||"");e.addClass("ng-animate-shim");n.addClass("ng-animate-shim");C.addClass("ng-anchor");H.append(C);var M;c=function(){var c=a(C,{addClass:"ng-anchor-out",delay:!0,from:l(e)});return c.$$willAnimate?c:null}();if(!c&&(M=p(),!M))return I();var L=c||M;return{start:function(){function a(){c&&c.end()}var b,c=L.start();c.done(function(){c=null;if(!M&&(M=p()))return c=M.start(),c.done(function(){c=null;I();b.complete()}),c;I();b.complete()});return b=new d({end:a,
|
||||
cancel:a})}}}function B(a,b,c,e){var g=Q(a,P),n=Q(b,P),l=[];s(e,function(a){(a=C(c,a.out,a["in"]))&&l.push(a)});if(g||n||0!==l.length)return{start:function(){function a(){s(b,function(a){a.end()})}var b=[];g&&b.push(g.start());n&&b.push(n.start());s(l,function(a){b.push(a.start())});var c=new d({end:a,cancel:a});d.all(b,function(a){c.complete(a)});return c}}}function Q(c){var d=c.element,e=c.options||{};c.structural&&(e.event=c.event,e.structural=!0,e.applyClassesEarly=!0,"leave"===c.event&&(e.onDone=
|
||||
e.domOperation));e.preparationClasses&&(e.event=$(e.event,e.preparationClasses));c=a(d,e);return c.$$willAnimate?c:null}if(!l.animations&&!l.transitions)return P;var t=I[0].body;c=y(e);var H=F(c.parentNode&&11===c.parentNode.nodeType||t.contains(c)?c:t);V(n);return function(a){return a.from&&a.to?B(a.from,a.to,a.classes,a.anchors):Q(a)}}]}]).provider("$$animateJs",["$animateProvider",function(a){this.$get=["$injector","$$AnimateRunner","$$jqLite",function(b,c,d){function e(c){c=Y(c)?c:c.split(" ");
|
||||
for(var d=[],e={},l=0;l<c.length;l++){var s=c[l],B=a.$$registeredAnimations[s];B&&!e[s]&&(d.push(b.get(B)),e[s]=!0)}return d}var l=V(d);return function(a,b,d,u){function C(){u.domOperation();l(a,u)}function B(a,b,d,e,f){switch(d){case "animate":b=[b,e.from,e.to,f];break;case "setClass":b=[b,F,G,f];break;case "addClass":b=[b,F,f];break;case "removeClass":b=[b,G,f];break;default:b=[b,f]}b.push(e);if(a=a.apply(a,b))if(Ca(a.start)&&(a=a.start()),a instanceof c)a.done(f);else if(Ca(a))return a;return P}
|
||||
function y(a,b,d,e,f){var g=[];s(e,function(e){var k=e[f];k&&g.push(function(){var e,f,g=!1,h=function(a){g||(g=!0,(f||P)(a),e.complete(!a))};e=new c({end:function(){h()},cancel:function(){h(!0)}});f=B(k,a,b,d,function(a){h(!1===a)});return e})});return g}function t(a,b,d,e,f){var g=y(a,b,d,e,f);if(0===g.length){var h,k;"beforeSetClass"===f?(h=y(a,"removeClass",d,e,"beforeRemoveClass"),k=y(a,"addClass",d,e,"beforeAddClass")):"setClass"===f&&(h=y(a,"removeClass",d,e,"removeClass"),k=y(a,"addClass",
|
||||
d,e,"addClass"));h&&(g=g.concat(h));k&&(g=g.concat(k))}if(0!==g.length)return function(a){var b=[];g.length&&s(g,function(a){b.push(a())});b.length?c.all(b,a):a();return function(a){s(b,function(b){a?b.cancel():b.end()})}}}var H=!1;3===arguments.length&&ba(d)&&(u=d,d=null);u=oa(u);d||(d=a.attr("class")||"",u.addClass&&(d+=" "+u.addClass),u.removeClass&&(d+=" "+u.removeClass));var F=u.addClass,G=u.removeClass,x=e(d),r,p;if(x.length){var K,J;"leave"==b?(J="leave",K="afterLeave"):(J="before"+b.charAt(0).toUpperCase()+
|
||||
b.substr(1),K=b);"enter"!==b&&"move"!==b&&(r=t(a,b,u,x,J));p=t(a,b,u,x,K)}if(r||p){var k;return{$$willAnimate:!0,end:function(){k?k.end():(H=!0,C(),ha(a,u),k=new c,k.complete(!0));return k},start:function(){function b(c){H=!0;C();ha(a,u);k.complete(c)}if(k)return k;k=new c;var d,e=[];r&&e.push(function(a){d=r(a)});e.length?e.push(function(a){C();a(!0)}):C();p&&e.push(function(a){d=p(a)});k.setHost({end:function(){H||((d||P)(void 0),b(void 0))},cancel:function(){H||((d||P)(!0),b(!0))}});c.chain(e,
|
||||
b);return k}}}}}]}]).provider("$$animateJsDriver",["$$animationProvider",function(a){a.drivers.push("$$animateJsDriver");this.$get=["$$animateJs","$$AnimateRunner",function(a,c){function d(c){return a(c.element,c.event,c.classes,c.options)}return function(a){if(a.from&&a.to){var b=d(a.from),n=d(a.to);if(b||n)return{start:function(){function a(){return function(){s(d,function(a){a.end()})}}var d=[];b&&d.push(b.start());n&&d.push(n.start());c.all(d,function(a){e.complete(a)});var e=new c({end:a(),cancel:a()});
|
||||
return e}}}else return d(a)}}]}])})(window,window.angular);
|
||||
//# sourceMappingURL=angular-animate.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
!function(){"use strict";angular.module("base64",[]).constant("$base64",function(){function a(a,b){var c=f.indexOf(a.charAt(b));if(-1==c)throw"Cannot decode base64";return c}function b(b){b=""+b;var c,d,f,g=b.length;if(0==g)return b;if(0!=g%4)throw"Cannot decode base64";c=0,b.charAt(g-1)==e&&(c=1,b.charAt(g-2)==e&&(c=2),g-=4);var h=[];for(d=0;g>d;d+=4)f=a(b,d)<<18|a(b,d+1)<<12|a(b,d+2)<<6|a(b,d+3),h.push(String.fromCharCode(f>>16,255&f>>8,255&f));switch(c){case 1:f=a(b,d)<<18|a(b,d+1)<<12|a(b,d+2)<<6,h.push(String.fromCharCode(f>>16,255&f>>8));break;case 2:f=a(b,d)<<18|a(b,d+1)<<12,h.push(String.fromCharCode(f>>16))}return h.join("")}function c(a,b){var c=a.charCodeAt(b);if(c>255)throw"INVALID_CHARACTER_ERR: DOM Exception 5";return c}function d(a){if(1!=arguments.length)throw"SyntaxError: Not enough arguments";var b,d,g=[];a=""+a;var h=a.length-a.length%3;if(0==a.length)return a;for(b=0;h>b;b+=3)d=c(a,b)<<16|c(a,b+1)<<8|c(a,b+2),g.push(f.charAt(d>>18)),g.push(f.charAt(63&d>>12)),g.push(f.charAt(63&d>>6)),g.push(f.charAt(63&d));switch(a.length-h){case 1:d=c(a,b)<<16,g.push(f.charAt(d>>18)+f.charAt(63&d>>12)+e+e);break;case 2:d=c(a,b)<<16|c(a,b+1)<<8,g.push(f.charAt(d>>18)+f.charAt(63&d>>12)+f.charAt(63&d>>6)+e)}return g.join("")}var e="=",f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";return{encode:d,decode:b}}())}();
|
||||
@@ -1,361 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
// -------------------------------
|
||||
// WARNING
|
||||
// -------------------------------
|
||||
// This file is taken from https://github.com/sebastianha/angular-bootstrap-multiselect
|
||||
// There are local modifications like support for translation
|
||||
// -------------------------------
|
||||
|
||||
angular.module("ui.multiselect", ["multiselect.tpl.html"])
|
||||
//from bootstrap-ui typeahead parser
|
||||
.factory("optionParser", ["$parse", function($parse) {
|
||||
// 00000111000000000000022200000000000000003333333333333330000000000044000
|
||||
var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
|
||||
|
||||
return {
|
||||
parse: function(input) {
|
||||
|
||||
var match = input.match(TYPEAHEAD_REGEXP);
|
||||
if(!match) {
|
||||
throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" + " but got '" + input + "'.");
|
||||
}
|
||||
|
||||
return {
|
||||
itemName : match[3],
|
||||
source : $parse(match[4]),
|
||||
viewMapper : $parse(match[2] || match[1]),
|
||||
modelMapper: $parse(match[1])
|
||||
};
|
||||
}
|
||||
};
|
||||
}])
|
||||
.directive("multiselect", ["$parse", "$document", "$compile", "$interpolate", "$translate", "optionParser", function($parse, $document, $compile, $interpolate, $translate, optionParser) {
|
||||
return {
|
||||
restrict: "E",
|
||||
require : "ngModel",
|
||||
link : function(originalScope, element, attrs, modelCtrl) {
|
||||
|
||||
var exp = attrs.options;
|
||||
var parsedResult = optionParser.parse(exp);
|
||||
var isMultiple = attrs.multiple ? true : false;
|
||||
var compareByKey = attrs.compareBy;
|
||||
var headerKey = attrs.headerKey;
|
||||
var dividerKey = attrs.dividerKey;
|
||||
var scrollAfterRows = attrs.scrollAfterRows;
|
||||
var tabindex = attrs.tabindex;
|
||||
var maxWidth = attrs.maxWidth;
|
||||
var required = false;
|
||||
var scope = originalScope.$new();
|
||||
scope.filterAfterRows = attrs.filterAfterRows;
|
||||
var changeHandler = attrs.change || angular.noop;
|
||||
|
||||
scope.items = [];
|
||||
scope.header = "Select";
|
||||
scope.multiple = isMultiple;
|
||||
scope.disabled = false;
|
||||
|
||||
scope.ulStyle = {};
|
||||
if(scrollAfterRows !== undefined && parseInt(scrollAfterRows).toString() === scrollAfterRows) {
|
||||
scope.ulStyle = {"max-height": (scrollAfterRows*26+14)+"px", "overflow-y": "auto", "overflow-x": "hidden"};
|
||||
}
|
||||
if(tabindex !== undefined && parseInt(tabindex).toString() === tabindex) {
|
||||
scope.tabindex = tabindex;
|
||||
}
|
||||
if(maxWidth !== undefined && parseInt(maxWidth).toString() === maxWidth) {
|
||||
scope.maxWidth = {"max-width": maxWidth + "px"};
|
||||
}
|
||||
|
||||
originalScope.$on("$destroy", function() {
|
||||
scope.$destroy();
|
||||
});
|
||||
|
||||
var popUpEl = angular.element("<multiselect-popup></multiselect-popup>");
|
||||
|
||||
//required validator
|
||||
if(attrs.required || attrs.ngRequired) {
|
||||
required = true;
|
||||
}
|
||||
attrs.$observe("required", function(newVal) {
|
||||
required = newVal;
|
||||
});
|
||||
|
||||
//watch disabled state
|
||||
scope.$watch(function() {
|
||||
return $parse(attrs.ngDisabled)(originalScope);
|
||||
}, function(newVal) {
|
||||
scope.disabled = newVal;
|
||||
});
|
||||
|
||||
//watch single/multiple state for dynamically change single to multiple
|
||||
scope.$watch(function() {
|
||||
return $parse(attrs.multiple)(originalScope);
|
||||
}, function(newVal) {
|
||||
isMultiple = newVal || false;
|
||||
});
|
||||
|
||||
//watch option changes for options that are populated dynamically
|
||||
scope.$watch(function() {
|
||||
return parsedResult.source(originalScope);
|
||||
}, function(newVal) {
|
||||
if(angular.isDefined(newVal)) {
|
||||
parseModel();
|
||||
}
|
||||
}, true);
|
||||
|
||||
//watch model change
|
||||
scope.$watch(function() {
|
||||
return modelCtrl.$modelValue;
|
||||
}, function(newVal, oldVal) {
|
||||
//when directive initialize, newVal usually undefined. Also, if model value already set in the controller
|
||||
//for preselected list then we need to mark checked in our scope item. But we don't want to do this every time
|
||||
//model changes. We need to do this only if it is done outside directive scope, from controller, for example.
|
||||
if(angular.isDefined(newVal)) {
|
||||
markChecked(newVal);
|
||||
scope.$eval(changeHandler);
|
||||
}
|
||||
getHeaderText();
|
||||
modelCtrl.$setValidity("required", scope.valid());
|
||||
});
|
||||
|
||||
function parseModel() {
|
||||
scope.items.length = 0;
|
||||
var model = parsedResult.source(originalScope);
|
||||
if(!angular.isDefined(model) || model === null) {
|
||||
return;
|
||||
}
|
||||
for(var i = 0; i < model.length; i++) {
|
||||
var local = {};
|
||||
local[parsedResult.itemName] = model[i];
|
||||
|
||||
// calculate checked status of the option
|
||||
// https://github.com/sebastianha/angular-bootstrap-multiselect/pull/4/files
|
||||
var id = model[i];
|
||||
var checked = false;
|
||||
var modelValue = modelCtrl.$modelValue;
|
||||
if (modelValue) {
|
||||
if (angular.isArray(modelValue)) {
|
||||
for (var j = 0; j < modelValue.length; j++)
|
||||
if (modelValue[j] == id) {
|
||||
checked = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
checked = modelValue == id;
|
||||
}
|
||||
}
|
||||
|
||||
scope.items.push({
|
||||
label : parsedResult.viewMapper(local),
|
||||
model : model[i],
|
||||
checked: checked,
|
||||
header : model[i][headerKey],
|
||||
divider : model[i][dividerKey]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
parseModel();
|
||||
|
||||
element.append($compile(popUpEl)(scope));
|
||||
|
||||
function getHeaderText() {
|
||||
if(isEmpty(modelCtrl.$modelValue)) {
|
||||
scope.header = attrs.msHeader || $translate.instant('main.multiselect.select');
|
||||
return scope.header;
|
||||
}
|
||||
|
||||
if(isMultiple) {
|
||||
if(attrs.msSelected) {
|
||||
scope.header = $interpolate(attrs.msSelected)(scope);
|
||||
} else {
|
||||
scope.header = $translate.instant('main.multiselect.selected', { n: modelCtrl.$modelValue.length });
|
||||
}
|
||||
} else {
|
||||
var local = {};
|
||||
local[parsedResult.itemName] = modelCtrl.$modelValue;
|
||||
scope.header = parsedResult.viewMapper(local);
|
||||
}
|
||||
}
|
||||
|
||||
function isEmpty(obj) {
|
||||
if(obj === true || obj === false) {
|
||||
return false;
|
||||
}
|
||||
if(!obj) {
|
||||
return true;
|
||||
}
|
||||
if(obj.length && obj.length > 0) {
|
||||
return false;
|
||||
}
|
||||
for(var prop in obj) {
|
||||
if(obj[prop]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(compareByKey !== undefined && obj[compareByKey] !== undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
scope.valid = function validModel() {
|
||||
if(!required) {
|
||||
return true;
|
||||
}
|
||||
var value = modelCtrl.$modelValue;
|
||||
return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value !== null);
|
||||
};
|
||||
|
||||
function selectSingle(item) {
|
||||
if(!item.checked) {
|
||||
scope.uncheckAll();
|
||||
item.checked = !item.checked;
|
||||
}
|
||||
setModelValue(false);
|
||||
}
|
||||
|
||||
function selectMultiple(item) {
|
||||
item.checked = !item.checked;
|
||||
setModelValue(true);
|
||||
}
|
||||
|
||||
function setModelValue(isMultiple) {
|
||||
var value;
|
||||
|
||||
if(isMultiple) {
|
||||
value = [];
|
||||
angular.forEach(scope.items, function(item) {
|
||||
if(item.checked) {
|
||||
value.push(item.model);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
angular.forEach(scope.items, function(item) {
|
||||
if(item.checked) {
|
||||
value = item.model;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
modelCtrl.$setViewValue(value);
|
||||
}
|
||||
|
||||
function markChecked(newVal) {
|
||||
if(!angular.isArray(newVal)) {
|
||||
angular.forEach(scope.items, function(item) {
|
||||
item.checked = false;
|
||||
if(compareByKey === undefined && angular.equals(item.model, newVal)) {
|
||||
item.checked = true;
|
||||
} else if(compareByKey !== undefined && newVal !== null && item.model[compareByKey] !== undefined && angular.equals(item.model[compareByKey], newVal[compareByKey])) {
|
||||
item.checked = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
angular.forEach(scope.items, function(item) {
|
||||
item.checked = false;
|
||||
angular.forEach(newVal, function(i) {
|
||||
if(compareByKey === undefined && angular.equals(item.model, i)) {
|
||||
item.checked = true;
|
||||
} else if(compareByKey !== undefined && item.model[compareByKey] !== undefined && angular.equals(item.model[compareByKey], i[compareByKey])) {
|
||||
item.checked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
scope.checkAll = function() {
|
||||
if(!isMultiple) {
|
||||
return;
|
||||
}
|
||||
angular.forEach(scope.items, function(item) {
|
||||
item.checked = true;
|
||||
});
|
||||
setModelValue(true);
|
||||
};
|
||||
|
||||
scope.uncheckAll = function() {
|
||||
angular.forEach(scope.items, function(item) {
|
||||
item.checked = false;
|
||||
});
|
||||
setModelValue(true);
|
||||
};
|
||||
|
||||
scope.select = function(event, item) {
|
||||
if(isMultiple === false) {
|
||||
selectSingle(item);
|
||||
scope.toggleSelect();
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
selectMultiple(item);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}])
|
||||
.directive("multiselectPopup", ["$document", function($document) {
|
||||
return {
|
||||
restrict : "E",
|
||||
scope : false,
|
||||
replace : true,
|
||||
templateUrl: "multiselect.tpl.html",
|
||||
link : function(scope, element, attrs) {
|
||||
|
||||
scope.isVisible = false;
|
||||
|
||||
scope.toggleSelect = function() {
|
||||
if(element.hasClass("open")) {
|
||||
scope.filter = "";
|
||||
element.removeClass("open");
|
||||
$document.unbind("click", clickHandler);
|
||||
} else {
|
||||
scope.filter = "";
|
||||
element.addClass("open");
|
||||
$document.bind("click", clickHandler);
|
||||
}
|
||||
};
|
||||
|
||||
// $("ul.dropdown-menu").on("click", "[data-stopPropagation]", function(e) {
|
||||
// e.stopPropagation();
|
||||
// });
|
||||
|
||||
function clickHandler(event) {
|
||||
if(elementMatchesAnyInArray(event.target, element.find(event.target.tagName))) {
|
||||
return;
|
||||
}
|
||||
element.removeClass("open");
|
||||
$document.unbind("click", clickHandler);
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
var elementMatchesAnyInArray = function(element, elementArray) {
|
||||
for(var i = 0; i < elementArray.length; i++) {
|
||||
if(element === elementArray[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
angular.module("multiselect.tpl.html", []).run(["$templateCache", function($templateCache) {
|
||||
$templateCache.put("multiselect.tpl.html",
|
||||
"<div class=\"btn-group\">\n" +
|
||||
" <button tabindex=\"{{tabindex}}\" title=\"{{header}}\" type=\"button\" class=\"btn btn-default dropdown-toggle\" ng-click=\"toggleSelect()\" ng-disabled=\"disabled\" ng-class=\"{'error': !valid()}\">\n" +
|
||||
" <div ng-style=\"maxWidth\" style=\"padding-right: 13px; overflow: hidden; text-overflow: ellipsis;\">{{header}}</div><span class=\"caret\" style=\"position:absolute;right:10px;top:14px;\"></span>\n" +
|
||||
" </button>\n" +
|
||||
" <ul class=\"dropdown-menu\" style=\"margin-bottom:30px;padding-left:5px;padding-right:5px;\" ng-style=\"ulStyle\">\n" +
|
||||
" <input ng-show=\"items.length > filterAfterRows\" ng-model=\"filter\" style=\"padding: 0px 3px; margin-bottom: 4px;\" placeholder=\"{{ 'main.multiselect.filterPlaceholder' | tr }}\">" +
|
||||
" <li data-stopPropagation=\"true\" ng-repeat=\"i in items | filter:filter\" ng-class=\"{'dropdown-header': i.header, 'divider': i.divider}\">\n" +
|
||||
" <a ng-if=\"!i.header && !i.divider\" ng-click=\"select($event, i)\" style=\"padding:3px 10px;cursor:pointer;\">\n" +
|
||||
" <i class=\"fa\" ng-class=\"{'fa-check': i.checked, 'empty': !i.checked}\"></i> {{i.label}}" +
|
||||
" </a>\n" +
|
||||
" <span ng-if=\"i.header\">{{i.label}}</span>" +
|
||||
" </li>\n" +
|
||||
" </ul>\n" +
|
||||
"</div>");
|
||||
}]);
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
AngularJS v1.5.8
|
||||
(c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(n,c){'use strict';function l(b,a,g){var d=g.baseHref(),k=b[0];return function(b,e,f){var g,h;f=f||{};h=f.expires;g=c.isDefined(f.path)?f.path:d;c.isUndefined(e)&&(h="Thu, 01 Jan 1970 00:00:00 GMT",e="");c.isString(h)&&(h=new Date(h));e=encodeURIComponent(b)+"="+encodeURIComponent(e);e=e+(g?";path="+g:"")+(f.domain?";domain="+f.domain:"");e+=h?";expires="+h.toUTCString():"";e+=f.secure?";secure":"";f=e.length+1;4096<f&&a.warn("Cookie '"+b+"' possibly not set or overflowed because it was too large ("+
|
||||
f+" > 4096 bytes)!");k.cookie=e}}c.module("ngCookies",["ng"]).provider("$cookies",[function(){var b=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(a,g){return{get:function(d){return a()[d]},getObject:function(d){return(d=this.get(d))?c.fromJson(d):d},getAll:function(){return a()},put:function(d,a,m){g(d,a,m?c.extend({},b,m):b)},putObject:function(d,b,a){this.put(d,c.toJson(b),a)},remove:function(a,k){g(a,void 0,k?c.extend({},b,k):b)}}}]}]);c.module("ngCookies").factory("$cookieStore",
|
||||
["$cookies",function(b){return{get:function(a){return b.getObject(a)},put:function(a,c){b.putObject(a,c)},remove:function(a){b.remove(a)}}}]);l.$inject=["$document","$log","$browser"];c.module("ngCookies").provider("$$cookieWriter",function(){this.$get=l})})(window,window.angular);
|
||||
//# sourceMappingURL=angular-cookies.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
!function(t,e,i,n){"use strict";i.module("ngFitText",[]).value("fitTextDefaultConfig",{debounce:!1,delay:250,loadDelay:10,compressor:1,min:0,max:Number.POSITIVE_INFINITY}).directive("fittext",["$timeout","fitTextDefaultConfig","fitTextConfig",function(e,n,o){return{restrict:"A",scope:!0,link:function(f,a,l){function r(){var t=T*h/s.offsetWidth/h;return Math.max(Math.min((c[0].offsetWidth-6)*t*p,parseFloat(y)),parseFloat(m))}function u(){s.offsetHeight*s.offsetWidth!==0&&(d.fontSize=T+"px",d.lineHeight="1",d.display="inline-block",d.fontSize=r()+"px",d.lineHeight=b,d.display=v)}i.extend(n,o.config);var c=a.parent(),s=a[0],d=s.style,x=t.getComputedStyle(a[0],null),h=a.children().length||1,g=l.fittextLoadDelay||n.loadDelay,p=l.fittext||n.compressor,m=("inherit"===l.fittextMin?x["font-size"]:l.fittextMin)||n.min,y=("inherit"===l.fittextMax?x["font-size"]:l.fittextMax)||n.max,b=x["line-height"],v=x.display,T=10;e(function(){u()},g),f.$watch(l.ngBind,function(){u()}),n.debounce?i.element(t).bind("resize",n.debounce(function(){f.$apply(u)},n.delay)):i.element(t).bind("resize",function(){f.$apply(u)})}}}]).provider("fitTextConfig",function(){var t=this;return this.config={},this.$get=function(){var e={};return e.config=t.config,e},this})}(window,document,angular);
|
||||
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
AngularJS v1.5.8
|
||||
(c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(){'use strict';function d(b){return function(){var a=arguments[0],e;e="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.5.8/"+(b?b+"/":"")+a;for(a=1;a<arguments.length;a++){e=e+(1==a?"?":"&")+"p"+(a-1)+"=";var d=encodeURIComponent,c;c=arguments[a];c="function"==typeof c?c.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof c?"undefined":"string"!=typeof c?JSON.stringify(c):c;e+=d(c)}return Error(e)}}(function(b){function a(c,a,b){return c[a]||(c[a]=b())}var e=d("$injector"),n=d("ng");
|
||||
b=a(b,"angular",Object);b.$$minErr=b.$$minErr||d;return a(b,"module",function(){var c={};return function(b,d,h){if("hasOwnProperty"===b)throw n("badname","module");d&&c.hasOwnProperty(b)&&(c[b]=null);return a(c,b,function(){function c(a,b,d,e){e||(e=f);return function(){e[d||"push"]([a,b,arguments]);return g}}function a(c,e){return function(a,d){d&&"function"===typeof d&&(d.$$moduleName=b);f.push([c,e,arguments]);return g}}if(!d)throw e("nomod",b);var f=[],k=[],l=[],m=c("$injector","invoke","push",
|
||||
k),g={_invokeQueue:f,_configBlocks:k,_runBlocks:l,requires:d,name:b,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:c("$provide","value"),constant:c("$provide","constant","unshift"),decorator:a("$provide","decorator"),animation:a("$animateProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),component:a("$compileProvider","component"),config:m,run:function(a){l.push(a);
|
||||
return this}};h&&m(h);return g})}})})(window)})(window);
|
||||
//# sourceMappingURL=angular-loader.min.js.map
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"version":3,
|
||||
"file":"angular-loader.min.js",
|
||||
"lineCount":9,
|
||||
"mappings":"A;;;;;aAMC,SAAQ,EAAG,CA6DZA,QAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,MAAAA,SAAAA,EAAAA,CAAAA,IAAAA,EAAAA,SAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,EAAAA,CAAAA,GAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,GAAAA,CAAAA,EAAAA,EAAAA,CAAAA,CAAAA,sCAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,GAAAA,CAAAA,EAAAA,EAAAA,CAAAA,KAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,SAAAA,OAAAA,CAAAA,CAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,EAAAA,CAAAA,EAAAA,CAAAA,CAAAA,GAAAA,CAAAA,GAAAA,EAAAA,GAAAA,EAAAA,CAAAA,CAAAA,CAAAA,EAAAA,GAAAA,KAAAA,EAAAA,kBAAAA,CAAAA,CAAAA,EAAAA,CAAAA,SAAAA,CAAAA,CAAAA,CAAAA,EAAAA,CAAAA,UAAAA,EAAAA,MAAAA,EAAAA,CAAAA,CAAAA,SAAAA,EAAAA,QAAAA,CAAAA,aAAAA,CAAAA,EAAAA,CAAAA,CAAAA,WAAAA,EAAAA,MAAAA,EAAAA,CAAAA,WAAAA,CAAAA,QAAAA,EAAAA,MAAAA,EAAAA,CAAAA,IAAAA,UAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,EAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,MAAAA,MAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CA2CAC,SAA0B,CAACC,CAAD,CAAS,CAKjCC,QAASA,EAAM,CAACC,CAAD,CAAMC,CAAN,CAAYC,CAAZ,CAAqB,CAClC,MAAOF,EAAA,CAAIC,CAAJ,CAAP,GAAqBD,CAAA,CAAIC,CAAJ,CAArB,CAAiCC,CAAA,EAAjC,CADkC,CAHpC,IAAIC,EAAkBP,CAAA,CAAO,WAAP,CAAtB,CACIQ,EAAWR,CAAA,CAAO,IAAP,CAMXS;CAAAA,CAAUN,CAAA,CAAOD,CAAP,CAAe,SAAf,CAA0BQ,MAA1B,CAGdD,EAAAE,SAAA,CAAmBF,CAAAE,SAAnB,EAAuCX,CAEvC,OAAOG,EAAA,CAAOM,CAAP,CAAgB,QAAhB,CAA0B,QAAQ,EAAG,CAE1C,IAAIG,EAAU,EAqDd,OAAOC,SAAe,CAACR,CAAD,CAAOS,CAAP,CAAiBC,CAAjB,CAA2B,CAE7C,GAAa,gBAAb,GAKsBV,CALtB,CACE,KAAMG,EAAA,CAAS,SAAT,CAIoBQ,QAJpB,CAAN,CAKAF,CAAJ,EAAgBF,CAAAK,eAAA,CAAuBZ,CAAvB,CAAhB,GACEO,CAAA,CAAQP,CAAR,CADF,CACkB,IADlB,CAGA,OAAOF,EAAA,CAAOS,CAAP,CAAgBP,CAAhB,CAAsB,QAAQ,EAAG,CAuPtCa,QAASA,EAAW,CAACC,CAAD,CAAWC,CAAX,CAAmBC,CAAnB,CAAiCC,CAAjC,CAAwC,CACrDA,CAAL,GAAYA,CAAZ,CAAoBC,CAApB,CACA,OAAO,SAAQ,EAAG,CAChBD,CAAA,CAAMD,CAAN,EAAsB,MAAtB,CAAA,CAA8B,CAACF,CAAD,CAAWC,CAAX,CAAmBI,SAAnB,CAA9B,CACA,OAAOC,EAFS,CAFwC,CAa5DC,QAASA,EAA2B,CAACP,CAAD,CAAWC,CAAX,CAAmB,CACrD,MAAO,SAAQ,CAACO,CAAD,CAAaC,CAAb,CAA8B,CACvCA,CAAJ,EA7b4C,UA6b5C,GA7b2B,MA6bOA,EAAlC,GAAoDA,CAAAC,aAApD,CAAmFxB,CAAnF,CACAkB,EAAAO,KAAA,CAAiB,CAACX,CAAD,CAAWC,CAAX,CAAmBI,SAAnB,CAAjB,CACA,OAAOC,EAHoC,CADQ,CAnQvD,GAAKX,CAAAA,CAAL,CACE,KAAMP,EAAA,CAAgB,OAAhB,CAEiDF,CAFjD,CAAN,CAMF,IAAIkB,EAAc,EAAlB,CAGIQ,EAAe,EAHnB,CAMIC,EAAY,EANhB,CAQIC,EAASf,CAAA,CAAY,WAAZ,CAAyB,QAAzB,CAAmC,MAAnC;AAA2Ca,CAA3C,CARb,CAWIN,EAAiB,CAEnBS,aAAcX,CAFK,CAGnBY,cAAeJ,CAHI,CAInBK,WAAYJ,CAJO,CAenBlB,SAAUA,CAfS,CAyBnBT,KAAMA,CAzBa,CAsCnBc,SAAUO,CAAA,CAA4B,UAA5B,CAAwC,UAAxC,CAtCS,CAiDnBpB,QAASoB,CAAA,CAA4B,UAA5B,CAAwC,SAAxC,CAjDU,CA4DnBW,QAASX,CAAA,CAA4B,UAA5B,CAAwC,SAAxC,CA5DU,CAuEnBY,MAAOpB,CAAA,CAAY,UAAZ,CAAwB,OAAxB,CAvEY,CAmFnBqB,SAAUrB,CAAA,CAAY,UAAZ,CAAwB,UAAxB,CAAoC,SAApC,CAnFS,CA+FnBsB,UAAWd,CAAA,CAA4B,UAA5B,CAAwC,WAAxC,CA/FQ,CAiInBe,UAAWf,CAAA,CAA4B,kBAA5B,CAAgD,UAAhD,CAjIQ,CAmJnBgB,OAAQhB,CAAA,CAA4B,iBAA5B,CAA+C,UAA/C,CAnJW,CA+JnBiB,WAAYjB,CAAA,CAA4B,qBAA5B,CAAmD,UAAnD,CA/JO,CA4KnBkB,UAAWlB,CAAA,CAA4B,kBAA5B,CAAgD,WAAhD,CA5KQ,CAyLnBmB,UAAWnB,CAAA,CAA4B,kBAA5B,CAAgD,WAAhD,CAzLQ,CAsMnBO,OAAQA,CAtMW,CAkNnBa,IAAKA,QAAQ,CAACC,CAAD,CAAQ,CACnBf,CAAAF,KAAA,CAAeiB,CAAf,CACA;MAAO,KAFY,CAlNF,CAwNjBhC,EAAJ,EACEkB,CAAA,CAAOlB,CAAP,CAGF,OAAOU,EA/O+B,CAAjC,CAXwC,CAvDP,CAArC,CAd0B,CAAnCxB,CAiWA,CAAkBC,MAAlB,CAzcY,CAAX,CAAD,CA0cGA,MA1cH;",
|
||||
"sources":["angular-loader.js"],
|
||||
"names":["minErr","setupModuleLoader","window","ensure","obj","name","factory","$injectorMinErr","ngMinErr","angular","Object","$$minErr","modules","module","requires","configFn","context","hasOwnProperty","invokeLater","provider","method","insertMethod","queue","invokeQueue","arguments","moduleInstance","invokeLaterAndSetModuleName","recipeName","factoryFunction","$$moduleName","push","configBlocks","runBlocks","config","_invokeQueue","_configBlocks","_runBlocks","service","value","constant","decorator","animation","filter","controller","directive","component","run","block"]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/*
|
||||
angular-md5 - v0.1.7
|
||||
2014-01-20
|
||||
*/
|
||||
|
||||
!function(a,b){b.module("angular-md5",["gdi2290.md5"]),b.module("ngMd5",["gdi2290.md5"]),b.module("gdi2290.md5",["gdi2290.gravatar-filter","gdi2290.md5-service","gdi2290.md5-filter"]),b.module("gdi2290.gravatar-filter",[]).filter("gravatar",["md5",function(a){var b={};return function(c,d){return b[c]||(d=d?a.createHash(d.toString().toLowerCase()):"",b[c]=c?a.createHash(c.toString().toLowerCase()):d),b[c]}}]),b.module("gdi2290.md5-filter",[]).filter("md5",["md5",function(a){return function(b){return b?a.createHash(b.toString().toLowerCase()):b}}]),b.module("gdi2290.md5-service",[]).factory("md5",[function(){var a={createHash:function(a){var b,c,d,e,f,g,h,i,j,k,l=function(a,b){return a<<b|a>>>32-b},m=function(a,b){var c,d,e,f,g;return e=2147483648&a,f=2147483648&b,c=1073741824&a,d=1073741824&b,g=(1073741823&a)+(1073741823&b),c&d?2147483648^g^e^f:c|d?1073741824&g?3221225472^g^e^f:1073741824^g^e^f:g^e^f},n=function(a,b,c){return a&b|~a&c},o=function(a,b,c){return a&c|b&~c},p=function(a,b,c){return a^b^c},q=function(a,b,c){return b^(a|~c)},r=function(a,b,c,d,e,f,g){return a=m(a,m(m(n(b,c,d),e),g)),m(l(a,f),b)},s=function(a,b,c,d,e,f,g){return a=m(a,m(m(o(b,c,d),e),g)),m(l(a,f),b)},t=function(a,b,c,d,e,f,g){return a=m(a,m(m(p(b,c,d),e),g)),m(l(a,f),b)},u=function(a,b,c,d,e,f,g){return a=m(a,m(m(q(b,c,d),e),g)),m(l(a,f),b)},v=function(a){for(var b,c=a.length,d=c+8,e=(d-d%64)/64,f=16*(e+1),g=new Array(f-1),h=0,i=0;c>i;)b=(i-i%4)/4,h=i%4*8,g[b]=g[b]|a.charCodeAt(i)<<h,i++;return b=(i-i%4)/4,h=i%4*8,g[b]=g[b]|128<<h,g[f-2]=c<<3,g[f-1]=c>>>29,g},w=function(a){var b,c,d="",e="";for(c=0;3>=c;c++)b=a>>>8*c&255,e="0"+b.toString(16),d+=e.substr(e.length-2,2);return d},x=[],y=7,z=12,A=17,B=22,C=5,D=9,E=14,F=20,G=4,H=11,I=16,J=23,K=6,L=10,M=15,N=21;for(x=v(a),h=1732584193,i=4023233417,j=2562383102,k=271733878,b=x.length,c=0;b>c;c+=16)d=h,e=i,f=j,g=k,h=r(h,i,j,k,x[c+0],y,3614090360),k=r(k,h,i,j,x[c+1],z,3905402710),j=r(j,k,h,i,x[c+2],A,606105819),i=r(i,j,k,h,x[c+3],B,3250441966),h=r(h,i,j,k,x[c+4],y,4118548399),k=r(k,h,i,j,x[c+5],z,1200080426),j=r(j,k,h,i,x[c+6],A,2821735955),i=r(i,j,k,h,x[c+7],B,4249261313),h=r(h,i,j,k,x[c+8],y,1770035416),k=r(k,h,i,j,x[c+9],z,2336552879),j=r(j,k,h,i,x[c+10],A,4294925233),i=r(i,j,k,h,x[c+11],B,2304563134),h=r(h,i,j,k,x[c+12],y,1804603682),k=r(k,h,i,j,x[c+13],z,4254626195),j=r(j,k,h,i,x[c+14],A,2792965006),i=r(i,j,k,h,x[c+15],B,1236535329),h=s(h,i,j,k,x[c+1],C,4129170786),k=s(k,h,i,j,x[c+6],D,3225465664),j=s(j,k,h,i,x[c+11],E,643717713),i=s(i,j,k,h,x[c+0],F,3921069994),h=s(h,i,j,k,x[c+5],C,3593408605),k=s(k,h,i,j,x[c+10],D,38016083),j=s(j,k,h,i,x[c+15],E,3634488961),i=s(i,j,k,h,x[c+4],F,3889429448),h=s(h,i,j,k,x[c+9],C,568446438),k=s(k,h,i,j,x[c+14],D,3275163606),j=s(j,k,h,i,x[c+3],E,4107603335),i=s(i,j,k,h,x[c+8],F,1163531501),h=s(h,i,j,k,x[c+13],C,2850285829),k=s(k,h,i,j,x[c+2],D,4243563512),j=s(j,k,h,i,x[c+7],E,1735328473),i=s(i,j,k,h,x[c+12],F,2368359562),h=t(h,i,j,k,x[c+5],G,4294588738),k=t(k,h,i,j,x[c+8],H,2272392833),j=t(j,k,h,i,x[c+11],I,1839030562),i=t(i,j,k,h,x[c+14],J,4259657740),h=t(h,i,j,k,x[c+1],G,2763975236),k=t(k,h,i,j,x[c+4],H,1272893353),j=t(j,k,h,i,x[c+7],I,4139469664),i=t(i,j,k,h,x[c+10],J,3200236656),h=t(h,i,j,k,x[c+13],G,681279174),k=t(k,h,i,j,x[c+0],H,3936430074),j=t(j,k,h,i,x[c+3],I,3572445317),i=t(i,j,k,h,x[c+6],J,76029189),h=t(h,i,j,k,x[c+9],G,3654602809),k=t(k,h,i,j,x[c+12],H,3873151461),j=t(j,k,h,i,x[c+15],I,530742520),i=t(i,j,k,h,x[c+2],J,3299628645),h=u(h,i,j,k,x[c+0],K,4096336452),k=u(k,h,i,j,x[c+7],L,1126891415),j=u(j,k,h,i,x[c+14],M,2878612391),i=u(i,j,k,h,x[c+5],N,4237533241),h=u(h,i,j,k,x[c+12],K,1700485571),k=u(k,h,i,j,x[c+3],L,2399980690),j=u(j,k,h,i,x[c+10],M,4293915773),i=u(i,j,k,h,x[c+1],N,2240044497),h=u(h,i,j,k,x[c+8],K,1873313359),k=u(k,h,i,j,x[c+15],L,4264355552),j=u(j,k,h,i,x[c+6],M,2734768916),i=u(i,j,k,h,x[c+13],N,1309151649),h=u(h,i,j,k,x[c+4],K,4149444226),k=u(k,h,i,j,x[c+11],L,3174756917),j=u(j,k,h,i,x[c+2],M,718787259),i=u(i,j,k,h,x[c+9],N,3951481745),h=m(h,d),i=m(i,e),j=m(j,f),k=m(k,g);var O=w(h)+w(i)+w(j)+w(k);return O.toLowerCase()}};return a}])}(this,this.angular,void 0);
|
||||
//# sourceMappingURL=angular-md5.min.js.map
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
AngularJS v1.5.8
|
||||
(c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(E,d){'use strict';function y(t,l,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(b,e,a,c,k){function p(){m&&(g.cancel(m),m=null);h&&(h.$destroy(),h=null);n&&(m=g.leave(n),m.then(function(){m=null}),n=null)}function B(){var a=t.current&&t.current.locals;if(d.isDefined(a&&a.$template)){var a=b.$new(),c=t.current;n=k(a,function(a){g.enter(a,null,n||e).then(function(){!d.isDefined(A)||A&&!b.$eval(A)||l()});p()});h=c.scope=a;h.$emit("$viewContentLoaded");
|
||||
h.$eval(s)}else p()}var h,n,m,A=a.autoscroll,s=a.onload||"";b.$on("$routeChangeSuccess",B);B()}}}function w(d,l,g){return{restrict:"ECA",priority:-400,link:function(b,e){var a=g.current,c=a.locals;e.html(c.$template);var k=d(e.contents());if(a.controller){c.$scope=b;var p=l(a.controller,c);a.controllerAs&&(b[a.controllerAs]=p);e.data("$ngControllerController",p);e.children().data("$ngControllerController",p)}b[a.resolveAs||"$resolve"]=c;k(b)}}}var x,C,s=d.module("ngRoute",["ng"]).provider("$route",
|
||||
function(){function t(b,e){return d.extend(Object.create(b),e)}function l(b,d){var a=d.caseInsensitiveMatch,c={originalPath:b,regexp:b},g=c.keys=[];b=b.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[\?\*])?/g,function(b,a,d,c){b="?"===c||"*?"===c?"?":null;c="*"===c||"*?"===c?"*":null;g.push({name:d,optional:!!b});a=a||"";return""+(b?"":a)+"(?:"+(b?a:"")+(c&&"(.+?)"||"([^/]+)")+(b||"")+")"+(b||"")}).replace(/([\/$\*])/g,"\\$1");c.regexp=new RegExp("^"+b+"$",a?"i":"");return c}x=d.isArray;C=
|
||||
d.isObject;var g={};this.when=function(b,e){var a;a=void 0;if(x(e)){a=a||[];for(var c=0,k=e.length;c<k;c++)a[c]=e[c]}else if(C(e))for(c in a=a||{},e)if("$"!==c.charAt(0)||"$"!==c.charAt(1))a[c]=e[c];a=a||e;d.isUndefined(a.reloadOnSearch)&&(a.reloadOnSearch=!0);d.isUndefined(a.caseInsensitiveMatch)&&(a.caseInsensitiveMatch=this.caseInsensitiveMatch);g[b]=d.extend(a,b&&l(b,a));b&&(c="/"==b[b.length-1]?b.substr(0,b.length-1):b+"/",g[c]=d.extend({redirectTo:b},l(c,a)));return this};this.caseInsensitiveMatch=
|
||||
!1;this.otherwise=function(b){"string"===typeof b&&(b={redirectTo:b});this.when(null,b);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(b,e,a,c,k,p,l){function h(a){var f=v.current;(x=(r=y())&&f&&r.$$route===f.$$route&&d.equals(r.pathParams,f.pathParams)&&!r.reloadOnSearch&&!z)||!f&&!r||b.$broadcast("$routeChangeStart",r,f).defaultPrevented&&a&&a.preventDefault()}function n(){var u=v.current,f=r;if(x)u.params=f.params,d.copy(u.params,
|
||||
a),b.$broadcast("$routeUpdate",u);else if(f||u)z=!1,(v.current=f)&&f.redirectTo&&(d.isString(f.redirectTo)?e.path(w(f.redirectTo,f.params)).search(f.params).replace():e.url(f.redirectTo(f.pathParams,e.path(),e.search())).replace()),c.when(f).then(m).then(function(c){f==v.current&&(f&&(f.locals=c,d.copy(f.params,a)),b.$broadcast("$routeChangeSuccess",f,u))},function(a){f==v.current&&b.$broadcast("$routeChangeError",f,u,a)})}function m(a){if(a){var b=d.extend({},a.resolve);d.forEach(b,function(a,c){b[c]=
|
||||
d.isString(a)?k.get(a):k.invoke(a,null,null,c)});a=s(a);d.isDefined(a)&&(b.$template=a);return c.all(b)}}function s(a){var b,c;d.isDefined(b=a.template)?d.isFunction(b)&&(b=b(a.params)):d.isDefined(c=a.templateUrl)&&(d.isFunction(c)&&(c=c(a.params)),d.isDefined(c)&&(a.loadedTemplateUrl=l.valueOf(c),b=p(c)));return b}function y(){var a,b;d.forEach(g,function(c,g){var q;if(q=!b){var h=e.path();q=c.keys;var l={};if(c.regexp)if(h=c.regexp.exec(h)){for(var k=1,p=h.length;k<p;++k){var m=q[k-1],n=h[k];m&&
|
||||
n&&(l[m.name]=n)}q=l}else q=null;else q=null;q=a=q}q&&(b=t(c,{params:d.extend({},e.search(),a),pathParams:a}),b.$$route=c)});return b||g[null]&&t(g[null],{params:{},pathParams:{}})}function w(a,b){var c=[];d.forEach((a||"").split(":"),function(a,d){if(0===d)c.push(a);else{var e=a.match(/(\w+)(?:[?*])?(.*)/),g=e[1];c.push(b[g]);c.push(e[2]||"");delete b[g]}});return c.join("")}var z=!1,r,x,v={routes:g,reload:function(){z=!0;var a={defaultPrevented:!1,preventDefault:function(){this.defaultPrevented=
|
||||
!0;z=!1}};b.$evalAsync(function(){h(a);a.defaultPrevented||n()})},updateParams:function(a){if(this.current&&this.current.$$route)a=d.extend({},this.current.params,a),e.path(w(this.current.$$route.originalPath,a)),e.search(a);else throw D("norout");}};b.$on("$locationChangeStart",h);b.$on("$locationChangeSuccess",n);return v}]}),D=d.$$minErr("ngRoute");s.provider("$routeParams",function(){this.$get=function(){return{}}});s.directive("ngView",y);s.directive("ngView",w);y.$inject=["$route","$anchorScroll",
|
||||
"$animate"];w.$inject=["$compile","$controller","$route"]})(window,window.angular);
|
||||
//# sourceMappingURL=angular-route.min.js.map
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
AngularJS v1.5.8
|
||||
(c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(s,g){'use strict';function H(g){var l=[];t(l,A).chars(g);return l.join("")}var B=g.$$minErr("$sanitize"),C,l,D,E,q,A,F,t;g.module("ngSanitize",[]).provider("$sanitize",function(){function k(a,e){var b={},c=a.split(","),h;for(h=0;h<c.length;h++)b[e?q(c[h]):c[h]]=!0;return b}function I(a){for(var e={},b=0,c=a.length;b<c;b++){var h=a[b];e[h.name]=h.value}return e}function G(a){return a.replace(/&/g,"&").replace(J,function(a){var b=a.charCodeAt(0);a=a.charCodeAt(1);return"&#"+(1024*(b-55296)+
|
||||
(a-56320)+65536)+";"}).replace(K,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"<").replace(/>/g,">")}function u(a){if(a.nodeType===s.Node.ELEMENT_NODE)for(var e=a.attributes,b=0,c=e.length;b<c;b++){var h=e[b],d=h.name.toLowerCase();if("xmlns:ns1"===d||0===d.lastIndexOf("ns1:",0))a.removeAttributeNode(h),b--,c--}(e=a.firstChild)&&u(e);(e=a.nextSibling)&&u(e)}var v=!1;this.$get=["$$sanitizeUri",function(a){v&&l(w,x);return function(e){var b=[];F(e,t(b,function(b,h){return!/^unsafe:/.test(a(b,
|
||||
h))}));return b.join("")}}];this.enableSvg=function(a){return E(a)?(v=a,this):v};C=g.bind;l=g.extend;D=g.forEach;E=g.isDefined;q=g.lowercase;A=g.noop;F=function(a,e){null===a||void 0===a?a="":"string"!==typeof a&&(a=""+a);f.innerHTML=a;var b=5;do{if(0===b)throw B("uinput");b--;s.document.documentMode&&u(f);a=f.innerHTML;f.innerHTML=a}while(a!==f.innerHTML);for(b=f.firstChild;b;){switch(b.nodeType){case 1:e.start(b.nodeName.toLowerCase(),I(b.attributes));break;case 3:e.chars(b.textContent)}var c;if(!(c=
|
||||
b.firstChild)&&(1==b.nodeType&&e.end(b.nodeName.toLowerCase()),c=b.nextSibling,!c))for(;null==c;){b=b.parentNode;if(b===f)break;c=b.nextSibling;1==b.nodeType&&e.end(b.nodeName.toLowerCase())}b=c}for(;b=f.firstChild;)f.removeChild(b)};t=function(a,e){var b=!1,c=C(a,a.push);return{start:function(a,d){a=q(a);!b&&z[a]&&(b=a);b||!0!==w[a]||(c("<"),c(a),D(d,function(b,d){var f=q(d),g="img"===a&&"src"===f||"background"===f;!0!==m[f]||!0===n[f]&&!e(b,g)||(c(" "),c(d),c('="'),c(G(b)),c('"'))}),c(">"))},end:function(a){a=
|
||||
q(a);b||!0!==w[a]||!0===y[a]||(c("</"),c(a),c(">"));a==b&&(b=!1)},chars:function(a){b||c(G(a))}}};var J=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,K=/([^\#-~ |!])/g,y=k("area,br,col,hr,img,wbr"),d=k("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),r=k("rp,rt"),p=l({},r,d),d=l({},d,k("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul")),r=l({},r,k("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),
|
||||
x=k("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,stop,svg,switch,text,title,tspan"),z=k("script,style"),w=l({},y,d,r,p),n=k("background,cite,href,longdesc,src,xlink:href"),p=k("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,valign,value,vspace,width"),
|
||||
r=k("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan",
|
||||
!0),m=l({},n,r,p),f;(function(a){if(a.document&&a.document.implementation)a=a.document.implementation.createHTMLDocument("inert");else throw B("noinert");var e=(a.documentElement||a.getDocumentElement()).getElementsByTagName("body");1===e.length?f=e[0]:(e=a.createElement("html"),f=a.createElement("body"),e.appendChild(f),a.appendChild(e))})(s)});g.module("ngSanitize").filter("linky",["$sanitize",function(k){var l=/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
|
||||
q=/^mailto:/i,u=g.$$minErr("linky"),v=g.isDefined,s=g.isFunction,t=g.isObject,y=g.isString;return function(d,g,p){function x(a){a&&m.push(H(a))}function z(a,b){var c,d=w(a);m.push("<a ");for(c in d)m.push(c+'="'+d[c]+'" ');!v(g)||"target"in d||m.push('target="',g,'" ');m.push('href="',a.replace(/"/g,"""),'">');x(b);m.push("</a>")}if(null==d||""===d)return d;if(!y(d))throw u("notstring",d);for(var w=s(p)?p:t(p)?function(){return p}:function(){return{}},n=d,m=[],f,a;d=n.match(l);)f=d[0],d[2]||
|
||||
d[4]||(f=(d[3]?"http://":"mailto:")+f),a=d.index,x(n.substr(0,a)),z(f,d[0].replace(q,"")),n=n.substring(a+d[0].length);x(n);return k(m.join(""))}}])})(window,window.angular);
|
||||
//# sourceMappingURL=angular-sanitize.min.js.map
|
||||
@@ -1,156 +0,0 @@
|
||||
'use strict';
|
||||
angular.module('slick', []).directive('slick', [
|
||||
'$timeout',
|
||||
function ($timeout) {
|
||||
return {
|
||||
restrict: 'AEC',
|
||||
scope: {
|
||||
initOnload: '@',
|
||||
data: '=',
|
||||
currentIndex: '=',
|
||||
accessibility: '@',
|
||||
adaptiveHeight: '@',
|
||||
arrows: '@',
|
||||
asNavFor: '@',
|
||||
appendArrows: '@',
|
||||
appendDots: '@',
|
||||
autoplay: '@',
|
||||
autoplaySpeed: '@',
|
||||
centerMode: '@',
|
||||
centerPadding: '@',
|
||||
cssEase: '@',
|
||||
customPaging: '&',
|
||||
dots: '@',
|
||||
draggable: '@',
|
||||
easing: '@',
|
||||
fade: '@',
|
||||
focusOnSelect: '@',
|
||||
infinite: '@',
|
||||
initialSlide: '@',
|
||||
lazyLoad: '@',
|
||||
onBeforeChange: '&',
|
||||
onAfterChange: '&',
|
||||
onInit: '&',
|
||||
onReInit: '&',
|
||||
onSetPosition: '&',
|
||||
pauseOnHover: '@',
|
||||
pauseOnDotsHover: '@',
|
||||
responsive: '=',
|
||||
rtl: '@',
|
||||
slide: '@',
|
||||
slidesToShow: '@',
|
||||
slidesToScroll: '@',
|
||||
speed: '@',
|
||||
swipe: '@',
|
||||
swipeToSlide: '@',
|
||||
touchMove: '@',
|
||||
touchThreshold: '@',
|
||||
useCSS: '@',
|
||||
variableWidth: '@',
|
||||
vertical: '@',
|
||||
prevArrow: '@',
|
||||
nextArrow: '@'
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
var destroySlick, initializeSlick, isInitialized;
|
||||
destroySlick = function () {
|
||||
return $timeout(function () {
|
||||
var slider;
|
||||
slider = $(element);
|
||||
slider.unslick();
|
||||
slider.find('.slick-list').remove();
|
||||
return slider;
|
||||
});
|
||||
};
|
||||
initializeSlick = function () {
|
||||
return $timeout(function () {
|
||||
var currentIndex, slider;
|
||||
slider = $(element);
|
||||
if (scope.currentIndex != null) {
|
||||
currentIndex = scope.currentIndex;
|
||||
}
|
||||
slider.slick({
|
||||
accessibility: scope.accessibility !== 'false',
|
||||
adaptiveHeight: scope.adaptiveHeight === 'true',
|
||||
arrows: scope.arrows !== 'false',
|
||||
asNavFor: scope.asNavFor ? scope.asNavFor : void 0,
|
||||
appendArrows: scope.appendArrows ? $(scope.appendArrows) : $(element),
|
||||
appendDots: scope.appendDots ? $(scope.appendDots) : $(element),
|
||||
autoplay: scope.autoplay === 'true',
|
||||
autoplaySpeed: scope.autoplaySpeed != null ? parseInt(scope.autoplaySpeed, 10) : 3000,
|
||||
centerMode: scope.centerMode === 'true',
|
||||
centerPadding: scope.centerPadding || '50px',
|
||||
cssEase: scope.cssEase || 'ease',
|
||||
customPaging: attrs.customPaging ? scope.customPaging : void 0,
|
||||
dots: scope.dots === 'true',
|
||||
draggable: scope.draggable !== 'false',
|
||||
easing: scope.easing || 'linear',
|
||||
fade: scope.fade === 'true',
|
||||
focusOnSelect: scope.focusOnSelect === 'true',
|
||||
infinite: scope.infinite !== 'false',
|
||||
initialSlide: scope.initialSlide || 0,
|
||||
lazyLoad: scope.lazyLoad || 'ondemand',
|
||||
onBeforeChange: attrs.onBeforeChange ? scope.onBeforeChange : void 0,
|
||||
onAfterChange: function (sl, index) {
|
||||
if (attrs.onAfterChange) {
|
||||
scope.onAfterChange();
|
||||
}
|
||||
if (currentIndex != null) {
|
||||
return scope.$apply(function () {
|
||||
currentIndex = index;
|
||||
return scope.currentIndex = index;
|
||||
});
|
||||
}
|
||||
},
|
||||
onInit: function (sl) {
|
||||
if (attrs.onInit) {
|
||||
scope.onInit();
|
||||
}
|
||||
if (currentIndex != null) {
|
||||
return sl.slideHandler(currentIndex);
|
||||
}
|
||||
},
|
||||
onReInit: attrs.onReInit ? scope.onReInit : void 0,
|
||||
onSetPosition: attrs.onSetPosition ? scope.onSetPosition : void 0,
|
||||
pauseOnHover: scope.pauseOnHover !== 'false',
|
||||
responsive: scope.responsive || void 0,
|
||||
rtl: scope.rtl === 'true',
|
||||
slide: scope.slide || 'div',
|
||||
slidesToShow: scope.slidesToShow != null ? parseInt(scope.slidesToShow, 10) : 1,
|
||||
slidesToScroll: scope.slidesToScroll != null ? parseInt(scope.slidesToScroll, 10) : 1,
|
||||
speed: scope.speed != null ? parseInt(scope.speed, 10) : 300,
|
||||
swipe: scope.swipe !== 'false',
|
||||
swipeToSlide: scope.swipeToSlide === 'true',
|
||||
touchMove: scope.touchMove !== 'false',
|
||||
touchThreshold: scope.touchThreshold ? parseInt(scope.touchThreshold, 10) : 5,
|
||||
useCSS: scope.useCSS !== 'false',
|
||||
variableWidth: scope.variableWidth === 'true',
|
||||
vertical: scope.vertical === 'true',
|
||||
prevArrow: scope.prevArrow ? $(scope.prevArrow) : void 0,
|
||||
nextArrow: scope.nextArrow ? $(scope.nextArrow) : void 0
|
||||
});
|
||||
return scope.$watch('currentIndex', function (newVal, oldVal) {
|
||||
if (currentIndex != null && newVal != null && newVal !== currentIndex) {
|
||||
return slider.slickGoTo(newVal);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
if (scope.initOnload) {
|
||||
isInitialized = false;
|
||||
return scope.$watch('data', function (newVal, oldVal) {
|
||||
if (newVal != null) {
|
||||
if (isInitialized) {
|
||||
destroySlick();
|
||||
}
|
||||
initializeSlick();
|
||||
return isInitialized = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return initializeSlick();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* angular-translate - v2.18.3 - 2020-07-08
|
||||
*
|
||||
* Copyright (c) 2020 The angular-translate team, Pascal Precht; Licensed MIT
|
||||
*/
|
||||
!function(e,i){"function"==typeof define&&define.amd?define([],function(){return i()}):"object"==typeof module&&module.exports?module.exports=i():i()}(0,function(){function e(n,a){"use strict";return function(r){if(!(r&&(angular.isArray(r.files)||angular.isString(r.prefix)&&angular.isString(r.suffix))))throw new Error("Couldn't load static files, no files and prefix or suffix specified!");r.files||(r.files=[{prefix:r.prefix,suffix:r.suffix}]);for(var e=function(e){if(!e||!angular.isString(e.prefix)||!angular.isString(e.suffix))throw new Error("Couldn't load static file, no prefix or suffix specified!");var i=[e.prefix,r.key,e.suffix].join("");return angular.isObject(r.fileMap)&&r.fileMap[i]&&(i=r.fileMap[i]),a(angular.extend({url:i,method:"GET"},r.$http)).then(function(e){return e.data},function(){return n.reject(r.key)})},i=[],t=r.files.length,f=0;f<t;f++)i.push(e({prefix:r.files[f].prefix,key:r.key,suffix:r.files[f].suffix}));return n.all(i).then(function(e){for(var i=e.length,r={},t=0;t<i;t++)for(var f in e[t])r[f]=e[t][f];return r})}}return e.$inject=["$q","$http"],angular.module("pascalprecht.translate").factory("$translateStaticFilesLoader",e),e.displayName="$translateStaticFilesLoader","pascalprecht.translate"});
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* angular-translate - v2.18.3 - 2020-07-08
|
||||
*
|
||||
* Copyright (c) 2020 The angular-translate team, Pascal Precht; Licensed MIT
|
||||
*/
|
||||
!function(t,e){"function"==typeof define&&define.amd?define([],function(){return e()}):"object"==typeof module&&module.exports?module.exports=e():e()}(0,function(){function t(t){"use strict";var n;if(1===angular.version.major&&4<=angular.version.minor){var o=t.get("$cookies");n={get:function(t){return o.get(t)},put:function(t,e){o.put(t,e)}}}else{var r=t.get("$cookieStore");n={get:function(t){return r.get(t)},put:function(t,e){r.put(t,e)}}}return{get:function(t){return n.get(t)},set:function(t,e){n.put(t,e)},put:function(t,e){n.put(t,e)}}}return t.$inject=["$injector"],angular.module("pascalprecht.translate").factory("$translateCookieStorage",t),t.displayName="$translateCookieStorage","pascalprecht.translate"});
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* angular-translate - v2.18.3 - 2020-07-08
|
||||
*
|
||||
* Copyright (c) 2020 The angular-translate team, Pascal Precht; Licensed MIT
|
||||
*/
|
||||
!function(t,e){"function"==typeof define&&define.amd?define([],function(){return e()}):"object"==typeof module&&module.exports?module.exports=e():e()}(0,function(){function t(a,t){"use strict";var o,e={get:function(t){return o||(o=a.localStorage.getItem(t)),o},set:function(t,e){o=e,a.localStorage.setItem(t,e)},put:function(t,e){o=e,a.localStorage.setItem(t,e)}},r="localStorage"in a;if(r){var n="pascalprecht.translate.storageTest";try{r=null!==a.localStorage&&(a.localStorage.setItem(n,"foo"),a.localStorage.removeItem(n),!0)}catch(t){r=!1}}return r?e:t}return t.$inject=["$window","$translateCookieStorage"],angular.module("pascalprecht.translate").factory("$translateLocalStorage",t),t.displayName="$translateLocalStorageFactory","pascalprecht.translate"});
|
||||
@@ -1,271 +0,0 @@
|
||||
/**
|
||||
* angular-ui-notification - Angular.js service providing simple notifications using Bootstrap 3 styles with css transitions for animating
|
||||
* @author Alex_Crack
|
||||
* @version v0.3.6
|
||||
* @link https://github.com/alexcrack/angular-ui-notification
|
||||
* @license MIT
|
||||
*/
|
||||
angular.module('ui-notification', []);
|
||||
|
||||
angular.module('ui-notification').provider('Notification', function () {
|
||||
|
||||
this.options = {
|
||||
delay: 5000,
|
||||
startTop: 10,
|
||||
startRight: 10,
|
||||
verticalSpacing: 10,
|
||||
horizontalSpacing: 10,
|
||||
positionX: 'right',
|
||||
positionY: 'top',
|
||||
replaceMessage: false,
|
||||
templateUrl: 'angular-ui-notification.html',
|
||||
onClose: undefined,
|
||||
onClick: undefined,
|
||||
closeOnClick: true,
|
||||
maxCount: 0, // 0 - Infinite
|
||||
container: 'body',
|
||||
priority: 10
|
||||
};
|
||||
|
||||
this.setOptions = function (options) {
|
||||
if (!angular.isObject(options)) throw new Error("Options should be an object!");
|
||||
this.options = angular.extend({}, this.options, options);
|
||||
};
|
||||
|
||||
this.$get = ["$timeout", "$http", "$compile", "$templateCache", "$rootScope", "$injector", "$sce", "$q", "$window", function ($timeout, $http, $compile, $templateCache, $rootScope, $injector, $sce, $q, $window) {
|
||||
var options = this.options;
|
||||
|
||||
var startTop = options.startTop;
|
||||
var startRight = options.startRight;
|
||||
var verticalSpacing = options.verticalSpacing;
|
||||
var horizontalSpacing = options.horizontalSpacing;
|
||||
var delay = options.delay;
|
||||
|
||||
var messageElements = [];
|
||||
var isResizeBound = false;
|
||||
|
||||
var notify = function (args, t) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (typeof args !== 'object' || args === null) {
|
||||
args = {message: args};
|
||||
}
|
||||
|
||||
args.scope = args.scope ? args.scope : $rootScope;
|
||||
args.template = args.templateUrl ? args.templateUrl : options.templateUrl;
|
||||
args.delay = !angular.isUndefined(args.delay) ? args.delay : delay;
|
||||
args.type = t || args.type || options.type || '';
|
||||
args.positionY = args.positionY ? args.positionY : options.positionY;
|
||||
args.positionX = args.positionX ? args.positionX : options.positionX;
|
||||
args.replaceMessage = args.replaceMessage ? args.replaceMessage : options.replaceMessage;
|
||||
args.onClose = args.onClose ? args.onClose : options.onClose;
|
||||
args.onClick = args.onClick ? args.onClick : options.onClick;
|
||||
args.closeOnClick = (args.closeOnClick !== null && args.closeOnClick !== undefined) ? args.closeOnClick : options.closeOnClick;
|
||||
args.container = args.container ? args.container : options.container;
|
||||
args.priority = args.priority ? args.priority : options.priority;
|
||||
|
||||
var template = $templateCache.get(args.template);
|
||||
|
||||
if (template) {
|
||||
processNotificationTemplate(template);
|
||||
} else {
|
||||
// load it via $http only if it isn't default template and template isn't exist in template cache
|
||||
// cache:true means cache it for later access.
|
||||
$http.get(args.template, {cache: true})
|
||||
.then(function (response) {
|
||||
processNotificationTemplate(response.data);
|
||||
})
|
||||
.catch(function (data) {
|
||||
throw new Error('Template (' + args.template + ') could not be loaded. ' + data);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function processNotificationTemplate(template) {
|
||||
|
||||
var scope = args.scope.$new();
|
||||
scope.message = $sce.trustAsHtml(args.message);
|
||||
scope.title = $sce.trustAsHtml(args.title);
|
||||
scope.t = args.type.substr(0, 1);
|
||||
scope.delay = args.delay;
|
||||
scope.onClose = args.onClose;
|
||||
scope.onClick = args.onClick;
|
||||
|
||||
var priorityCompareTop = function (a, b) {
|
||||
return a._priority - b._priority;
|
||||
};
|
||||
|
||||
var priorityCompareBtm = function (a, b) {
|
||||
return b._priority - a._priority;
|
||||
};
|
||||
|
||||
var reposite = function () {
|
||||
var j = 0;
|
||||
var k = 0;
|
||||
var lastTop = startTop;
|
||||
var lastRight = startRight;
|
||||
var lastPosition = [];
|
||||
|
||||
if (args.positionY === 'top') {
|
||||
messageElements.sort(priorityCompareTop);
|
||||
} else if (args.positionY === 'bottom') {
|
||||
messageElements.sort(priorityCompareBtm);
|
||||
}
|
||||
|
||||
for (var i = messageElements.length - 1; i >= 0; i--) {
|
||||
var element = messageElements[i];
|
||||
if (args.replaceMessage && i < messageElements.length - 1) {
|
||||
element.addClass('killed');
|
||||
continue;
|
||||
}
|
||||
var elHeight = parseInt(element[0].offsetHeight);
|
||||
var elWidth = parseInt(element[0].offsetWidth);
|
||||
var position = lastPosition[element._positionY + element._positionX];
|
||||
|
||||
if ((top + elHeight) > window.innerHeight) {
|
||||
position = startTop;
|
||||
k++;
|
||||
j = 0;
|
||||
}
|
||||
|
||||
var top = (lastTop = position ? (j === 0 ? position : position + verticalSpacing) : startTop);
|
||||
var right = lastRight + (k * (horizontalSpacing + elWidth));
|
||||
|
||||
element.css(element._positionY, top + 'px');
|
||||
if (element._positionX === 'center') {
|
||||
element.css('left', parseInt(window.innerWidth / 2 - elWidth / 2) + 'px');
|
||||
} else {
|
||||
element.css(element._positionX, right + 'px');
|
||||
}
|
||||
|
||||
lastPosition[element._positionY + element._positionX] = top + elHeight;
|
||||
|
||||
if (options.maxCount > 0 && messageElements.length > options.maxCount && i === 0) {
|
||||
element.scope().kill(true);
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
};
|
||||
|
||||
var templateElement = $compile(template)(scope);
|
||||
templateElement._positionY = args.positionY;
|
||||
templateElement._positionX = args.positionX;
|
||||
templateElement._priority = args.priority;
|
||||
templateElement.addClass(args.type);
|
||||
|
||||
var closeEvent = function (e) {
|
||||
e = e.originalEvent || e;
|
||||
if (e.type === 'click' || e.propertyName === 'opacity' && e.elapsedTime >= 1) {
|
||||
|
||||
if (scope.onClose) {
|
||||
scope.$apply(scope.onClose(templateElement));
|
||||
}
|
||||
|
||||
if (e.type === 'click')
|
||||
if (scope.onClick) {
|
||||
scope.$apply(scope.onClick(templateElement));
|
||||
}
|
||||
|
||||
templateElement.remove();
|
||||
messageElements.splice(messageElements.indexOf(templateElement), 1);
|
||||
scope.$destroy();
|
||||
reposite();
|
||||
}
|
||||
};
|
||||
|
||||
if (args.closeOnClick) {
|
||||
templateElement.addClass('clickable');
|
||||
templateElement.bind('click', closeEvent);
|
||||
}
|
||||
|
||||
templateElement.bind('webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd', closeEvent);
|
||||
|
||||
if (angular.isNumber(args.delay)) {
|
||||
$timeout(function () {
|
||||
templateElement.addClass('killed');
|
||||
}, args.delay);
|
||||
}
|
||||
|
||||
setCssTransitions('none');
|
||||
|
||||
angular.element(document.querySelector(args.container)).append(templateElement);
|
||||
var offset = -(parseInt(templateElement[0].offsetHeight) + 50);
|
||||
templateElement.css(templateElement._positionY, offset + "px");
|
||||
messageElements.push(templateElement);
|
||||
|
||||
if (args.positionX == 'center') {
|
||||
var elWidth = parseInt(templateElement[0].offsetWidth);
|
||||
templateElement.css('left', parseInt(window.innerWidth / 2 - elWidth / 2) + 'px');
|
||||
}
|
||||
|
||||
$timeout(function () {
|
||||
setCssTransitions('');
|
||||
});
|
||||
|
||||
function setCssTransitions(value) {
|
||||
['-webkit-transition', '-o-transition', 'transition'].forEach(function (prefix) {
|
||||
templateElement.css(prefix, value);
|
||||
});
|
||||
}
|
||||
|
||||
scope._templateElement = templateElement;
|
||||
|
||||
scope.kill = function (isHard) {
|
||||
if (isHard) {
|
||||
if (scope.onClose) {
|
||||
scope.$apply(scope.onClose(scope._templateElement));
|
||||
}
|
||||
|
||||
messageElements.splice(messageElements.indexOf(scope._templateElement), 1);
|
||||
scope._templateElement.remove();
|
||||
scope.$destroy();
|
||||
$timeout(reposite);
|
||||
} else {
|
||||
scope._templateElement.addClass('killed');
|
||||
}
|
||||
};
|
||||
|
||||
$timeout(reposite);
|
||||
|
||||
if (!isResizeBound) {
|
||||
angular.element($window).bind('resize', function (e) {
|
||||
$timeout(reposite);
|
||||
});
|
||||
isResizeBound = true;
|
||||
}
|
||||
|
||||
deferred.resolve(scope);
|
||||
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
notify.primary = function (args) {
|
||||
return this(args, 'primary');
|
||||
};
|
||||
notify.error = function (args) {
|
||||
return this(args, 'error');
|
||||
};
|
||||
notify.success = function (args) {
|
||||
return this(args, 'success');
|
||||
};
|
||||
notify.info = function (args) {
|
||||
return this(args, 'info');
|
||||
};
|
||||
notify.warning = function (args) {
|
||||
return this(args, 'warning');
|
||||
};
|
||||
|
||||
notify.clearAll = function () {
|
||||
angular.forEach(messageElements, function (element) {
|
||||
element.addClass('killed');
|
||||
});
|
||||
};
|
||||
|
||||
return notify;
|
||||
}];
|
||||
});
|
||||
|
||||
angular.module("ui-notification").run(["$templateCache", function($templateCache) {$templateCache.put("angular-ui-notification.html","<div class=\"ui-notification\"><h3 ng-show=\"title\" ng-bind-html=\"title\"></h3><div class=\"message\" ng-bind-html=\"message\"></div></div>");}]);
|
||||
@@ -1,318 +0,0 @@
|
||||
/*
|
||||
AngularJS v1.5.8
|
||||
(c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(C){'use strict';function N(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.5.8/"+(a?a+"/":"")+b;for(b=1;b<arguments.length;b++){d=d+(1==b?"?":"&")+"p"+(b-1)+"=";var c=encodeURIComponent,e;e=arguments[b];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;d+=c(e)}return Error(d)}}function ta(a){if(null==a||Va(a))return!1;if(L(a)||G(a)||F&&a instanceof F)return!0;
|
||||
var b="length"in Object(a)&&a.length;return T(b)&&(0<=b&&(b-1 in a||a instanceof Array)||"function"==typeof a.item)}function q(a,b,d){var c,e;if(a)if(z(a))for(c in a)"prototype"==c||"length"==c||"name"==c||a.hasOwnProperty&&!a.hasOwnProperty(c)||b.call(d,a[c],c,a);else if(L(a)||ta(a)){var f="object"!==typeof a;c=0;for(e=a.length;c<e;c++)(f||c in a)&&b.call(d,a[c],c,a)}else if(a.forEach&&a.forEach!==q)a.forEach(b,d,a);else if(sc(a))for(c in a)b.call(d,a[c],c,a);else if("function"===typeof a.hasOwnProperty)for(c in a)a.hasOwnProperty(c)&&
|
||||
b.call(d,a[c],c,a);else for(c in a)ua.call(a,c)&&b.call(d,a[c],c,a);return a}function tc(a,b,d){for(var c=Object.keys(a).sort(),e=0;e<c.length;e++)b.call(d,a[c[e]],c[e]);return c}function uc(a){return function(b,d){a(d,b)}}function Yd(){return++pb}function Pb(a,b,d){for(var c=a.$$hashKey,e=0,f=b.length;e<f;++e){var g=b[e];if(D(g)||z(g))for(var h=Object.keys(g),k=0,l=h.length;k<l;k++){var m=h[k],n=g[m];d&&D(n)?da(n)?a[m]=new Date(n.valueOf()):Wa(n)?a[m]=new RegExp(n):n.nodeName?a[m]=n.cloneNode(!0):
|
||||
Qb(n)?a[m]=n.clone():(D(a[m])||(a[m]=L(n)?[]:{}),Pb(a[m],[n],!0)):a[m]=n}}c?a.$$hashKey=c:delete a.$$hashKey;return a}function S(a){return Pb(a,va.call(arguments,1),!1)}function Zd(a){return Pb(a,va.call(arguments,1),!0)}function Z(a){return parseInt(a,10)}function Rb(a,b){return S(Object.create(a),b)}function A(){}function Xa(a){return a}function ha(a){return function(){return a}}function vc(a){return z(a.toString)&&a.toString!==ma}function y(a){return"undefined"===typeof a}function w(a){return"undefined"!==
|
||||
typeof a}function D(a){return null!==a&&"object"===typeof a}function sc(a){return null!==a&&"object"===typeof a&&!wc(a)}function G(a){return"string"===typeof a}function T(a){return"number"===typeof a}function da(a){return"[object Date]"===ma.call(a)}function z(a){return"function"===typeof a}function Wa(a){return"[object RegExp]"===ma.call(a)}function Va(a){return a&&a.window===a}function Ya(a){return a&&a.$evalAsync&&a.$watch}function Ga(a){return"boolean"===typeof a}function $d(a){return a&&T(a.length)&&
|
||||
ae.test(ma.call(a))}function Qb(a){return!(!a||!(a.nodeName||a.prop&&a.attr&&a.find))}function be(a){var b={};a=a.split(",");var d;for(d=0;d<a.length;d++)b[a[d]]=!0;return b}function wa(a){return Q(a.nodeName||a[0]&&a[0].nodeName)}function Za(a,b){var d=a.indexOf(b);0<=d&&a.splice(d,1);return d}function pa(a,b){function d(a,b){var d=b.$$hashKey,e;if(L(a)){e=0;for(var f=a.length;e<f;e++)b.push(c(a[e]))}else if(sc(a))for(e in a)b[e]=c(a[e]);else if(a&&"function"===typeof a.hasOwnProperty)for(e in a)a.hasOwnProperty(e)&&
|
||||
(b[e]=c(a[e]));else for(e in a)ua.call(a,e)&&(b[e]=c(a[e]));d?b.$$hashKey=d:delete b.$$hashKey;return b}function c(a){if(!D(a))return a;var b=f.indexOf(a);if(-1!==b)return g[b];if(Va(a)||Ya(a))throw xa("cpws");var b=!1,c=e(a);void 0===c&&(c=L(a)?[]:Object.create(wc(a)),b=!0);f.push(a);g.push(c);return b?d(a,c):c}function e(a){switch(ma.call(a)){case "[object Int8Array]":case "[object Int16Array]":case "[object Int32Array]":case "[object Float32Array]":case "[object Float64Array]":case "[object Uint8Array]":case "[object Uint8ClampedArray]":case "[object Uint16Array]":case "[object Uint32Array]":return new a.constructor(c(a.buffer),
|
||||
a.byteOffset,a.length);case "[object ArrayBuffer]":if(!a.slice){var b=new ArrayBuffer(a.byteLength);(new Uint8Array(b)).set(new Uint8Array(a));return b}return a.slice(0);case "[object Boolean]":case "[object Number]":case "[object String]":case "[object Date]":return new a.constructor(a.valueOf());case "[object RegExp]":return b=new RegExp(a.source,a.toString().match(/[^\/]*$/)[0]),b.lastIndex=a.lastIndex,b;case "[object Blob]":return new a.constructor([a],{type:a.type})}if(z(a.cloneNode))return a.cloneNode(!0)}
|
||||
var f=[],g=[];if(b){if($d(b)||"[object ArrayBuffer]"===ma.call(b))throw xa("cpta");if(a===b)throw xa("cpi");L(b)?b.length=0:q(b,function(a,d){"$$hashKey"!==d&&delete b[d]});f.push(a);g.push(b);return d(a,b)}return c(a)}function na(a,b){if(a===b)return!0;if(null===a||null===b)return!1;if(a!==a&&b!==b)return!0;var d=typeof a,c;if(d==typeof b&&"object"==d)if(L(a)){if(!L(b))return!1;if((d=a.length)==b.length){for(c=0;c<d;c++)if(!na(a[c],b[c]))return!1;return!0}}else{if(da(a))return da(b)?na(a.getTime(),
|
||||
b.getTime()):!1;if(Wa(a))return Wa(b)?a.toString()==b.toString():!1;if(Ya(a)||Ya(b)||Va(a)||Va(b)||L(b)||da(b)||Wa(b))return!1;d=U();for(c in a)if("$"!==c.charAt(0)&&!z(a[c])){if(!na(a[c],b[c]))return!1;d[c]=!0}for(c in b)if(!(c in d)&&"$"!==c.charAt(0)&&w(b[c])&&!z(b[c]))return!1;return!0}return!1}function $a(a,b,d){return a.concat(va.call(b,d))}function ab(a,b){var d=2<arguments.length?va.call(arguments,2):[];return!z(b)||b instanceof RegExp?b:d.length?function(){return arguments.length?b.apply(a,
|
||||
$a(d,arguments,0)):b.apply(a,d)}:function(){return arguments.length?b.apply(a,arguments):b.call(a)}}function ce(a,b){var d=b;"string"===typeof a&&"$"===a.charAt(0)&&"$"===a.charAt(1)?d=void 0:Va(b)?d="$WINDOW":b&&C.document===b?d="$DOCUMENT":Ya(b)&&(d="$SCOPE");return d}function bb(a,b){if(!y(a))return T(b)||(b=b?2:null),JSON.stringify(a,ce,b)}function xc(a){return G(a)?JSON.parse(a):a}function yc(a,b){a=a.replace(de,"");var d=Date.parse("Jan 01, 1970 00:00:00 "+a)/6E4;return isNaN(d)?b:d}function Sb(a,
|
||||
b,d){d=d?-1:1;var c=a.getTimezoneOffset();b=yc(b,c);d*=b-c;a=new Date(a.getTime());a.setMinutes(a.getMinutes()+d);return a}function ya(a){a=F(a).clone();try{a.empty()}catch(b){}var d=F("<div>").append(a).html();try{return a[0].nodeType===Ma?Q(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+Q(b)})}catch(c){return Q(d)}}function zc(a){try{return decodeURIComponent(a)}catch(b){}}function Ac(a){var b={};q((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),
|
||||
c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=zc(e),w(e)&&(f=w(f)?zc(f):!0,ua.call(b,e)?L(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Tb(a){var b=[];q(a,function(a,c){L(a)?q(a,function(a){b.push(ea(c,!0)+(!0===a?"":"="+ea(a,!0)))}):b.push(ea(c,!0)+(!0===a?"":"="+ea(a,!0)))});return b.length?b.join("&"):""}function qb(a){return ea(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ea(a,b){return encodeURIComponent(a).replace(/%40/gi,
|
||||
"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ee(a,b){var d,c,e=Na.length;for(c=0;c<e;++c)if(d=Na[c]+b,G(d=a.getAttribute(d)))return d;return null}function fe(a,b){var d,c,e={};q(Na,function(b){b+="app";!d&&a.hasAttribute&&a.hasAttribute(b)&&(d=a,c=a.getAttribute(b))});q(Na,function(b){b+="app";var e;!d&&(e=a.querySelector("["+b.replace(":","\\:")+"]"))&&(d=e,c=e.getAttribute(b))});d&&(e.strictDi=null!==ee(d,"strict-di"),
|
||||
b(d,c?[c]:[],e))}function Bc(a,b,d){D(d)||(d={});d=S({strictDi:!1},d);var c=function(){a=F(a);if(a.injector()){var c=a[0]===C.document?"document":ya(a);throw xa("btstrpd",c.replace(/</,"<").replace(/>/,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=cb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",
|
||||
d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;C&&e.test(C.name)&&(d.debugInfoEnabled=!0,C.name=C.name.replace(e,""));if(C&&!f.test(C.name))return c();C.name=C.name.replace(f,"");ca.resumeBootstrap=function(a){q(a,function(a){b.push(a)});return c()};z(ca.resumeDeferredBootstrap)&&ca.resumeDeferredBootstrap()}function ge(){C.name="NG_ENABLE_DEBUG_INFO!"+C.name;C.location.reload()}function he(a){a=ca.element(a).injector();if(!a)throw xa("test");return a.get("$$testability")}
|
||||
function Cc(a,b){b=b||"_";return a.replace(ie,function(a,c){return(c?b:"")+a.toLowerCase()})}function je(){var a;if(!Dc){var b=rb();(qa=y(b)?C.jQuery:b?C[b]:void 0)&&qa.fn.on?(F=qa,S(qa.fn,{scope:Oa.scope,isolateScope:Oa.isolateScope,controller:Oa.controller,injector:Oa.injector,inheritedData:Oa.inheritedData}),a=qa.cleanData,qa.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=qa._data(f,"events"))&&c.$destroy&&qa(f).triggerHandler("$destroy");a(b)}):F=O;ca.element=F;Dc=!0}}function sb(a,
|
||||
b,d){if(!a)throw xa("areq",b||"?",d||"required");return a}function Pa(a,b,d){d&&L(a)&&(a=a[a.length-1]);sb(z(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Qa(a,b){if("hasOwnProperty"===a)throw xa("badname",b);}function Ec(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g<f;g++)c=b[g],a&&(a=(e=a)[c]);return!d&&z(a)?ab(e,a):a}function tb(a){for(var b=a[0],d=a[a.length-1],c,e=1;b!==d&&(b=b.nextSibling);e++)if(c||a[e]!==
|
||||
b)c||(c=F(va.call(a,0,e))),c.push(b);return c||a}function U(){return Object.create(null)}function ke(a){function b(a,b,c){return a[b]||(a[b]=c())}var d=N("$injector"),c=N("ng");a=b(a,"angular",Object);a.$$minErr=a.$$minErr||N;return b(a,"module",function(){var a={};return function(f,g,h){if("hasOwnProperty"===f)throw c("badname","module");g&&a.hasOwnProperty(f)&&(a[f]=null);return b(a,f,function(){function a(b,d,e,f){f||(f=c);return function(){f[e||"push"]([b,d,arguments]);return R}}function b(a,
|
||||
d){return function(b,e){e&&z(e)&&(e.$$moduleName=f);c.push([a,d,arguments]);return R}}if(!g)throw d("nomod",f);var c=[],e=[],p=[],u=a("$injector","invoke","push",e),R={_invokeQueue:c,_configBlocks:e,_runBlocks:p,requires:g,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide","decorator"),animation:b("$animateProvider","register"),filter:b("$filterProvider",
|
||||
"register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),component:b("$compileProvider","component"),config:u,run:function(a){p.push(a);return this}};h&&u(h);return R})}})}function ia(a,b){if(L(a)){b=b||[];for(var d=0,c=a.length;d<c;d++)b[d]=a[d]}else if(D(a))for(d in b=b||{},a)if("$"!==d.charAt(0)||"$"!==d.charAt(1))b[d]=a[d];return b||a}function le(a){S(a,{bootstrap:Bc,copy:pa,extend:S,merge:Zd,equals:na,element:F,forEach:q,injector:cb,noop:A,bind:ab,
|
||||
toJson:bb,fromJson:xc,identity:Xa,isUndefined:y,isDefined:w,isString:G,isFunction:z,isObject:D,isNumber:T,isElement:Qb,isArray:L,version:me,isDate:da,lowercase:Q,uppercase:ub,callbacks:{$$counter:0},getTestability:he,$$minErr:N,$$csp:Ba,reloadWithDebugInfo:ge});Ub=ke(C);Ub("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:ne});a.provider("$compile",Fc).directive({a:oe,input:Gc,textarea:Gc,form:pe,script:qe,select:re,style:se,option:te,ngBind:ue,ngBindHtml:ve,ngBindTemplate:we,ngClass:xe,
|
||||
ngClassEven:ye,ngClassOdd:ze,ngCloak:Ae,ngController:Be,ngForm:Ce,ngHide:De,ngIf:Ee,ngInclude:Fe,ngInit:Ge,ngNonBindable:He,ngPluralize:Ie,ngRepeat:Je,ngShow:Ke,ngStyle:Le,ngSwitch:Me,ngSwitchWhen:Ne,ngSwitchDefault:Oe,ngOptions:Pe,ngTransclude:Qe,ngModel:Re,ngList:Se,ngChange:Te,pattern:Hc,ngPattern:Hc,required:Ic,ngRequired:Ic,minlength:Jc,ngMinlength:Jc,maxlength:Kc,ngMaxlength:Kc,ngValue:Ue,ngModelOptions:Ve}).directive({ngInclude:We}).directive(vb).directive(Lc);a.provider({$anchorScroll:Xe,
|
||||
$animate:Ye,$animateCss:Ze,$$animateJs:$e,$$animateQueue:af,$$AnimateRunner:bf,$$animateAsyncRun:cf,$browser:df,$cacheFactory:ef,$controller:ff,$document:gf,$exceptionHandler:hf,$filter:Mc,$$forceReflow:jf,$interpolate:kf,$interval:lf,$http:mf,$httpParamSerializer:nf,$httpParamSerializerJQLike:of,$httpBackend:pf,$xhrFactory:qf,$jsonpCallbacks:rf,$location:sf,$log:tf,$parse:uf,$rootScope:vf,$q:wf,$$q:xf,$sce:yf,$sceDelegate:zf,$sniffer:Af,$templateCache:Bf,$templateRequest:Cf,$$testability:Df,$timeout:Ef,
|
||||
$window:Ff,$$rAF:Gf,$$jqLite:Hf,$$HashMap:If,$$cookieReader:Jf})}])}function db(a){return a.replace(Kf,function(a,d,c,e){return e?c.toUpperCase():c}).replace(Lf,"Moz$1")}function Nc(a){a=a.nodeType;return 1===a||!a||9===a}function Oc(a,b){var d,c,e=b.createDocumentFragment(),f=[];if(Vb.test(a)){d=e.appendChild(b.createElement("div"));c=(Mf.exec(a)||["",""])[1].toLowerCase();c=ja[c]||ja._default;d.innerHTML=c[1]+a.replace(Nf,"<$1></$2>")+c[2];for(c=c[0];c--;)d=d.lastChild;f=$a(f,d.childNodes);d=e.firstChild;
|
||||
d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";q(f,function(a){e.appendChild(a)});return e}function Pc(a,b){var d=a.parentNode;d&&d.replaceChild(b,a);b.appendChild(a)}function O(a){if(a instanceof O)return a;var b;G(a)&&(a=W(a),b=!0);if(!(this instanceof O)){if(b&&"<"!=a.charAt(0))throw Wb("nosel");return new O(a)}if(b){b=C.document;var d;a=(d=Of.exec(a))?[b.createElement(d[1])]:(d=Oc(a,b))?d.childNodes:[]}Qc(this,a)}function Xb(a){return a.cloneNode(!0)}function wb(a,
|
||||
b){b||eb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;c<e;c++)eb(d[c])}function Rc(a,b,d,c){if(w(c))throw Wb("offargs");var e=(c=xb(a))&&c.events,f=c&&c.handle;if(f)if(b){var g=function(b){var c=e[b];w(d)&&Za(c||[],d);w(d)&&c&&0<c.length||(a.removeEventListener(b,f,!1),delete e[b])};q(b.split(" "),function(a){g(a);yb[a]&&g(yb[a])})}else for(b in e)"$destroy"!==b&&a.removeEventListener(b,f,!1),delete e[b]}function eb(a,b){var d=a.ng339,c=d&&fb[d];c&&(b?delete c.data[b]:
|
||||
(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),Rc(a)),delete fb[d],a.ng339=void 0))}function xb(a,b){var d=a.ng339,d=d&&fb[d];b&&!d&&(a.ng339=d=++Pf,d=fb[d]={events:{},data:{},handle:void 0});return d}function Yb(a,b,d){if(Nc(a)){var c=w(d),e=!c&&b&&!D(b),f=!b;a=(a=xb(a,!e))&&a.data;if(c)a[b]=d;else{if(f)return a;if(e)return a&&a[b];S(a,b)}}}function zb(a,b){return a.getAttribute?-1<(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+b+" "):!1}function Ab(a,b){b&&a.setAttribute&&
|
||||
q(b.split(" "),function(b){a.setAttribute("class",W((" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+W(b)+" "," ")))})}function Bb(a,b){if(b&&a.setAttribute){var d=(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");q(b.split(" "),function(a){a=W(a);-1===d.indexOf(" "+a+" ")&&(d+=a+" ")});a.setAttribute("class",W(d))}}function Qc(a,b){if(b)if(b.nodeType)a[a.length++]=b;else{var d=b.length;if("number"===typeof d&&b.window!==b){if(d)for(var c=0;c<d;c++)a[a.length++]=
|
||||
b[c]}else a[a.length++]=b}}function Sc(a,b){return Cb(a,"$"+(b||"ngController")+"Controller")}function Cb(a,b,d){9==a.nodeType&&(a=a.documentElement);for(b=L(b)?b:[b];a;){for(var c=0,e=b.length;c<e;c++)if(w(d=F.data(a,b[c])))return d;a=a.parentNode||11===a.nodeType&&a.host}}function Tc(a){for(wb(a,!0);a.firstChild;)a.removeChild(a.firstChild)}function Db(a,b){b||wb(a);var d=a.parentNode;d&&d.removeChild(a)}function Qf(a,b){b=b||C;if("complete"===b.document.readyState)b.setTimeout(a);else F(b).on("load",
|
||||
a)}function Uc(a,b){var d=Eb[b.toLowerCase()];return d&&Vc[wa(a)]&&d}function Rf(a,b){var d=function(c,d){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=b[d||c.type],g=f?f.length:0;if(g){if(y(c.immediatePropagationStopped)){var h=c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=!0;c.stopPropagation&&c.stopPropagation();h&&h.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};var k=f.specialHandlerWrapper||
|
||||
Sf;1<g&&(f=ia(f));for(var l=0;l<g;l++)c.isImmediatePropagationStopped()||k(a,c,f[l])}};d.elem=a;return d}function Sf(a,b,d){d.call(a,b)}function Tf(a,b,d){var c=b.relatedTarget;c&&(c===a||Uf.call(a,c))||d.call(a,b)}function Hf(){this.$get=function(){return S(O,{hasClass:function(a,b){a.attr&&(a=a[0]);return zb(a,b)},addClass:function(a,b){a.attr&&(a=a[0]);return Bb(a,b)},removeClass:function(a,b){a.attr&&(a=a[0]);return Ab(a,b)}})}}function Ca(a,b){var d=a&&a.$$hashKey;if(d)return"function"===typeof d&&
|
||||
(d=a.$$hashKey()),d;d=typeof a;return d="function"==d||"object"==d&&null!==a?a.$$hashKey=d+":"+(b||Yd)():d+":"+a}function Ra(a,b){if(b){var d=0;this.nextUid=function(){return++d}}q(a,this.put,this)}function Wc(a){a=(Function.prototype.toString.call(a)+" ").replace(Vf,"");return a.match(Wf)||a.match(Xf)}function Yf(a){return(a=Wc(a))?"function("+(a[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function cb(a,b){function d(a){return function(b,c){if(D(b))q(b,uc(a));else return a(b,c)}}function c(a,b){Qa(a,
|
||||
"service");if(z(b)||L(b))b=p.instantiate(b);if(!b.$get)throw Ha("pget",a);return n[a+"Provider"]=b}function e(a,b){return function(){var c=B.invoke(b,this);if(y(c))throw Ha("undef",a);return c}}function f(a,b,d){return c(a,{$get:!1!==d?e(a,b):b})}function g(a){sb(y(a)||L(a),"modulesToLoad","not an array");var b=[],c;q(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=p.get(e[0]);f[e[1]].apply(f,e[2])}}if(!m.get(a)){m.put(a,!0);try{G(a)?(c=Ub(a),b=b.concat(g(c.requires)).concat(c._runBlocks),
|
||||
d(c._invokeQueue),d(c._configBlocks)):z(a)?b.push(p.invoke(a)):L(a)?b.push(p.invoke(a)):Pa(a,"module")}catch(e){throw L(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ha("modulerr",a,e.stack||e.message||e);}}});return b}function h(a,c){function d(b,e){if(a.hasOwnProperty(b)){if(a[b]===k)throw Ha("cdep",b+" <- "+l.join(" <- "));return a[b]}try{return l.unshift(b),a[b]=k,a[b]=c(b,e)}catch(f){throw a[b]===k&&delete a[b],f;}finally{l.shift()}}function e(a,
|
||||
c,f){var g=[];a=cb.$$annotate(a,b,f);for(var h=0,k=a.length;h<k;h++){var l=a[h];if("string"!==typeof l)throw Ha("itkn",l);g.push(c&&c.hasOwnProperty(l)?c[l]:d(l,f))}return g}return{invoke:function(a,b,c,d){"string"===typeof c&&(d=c,c=null);c=e(a,c,d);L(a)&&(a=a[a.length-1]);d=11>=Ea?!1:"function"===typeof a&&/^(?:class\b|constructor\()/.test(Function.prototype.toString.call(a)+" ");return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=
|
||||
L(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:cb.$$annotate,has:function(b){return n.hasOwnProperty(b+"Provider")||a.hasOwnProperty(b)}}}b=!0===b;var k={},l=[],m=new Ra([],!0),n={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,ha(b),!1)}),constant:d(function(a,b){Qa(a,"constant");n[a]=b;u[a]=b}),decorator:function(a,b){var c=
|
||||
p.get(a+"Provider"),d=c.$get;c.$get=function(){var a=B.invoke(d,c);return B.invoke(b,null,{$delegate:a})}}}},p=n.$injector=h(n,function(a,b){ca.isString(b)&&l.push(b);throw Ha("unpr",l.join(" <- "));}),u={},R=h(u,function(a,b){var c=p.get(a+"Provider",b);return B.invoke(c.$get,c,void 0,a)}),B=R;n.$injectorProvider={$get:ha(R)};var r=g(a),B=R.get("$injector");B.strictDi=b;q(r,function(a){a&&B.invoke(a)});return B}function Xe(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window",
|
||||
"$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===wa(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView();var c;c=g.yOffset;z(c)?c=c():Qb(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):T(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=G(a)?a:d.hash();var b;a?(b=h.getElementById(a))?f(b):(b=e(h.getElementsByName(a)))?f(b):"top"===
|
||||
a&&f(null):f(null)}var h=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a===b&&""===a||Qf(function(){c.$evalAsync(g)})});return g}]}function gb(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;L(a)&&(a=a.join(" "));L(b)&&(b=b.join(" "));return a+" "+b}function Zf(a){G(a)&&(a=a.split(" "));var b=U();q(a,function(a){a.length&&(b[a]=!0)});return b}function Ia(a){return D(a)?a:{}}function $f(a,b,d,c){function e(a){try{a.apply(null,va.call(arguments,1))}finally{if(R--,0===R)for(;B.length;)try{B.pop()()}catch(b){d.error(b)}}}
|
||||
function f(){t=null;g();h()}function g(){r=K();r=y(r)?null:r;na(r,E)&&(r=E);E=r}function h(){if(v!==k.url()||J!==r)v=k.url(),J=r,q(M,function(a){a(k.url(),r)})}var k=this,l=a.location,m=a.history,n=a.setTimeout,p=a.clearTimeout,u={};k.isMock=!1;var R=0,B=[];k.$$completeOutstandingRequest=e;k.$$incOutstandingRequestCount=function(){R++};k.notifyWhenNoOutstandingRequests=function(a){0===R?a():B.push(a)};var r,J,v=l.href,fa=b.find("base"),t=null,K=c.history?function(){try{return m.state}catch(a){}}:
|
||||
A;g();J=r;k.url=function(b,d,e){y(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=J===e;if(v===b&&(!c.history||f))return k;var h=v&&Ja(v)===Ja(b);v=b;J=e;!c.history||h&&f?(h||(t=b),d?l.replace(b):h?(d=l,e=b.indexOf("#"),e=-1===e?"":b.substr(e),d.hash=e):l.href=b,l.href!==b&&(t=b)):(m[d?"replaceState":"pushState"](e,"",b),g(),J=r);t&&(t=b);return k}return t||l.href.replace(/%27/g,"'")};k.state=function(){return r};var M=[],H=!1,E=null;k.onUrlChange=function(b){if(!H){if(c.history)F(a).on("popstate",
|
||||
f);F(a).on("hashchange",f);H=!0}M.push(b);return b};k.$$applicationDestroyed=function(){F(a).off("hashchange popstate",f)};k.$$checkUrlChange=h;k.baseHref=function(){var a=fa.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};k.defer=function(a,b){var c;R++;c=n(function(){delete u[c];e(a)},b||0);u[c]=!0;return c};k.defer.cancel=function(a){return u[a]?(delete u[a],p(a),e(A),!0):!1}}function df(){this.$get=["$window","$log","$sniffer","$document",function(a,b,d,c){return new $f(a,c,b,
|
||||
d)}]}function ef(){this.$get=function(){function a(a,c){function e(a){a!=n&&(p?p==a&&(p=a.n):p=a,f(a.n,a.p),f(a,n),n=a,n.n=null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw N("$cacheFactory")("iid",a);var g=0,h=S({},c,{id:a}),k=U(),l=c&&c.capacity||Number.MAX_VALUE,m=U(),n=null,p=null;return b[a]={put:function(a,b){if(!y(b)){if(l<Number.MAX_VALUE){var c=m[a]||(m[a]={key:a});e(c)}a in k||g++;k[a]=b;g>l&&this.remove(p.key);return b}},get:function(a){if(l<Number.MAX_VALUE){var b=m[a];
|
||||
if(!b)return;e(b)}return k[a]},remove:function(a){if(l<Number.MAX_VALUE){var b=m[a];if(!b)return;b==n&&(n=b.p);b==p&&(p=b.n);f(b.n,b.p);delete m[a]}a in k&&(delete k[a],g--)},removeAll:function(){k=U();g=0;m=U();n=p=null},destroy:function(){m=h=k=null;delete b[a]},info:function(){return S({},h,{size:g})}}}var b={};a.info=function(){var a={};q(b,function(b,e){a[e]=b.info()});return a};a.get=function(a){return b[a]};return a}}function Bf(){this.$get=["$cacheFactory",function(a){return a("templates")}]}
|
||||
function Fc(a,b){function d(a,b,c){var d=/^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/,e=U();q(a,function(a,f){if(a in n)e[f]=n[a];else{var g=a.match(d);if(!g)throw ga("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f};g[4]&&(n[a]=e[f])}});return e}function c(a){var b=a.charAt(0);if(!b||b!==Q(b))throw ga("baddir",a);if(a!==a.trim())throw ga("baddir",a);}function e(a){var b=a.require||a.controller&&a.name;
|
||||
!L(b)&&D(b)&&q(b,function(a,c){var d=a.match(l);a.substring(d[0].length)||(b[c]=d[0]+c)});return b}var f={},g=/^\s*directive\:\s*([\w\-]+)\s+(.*)$/,h=/(([\w\-]+)(?:\:([^;]+))?;?)/,k=be("ngSrc,ngSrcset,src,srcset"),l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,m=/^(on[a-z]+|formaction)$/,n=U();this.directive=function B(b,d){Qa(b,"directive");G(b)?(c(b),sb(d,"directiveFactory"),f.hasOwnProperty(b)||(f[b]=[],a.factory(b+"Directive",["$injector","$exceptionHandler",function(a,c){var d=[];q(f[b],function(f,g){try{var h=
|
||||
a.invoke(f);z(h)?h={compile:ha(h)}:!h.compile&&h.link&&(h.compile=ha(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||b;h.require=e(h);h.restrict=h.restrict||"EA";h.$$moduleName=f.$$moduleName;d.push(h)}catch(k){c(k)}});return d}])),f[b].push(d)):q(b,uc(B));return this};this.component=function(a,b){function c(a){function e(b){return z(b)||L(b)?function(c,d){return a.invoke(b,this,{$element:c,$attrs:d})}:b}var f=b.template||b.templateUrl?b.template:"",g={controller:d,controllerAs:Xc(b.controller)||
|
||||
b.controllerAs||"$ctrl",template:e(f),templateUrl:e(b.templateUrl),transclude:b.transclude,scope:{},bindToController:b.bindings||{},restrict:"E",require:b.require};q(b,function(a,b){"$"===b.charAt(0)&&(g[b]=a)});return g}var d=b.controller||function(){};q(b,function(a,b){"$"===b.charAt(0)&&(c[b]=a,z(d)&&(d[b]=a))});c.$inject=["$injector"];return this.directive(a,c)};this.aHrefSanitizationWhitelist=function(a){return w(a)?(b.aHrefSanitizationWhitelist(a),this):b.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=
|
||||
function(a){return w(a)?(b.imgSrcSanitizationWhitelist(a),this):b.imgSrcSanitizationWhitelist()};var p=!0;this.debugInfoEnabled=function(a){return w(a)?(p=a,this):p};var u=10;this.onChangesTtl=function(a){return arguments.length?(u=a,this):u};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$sce","$animate","$$sanitizeUri",function(a,b,c,e,n,t,K,M,H,E){function I(){try{if(!--qa)throw Y=void 0,ga("infchng",u);K.$apply(function(){for(var a=
|
||||
[],b=0,c=Y.length;b<c;++b)try{Y[b]()}catch(d){a.push(d)}Y=void 0;if(a.length)throw a;})}finally{qa++}}function Da(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a}function P(a,b,c){pa.innerHTML="<span "+b+">";b=pa.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function x(a,b){try{a.addClass(b)}catch(c){}}function aa(a,b,c,d,e){a instanceof F||(a=F(a));for(var f=/\S+/,g=0,h=a.length;g<
|
||||
h;g++){var k=a[g];k.nodeType===Ma&&k.nodeValue.match(f)&&Pc(k,a[g]=C.document.createElement("span"))}var l=s(a,b,a,c,d,e);aa.$$addScopeClass(a);var m=null;return function(b,c,d){sb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var f=d.parentBoundTranscludeFn,g=d.transcludeControllers;d=d.futureParentElement;f&&f.$$boundTransclude&&(f=f.$$boundTransclude);m||(m=(d=d&&d[0])?"foreignobject"!==wa(d)&&ma.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==m?F(da(m,F("<div>").append(a).html())):
|
||||
c?Oa.clone.call(a):a;if(g)for(var h in g)d.data("$"+h+"Controller",g[h].instance);aa.$$addScopeInfo(d,b);c&&c(d,b);l&&l(b,d,d,f);return d}}function s(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,p,r,v;if(n)for(v=Array(c.length),m=0;m<h.length;m+=3)f=h[m],v[f]=c[f];else v=c;m=0;for(p=h.length;m<p;)k=v[h[m++]],c=h[m++],f=h[m++],c?(c.scope?(l=a.$new(),aa.$$addScopeInfo(F(k),l)):l=a,r=c.transcludeOnThisElement?za(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?za(a,b):null,c(f,l,k,d,r)):f&&f(a,
|
||||
k.childNodes,void 0,e)}for(var h=[],k,l,m,p,n,r=0;r<a.length;r++){k=new Da;l=$b(a[r],[],k,0===r?d:void 0,e);(f=l.length?oa(l,a[r],k,b,c,null,[],[],f):null)&&f.scope&&aa.$$addScopeClass(k.$$element);k=f&&f.terminal||!(m=a[r].childNodes)||!m.length?null:s(m,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)h.push(r,f,k),p=!0,n=n||f;f=null}return p?g:null}function za(a,b,c){function d(e,f,g,h,k){e||(e=a.$new(!1,k),e.$$transcluded=!0);return b(e,f,{parentBoundTranscludeFn:c,
|
||||
transcludeControllers:g,futureParentElement:h})}var e=d.$$slots=U(),f;for(f in b.$$slots)e[f]=b.$$slots[f]?za(a,b.$$slots[f],c):null;return d}function $b(a,b,c,d,e){var f=c.$attr;switch(a.nodeType){case 1:O(b,Aa(wa(a)),"E",d,e);for(var g,k,l,m,p=a.attributes,n=0,r=p&&p.length;n<r;n++){var v=!1,u=!1;g=p[n];k=g.name;l=W(g.value);g=Aa(k);if(m=Ba.test(g))k=k.replace(Yc,"").substr(8).replace(/_(.)/g,function(a,b){return b.toUpperCase()});(g=g.match(Ca))&&V(g[1])&&(v=k,u=k.substr(0,k.length-5)+"end",k=
|
||||
k.substr(0,k.length-6));g=Aa(k.toLowerCase());f[g]=k;if(m||!c.hasOwnProperty(g))c[g]=l,Uc(a,g)&&(c[g]=!0);ia(a,b,l,g,m);O(b,g,"A",d,e,v,u)}f=a.className;D(f)&&(f=f.animVal);if(G(f)&&""!==f)for(;a=h.exec(f);)g=Aa(a[2]),O(b,g,"C",d,e)&&(c[g]=W(a[3])),f=f.substr(a.index+a[0].length);break;case Ma:if(11===Ea)for(;a.parentNode&&a.nextSibling&&a.nextSibling.nodeType===Ma;)a.nodeValue+=a.nextSibling.nodeValue,a.parentNode.removeChild(a.nextSibling);ca(b,a.nodeValue);break;case 8:hb(a,b,c,d,e)}b.sort(Z);
|
||||
return b}function hb(a,b,c,d,e){try{var f=g.exec(a.nodeValue);if(f){var h=Aa(f[1]);O(b,h,"M",d,e)&&(c[h]=W(f[2]))}}catch(k){}}function N(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ga("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return F(d)}function Zc(a,b,c){return function(d,e,f,g,h){e=N(e[0],b,c);return a(d,e,f,g,h)}}function ac(a,b,c,d,e,f){var g;return a?aa(b,c,d,e,f):function(){g||
|
||||
(g=aa(b,c,d,e,f),b=c=f=null);return g.apply(this,arguments)}}function oa(a,b,d,e,f,g,h,k,l){function m(a,b,c,d){if(a){c&&(a=Zc(a,c,d));a.require=x.require;a.directiveName=I;if(u===x||x.$$isolateScope)a=ja(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=Zc(b,c,d));b.require=x.require;b.directiveName=I;if(u===x||x.$$isolateScope)b=ja(b,{isolateScope:!0});k.push(b)}}function p(a,e,f,g,l){function m(a,b,c,d){var e;Ya(a)||(d=c,c=b,b=a,a=void 0);fa&&(e=t);c||(c=fa?I.parent():I);if(d){var f=l.$$slots[d];if(f)return f(a,
|
||||
b,e,c,s);if(y(f))throw ga("noslot",d,ya(I));}else return l(a,b,e,c,s)}var n,E,x,M,B,t,P,I;b===f?(g=d,I=d.$$element):(I=F(f),g=new Da(I,d));B=e;u?M=e.$new(!0):r&&(B=e.$parent);l&&(P=m,P.$$boundTransclude=l,P.isSlotFilled=function(a){return!!l.$$slots[a]});v&&(t=ag(I,g,P,v,M,e,u));u&&(aa.$$addScopeInfo(I,M,!0,!(H&&(H===u||H===u.$$originalDirective))),aa.$$addScopeClass(I,!0),M.$$isolateBindings=u.$$isolateBindings,E=ka(e,g,M,M.$$isolateBindings,u),E.removeWatches&&M.$on("$destroy",E.removeWatches));
|
||||
for(n in t){E=v[n];x=t[n];var Zb=E.$$bindings.bindToController;x.bindingInfo=x.identifier&&Zb?ka(B,g,x.instance,Zb,E):{};var K=x();K!==x.instance&&(x.instance=K,I.data("$"+E.name+"Controller",K),x.bindingInfo.removeWatches&&x.bindingInfo.removeWatches(),x.bindingInfo=ka(B,g,x.instance,Zb,E))}q(v,function(a,b){var c=a.require;a.bindToController&&!L(c)&&D(c)&&S(t[b].instance,ib(b,c,I,t))});q(t,function(a){var b=a.instance;if(z(b.$onChanges))try{b.$onChanges(a.bindingInfo.initialChanges)}catch(d){c(d)}if(z(b.$onInit))try{b.$onInit()}catch(e){c(e)}z(b.$doCheck)&&
|
||||
(B.$watch(function(){b.$doCheck()}),b.$doCheck());z(b.$onDestroy)&&B.$on("$destroy",function(){b.$onDestroy()})});n=0;for(E=h.length;n<E;n++)x=h[n],la(x,x.isolateScope?M:e,I,g,x.require&&ib(x.directiveName,x.require,I,t),P);var s=e;u&&(u.template||null===u.templateUrl)&&(s=M);a&&a(s,f.childNodes,void 0,l);for(n=k.length-1;0<=n;n--)x=k[n],la(x,x.isolateScope?M:e,I,g,x.require&&ib(x.directiveName,x.require,I,t),P);q(t,function(a){a=a.instance;z(a.$postLink)&&a.$postLink()})}l=l||{};for(var n=-Number.MAX_VALUE,
|
||||
r=l.newScopeDirective,v=l.controllerDirectives,u=l.newIsolateScopeDirective,H=l.templateDirective,E=l.nonTlbTranscludeDirective,M=!1,B=!1,fa=l.hasElementTranscludeDirective,t=d.$$element=F(b),x,I,P,K=e,s,Fa=!1,za=!1,w,A=0,C=a.length;A<C;A++){x=a[A];var G=x.$$start,hb=x.$$end;G&&(t=N(b,G,hb));P=void 0;if(n>x.priority)break;if(w=x.scope)x.templateUrl||(D(w)?(X("new/isolated scope",u||r,x,t),u=x):X("new/isolated scope",u,x,t)),r=r||x;I=x.name;if(!Fa&&(x.replace&&(x.templateUrl||x.template)||x.transclude&&
|
||||
!x.$$tlb)){for(w=A+1;Fa=a[w++];)if(Fa.transclude&&!Fa.$$tlb||Fa.replace&&(Fa.templateUrl||Fa.template)){za=!0;break}Fa=!0}!x.templateUrl&&x.controller&&(w=x.controller,v=v||U(),X("'"+I+"' controller",v[I],x,t),v[I]=x);if(w=x.transclude)if(M=!0,x.$$tlb||(X("transclusion",E,x,t),E=x),"element"==w)fa=!0,n=x.priority,P=t,t=d.$$element=F(aa.$$createComment(I,d[I])),b=t[0],ea(f,va.call(P,0),b),P[0].$$parentNode=P[0].parentNode,K=ac(za,P,e,n,g&&g.name,{nonTlbTranscludeDirective:E});else{var oa=U();P=F(Xb(b)).contents();
|
||||
if(D(w)){P=[];var Q=U(),O=U();q(w,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Q[a]=b;oa[b]=null;O[b]=c});q(t.contents(),function(a){var b=Q[Aa(wa(a))];b?(O[b]=!0,oa[b]=oa[b]||[],oa[b].push(a)):P.push(a)});q(O,function(a,b){if(!a)throw ga("reqslot",b);});for(var V in oa)oa[V]&&(oa[V]=ac(za,oa[V],e))}t.empty();K=ac(za,P,e,void 0,void 0,{needsNewScope:x.$$isolateScope||x.$$newScope});K.$$slots=oa}if(x.template)if(B=!0,X("template",H,x,t),H=x,w=z(x.template)?x.template(t,d):x.template,
|
||||
w=xa(w),x.replace){g=x;P=Vb.test(w)?$c(da(x.templateNamespace,W(w))):[];b=P[0];if(1!=P.length||1!==b.nodeType)throw ga("tplrt",I,"");ea(f,t,b);C={$attr:{}};w=$b(b,[],C);var Z=a.splice(A+1,a.length-(A+1));(u||r)&&T(w,u,r);a=a.concat(w).concat(Z);$(d,C);C=a.length}else t.html(w);if(x.templateUrl)B=!0,X("template",H,x,t),H=x,x.replace&&(g=x),p=ba(a.splice(A,a.length-A),t,d,f,M&&K,h,k,{controllerDirectives:v,newScopeDirective:r!==x&&r,newIsolateScopeDirective:u,templateDirective:H,nonTlbTranscludeDirective:E}),
|
||||
C=a.length;else if(x.compile)try{s=x.compile(t,d,K);var Y=x.$$originalDirective||x;z(s)?m(null,ab(Y,s),G,hb):s&&m(ab(Y,s.pre),ab(Y,s.post),G,hb)}catch(ca){c(ca,ya(t))}x.terminal&&(p.terminal=!0,n=Math.max(n,x.priority))}p.scope=r&&!0===r.scope;p.transcludeOnThisElement=M;p.templateOnThisElement=B;p.transclude=K;l.hasElementTranscludeDirective=fa;return p}function ib(a,b,c,d){var e;if(G(b)){var f=b.match(l);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&
|
||||
e.instance;if(!e){var h="$"+b+"Controller";e=g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw ga("ctreq",b,a);}else if(L(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=ib(a,b[g],c,d);else D(b)&&(e={},q(b,function(b,f){e[f]=ib(a,b,c,d)}));return e||null}function ag(a,b,c,d,e,f,g){var h=U(),k;for(k in d){var l=d[k],m={$scope:l===g||l.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},p=l.controller;"@"==p&&(p=b[l.name]);m=t(p,m,!0,l.controllerAs);h[l.name]=m;a.data("$"+l.name+"Controller",m.instance)}return h}
|
||||
function T(a,b,c){for(var d=0,e=a.length;d<e;d++)a[d]=Rb(a[d],{$$isolateScope:b,$$newScope:c})}function O(b,e,g,h,k,l,m){if(e===k)return null;k=null;if(f.hasOwnProperty(e)){var p;e=a.get(e+"Directive");for(var n=0,r=e.length;n<r;n++)try{if(p=e[n],(y(h)||h>p.priority)&&-1!=p.restrict.indexOf(g)){l&&(p=Rb(p,{$$start:l,$$end:m}));if(!p.$$bindings){var u=p,v=p,x=p.name,H={isolateScope:null,bindToController:null};D(v.scope)&&(!0===v.bindToController?(H.bindToController=d(v.scope,x,!0),H.isolateScope={}):
|
||||
H.isolateScope=d(v.scope,x,!1));D(v.bindToController)&&(H.bindToController=d(v.bindToController,x,!0));if(D(H.bindToController)){var E=v.controller,M=v.controllerAs;if(!E)throw ga("noctrl",x);if(!Xc(E,M))throw ga("noident",x);}var t=u.$$bindings=H;D(t.isolateScope)&&(p.$$isolateBindings=t.isolateScope)}b.push(p);k=p}}catch(I){c(I)}}return k}function V(b){if(f.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,e=c.length;d<e;d++)if(b=c[d],b.multiElement)return!0;return!1}function $(a,b){var c=b.$attr,
|
||||
d=a.$attr;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,e){a.hasOwnProperty(e)||"$"===e.charAt(0)||(a[e]=b,"class"!==e&&"style"!==e&&(d[e]=c[e]))})}function ba(a,b,c,d,f,g,h,k){var l=[],m,p,n=b[0],r=a.shift(),u=Rb(r,{templateUrl:null,transclude:null,replace:null,$$originalDirective:r}),H=z(r.templateUrl)?r.templateUrl(b,c):r.templateUrl,E=r.templateNamespace;b.empty();e(H).then(function(e){var v,M;e=xa(e);if(r.replace){e=
|
||||
Vb.test(e)?$c(da(E,W(e))):[];v=e[0];if(1!=e.length||1!==v.nodeType)throw ga("tplrt",r.name,H);e={$attr:{}};ea(d,b,v);var B=$b(v,[],e);D(r.scope)&&T(B,!0);a=B.concat(a);$(c,e)}else v=n,b.html(e);a.unshift(u);m=oa(a,v,c,f,b,r,g,h,k);q(d,function(a,c){a==v&&(d[c]=b[0])});for(p=s(b[0].childNodes,f);l.length;){e=l.shift();M=l.shift();var t=l.shift(),I=l.shift(),B=b[0];if(!e.$$destroyed){if(M!==n){var P=M.className;k.hasElementTranscludeDirective&&r.replace||(B=Xb(v));ea(t,F(M),B);x(F(B),P)}M=m.transcludeOnThisElement?
|
||||
za(e,m.transclude,I):I;m(p,e,B,d,M)}}l=null});return function(a,b,c,d,e){a=e;b.$$destroyed||(l?l.push(b,c,d,a):(m.transcludeOnThisElement&&(a=za(b,m.transclude,e)),m(p,b,c,d,a)))}}function Z(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function X(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw ga("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),a,ya(d));}function ca(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=
|
||||
a.parent();var b=!!a.length;b&&aa.$$addBindingClass(a);return function(a,c){var e=c.parent();b||aa.$$addBindingClass(e);aa.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function da(a,b){a=Q(a||"html");switch(a){case "svg":case "math":var c=C.document.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function ha(a,b){if("srcdoc"==b)return M.HTML;var c=wa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=
|
||||
c&&("src"==b||"ngSrc"==b))return M.RESOURCE_URL}function ia(a,c,d,e,f){var g=ha(a,e);f=k[e]||f;var h=b(d,!0,g,f);if(h){if("multiple"===e&&"select"===wa(a))throw ga("selmulti",ya(a));c.push({priority:100,compile:function(){return{pre:function(a,c,k){c=k.$$observers||(k.$$observers=U());if(m.test(e))throw ga("nodomevents");var l=k[e];l!==d&&(h=l&&b(l,!0,g,f),d=l);h&&(k[e]=h(a),(c[e]||(c[e]=[])).$$inter=!0,(k.$$observers&&k.$$observers[e].$$scope||a).$watch(h,function(a,b){"class"===e&&a!=b?k.$updateClass(a,
|
||||
b):k.$set(e,a)}))}}}})}}function ea(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]==d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=C.document.createDocumentFragment();for(g=0;g<e;g++)a.appendChild(b[g]);F.hasData(d)&&(F.data(c,F.data(d)),F(d).off("$destroy"));F.cleanData(a.querySelectorAll("*"));for(g=1;g<e;g++)delete b[g];b[0]=c;b.length=1}function ja(a,
|
||||
b){return S(function(){return a.apply(null,arguments)},a,b)}function la(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,ya(d))}}function ka(a,c,d,e,f){function g(b,c,e){z(d.$onChanges)&&c!==e&&(Y||(a.$$postDigest(I),Y=[]),m||(m={},Y.push(h)),m[b]&&(e=m[b].previousValue),m[b]=new Fb(e,c))}function h(){d.$onChanges(m);m=void 0}var k=[],l={},m;q(e,function(e,h){var m=e.attrName,p=e.optional,v,u,x,H;switch(e.mode){case "@":p||ua.call(c,m)||(d[h]=c[m]=void 0);c.$observe(m,function(a){if(G(a)||Ga(a))g(h,a,d[h]),
|
||||
d[h]=a});c.$$observers[m].$$scope=a;v=c[m];G(v)?d[h]=b(v)(a):Ga(v)&&(d[h]=v);l[h]=new Fb(bc,d[h]);break;case "=":if(!ua.call(c,m)){if(p)break;c[m]=void 0}if(p&&!c[m])break;u=n(c[m]);H=u.literal?na:function(a,b){return a===b||a!==a&&b!==b};x=u.assign||function(){v=d[h]=u(a);throw ga("nonassign",c[m],m,f.name);};v=d[h]=u(a);p=function(b){H(b,d[h])||(H(b,v)?x(a,b=d[h]):d[h]=b);return v=b};p.$stateful=!0;p=e.collection?a.$watchCollection(c[m],p):a.$watch(n(c[m],p),null,u.literal);k.push(p);break;case "<":if(!ua.call(c,
|
||||
m)){if(p)break;c[m]=void 0}if(p&&!c[m])break;u=n(c[m]);var E=d[h]=u(a);l[h]=new Fb(bc,d[h]);p=a.$watch(u,function(a,b){if(b===a){if(b===E)return;b=E}g(h,a,b);d[h]=a},u.literal);k.push(p);break;case "&":u=c.hasOwnProperty(m)?n(c[m]):A;if(u===A&&p)break;d[h]=function(b){return u(a,b)}}});return{initialChanges:l,removeWatches:k.length&&function(){for(var a=0,b=k.length;a<b;++a)k[a]()}}}var ta=/^\w/,pa=C.document.createElement("div"),qa=u,Y;Da.prototype={$normalize:Aa,$addClass:function(a){a&&0<a.length&&
|
||||
H.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&H.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=ad(a,b);c&&c.length&&H.addClass(this.$$element,c);(c=ad(b,a))&&c.length&&H.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=Uc(this.$$element[0],a),g=bd[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]=b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=Cc(a,"-"));f=wa(this.$$element);if("a"===f&&("href"===a||"xlinkHref"===a)||"img"===
|
||||
f&&"src"===a)this[a]=b=E(b,"src"===a);else if("img"===f&&"srcset"===a&&w(b)){for(var f="",g=W(b),k=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,k=/\s/.test(g)?k:/(,)/,g=g.split(k),k=Math.floor(g.length/2),l=0;l<k;l++)var m=2*l,f=f+E(W(g[m]),!0),f=f+(" "+W(g[m+1]));g=W(g[2*l]).split(/\s/);f+=E(W(g[0]),!0);2===g.length&&(f+=" "+W(g[1]));this[a]=b=f}!1!==d&&(null===b||y(b)?this.$$element.removeAttr(e):ta.test(e)?this.$$element.attr(e,b):P(this.$$element[0],e,b));(a=this.$$observers)&&q(a[h],function(a){try{a(b)}catch(d){c(d)}})},
|
||||
$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=U()),e=d[a]||(d[a]=[]);e.push(b);K.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||y(c[a])||b(c[a])});return function(){Za(e,b)}}};var ra=b.startSymbol(),sa=b.endSymbol(),xa="{{"==ra&&"}}"==sa?Xa:function(a){return a.replace(/\{\{/g,ra).replace(/}}/g,sa)},Ba=/^ngAttr[A-Z]/,Ca=/^(.+)Start$/;aa.$$addBindingInfo=p?function(a,b){var c=a.data("$binding")||[];L(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:A;aa.$$addBindingClass=
|
||||
p?function(a){x(a,"ng-binding")}:A;aa.$$addScopeInfo=p?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:A;aa.$$addScopeClass=p?function(a,b){x(a,b?"ng-isolate-scope":"ng-scope")}:A;aa.$$createComment=function(a,b){var c="";p&&(c=" "+(a||"")+": ",b&&(c+=b+" "));return C.document.createComment(c)};return aa}]}function Fb(a,b){this.previousValue=a;this.currentValue=b}function Aa(a){return db(a.replace(Yc,""))}function ad(a,b){var d="",c=a.split(/\s+/),e=b.split(/\s+/),
|
||||
f=0;a:for(;f<c.length;f++){for(var g=c[f],h=0;h<e.length;h++)if(g==e[h])continue a;d+=(0<d.length?" ":"")+g}return d}function $c(a){a=F(a);var b=a.length;if(1>=b)return a;for(;b--;)8===a[b].nodeType&&bg.call(a,b,1);return a}function Xc(a,b){if(b&&G(b))return b;if(G(a)){var d=cd.exec(a);if(d)return d[3]}}function ff(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,c){Qa(b,"controller");D(b)?S(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector",
|
||||
"$window",function(d,c){function e(a,b,c,d){if(!a||!D(a.$scope))throw N("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,n;h=!0===h;k&&G(k)&&(n=k);if(G(f)){k=f.match(cd);if(!k)throw cg("ctrlfmt",f);m=k[1];n=n||k[3];f=a.hasOwnProperty(m)?a[m]:Ec(g.$scope,m,!0)||(b?Ec(c,m,!0):void 0);Pa(f,m,!0)}if(h)return h=(L(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),n&&e(g,n,l,m||f.name),S(function(){var a=d.invoke(f,l,g,m);a!==l&&(D(a)||z(a))&&(l=a,n&&e(g,n,l,m||f.name));return l},
|
||||
{instance:l,identifier:n});l=d.instantiate(f,g,m);n&&e(g,n,l,m||f.name);return l}}]}function gf(){this.$get=["$window",function(a){return F(a.document)}]}function hf(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function cc(a){return D(a)?da(a)?a.toISOString():bb(a):a}function nf(){this.$get=function(){return function(a){if(!a)return"";var b=[];tc(a,function(a,c){null===a||y(a)||(L(a)?q(a,function(a){b.push(ea(c)+"="+ea(cc(a)))}):b.push(ea(c)+"="+ea(cc(a))))});
|
||||
return b.join("&")}}}function of(){this.$get=function(){return function(a){function b(a,e,f){null===a||y(a)||(L(a)?q(a,function(a,c){b(a,e+"["+(D(a)?c:"")+"]")}):D(a)&&!da(a)?tc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ea(e)+"="+ea(cc(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function dc(a,b){if(G(a)){var d=a.replace(dg,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf(dd))||(c=(c=d.match(eg))&&fg[c[0]].test(d));c&&(a=xc(d))}}return a}function ed(a){var b=
|
||||
U(),d;G(a)?q(a.split("\n"),function(a){d=a.indexOf(":");var e=Q(W(a.substr(0,d)));a=W(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):D(a)&&q(a,function(a,d){var f=Q(d),g=W(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function fd(a){var b;return function(d){b||(b=ed(a));return d?(d=b[Q(d)],void 0===d&&(d=null),d):b}}function gd(a,b,d,c){if(z(c))return c(a,b,d);q(c,function(c){a=c(a,b,d)});return a}function mf(){var a=this.defaults={transformResponse:[dc],transformRequest:[function(a){return D(a)&&"[object File]"!==
|
||||
ma.call(a)&&"[object Blob]"!==ma.call(a)&&"[object FormData]"!==ma.call(a)?bb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ia(ec),put:ia(ec),patch:ia(ec)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return w(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions=function(a){return w(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory",
|
||||
"$rootScope","$q","$injector",function(e,f,g,h,k,l){function m(b){function c(a,b){for(var d=0,e=b.length;d<e;){var f=b[d++],g=b[d++];a=a.then(f,g)}b.length=0;return a}function e(a,b){var c,d={};q(a,function(a,e){z(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}function f(a){var b=S({},a);b.data=gd(a.data,a.headers,a.status,g.transformResponse);a=a.status;return 200<=a&&300>a?b:k.reject(b)}if(!D(b))throw N("$http")("badreq",b);if(!G(b.url))throw N("$http")("badreq",b.url);var g=S({method:"get",transformRequest:a.transformRequest,
|
||||
transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);g.headers=function(b){var c=a.headers,d=S({},b.headers),f,g,h,c=S({},c.common,c[Q(b.method)]);a:for(f in c){g=Q(f);for(h in d)if(Q(h)===g)continue a;d[f]=c[f]}return e(d,ia(b))}(b);g.method=ub(g.method);g.paramSerializer=G(g.paramSerializer)?l.get(g.paramSerializer):g.paramSerializer;var h=[],m=[],p=k.when(g);q(R,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&m.push(a.response,
|
||||
a.responseError)});p=c(p,h);p=p.then(function(b){var c=b.headers,d=gd(b.data,fd(c),void 0,b.transformRequest);y(d)&&q(c,function(a,b){"content-type"===Q(b)&&delete c[b]});y(b.withCredentials)&&!y(a.withCredentials)&&(b.withCredentials=a.withCredentials);return n(b,d).then(f,f)});p=c(p,m);d?(p.success=function(a){Pa(a,"fn");p.then(function(b){a(b.data,b.status,b.headers,g)});return p},p.error=function(a){Pa(a,"fn");p.then(null,function(b){a(b.data,b.status,b.headers,g)});return p}):(p.success=hd("success"),
|
||||
p.error=hd("error"));return p}function n(c,d){function g(a){if(a){var c={};q(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d():h.$apply(d)}});return c}}function l(a,c,d,e){function f(){n(c,a,d,e)}E&&(200<=a&&300>a?E.put(P,[a,c,ed(d),e]):E.remove(P));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function n(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?M.resolve:M.reject)({data:a,status:b,headers:fd(d),config:c,statusText:e})}function t(a){n(a.data,a.status,ia(a.headers()),
|
||||
a.statusText)}function R(){var a=m.pendingRequests.indexOf(c);-1!==a&&m.pendingRequests.splice(a,1)}var M=k.defer(),H=M.promise,E,I,Da=c.headers,P=p(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);H.then(R,R);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(E=D(c.cache)?c.cache:D(a.cache)?a.cache:u);E&&(I=E.get(P),w(I)?I&&z(I.then)?I.then(t,t):L(I)?n(I[1],I[0],ia(I[2]),I[3]):n(I,200,{},"OK"):E.put(P,H));y(I)&&((I=id(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:
|
||||
void 0)&&(Da[c.xsrfHeaderName||a.xsrfHeaderName]=I),e(c.method,P,d,l,Da,c.timeout,c.withCredentials,c.responseType,g(c.eventHandlers),g(c.uploadEventHandlers)));return H}function p(a,b){0<b.length&&(a+=(-1==a.indexOf("?")?"?":"&")+b);return a}var u=g("$http");a.paramSerializer=G(a.paramSerializer)?l.get(a.paramSerializer):a.paramSerializer;var R=[];q(c,function(a){R.unshift(G(a)?l.get(a):l.invoke(a))});m.pendingRequests=[];(function(a){q(arguments,function(a){m[a]=function(b,c){return m(S({},c||{},
|
||||
{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){m[a]=function(b,c,d){return m(S({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");m.defaults=a;return m}]}function qf(){this.$get=function(){return function(){return new C.XMLHttpRequest}}}function pf(){this.$get=["$browser","$jsonpCallbacks","$document","$xhrFactory",function(a,b,d,c){return gg(a,c,a.defer,b,d[0])}]}function gg(a,b,d,c,e){function f(a,b,d){a=a.replace("JSON_CALLBACK",b);var f=
|
||||
e.createElement("script"),m=null;f.type="text/javascript";f.src=a;f.async=!0;m=function(a){f.removeEventListener("load",m,!1);f.removeEventListener("error",m,!1);e.body.removeChild(f);f=null;var g=-1,u="unknown";a&&("load"!==a.type||c.wasCalled(b)||(a={type:"error"}),u=a.type,g="error"===a.type?404:200);d&&d(g,u)};f.addEventListener("load",m,!1);f.addEventListener("error",m,!1);e.body.appendChild(f);return m}return function(e,h,k,l,m,n,p,u,R,B){function r(){fa&&fa();t&&t.abort()}function J(b,c,e,
|
||||
f,g){w(M)&&d.cancel(M);fa=t=null;b(c,e,f,g);a.$$completeOutstandingRequest(A)}a.$$incOutstandingRequestCount();h=h||a.url();if("jsonp"===Q(e))var v=c.createCallback(h),fa=f(h,v,function(a,b){var d=200===a&&c.getResponse(v);J(l,a,d,"",b);c.removeCallback(v)});else{var t=b(e,h);t.open(e,h,!0);q(m,function(a,b){w(a)&&t.setRequestHeader(b,a)});t.onload=function(){var a=t.statusText||"",b="response"in t?t.response:t.responseText,c=1223===t.status?204:t.status;0===c&&(c=b?200:"file"==Y(h).protocol?404:
|
||||
0);J(l,c,b,t.getAllResponseHeaders(),a)};e=function(){J(l,-1,null,null,"")};t.onerror=e;t.onabort=e;q(R,function(a,b){t.addEventListener(b,a)});q(B,function(a,b){t.upload.addEventListener(b,a)});p&&(t.withCredentials=!0);if(u)try{t.responseType=u}catch(K){if("json"!==u)throw K;}t.send(y(k)?null:k)}if(0<n)var M=d(r,n);else n&&z(n.then)&&n.then(r)}}function kf(){var a="{{",b="}}";this.startSymbol=function(b){return b?(a=b,this):a};this.endSymbol=function(a){return a?(b=a,this):b};this.$get=["$parse",
|
||||
"$exceptionHandler","$sce",function(d,c,e){function f(a){return"\\\\\\"+a}function g(c){return c.replace(n,a).replace(p,b)}function h(a,b,c,d){var e;return e=a.$watch(function(a){e();return d(a)},b,c)}function k(f,k,p,n){function J(a){try{var b=a;a=p?e.getTrusted(p,b):e.valueOf(b);var d;if(n&&!w(a))d=a;else if(null==a)d="";else{switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=bb(a)}d=a}return d}catch(g){c(Ka.interr(f,g))}}if(!f.length||-1===f.indexOf(a)){var v;k||(k=g(f),
|
||||
v=ha(k),v.exp=f,v.expressions=[],v.$$watchDelegate=h);return v}n=!!n;var q,t,K=0,M=[],H=[];v=f.length;for(var E=[],I=[];K<v;)if(-1!=(q=f.indexOf(a,K))&&-1!=(t=f.indexOf(b,q+l)))K!==q&&E.push(g(f.substring(K,q))),K=f.substring(q+l,t),M.push(K),H.push(d(K,J)),K=t+m,I.push(E.length),E.push("");else{K!==v&&E.push(g(f.substring(K)));break}p&&1<E.length&&Ka.throwNoconcat(f);if(!k||M.length){var Da=function(a){for(var b=0,c=M.length;b<c;b++){if(n&&y(a[b]))return;E[I[b]]=a[b]}return E.join("")};return S(function(a){var b=
|
||||
0,d=M.length,e=Array(d);try{for(;b<d;b++)e[b]=H[b](a);return Da(e)}catch(g){c(Ka.interr(f,g))}},{exp:f,expressions:M,$$watchDelegate:function(a,b){var c;return a.$watchGroup(H,function(d,e){var f=Da(d);z(b)&&b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=a.length,m=b.length,n=new RegExp(a.replace(/./g,f),"g"),p=new RegExp(b.replace(/./g,f),"g");k.startSymbol=function(){return a};k.endSymbol=function(){return b};return k}]}function lf(){this.$get=["$rootScope","$window","$q","$$q","$browser",function(a,
|
||||
b,d,c,e){function f(f,k,l,m){function n(){p?f.apply(null,u):f(r)}var p=4<arguments.length,u=p?va.call(arguments,4):[],R=b.setInterval,q=b.clearInterval,r=0,J=w(m)&&!m,v=(J?c:d).defer(),fa=v.promise;l=w(l)?l:0;fa.$$intervalId=R(function(){J?e.defer(n):a.$evalAsync(n);v.notify(r++);0<l&&r>=l&&(v.resolve(r),q(fa.$$intervalId),delete g[fa.$$intervalId]);J||a.$apply()},k);g[fa.$$intervalId]=v;return fa}var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),
|
||||
delete g[a.$$intervalId],!0):!1};return f}]}function fc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=qb(a[b]);return a.join("/")}function jd(a,b){var d=Y(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Z(d.port)||hg[d.protocol]||null}function kd(a,b){var d="/"!==a.charAt(0);d&&(a="/"+a);var c=Y(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=Ac(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path=
|
||||
"/"+b.$$path)}function ka(a,b){if(0===b.lastIndexOf(a,0))return b.substr(a.length)}function Ja(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function jb(a){return a.replace(/(#.+)|#$/,"$1")}function gc(a,b,d){this.$$html5=!0;d=d||"";jd(a,this);this.$$parse=function(a){var d=ka(b,a);if(!G(d))throw Gb("ipthprfx",a,b);kd(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Tb(this.$$search),d=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(a?"?"+
|
||||
a:"")+d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;w(f=ka(a,c))?(g=f,g=w(f=ka(d,f))?b+(ka("/",f)||f):a+g):w(f=ka(b,c))?g=b+f:b==c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function hc(a,b,d){jd(a,this);this.$$parse=function(c){var e=ka(a,c)||ka(b,c),f;y(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",y(e)&&(a=c,this.replace())):(f=ka(d,e),y(f)&&(f=e));kd(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.lastIndexOf(e,
|
||||
0)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Tb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Ja(a)==Ja(b)?(this.$$parse(b),!0):!1}}function ld(a,b,d){this.$$html5=!0;hc.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Ja(c)?
|
||||
f=c:(g=ka(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Tb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url}}function Hb(a){return function(){return this[a]}}function md(a,b){return function(d){if(y(d))return this[a];this[a]=b(d);this.$$compose();return this}}function sf(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return w(b)?(a=b,this):
|
||||
a};this.html5Mode=function(a){return Ga(a)?(b.enabled=a,this):D(a)?(Ga(a.enabled)&&(b.enabled=a.enabled),Ga(a.requireBase)&&(b.requireBase=a.requireBase),Ga(a.rewriteLinks)&&(b.rewriteLinks=a.rewriteLinks),this):b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,
|
||||
b)}var l,m;m=c.baseHref();var n=c.url(),p;if(b.enabled){if(!m&&b.requireBase)throw Gb("nobase");p=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=e.history?gc:ld}else p=Ja(n),m=hc;var u=p.substr(0,Ja(p).lastIndexOf("/")+1);l=new m(p,u,"#"+a);l.$$parseLinkUrl(n,n);l.$$state=c.state();var R=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=F(a.target);"a"!==wa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;
|
||||
var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");D(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=Y(h.animVal).href);R.test(h)||!h||e.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(h,k)||(a.preventDefault(),l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});jb(l.absUrl())!=jb(n)&&c.url(l.absUrl(),!0);var q=!0;c.onUrlChange(function(a,b){y(ka(u,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=jb(a);l.$$parse(a);l.$$state=
|
||||
b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(q=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a=jb(c.url()),b=jb(l.absUrl()),f=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(q||m)q=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null:l.$$state),
|
||||
k(a,f)))});l.$$replace=!1});return l}]}function tf(){var a=!0,b=this;this.debugEnabled=function(b){return w(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||A;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];q(arguments,function(b){a.push(c(b))});
|
||||
return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Sa(a,b){if("__defineGetter__"===a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"===a||"__proto__"===a)throw X("isecfld",b);return a}function ig(a){return a+""}function ra(a,b){if(a){if(a.constructor===a)throw X("isecfn",b);if(a.window===a)throw X("isecwindow",b);if(a.children&&
|
||||
(a.nodeName||a.prop&&a.attr&&a.find))throw X("isecdom",b);if(a===Object)throw X("isecobj",b);}return a}function nd(a,b){if(a){if(a.constructor===a)throw X("isecfn",b);if(a===jg||a===kg||a===lg)throw X("isecff",b);}}function Ib(a,b){if(a&&(a===(0).constructor||a===(!1).constructor||a==="".constructor||a==={}.constructor||a===[].constructor||a===Function.constructor))throw X("isecaf",b);}function mg(a,b){return"undefined"!==typeof a?a:b}function od(a,b){return"undefined"===typeof a?b:"undefined"===
|
||||
typeof b?a:a+b}function V(a,b){var d,c;switch(a.type){case s.Program:d=!0;q(a.body,function(a){V(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case s.Literal:a.constant=!0;a.toWatch=[];break;case s.UnaryExpression:V(a.argument,b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case s.BinaryExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:V(a.left,b);V(a.right,
|
||||
b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:V(a.test,b);V(a.alternate,b);V(a.consequent,b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant=!1;a.toWatch=[a];break;case s.MemberExpression:V(a.object,b);a.computed&&V(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d=a.filter?!b(a.callee.name).$stateful:
|
||||
!1;c=[];q(a.arguments,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c:[a];break;case s.AssignmentExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];q(a.elements,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d=!0;c=[];q(a.properties,function(a){V(a.value,
|
||||
b);d=d&&a.value.constant&&!a.computed;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant=!1;a.toWatch=[];break;case s.LocalsExpression:a.constant=!1,a.toWatch=[]}}function pd(a){if(1==a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function qd(a){return a.type===s.Identifier||a.type===s.MemberExpression}function rd(a){if(1===a.body.length&&qd(a.body[0].expression))return{type:s.AssignmentExpression,
|
||||
left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function sd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type===s.ObjectExpression)}function td(a,b){this.astBuilder=a;this.$filter=b}function ud(a,b){this.astBuilder=a;this.$filter=b}function Jb(a){return"constructor"==a}function ic(a){return z(a.valueOf)?a.valueOf():ng.call(a)}function uf(){var a=U(),b=U(),d={"true":!0,
|
||||
"false":!1,"null":null,undefined:void 0},c,e;this.addLiteral=function(a,b){d[a]=b};this.setIdentifierFns=function(a,b){c=a;e=b;return this};this.$get=["$filter",function(f){function g(c,d,e){var g,k,H;e=e||J;switch(typeof c){case "string":H=c=c.trim();var E=e?b:a;g=E[H];if(!g){":"===c.charAt(0)&&":"===c.charAt(1)&&(k=!0,c=c.substring(2));g=e?r:B;var q=new jc(g);g=(new kc(q,f,g)).parse(c);g.constant?g.$$watchDelegate=p:k?g.$$watchDelegate=g.literal?n:m:g.inputs&&(g.$$watchDelegate=l);e&&(g=h(g));E[H]=
|
||||
g}return u(g,d);case "function":return u(c,d);default:return u(A,d)}}function h(a){function b(c,d,e,f){var g=J;J=!0;try{return a(c,d,e,f)}finally{J=g}}if(!a)return a;b.$$watchDelegate=a.$$watchDelegate;b.assign=h(a.assign);b.constant=a.constant;b.literal=a.literal;for(var c=0;a.inputs&&c<a.inputs.length;++c)a.inputs[c]=h(a.inputs[c]);b.inputs=a.inputs;return b}function k(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=ic(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function l(a,b,c,d,
|
||||
e){var f=d.inputs,g;if(1===f.length){var h=k,f=f[0];return a.$watch(function(a){var b=f(a);k(b,h)||(g=d(a,void 0,void 0,[b]),h=b&&ic(b));return g},b,c,e)}for(var l=[],m=[],p=0,n=f.length;p<n;p++)l[p]=k,m[p]=null;return a.$watch(function(a){for(var b=!1,c=0,e=f.length;c<e;c++){var h=f[c](a);if(b||(b=!k(h,l[c])))m[c]=h,l[c]=h&&ic(h)}b&&(g=d(a,void 0,void 0,m));return g},b,c,e)}function m(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;z(b)&&b.apply(this,arguments);w(a)&&
|
||||
d.$$postDigest(function(){w(f)&&e()})},c)}function n(a,b,c,d){function e(a){var b=!0;q(a,function(a){w(a)||(b=!1)});return b}var f,g;return f=a.$watch(function(a){return d(a)},function(a,c,d){g=a;z(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(g)&&f()})},c)}function p(a,b,c,d){var e;return e=a.$watch(function(a){e();return d(a)},b,c)}function u(a,b){if(!b)return a;var c=a.$$watchDelegate,d=!1,c=c!==n&&c!==m?function(c,e,f,g){f=d&&g?g[0]:a(c,e,f,g);return b(f,c,e)}:function(c,d,e,f){e=a(c,
|
||||
d,e,f);c=b(e,c,d);return w(e)?c:e};a.$$watchDelegate&&a.$$watchDelegate!==l?c.$$watchDelegate=a.$$watchDelegate:b.$stateful||(c.$$watchDelegate=l,d=!a.inputs,c.inputs=a.inputs?a.inputs:[a]);return c}var R=Ba().noUnsafeEval,B={csp:R,expensiveChecks:!1,literals:pa(d),isIdentifierStart:z(c)&&c,isIdentifierContinue:z(e)&&e},r={csp:R,expensiveChecks:!0,literals:pa(d),isIdentifierStart:z(c)&&c,isIdentifierContinue:z(e)&&e},J=!1;g.$$runningExpensiveChecks=function(){return J};return g}]}function wf(){this.$get=
|
||||
["$rootScope","$exceptionHandler",function(a,b){return vd(function(b){a.$evalAsync(b)},b)}]}function xf(){this.$get=["$browser","$exceptionHandler",function(a,b){return vd(function(b){a.defer(b)},b)}]}function vd(a,b){function d(){this.$$state={status:0}}function c(a,b){return function(c){b.call(a,c)}}function e(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,a(function(){var a,d,e;e=c.pending;c.processScheduled=!1;c.pending=void 0;for(var f=0,g=e.length;f<g;++f){d=e[f][0];a=e[f][c.status];
|
||||
try{z(a)?d.resolve(a(c.value)):1===c.status?d.resolve(c.value):d.reject(c.value)}catch(h){d.reject(h),b(h)}}}))}function f(){this.promise=new d}var g=N("$q",TypeError),h=function(){var a=new f;a.resolve=c(a,a.resolve);a.reject=c(a,a.reject);a.notify=c(a,a.notify);return a};S(d.prototype,{then:function(a,b,c){if(y(a)&&y(b)&&y(c))return this;var d=new f;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&e(this.$$state);return d.promise},"catch":function(a){return this.then(null,
|
||||
a)},"finally":function(a,b){return this.then(function(b){return l(b,!0,a)},function(b){return l(b,!1,a)},b)}});S(f.prototype,{resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(g("qcycle",a)):this.$$resolve(a))},$$resolve:function(a){function d(a){k||(k=!0,h.$$resolve(a))}function f(a){k||(k=!0,h.$$reject(a))}var g,h=this,k=!1;try{if(D(a)||z(a))g=a&&a.then;z(g)?(this.promise.$$state.status=-1,g.call(a,d,f,c(this,this.notify))):(this.promise.$$state.value=a,this.promise.$$state.status=
|
||||
1,e(this.promise.$$state))}catch(l){f(l),b(l)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)},$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;e(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&a(function(){for(var a,e,f=0,g=d.length;f<g;f++){e=d[f][0];a=d[f][3];try{e.notify(z(a)?a(c):c)}catch(h){b(h)}}})}});var k=function(a,b){var c=new f;b?c.resolve(a):c.reject(a);return c.promise},
|
||||
l=function(a,b,c){var d=null;try{z(c)&&(d=c())}catch(e){return k(e,!1)}return d&&z(d.then)?d.then(function(){return k(a,b)},function(a){return k(a,!1)}):k(a,b)},m=function(a,b,c,d){var e=new f;e.resolve(a);return e.promise.then(b,c,d)},n=function(a){if(!z(a))throw g("norslvr",a);var b=new f;a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};n.prototype=d.prototype;n.defer=h;n.reject=function(a){var b=new f;b.reject(a);return b.promise};n.when=m;n.resolve=m;n.all=function(a){var b=
|
||||
new f,c=0,d=L(a)?[]:{};q(a,function(a,e){c++;m(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};n.race=function(a){var b=h();q(a,function(a){m(a).then(b.resolve,b.reject)});return b.promise};return n}function Gf(){this.$get=["$window","$timeout",function(a,b){var d=a.requestAnimationFrame||a.webkitRequestAnimationFrame,c=a.cancelAnimationFrame||a.webkitCancelAnimationFrame||a.webkitCancelRequestAnimationFrame,
|
||||
e=!!d,f=e?function(a){var b=d(a);return function(){c(b)}}:function(a){var c=b(a,16.66,!1);return function(){b.cancel(c)}};f.supported=e;return f}]}function vf(){function a(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++pb;this.$$ChildScope=null}b.prototype=a;return b}var b=10,d=N("$rootScope"),c=null,e=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=
|
||||
["$exceptionHandler","$parse","$browser",function(f,g,h){function k(a){a.currentScope.$$destroyed=!0}function l(a){9===Ea&&(a.$$childHead&&l(a.$$childHead),a.$$nextSibling&&l(a.$$nextSibling));a.$parent=a.$$nextSibling=a.$$prevSibling=a.$$childHead=a.$$childTail=a.$root=a.$$watchers=null}function m(){this.$id=++pb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount=
|
||||
{};this.$$watchersCount=0;this.$$isolateBindings=null}function n(a){if(J.$$phase)throw d("inprog",J.$$phase);J.$$phase=a}function p(a,b){do a.$$watchersCount+=b;while(a=a.$parent)}function u(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function s(){}function B(){for(;t.length;)try{t.shift()()}catch(a){f(a)}e=null}function r(){null===e&&(e=h.defer(function(){J.$apply(B)}))}m.prototype={constructor:m,$new:function(b,c){var d;c=c||this;b?
|
||||
(d=new m,d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=a(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(b||c!=this)&&d.$on("$destroy",k);return d},$watch:function(a,b,d,e){var f=g(a);if(f.$$watchDelegate)return f.$$watchDelegate(this,b,d,f,a);var h=this,k=h.$$watchers,l={fn:b,last:s,get:f,exp:e||a,eq:!!d};c=null;z(b)||(l.fn=A);k||(k=h.$$watchers=[]);k.unshift(l);p(this,
|
||||
1);return function(){0<=Za(k,l)&&p(h,-1);c=null}},$watchGroup:function(a,b){function c(){h=!1;k?(k=!1,b(e,e,g)):b(e,d,g)}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});q(a,function(a,b){var k=g.$watch(a,function(a,f){e[b]=a;d[b]=f;h||(h=!0,g.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},
|
||||
$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!y(e)){if(D(e))if(ta(e))for(f!==n&&(f=n,u=f.length=0,l++),a=e.length,u!==a&&(l++,f.length=u=a),b=0;b<a;b++)h=f[b],g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==p&&(f=p={},u=0,l++);a=0;for(b in e)ua.call(e,b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(u++,f[b]=g,l++));if(u>a)for(b in l++,f)ua.call(e,b)||(u--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1<b.length,l=0,m=
|
||||
g(a,c),n=[],p={},r=!0,u=0;return this.$watch(m,function(){r?(r=!1,b(e,e,d)):b(e,h,d);if(k)if(D(e))if(ta(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h={},e)ua.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var a,g,k,l,m,p,u,r,q=b,t,y=[],A,C;n("$digest");h.$$checkUrlChange();this===J&&null!==e&&(h.defer.cancel(e),B());c=null;do{r=!1;t=this;for(p=0;p<v.length;p++){try{C=v[p],C.scope.$eval(C.expression,C.locals)}catch(F){f(F)}c=null}v.length=0;a:do{if(p=t.$$watchers)for(u=
|
||||
p.length;u--;)try{if(a=p[u])if(m=a.get,(g=m(t))!==(k=a.last)&&!(a.eq?na(g,k):"number"===typeof g&&"number"===typeof k&&isNaN(g)&&isNaN(k)))r=!0,c=a,a.last=a.eq?pa(g,null):g,l=a.fn,l(g,k===s?g:k,t),5>q&&(A=4-q,y[A]||(y[A]=[]),y[A].push({msg:z(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:k}));else if(a===c){r=!1;break a}}catch(G){f(G)}if(!(p=t.$$watchersCount&&t.$$childHead||t!==this&&t.$$nextSibling))for(;t!==this&&!(p=t.$$nextSibling);)t=t.$parent}while(t=p);if((r||v.length)&&
|
||||
!q--)throw J.$$phase=null,d("infdig",b,y);}while(r||v.length);for(J.$$phase=null;K<w.length;)try{w[K++]()}catch(D){f(D)}w.length=K=0},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===J&&h.$$applicationDestroyed();p(this,-this.$$watchersCount);for(var b in this.$$listenerCount)u(this,this.$$listenerCount[b],b);a&&a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&
|
||||
(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=A;this.$on=this.$watch=this.$watchGroup=function(){return A};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){J.$$phase||v.length||h.defer(function(){v.length&&J.$digest()});v.push({scope:this,expression:g(a),locals:b})},$$postDigest:function(a){w.push(a)},
|
||||
$apply:function(a){try{n("$apply");try{return this.$eval(a)}finally{J.$$phase=null}}catch(b){f(b)}finally{try{J.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&t.push(b);a=g(a);r()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,u(e,1,a))}},$emit:function(a,
|
||||
b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=$a([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;l<m;l++)if(d[l])try{d[l].apply(null,k)}catch(n){f(n)}else d.splice(l,1),l--,m--;if(g)return h.currentScope=null,h;e=e.$parent}while(e);h.currentScope=null;return h},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=
|
||||
!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var g=$a([e],arguments,1),h,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,g)}catch(l){f(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var J=new m,v=J.$$asyncQueue=[],w=J.$$postDigestQueue=[],t=J.$$applyAsyncQueue=[],K=0;return J}]}function ne(){var a=
|
||||
/^\s*(https?|ftp|mailto|tel|file):/,b=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(b){return w(b)?(a=b,this):a};this.imgSrcSanitizationWhitelist=function(a){return w(a)?(b=a,this):b};this.$get=function(){return function(d,c){var e=c?b:a,f;f=Y(d).href;return""===f||f.match(e)?d:"unsafe:"+f}}}function og(a){if("self"===a)return a;if(G(a)){if(-1<a.indexOf("***"))throw sa("iwcard",a);a=wd(a).replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+
|
||||
a+"$")}if(Wa(a))return new RegExp("^"+a.source+"$");throw sa("imatcher");}function xd(a){var b=[];w(a)&&q(a,function(a){b.push(og(a))});return b}function zf(){this.SCE_CONTEXTS=la;var a=["self"],b=[];this.resourceUrlWhitelist=function(b){arguments.length&&(a=xd(b));return a};this.resourceUrlBlacklist=function(a){arguments.length&&(b=xd(a));return b};this.$get=["$injector",function(d){function c(a,b){return"self"===a?id(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=
|
||||
function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var f=function(a){throw sa("unsafe");};d.has("$sanitize")&&(f=d.get("$sanitize"));var g=e(),h={};h[la.HTML]=e(g);h[la.CSS]=e(g);h[la.URL]=e(g);h[la.JS]=e(g);h[la.RESOURCE_URL]=e(h[la.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw sa("icontext",a,b);if(null===b||y(b)||
|
||||
""===b)return b;if("string"!==typeof b)throw sa("itype",a);return new c(b)},getTrusted:function(d,e){if(null===e||y(e)||""===e)return e;var g=h.hasOwnProperty(d)?h[d]:null;if(g&&e instanceof g)return e.$$unwrapTrustedValue();if(d===la.RESOURCE_URL){var g=Y(e.toString()),n,p,u=!1;n=0;for(p=a.length;n<p;n++)if(c(a[n],g)){u=!0;break}if(u)for(n=0,p=b.length;n<p;n++)if(c(b[n],g)){u=!1;break}if(u)return e;throw sa("insecurl",e.toString());}if(d===la.HTML)return f(e);throw sa("unsafe");},valueOf:function(a){return a instanceof
|
||||
g?a.$$unwrapTrustedValue():a}}}]}function yf(){var a=!0;this.enabled=function(b){arguments.length&&(a=!!b);return a};this.$get=["$parse","$sceDelegate",function(b,d){if(a&&8>Ea)throw sa("iequirks");var c=ia(la);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=Xa);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,
|
||||
f=c.getTrusted,g=c.trustAs;q(la,function(a,b){var d=Q(b);c[db("parse_as_"+d)]=function(b){return e(a,b)};c[db("get_trusted_"+d)]=function(b){return f(a,b)};c[db("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function Af(){this.$get=["$window","$document",function(a,b){var d={},c=!(a.chrome&&a.chrome.app&&a.chrome.app.runtime)&&a.history&&a.history.pushState,e=Z((/android (\d+)/.exec(Q((a.navigator||{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h,k=/^(Moz|webkit|ms)(?=[A-Z])/,
|
||||
l=g.body&&g.body.style,m=!1,n=!1;if(l){for(var p in l)if(m=k.exec(p)){h=m[0];h=h[0].toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in l&&"webkit");m=!!("transition"in l||h+"Transition"in l);n=!!("animation"in l||h+"Animation"in l);!e||m&&n||(m=G(l.webkitTransition),n=G(l.webkitAnimation))}return{history:!(!c||4>e||f),hasEvent:function(a){if("input"===a&&11>=Ea)return!1;if(y(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ba(),vendorPrefix:h,transitions:m,animations:n,android:e}}]}
|
||||
function Cf(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$templateCache","$http","$q","$sce",function(b,d,c,e){function f(g,h){f.totalPendingRequests++;if(!G(g)||y(b.get(g)))g=e.getTrustedResourceUrl(g);var k=d.defaults&&d.defaults.transformResponse;L(k)?k=k.filter(function(a){return a!==dc}):k===dc&&(k=null);return d.get(g,S({cache:b,transformResponse:k},a))["finally"](function(){f.totalPendingRequests--}).then(function(a){b.put(g,a.data);return a.data},function(a){if(!h)throw pg("tpload",
|
||||
g,a.status,a.statusText);return c.reject(a)})}f.totalPendingRequests=0;return f}]}function Df(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];q(a,function(a){var c=ca.element(a).data("$binding");c&&q(c,function(c){d?(new RegExp("(^|\\s)"+wd(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!=c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;h<g.length;++h){var k=
|
||||
a.querySelectorAll("["+g[h]+"model"+(d?"=":"*=")+'"'+b+'"]');if(k.length)return k}},getLocation:function(){return d.url()},setLocation:function(b){b!==d.url()&&(d.url(b),a.$digest())},whenStable:function(a){b.notifyWhenNoOutstandingRequests(a)}}}]}function Ef(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(a,b,d,c,e){function f(f,k,l){z(f)||(l=k,k=f,f=A);var m=va.call(arguments,3),n=w(l)&&!l,p=(n?c:d).defer(),u=p.promise,q;q=b.defer(function(){try{p.resolve(f.apply(null,
|
||||
m))}catch(b){p.reject(b),e(b)}finally{delete g[u.$$timeoutId]}n||a.$apply()},k);u.$$timeoutId=q;g[q]=p;return u}var g={};f.cancel=function(a){return a&&a.$$timeoutId in g?(g[a.$$timeoutId].reject("canceled"),delete g[a.$$timeoutId],b.defer.cancel(a.$$timeoutId)):!1};return f}]}function Y(a){Ea&&($.setAttribute("href",a),a=$.href);$.setAttribute("href",a);return{href:$.href,protocol:$.protocol?$.protocol.replace(/:$/,""):"",host:$.host,search:$.search?$.search.replace(/^\?/,""):"",hash:$.hash?$.hash.replace(/^#/,
|
||||
""):"",hostname:$.hostname,port:$.port,pathname:"/"===$.pathname.charAt(0)?$.pathname:"/"+$.pathname}}function id(a){a=G(a)?Y(a):a;return a.protocol===yd.protocol&&a.host===yd.host}function Ff(){this.$get=ha(C)}function zd(a){function b(a){try{return decodeURIComponent(a)}catch(b){return a}}var d=a[0]||{},c={},e="";return function(){var a,g,h,k,l;a=d.cookie||"";if(a!==e)for(e=a,a=e.split("; "),c={},h=0;h<a.length;h++)g=a[h],k=g.indexOf("="),0<k&&(l=b(g.substring(0,k)),y(c[l])&&(c[l]=b(g.substring(k+
|
||||
1))));return c}}function Jf(){this.$get=zd}function Mc(a){function b(d,c){if(D(d)){var e={};q(d,function(a,c){e[c]=b(c,a)});return e}return a.factory(d+"Filter",c)}this.register=b;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];b("currency",Ad);b("date",Bd);b("filter",qg);b("json",rg);b("limitTo",sg);b("lowercase",tg);b("number",Cd);b("orderBy",Dd);b("uppercase",ug)}function qg(){return function(a,b,d,c){if(!ta(a)){if(null==a)return a;throw N("filter")("notarray",
|
||||
a);}c=c||"$";var e;switch(lc(b)){case "function":break;case "boolean":case "null":case "number":case "string":e=!0;case "object":b=vg(b,d,c,e);break;default:return a}return Array.prototype.filter.call(a,b)}}function vg(a,b,d,c){var e=D(a)&&d in a;!0===b?b=na:z(b)||(b=function(a,b){if(y(a))return!1;if(null===a||null===b)return a===b;if(D(b)||D(a)&&!vc(a))return!1;a=Q(""+a);b=Q(""+b);return-1!==a.indexOf(b)});return function(f){return e&&!D(f)?La(f,a[d],b,d,!1):La(f,a,b,d,c)}}function La(a,b,d,c,e,
|
||||
f){var g=lc(a),h=lc(b);if("string"===h&&"!"===b.charAt(0))return!La(a,b.substring(1),d,c,e);if(L(a))return a.some(function(a){return La(a,b,d,c,e)});switch(g){case "object":var k;if(e){for(k in a)if("$"!==k.charAt(0)&&La(a[k],b,d,c,!0))return!0;return f?!1:La(a,b,d,c,!1)}if("object"===h){for(k in b)if(f=b[k],!z(f)&&!y(f)&&(g=k===c,!La(g?a:a[k],f,d,c,g,g)))return!1;return!0}return d(a,b);case "function":return!1;default:return d(a,b)}}function lc(a){return null===a?"null":typeof a}function Ad(a){var b=
|
||||
a.NUMBER_FORMATS;return function(a,c,e){y(c)&&(c=b.CURRENCY_SYM);y(e)&&(e=b.PATTERNS[1].maxFrac);return null==a?a:Ed(a,b.PATTERNS[1],b.GROUP_SEP,b.DECIMAL_SEP,e).replace(/\u00A4/g,c)}}function Cd(a){var b=a.NUMBER_FORMATS;return function(a,c){return null==a?a:Ed(a,b.PATTERNS[0],b.GROUP_SEP,b.DECIMAL_SEP,c)}}function wg(a){var b=0,d,c,e,f,g;-1<(c=a.indexOf(Fd))&&(a=a.replace(Fd,""));0<(e=a.search(/e/i))?(0>c&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)==mc;e++);
|
||||
if(e==(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)==mc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Gd&&(d=d.splice(0,Gd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function xg(a,b,d,c){var e=a.d,f=e.length-a.i;b=y(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0<d){e.splice(Math.max(a.i,d));for(var g=d;g<e.length;g++)e[g]=0}else for(f=Math.max(0,f),a.i=1,e.length=Math.max(1,d=b+1),e[0]=0,g=1;g<d;g++)e[g]=0;if(5<=c)if(0>d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;
|
||||
for(;f<Math.max(0,b);f++)e.push(0);if(b=e.reduceRight(function(a,b,c,d){b+=a;d[c]=b%10;return Math.floor(b/10)},0))e.unshift(b),a.i++}function Ed(a,b,d,c,e){if(!G(a)&&!T(a)||isNaN(a))return"";var f=!isFinite(a),g=!1,h=Math.abs(a)+"",k="";if(f)k="\u221e";else{g=wg(h);xg(g,e,b.minFrac,b.maxFrac);k=g.d;h=g.i;e=g.e;f=[];for(g=k.reduce(function(a,b){return a&&!b},!0);0>h;)k.unshift(0),h++;0<h?f=k.splice(h,k.length):(f=k,k=[0]);h=[];for(k.length>=b.lgSize&&h.unshift(k.splice(-b.lgSize,k.length).join(""));k.length>
|
||||
b.gSize;)h.unshift(k.splice(-b.gSize,k.length).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0>a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Kb(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length<b;)a=mc+a;d&&(a=a.substr(a.length-b));return e+a}function ba(a,b,d,c,e){d=d||0;return function(f){f=f["get"+a]();if(0<d||f>-d)f+=d;0===f&&-12==d&&(f=12);return Kb(f,b,c,e)}}function kb(a,b,d){return function(c,e){var f=
|
||||
c["get"+a](),g=ub((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Hd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Id(a){return function(b){var d=Hd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Kb(b,a)}}function nc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Bd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,
|
||||
k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Z(b[9]+b[10]),g=Z(b[9]+b[11]));h.call(a,Z(b[1]),Z(b[2])-1,Z(b[3]));f=Z(b[4]||0)-f;g=Z(b[5]||0)-g;h=Z(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",h=[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;G(c)&&(c=yg.test(c)?Z(c):b(c));T(c)&&(c=new Date(c));if(!da(c)||!isFinite(c.getTime()))return c;
|
||||
for(;d;)(l=zg.exec(d))?(h=$a(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=yc(f,m),c=Sb(c,f,!0));q(h,function(b){k=Ag[b];g+=k?k(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function rg(){return function(a,b){y(b)&&(b=2);return bb(a,b)}}function sg(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):Z(b);if(isNaN(b))return a;T(a)&&(a=a.toString());if(!ta(a))return a;d=!d||isNaN(d)?0:Z(d);d=0>d?Math.max(0,a.length+
|
||||
d):d;return 0<=b?oc(a,d,d+b):0===d?oc(a,b,a.length):oc(a,Math.max(0,d+b),d)}}function oc(a,b,d){return G(a)?a.slice(b,d):va.call(a,b,d)}function Dd(a){function b(b){return b.map(function(b){var c=1,d=Xa;if(z(b))d=b;else if(G(b)){if("+"==b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var e=d(),d=function(a){return a[e]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}
|
||||
function c(a,b){var c=0,d=a.type,k=b.type;if(d===k){var k=a.value,l=b.value;"string"===d?(k=k.toLowerCase(),l=l.toLowerCase()):"object"===d&&(D(k)&&(k=a.index),D(l)&&(l=b.index));k!==l&&(c=k<l?-1:1)}else c=d<k?-1:1;return c}return function(a,f,g,h){if(null==a)return a;if(!ta(a))throw N("orderBy")("notarray",a);L(f)||(f=[f]);0===f.length&&(f=["+"]);var k=b(f),l=g?-1:1,m=z(h)?h:c;a=Array.prototype.map.call(a,function(a,b){return{value:a,tieBreaker:{value:b,type:"number",index:b},predicateValues:k.map(function(c){var e=
|
||||
c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("object"===c)a:{if(z(e.valueOf)&&(e=e.valueOf(),d(e)))break a;vc(e)&&(e=e.toString(),d(e))}return{value:e,type:c,index:b}})}});a.sort(function(a,b){for(var c=0,d=k.length;c<d;c++){var e=m(a.predicateValues[c],b.predicateValues[c]);if(e)return e*k[c].descending*l}return m(a.tieBreaker,b.tieBreaker)*l});return a=a.map(function(a){return a.value})}}function Ta(a){z(a)&&(a={link:a});a.restrict=a.restrict||"AC";return ha(a)}function Jd(a,b,d,
|
||||
c,e){var f=this,g=[];f.$error={};f.$$success={};f.$pending=void 0;f.$name=e(b.name||b.ngForm||"")(d);f.$dirty=!1;f.$pristine=!0;f.$valid=!0;f.$invalid=!1;f.$submitted=!1;f.$$parentForm=Lb;f.$rollbackViewValue=function(){q(g,function(a){a.$rollbackViewValue()})};f.$commitViewValue=function(){q(g,function(a){a.$commitViewValue()})};f.$addControl=function(a){Qa(a.$name,"input");g.push(a);a.$name&&(f[a.$name]=a);a.$$parentForm=f};f.$$renameControl=function(a,b){var c=a.$name;f[c]===a&&delete f[c];f[b]=
|
||||
a;a.$name=b};f.$removeControl=function(a){a.$name&&f[a.$name]===a&&delete f[a.$name];q(f.$pending,function(b,c){f.$setValidity(c,null,a)});q(f.$error,function(b,c){f.$setValidity(c,null,a)});q(f.$$success,function(b,c){f.$setValidity(c,null,a)});Za(g,a);a.$$parentForm=Lb};Kd({ctrl:this,$element:a,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(Za(d,c),0===d.length&&delete a[b])},$animate:c});f.$setDirty=function(){c.removeClass(a,Ua);c.addClass(a,
|
||||
Mb);f.$dirty=!0;f.$pristine=!1;f.$$parentForm.$setDirty()};f.$setPristine=function(){c.setClass(a,Ua,Mb+" ng-submitted");f.$dirty=!1;f.$pristine=!0;f.$submitted=!1;q(g,function(a){a.$setPristine()})};f.$setUntouched=function(){q(g,function(a){a.$setUntouched()})};f.$setSubmitted=function(){c.addClass(a,"ng-submitted");f.$submitted=!0;f.$$parentForm.$setSubmitted()}}function pc(a){a.$formatters.push(function(b){return a.$isEmpty(b)?b:b.toString()})}function lb(a,b,d,c,e,f){var g=Q(b[0].type);if(!e.android){var h=
|
||||
!1;b.on("compositionstart",function(){h=!0});b.on("compositionend",function(){h=!1;l()})}var k,l=function(a){k&&(f.defer.cancel(k),k=null);if(!h){var e=b.val();a=a&&a.type;"password"===g||d.ngTrim&&"false"===d.ngTrim||(e=W(e));(c.$viewValue!==e||""===e&&c.$$hasNativeValidators)&&c.$setViewValue(e,a)}};if(e.hasEvent("input"))b.on("input",l);else{var m=function(a,b,c){k||(k=f.defer(function(){k=null;b&&b.value===c||l(a)}))};b.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||
|
||||
m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",l);if(Ld[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=f.defer(function(){k=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Nb(a,b){return function(d,c){var e,f;if(da(d))return d;if(G(d)){'"'==d.charAt(0)&&'"'==d.charAt(d.length-
|
||||
1)&&(d=d.substring(1,d.length-1));if(Bg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},q(e,function(a,c){c<b.length&&(f[b[c]]=+a)}),new Date(f.yyyy,f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0)}return NaN}}function mb(a,b,d,c){return function(e,f,g,h,k,l,m){function n(a){return a&&!(a.getTime&&
|
||||
a.getTime()!==a.getTime())}function p(a){return w(a)&&!da(a)?d(a)||void 0:a}Md(e,f,g,h);lb(e,f,g,h,k,l);var u=h&&h.$options&&h.$options.timezone,q;h.$$parserName=a;h.$parsers.push(function(a){if(h.$isEmpty(a))return null;if(b.test(a))return a=d(a,q),u&&(a=Sb(a,u)),a});h.$formatters.push(function(a){if(a&&!da(a))throw nb("datefmt",a);if(n(a))return(q=a)&&u&&(q=Sb(q,u,!0)),m("date")(a,c,u);q=null;return""});if(w(g.min)||g.ngMin){var s;h.$validators.min=function(a){return!n(a)||y(s)||d(a)>=s};g.$observe("min",
|
||||
function(a){s=p(a);h.$validate()})}if(w(g.max)||g.ngMax){var r;h.$validators.max=function(a){return!n(a)||y(r)||d(a)<=r};g.$observe("max",function(a){r=p(a);h.$validate()})}}}function Md(a,b,d,c){(c.$$hasNativeValidators=D(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?void 0:a})}function Nd(a,b,d,c,e){if(w(c)){a=a(c);if(!a.constant)throw nb("constexpr",d,c);return a(b)}return e}function qc(a,b){a="ngClass"+a;return["$animate",function(d){function c(a,
|
||||
b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],m=0;m<b.length;m++)if(e==b[m])continue a;c.push(e)}return c}function e(a){var b=[];return L(a)?(q(a,function(a){b=b.concat(e(a))}),b):G(a)?a.split(" "):D(a)?(q(a,function(a,c){a&&(b=b.concat(c.split(" ")))}),b):a}return{restrict:"AC",link:function(f,g,h){function k(a){a=l(a,1);h.$addClass(a)}function l(a,b){var c=g.data("$classCounts")||U(),d=[];q(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});g.data("$classCounts",c);return d.join(" ")}
|
||||
function m(a,b){var e=c(b,a),f=c(a,b),e=l(e,1),f=l(f,-1);e&&e.length&&d.addClass(g,e);f&&f.length&&d.removeClass(g,f)}function n(a){if(!0===b||(f.$index&1)===b){var c=e(a||[]);if(!p)k(c);else if(!na(a,p)){var d=e(p);m(d,c)}}p=L(a)?a.map(function(a){return ia(a)}):ia(a)}var p;f.$watch(h[a],n,!0);h.$observe("class",function(b){n(f.$eval(h[a]))});"ngClass"!==a&&f.$watch("$index",function(c,d){var g=c&1;if(g!==(d&1)){var m=e(f.$eval(h[a]));g===b?k(m):(g=l(m,-1),h.$removeClass(g))}})}}}]}function Kd(a){function b(a,
|
||||
b){b&&!f[a]?(k.addClass(e,a),f[a]=!0):!b&&f[a]&&(k.removeClass(e,a),f[a]=!1)}function d(a,c){a=a?"-"+Cc(a,"-"):"";b(ob+a,!0===c);b(Od+a,!1===c)}var c=a.ctrl,e=a.$element,f={},g=a.set,h=a.unset,k=a.$animate;f[Od]=!(f[ob]=e.hasClass(ob));c.$setValidity=function(a,e,f){y(e)?(c.$pending||(c.$pending={}),g(c.$pending,a,f)):(c.$pending&&h(c.$pending,a,f),Pd(c.$pending)&&(c.$pending=void 0));Ga(e)?e?(h(c.$error,a,f),g(c.$$success,a,f)):(g(c.$error,a,f),h(c.$$success,a,f)):(h(c.$error,a,f),h(c.$$success,
|
||||
a,f));c.$pending?(b(Qd,!0),c.$valid=c.$invalid=void 0,d("",null)):(b(Qd,!1),c.$valid=Pd(c.$error),c.$invalid=!c.$valid,d("",c.$valid));e=c.$pending&&c.$pending[a]?void 0:c.$error[a]?!1:c.$$success[a]?!0:null;d(a,e);c.$$parentForm.$setValidity(a,e,c)}}function Pd(a){if(a)for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}var Cg=/^\/(.+)\/([a-z]*)$/,ua=Object.prototype.hasOwnProperty,Q=function(a){return G(a)?a.toLowerCase():a},ub=function(a){return G(a)?a.toUpperCase():a},Ea,F,qa,va=[].slice,
|
||||
bg=[].splice,Dg=[].push,ma=Object.prototype.toString,wc=Object.getPrototypeOf,xa=N("ng"),ca=C.angular||(C.angular={}),Ub,pb=0;Ea=C.document.documentMode;A.$inject=[];Xa.$inject=[];var L=Array.isArray,ae=/^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/,W=function(a){return G(a)?a.trim():a},wd=function(a){return a.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Ba=function(){if(!w(Ba.rules)){var a=C.document.querySelector("[ng-csp]")||
|
||||
C.document.querySelector("[data-ng-csp]");if(a){var b=a.getAttribute("ng-csp")||a.getAttribute("data-ng-csp");Ba.rules={noUnsafeEval:!b||-1!==b.indexOf("no-unsafe-eval"),noInlineStyle:!b||-1!==b.indexOf("no-inline-style")}}else{a=Ba;try{new Function(""),b=!1}catch(d){b=!0}a.rules={noUnsafeEval:b,noInlineStyle:!1}}}return Ba.rules},rb=function(){if(w(rb.name_))return rb.name_;var a,b,d=Na.length,c,e;for(b=0;b<d;++b)if(c=Na[b],a=C.document.querySelector("["+c.replace(":","\\:")+"jq]")){e=a.getAttribute(c+
|
||||
"jq");break}return rb.name_=e},de=/:/g,Na=["ng-","data-ng-","ng:","x-ng-"],ie=/[A-Z]/g,Dc=!1,Ma=3,me={full:"1.5.8",major:1,minor:5,dot:8,codeName:"arbitrary-fallbacks"};O.expando="ng339";var fb=O.cache={},Pf=1;O._data=function(a){return this.cache[a[this.expando]]||{}};var Kf=/([\:\-\_]+(.))/g,Lf=/^moz([A-Z])/,yb={mouseleave:"mouseout",mouseenter:"mouseover"},Wb=N("jqLite"),Of=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Vb=/<|&#?\w+;/,Mf=/<([\w:-]+)/,Nf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
|
||||
ja={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ja.optgroup=ja.option;ja.tbody=ja.tfoot=ja.colgroup=ja.caption=ja.thead;ja.th=ja.td;var Uf=C.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Oa=O.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"===
|
||||
C.document.readyState?C.setTimeout(b):(this.on("DOMContentLoaded",b),O(C).on("load",b))},toString:function(){var a=[];q(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?F(this[a]):F(this[this.length+a])},length:0,push:Dg,sort:[].sort,splice:[].splice},Eb={};q("multiple selected checked disabled readOnly required open".split(" "),function(a){Eb[Q(a)]=a});var Vc={};q("input select option textarea button form details".split(" "),function(a){Vc[a]=!0});var bd={ngMinlength:"minlength",
|
||||
ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};q({data:Yb,removeData:eb,hasData:function(a){for(var b in fb[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b<d;b++)eb(a[b])}},function(a,b){O[b]=a});q({data:Yb,inheritedData:Cb,scope:function(a){return F.data(a,"$scope")||Cb(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return F.data(a,"$isolateScope")||F.data(a,"$isolateScopeNoTemplate")},controller:Sc,injector:function(a){return Cb(a,
|
||||
"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:zb,css:function(a,b,d){b=db(b);if(w(d))a.style[b]=d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Ma&&2!==c&&8!==c)if(c=Q(b),Eb[c])if(w(d))d?(a[b]=!0,a.setAttribute(b,c)):(a[b]=!1,a.removeAttribute(c));else return a[b]||(a.attributes.getNamedItem(b)||A).specified?c:void 0;else if(w(d))a.setAttribute(b,d);else if(a.getAttribute)return a=a.getAttribute(b,2),null===a?void 0:a},prop:function(a,b,d){if(w(d))a[b]=
|
||||
d;else return a[b]},text:function(){function a(a,d){if(y(d)){var c=a.nodeType;return 1===c||c===Ma?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(y(b)){if(a.multiple&&"select"===wa(a)){var d=[];q(a.options,function(a){a.selected&&d.push(a.value||a.text)});return 0===d.length?null:d}return a.value}a.value=b},html:function(a,b){if(y(b))return a.innerHTML;wb(a,!0);a.innerHTML=b},empty:Tc},function(a,b){O.prototype[b]=function(b,c){var e,f,g=this.length;if(a!==Tc&&y(2==a.length&&
|
||||
a!==zb&&a!==Sc?b:c)){if(D(b)){for(e=0;e<g;e++)if(a===Yb)a(this[e],b);else for(f in b)a(this[e],f,b[f]);return this}e=a.$dv;g=y(e)?Math.min(g,1):g;for(f=0;f<g;f++){var h=a(this[f],b,c);e=e?e+h:h}return e}for(e=0;e<g;e++)a(this[e],b,c);return this}});q({removeData:eb,on:function(a,b,d,c){if(w(c))throw Wb("onargs");if(Nc(a)){c=xb(a,!0);var e=c.events,f=c.handle;f||(f=c.handle=Rf(a,e));c=0<=b.indexOf(" ")?b.split(" "):[b];for(var g=c.length,h=function(b,c,g){var h=e[b];h||(h=e[b]=[],h.specialHandlerWrapper=
|
||||
c,"$destroy"===b||g||a.addEventListener(b,f,!1));h.push(d)};g--;)b=c[g],yb[b]?(h(yb[b],Tf),h(b,void 0,!0)):h(b)}},off:Rc,one:function(a,b,d){a=F(a);a.on(b,function e(){a.off(b,d);a.off(b,e)});a.on(b,d)},replaceWith:function(a,b){var d,c=a.parentNode;wb(a);q(new O(b),function(b){d?c.insertBefore(b,d.nextSibling):c.replaceChild(b,a);d=b})},children:function(a){var b=[];q(a.childNodes,function(a){1===a.nodeType&&b.push(a)});return b},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,
|
||||
b){var d=a.nodeType;if(1===d||11===d){b=new O(b);for(var d=0,c=b.length;d<c;d++)a.appendChild(b[d])}},prepend:function(a,b){if(1===a.nodeType){var d=a.firstChild;q(new O(b),function(b){a.insertBefore(b,d)})}},wrap:function(a,b){Pc(a,F(b).eq(0).clone()[0])},remove:Db,detach:function(a){Db(a,!0)},after:function(a,b){var d=a,c=a.parentNode;b=new O(b);for(var e=0,f=b.length;e<f;e++){var g=b[e];c.insertBefore(g,d.nextSibling);d=g}},addClass:Bb,removeClass:Ab,toggleClass:function(a,b,d){b&&q(b.split(" "),
|
||||
function(b){var e=d;y(e)&&(e=!zb(a,b));(e?Bb:Ab)(a,b)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,b){return a.getElementsByTagName?a.getElementsByTagName(b):[]},clone:Xb,triggerHandler:function(a,b,d){var c,e,f=b.type||b,g=xb(a);if(g=(g=g&&g.events)&&g[f])c={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped=
|
||||
!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:A,type:f,target:a},b.type&&(c=S(c,b)),b=ia(g),e=d?[c].concat(d):[c],q(b,function(b){c.isImmediatePropagationStopped()||b.apply(a,e)})}},function(a,b){O.prototype[b]=function(b,c,e){for(var f,g=0,h=this.length;g<h;g++)y(f)?(f=a(this[g],b,c,e),w(f)&&(f=F(f))):Qc(f,a(this[g],b,c,e));return w(f)?f:this};O.prototype.bind=O.prototype.on;O.prototype.unbind=O.prototype.off});Ra.prototype={put:function(a,
|
||||
b){this[Ca(a,this.nextUid)]=b},get:function(a){return this[Ca(a,this.nextUid)]},remove:function(a){var b=this[a=Ca(a,this.nextUid)];delete this[a];return b}};var If=[function(){this.$get=[function(){return Ra}]}],Wf=/^([^\(]+?)=>/,Xf=/^[^\(]*\(\s*([^\)]*)\)/m,Eg=/,/,Fg=/^\s*(_?)(\S+?)\1\s*$/,Vf=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ha=N("$injector");cb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw G(d)&&d||(d=a.name||Yf(a)),Ha("strictdi",d);
|
||||
b=Wc(a);q(b[1].split(Eg),function(a){a.replace(Fg,function(a,b,d){c.push(d)})})}a.$inject=c}}else L(a)?(b=a.length-1,Pa(a[b],"fn"),c=a.slice(0,b)):Pa(a,"fn",!0);return c};var Rd=N("$animate"),$e=function(){this.$get=A},af=function(){var a=new Ra,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=G(b)?b.split(" "):L(b)?b:[],q(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){q(b,function(b){var c=a.get(b);if(c){var d=Zf(b.attr("class")),e="",f="";q(c,
|
||||
function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});q(b,function(a){e&&Bb(a,e);f&&Ab(a,f)});a.remove(b)}});b.length=0}return{enabled:A,on:A,off:A,pin:A,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=e(k,h,!0),l=e(k,l,!1),h||l)a.put(g,k),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},Ye=["$provide",function(a){var b=this;this.$$registeredAnimations=
|
||||
Object.create(null);this.register=function(d,c){if(d&&"."!==d.charAt(0))throw Rd("notcsel",d);var e=d+"-animation";b.$$registeredAnimations[d.substr(1)]=e;a.factory(e,c)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Rd("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var h;a:{for(h=0;h<d.length;h++){var k=
|
||||
d[h];if(1===k.nodeType){h=k;break a}}h=void 0}!h||h.parentNode||h.previousElementSibling||(d=null)}d?d.after(a):c.prepend(a)}return{on:a.on,off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.end&&a.end()},enter:function(e,f,g,h){f=f&&F(f);g=g&&F(g);f=f||g.parent();b(e,f,g);return a.push(e,"enter",Ia(h))},move:function(e,f,g,h){f=f&&F(f);g=g&&F(g);f=f||g.parent();b(e,f,g);return a.push(e,"move",Ia(h))},leave:function(b,c){return a.push(b,"leave",Ia(c),function(){b.remove()})},addClass:function(b,
|
||||
c,g){g=Ia(g);g.addClass=gb(g.addclass,c);return a.push(b,"addClass",g)},removeClass:function(b,c,g){g=Ia(g);g.removeClass=gb(g.removeClass,c);return a.push(b,"removeClass",g)},setClass:function(b,c,g,h){h=Ia(h);h.addClass=gb(h.addClass,c);h.removeClass=gb(h.removeClass,g);return a.push(b,"setClass",h)},animate:function(b,c,g,h,k){k=Ia(k);k.from=k.from?S(k.from,c):c;k.to=k.to?S(k.to,g):g;k.tempClasses=gb(k.tempClasses,h||"ng-inline-animate");return a.push(b,"animate",k)}}}]}],cf=function(){this.$get=
|
||||
["$$rAF",function(a){function b(b){d.push(b);1<d.length||a(function(){for(var a=0;a<d.length;a++)d[a]();d=[]})}var d=[];return function(){var a=!1;b(function(){a=!0});return function(d){a?d():b(d)}}}]},bf=function(){this.$get=["$q","$sniffer","$$animateAsyncRun","$document","$timeout",function(a,b,d,c,e){function f(a){this.setHost(a);var b=d();this._doneCallbacks=[];this._tick=function(a){var d=c[0];d&&d.hidden?e(a,0,!1):b(a)};this._state=0}f.chain=function(a,b){function c(){if(d===a.length)b(!0);
|
||||
else a[d](function(a){!1===a?b(!1):(d++,c())})}var d=0;c()};f.all=function(a,b){function c(f){e=e&&f;++d===a.length&&b(e)}var d=0,e=!0;q(a,function(a){a.done(c)})};f.prototype={setHost:function(a){this.host=a||{}},done:function(a){2===this._state?a():this._doneCallbacks.push(a)},progress:A,getPromise:function(){if(!this.promise){var b=this;this.promise=a(function(a,c){b.done(function(b){!1===b?c():a()})})}return this.promise},then:function(a,b){return this.getPromise().then(a,b)},"catch":function(a){return this.getPromise()["catch"](a)},
|
||||
"finally":function(a){return this.getPromise()["finally"](a)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&&this.host.resume()},end:function(){this.host.end&&this.host.end();this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel();this._resolve(!1)},complete:function(a){var b=this;0===b._state&&(b._state=1,b._tick(function(){b._resolve(a)}))},_resolve:function(a){2!==this._state&&(q(this._doneCallbacks,function(b){b(a)}),this._doneCallbacks.length=
|
||||
0,this._state=2)}};return f}]},Ze=function(){this.$get=["$$rAF","$q","$$AnimateRunner",function(a,b,d){return function(b,e){function f(){a(function(){g.addClass&&(b.addClass(g.addClass),g.addClass=null);g.removeClass&&(b.removeClass(g.removeClass),g.removeClass=null);g.to&&(b.css(g.to),g.to=null);h||k.complete();h=!0});return k}var g=e||{};g.$$prepared||(g=pa(g));g.cleanupStyles&&(g.from=g.to=null);g.from&&(b.css(g.from),g.from=null);var h,k=new d;return{start:f,end:f}}}]},ga=N("$compile"),bc=new function(){};
|
||||
Fc.$inject=["$provide","$$sanitizeUriProvider"];Fb.prototype.isFirstChange=function(){return this.previousValue===bc};var Yc=/^((?:x|data)[\:\-_])/i,cg=N("$controller"),cd=/^(\S+)(\s+as\s+([\w$]+))?$/,jf=function(){this.$get=["$document",function(a){return function(b){b?!b.nodeType&&b instanceof F&&(b=b[0]):b=a[0].body;return b.offsetWidth+1}}]},dd="application/json",ec={"Content-Type":dd+";charset=utf-8"},eg=/^\[|^\{(?!\{)/,fg={"[":/]$/,"{":/}$/},dg=/^\)\]\}',?\n/,Gg=N("$http"),hd=function(a){return function(){throw Gg("legacy",
|
||||
a);}},Ka=ca.$interpolateMinErr=N("$interpolate");Ka.throwNoconcat=function(a){throw Ka("noconcat",a);};Ka.interr=function(a,b){return Ka("interr",a,b.toString())};var rf=function(){this.$get=["$window",function(a){function b(a){var b=function(a){b.data=a;b.called=!0};b.id=a;return b}var d=a.angular.callbacks,c={};return{createCallback:function(a){a="_"+(d.$$counter++).toString(36);var f="angular.callbacks."+a,g=b(a);c[f]=d[a]=g;return f},wasCalled:function(a){return c[a].called},getResponse:function(a){return c[a].data},
|
||||
removeCallback:function(a){delete d[c[a].id];delete c[a]}}}]},Hg=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,hg={http:80,https:443,ftp:21},Gb=N("$location"),Ig={$$absUrl:"",$$html5:!1,$$replace:!1,absUrl:Hb("$$absUrl"),url:function(a){if(y(a))return this.$$url;var b=Hg.exec(a);(b[1]||""===a)&&this.path(decodeURIComponent(b[1]));(b[2]||b[1]||""===a)&&this.search(b[3]||"");this.hash(b[5]||"");return this},protocol:Hb("$$protocol"),host:Hb("$$host"),port:Hb("$$port"),path:md("$$path",function(a){a=null!==a?a.toString():
|
||||
"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,b){switch(arguments.length){case 0:return this.$$search;case 1:if(G(a)||T(a))a=a.toString(),this.$$search=Ac(a);else if(D(a))a=pa(a,{}),q(a,function(b,c){null==b&&delete a[c]}),this.$$search=a;else throw Gb("isrcharg");break;default:y(b)||null===b?delete this.$$search[a]:this.$$search[a]=b}this.$$compose();return this},hash:md("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};q([ld,hc,gc],
|
||||
function(a){a.prototype=Object.create(Ig);a.prototype.state=function(b){if(!arguments.length)return this.$$state;if(a!==gc||!this.$$html5)throw Gb("nostate");this.$$state=y(b)?null:b;return this}});var X=N("$parse"),jg=Function.prototype.call,kg=Function.prototype.apply,lg=Function.prototype.bind,Ob=U();q("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Ob[a]=!0});var Jg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},jc=function(a){this.options=a};jc.prototype={constructor:jc,
|
||||
lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdentifierStart(this.peekMultichar()))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var b=a+this.peek(),d=b+this.peek(2),c=Ob[b],e=Ob[d];Ob[a]||
|
||||
c||e?(a=e?d:c?b:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,b){return-1!==b.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?
|
||||
this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue?this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)},isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a,b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):
|
||||
(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=w(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw X("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index<
|
||||
this.text.length;){var d=Q(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var c=this.peek();if("e"==d&&this.isExpOperator(c))a+=d;else if(this.isExpOperator(d)&&c&&this.isNumber(c)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||c&&this.isNumber(c)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:b,text:a,constant:!0,value:Number(a)})},readIdent:function(){var a=this.index;for(this.index+=this.peekMultichar().length;this.index<
|
||||
this.text.length;){var b=this.peekMultichar();if(!this.isIdentifierContinue(b))break;this.index+=b.length}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var b=this.index;this.index++;for(var d="",c=a,e=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),c=c+f;if(e)"u"===f?(e=this.text.substring(this.index+1,this.index+5),e.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+e+"]"),this.index+=4,d+=String.fromCharCode(parseInt(e,
|
||||
16))):d+=Jg[f]||f,e=!1;else if("\\"===f)e=!0;else{if(f===a){this.index++;this.tokens.push({index:b,text:c,constant:!0,value:d});return}d+=f}this.index++}this.throwError("Unterminated quote",b)}};var s=function(a,b){this.lexer=a;this.options=b};s.Program="Program";s.ExpressionStatement="ExpressionStatement";s.AssignmentExpression="AssignmentExpression";s.ConditionalExpression="ConditionalExpression";s.LogicalExpression="LogicalExpression";s.BinaryExpression="BinaryExpression";s.UnaryExpression="UnaryExpression";
|
||||
s.CallExpression="CallExpression";s.MemberExpression="MemberExpression";s.Identifier="Identifier";s.Literal="Literal";s.ArrayExpression="ArrayExpression";s.Property="Property";s.ObjectExpression="ObjectExpression";s.ThisExpression="ThisExpression";s.LocalsExpression="LocalsExpression";s.NGValueParameter="NGValueParameter";s.prototype={ast:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},
|
||||
program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:s.Program,body:a}},expressionStatement:function(){return{type:s.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=this.filter(a);return a},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();this.expect("=")&&(a={type:s.AssignmentExpression,
|
||||
left:a,right:this.assignment(),operator:"="});return a},ternary:function(){var a=this.logicalOR(),b,d;return this.expect("?")&&(b=this.expression(),this.consume(":"))?(d=this.expression(),{type:s.ConditionalExpression,test:a,alternate:b,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:s.LogicalExpression,operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a={type:s.LogicalExpression,
|
||||
operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),b;b=this.expect("==","!=","===","!==");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),b;b=this.expect("<",">","<=",">=");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,
|
||||
left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():
|
||||
this.selfReferential.hasOwnProperty(this.peek().text)?a=pa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:s.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:s.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):
|
||||
"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))
|
||||
}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:s.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;
|
||||
b={type:s.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")?(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"),b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");
|
||||
return{type:s.ObjectExpression,properties:a}},throwError:function(a,b){throw X("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw X("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw X("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>
|
||||
a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:s.ThisExpression},$locals:{type:s.LocalsExpression}}};td.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};V(c,d.$filter);var e="",f;this.stage="assign";
|
||||
if(f=rd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+this.generateFunction("assign","s,v,l");f=pd(c.body);d.stage="inputs";q(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+
|
||||
e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue","ensureSafeAssignContext","ifDefined","plus","text",e))(this.$filter,Sa,ra,nd,ig,Ib,mg,od,a);this.state=this.stage=void 0;e.literal=sd(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;q(b,function(b){a.push("var "+b+"="+d.generateFunction(b,"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");
|
||||
return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;q(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,h,k=this,l,m,n;c=c||A;if(!f&&w(a.watchId))b=
|
||||
b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case s.Program:q(a.body,function(b,c){k.recurse(b.expression,void 0,void 0,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:m=this.escape(a.value);this.assign(b,m);c(m);break;case s.UnaryExpression:this.recurse(a.argument,void 0,void 0,function(a){h=a});m=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,m);
|
||||
c(m);break;case s.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){h=a});m="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,
|
||||
b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Sa(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",
|
||||
a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Jb(a.name))&&k.addEnsureSafeObject(b);c(b);break;case s.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,void 0,function(){k.if_(k.notNull(g),function(){e&&1!==e&&k.addEnsureSafeAssignContext(g);if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,
|
||||
h),"{}")),m=k.ensureSafeObject(k.computedMember(g,h)),k.assign(b,m),d&&(d.computed=!0,d.name=h);else{Sa(a.property.name);e&&1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));m=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Jb(a.property.name))m=k.ensureSafeObject(m);k.assign(b,m);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case s.CallExpression:b=b||this.nextId();
|
||||
a.filter?(h=k.filter(a.callee.name),l=[],q(a.arguments,function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);q(a.arguments,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),m=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):m=
|
||||
h+"("+l.join(",")+")";m=k.ensureSafeObject(m);k.assign(b,m)},function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h=this.nextId();g={};if(!qd(a.left))throw X("lval");this.recurse(a.left,void 0,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case s.ArrayExpression:l=
|
||||
[];q(a.elements,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(m);break;case s.ObjectExpression:l=[];n=!1;q(a.properties,function(a){a.computed&&(n=!0)});n?(b=b||this.nextId(),this.assign(b,"{}"),q(a.properties,function(a){a.computed?(g=k.nextId(),k.recurse(a.key,g)):g=a.key.type===s.Identifier?a.key.name:""+a.key.value;h=k.nextId();k.recurse(a.value,h);k.assign(k.member(b,g,a.computed),h)})):(q(a.properties,function(b){k.recurse(b.value,
|
||||
a.constant?void 0:k.nextId(),void 0,function(a){l.push(k.escape(b.key.type===s.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case s.ThisExpression:this.assign(b,"s");c("s");break;case s.LocalsExpression:this.assign(b,"l");c("l");break;case s.NGValueParameter:this.assign(b,"v"),c("v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},
|
||||
assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),
|
||||
d(),c.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/[$_a-zA-Z][$_a-zA-Z0-9]*/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),
|
||||
";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+
|
||||
a+",text)"},lazyRecurse:function(a,b,d,c,e,f){var g=this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(G(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(T(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===
|
||||
typeof a)return"undefined";throw X("esc");},nextId:function(a,b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};ud.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;V(c,d.$filter);var e,f;if(e=rd(c))f=this.recurse(e);e=pd(c.body);var g;e&&(g=[],q(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];q(c.body,function(a){h.push(d.recurse(a.expression))});
|
||||
e=0===c.body.length?A:1===c.body.length?h[0]:function(a,b){var c;q(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=sd(c);e.constant=c.constant;return e},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,b);case s.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case s.BinaryExpression:return c=this.recurse(a.left),
|
||||
e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return Sa(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Jb(a.name),b,d,f.expression);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Sa(a.property.name,
|
||||
f.expression),e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case s.CallExpression:return g=[],q(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var n=[],p=0;p<g.length;++p)n.push(g[p](a,c,d,f));a=e.apply(void 0,n,f);return b?{context:void 0,name:void 0,value:a}:
|
||||
a}:function(a,c,d,m){var n=e(a,c,d,m),p;if(null!=n.value){ra(n.context,f.expression);nd(n.value,f.expression);p=[];for(var q=0;q<g.length;++q)p.push(ra(g[q](a,c,d,m),f.expression));p=ra(n.value.apply(n.context,p),f.expression)}return b?{value:p}:p};case s.AssignmentExpression:return c=this.recurse(a.left,!0,1),e=this.recurse(a.right),function(a,d,g,m){var n=c(a,d,g,m);a=e(a,d,g,m);ra(n.value,f.expression);Ib(n.context);n.context[n.name]=a;return b?{value:a}:a};case s.ArrayExpression:return g=[],q(a.elements,
|
||||
function(a){g.push(f.recurse(a))}),function(a,c,d,e){for(var f=[],p=0;p<g.length;++p)f.push(g[p](a,c,d,e));return b?{value:f}:f};case s.ObjectExpression:return g=[],q(a.properties,function(a){a.computed?g.push({key:f.recurse(a.key),computed:!0,value:f.recurse(a.value)}):g.push({key:a.key.type===s.Identifier?a.key.name:""+a.key.value,computed:!1,value:f.recurse(a.value)})}),function(a,c,d,e){for(var f={},p=0;p<g.length;++p)g[p].computed?f[g[p].key(a,c,d,e)]=g[p].value(a,c,d,e):f[g[p].key]=g[p].value(a,
|
||||
c,d,e);return b?{value:f}:f};case s.ThisExpression:return function(a){return b?{value:a}:a};case s.LocalsExpression:return function(a,c){return b?{value:c}:c};case s.NGValueParameter:return function(a,c,d){return b?{value:d}:d}}},"unary+":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?+d:0;return b?{value:d}:d}},"unary-":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?-d:0;return b?{value:d}:d}},"unary!":function(a,b){return function(d,c,e,f){d=!a(d,c,e,f);return b?{value:d}:
|
||||
d}},"binary+":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g);h=od(h,c);return d?{value:h}:h}},"binary-":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g);h=(w(h)?h:0)-(w(c)?c:0);return d?{value:h}:h}},"binary*":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)*b(c,e,f,g);return d?{value:c}:c}},"binary/":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)/b(c,e,f,g);return d?{value:c}:c}},"binary%":function(a,b,d){return function(c,e,f,g){c=a(c,e,
|
||||
f,g)%b(c,e,f,g);return d?{value:c}:c}},"binary===":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)===b(c,e,f,g);return d?{value:c}:c}},"binary!==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!==b(c,e,f,g);return d?{value:c}:c}},"binary==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)==b(c,e,f,g);return d?{value:c}:c}},"binary!=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!=b(c,e,f,g);return d?{value:c}:c}},"binary<":function(a,b,d){return function(c,e,f,g){c=a(c,e,
|
||||
f,g)<b(c,e,f,g);return d?{value:c}:c}},"binary>":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||
|
||||
b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0,name:void 0,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f=g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:void 0;b&&ra(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),m,n;null!=l&&(m=b(f,
|
||||
g,h,k),m+="",Sa(m,e),c&&1!==c&&(Ib(l),l&&!l[m]&&(l[m]={})),n=l[m],ra(n,e));return d?{context:l,name:m,value:n}:n}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&(Ib(g),g&&!g[b]&&(g[b]={}));h=null!=g?g[b]:void 0;(d||Jb(b))&&ra(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var kc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(a,d);this.astCompiler=d.csp?new ud(this.ast,
|
||||
b):new td(this.ast,b)};kc.prototype={constructor:kc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};var ng=Object.prototype.valueOf,sa=N("$sce"),la={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},pg=N("$compile"),$=C.document.createElement("a"),yd=Y(C.location.href);zd.$inject=["$document"];Mc.$inject=["$provide"];var Gd=22,Fd=".",mc="0";Ad.$inject=["$locale"];Cd.$inject=["$locale"];var Ag={yyyy:ba("FullYear",4,0,!1,!0),yy:ba("FullYear",2,0,
|
||||
!0,!0),y:ba("FullYear",1,0,!1,!0),MMMM:kb("Month"),MMM:kb("Month",!0),MM:ba("Month",2,1),M:ba("Month",1,1),LLLL:kb("Month",!1,!0),dd:ba("Date",2),d:ba("Date",1),HH:ba("Hours",2),H:ba("Hours",1),hh:ba("Hours",2,-12),h:ba("Hours",1,-12),mm:ba("Minutes",2),m:ba("Minutes",1),ss:ba("Seconds",2),s:ba("Seconds",1),sss:ba("Milliseconds",3),EEEE:kb("Day"),EEE:kb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Kb(Math[0<a?"floor":"ceil"](a/
|
||||
60),2)+Kb(Math.abs(a%60),2))},ww:Id(2),w:Id(1),G:nc,GG:nc,GGG:nc,GGGG:function(a,b){return 0>=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},zg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,yg=/^\-?\d+$/;Bd.$inject=["$locale"];var tg=ha(Q),ug=ha(ub);Dd.$inject=["$parse"];var oe=ha({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ma.call(b.prop("href"))?
|
||||
"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),vb={};q(Eb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=Aa("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});vb[c]=function(){return{restrict:"A",priority:100,link:e}}}});q(bd,function(a,b){vb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(Cg))){e.$set("ngPattern",
|
||||
new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});q(["src","srcset","href"],function(a){var b=Aa("ng-"+a);vb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"===ma.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Ea&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Lb={$addControl:A,$$renameControl:function(a,b){a.$name=b},$removeControl:A,$setValidity:A,
|
||||
$setDirty:A,$setPristine:A,$setSubmitted:A};Jd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Sd=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||A}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Jd,compile:function(d,f){d.addClass(Ua).addClass(ob);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var p=function(b){a.$apply(function(){n.$commitViewValue();
|
||||
n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",p,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",p,!1)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var q=g?c(n.$name):A;g&&(q(a,n),e.$observe(g,function(b){n.$name!==b&&(q(a,void 0),n.$$parentForm.$$renameControl(n,b),q=c(n.$name),q(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n);q(a,void 0);S(n,Lb)})}}}}}]},pe=Sd(),Ce=Sd(!0),Bg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,
|
||||
Kg=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Lg=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/,Mg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Td=/^(\d{4,})-(\d{2})-(\d{2})$/,Ud=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,rc=/^(\d{4,})-W(\d\d)$/,Vd=/^(\d{4,})-(\d\d)$/,
|
||||
Wd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Ld=U();q(["date","datetime-local","month","time","week"],function(a){Ld[a]=!0});var Xd={text:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c)},date:mb("date",Td,Nb(Td,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":mb("datetimelocal",Ud,Nb(Ud,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:mb("time",Wd,Nb(Wd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:mb("week",rc,function(a,b){if(da(a))return a;if(G(a)){rc.lastIndex=0;var d=rc.exec(a);
|
||||
if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Hd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:mb("month",Vd,Nb(Vd,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Md(a,b,d,c);lb(a,b,d,c,e,f);c.$$parserName="number";c.$parsers.push(function(a){if(c.$isEmpty(a))return null;if(Mg.test(a))return parseFloat(a)});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!T(a))throw nb("numfmt",
|
||||
a);a=a.toString()}return a});if(w(d.min)||d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)||y(g)||a>=g};d.$observe("min",function(a){w(a)&&!T(a)&&(a=parseFloat(a));g=T(a)&&!isNaN(a)?a:void 0;c.$validate()})}if(w(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||y(h)||a<=h};d.$observe("max",function(a){w(a)&&!T(a)&&(a=parseFloat(a));h=T(a)&&!isNaN(a)?a:void 0;c.$validate()})}},url:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c);c.$$parserName="url";c.$validators.url=
|
||||
function(a,b){var d=a||b;return c.$isEmpty(d)||Kg.test(d)}},email:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Lg.test(d)}},radio:function(a,b,d,c){y(d.name)&&b.attr("name",++pb);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value,a&&a.type)});c.$render=function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Nd(h,a,"ngTrueValue",d.ngTrueValue,
|
||||
!0),l=Nd(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return na(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:A,button:A,submit:A,reset:A,file:A},Gc=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Xd[Q(g.type)]||Xd.text)(e,f,
|
||||
g,h[0],b,a,d,c)}}}}],Ng=/^(true|false|\d+)$/,Ue=function(){return{restrict:"A",priority:100,compile:function(a,b){return Ng.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},ue=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=y(a)?"":a})}}}}],we=["$interpolate","$compile",
|
||||
function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=y(a)?"":a})}}}}],ve=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(b){return a.valueOf(b)});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){var d=
|
||||
f(b);c.html(a.getTrustedHtml(d)||"")})}}}}],Te=ha({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),xe=qc("",!0),ze=qc("Odd",0),ye=qc("Even",1),Ae=Ta({compile:function(a,b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),Be=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Lc={},Og={blur:!0,focus:!0};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),
|
||||
function(a){var b=Aa("ng-"+a);Lc[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Og[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var Ee=["$animate","$compile",function(a,b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var h,k,l;d.$watch(e.ngIf,function(d){d?k||g(function(d,f){k=f;d[d.length++]=
|
||||
b.$$createComment("end ngIf",e.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l=tb(h.clone),a.leave(l).then(function(){l=null}),h=null))})}}}],Fe=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ca.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,n,p){var q=0,s,B,r,y=function(){B&&(B.remove(),B=null);s&&
|
||||
(s.$destroy(),s=null);r&&(d.leave(r).then(function(){B=null}),B=r,r=null)};c.$watch(f,function(f){var m=function(){!w(h)||h&&!c.$eval(h)||b()},t=++q;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&t===q){var b=c.$new();n.template=a;a=p(b,function(a){y();d.enter(a,null,e).then(m)});s=b;r=a;s.$emit("$includeContentLoaded",f);c.$eval(g)}},function(){c.$$destroyed||t!==q||(y(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(y(),n.template=null)})}}}}],We=["$compile",function(a){return{restrict:"ECA",
|
||||
priority:-400,require:"ngInclude",link:function(b,d,c,e){ma.call(d[0]).match(/SVG/)?(d.empty(),a(Oc(e.template,C.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Ge=Ta({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),Se=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?W(e):e;c.$parsers.push(function(a){if(!y(a)){var b=
|
||||
[];a&&q(a.split(g),function(a){a&&b.push(f?W(a):a)});return b}});c.$formatters.push(function(a){if(L(a))return a.join(e)});c.$isEmpty=function(a){return!a||!a.length}}}},ob="ng-valid",Od="ng-invalid",Ua="ng-pristine",Mb="ng-dirty",Qd="ng-pending",nb=N("ngModel"),Pg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=void 0;this.$validators={};
|
||||
this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=void 0;this.$name=l(d.name||"",!1)(a);this.$$parentForm=Lb;var m=e(d.ngModel),n=m.assign,p=m,u=n,s=null,B,r=this;this.$$setOptions=function(a){if((r.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");p=function(a){var c=m(a);z(c)&&(c=b(a));
|
||||
return c};u=function(a,b){z(m(a))?f(a,{$$$p:b}):n(a,b)}}else if(!m.assign)throw nb("nonassign",d.ngModel,ya(c));};this.$render=A;this.$isEmpty=function(a){return y(a)||""===a||null===a||a!==a};this.$$updateEmptyClasses=function(a){r.$isEmpty(a)?(f.removeClass(c,"ng-not-empty"),f.addClass(c,"ng-empty")):(f.removeClass(c,"ng-empty"),f.addClass(c,"ng-not-empty"))};var J=0;Kd({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){r.$dirty=
|
||||
!1;r.$pristine=!0;f.removeClass(c,Mb);f.addClass(c,Ua)};this.$setDirty=function(){r.$dirty=!0;r.$pristine=!1;f.removeClass(c,Ua);f.addClass(c,Mb);r.$$parentForm.$setDirty()};this.$setUntouched=function(){r.$touched=!1;r.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")};this.$setTouched=function(){r.$touched=!0;r.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(s);r.$viewValue=r.$$lastCommittedViewValue;r.$render()};this.$validate=function(){if(!T(r.$modelValue)||
|
||||
!isNaN(r.$modelValue)){var a=r.$$rawModelValue,b=r.$valid,c=r.$modelValue,d=r.$options&&r.$options.allowInvalid;r.$$runValidators(a,r.$$lastCommittedViewValue,function(e){d||b===e||(r.$modelValue=e?a:void 0,r.$modelValue!==c&&r.$$writeModelToScope())})}};this.$$runValidators=function(a,b,c){function d(){var c=!0;q(r.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(q(r.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;q(r.$asyncValidators,function(e,g){var h=
|
||||
e(a,b);if(!h||!z(h.then))throw nb("nopromise",h);f(g,void 0);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)},A):g(!0)}function f(a,b){h===J&&r.$setValidity(a,b)}function g(a){h===J&&c(a)}J++;var h=J;(function(){var a=r.$$parserName||"parse";if(y(B))f(a,null);else return B||(q(r.$validators,function(a,b){f(b,null)}),q(r.$asyncValidators,function(a,b){f(b,null)})),f(a,B),B;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=
|
||||
r.$viewValue;g.cancel(s);if(r.$$lastCommittedViewValue!==a||""===a&&r.$$hasNativeValidators)r.$$updateEmptyClasses(a),r.$$lastCommittedViewValue=a,r.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var b=r.$$lastCommittedViewValue;if(B=y(b)?void 0:!0)for(var c=0;c<r.$parsers.length;c++)if(b=r.$parsers[c](b),y(b)){B=!1;break}T(r.$modelValue)&&isNaN(r.$modelValue)&&(r.$modelValue=p(a));var d=r.$modelValue,e=r.$options&&r.$options.allowInvalid;r.$$rawModelValue=
|
||||
b;e&&(r.$modelValue=b,r.$modelValue!==d&&r.$$writeModelToScope());r.$$runValidators(b,r.$$lastCommittedViewValue,function(a){e||(r.$modelValue=a?b:void 0,r.$modelValue!==d&&r.$$writeModelToScope())})};this.$$writeModelToScope=function(){u(a,r.$modelValue);q(r.$viewChangeListeners,function(a){try{a()}catch(c){b(c)}})};this.$setViewValue=function(a,b){r.$viewValue=a;r.$options&&!r.$options.updateOnDefault||r.$$debounceViewValueCommit(b)};this.$$debounceViewValueCommit=function(b){var c=0,d=r.$options;
|
||||
d&&w(d.debounce)&&(d=d.debounce,T(d)?c=d:T(d[b])?c=d[b]:T(d["default"])&&(c=d["default"]));g.cancel(s);c?s=g(function(){r.$commitViewValue()},c):h.$$phase?r.$commitViewValue():a.$apply(function(){r.$commitViewValue()})};a.$watch(function(){var b=p(a);if(b!==r.$modelValue&&(r.$modelValue===r.$modelValue||b===b)){r.$modelValue=r.$$rawModelValue=b;B=void 0;for(var c=r.$formatters,d=c.length,e=b;d--;)e=c[d](e);r.$viewValue!==e&&(r.$$updateEmptyClasses(e),r.$viewValue=r.$$lastCommittedViewValue=e,r.$render(),
|
||||
r.$$runValidators(b,e,A))}return b})}],Re=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:Pg,priority:1,compile:function(b){b.addClass(Ua).addClass("ng-untouched").addClass(ob);return{pre:function(a,b,e,f){var g=f[0];b=f[1]||g.$$parentForm;g.$$setOptions(f[2]&&f[2].$options);b.$addControl(g);e.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})},post:function(b,
|
||||
c,e,f){var g=f[0];if(g.$options&&g.$options.updateOn)c.on(g.$options.updateOn,function(a){g.$$debounceViewValueCommit(a&&a.type)});c.on("blur",function(){g.$touched||(a.$$phase?b.$evalAsync(g.$setTouched):b.$apply(g.$setTouched))})}}}}}],Qg=/(\s+|^)default(\s+|$)/,Ve=function(){return{restrict:"A",controller:["$scope","$attrs",function(a,b){var d=this;this.$options=pa(a.$eval(b.ngModelOptions));w(this.$options.updateOn)?(this.$options.updateOnDefault=!1,this.$options.updateOn=W(this.$options.updateOn.replace(Qg,
|
||||
function(){d.$options.updateOnDefault=!0;return" "}))):this.$options.updateOnDefault=!0}]}},He=Ta({terminal:!0,priority:1E3}),Rg=N("ngOptions"),Sg=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,Pe=["$compile","$document","$parse",function(a,b,d){function c(a,b,c){function e(a,b,c,d,f){this.selectValue=a;this.viewValue=
|
||||
b;this.label=c;this.group=d;this.disabled=f}function f(a){var b;if(!q&&ta(a))b=a;else{b=[];for(var c in a)a.hasOwnProperty(c)&&"$"!==c.charAt(0)&&b.push(c)}return b}var n=a.match(Sg);if(!n)throw Rg("iexp",a,ya(b));var p=n[5]||n[7],q=n[6];a=/ as /.test(n[0])&&n[1];var s=n[9];b=d(n[2]?n[1]:p);var w=a&&d(a)||b,r=s&&d(s),y=s?function(a,b){return r(c,b)}:function(a){return Ca(a)},v=function(a,b){return y(a,E(a,b))},A=d(n[2]||n[1]),t=d(n[3]||""),K=d(n[4]||""),z=d(n[8]),H={},E=q?function(a,b){H[q]=b;H[p]=
|
||||
a;return H}:function(a){H[p]=a;return H};return{trackBy:s,getTrackByValue:v,getWatchables:d(z,function(a){var b=[];a=a||[];for(var d=f(a),e=d.length,g=0;g<e;g++){var h=a===d?g:d[g],l=a[h],h=E(l,h),l=y(l,h);b.push(l);if(n[2]||n[1])l=A(c,h),b.push(l);n[4]&&(h=K(c,h),b.push(h))}return b}),getOptions:function(){for(var a=[],b={},d=z(c)||[],g=f(d),h=g.length,n=0;n<h;n++){var p=d===g?n:g[n],q=E(d[p],p),r=w(c,q),p=y(r,q),u=A(c,q),H=t(c,q),q=K(c,q),r=new e(p,r,u,H,q);a.push(r);b[p]=r}return{items:a,selectValueMap:b,
|
||||
getOptionFromViewValue:function(a){return b[v(a)]},getViewValueFromOption:function(a){return s?ca.copy(a.viewValue):a.viewValue}}}}}var e=C.document.createElement("option"),f=C.document.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","ngModel"],link:{pre:function(a,b,c,d){d[0].registerOption=A},post:function(d,h,k,l){function m(a,b){a.element=b;b.disabled=a.disabled;a.label!==b.label&&(b.label=a.label,b.textContent=a.label);a.value!==b.value&&(b.value=a.selectValue)}function n(){var a=
|
||||
t&&p.readValue();if(t)for(var b=t.items.length-1;0<=b;b--){var c=t.items[b];w(c.group)?Db(c.element.parentNode):Db(c.element)}t=K.getOptions();var d={};v&&h.prepend(B);t.items.forEach(function(a){var b;if(w(a.group)){b=d[a.group];b||(b=f.cloneNode(!1),C.appendChild(b),b.label=null===a.group?"null":a.group,d[a.group]=b);var c=e.cloneNode(!1)}else b=C,c=e.cloneNode(!1);b.appendChild(c);m(a,c)});h[0].appendChild(C);s.$render();s.$isEmpty(a)||(b=p.readValue(),(K.trackBy||y?na(a,b):a===b)||(s.$setViewValue(b),
|
||||
s.$render()))}var p=l[0],s=l[1],y=k.multiple,B;l=0;for(var r=h.children(),A=r.length;l<A;l++)if(""===r[l].value){B=r.eq(l);break}var v=!!B,z=F(e.cloneNode(!1));z.val("?");var t,K=c(k.ngOptions,h,d),C=b[0].createDocumentFragment();y?(s.$isEmpty=function(a){return!a||0===a.length},p.writeValue=function(a){t.items.forEach(function(a){a.element.selected=!1});a&&a.forEach(function(a){if(a=t.getOptionFromViewValue(a))a.element.selected=!0})},p.readValue=function(){var a=h.val()||[],b=[];q(a,function(a){(a=
|
||||
t.selectValueMap[a])&&!a.disabled&&b.push(t.getViewValueFromOption(a))});return b},K.trackBy&&d.$watchCollection(function(){if(L(s.$viewValue))return s.$viewValue.map(function(a){return K.getTrackByValue(a)})},function(){s.$render()})):(p.writeValue=function(a){var b=t.getOptionFromViewValue(a);b?(h[0].value!==b.selectValue&&(z.remove(),v||B.remove(),h[0].value=b.selectValue,b.element.selected=!0),b.element.setAttribute("selected","selected")):null===a||v?(z.remove(),v||h.prepend(B),h.val(""),B.prop("selected",
|
||||
!0),B.attr("selected",!0)):(v||B.remove(),h.prepend(z),h.val("?"),z.prop("selected",!0),z.attr("selected",!0))},p.readValue=function(){var a=t.selectValueMap[h.val()];return a&&!a.disabled?(v||B.remove(),z.remove(),t.getViewValueFromOption(a)):null},K.trackBy&&d.$watch(function(){return K.getTrackByValue(s.$viewValue)},function(){s.$render()}));v?(B.remove(),a(B)(d),B.removeClass("ng-scope")):B=F(e.cloneNode(!1));h.empty();n();d.$watchCollection(K.getWatchables,n)}}}}],Ie=["$locale","$interpolate",
|
||||
"$log",function(a,b,d){var c=/{}/g,e=/^when(Minus)?(.+)$/;return{link:function(f,g,h){function k(a){g.text(a||"")}var l=h.count,m=h.$attr.when&&g.attr(h.$attr.when),n=h.offset||0,p=f.$eval(m)||{},s={},w=b.startSymbol(),B=b.endSymbol(),r=w+l+"-"+n+B,z=ca.noop,v;q(h,function(a,b){var c=e.exec(b);c&&(c=(c[1]?"-":"")+Q(c[2]),p[c]=g.attr(h.$attr[b]))});q(p,function(a,d){s[d]=b(a.replace(c,r))});f.$watch(l,function(b){var c=parseFloat(b),e=isNaN(c);e||c in p||(c=a.pluralCat(c-n));c===v||e&&T(v)&&isNaN(v)||
|
||||
(z(),e=s[c],y(e)?(null!=b&&d.debug("ngPluralize: no rule defined for '"+c+"' in "+m),z=A,k()):z=f.$watch(e,k),v=c)})}}}],Je=["$parse","$animate","$compile",function(a,b,d){var c=N("ngRepeat"),e=function(a,b,c,d,e,m,n){a[c]=d;e&&(a[e]=m);a.$index=b;a.$first=0===b;a.$last=b===n-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(b&1))};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(f,g){var h=g.ngRepeat,k=d.$$createComment("end ngRepeat",
|
||||
h),l=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!l)throw c("iexp",h);var m=l[1],n=l[2],p=l[3],s=l[4],l=m.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);if(!l)throw c("iidexp",m);var w=l[3]||l[1],y=l[2];if(p&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(p)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(p)))throw c("badident",p);var r,z,v,A,t={$id:Ca};s?r=a(s):(v=function(a,b){return Ca(b)},
|
||||
A=function(a){return a});return function(a,d,f,g,l){r&&(z=function(b,c,d){y&&(t[y]=b);t[w]=c;t.$index=d;return r(a,t)});var m=U();a.$watchCollection(n,function(f){var g,n,r=d[0],s,u=U(),t,C,F,E,G,D,H;p&&(a[p]=f);if(ta(f))G=f,n=z||v;else for(H in n=z||A,G=[],f)ua.call(f,H)&&"$"!==H.charAt(0)&&G.push(H);t=G.length;H=Array(t);for(g=0;g<t;g++)if(C=f===G?g:G[g],F=f[C],E=n(C,F,g),m[E])D=m[E],delete m[E],u[E]=D,H[g]=D;else{if(u[E])throw q(H,function(a){a&&a.scope&&(m[a.id]=a)}),c("dupes",h,E,F);H[g]={id:E,
|
||||
scope:void 0,clone:void 0};u[E]=!0}for(s in m){D=m[s];E=tb(D.clone);b.leave(E);if(E[0].parentNode)for(g=0,n=E.length;g<n;g++)E[g].$$NG_REMOVED=!0;D.scope.$destroy()}for(g=0;g<t;g++)if(C=f===G?g:G[g],F=f[C],D=H[g],D.scope){s=r;do s=s.nextSibling;while(s&&s.$$NG_REMOVED);D.clone[0]!=s&&b.move(tb(D.clone),null,r);r=D.clone[D.clone.length-1];e(D.scope,g,w,F,y,C,t)}else l(function(a,c){D.scope=c;var d=k.cloneNode(!1);a[a.length++]=d;b.enter(a,null,r);r=d;D.clone=a;u[D.id]=D;e(D.scope,g,w,F,y,C,t)});m=
|
||||
u})}}}}],Ke=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngShow,function(b){a[b?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],De=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngHide,function(b){a[b?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],Le=Ta(function(a,b,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&q(d,function(a,c){b.css(c,"")});a&&b.css(a)},
|
||||
!0)}),Me=["$animate","$compile",function(a,b){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(d,c,e,f){var g=[],h=[],k=[],l=[],m=function(a,b){return function(){a.splice(b,1)}};d.$watch(e.ngSwitch||e.on,function(c){var d,e;d=0;for(e=k.length;d<e;++d)a.cancel(k[d]);d=k.length=0;for(e=l.length;d<e;++d){var s=tb(h[d].clone);l[d].$destroy();(k[d]=a.leave(s)).then(m(k,d))}h.length=0;l.length=0;(g=f.cases["!"+c]||f.cases["?"])&&q(g,function(c){c.transclude(function(d,
|
||||
e){l.push(e);var f=c.element;d[d.length++]=b.$$createComment("end ngSwitchWhen");h.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],Ne=Ta({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["!"+d.ngSwitchWhen]=c.cases["!"+d.ngSwitchWhen]||[];c.cases["!"+d.ngSwitchWhen].push({transclude:e,element:b})}}),Oe=Ta({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["?"]=c.cases["?"]||[];c.cases["?"].push({transclude:e,
|
||||
element:b})}}),Tg=N("ngTransclude"),Qe=["$compile",function(a){return{restrict:"EAC",terminal:!0,compile:function(b){var d=a(b.contents());b.empty();return function(a,b,f,g,h){function k(){d(a,function(a){b.append(a)})}if(!h)throw Tg("orphan",ya(b));f.ngTransclude===f.$attr.ngTransclude&&(f.ngTransclude="");f=f.ngTransclude||f.ngTranscludeSlot;h(function(a,c){a.length?b.append(a):(k(),c.$destroy())},null,f);f&&!h.isSlotFilled(f)&&k()}}}}],qe=["$templateCache",function(a){return{restrict:"E",terminal:!0,
|
||||
compile:function(b,d){"text/ng-template"==d.type&&a.put(d.id,b[0].text)}}}],Ug={$setViewValue:A,$render:A},Vg=["$element","$scope",function(a,b){var d=this,c=new Ra;d.ngModelCtrl=Ug;d.unknownOption=F(C.document.createElement("option"));d.renderUnknownOption=function(b){b="? "+Ca(b)+" ?";d.unknownOption.val(b);a.prepend(d.unknownOption);a.val(b)};b.$on("$destroy",function(){d.renderUnknownOption=A});d.removeUnknownOption=function(){d.unknownOption.parent()&&d.unknownOption.remove()};d.readValue=function(){d.removeUnknownOption();
|
||||
return a.val()};d.writeValue=function(b){d.hasOption(b)?(d.removeUnknownOption(),a.val(b),""===b&&d.emptyOption.prop("selected",!0)):null==b&&d.emptyOption?(d.removeUnknownOption(),a.val("")):d.renderUnknownOption(b)};d.addOption=function(a,b){if(8!==b[0].nodeType){Qa(a,'"option value"');""===a&&(d.emptyOption=b);var g=c.get(a)||0;c.put(a,g+1);d.ngModelCtrl.$render();b[0].hasAttribute("selected")&&(b[0].selected=!0)}};d.removeOption=function(a){var b=c.get(a);b&&(1===b?(c.remove(a),""===a&&(d.emptyOption=
|
||||
void 0)):c.put(a,b-1))};d.hasOption=function(a){return!!c.get(a)};d.registerOption=function(a,b,c,h,k){if(h){var l;c.$observe("value",function(a){w(l)&&d.removeOption(l);l=a;d.addOption(a,b)})}else k?a.$watch(k,function(a,e){c.$set("value",a);e!==a&&d.removeOption(e);d.addOption(a,b)}):d.addOption(c.value,b);b.on("$destroy",function(){d.removeOption(c.value);d.ngModelCtrl.$render()})}}],re=function(){return{restrict:"E",require:["select","?ngModel"],controller:Vg,priority:1,link:{pre:function(a,b,
|
||||
d,c){var e=c[1];if(e){var f=c[0];f.ngModelCtrl=e;b.on("change",function(){a.$apply(function(){e.$setViewValue(f.readValue())})});if(d.multiple){f.readValue=function(){var a=[];q(b.find("option"),function(b){b.selected&&a.push(b.value)});return a};f.writeValue=function(a){var c=new Ra(a);q(b.find("option"),function(a){a.selected=w(c.get(a.value))})};var g,h=NaN;a.$watch(function(){h!==e.$viewValue||na(g,e.$viewValue)||(g=ia(e.$viewValue),e.$render());h=e.$viewValue});e.$isEmpty=function(a){return!a||
|
||||
0===a.length}}}},post:function(a,b,d,c){var e=c[1];if(e){var f=c[0];e.$render=function(){f.writeValue(e.$viewValue)}}}}}},te=["$interpolate",function(a){return{restrict:"E",priority:100,compile:function(b,d){if(w(d.value))var c=a(d.value,!0);else{var e=a(b.text(),!0);e||d.$set("value",b.text())}return function(a,b,d){var k=b.parent();(k=k.data("$selectController")||k.parent().data("$selectController"))&&k.registerOption(a,b,d,c,e)}}}}],se=ha({restrict:"E",terminal:!1}),Ic=function(){return{restrict:"A",
|
||||
require:"?ngModel",link:function(a,b,d,c){c&&(d.required=!0,c.$validators.required=function(a,b){return!d.required||!c.$isEmpty(b)},d.$observe("required",function(){c.$validate()}))}}},Hc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e,f=d.ngPattern||d.pattern;d.$observe("pattern",function(a){G(a)&&0<a.length&&(a=new RegExp("^"+a+"$"));if(a&&!a.test)throw N("ngPattern")("noregexp",f,a,ya(b));e=a||void 0;c.$validate()});c.$validators.pattern=function(a,b){return c.$isEmpty(b)||
|
||||
y(e)||e.test(b)}}}}},Kc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=-1;d.$observe("maxlength",function(a){a=Z(a);e=isNaN(a)?-1:a;c.$validate()});c.$validators.maxlength=function(a,b){return 0>e||c.$isEmpty(b)||b.length<=e}}}}},Jc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=Z(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};C.angular.bootstrap?
|
||||
C.console&&console.log("WARNING: Tried to load angular more than once."):(je(),le(ca),ca.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),
|
||||
SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",
|
||||
PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a,c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),F(C.document).ready(function(){fe(C.document,Bc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
|
||||
//# sourceMappingURL=angular.min.js.map
|
||||
@@ -1,854 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* 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
|
||||
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 (search.accessToken) {
|
||||
localStorage.token = search.accessToken;
|
||||
|
||||
// strip the accessToken and expiresAt, then preserve the rest
|
||||
delete search.accessToken;
|
||||
delete search.expiresAt;
|
||||
|
||||
// this will reload the page as this is not a hash change
|
||||
window.location.search = encodeURIComponent(Object.keys(search).map(function (key) { return key + '=' + search[key]; }).join('&'));
|
||||
}
|
||||
|
||||
// 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.multiselect']);
|
||||
|
||||
app.config(['NotificationProvider', function (NotificationProvider) {
|
||||
NotificationProvider.setOptions({
|
||||
delay: 5000,
|
||||
startTop: 60,
|
||||
positionX: 'left',
|
||||
templateUrl: 'notification.html'
|
||||
});
|
||||
}]);
|
||||
|
||||
// configure resourceUrlWhitelist https://code.angularjs.org/1.5.8/docs/api/ng/provider/$sceDelegateProvider#resourceUrlWhitelist
|
||||
app.config(function ($sceDelegateProvider) {
|
||||
$sceDelegateProvider.resourceUrlWhitelist([
|
||||
// Allow same origin resource loads.
|
||||
'self',
|
||||
// Allow loading from our assets domain.
|
||||
'https://cloudron.io/**',
|
||||
'https://staging.cloudron.io/**',
|
||||
'https://dev.cloudron.io/**',
|
||||
// Allow local development against the appstore pages
|
||||
'http://localhost:5000/**'
|
||||
]);
|
||||
});
|
||||
|
||||
// setup all major application routes
|
||||
app.config(['$routeProvider', function ($routeProvider) {
|
||||
$routeProvider.when('/', {
|
||||
redirectTo: '/apps'
|
||||
}).when('/users', {
|
||||
controller: 'UsersController',
|
||||
templateUrl: 'views/users.html?' + window.VITE_CACHE_ID
|
||||
}).when('/user-directory', {
|
||||
controller: 'UserSettingsController',
|
||||
templateUrl: 'views/user-directory.html?' + window.VITE_CACHE_ID
|
||||
}).when('/app/:appId/:view?', {
|
||||
controller: 'AppController',
|
||||
templateUrl: 'views/app.html?' + window.VITE_CACHE_ID
|
||||
}).when('/appstore', {
|
||||
controller: 'AppStoreController',
|
||||
templateUrl: 'views/appstore.html?' + window.VITE_CACHE_ID
|
||||
}).when('/appstore/:appId', {
|
||||
controller: 'AppStoreController',
|
||||
templateUrl: 'views/appstore.html?' + window.VITE_CACHE_ID
|
||||
}).when('/apps', {
|
||||
controller: 'AppsController',
|
||||
templateUrl: 'views/apps.html?' + window.VITE_CACHE_ID
|
||||
}).when('/profile', {
|
||||
controller: 'ProfileController',
|
||||
templateUrl: 'views/profile.html?' + window.VITE_CACHE_ID
|
||||
}).when('/backups', {
|
||||
controller: 'BackupsController',
|
||||
templateUrl: 'views/backups.html?' + window.VITE_CACHE_ID
|
||||
}).when('/branding', {
|
||||
controller: 'BrandingController',
|
||||
templateUrl: 'views/branding.html?' + window.VITE_CACHE_ID
|
||||
}).when('/network', {
|
||||
controller: 'NetworkController',
|
||||
templateUrl: 'views/network.html?' + window.VITE_CACHE_ID
|
||||
}).when('/domains', {
|
||||
controller: 'DomainsController',
|
||||
templateUrl: 'views/domains.html?' + window.VITE_CACHE_ID
|
||||
}).when('/email', {
|
||||
controller: 'EmailsController',
|
||||
templateUrl: 'views/emails.html?' + window.VITE_CACHE_ID
|
||||
}).when('/emails-eventlog', {
|
||||
controller: 'EmailsEventlogController',
|
||||
templateUrl: 'views/emails-eventlog.html?' + window.VITE_CACHE_ID
|
||||
}).when('/emails-queue', {
|
||||
controller: 'EmailsQueueController',
|
||||
templateUrl: 'views/emails-queue.html?' + window.VITE_CACHE_ID
|
||||
}).when('/email/:domain/:view?', {
|
||||
controller: 'EmailController',
|
||||
templateUrl: 'views/email.html?' + window.VITE_CACHE_ID
|
||||
}).when('/notifications', {
|
||||
controller: 'NotificationsController',
|
||||
templateUrl: 'views/notifications.html?' + window.VITE_CACHE_ID
|
||||
}).when('/oidc', {
|
||||
redirectTo: '/user-directory'
|
||||
}).when('/settings', {
|
||||
controller: 'SettingsController',
|
||||
templateUrl: 'views/settings.html?' + window.VITE_CACHE_ID
|
||||
}).when('/eventlog', {
|
||||
controller: 'EventLogController',
|
||||
templateUrl: 'views/eventlog.html?' + window.VITE_CACHE_ID
|
||||
}).when('/support', {
|
||||
controller: 'SupportController',
|
||||
templateUrl: 'views/support.html?' + window.VITE_CACHE_ID
|
||||
}).when('/system', {
|
||||
controller: 'SystemController',
|
||||
templateUrl: 'views/system.html?' + window.VITE_CACHE_ID
|
||||
}).when('/services', {
|
||||
controller: 'ServicesController',
|
||||
templateUrl: 'views/services.html?' + window.VITE_CACHE_ID
|
||||
}).when('/volumes', {
|
||||
controller: 'VolumesController',
|
||||
templateUrl: 'views/volumes.html?' + window.VITE_CACHE_ID
|
||||
}).otherwise({ redirectTo: '/'});
|
||||
}]);
|
||||
|
||||
app.filter('notificationTypeToColor', function () {
|
||||
return function (n) {
|
||||
switch (n.type) {
|
||||
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.BOX_UPDATE:
|
||||
case NOTIFICATION_TYPES.MANUAL_APP_UPDATE:
|
||||
return '#f0ad4e';
|
||||
default:
|
||||
return '#2196f3';
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('capitalize', function () {
|
||||
return function (s) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('activeTask', function () {
|
||||
return function (app) {
|
||||
if (!app) return false;
|
||||
return app.taskId !== null;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('installSuccess', function () {
|
||||
return function (app) {
|
||||
if (!app) return false;
|
||||
return app.installationState === ISTATES.INSTALLED;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('appIsInstalledAndHealthy', function () {
|
||||
return function (app) {
|
||||
if (!app) return false;
|
||||
return (app.installationState === ISTATES.INSTALLED && app.health === HSTATES.HEALTHY && app.runState === RSTATES.RUNNING);
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('applicationLink', function () {
|
||||
return function(app) {
|
||||
if (!app) return '';
|
||||
|
||||
// app links have http already in the fqdn
|
||||
if (app.fqdn.indexOf('http') !== 0) return 'https://' + app.fqdn;
|
||||
|
||||
return app.fqdn;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('userManagementFilter', function () {
|
||||
return function(apps, option) {
|
||||
return apps.filter(function (app) {
|
||||
if (option.id === '') return true;
|
||||
if (option.id === 'sso') return !!(app.manifest.optionalSso || app.manifest.addons.ldap || app.manifest.addons.proxyAuth);
|
||||
if (option.id === 'nosso') return app.manifest.optionalSso || (!app.manifest.addons.ldap && !app.manifest.addons.proxyAuth);
|
||||
if (option.id === 'email') return !!app.manifest.addons.email;
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
// this appears when an item in app grid is clicked
|
||||
app.filter('prettyAppErrorMessage', function () {
|
||||
return function (app) {
|
||||
if (!app) return '';
|
||||
|
||||
if (app.installationState === ISTATES.INSTALLED) {
|
||||
// app.health can also be null to indicate insufficient data
|
||||
if (app.health === HSTATES.UNHEALTHY) return 'The app is not responding to health checks. Check the logs for any error messages.';
|
||||
}
|
||||
|
||||
if (app.error.reason === 'Access Denied') {
|
||||
if (app.error.domain) return 'The DNS record for this location is not setup correctly. Please verify your DNS settings and repair this app.';
|
||||
} else if (app.error.reason === 'Already Exists') {
|
||||
if (app.error.domain) return 'The DNS record for this location already exists. Cloudron does not remove existing DNS records. Manually remove the DNS record and then click on repair.';
|
||||
}
|
||||
|
||||
return app.error.message;
|
||||
};
|
||||
});
|
||||
|
||||
// this appears as tool tip in app grid
|
||||
app.filter('appProgressMessage', function () {
|
||||
return function (app) {
|
||||
var message = app.message || (app.error ? app.error.message : '');
|
||||
return message;
|
||||
};
|
||||
});
|
||||
|
||||
// see apps.js $scope.states
|
||||
app.filter('selectedStateFilter', ['Client', function (Client) {
|
||||
return function selectedStateFilter(apps, selectedState) {
|
||||
return apps.filter(function (app) {
|
||||
if (!selectedState || !selectedState.state) return true;
|
||||
|
||||
if (selectedState.state === 'running') return app.runState === RSTATES.RUNNING && app.health === HSTATES.HEALTHY && app.installationState === ISTATES.INSTALLED;
|
||||
if (selectedState.state === 'stopped') return app.runState === RSTATES.STOPPED;
|
||||
if (selectedState.state === 'update_available') return !!(Client.getConfig().update[app.id] && Client.getConfig().update[app.id].manifest.version && Client.getConfig().update[app.id].manifest.version !== app.manifest.version);
|
||||
|
||||
return app.runState === RSTATES.RUNNING && (app.health !== HSTATES.HEALTHY || app.installationState !== ISTATES.INSTALLED); // not responding
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
app.filter('selectedGroupAccessFilter', function () {
|
||||
return function selectedGroupAccessFilter(apps, group) {
|
||||
return apps.filter(function (app) {
|
||||
if (!group.id) return true; // case for no filter entry
|
||||
if (!app.accessRestriction) return true;
|
||||
|
||||
if (!app.accessRestriction.groups) return false;
|
||||
|
||||
if (app.accessRestriction.groups.indexOf(group.id) !== -1) return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('selectedTagFilter', function () {
|
||||
return function selectedTagFilter(apps, selectedTags) {
|
||||
return apps.filter(function (app) {
|
||||
if (selectedTags.length === 0) return true;
|
||||
if (!app.tags) return false;
|
||||
|
||||
for (var i = 0; i < selectedTags.length; i++) {
|
||||
if (app.tags.indexOf(selectedTags[i]) === -1) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('selectedDomainFilter', function () {
|
||||
return function selectedDomainFilter(apps, selectedDomain) {
|
||||
return apps.filter(function (app) {
|
||||
if (selectedDomain._alldomains) return true; // magic domain for single select, see apps.js ALL_DOMAINS_DOMAIN
|
||||
if (app.type === APP_TYPES.LINK) return false;
|
||||
|
||||
if (selectedDomain.domain === app.domain) return true;
|
||||
if (app.aliasDomains.find(function (ad) { return ad.domain === selectedDomain.domain; })) return true;
|
||||
if (app.redirectDomains.find(function (ad) { return ad.domain === selectedDomain.domain; })) return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('appSearchFilter', function () {
|
||||
return function appSearchFilter(apps, appSearch) {
|
||||
return apps.filter(function (app) {
|
||||
if (!appSearch) return true;
|
||||
appSearch = appSearch.toLowerCase();
|
||||
return app.fqdn.indexOf(appSearch) !== -1
|
||||
|| (app.label && app.label.toLowerCase().indexOf(appSearch) !== -1)
|
||||
|| (app.manifest.title && app.manifest.title.toLowerCase().indexOf(appSearch) !== -1)
|
||||
|| (appSearch.length >=6 && app.id.indexOf(appSearch) !== -1);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyDomains', function () {
|
||||
return function prettyDomains(domains) {
|
||||
return domains.map(function (d) { return d.domain; }).join(', ');
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('installationActive', function () {
|
||||
return function (app) {
|
||||
if (app.installationState === ISTATES.ERROR) return false;
|
||||
if (app.installationState === ISTATES.INSTALLED) return false;
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
// 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) {
|
||||
if (!app) return '';
|
||||
|
||||
var waiting = app.progress === 0 ? ' (Queued)' : '';
|
||||
|
||||
switch (app.installationState) {
|
||||
case ISTATES.PENDING_INSTALL:
|
||||
return 'Installing' + waiting;
|
||||
case ISTATES.PENDING_CLONE:
|
||||
return 'Cloning' + waiting;
|
||||
case ISTATES.PENDING_LOCATION_CHANGE:
|
||||
case ISTATES.PENDING_CONFIGURE:
|
||||
case ISTATES.PENDING_RECREATE_CONTAINER:
|
||||
case ISTATES.PENDING_SERVICES_CHANGE:
|
||||
case ISTATES.PENDING_DEBUG:
|
||||
return 'Configuring' + waiting;
|
||||
case ISTATES.PENDING_RESIZE:
|
||||
return 'Resizing' + waiting;
|
||||
case ISTATES.PENDING_DATA_DIR_MIGRATION:
|
||||
return 'Migrating data' + waiting;
|
||||
case ISTATES.PENDING_UNINSTALL: return 'Uninstalling' + waiting;
|
||||
case ISTATES.PENDING_RESTORE: return 'Restoring' + waiting;
|
||||
case ISTATES.PENDING_IMPORT: return 'Importing' + waiting;
|
||||
case ISTATES.PENDING_UPDATE: return 'Updating' + waiting;
|
||||
case ISTATES.PENDING_BACKUP: return 'Backing up' + waiting;
|
||||
case ISTATES.PENDING_START: return 'Starting' + waiting;
|
||||
case ISTATES.PENDING_STOP: return 'Stopping' + waiting;
|
||||
case ISTATES.PENDING_RESTART: return 'Restarting' + waiting;
|
||||
case ISTATES.ERROR: {
|
||||
if (app.error && app.error.message === 'ETRYAGAIN') return 'DNS Error';
|
||||
return 'Error';
|
||||
}
|
||||
case ISTATES.INSTALLED: {
|
||||
if (app.debugMode) {
|
||||
return 'Recovery Mode';
|
||||
} else if (app.runState === RSTATES.RUNNING) {
|
||||
if (!app.health) return 'Starting...'; // no data yet
|
||||
if (app.type === APP_TYPES.LINK) return '';
|
||||
if (app.health === HSTATES.HEALTHY) return 'Running';
|
||||
return 'Not responding'; // dead/exit/unhealthy
|
||||
} else if (app.runState === RSTATES.STOPPED) return 'Stopped';
|
||||
else return app.runState;
|
||||
}
|
||||
default: return app.installationState;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('taskName', function () {
|
||||
return function(installationState) {
|
||||
switch (installationState) {
|
||||
case ISTATES.PENDING_INSTALL: return 'install';
|
||||
case ISTATES.PENDING_CLONE: return 'clone';
|
||||
case ISTATES.PENDING_LOCATION_CHANGE: return 'location change';
|
||||
case ISTATES.PENDING_CONFIGURE: return 'configure';
|
||||
case ISTATES.PENDING_RECREATE_CONTAINER: return 'create container';
|
||||
case ISTATES.PENDING_DEBUG: return 'debug';
|
||||
case ISTATES.PENDING_RESIZE: return 'resize';
|
||||
case ISTATES.PENDING_DATA_DIR_MIGRATION: return 'data migration';
|
||||
case ISTATES.PENDING_UNINSTALL: return 'uninstall';
|
||||
case ISTATES.PENDING_RESTORE: return 'restore';
|
||||
case ISTATES.PENDING_IMPORT: return 'import';
|
||||
case ISTATES.PENDING_UPDATE: return 'update';
|
||||
case ISTATES.PENDING_BACKUP: return 'backup';
|
||||
case ISTATES.PENDING_START: return 'start app';
|
||||
case ISTATES.PENDING_STOP: return 'stop app';
|
||||
case ISTATES.PENDING_RESTART: return 'restart app';
|
||||
default: return installationState || '';
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('errorSuggestion', function () {
|
||||
return function (error) {
|
||||
if (!error) return '';
|
||||
|
||||
switch (error.reason) {
|
||||
case ERROR.ACCESS_DENIED:
|
||||
if (error.domain) return 'Check the DNS credentials of ' + error.domain.domain + ' in the Domains & Certs view';
|
||||
return '';
|
||||
case ERROR.COLLECTD_ERROR: return 'Check if collectd is running on the server';
|
||||
case ERROR.DATABASE_ERROR: return 'Check if MySQL database is running on the server';
|
||||
case ERROR.DOCKER_ERROR: return 'Check if docker is running on the server';
|
||||
case ERROR.DNS_ERROR: return 'Check if the DNS service of the domain is running';
|
||||
case ERROR.LOGROTATE_ERROR: return 'Check if logrotate is running on the server';
|
||||
case ERROR.NETWORK_ERROR: return 'Check if there are any network issues on the server';
|
||||
case ERROR.REVERSEPROXY_ERROR: return 'Check if nginx is running on the server';
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('canUpdate', function () {
|
||||
return function (apps) {
|
||||
return apps.every(function (app) {
|
||||
return (app.installationState === ISTATES.ERROR) || (app.installationState === ISTATES.INSTALLED);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('inProgressApps', function () {
|
||||
return function (apps) {
|
||||
return apps.filter(function (app) {
|
||||
return app.installationState !== ISTATES.ERROR && app.installationState !== ISTATES.INSTALLED;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyHref', function () {
|
||||
return function (input) {
|
||||
if (!input) return input;
|
||||
if (input.indexOf('http://') === 0) return input.slice('http://'.length);
|
||||
if (input.indexOf('https://') === 0) return input.slice('https://'.length);
|
||||
return input;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter('prettyEmailAddresses', function () {
|
||||
return function prettyEmailAddresses(addresses) {
|
||||
if (!addresses) return '';
|
||||
if (addresses === '<>') return '<>';
|
||||
if (Array.isArray(addresses)) return addresses.map(function (a) { return a.slice(1, -1); }).join(', ');
|
||||
return addresses.slice(1, -1);
|
||||
};
|
||||
});
|
||||
|
||||
// custom directive for dynamic names in forms
|
||||
// See http://stackoverflow.com/questions/23616578/issue-registering-form-control-with-interpolated-name#answer-23617401
|
||||
app.directive('laterName', function () { // (2)
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: ['?ngModel', '^?form'], // (3)
|
||||
link: function postLink(scope, elem, attrs, ctrls) {
|
||||
attrs.$set('name', attrs.laterName);
|
||||
|
||||
var modelCtrl = ctrls[0]; // (3)
|
||||
var formCtrl = ctrls[1]; // (3)
|
||||
if (modelCtrl && formCtrl) {
|
||||
modelCtrl.$name = attrs.name; // (4)
|
||||
formCtrl.$addControl(modelCtrl); // (2)
|
||||
scope.$on('$destroy', function () {
|
||||
formCtrl.$removeControl(modelCtrl); // (5)
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
|
||||
var original = $location.path;
|
||||
$location.path = function (path, reload) {
|
||||
if (reload === false) {
|
||||
var lastRoute = $route.current;
|
||||
var un = $rootScope.$on('$locationChangeSuccess', function () {
|
||||
$route.current = lastRoute;
|
||||
un();
|
||||
});
|
||||
}
|
||||
return original.apply($location, [path]);
|
||||
};
|
||||
}]);
|
||||
|
||||
app.directive('ngClickSelect', function () {
|
||||
return {
|
||||
restrict: 'AC',
|
||||
link: function (scope, element/*, attrs */) {
|
||||
element.bind('click', function () {
|
||||
var selection = window.getSelection();
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(this);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// handles various states and triggers a href or configure/repair view
|
||||
// used by attaching to controller $scope
|
||||
// if $scope.appPostInstallConfirm.show(app); exists it will be called if not yet confirmed
|
||||
function onAppClick(app, $event, isOperator, $scope) {
|
||||
function stopEvent() {
|
||||
$event.originalEvent.stopPropagation();
|
||||
$event.originalEvent.preventDefault();
|
||||
}
|
||||
|
||||
if (app.installationState !== ISTATES.INSTALLED) {
|
||||
if (app.installationState === ISTATES.ERROR && isOperator) $scope.showAppConfigure(app, 'repair');
|
||||
return stopEvent();
|
||||
}
|
||||
|
||||
// app.health can also be null to indicate insufficient data
|
||||
if (!app.health) return stopEvent();
|
||||
if (app.runState === RSTATES.STOPPED) return stopEvent();
|
||||
if (app.runState === RSTATES.STOPPED) return stopEvent();
|
||||
|
||||
if (app.health === HSTATES.UNHEALTHY || app.health === HSTATES.ERROR || app.health === HSTATES.DEAD) {
|
||||
if (isOperator) $scope.showAppConfigure(app, 'repair');
|
||||
return stopEvent();
|
||||
}
|
||||
|
||||
if (app.pendingPostInstallConfirmation && $scope.appPostInstallConfirm) {
|
||||
$scope.appPostInstallConfirm.show(app);
|
||||
return stopEvent();
|
||||
}
|
||||
}
|
||||
|
||||
app.directive('ngClickReveal', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs) {
|
||||
element.addClass('hand');
|
||||
|
||||
var value = '';
|
||||
|
||||
scope.$watch(attrs.ngClickReveal, function (newValue, oldValue) {
|
||||
if (newValue !== oldValue) {
|
||||
element.html('<i>hidden</i>');
|
||||
value = newValue;
|
||||
}
|
||||
});
|
||||
|
||||
element.bind('click', function () {
|
||||
element.text(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// https://codepen.io/webmatze/pen/isuHh
|
||||
app.directive('tagInput', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
inputTags: '=taglist'
|
||||
},
|
||||
require: '^form',
|
||||
link: function ($scope, element, attrs, formCtrl) {
|
||||
$scope.defaultWidth = 200;
|
||||
$scope.tagText = ''; // current tag being edited
|
||||
$scope.placeholder = attrs.placeholder;
|
||||
$scope.tagArray = function () {
|
||||
if ($scope.inputTags === undefined) {
|
||||
return [];
|
||||
}
|
||||
return $scope.inputTags.split(' ').filter(function (tag) {
|
||||
return tag !== '';
|
||||
});
|
||||
};
|
||||
$scope.addTag = function () {
|
||||
var tagArray = $scope.tagArray();
|
||||
|
||||
// prevent adding empty or existing items
|
||||
if ($scope.tagText.length === 0 || tagArray.indexOf($scope.tagText) !== -1) {
|
||||
return $scope.tagText = '';
|
||||
}
|
||||
|
||||
tagArray.push($scope.tagText);
|
||||
$scope.inputTags = tagArray.join(' ');
|
||||
return $scope.tagText = '';
|
||||
};
|
||||
$scope.deleteTag = function (key) {
|
||||
var tagArray;
|
||||
tagArray = $scope.tagArray();
|
||||
if (tagArray.length > 0 && $scope.tagText.length === 0 && key === undefined) {
|
||||
tagArray.pop();
|
||||
} else {
|
||||
if (key !== undefined) {
|
||||
tagArray.splice(key, 1);
|
||||
}
|
||||
}
|
||||
formCtrl.$setDirty();
|
||||
return $scope.inputTags = tagArray.join(' ');
|
||||
};
|
||||
$scope.$watch('tagText', function (newVal, oldVal) {
|
||||
var tempEl;
|
||||
if (!(newVal === oldVal && newVal === undefined)) {
|
||||
tempEl = $('<span>' + newVal + '</span>').appendTo('body');
|
||||
$scope.inputWidth = tempEl.width() + 5;
|
||||
if ($scope.inputWidth < $scope.defaultWidth) {
|
||||
$scope.inputWidth = $scope.defaultWidth;
|
||||
}
|
||||
return tempEl.remove();
|
||||
}
|
||||
});
|
||||
element.bind('click', function () {
|
||||
element[0].firstChild.lastChild.focus();
|
||||
});
|
||||
element.bind('keydown', function (e) {
|
||||
var key = e.which;
|
||||
if (key === 9 || key === 13) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (key === 8) {
|
||||
return $scope.$apply('deleteTag()');
|
||||
}
|
||||
});
|
||||
element.bind('keyup', function (e) {
|
||||
var key = e.which;
|
||||
if (key === 9 || key === 13 || key === 32) {
|
||||
e.preventDefault();
|
||||
return $scope.$apply('addTag()');
|
||||
}
|
||||
});
|
||||
},
|
||||
template:
|
||||
'<div class="tag-input-container">' +
|
||||
'<div class="btn-group input-tag" data-ng-repeat="tag in tagArray()">' +
|
||||
'<button type="button" class="btn btn-xs btn-primary" disabled>{{ tag }}</button>' +
|
||||
'<button type="button" class="btn btn-xs btn-primary" data-ng-click="deleteTag($index)">×</button>' +
|
||||
'</div>' +
|
||||
'<input type="text" data-ng-model="tagText" ng-blur="addTag()" placeholder="{{placeholder}}"/>' +
|
||||
'</div>'
|
||||
};
|
||||
});
|
||||
|
||||
app.config(['fitTextConfigProvider', function (fitTextConfigProvider) {
|
||||
fitTextConfigProvider.config = {
|
||||
loadDelay: 250,
|
||||
compressor: 0.9,
|
||||
min: 8,
|
||||
max: 24
|
||||
};
|
||||
}]);
|
||||
|
||||
app.controller('MainController', ['$scope', '$route', '$timeout', '$location', '$interval', 'Notification', 'Client', function ($scope, $route, $timeout, $location, $interval, Notification, Client) {
|
||||
$scope.initialized = false; // used to animate the UI
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.installedApps = Client.getInstalledApps();
|
||||
$scope.config = {};
|
||||
$scope.client = Client;
|
||||
$scope.subscription = {};
|
||||
$scope.notificationCount = 0;
|
||||
$scope.hideNavBarActions = $location.path() === '/logs';
|
||||
$scope.backgroundImageUrl = '';
|
||||
|
||||
$scope.closeNavbar = function () {
|
||||
$('.navbar-collapse').collapse('hide');
|
||||
};
|
||||
|
||||
$scope.reboot = {
|
||||
busy: false,
|
||||
|
||||
show: function () {
|
||||
$scope.reboot.busy = false;
|
||||
$('#rebootModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.reboot.busy = true;
|
||||
|
||||
Client.reboot(function (error) {
|
||||
if (error) return Client.error(error);
|
||||
|
||||
$('#rebootModal').modal('hide');
|
||||
|
||||
// trigger refetch to show offline banner
|
||||
$timeout(function () { Client.getStatus(function () {}); }, 5000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.isActive = function (url) {
|
||||
if (!$route.current) return false;
|
||||
return $route.current.$$route.originalPath.indexOf(url) === 0;
|
||||
};
|
||||
|
||||
$scope.logout = function (event) {
|
||||
event.stopPropagation();
|
||||
$scope.initialized = false;
|
||||
Client.logout();
|
||||
};
|
||||
|
||||
$scope.openSubscriptionSetup = function () {
|
||||
Client.openSubscriptionSetup($scope.subscription);
|
||||
};
|
||||
|
||||
// NOTE: this function is exported and called from the appstore.js
|
||||
$scope.updateSubscriptionStatus = function () {
|
||||
Client.getSubscription(function (error, subscription) {
|
||||
if (error && error.statusCode === 412) return; // not yet registered
|
||||
if (error && error.statusCode === 402) return; // invalid appstore token
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.subscription = subscription;
|
||||
});
|
||||
};
|
||||
|
||||
function refreshNotifications() {
|
||||
if (!Client.getUserInfo().isAtLeastAdmin) return;
|
||||
|
||||
Client.getNotifications({ acknowledged: false }, 1, 100, function (error, results) { // counter maxes out at 100
|
||||
if (error) console.error(error);
|
||||
else $scope.notificationCount = results.length;
|
||||
});
|
||||
}
|
||||
|
||||
// update state of acknowledged notification
|
||||
$scope.notificationAcknowledged = function () {
|
||||
refreshNotifications();
|
||||
};
|
||||
|
||||
function redirectOnMandatory2FA() {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
// Make it redirect if the browser URL is changed directly - https://forum.cloudron.io/topic/7510/bug-in-2fa-force
|
||||
$scope.$on('$routeChangeStart', function (/* event */) {
|
||||
if ($scope.initialized) redirectOnMandatory2FA();
|
||||
});
|
||||
|
||||
var gPlatformStatusNotification = null;
|
||||
function trackPlatformStatus() {
|
||||
Client.getPlatformStatus(function (error, result) {
|
||||
if (error) return console.error('Failed to get platform status.', error);
|
||||
|
||||
// see box/src/platform.js
|
||||
if (result.message === 'Ready') {
|
||||
if (gPlatformStatusNotification) {
|
||||
gPlatformStatusNotification.kill();
|
||||
gPlatformStatusNotification = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gPlatformStatusNotification) {
|
||||
var options = { title: 'Platform status', message: result.message, delay: 'notimeout', replaceMessage: true, closeOnClick: false };
|
||||
|
||||
Notification.primary(options).then(function (result) {
|
||||
gPlatformStatusNotification = result;
|
||||
$timeout(trackPlatformStatus, 5000);
|
||||
});
|
||||
} else {
|
||||
gPlatformStatusNotification.message = result.message;
|
||||
$timeout(trackPlatformStatus, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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, 'dashboard')) return; // we got redirected...
|
||||
|
||||
// check version and force reload if needed
|
||||
if (!localStorage.version) {
|
||||
localStorage.version = status.version;
|
||||
} else if (localStorage.version !== status.version) {
|
||||
localStorage.version = status.version;
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
console.log('Running dashboard version ', localStorage.version);
|
||||
|
||||
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
|
||||
async.series([
|
||||
Client.refreshProfile.bind(Client),
|
||||
Client.refreshConfig.bind(Client),
|
||||
Client.refreshAvailableLanguages.bind(Client),
|
||||
Client.refreshInstalledApps.bind(Client)
|
||||
], function (error) {
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
// now mark the Client to be ready
|
||||
Client.setReady();
|
||||
|
||||
$scope.config = Client.getConfig();
|
||||
|
||||
if (Client.getUserInfo().hasBackgroundImage) {
|
||||
document.getElementById('mainContentContainer').style.backgroundImage = 'url("' + Client.getBackgroundImageUrl() + '")';
|
||||
document.getElementById('mainContentContainer').classList.add('has-background');
|
||||
}
|
||||
|
||||
$scope.initialized = true;
|
||||
|
||||
redirectOnMandatory2FA();
|
||||
|
||||
$interval(refreshNotifications, 60 * 1000);
|
||||
refreshNotifications();
|
||||
|
||||
Client.getSubscription(function (error, subscription) {
|
||||
if (error && error.statusCode === 412) return; // not yet registered
|
||||
if (error && error.statusCode === 402) return; // invalid appstore token
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.subscription = subscription;
|
||||
|
||||
// only track platform status if we are registered
|
||||
trackPlatformStatus();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Client.onConfig(function (config) {
|
||||
if (config.cloudronName) {
|
||||
document.title = config.cloudronName;
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
// setup all the dialog focus handling
|
||||
['updateModal'].forEach(function (id) {
|
||||
$('#' + id).on('shown.bs.modal', function () {
|
||||
$(this).find('[autofocus]:first').focus();
|
||||
});
|
||||
});
|
||||
}]);
|
||||
@@ -1,160 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular, $, showdown */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies']);
|
||||
|
||||
app.filter('markdown2html', function () {
|
||||
var converter = new showdown.Converter({
|
||||
simplifiedAutoLink: true,
|
||||
strikethrough: true,
|
||||
tables: true,
|
||||
openLinksInNewWindow: true
|
||||
});
|
||||
|
||||
return function (text) {
|
||||
return converter.makeHtml(text);
|
||||
};
|
||||
});
|
||||
|
||||
// disable sce for footer https://code.angularjs.org/1.5.8/docs/api/ng/service/$sce
|
||||
app.config(function ($sceProvider) {
|
||||
$sceProvider.enabled(false);
|
||||
});
|
||||
|
||||
app.config(['$translateProvider', function ($translateProvider) {
|
||||
$translateProvider.useStaticFilesLoader({
|
||||
prefix: 'translation/',
|
||||
suffix: '.json'
|
||||
});
|
||||
$translateProvider.preferredLanguage('en');
|
||||
$translateProvider.fallbackLanguage('en');
|
||||
}]);
|
||||
|
||||
// Add shorthand "tr" filter to avoid having ot use "translate"
|
||||
// This is a copy of the code at https://github.com/angular-translate/angular-translate/blob/master/src/filter/translate.js
|
||||
// If we find out how to get that function handle somehow dynamically we can use that, otherwise the copy is required
|
||||
function translateFilterFactory($parse, $translate) {
|
||||
var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) {
|
||||
if (!angular.isObject(interpolateParams)) {
|
||||
var ctx = this || {
|
||||
'__SCOPE_IS_NOT_AVAILABLE': 'More info at https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f'
|
||||
};
|
||||
interpolateParams = $parse(interpolateParams)(ctx);
|
||||
}
|
||||
|
||||
return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage);
|
||||
};
|
||||
|
||||
if ($translate.statefulFilter()) {
|
||||
translateFilter.$stateful = true;
|
||||
}
|
||||
|
||||
return translateFilter;
|
||||
}
|
||||
translateFilterFactory.displayName = 'translateFilterFactory';
|
||||
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
|
||||
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 = '';
|
||||
$scope.busy = false;
|
||||
$scope.error = false;
|
||||
$scope.branding = null;
|
||||
$scope.username = '';
|
||||
$scope.password = '';
|
||||
$scope.totpToken = '';
|
||||
$scope.passwordResetIdentifier = '';
|
||||
$scope.newPassword = '';
|
||||
$scope.newPasswordRepeat = '';
|
||||
|
||||
const API_ORIGIN = window.cloudronApiOrigin || window.location.origin;
|
||||
|
||||
$scope.onPasswordReset = function () {
|
||||
$scope.busy = true;
|
||||
|
||||
var data = {
|
||||
identifier: $scope.passwordResetIdentifier
|
||||
};
|
||||
|
||||
function done(error) {
|
||||
if (error) $scope.error = error.message;
|
||||
$scope.busy = false;
|
||||
$scope.mode = 'passwordResetDone';
|
||||
}
|
||||
|
||||
$http.post(API_ORIGIN + '/api/v1/auth/password_reset_request', data).success(done).error(done);
|
||||
};
|
||||
|
||||
$scope.onNewPassword = function () {
|
||||
$scope.busy = true;
|
||||
|
||||
var data = {
|
||||
resetToken: search.resetToken,
|
||||
password: $scope.newPassword,
|
||||
totpToken: $scope.totpToken
|
||||
};
|
||||
|
||||
function error(data, status) {
|
||||
console.log('error', status);
|
||||
$scope.busy = false;
|
||||
|
||||
if (status === 401) $scope.error = data.message;
|
||||
else if (status === 409) $scope.error = 'Ask your admin for an invite link first';
|
||||
else $scope.error = 'Unknown error';
|
||||
}
|
||||
|
||||
$http.post(API_ORIGIN + '/api/v1/auth/password_reset', data).success(function (data, status) {
|
||||
if (status !== 202) return error(data, status);
|
||||
|
||||
// set token to autologin
|
||||
localStorage.token = data.accessToken;
|
||||
|
||||
$scope.mode = 'newPasswordDone';
|
||||
}).error(function (data, status) {
|
||||
error(data, status);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showPasswordReset = function () {
|
||||
window.document.title = 'Password Reset Request';
|
||||
$scope.mode = 'passwordReset';
|
||||
$scope.passwordResetIdentifier = '';
|
||||
setTimeout(function () { $('#inputPasswordResetIdentifier').focus(); }, 200);
|
||||
};
|
||||
|
||||
$scope.showNewPassword = function () {
|
||||
window.document.title = 'Set New Password';
|
||||
$scope.mode = 'newPassword';
|
||||
setTimeout(function () { $('#inputNewPassword').focus(); }, 200);
|
||||
};
|
||||
|
||||
$http.get(API_ORIGIN + '/api/v1/auth/branding').success(function (data, status) {
|
||||
$scope.initialized = true;
|
||||
|
||||
if (status !== 200) return;
|
||||
|
||||
if (data.language) $translate.use(data.language);
|
||||
|
||||
$scope.branding = data;
|
||||
}).error(function () {
|
||||
$scope.initialized = false;
|
||||
});
|
||||
|
||||
// Init into the correct view
|
||||
if (search.passwordReset) {
|
||||
$scope.showPasswordReset();
|
||||
} else if (search.resetToken) {
|
||||
$scope.showNewPassword();
|
||||
} else if (search.accessToken || search.access_token) { // auto-login feature
|
||||
localStorage.token = search.accessToken || search.access_token;
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
$scope.showPasswordReset();
|
||||
}
|
||||
}]);
|
||||
@@ -1,397 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* 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.controller('RestoreController', ['$scope', 'Client', function ($scope, Client) {
|
||||
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||
|
||||
$scope.client = Client;
|
||||
$scope.busy = false;
|
||||
$scope.error = {};
|
||||
$scope.message = ''; // progress
|
||||
|
||||
// variables here have to match the import config logic!
|
||||
$scope.provider = '';
|
||||
$scope.bucket = '';
|
||||
$scope.prefix = '';
|
||||
$scope.mountPoint = '';
|
||||
$scope.accessKeyId = '';
|
||||
$scope.secretAccessKey = '';
|
||||
$scope.gcsKey = { keyFileName: '', content: '' };
|
||||
$scope.region = '';
|
||||
$scope.endpoint = '';
|
||||
$scope.backupFolder = '';
|
||||
$scope.remotePath = '';
|
||||
$scope.instanceId = '';
|
||||
$scope.acceptSelfSignedCerts = false;
|
||||
$scope.format = 'tgz';
|
||||
$scope.advancedVisible = false;
|
||||
$scope.password = '';
|
||||
$scope.encryptedFilenames = true;
|
||||
$scope.encrypted = false; // only used if a backup config contains that flag
|
||||
$scope.setupToken = '';
|
||||
$scope.skipDnsSetup = false;
|
||||
$scope.disk = null;
|
||||
$scope.blockDevices = [];
|
||||
|
||||
$scope.mountOptions = {
|
||||
host: '',
|
||||
remoteDir: '',
|
||||
username: '',
|
||||
password: '',
|
||||
diskPath: '',
|
||||
user: '',
|
||||
seal: true,
|
||||
port: 22,
|
||||
privateKey: ''
|
||||
};
|
||||
|
||||
$scope.$watch('disk', function (newValue) {
|
||||
if (!newValue) return;
|
||||
$scope.mountOptions.diskPath = '/dev/disk/by-uuid/' + newValue.uuid;
|
||||
});
|
||||
|
||||
$scope.ipv4Config = {
|
||||
provider: 'generic',
|
||||
ip: '',
|
||||
ifname: ''
|
||||
};
|
||||
|
||||
$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.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;
|
||||
$scope.ovhRegions = REGIONS_OVH;
|
||||
$scope.ionosRegions = REGIONS_IONOS;
|
||||
$scope.upcloudRegions = REGIONS_UPCLOUD;
|
||||
$scope.vultrRegions = REGIONS_VULTR;
|
||||
$scope.contaboRegions = REGIONS_CONTABO;
|
||||
|
||||
$scope.storageProviders = STORAGE_PROVIDERS;
|
||||
|
||||
$scope.formats = BACKUP_FORMATS;
|
||||
|
||||
$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 === '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';
|
||||
};
|
||||
|
||||
$scope.mountlike = function (provider) {
|
||||
return provider === 'disk' || provider === 'sshfs' || provider === 'cifs' || provider === 'nfs' || provider === 'mountpoint' || provider === 'ext4' || provider === 'xfs';
|
||||
};
|
||||
|
||||
$scope.restore = function () {
|
||||
$scope.error = {};
|
||||
$scope.busy = true;
|
||||
|
||||
var backupConfig = {
|
||||
provider: $scope.provider,
|
||||
format: $scope.format,
|
||||
};
|
||||
if ($scope.password) {
|
||||
backupConfig.password = $scope.password;
|
||||
backupConfig.encryptedFilenames = $scope.encryptedFilenames;
|
||||
}
|
||||
|
||||
// only set provider specific fields, this will clear them in the db
|
||||
if ($scope.s3like(backupConfig.provider)) {
|
||||
backupConfig.bucket = $scope.bucket;
|
||||
backupConfig.prefix = $scope.prefix;
|
||||
backupConfig.accessKeyId = $scope.accessKeyId;
|
||||
backupConfig.secretAccessKey = $scope.secretAccessKey;
|
||||
|
||||
if ($scope.endpoint) backupConfig.endpoint = $scope.endpoint;
|
||||
|
||||
if (backupConfig.provider === 's3') {
|
||||
if ($scope.region) backupConfig.region = $scope.region;
|
||||
delete backupConfig.endpoint;
|
||||
} else if (backupConfig.provider === 'minio' || backupConfig.provider === 's3-v4-compat') {
|
||||
backupConfig.region = backupConfig.region || 'us-east-1';
|
||||
backupConfig.acceptSelfSignedCerts = $scope.acceptSelfSignedCerts;
|
||||
backupConfig.s3ForcePathStyle = true; // might want to expose this in the UI
|
||||
} else if (backupConfig.provider === 'exoscale-sos') {
|
||||
backupConfig.region = 'us-east-1';
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'wasabi') {
|
||||
backupConfig.region = $scope.wasabiRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'scaleway-objectstorage') {
|
||||
backupConfig.region = $scope.scalewayRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'linode-objectstorage') {
|
||||
backupConfig.region = $scope.linodeRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'ovh-objectstorage') {
|
||||
backupConfig.region = $scope.ovhRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'ionos-objectstorage') {
|
||||
backupConfig.region = $scope.ionosRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'vultr-objectstorage') {
|
||||
backupConfig.region = $scope.vultrRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
} else if (backupConfig.provider === 'contabo-objectstorage') {
|
||||
backupConfig.region = $scope.contaboRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
|
||||
backupConfig.signatureVersion = 'v4';
|
||||
backupConfig.s3ForcePathStyle = true; // https://docs.contabo.com/docs/products/Object-Storage/technical-description (no virtual buckets)
|
||||
} else if (backupConfig.provider === 'upcloud-objectstorage') {
|
||||
var m = /^.*\.(.*)\.upcloudobjects.com$/.exec(backupConfig.endpoint);
|
||||
backupConfig.region = m ? m[1] : 'us-east-1'; // let it fail in validation phase if m is not valid
|
||||
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;
|
||||
backupConfig.prefix = $scope.prefix;
|
||||
try {
|
||||
var serviceAccountKey = JSON.parse($scope.gcsKey.content);
|
||||
backupConfig.projectId = serviceAccountKey.project_id;
|
||||
backupConfig.credentials = {
|
||||
client_email: serviceAccountKey.client_email,
|
||||
private_key: serviceAccountKey.private_key
|
||||
};
|
||||
|
||||
if (!backupConfig.projectId || !backupConfig.credentials || !backupConfig.credentials.client_email || !backupConfig.credentials.private_key) {
|
||||
throw 'fields_missing';
|
||||
}
|
||||
} catch (e) {
|
||||
$scope.error.generic = 'Cannot parse Google Service Account Key: ' + e.message;
|
||||
$scope.error.gcsKeyInput = true;
|
||||
$scope.busy = false;
|
||||
return;
|
||||
}
|
||||
} else if ($scope.mountlike(backupConfig.provider)) {
|
||||
backupConfig.prefix = $scope.prefix;
|
||||
backupConfig.mountOptions = {};
|
||||
|
||||
if (backupConfig.provider === 'cifs' || backupConfig.provider === 'sshfs' || backupConfig.provider === 'nfs') {
|
||||
backupConfig.mountOptions.host = $scope.mountOptions.host;
|
||||
backupConfig.mountOptions.remoteDir = $scope.mountOptions.remoteDir;
|
||||
|
||||
if (backupConfig.provider === 'cifs') {
|
||||
backupConfig.mountOptions.username = $scope.mountOptions.username;
|
||||
backupConfig.mountOptions.password = $scope.mountOptions.password;
|
||||
backupConfig.mountOptions.seal = $scope.mountOptions.seal;
|
||||
} else if (backupConfig.provider === 'sshfs') {
|
||||
backupConfig.mountOptions.user = $scope.mountOptions.user;
|
||||
backupConfig.mountOptions.port = $scope.mountOptions.port;
|
||||
backupConfig.mountOptions.privateKey = $scope.mountOptions.privateKey;
|
||||
}
|
||||
} else if (backupConfig.provider === 'disk' || backupConfig.provider === 'ext4' || backupConfig.provider === 'xfs') {
|
||||
backupConfig.mountOptions.diskPath = $scope.mountOptions.diskPath;
|
||||
} else if (backupConfig.provider === 'mountpoint') {
|
||||
backupConfig.mountPoint = $scope.mountPoint;
|
||||
}
|
||||
} else if (backupConfig.provider === 'filesystem') {
|
||||
backupConfig.backupFolder = $scope.backupFolder;
|
||||
}
|
||||
|
||||
if ($scope.remotePath.indexOf('/') === -1) {
|
||||
$scope.error.generic = 'Backup id must include the directory path';
|
||||
$scope.error.remotePath = true;
|
||||
$scope.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.remotePath.indexOf('box') === -1) {
|
||||
$scope.error.generic = 'Backup id must contain "box"';
|
||||
$scope.error.remotePath = true;
|
||||
$scope.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var version = $scope.remotePath.match(/_v(\d+.\d+.\d+)/);
|
||||
if (!version) {
|
||||
$scope.error.generic = 'Backup id is missing version information';
|
||||
$scope.error.remotePath = true;
|
||||
$scope.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
Client.restore(data, function (error) {
|
||||
$scope.busy = false;
|
||||
|
||||
if (error) {
|
||||
if (error.statusCode === 424) {
|
||||
$scope.error.generic = error.message;
|
||||
|
||||
if (error.message.indexOf('AWS Access Key Id') !== -1) {
|
||||
$scope.error.accessKeyId = true;
|
||||
$scope.accessKeyId = '';
|
||||
$scope.configureBackupForm.accessKeyId.$setPristine();
|
||||
$('#inputConfigureBackupAccessKeyId').focus();
|
||||
} else if (error.message.indexOf('not match the signature') !== -1 ) {
|
||||
$scope.error.secretAccessKey = true;
|
||||
$scope.secretAccessKey = '';
|
||||
$scope.configureBackupForm.secretAccessKey.$setPristine();
|
||||
$('#inputConfigureBackupSecretAccessKey').focus();
|
||||
} else if (error.message.toLowerCase() === 'access denied') {
|
||||
$scope.error.bucket = true;
|
||||
$scope.bucket = '';
|
||||
$scope.configureBackupForm.bucket.$setPristine();
|
||||
$('#inputConfigureBackupBucket').focus();
|
||||
} else if (error.message.indexOf('ECONNREFUSED') !== -1) {
|
||||
$scope.error.generic = 'Unknown region';
|
||||
$scope.error.region = true;
|
||||
$scope.configureBackupForm.region.$setPristine();
|
||||
$('#inputConfigureBackupDORegion').focus();
|
||||
} else if (error.message.toLowerCase() === 'wrong region') {
|
||||
$scope.error.generic = 'Wrong S3 Region';
|
||||
$scope.error.region = true;
|
||||
$scope.configureBackupForm.region.$setPristine();
|
||||
$('#inputConfigureBackupS3Region').focus();
|
||||
} else {
|
||||
$('#inputConfigureBackupBucket').focus();
|
||||
}
|
||||
} else {
|
||||
$scope.error.generic = error.message;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
waitForRestore();
|
||||
});
|
||||
};
|
||||
|
||||
function waitForRestore() {
|
||||
$scope.busy = true;
|
||||
|
||||
Client.getProvisionStatus(function (error, status) {
|
||||
if (!error && !status.restore.active) { // restore finished
|
||||
if (status.restore.errorMessage) {
|
||||
$scope.busy = false;
|
||||
$scope.error.generic = status.restore.errorMessage;
|
||||
} else { // restore worked, redirect to admin page
|
||||
window.location.href = 'https://' + status.adminFqdn + '/';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!error) $scope.message = status.restore.message;
|
||||
|
||||
setTimeout(waitForRestore, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
function readFileLocally(obj, file, fileName) {
|
||||
return function (event) {
|
||||
$scope.$apply(function () {
|
||||
obj[file] = null;
|
||||
obj[fileName] = event.target.files[0].name;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (result) {
|
||||
if (!result.target || !result.target.result) return console.error('Unable to read local file');
|
||||
obj[file] = result.target.result;
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById('gcsKeyFileInput').onchange = readFileLocally($scope.gcsKey, 'content', 'keyFileName');
|
||||
|
||||
document.getElementById('backupConfigFileInput').onchange = function (event) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (result) {
|
||||
if (!result.target || !result.target.result) return console.error('Unable to read backup config');
|
||||
|
||||
var backupConfig;
|
||||
try {
|
||||
backupConfig = JSON.parse(result.target.result);
|
||||
} catch (e) {
|
||||
console.error('Unable to parse backup config');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.$apply(function () {
|
||||
// we assume property names match here, this does not yet work for gcs keys
|
||||
Object.keys(backupConfig).forEach(function (k) {
|
||||
if (k in $scope) $scope[k] = backupConfig[k];
|
||||
});
|
||||
|
||||
// this allows the config to potentially have a raw password (though our UI sets it to placeholder)
|
||||
if ($scope.mountOptions.password === SECRET_PLACEHOLDER) $scope.mountOptions.password = '';
|
||||
});
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
};
|
||||
|
||||
function init() {
|
||||
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; // any previous restore error
|
||||
|
||||
Client.getProvisionBlockDevices(function (error, result) {
|
||||
if (error) {
|
||||
console.error('Failed to list blockdevices:', error);
|
||||
} else {
|
||||
// only offer non /, /boot or /home disks
|
||||
result = result.filter(function (d) { return d.mountpoint !== '/' && d.mountpoint !== '/home' && d.mountpoint !== '/boot'; });
|
||||
// only offer xfs and ext4 disks
|
||||
result = result.filter(function (d) { return d.type === 'xfs' || d.type === 'ext4'; });
|
||||
|
||||
// amend label for UI
|
||||
result.forEach(function (d) { d.label = d.path; });
|
||||
}
|
||||
|
||||
$scope.blockDevices = result;
|
||||
|
||||
$scope.instanceId = search.instanceId;
|
||||
$scope.setupToken = search.setupToken;
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
}]);
|
||||
@@ -1,344 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* 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.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; }, {});
|
||||
|
||||
$scope.state = null; // 'initialized', 'waitingForDnsSetup', 'waitingForBox'
|
||||
$scope.error = {};
|
||||
$scope.provider = '';
|
||||
$scope.showDNSSetup = false;
|
||||
$scope.instanceId = '';
|
||||
$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' },
|
||||
{ name: 'Let\'s Encrypt Prod - Wildcard', value: 'letsencrypt-prod-wildcard' },
|
||||
{ name: 'Let\'s Encrypt Staging', value: 'letsencrypt-staging' },
|
||||
{ name: 'Let\'s Encrypt Staging - Wildcard', value: 'letsencrypt-staging-wildcard' },
|
||||
{ name: 'Self-Signed', value: 'fallback' }, // this is not 'Custom' because we don't allow user to upload certs during setup phase
|
||||
];
|
||||
|
||||
$scope.ipv4Config = {
|
||||
provider: 'generic',
|
||||
ip: '',
|
||||
ifname: ''
|
||||
};
|
||||
|
||||
$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.ovhEndpoints = ENDPOINTS_OVH;
|
||||
|
||||
$scope.needsPort80 = function (dnsProvider, tlsProvider) {
|
||||
return ((dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') &&
|
||||
(tlsProvider === 'letsencrypt-prod' || tlsProvider === 'letsencrypt-staging'));
|
||||
};
|
||||
|
||||
// If we migrate the api origin we have to poll the new location
|
||||
if (search.admin_fqdn) Client.apiOrigin = 'https://' + search.admin_fqdn;
|
||||
|
||||
// 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' },
|
||||
{ name: 'GoDaddy', value: 'godaddy' },
|
||||
{ name: 'Google Cloud DNS', value: 'gcdns' },
|
||||
{ name: 'Hetzner', value: 'hetzner' },
|
||||
{ name: 'INWX', value: 'inwx' },
|
||||
{ name: 'Linode', value: 'linode' },
|
||||
{ name: 'Name.com', value: 'namecom' },
|
||||
{ name: 'Namecheap', value: 'namecheap' },
|
||||
{ name: 'Netcup', value: 'netcup' },
|
||||
{ name: 'OVH', value: 'ovh' },
|
||||
{ name: 'Porkbun', value: 'porkbun' },
|
||||
{ name: 'Vultr', value: 'vultr' },
|
||||
{ name: 'Wildcard', value: 'wildcard' },
|
||||
{ name: 'Manual (not recommended)', value: 'manual' },
|
||||
{ name: 'No-op (only for development)', value: 'noop' }
|
||||
];
|
||||
$scope.dnsCredentials = {
|
||||
busy: false,
|
||||
domain: '',
|
||||
accessKeyId: '',
|
||||
secretAccessKey: '',
|
||||
gcdnsKey: { keyFileName: '', content: '' },
|
||||
digitalOceanToken: '',
|
||||
gandiApiKey: '',
|
||||
gandiTokenType: 'PAT',
|
||||
cloudflareEmail: '',
|
||||
cloudflareToken: '',
|
||||
cloudflareTokenType: 'GlobalApiKey',
|
||||
cloudflareDefaultProxyStatus: false,
|
||||
godaddyApiKey: '',
|
||||
godaddyApiSecret: '',
|
||||
linodeToken: '',
|
||||
bunnyAccessKey: '',
|
||||
dnsimpleAccessToken: '',
|
||||
hetznerToken: '',
|
||||
inwxUsername: '',
|
||||
inwxPassword: '',
|
||||
vultrToken: '',
|
||||
deSecToken: '',
|
||||
nameComUsername: '',
|
||||
nameComToken: '',
|
||||
namecheapUsername: '',
|
||||
namecheapApiKey: '',
|
||||
netcupCustomerNumber: '',
|
||||
netcupApiKey: '',
|
||||
netcupApiPassword: '',
|
||||
ovhEndpoint: 'ovh-eu',
|
||||
ovhConsumerKey: '',
|
||||
ovhAppKey: '',
|
||||
ovhAppSecret: '',
|
||||
porkbunSecretapikey: '',
|
||||
porkbunApikey: '',
|
||||
|
||||
provider: 'route53',
|
||||
zoneName: '',
|
||||
tlsConfig: {
|
||||
provider: 'letsencrypt-prod-wildcard'
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setDefaultTlsProvider = function () {
|
||||
var dnsProvider = $scope.dnsCredentials.provider;
|
||||
// wildcard LE won't work without automated DNS
|
||||
if (dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') {
|
||||
$scope.dnsCredentials.tlsConfig.provider = 'letsencrypt-prod';
|
||||
} else {
|
||||
$scope.dnsCredentials.tlsConfig.provider = 'letsencrypt-prod-wildcard';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function readFileLocally(obj, file, fileName) {
|
||||
return function (event) {
|
||||
$scope.$apply(function () {
|
||||
obj[file] = null;
|
||||
obj[fileName] = event.target.files[0].name;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (result) {
|
||||
if (!result.target || !result.target.result) return console.error('Unable to read local file');
|
||||
obj[file] = result.target.result;
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById('gcdnsKeyFileInput').onchange = readFileLocally($scope.dnsCredentials.gcdnsKey, 'content', 'keyFileName');
|
||||
|
||||
$scope.setDnsCredentials = function () {
|
||||
$scope.dnsCredentials.busy = true;
|
||||
$scope.error = {};
|
||||
|
||||
var provider = $scope.dnsCredentials.provider;
|
||||
|
||||
var config = {};
|
||||
|
||||
if (provider === 'route53') {
|
||||
config.accessKeyId = $scope.dnsCredentials.accessKeyId;
|
||||
config.secretAccessKey = $scope.dnsCredentials.secretAccessKey;
|
||||
} else if (provider === 'gcdns') {
|
||||
try {
|
||||
var serviceAccountKey = JSON.parse($scope.dnsCredentials.gcdnsKey.content);
|
||||
config.projectId = serviceAccountKey.project_id;
|
||||
config.credentials = {
|
||||
client_email: serviceAccountKey.client_email,
|
||||
private_key: serviceAccountKey.private_key
|
||||
};
|
||||
|
||||
if (!config.projectId || !config.credentials || !config.credentials.client_email || !config.credentials.private_key) {
|
||||
throw new Error('One or more fields are missing in the JSON');
|
||||
}
|
||||
} catch (e) {
|
||||
$scope.error.dnsCredentials = 'Cannot parse Google Service Account Key: ' + e.message;
|
||||
$scope.dnsCredentials.busy = false;
|
||||
return;
|
||||
}
|
||||
} else if (provider === 'digitalocean') {
|
||||
config.token = $scope.dnsCredentials.digitalOceanToken;
|
||||
} else if (provider === 'gandi') {
|
||||
config.token = $scope.dnsCredentials.gandiApiKey;
|
||||
config.tokenType = $scope.dnsCredentials.gandiTokenType;
|
||||
} else if (provider === 'godaddy') {
|
||||
config.apiKey = $scope.dnsCredentials.godaddyApiKey;
|
||||
config.apiSecret = $scope.dnsCredentials.godaddyApiSecret;
|
||||
} else if (provider === 'cloudflare') {
|
||||
config.email = $scope.dnsCredentials.cloudflareEmail;
|
||||
config.token = $scope.dnsCredentials.cloudflareToken;
|
||||
config.tokenType = $scope.dnsCredentials.cloudflareTokenType;
|
||||
config.defaultProxyStatus = $scope.dnsCredentials.cloudflareDefaultProxyStatus;
|
||||
} else if (provider === 'linode') {
|
||||
config.token = $scope.dnsCredentials.linodeToken;
|
||||
} else if (provider === 'bunny') {
|
||||
config.accessKey = $scope.dnsCredentials.bunnyAccessKey;
|
||||
} else if (provider === 'dnsimple') {
|
||||
config.accessToken = $scope.dnsCredentials.dnsimpleAccessToken;
|
||||
} else if (provider === 'hetzner') {
|
||||
config.token = $scope.dnsCredentials.hetznerToken;
|
||||
} else if (provider === 'inwx') {
|
||||
config.username = $scope.dnsCredentials.inwxUsername;
|
||||
config.password = $scope.dnsCredentials.inwxPassword;
|
||||
} 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;
|
||||
} else if (provider === 'namecheap') {
|
||||
config.token = $scope.dnsCredentials.namecheapApiKey;
|
||||
config.username = $scope.dnsCredentials.namecheapUsername;
|
||||
} else if (provider === 'netcup') {
|
||||
config.customerNumber = $scope.dnsCredentials.netcupCustomerNumber;
|
||||
config.apiKey = $scope.dnsCredentials.netcupApiKey;
|
||||
config.apiPassword = $scope.dnsCredentials.netcupApiPassword;
|
||||
} else if (provider === 'ovh') {
|
||||
config.endpoint = $scope.dnsCredentials.ovhEndpoint;
|
||||
config.consumerKey = $scope.dnsCredentials.ovhConsumerKey;
|
||||
config.appKey = $scope.dnsCredentials.ovhAppKey;
|
||||
config.appSecret = $scope.dnsCredentials.ovhAppSecret;
|
||||
} else if (provider === 'porkbun') {
|
||||
config.apikey = $scope.dnsCredentials.porkbunApikey;
|
||||
config.secretapikey = $scope.dnsCredentials.porkbunSecretapikey;
|
||||
}
|
||||
|
||||
var tlsConfig = {
|
||||
provider: $scope.dnsCredentials.tlsConfig.provider,
|
||||
wildcard: false
|
||||
};
|
||||
if ($scope.dnsCredentials.tlsConfig.provider.indexOf('-wildcard') !== -1) {
|
||||
tlsConfig.provider = tlsConfig.provider.replace('-wildcard', '');
|
||||
tlsConfig.wildcard = true;
|
||||
}
|
||||
|
||||
var data = {
|
||||
domainConfig: {
|
||||
domain: $scope.dnsCredentials.domain,
|
||||
zoneName: $scope.dnsCredentials.zoneName,
|
||||
provider: provider,
|
||||
config: config,
|
||||
tlsConfig: tlsConfig
|
||||
},
|
||||
ipv4Config: $scope.ipv4Config,
|
||||
ipv6Config: $scope.ipv6Config,
|
||||
providerToken: $scope.instanceId,
|
||||
setupToken: $scope.setupToken
|
||||
};
|
||||
|
||||
Client.setup(data, function (error) {
|
||||
if (error) {
|
||||
$scope.dnsCredentials.busy = false;
|
||||
if (error.statusCode === 422) {
|
||||
if (provider === 'ami') {
|
||||
$scope.error.ami = error.message;
|
||||
} else {
|
||||
$scope.error.setup = error.message;
|
||||
}
|
||||
} else {
|
||||
$scope.error.dnsCredentials = error.message;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
waitForDnsSetup();
|
||||
});
|
||||
};
|
||||
|
||||
function waitForDnsSetup() {
|
||||
$scope.state = 'waitingForDnsSetup';
|
||||
|
||||
Client.getProvisionStatus(function (error, status) {
|
||||
if (!error && !status.setup.active) {
|
||||
if (!status.adminFqdn || status.setup.errorMessage) { // setup reset or errored. start over
|
||||
$scope.error.setup = status.setup.errorMessage;
|
||||
$scope.state = 'initialized';
|
||||
$scope.dnsCredentials.busy = false;
|
||||
} else { // proceed to activation
|
||||
window.location.href = 'https://' + status.adminFqdn + '/activation.html' + (window.location.search);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
$scope.message = status.setup.message;
|
||||
$scope.taskMinutesActive = (new Date() - new Date(status.setup.startTime)) / 60000;
|
||||
}
|
||||
|
||||
setTimeout(waitForDnsSetup, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
Client.getProvisionStatus(function (error, status) {
|
||||
$scope.state = 'waitingForBox';
|
||||
if (error) return Client.initError(error, init);
|
||||
|
||||
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';
|
||||
} else if (status.provider === 'linode' || status.provider === 'linode-oneclick' || status.provider === 'linode-stackscript') {
|
||||
$scope.dnsCredentials.provider = 'linode';
|
||||
} else if (status.provider === 'vultr' || status.provider === 'vultr-mp') {
|
||||
$scope.dnsCredentials.provider = 'vultr';
|
||||
} else if (status.provider === 'gce') {
|
||||
$scope.dnsCredentials.provider = 'gcdns';
|
||||
} else if (status.provider === 'ami') {
|
||||
// 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;
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var clipboard = new Clipboard('.clipboard');
|
||||
clipboard.on('success', function () {
|
||||
$scope.$apply(function () { $scope.clipboardDone = true; });
|
||||
$timeout(function () { $scope.clipboardDone = false; }, 5000);
|
||||
});
|
||||
|
||||
init();
|
||||
}]);
|
||||
@@ -1,151 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular, $, showdown */
|
||||
|
||||
// create main application module
|
||||
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies']);
|
||||
|
||||
app.filter('markdown2html', function () {
|
||||
var converter = new showdown.Converter({
|
||||
simplifiedAutoLink: true,
|
||||
strikethrough: true,
|
||||
tables: true,
|
||||
openLinksInNewWindow: true
|
||||
});
|
||||
|
||||
return function (text) {
|
||||
return converter.makeHtml(text);
|
||||
};
|
||||
});
|
||||
|
||||
// disable sce for footer https://code.angularjs.org/1.5.8/docs/api/ng/service/$sce
|
||||
app.config(function ($sceProvider) {
|
||||
$sceProvider.enabled(false);
|
||||
});
|
||||
|
||||
app.config(['$translateProvider', function ($translateProvider) {
|
||||
$translateProvider.useStaticFilesLoader({
|
||||
prefix: 'translation/',
|
||||
suffix: '.json'
|
||||
});
|
||||
$translateProvider.useLocalStorage();
|
||||
$translateProvider.preferredLanguage('en');
|
||||
$translateProvider.fallbackLanguage('en');
|
||||
}]);
|
||||
|
||||
// Add shorthand "tr" filter to avoid having ot use "translate"
|
||||
// This is a copy of the code at https://github.com/angular-translate/angular-translate/blob/master/src/filter/translate.js
|
||||
// If we find out how to get that function handle somehow dynamically we can use that, otherwise the copy is required
|
||||
function translateFilterFactory($parse, $translate) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) {
|
||||
if (!angular.isObject(interpolateParams)) {
|
||||
var ctx = this || {
|
||||
'__SCOPE_IS_NOT_AVAILABLE': 'More info at https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f'
|
||||
};
|
||||
interpolateParams = $parse(interpolateParams)(ctx);
|
||||
}
|
||||
|
||||
return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage);
|
||||
};
|
||||
|
||||
if ($translate.statefulFilter()) {
|
||||
translateFilter.$stateful = true;
|
||||
}
|
||||
|
||||
return translateFilter;
|
||||
}
|
||||
translateFilterFactory.displayName = 'translateFilterFactory';
|
||||
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
|
||||
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; }, {});
|
||||
|
||||
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;
|
||||
$scope.username = search.username || '';
|
||||
$scope.displayName = search.displayName || '';
|
||||
$scope.password = '';
|
||||
$scope.passwordRepeat = '';
|
||||
|
||||
$scope.onSubmit = function () {
|
||||
$scope.busy = true;
|
||||
$scope.error = null;
|
||||
|
||||
var data = {
|
||||
inviteToken: search.inviteToken,
|
||||
password: $scope.password
|
||||
};
|
||||
|
||||
if (!$scope.profileLocked) {
|
||||
if (!$scope.existingUsername) data.username = $scope.username;
|
||||
data.displayName = $scope.displayName;
|
||||
}
|
||||
|
||||
function error(data, status) {
|
||||
$scope.busy = false;
|
||||
|
||||
if (status === 401) {
|
||||
$scope.view = 'invalidToken';
|
||||
} else if (status === 409) {
|
||||
$scope.error = {
|
||||
username: true,
|
||||
message: 'Username already taken'
|
||||
};
|
||||
$scope.setupAccountForm.username.$setPristine();
|
||||
setTimeout(function () { $('#inputUsername').focus(); }, 200);
|
||||
} else if (status === 400) {
|
||||
$scope.error = {
|
||||
message: data.message
|
||||
};
|
||||
if (data.message.indexOf('Username') === 0) {
|
||||
$scope.setupAccountForm.username.$setPristine();
|
||||
$scope.error.username = true;
|
||||
}
|
||||
} else {
|
||||
$scope.error = { message: 'Unknown error. Please try again later.' };
|
||||
console.error(status, data);
|
||||
}
|
||||
}
|
||||
|
||||
$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 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);
|
||||
};
|
||||
|
||||
if (!$scope.existingUsername && $scope.profileLocked) {
|
||||
$scope.view = 'noUsername';
|
||||
$scope.initialized = true;
|
||||
} else {
|
||||
$http.get(API_ORIGIN + '/api/v1/auth/branding').success(function (data, status) {
|
||||
$scope.initialized = true;
|
||||
|
||||
if (status !== 200) return;
|
||||
|
||||
if (data.language) $translate.use(data.language);
|
||||
|
||||
$scope.branding = data;
|
||||
}).error(function () {
|
||||
$scope.initialized = false;
|
||||
});
|
||||
}
|
||||
}]);
|
||||
@@ -1,42 +0,0 @@
|
||||
/* 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>';
|
||||
|
||||
return {
|
||||
link: function (scope, elements) {
|
||||
var element = elements[0];
|
||||
|
||||
if (!element.parentNode) {
|
||||
console.error('Wrong password-reveal directive usage. Element has no parent.');
|
||||
return;
|
||||
}
|
||||
|
||||
var eye = document.createElement('i');
|
||||
eye.innerHTML = svgEyeSlash;
|
||||
eye.style.width = '18px';
|
||||
eye.style.height = '18px';
|
||||
eye.style.position = 'relative';
|
||||
eye.style.float = 'right';
|
||||
eye.style.marginTop = '-24px';
|
||||
eye.style.marginRight = '10px';
|
||||
eye.style.cursor = 'pointer';
|
||||
|
||||
eye.addEventListener('click', function () {
|
||||
if (element.type === 'password') {
|
||||
element.type = 'text';
|
||||
eye.innerHTML = svgEye;
|
||||
} else {
|
||||
element.type = 'password';
|
||||
eye.innerHTML = svgEyeSlash;
|
||||
}
|
||||
});
|
||||
|
||||
element.parentNode.style.position = 'relative';
|
||||
element.parentNode.insertBefore(eye, element.nextSibling);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
@charset "UTF-8";
|
||||
/* Slider */
|
||||
.slick-slider { position: relative; display: block; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -ms-touch-action: pan-y; touch-action: pan-y; -webkit-tap-highlight-color: transparent; }
|
||||
|
||||
.slick-list { position: relative; overflow: hidden; display: block; margin: 0; padding: 0; }
|
||||
.slick-list:focus { outline: none; }
|
||||
.slick-loading .slick-list { background: #fff url("./ajax-loader.gif") center center no-repeat; }
|
||||
.slick-list.dragging { cursor: pointer; cursor: hand; }
|
||||
|
||||
.slick-slider .slick-track { -webkit-transform: translate3d(0, 0, 0); -moz-transform: translate3d(0, 0, 0); -ms-transform: translate3d(0, 0, 0); -o-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); }
|
||||
|
||||
.slick-track { position: relative; left: 0; top: 0; display: block; }
|
||||
.slick-track:before, .slick-track:after { content: ""; display: table; }
|
||||
.slick-track:after { clear: both; }
|
||||
.slick-loading .slick-track { visibility: hidden; }
|
||||
|
||||
.slick-slide { float: left; height: 100%; min-height: 1px; display: none; }
|
||||
[dir="rtl"] .slick-slide { float: right; }
|
||||
.slick-slide img { display: block; }
|
||||
.slick-slide.slick-loading img { display: none; }
|
||||
.slick-slide.dragging img { pointer-events: none; }
|
||||
.slick-initialized .slick-slide { display: block; }
|
||||
.slick-loading .slick-slide { visibility: hidden; }
|
||||
.slick-vertical .slick-slide { display: block; height: auto; border: 1px solid transparent; }
|
||||
|
||||
/* Icons */
|
||||
@font-face { font-family: "slick"; src: url("./fonts/slick.eot"); src: url("./fonts/slick.eot?#iefix") format("embedded-opentype"), url("./fonts/slick.woff") format("woff"), url("./fonts/slick.ttf") format("truetype"), url("./fonts/slick.svg#slick") format("svg"); font-weight: normal; font-style: normal; }
|
||||
/* Arrows */
|
||||
.slick-prev, .slick-next { position: absolute; display: block; height: 20px; width: 20px; line-height: 0; font-size: 0; cursor: pointer; background: transparent; color: transparent; top: 50%; margin-top: -10px; padding: 0; border: none; outline: none; }
|
||||
.slick-prev:hover, .slick-prev:focus, .slick-next:hover, .slick-next:focus { outline: none; background: transparent; color: transparent; }
|
||||
.slick-prev:hover:before, .slick-prev:focus:before, .slick-next:hover:before, .slick-next:focus:before { opacity: 1; }
|
||||
.slick-prev.slick-disabled:before, .slick-next.slick-disabled:before { opacity: 0.25; }
|
||||
|
||||
.slick-prev:before, .slick-next:before { font-family: "slick"; font-size: 20px; line-height: 1; color: white; opacity: 0.75; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
||||
|
||||
.slick-prev { left: -25px; }
|
||||
[dir="rtl"] .slick-prev { left: auto; right: -25px; }
|
||||
.slick-prev:before { content: "←"; }
|
||||
[dir="rtl"] .slick-prev:before { content: "→"; }
|
||||
|
||||
.slick-next { right: -25px; }
|
||||
[dir="rtl"] .slick-next { left: -25px; right: auto; }
|
||||
.slick-next:before { content: "→"; }
|
||||
[dir="rtl"] .slick-next:before { content: "←"; }
|
||||
|
||||
/* Dots */
|
||||
.slick-slider { margin-bottom: 30px; }
|
||||
|
||||
.slick-dots { position: absolute; bottom: -45px; list-style: none; display: block; text-align: center; padding: 0; width: 100%; }
|
||||
.slick-dots li { position: relative; display: inline-block; height: 20px; width: 20px; margin: 0 5px; padding: 0; cursor: pointer; }
|
||||
.slick-dots li button { border: 0; background: transparent; display: block; height: 20px; width: 20px; outline: none; line-height: 0; font-size: 0; color: transparent; padding: 5px; cursor: pointer; }
|
||||
.slick-dots li button:hover, .slick-dots li button:focus { outline: none; }
|
||||
.slick-dots li button:hover:before, .slick-dots li button:focus:before { opacity: 1; }
|
||||
.slick-dots li button:before { position: absolute; top: 0; left: 0; content: "•"; width: 20px; height: 20px; font-family: "slick"; font-size: 6px; line-height: 20px; text-align: center; color: black; opacity: 0.25; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
||||
.slick-dots li.slick-active button:before { color: black; opacity: 0.75; }
|
||||
|
||||
/*# sourceMappingURL=slick.css.map */
|
||||
@@ -1,12 +1,6 @@
|
||||
{
|
||||
"apps": {
|
||||
"tagsFilterHeaderAll": "Semua Tag",
|
||||
"adminPageActionTooltip": "Halaman Admin",
|
||||
"domainsFilterHeader": "Semua Domain",
|
||||
"groupsFilterHeader": "Semua Grup",
|
||||
"addAppAction": "Tambah Aplikasi",
|
||||
"title": "Aplikasi Saya",
|
||||
"tagsFilterHeader": "Tag: {{ tags }}"
|
||||
"title": "Aplikasi Saya"
|
||||
},
|
||||
"main": {
|
||||
"dialog": {
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
"rebootDialog": {
|
||||
"title": "本当にサーバーを再起動しますか?"
|
||||
},
|
||||
"clipboard": {
|
||||
"clickToCopyBackupId": "バックアップIDをクリックしてコピー",
|
||||
"clickToCopy": "クリックしてコピー",
|
||||
"copied": "クリップボードにコピーしました"
|
||||
},
|
||||
"action": {
|
||||
"logs": "ログ",
|
||||
"reboot": "再起動"
|
||||
@@ -15,10 +10,6 @@
|
||||
"table": {
|
||||
"date": "日付"
|
||||
},
|
||||
"pagination": {
|
||||
"next": "次",
|
||||
"prev": "前"
|
||||
},
|
||||
"displayName": "表示名",
|
||||
"username": "ユーザー名",
|
||||
"dialog": {
|
||||
@@ -32,14 +23,7 @@
|
||||
"offline": "Cloudronはオフラインです。再接続中…"
|
||||
},
|
||||
"apps": {
|
||||
"tagsFilterHeaderAll": "タグ一覧",
|
||||
"domainsFilterHeader": "ドメイン一覧",
|
||||
"tagsFilterHeader": "タグ: {{ tags }}",
|
||||
"searchPlaceholder": "アプリを探す",
|
||||
"adminPageActionTooltip": "管理者ページ",
|
||||
"infoActionTooltip": "情報",
|
||||
"logsActionTooltip": "ログ",
|
||||
"configActionTooltip": "設定",
|
||||
"noAccess": {
|
||||
"description": "アクセス権のあるアプリは、ここにに表示されます。",
|
||||
"title": "アプリへのアクセス権がありません。"
|
||||
|
||||
@@ -15,33 +15,11 @@
|
||||
"userManagementNone": "Ta aplikacja posiada własne zarządzanie użytkownikami.",
|
||||
"userManagement": "Zarządanie użytkownikami",
|
||||
"manualWarning": "Manualnie dodaj rekord A dla <b>{{ location }}</b> do publicznego IP tego Cloudrona",
|
||||
"configuredForCloudronEmail": "Ta aplikacja jest przygotowana aby używała <a href=\"{{ emailDocsLink }}\" target=\"_blank\">Email Cloudrona</a>.",
|
||||
"lowOnResources": "Ten Cloudron jest blisko wyczerpania dostępnych zasobów."
|
||||
"configuredForCloudronEmail": "Ta aplikacja jest przygotowana aby używała <a href=\"{{ emailDocsLink }}\" target=\"_blank\">Email Cloudrona</a>."
|
||||
},
|
||||
"unstable": "Niestabilne",
|
||||
"appMissing": "Szukasz innej aplikacji? Daj nam znać.",
|
||||
"noAppsFound": "Nie znaleziono żadnych aplikacji.",
|
||||
"searchPlaceholder": "Szukaj alternatyw jak Github, Dropbox, Slack, Trello, ...",
|
||||
"category": {
|
||||
"vpn": "VPN",
|
||||
"wiki": "Wiki",
|
||||
"project": "Zarządzanie projetkami",
|
||||
"sync": "Synchronizacja plików",
|
||||
"learning": "Nauka",
|
||||
"notes": "Notatki",
|
||||
"media": "Media",
|
||||
"git": "Hostowanie kodu",
|
||||
"hosting": "Web Hosting",
|
||||
"game": "Gry",
|
||||
"email": "Email",
|
||||
"finance": "Finanse",
|
||||
"gallery": "Galeria",
|
||||
"forum": "Forum",
|
||||
"crm": "CRM",
|
||||
"document": "Dokumenty",
|
||||
"blog": "Blog",
|
||||
"chat": "Czat",
|
||||
"analytics": "Analityka",
|
||||
"newApps": "Nowe aplikacje",
|
||||
"popular": "Popularne",
|
||||
"all": "Wszystko"
|
||||
@@ -52,23 +30,12 @@
|
||||
"rebootDialog": {
|
||||
"rebootAction": "Zrestartuj teraz",
|
||||
"description": "Restartuj serwer by sfinalizowac instalacje aktualizacji bezpieczeństwa lub w przypadku nieoczekiwanych zachowań. Wszytskie usługi i aplikacje aktywne na tym Cloudronie zostaną automatycznie uruchomione ponownie po restarcie.",
|
||||
"warning": "Restart serwera spowoduje tymczasową niedostepność wszystkich aplikacji zainstalowanych na tym Cloudronie!",
|
||||
"title": "Na pewno zrestartować serwer?"
|
||||
},
|
||||
"clipboard": {
|
||||
"clickToCopyBackupId": "Kliknij by skopiowac Backup ID",
|
||||
"clickToCopy": "Kliknij by skopiować",
|
||||
"copied": "Skopiowano do schowka"
|
||||
},
|
||||
"action": {
|
||||
"logs": "Logi",
|
||||
"reboot": "Restart"
|
||||
},
|
||||
"pagination": {
|
||||
"perPageSelector": "Pokazuj {{ n }} na stronie",
|
||||
"prev": "Poprzednia",
|
||||
"next": "Następna"
|
||||
},
|
||||
"table": {
|
||||
"date": "Data"
|
||||
},
|
||||
@@ -86,15 +53,7 @@
|
||||
"offline": "Cloudron jest niedostępny. Odnawiam połączenie…"
|
||||
},
|
||||
"apps": {
|
||||
"domainsFilterHeader": "Wszytskie domeny",
|
||||
"tagsFilterHeaderAll": "Wszystkie tagi",
|
||||
"tagsFilterHeader": "Tagi: {{ tags }}",
|
||||
"stateFilterHeader": "Wszytskie stany",
|
||||
"searchPlaceholder": "Szukaj Aplikacji",
|
||||
"adminPageActionTooltip": "Panel Administratora",
|
||||
"infoActionTooltip": "Informacje",
|
||||
"logsActionTooltip": "Logi",
|
||||
"configActionTooltip": "Konfiguracja",
|
||||
"noAccess": {
|
||||
"description": "Po uzyskaniu dostępu będą one widoczne tutaj.",
|
||||
"title": "Nie masz obecnie dostępu do żadnych aplikacji."
|
||||
|
||||
@@ -2,50 +2,38 @@
|
||||
"apps": {
|
||||
"title": "As Minhas Aplicações",
|
||||
"noApps": {
|
||||
"description": "Que tal instalar algumas? Vê a <a href=\"{{ appStoreLink }}\">Loja de Aplicações</a>",
|
||||
"title": "Sem aplicações instaladas!"
|
||||
"description": "E que tal instalar algumas? Veja na <a href=\"{{ appStoreLink }}\">Loja de Aplicações</a>",
|
||||
"title": "Ainda sem aplicações instaladas!"
|
||||
},
|
||||
"groupsFilterHeader": "Selecionar Grupo",
|
||||
"addApplinkAction": "Adicionar Applink",
|
||||
"noAccess": {
|
||||
"title": "Não tem acesso a nenhuma aplicação.",
|
||||
"description": "Assim que tiver, elas vão aparecer aqui."
|
||||
"title": "Ainda não tem acesso a quaisquer aplicações.",
|
||||
"description": "Assim que tiver, elas irão aparecer aqui."
|
||||
},
|
||||
"configActionTooltip": "Configurar",
|
||||
"logsActionTooltip": "Eventos",
|
||||
"infoActionTooltip": "Informação",
|
||||
"adminPageActionTooltip": "Página de Adminstração",
|
||||
"searchPlaceholder": "Pesquisar Aplicações",
|
||||
"stateFilterHeader": "Todos os Estados",
|
||||
"tagsFilterHeader": "Etiquetas: {{ tags }}",
|
||||
"tagsFilterHeaderAll": "Todas as Etiquetas",
|
||||
"domainsFilterHeader": "Todos os Domínios",
|
||||
"auth": {
|
||||
"sso": "Entrar com as credenciais Cloudron",
|
||||
"nosso": "Entrar com conta dedicada",
|
||||
"email": "Entrar com endereço de email"
|
||||
},
|
||||
"addAppAction": "Adicionar Aplicação",
|
||||
"addAppproxyAction": "Adicionar Appproxy",
|
||||
"filter": {
|
||||
"clearAll": "Limpar Tudo"
|
||||
"sso": "Iniciar sessão com as credenciais Cloudron",
|
||||
"nosso": "Iniciar sessão com conta dedicada",
|
||||
"email": "Iniciar sessão com endereço de correio eletrónico",
|
||||
"openid": "Iniciar a sessão com Couldron OpenID"
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
"displayName": "Nome de Apresentação",
|
||||
"displayName": "Nome a exibir",
|
||||
"rebootDialog": {
|
||||
"warning": "Reiniciar o servidor irá causar que todas as aplicações instaladas neste Cloudron fiquem indisponíveis temporariamente!",
|
||||
"description": "Utilize isto para aplicar atualizações de segurança ou se experienciar comportamento inesperado. Todas as aplicações e serviços em execução neste Cloudron vão iniciar automaticamente quando o reinício estiver completo.",
|
||||
"title": "Realmente reiniciar o servidor?",
|
||||
"description": "Utilize isto para aplicar as atualizações de segurança ou se tiver um comportamento inesperado. Todas as aplicações e serviços em execução neste Cloudron irão iniciar automaticamente quando o reinício estiver concluído.",
|
||||
"title": "Deseja reiniciar o servidor?",
|
||||
"rebootAction": "Reiniciar agora"
|
||||
},
|
||||
"offline": "O Cloudron está offline. A ligar novamente…",
|
||||
"offline": "Cloudron está off-line. A religar…",
|
||||
"dialog": {
|
||||
"cancel": "Cancelar",
|
||||
"save": "Guardar",
|
||||
"close": "Fechar",
|
||||
"no": "Não",
|
||||
"yes": "Sim"
|
||||
"yes": "Sim",
|
||||
"edit": "Editar",
|
||||
"done": "Concluído",
|
||||
"delete": "Eliminar"
|
||||
},
|
||||
"logout": "Terminar Sessão",
|
||||
"username": "Nome de Utilizador",
|
||||
@@ -53,104 +41,849 @@
|
||||
"table": {
|
||||
"date": "Data"
|
||||
},
|
||||
"pagination": {
|
||||
"next": "seguinte",
|
||||
"prev": "anterior",
|
||||
"perPageSelector": "Mostrar {{ n }} por página"
|
||||
},
|
||||
"action": {
|
||||
"reboot": "Reiniciar",
|
||||
"logs": "Eventos"
|
||||
},
|
||||
"clipboard": {
|
||||
"copied": "Copiado para a área de transferência",
|
||||
"clickToCopy": "Clique para copiar",
|
||||
"clickToCopyBackupId": "Clique para copiar o ID da cópia de segurança"
|
||||
"logs": "Registos de Eventos",
|
||||
"remove": "Remover",
|
||||
"edit": "Editar",
|
||||
"add": "Adicionar",
|
||||
"next": "Seguinte"
|
||||
},
|
||||
"searchPlaceholder": "Pesquisar",
|
||||
"multiselect": {
|
||||
"selected": "{{ n }} selecionados",
|
||||
"select": "Selecionar",
|
||||
"filterPlaceholder": "Escreva para filtrar opções"
|
||||
},
|
||||
"prettyDate": {
|
||||
"justNow": "agora mesmo",
|
||||
"yeserday": "Ontem",
|
||||
"minutesAgo": "{{ m }} minutos atrás",
|
||||
"hoursAgo": "{{ h }} horas atrás"
|
||||
"select": "Selecionar"
|
||||
},
|
||||
"navbar": {
|
||||
"users": "Utilizadores"
|
||||
"users": "Utilizadores",
|
||||
"groups": "Grupos"
|
||||
},
|
||||
"disableAction": "Desativar",
|
||||
"enableAction": "Ativar",
|
||||
"statusEnabled": "Ativado",
|
||||
"statusDisabled": "Desativado"
|
||||
"loadingPlaceholder": "A carregar"
|
||||
},
|
||||
"appstore": {
|
||||
"category": {
|
||||
"analytics": "Estatísticas",
|
||||
"game": "Jogos",
|
||||
"project": "Gestão de Projetos",
|
||||
"all": "Tudo",
|
||||
"popular": "Popular",
|
||||
"newApps": "Novas Aplicações",
|
||||
"chat": "Chat",
|
||||
"blog": "Blog",
|
||||
"document": "Documentos",
|
||||
"crm": "CRM",
|
||||
"forum": "Fórum",
|
||||
"gallery": "Galeria",
|
||||
"finance": "Finanças",
|
||||
"git": "Alojamento de Código",
|
||||
"email": "Email",
|
||||
"hosting": "Alojamento Web",
|
||||
"media": "Multimédia",
|
||||
"learning": "Aprendizagem",
|
||||
"notes": "Notas",
|
||||
"sync": "Sincronização de Ficheiros",
|
||||
"wiki": "Wiki",
|
||||
"vpn": "VPN",
|
||||
"federated": "Federados"
|
||||
"newApps": "Novas Aplicações"
|
||||
},
|
||||
"installDialog": {
|
||||
"lastUpdated": "Última atualização a {{ date }}",
|
||||
"locationPlaceholder": "Deixe em branco para usar o domínio de raiz",
|
||||
"lastUpdated": "Última atualização em {{ date }}",
|
||||
"locationPlaceholder": "Deixe em branco para utilizar o domínio de raiz",
|
||||
"userManagementNone": "Esta aplicação tem a sua própria gestão de utilizadores. Esta definição determina se a aplicação está ou não visível no painel do utilizador.",
|
||||
"memoryRequirement": "Requere pelo menos {{ size }} de memória",
|
||||
"memoryRequirement": "Requer pelo menos {{ size }} de memória",
|
||||
"location": "Localização",
|
||||
"manualWarning": "Adicione um registo A manualmente para <b>{{ location }}</b> apontando para o endereço IP público deste Cloudron",
|
||||
"manualWarning": "Configure manualmente os registos A (IPv4) e AAA (IPv6) para <b>{{ location }}</b> apontando para este servidor",
|
||||
"userManagement": "Gestão de utilizadores",
|
||||
"userManagementMailbox": "Todos os utilizadores com uma caixa de correio neste Cloudron têm acesso.",
|
||||
"userManagementLeaveToApp": "Deixar a gestão de utilizadores para a aplicação",
|
||||
"userManagementAllUsers": "Permitir todos os utilizadores deste Cloudron",
|
||||
"installAnywayAction": "Instalar na mesma",
|
||||
"doInstallAction": "Instalar {{ dnsOverwrite ? 'e sobrescrever DNS' : '' }}",
|
||||
"userManagementSelectUsers": "Apenas permitir os seguintes utilizadores e grupos",
|
||||
"userManagementSelectUsers": "Permitir apenas os seguintes utilizadores e grupos",
|
||||
"errorUserManagementSelectAtLeastOne": "Selecione pelo menos um utilizador ou grupo",
|
||||
"users": "Utilizadores",
|
||||
"groups": "Grupos",
|
||||
"configuredForCloudronEmail": "Esta aplicação está pré-configurada para ser utilizada com o <a href=\"{{ emailDocsLink }}\" target=\"_blank\">Email do Cloudron</a>.",
|
||||
"lowOnResources": "Este Cloudron está baixo em recursos.",
|
||||
"pleaseUpgradeServer": "Por favor, atualize para um servidor com mais memória. Em alternativa, liberte recursos removendo aplicações que não utiliza.",
|
||||
"subscriptionRequired": "Para instalar mais aplicação, uma subscrição paga é necessária.",
|
||||
"setupSubscriptionAction": "Configurar Subscrição",
|
||||
"installAction": "Instalar",
|
||||
"configuredForCloudronEmail": "Esta aplicação está pré-configurada para ser utilizada com o <a href=\"{{ emailDocsLink }}\" target=\"_blank\">E-mail do Cloudron</a>.",
|
||||
"cloudflarePortWarning": "O proxy do Cloudflare deve estar desativado para o domínio da aplicação para que possa aceder a esta porta",
|
||||
"titleAndVersion": "Esta aplicação inclui {{ title }} {{ version }}"
|
||||
"portReadOnly": "apenas de leitura"
|
||||
},
|
||||
"title": "Loja de Aplicações",
|
||||
"searchPlaceholder": "Pesquise por alternativas como Github, Dropbox, Slack, Trello, …",
|
||||
"noAppsFound": "Nenhuma aplicação encontrada.",
|
||||
"appMissing": "Falta uma aplicação? Contacte-nos.",
|
||||
"searchPlaceholder": "Procure por alternativas, tais como Github, Dropbox, Slack, Trello, …",
|
||||
"unstable": "Instável",
|
||||
"appNotFoundDialog": {
|
||||
"description": "Não existe nenhuma aplicação <b>{{ appId }}</b> com a versão <b>{{ version }}</b>.",
|
||||
"title": "Aplicação não encontrada"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"changeEmail": {
|
||||
"password": "Palavra-passe para confirmação",
|
||||
"email": "Novo Endereço de Correio Eletrónico",
|
||||
"title": "Alterar endereço de correio eletrónico principal"
|
||||
},
|
||||
"accountDialog": {
|
||||
"titleSignUp": "Registar com Cloudron.io",
|
||||
"titleLogin": "Entrar com Cloudron.io"
|
||||
"changePassword": {
|
||||
"title": "Alterar palavra-passe",
|
||||
"currentPassword": "Palavra-passe atual",
|
||||
"newPassword": "Nova palavra-passe",
|
||||
"newPasswordRepeat": "Repetir palavra-passe",
|
||||
"errorPasswordsDontMatch": "As palavras-passe não coincidem"
|
||||
},
|
||||
"enable2FA": {
|
||||
"title": "Ativar Autenticação de Dois Fatores",
|
||||
"token": "Código",
|
||||
"enable": "Ativar",
|
||||
"description": "O seu administrador do Cloudron exigiu que todos os membros ativassem a autenticação de dois fatores. Você não poderá aceder ao painel até ativar a 2FA.",
|
||||
"authenticatorAppDescription": "Utilize o Google Authenticator (<a href=\"{{ googleAuthenticatorPlayStoreLink }}\" target=\"_blank\">Android</a>, <a href=\"{{ googleAuthenticatorITunesLink }}\" target=\"_blank\">iOS</a>), autenticador FreeOTP (<a href=\"{{ freeOTPPlayStoreLink }}\" target=\"_blank\">Android</a>, <a href=\"{{ freeOTPITunesLink }}\" target=\"_blank\">iOS</a>) ou uma aplicação de TOTP similar para digitalizar o segredo."
|
||||
},
|
||||
"apiTokens": {
|
||||
"title": "Códigos de API",
|
||||
"lastUsed": "Última Utilização",
|
||||
"neverUsed": "nunca",
|
||||
"scope": "Âmbito",
|
||||
"readonly": "Apenas de leitura",
|
||||
"readwrite": "Ler e Gravar",
|
||||
"name": "Nome",
|
||||
"description": "Utilize estes códigos de acesso pessoais para autenticar a <a target=\"_blank\" href=\"{{ apiDocsLink }}\">API do Cloudron</a>",
|
||||
"noTokensPlaceholder": "Sem Códigos da API criados",
|
||||
"allowedIpRanges": "IPs Permitidos",
|
||||
"allowedIpRangesPlaceholder": "IPs ou sub-redes separados por vírgulas"
|
||||
},
|
||||
"createAppPassword": {
|
||||
"generatePassword": "Gerar Palavra-passe",
|
||||
"name": "Nome da Palavra-passe",
|
||||
"title": "Criar Palavra-passe da Aplicação",
|
||||
"app": "Aplicação",
|
||||
"description": "Utilize a palavra-passe seguinte para se autenticar na aplicação:",
|
||||
"copyNow": "Por favor, copie a palavra-passe agora. Esta não será mostrada novamente por motivos de segurança."
|
||||
},
|
||||
"createApiToken": {
|
||||
"name": "Nome do Código de API",
|
||||
"title": "Criar Código de API",
|
||||
"description": "Novo código de API:",
|
||||
"generateToken": "Gerar Código de API",
|
||||
"access": "Acesso de API",
|
||||
"copyNow": "Por favor, copie o código da API agora. Este não será mostrado novamente por motivos de segurança.",
|
||||
"allowedIpRanges": "Intervalo(s) de IP Permitido(s)"
|
||||
},
|
||||
"passwordResetNotification": {
|
||||
"body": "Mensagem enviada para {{ email }}"
|
||||
},
|
||||
"title": "Perfil",
|
||||
"primaryEmail": "E-mail principal",
|
||||
"language": "Idioma",
|
||||
"disable2FA": {
|
||||
"title": "Desativar Autenticação de Dois Fatores",
|
||||
"password": "Palavra-passe",
|
||||
"disable": "Desativar"
|
||||
},
|
||||
"changeFallbackEmail": {
|
||||
"title": "Alterar endereço de correio eletrónico da recuperação de palavra-passe"
|
||||
},
|
||||
"loginTokens": {
|
||||
"title": "Códigos de Autenticação",
|
||||
"logoutAll": "Terminar Sessão de Todos",
|
||||
"description": "Tem {{ webadminTokenCount}} código(s) da Web ativo(s) e {{ cliTokenCount }} código(s) de CLI."
|
||||
},
|
||||
"appPasswords": {
|
||||
"title": "Palavras-passe da Aplicação",
|
||||
"app": "Aplicação",
|
||||
"name": "Nome",
|
||||
"noPasswordsPlaceholder": "Nenhumas Palavras-passe de Aplicação criadas",
|
||||
"description": "As palavras-passe da aplicação são uma medida de segurança para proteger a sua conta de utilizador Cloudron. Se precisar de aceder a uma aplicação Cloudron a partir de uma aplicação móvel ou cliente não fidedigno, pode iniciar a sessão com o seu nome de utilizador e a palavra-passe alternativa gerada aqui."
|
||||
},
|
||||
"changePasswordAction": "Alterar Palavra-passe",
|
||||
"disable2FAAction": "Desativar 2FA",
|
||||
"enable2FAAction": "Ativar 2FA",
|
||||
"removeAppPassword": {
|
||||
"title": "Deseja remover a palavra-passe {{ name }}?"
|
||||
},
|
||||
"removeApiToken": {
|
||||
"title": "Deseja remover o código {{ name }}?"
|
||||
},
|
||||
"passwordRecoveryEmail": "Mensagem de recuperação da palavra-passe"
|
||||
},
|
||||
"users": {
|
||||
"exposedLdap": {
|
||||
"ipRestriction": {
|
||||
"label": "Restringir Acesso",
|
||||
"placeholder": "Endereço de IP ou Sub-rede separado por linha",
|
||||
"description": "Limite o acesso do Servidor de Diretoria para IPs ou intervalos específicos. As linhas que começam com <code>#</code> são tratadas como comentários."
|
||||
},
|
||||
"secret": {
|
||||
"label": "Associar Palavra-passe",
|
||||
"url": "URL do Servidor",
|
||||
"description": "Todas as consultas de LDAP tem de ser autenticadas com este segredo e o utilizador <i>{{ userDN }}</i> de DN"
|
||||
},
|
||||
"description": "O servidor LDAP pode ser utilizado pelas aplicações externas para autenticação.",
|
||||
"cloudflarePortWarning": "O proxy de Cloudflare deve estar desativado no domínio do painel para aceder ao servidor LDAP"
|
||||
},
|
||||
"users": {
|
||||
"superadminTooltip": "Este utilizador é um super administrador",
|
||||
"invitationTooltip": "Convidar",
|
||||
"setGhostTooltip": "Impersonar",
|
||||
"mailmanagerTooltip": "Este utilizador pode gerir os utilizadores e caixas de corrreio",
|
||||
"user": "Utilizador",
|
||||
"groups": "Grupos",
|
||||
"empty": "Não foram encontrados utilizadores",
|
||||
"adminTooltip": "Este utilizador é um administrador",
|
||||
"usermanagerTooltip": "Este utilizador pode gerir os grupos e os outros utilizadores",
|
||||
"inactiveTooltip": "Utilizador está inativo",
|
||||
"externalLdapTooltip": "Da diretoria LDAP externa",
|
||||
"resetPasswordTooltip": "Redefinir Palavra-passe"
|
||||
},
|
||||
"groups": {
|
||||
"emptyPlaceholder": "Sem Grupos",
|
||||
"name": "Nome",
|
||||
"users": "Utilizadores",
|
||||
"externalLdapTooltip": "Da diretoria LDAP externa"
|
||||
},
|
||||
"user": {
|
||||
"fullName": "Nome Completo",
|
||||
"username": "Nome de utilizador",
|
||||
"role": "Função",
|
||||
"groups": "Grupos",
|
||||
"noGroups": "Nenhum grupo disponível.",
|
||||
"displayName": "Nome a Exibir",
|
||||
"primaryEmail": "E-mail principal",
|
||||
"usernamePlaceholder": "Opcional. Se não for fornecido, o utilizador pode escolher durante o registo",
|
||||
"activeCheckbox": "O utilizador está ativo",
|
||||
"displayNamePlaceholder": "Opcional. Se não fornecido, o utilizador pode fornecer durante o registo",
|
||||
"fallbackEmailPlaceholder": "Se não especificado, será utilizado o e-mail principal",
|
||||
"recoveryEmail": "Mensagem de recuperação da palavra-passe"
|
||||
},
|
||||
"passwordResetDialog": {
|
||||
"description": "A seguinte hiperligação de redefinir palavra-passe foi enviada para {{ email }}:",
|
||||
"sendAction": "Enviar Mensagem",
|
||||
"reset2FAAction": "Redefinir 2FA",
|
||||
"title": "Redefinir palavra-passe para {{ username }}",
|
||||
"descriptionLink": "Copiar hiperligação de redefinição da palavra-passe",
|
||||
"descriptionEmail": "Enviar hiperligação de redefinição da palavra-passe"
|
||||
},
|
||||
"editUserDialog": {
|
||||
"externalLdapWarning": "Este utilizador é sincronizado a partir da diretoria LDAP externa.",
|
||||
"title": "Editar utilizador {{ username }}"
|
||||
},
|
||||
"deleteGroupDialog": {
|
||||
"description": "Este grupo tem {{ memberCount }} membro(s). Deseja remover este grupo?",
|
||||
"deleteAction": "Eliminar",
|
||||
"title": "Eliminar grupo {{ name }}"
|
||||
},
|
||||
"invitationDialog": {
|
||||
"descriptionEmail": "Enviar hiperligação de convite",
|
||||
"title": "Convidar {{ username }}",
|
||||
"sendAction": "Enviar Mensagem",
|
||||
"descriptionLink": "Copiar hiperligação de convite",
|
||||
"description": "A seguinte hiperligação de convite foi enviada para {{ email }}:"
|
||||
},
|
||||
"externalLdap": {
|
||||
"autocreateUsersOnLogin": "Criar utilizadores automaticamente ao iniciar a sessão",
|
||||
"provider": "Fornecedor",
|
||||
"server": "URL do Servidor",
|
||||
"filter": "Filtro",
|
||||
"usernameField": "Campo do Nome do Utilizador",
|
||||
"syncGroups": "Sincronizar Grupos",
|
||||
"auth": "Autenticar",
|
||||
"syncAction": "Sincronizar",
|
||||
"configureAction": "Configurar",
|
||||
"noopInfo": "A autenticação LDAP não está configurada.",
|
||||
"title": "Ligar uma Diretoria Externa",
|
||||
"acceptSelfSignedCert": "Aceitar certificado Auto Assinado",
|
||||
"groupnameField": "Campo do Nome do Grupo",
|
||||
"errorSelfSignedCert": "O servidor está a utilizar um certificado inválido ou assinado automaticamente.",
|
||||
"description": "Esta definição sincronizará e autenticará os utilizadores e grupos de um servidor LDAP ou Active Directory externa. A sincronização é executada periodicamente, mas também pode ser acionada manualmente.",
|
||||
"bindPassword": "Vincular Palavra-passe (opcional)",
|
||||
"disableWarning": "A fonte de autenticação de todos os utilizadores existentes será reiniciada para se autenticar na base de dados da palavra-passe atual.",
|
||||
"baseDn": "Base DN",
|
||||
"bindUsername": "Vincular DN/Nome de utilizador (opcional)",
|
||||
"groupFilter": "Filtro de Grupo",
|
||||
"groupBaseDn": "Base DN do Grupo"
|
||||
},
|
||||
"deleteUserDialog": {
|
||||
"title": "Eliminar utilizador {{ username }}",
|
||||
"deleteAction": "Eliminar",
|
||||
"description": "Depois da eliminação, o utilizador não poderá aceder ao painel ou iniciar a sessão em quaisquer aplicações. Note que não são removidos quaisquer dados do utilizador dentro das aplicações."
|
||||
},
|
||||
"externalLdapDialog": {
|
||||
"title": "Configurar LDAP"
|
||||
},
|
||||
"role": {
|
||||
"user": "Utilizador",
|
||||
"usermanager": "Gestor de Utilizadores",
|
||||
"admin": "Administrador",
|
||||
"owner": "Super Administrador",
|
||||
"mailmanager": "Gestor de E-mails e Utilizadores"
|
||||
},
|
||||
"setGhostDialog": {
|
||||
"password": "Palavra-passe Temporária",
|
||||
"setPassword": "Definir Palavra-passe",
|
||||
"generatePassword": "Gerar Palavra-passe",
|
||||
"title": "Criar palavra-passe para se passar por {{ username }}",
|
||||
"description": "Defina uma palavra-passe temporária para fazer iniciar a sessão em nome deste utilizador nas aplicações ou no painel. Esta palavra-passe é válida por 6 horas."
|
||||
},
|
||||
"settings": {
|
||||
"saveAction": "Guardar",
|
||||
"allowProfileEditCheckbox": "Permitir que os utilizadores editem o seu nome e e-mail",
|
||||
"require2FACheckbox": "Requer que os utilizadores configurem 2FA"
|
||||
},
|
||||
"addGroupDialog": {
|
||||
"title": "Adicionar Grupo"
|
||||
},
|
||||
"group": {
|
||||
"name": "Nome",
|
||||
"users": "Utilizadores",
|
||||
"addGroupAction": "Adicionar Grupo"
|
||||
},
|
||||
"editGroupDialog": {
|
||||
"title": "Editar grupo {{ name }}",
|
||||
"externalLdapWarning": "Este grupo é sincronizado a partir da diretoria LDAP externa."
|
||||
},
|
||||
"addUserDialog": {
|
||||
"title": "Adicionar Utilizador",
|
||||
"addUserAction": "Adicionar Utilizador",
|
||||
"sendInviteCheckbox": "Enviar agora uma mensagem de convite"
|
||||
},
|
||||
"invitationNotification": {
|
||||
"body": "Mensagem enviada para {{ email }}"
|
||||
},
|
||||
"title": "Utilizadores e Grupos"
|
||||
},
|
||||
"login": {
|
||||
"2faToken": "Código 2FA",
|
||||
"resetPasswordAction": "Redefinir Palavra-passe",
|
||||
"errorIncorrectCredentials": "Nome de utilizador ou palavra-passe incorreta",
|
||||
"username": "Nome de utilizador",
|
||||
"password": "Palavra-passe",
|
||||
"errorIncorrect2FAToken": "Código 2FA é inválido",
|
||||
"errorInternal": "Erro interno, tente novamente mais tarde"
|
||||
},
|
||||
"newLoginEmail": {
|
||||
"salutation": "Olá <%= user %>,",
|
||||
"notice": "Nós detetamos um novo início de sessão na sua conta Cloudron a partir de um novo dispositivo.",
|
||||
"topic": "Nós detetamos um novo inicio de sessão na sua conta Cloudron.",
|
||||
"subject": "[<%= cloudron %>] Novo inicio de sessão na sua conta"
|
||||
},
|
||||
"emails": {
|
||||
"testMailDialog": {
|
||||
"sendAction": "Enviar"
|
||||
},
|
||||
"eventlog": {
|
||||
"type": {
|
||||
"sentInfo": "Enviada",
|
||||
"queued": "Em Fila",
|
||||
"savedInfo": "Guardada",
|
||||
"denied": "Negado",
|
||||
"bounce": "Desativado",
|
||||
"outgoing": "De Saída",
|
||||
"incoming": "A Receber",
|
||||
"deferred": "Adiado",
|
||||
"deniedInfo": "Ligação negada",
|
||||
"overQuotaInfo": "Caixa de Correio {{ mailbox }} está {{ quotaPercent }}% cheia",
|
||||
"underQuotaInfo": "Caixa de Correio {{ mailbox }} está abaixo da quota {{ quotaPercent }}%",
|
||||
"quota": "Quota da Caixa de Correio"
|
||||
},
|
||||
"mailFrom": "De",
|
||||
"rcptTo": "Para",
|
||||
"time": "Hora",
|
||||
"details": "Detalhes"
|
||||
},
|
||||
"changeDomainDialog": {
|
||||
"description": "Isto irá mover o servidor de IMAP e SMTP para a localização especificada."
|
||||
},
|
||||
"title": "E-mail",
|
||||
"typeFilterHeader": "Todos os Eventos",
|
||||
"aclDialog": {
|
||||
"dnsblZones": "Zonas DNSBL"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Definições",
|
||||
"location": "Localização do Servidor de Correio",
|
||||
"aclOverview": "{{ dnsblZonesCount }} DNSBL zona(s)",
|
||||
"virtualAllMail": "Pasta \"Todas as Mensagens\"",
|
||||
"acl": "Correio ACL",
|
||||
"spamFilter": "Filtragem de lixo do correio eletrónico",
|
||||
"solrFts": "Pesquisa de Texto Completo"
|
||||
},
|
||||
"domains": {
|
||||
"outbound": "Apenas de saída",
|
||||
"disabled": "Desativado",
|
||||
"stats": "Contagem: {{ mailboxCount }} / Utilização: {{ usage }}",
|
||||
"testEmailTooltip": "Enviar Mensagem de Teste",
|
||||
"title": "Domínios"
|
||||
},
|
||||
"spamFilterDialog": {
|
||||
"title": "Filtragem de Lixo de Correio Eletrónico",
|
||||
"blacklisteAddresses": "Endereços na lista de bloqueios"
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"incoming": {
|
||||
"mailboxes": {
|
||||
"addAction": "Adicionar"
|
||||
}
|
||||
},
|
||||
"addMailboxDialog": {
|
||||
"title": "Adicionar Caixa de Correio",
|
||||
"name": "Nome"
|
||||
},
|
||||
"addMailinglistDialog": {
|
||||
"members": "Listar Membros",
|
||||
"name": "Nome"
|
||||
},
|
||||
"smtpStatus": {
|
||||
"rblCheck": "Verificar Lista Negra de DNS",
|
||||
"outboundSmtp": "SMTP de Saída"
|
||||
},
|
||||
"config": {
|
||||
"sending": {
|
||||
"title": "A enviar"
|
||||
},
|
||||
"receiving": {
|
||||
"title": "A receber"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backups": {
|
||||
"location": {
|
||||
"remount": "Remontar Armazenamento"
|
||||
},
|
||||
"configureBackupStorage": {
|
||||
"s3SecretAccessKey": "Código de acesso secreto",
|
||||
"format": "Formato do Armazenamento",
|
||||
"preserveAttributesLabel": "Preservar atributos de ficheiro",
|
||||
"port": "Porta",
|
||||
"provider": "Fornecedor de Armazenamento",
|
||||
"prefix": "Prefixo",
|
||||
"region": "Região",
|
||||
"title": "Configurar Site da Cópia de Segurança",
|
||||
"localDirectory": "Diretoria da cópia de segurança local",
|
||||
"acceptSelfSignedCerts": "Aceitar certificado Assinado Automaticamente",
|
||||
"s3AccessKeyId": "Id. do código de acesso",
|
||||
"gcsServiceKey": "Código da Conta de Serviço",
|
||||
"encryptionPassword": "Encriptação da Palavra-passe",
|
||||
"encryptFilenames": "Nomes de Ficheiro Encriptados",
|
||||
"memoryLimit": "Limite da Memória",
|
||||
"encryptionPasswordRepeat": "Repetir Palavra-passe",
|
||||
"server": "IP do Servidor ou Nome do Anfitrião",
|
||||
"remoteDirectory": "Diretoria Remota",
|
||||
"username": "Nome de utilizador",
|
||||
"password": "Palavra-passe",
|
||||
"user": "Utilizador",
|
||||
"privateKey": "Código Privado",
|
||||
"diskPath": "Caminho do Disco",
|
||||
"downloadConcurrencyDescription": "Número de ficheiros para transferir em paralelo quando restaurar",
|
||||
"downloadConcurrency": "Transferir Moeda",
|
||||
"uploadConcurrency": "Enviar Moeda",
|
||||
"uploadConcurrencyDescription": "Número de ficheiros para enviar em paralelo quando realizar a cópia de segurança",
|
||||
"copyConcurrency": "Copiar Moeda",
|
||||
"copyConcurrencyDescription": "Número de cópias de ficheiros em paralelo quando realizar a cópia de segurança.",
|
||||
"name": "Nome",
|
||||
"encryptionHint": "Dica da Encriptação da Palavra-passe",
|
||||
"usesEncryption": "A cópia de segurança utiliza encriptação",
|
||||
"mountPoint": "Ponto de montagem",
|
||||
"hardlinksLabel": "Utilizar ligações físicas",
|
||||
"s3Endpoint": "Endpoint",
|
||||
"uploadPartSize": "Tamanho da Parte a Enviar",
|
||||
"uploadPartSizeDescription": "Tamanho da parte de envio de várias partes. São enviadas até 3 partes em paralelo e requerem o máximo de memória.",
|
||||
"mountPointDescription": "O ponto de montagem tem de ser configurado manualmente. Consulte a <a href=\"{{ providerDocsLink }}\" target=\"_blank\">documentação</a>."
|
||||
},
|
||||
"schedule": {
|
||||
"schedule": "Agendar",
|
||||
"retentionPolicy": "Política de Retenção",
|
||||
"title": "Agendar e Retenção"
|
||||
},
|
||||
"backupEdit": {
|
||||
"remotePath": "Remover Caminho",
|
||||
"label": "Etiqueta",
|
||||
"title": "Editar Cópia"
|
||||
},
|
||||
"restoreArchiveDialog": {
|
||||
"restoreAction": "Restaurar",
|
||||
"restoreActionOverwrite": "Restaurar e substituir DNS",
|
||||
"title": "Restaurar do Arquivo"
|
||||
},
|
||||
"archives": {
|
||||
"info": "Informação",
|
||||
"title": "Arquivo de Aplicações"
|
||||
},
|
||||
"deleteArchive": {
|
||||
"deleteAction": "Eliminar"
|
||||
},
|
||||
"deleteArchiveDialog": {
|
||||
"description": "Depois da eliminação, o arquivo será limpo com base na política das cópias de segurança.",
|
||||
"title": "Eliminar Arquivo de {{appTitle}} ({{fqdn}})"
|
||||
},
|
||||
"configureBackupSchedule": {
|
||||
"schedule": "Agendar",
|
||||
"days": "Dias",
|
||||
"hours": "Horas",
|
||||
"retentionPolicy": "Política de Retenção",
|
||||
"title": "Configurar Agendamento da Cópia de Segurança e Retenção"
|
||||
},
|
||||
"title": "Cópias de Segurança",
|
||||
"listing": {
|
||||
"contents": "Conteúdos",
|
||||
"version": "Versão",
|
||||
"noApps": "Sem Aplicações",
|
||||
"appCount": "{{ appCount }} aplicações",
|
||||
"backupNow": "Copiar Agora",
|
||||
"tooltipPreservedBackup": "Esta cópia de segurança será preservada",
|
||||
"title": "Listagem",
|
||||
"noBackups": "Sem Cópias de Segurança",
|
||||
"tooltipDownloadBackupConfig": "Transferir Configuração",
|
||||
"cleanupBackups": "Limpeza das Cópias de Segurança"
|
||||
},
|
||||
"backupDetails": {
|
||||
"title": "Detalhes da Cópia",
|
||||
"id": "Id.",
|
||||
"date": "Data",
|
||||
"version": "Versão",
|
||||
"list": "Referencia as cópias de segurança de {{ appCount }} aplicações"
|
||||
}
|
||||
},
|
||||
"passwordReset": {
|
||||
"backToLoginAction": "Voltar para iniciar sessão",
|
||||
"passwordChanged": {
|
||||
"submitAction": "Submeter"
|
||||
},
|
||||
"success": {
|
||||
"title": "Palavra-passe alterada",
|
||||
"openDashboardAction": "Abrir Painel"
|
||||
}
|
||||
},
|
||||
"branding": {
|
||||
"footer": {
|
||||
"title": "Rodapé"
|
||||
},
|
||||
"title": "Aparência",
|
||||
"cloudronName": "Nome de Cloudron",
|
||||
"logo": "Logótipo"
|
||||
},
|
||||
"network": {
|
||||
"firewall": {
|
||||
"title": "Firewall",
|
||||
"configure": {
|
||||
"title": "Configuração da Firewall"
|
||||
}
|
||||
},
|
||||
"configureIp": {
|
||||
"title": "Configurar Provedor de IPv4"
|
||||
},
|
||||
"ip": {
|
||||
"title": "IPv4",
|
||||
"description": "Este endereço IPv4 é utilizado para configurar os registos de DNS A.",
|
||||
"provider": "Provedor",
|
||||
"interface": "Nome da Interface da Rede",
|
||||
"configure": "Configurar",
|
||||
"detected": "detetado",
|
||||
"address": "Endereço de IP",
|
||||
"interfaceDescription": "Listar os dispositivos disponíveis no servidor com:"
|
||||
},
|
||||
"title": "Rede",
|
||||
"dyndns": {
|
||||
"title": "DNS Dinâmico"
|
||||
},
|
||||
"ipv4": {
|
||||
"address": "Endereço de IPv4"
|
||||
},
|
||||
"ipv6": {
|
||||
"address": "Endereço de IPv6",
|
||||
"title": "IPv6"
|
||||
},
|
||||
"configureIpv6": {
|
||||
"title": "Configurar Provedor de IPv6"
|
||||
},
|
||||
"trustedIps": {
|
||||
"title": "Configurar IPs Confiáveis"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"timezone": {
|
||||
"title": "Zona Horária do Sistema"
|
||||
},
|
||||
"language": {
|
||||
"description": "Define o idioma predefinido para Cloudron e mensagens do sistema. (por exemplo, convites, redefinições de palavra-passe). Os utilizadores podem substituir o idioma do painel no seu perfil.",
|
||||
"title": "Idioma"
|
||||
},
|
||||
"title": "Sistema",
|
||||
"appstoreAccount": {
|
||||
"title": "Conta Cloudron.io",
|
||||
"setupAction": "Configurar Conta",
|
||||
"subscription": "Subscrição",
|
||||
"cloudronId": "Id. de Cloudron",
|
||||
"subscriptionChangeAction": "Gerir Subscrição",
|
||||
"subscriptionReactivateAction": "Reativar Subscrição",
|
||||
"emailNotVerified": "E-mail ainda não foi verificado"
|
||||
},
|
||||
"updates": {
|
||||
"checkForUpdatesAction": "Procurar por Atualizações",
|
||||
"schedule": "Agendar",
|
||||
"updateAvailableAction": "Disponível Atualização",
|
||||
"stopUpdateAction": "Parar Atualização",
|
||||
"disabled": "Desativada"
|
||||
},
|
||||
"updateDialog": {
|
||||
"title": "Atualizar Cloudron para",
|
||||
"changes": "Alterações",
|
||||
"skipBackupCheckbox": "Ignorar cópia",
|
||||
"updateAction": "Atualizar",
|
||||
"blockingApps": "As aplicações em baixo estão a bloquear a atualização porque elas têm ações pendentes:",
|
||||
"blockingAppsInfo": "Por favor, aguarde que as operações em cima terminem."
|
||||
},
|
||||
"updateScheduleDialog": {
|
||||
"days": "Dias",
|
||||
"hours": "Horas",
|
||||
"disableCheckbox": "Desativar Atualizações Automáticas",
|
||||
"enableCheckbox": "Ativar Atualizações Automáticas",
|
||||
"selectOne": "Selecione pelo menos um dia e hora"
|
||||
},
|
||||
"registryConfig": {
|
||||
"providerOther": "Outro"
|
||||
}
|
||||
},
|
||||
"support": {
|
||||
"help": {
|
||||
"title": "Ajuda",
|
||||
"description": "Por favor, utilize os seguintes recursos para ajuda e apoio:\n* \n* [Fórum do Cloudron]({{ forumLink }}) - Por favor, utilize as categorias específicas da Aplicação e Apoio para questões.\n* [Documentação]({{ docsLink }})\n* [Empacotamento de Aplicação]({{ packagingLink }})\n* [API]({{ packagingLink }})"
|
||||
}
|
||||
},
|
||||
"domains": {
|
||||
"renewCerts": {
|
||||
"renewAllAction": "Renovar Todos os Certificados",
|
||||
"title": "Renovar certificados"
|
||||
},
|
||||
"domainDialog": {
|
||||
"editTitle": "Configurar {{ domain }}",
|
||||
"addTitle": "Adicionar Domínio",
|
||||
"fallbackCertCustomCert": "Certificado Personalizado",
|
||||
"jitsiHostname": "Localização de Jitsi",
|
||||
"inwxUsername": "Nome de utilizador",
|
||||
"inwxPassword": "Palavra-passe",
|
||||
"domain": "Domínio",
|
||||
"matrixHostname": "Localização do Servidor Matrix",
|
||||
"mastodonHostname": "Localização do Servidor Mastodon",
|
||||
"netcupApiKey": "Código da API",
|
||||
"provider": "Provedor de DNS",
|
||||
"route53AccessKeyId": "Id. do Código de Acesso",
|
||||
"route53SecretAccessKey": "Código de Acesso Secreto",
|
||||
"gcdnsServiceAccountKey": "Código da Conta de Serviço",
|
||||
"digitalOceanToken": "Código de DigitalOcean",
|
||||
"gandiApiKey": "Código da API Gandi",
|
||||
"goDaddyApiKey": "Código da API",
|
||||
"goDaddyApiSecret": "Segredo da API",
|
||||
"cloudflareTokenType": "Tipo de Código",
|
||||
"cloudflareEmail": "E-mail de Cloudflare",
|
||||
"linodeToken": "Código de Linode",
|
||||
"nameComUsername": "Nome de Utilizador de Name.com",
|
||||
"nameComApiToken": "Código da API",
|
||||
"namecheapApiKey": "Código da API",
|
||||
"advancedAction": "Definições avançadas…",
|
||||
"zoneName": "Nome da Zona (Opcional)",
|
||||
"fallbackCertKeyPlaceholder": "Código",
|
||||
"fallbackCertCertificatePlaceholder": "Certificado",
|
||||
"netcupCustomerNumber": "Número de Cliente",
|
||||
"netcupApiPassword": "Palavra-passe da API",
|
||||
"hetznerToken": "Código de Hetzner",
|
||||
"cloudflareDefaultProxyStatus": "Ativar ''proxying'' para os novos registos de DNS",
|
||||
"porkbunApikey": "Código da API",
|
||||
"porkbunSecretapikey": "Código da API Secreto",
|
||||
"vultrToken": "Código de Vultr",
|
||||
"cloudflareTokenTypeGlobalApiKey": "Código da API Global",
|
||||
"cloudflareTokenTypeApiToken": "Código da API"
|
||||
},
|
||||
"title": "Domínios e Certificados",
|
||||
"domain": "Domínio",
|
||||
"provider": "Provedor",
|
||||
"changeDashboardDomain": {
|
||||
"title": "Alterar Domínio do Painel",
|
||||
"changeAction": "Alterar Domínio"
|
||||
},
|
||||
"syncDns": {
|
||||
"title": "Sincronizar DNS",
|
||||
"syncAction": "Sincronizar DNS"
|
||||
},
|
||||
"removeDialog": {
|
||||
"removeAction": "Remover"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"restartActionTooltip": "Reiniciar",
|
||||
"configure": {
|
||||
"title": "Configurar {{ name }}",
|
||||
"resetToDefaults": "Reiniciar para predefinição",
|
||||
"enableRecoveryMode": "Ativar Modo de Recuperação"
|
||||
},
|
||||
"memoryUsage": "Utilização de Memória",
|
||||
"memoryLimit": "Limite de Memória",
|
||||
"title": "Serviços",
|
||||
"service": "Serviço"
|
||||
},
|
||||
"system": {
|
||||
"diskUsage": {
|
||||
"title": "Utilização do Disco"
|
||||
},
|
||||
"systemMemory": {
|
||||
"title": "Memória do Sistema"
|
||||
},
|
||||
"cpuUsage": {
|
||||
"title": "CPU"
|
||||
},
|
||||
"info": {
|
||||
"vendor": "Empresa",
|
||||
"product": "Produto",
|
||||
"memory": "Memória",
|
||||
"uptime": "Em Execução",
|
||||
"activationTime": "Hora de Criação do Cloudron"
|
||||
},
|
||||
"locale": {
|
||||
"title": "Local"
|
||||
},
|
||||
"graphs": {
|
||||
"title": "Gráficos"
|
||||
}
|
||||
},
|
||||
"eventlog": {
|
||||
"source": "Fonte",
|
||||
"details": "Detalhes",
|
||||
"filterAllEvents": "Todos os Eventos",
|
||||
"time": "Hora",
|
||||
"title": "Registo de Evento"
|
||||
},
|
||||
"notifications": {
|
||||
"settings": {
|
||||
"diskSpace": "Pouco espaço em disco",
|
||||
"appUp": "A aplicação está novamente on-line",
|
||||
"rebootRequired": "É necessário reiniciar o servidor",
|
||||
"cloudronUpdateFailed": "Atualização do Cloudron Falhou",
|
||||
"title": "Definições da Notificação",
|
||||
"backupFailed": "Cópia de segurança falhou",
|
||||
"certificateRenewalFailed": "Renovação do certificado falhou",
|
||||
"appOutOfMemory": "A aplicação não tem memória",
|
||||
"appDown": "A aplicação está em baixo"
|
||||
},
|
||||
"allCaughtUp": "Tudo OK",
|
||||
"dismissTooltip": "Rejeitar",
|
||||
"markAllAsRead": "Marcar Tudo como Lido"
|
||||
},
|
||||
"app": {
|
||||
"backups": {
|
||||
"backups": {
|
||||
"downloadConfigTooltip": "Transferir Configuração",
|
||||
"downloadBackupTooltip": "Transferir",
|
||||
"restoreTooltip": "Restaurar",
|
||||
"cloneTooltip": "Clonar",
|
||||
"createBackupAction": "Criar Cópia de Segurança",
|
||||
"importAction": "Importar Cópia de Segurança",
|
||||
"title": "Cópias de Segurança",
|
||||
"checkIntegrity": "Verificar Integridade"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importar da Cópia de Segurança Externa"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"taskError": {
|
||||
"description": "Se uma instalação, configuração, atualização, restauração ou cópia de segurança resultou num erro, pode tentar novamente a tarefa.",
|
||||
"retryAction": "Repetir Tarefa {{ task }}"
|
||||
},
|
||||
"recovery": {
|
||||
"title": "Modo de Recuperação"
|
||||
}
|
||||
},
|
||||
"updates": {
|
||||
"info": {
|
||||
"customAppUpdateInfo": "A atualização automática não está disponível para as aplicações personalizadas.",
|
||||
"installedAt": "Instalado às",
|
||||
"lastUpdated": "Última Atualização",
|
||||
"packageVersion": "Versão do Pacote",
|
||||
"description": "Título e Versão da Aplicação"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"hstsPreload": "Ativar pré-carregamento de HSTS para este site e todos os subdomínios"
|
||||
},
|
||||
"forumAction": "Fórum",
|
||||
"resources": {
|
||||
"devices": {
|
||||
"label": "Dispositivos"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"title": "Registos",
|
||||
"clear": "Limpar Visualização",
|
||||
"download": "Transferir Registos Completos"
|
||||
},
|
||||
"terminal": {
|
||||
"download": {
|
||||
"download": "Transferir"
|
||||
},
|
||||
"downloadAction": "Transferir"
|
||||
},
|
||||
"archives": {
|
||||
"listing": {
|
||||
"placeholder": "Sem Aplicações Arquivadas"
|
||||
}
|
||||
},
|
||||
"userdirectory": {
|
||||
"settings": {
|
||||
"title": "Definições"
|
||||
}
|
||||
},
|
||||
"oidc": {
|
||||
"editClientDialog": {
|
||||
"title": "Editar Cliente {{ client }}"
|
||||
},
|
||||
"deleteClientDialog": {
|
||||
"title": "Deseja eliminar cliente {{ client }}?"
|
||||
},
|
||||
"newClientDialog": {
|
||||
"title": "Adicionar Cliente OIDC",
|
||||
"createAction": "Adicionar"
|
||||
},
|
||||
"client": {
|
||||
"name": "Nome",
|
||||
"id": "Id. do Cliente",
|
||||
"secret": "Segredo do Cliente",
|
||||
"signingAlgorithm": "Algoritmo de Assinatura"
|
||||
},
|
||||
"env": {
|
||||
"discoveryUrl": "URL de Descobrir"
|
||||
}
|
||||
},
|
||||
"volumes": {
|
||||
"localDirectory": "Diretoria Local",
|
||||
"remountActionTooltip": "Remontar",
|
||||
"editVolumeDialog": {
|
||||
"title": "Editar volume {{ name }}"
|
||||
},
|
||||
"addVolumeDialog": {
|
||||
"privateKey": "Chave SSH Privada",
|
||||
"title": "Adicionar Volume",
|
||||
"server": "IP do Servidor ou Nome de Anfitrião",
|
||||
"remoteDirectory": "Diretoria Remota",
|
||||
"username": "Nome de utilizador",
|
||||
"password": "Palavra-passe",
|
||||
"diskPath": "Caminho do Disco",
|
||||
"port": "Porta",
|
||||
"user": "Utilizador"
|
||||
},
|
||||
"name": "Nome",
|
||||
"openFileManagerActionTooltip": "Gestor de Ficheiros",
|
||||
"title": "Volumes",
|
||||
"removeVolumeDialog": {
|
||||
"removeAction": "Remover"
|
||||
},
|
||||
"mountType": "Tipo de Montagem"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"salutation": "Olá <%= user %>,",
|
||||
"inviteLinkAction": "Começar",
|
||||
"subject": "Bem-vindo ao <%= cloudron %>"
|
||||
},
|
||||
"setupAccount": {
|
||||
"fullName": "Nome Completo",
|
||||
"password": "Nova Palavra-passe",
|
||||
"passwordRepeat": "Repetir Palavra-passe",
|
||||
"errorPassword": "A palavra-passe deve ter pelo menos 8 carateres",
|
||||
"errorPasswordNoMatch": "As palavra-passe não coincidem",
|
||||
"setupAction": "Configurar",
|
||||
"welcomeTo": "Bem-vindo ao",
|
||||
"description": "Por favor, configure a sua conta",
|
||||
"username": "Nome de utilizador",
|
||||
"success": {
|
||||
"title": "A sua Conta está pronta",
|
||||
"openDashboardAction": "Abrir Painel"
|
||||
},
|
||||
"noUsername": {
|
||||
"title": "Não é possível configurar a conta"
|
||||
}
|
||||
},
|
||||
"passwordResetEmail": {
|
||||
"salutation": "Olá <%= user %>,",
|
||||
"resetAction": "Clique para redefinir a sua palavra-passe"
|
||||
},
|
||||
"backup": {
|
||||
"target": {
|
||||
"label": "Site da Cópia de Segurança"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
"main": {
|
||||
"logout": "නික්මෙන්න",
|
||||
"actions": "ක්රියාමාර්ග",
|
||||
"prettyDate": {
|
||||
"minutesAgo": "විනාඩි {{ m }} ට පෙර",
|
||||
"hoursAgo": "හෝරා {{ h }} ට පෙර",
|
||||
"justNow": "මේ දැන්",
|
||||
"yeserday": "ඊයේ"
|
||||
},
|
||||
"dialog": {
|
||||
"cancel": "අවලංගු",
|
||||
"save": "සුරකින්න",
|
||||
@@ -19,10 +13,6 @@
|
||||
"table": {
|
||||
"date": "දිනය"
|
||||
},
|
||||
"pagination": {
|
||||
"prev": "පෙර",
|
||||
"next": "ඊළඟ"
|
||||
},
|
||||
"searchPlaceholder": "සොයන්න",
|
||||
"multiselect": {
|
||||
"select": "තෝරන්න"
|
||||
@@ -30,35 +20,18 @@
|
||||
},
|
||||
"appstore": {
|
||||
"category": {
|
||||
"chat": "සම්භාෂණය",
|
||||
"learning": "ඉගෙනීම",
|
||||
"project": "ව්යාපෘති කළමනාකරණය",
|
||||
"all": "සියල්ල",
|
||||
"popular": "ජනප්රිය",
|
||||
"newApps": "නව යෙදුම්",
|
||||
"analytics": "විශ්ලේෂ",
|
||||
"document": "ලේඛන",
|
||||
"crm": "පා.ස.ක. (CRM)",
|
||||
"finance": "මූල්ය",
|
||||
"email": "වි-තැපෑල",
|
||||
"game": "ක්රීඩා",
|
||||
"media": "මාධ්ය",
|
||||
"notes": "සටහන්"
|
||||
"newApps": "නව යෙදුම්"
|
||||
},
|
||||
"title": "යෙදුම් ගබඩාව",
|
||||
"installDialog": {
|
||||
"location": "ස්ථානය",
|
||||
"groups": "සමූහ"
|
||||
},
|
||||
"accountDialog": {
|
||||
"password": "මුරපදය",
|
||||
"email": "වි-තැපෑල"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"title": "මාගේ යෙදුම්",
|
||||
"infoActionTooltip": "තොරතුරු",
|
||||
"searchPlaceholder": "යෙදුම් සොයන්න",
|
||||
"domainsFilterHeader": "සියලුම වසම්"
|
||||
"searchPlaceholder": "යෙදුම් සොයන්න"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
<!-- Modal postinstall confirm -->
|
||||
<div class="modal fade" id="appsPostInstallConfirmModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<img ng-src="{{appPostInstallConfirm.app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-info-icon"/>
|
||||
<div class="app-info-title">
|
||||
{{ appPostInstallConfirm.app.manifest.title }}<br/>
|
||||
<span class="text-muted text-small">{{ 'app.appInfo.package' | tr }} <a ng-href="/#/appstore/{{appPostInstallConfirm.app.manifest.id}}?version={{appPostInstallConfirm.app.manifest.version}}">v{{ appPostInstallConfirm.app.manifest.version }}</a> </span>
|
||||
<br/>
|
||||
<span ng-show="appPostInstallConfirm.app.manifest.documentationUrl" class="text-small"><a target="_blank" ng-href="{{appPostInstallConfirm.app.manifest.documentationUrl}}">{{ 'app.docsAction' | tr }}</a> </span>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!--
|
||||
<p ng-show="appPostInstallConfirm.app.manifest.addons.email">{{ 'app.appInfo.ssoEmail' | tr }}</p>
|
||||
<p ng-show="appPostInstallConfirm.app.sso && !appPostInstallConfirm.app.manifest.addons.email">{{ 'app.appInfo.sso' | tr }}</p>
|
||||
-->
|
||||
<div ng-bind-html="appPostInstallConfirm.message | markdown2html"></div>
|
||||
<div ng-show="appPostInstallConfirm.app.manifest.documentationUrl" ng-bind-html="'app.appInfo.appDocsUrl' | tr:{ docsUrl: appPostInstallConfirm.app.manifest.documentationUrl, title: appPostInstallConfirm.app.manifest.title, forumUrl: (appPostInstallConfirm.app.manifest.forumUrl || 'https://forum.cloudron.io') }"></div>
|
||||
|
||||
<div style="margin-top: 10px; margin-bottom: 5px;" ng-show="pendingChecklistItems(appPostInstallConfirm.app)">
|
||||
<label class="control-label">{{ 'app.appInfo.checklist' | tr }}</label>
|
||||
</div>
|
||||
<div ng-repeat="item in appPostInstallConfirm.app.checklist">
|
||||
<div class="checklist-item" ng-hide="item.acknowledged">
|
||||
<span ng-bind-html="item.message | markdown2html"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
||||
<a class="btn btn-success" ng-href="{{ 'https://' + appPostInstallConfirm.app.fqdn }}" target="_blank" ng-click="appPostInstallConfirm.submit()">{{ 'app.appInfo.openAction' | tr:{ app: appPostInstallConfirm.app.manifest.title } }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal applinks edit -->
|
||||
<div class="modal fade" id="applinksEditModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'app.editApplinkDialog.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="applinksEditForm" role="form" ng-submit="applinksEdit.submit()" autocomplete="off">
|
||||
<div class="form-group" ng-class="{ 'has-error': (applinksEditForm.upstreamUri.$dirty && applinksEditForm.upstreamUri.$invalid) || (!applinksEditForm.upstreamUri.$dirty && applinksEdit.error.upstreamUri) }">
|
||||
<label class="control-label">{{ 'app.applinks.upstreamUri' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="applinksEdit.upstreamUri" name="upstreamUri" id="inputUpstreamUri" autofocus autocomplete="off" required>
|
||||
<span class="text-danger" ng-show="applinksEdit.error.upstreamUri">{{ applinksEdit.error.upstreamUri }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'app.applinks.label' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="applinksEdit.label" name="label" id="inputLabel" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<label class="control-label">{{ 'app.display.icon' | tr }}</label>
|
||||
</div>
|
||||
<div id="previewIcon" class="app-custom-icon" ng-click="applinksEdit.showCustomIconSelector()">
|
||||
<img ng-src="{{ applinksEdit.iconUrl() || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)"/>
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
<a href="" style="font-weight: normal;" ng-click="applinksEdit.resetCustomIcon()">{{ 'app.applinks.clearIconAction' | tr }}</a> - <span class="text-small">{{ 'app.applinks.clearIconDescription' | tr }}</span>
|
||||
<input type="file" id="applinksEditIconFileInput" style="display: none" accept="image/png"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'app.display.tags' | tr }}</label>
|
||||
<tag-input class="form-control" placeholder="{{ 'app.display.tagsPlaceholder' | tr }}" taglist="applinksEdit.tags" name="tags" uib-tooltip="{{ 'app.display.tagsTooltip' | tr }}"></tag-input>
|
||||
</div>
|
||||
|
||||
<label class="control-label">{{ 'app.accessControl.userManagement.dashboardVisibility' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="applinksEdit.accessRestrictionOption" value="any">
|
||||
<span>{{ 'app.accessControl.userManagement.visibleForAllUsers' | tr }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="applinksEdit.accessRestrictionOption" value="groups">
|
||||
<span>{{ 'app.accessControl.userManagement.visibleForSelected' | tr }}</span>
|
||||
<span class="label label-danger" ng-show="applinksEdit.accessRestrictionOption === 'groups' && !applinksEdit.isAccessRestrictionValid()">{{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin-left: 20px; display: flex;">
|
||||
<div>
|
||||
{{ 'appstore.installDialog.users' | tr }}: <multiselect name="accessUsersSelect" class="input-sm stretch" ng-model="applinksEdit.accessRestriction.users" ng-disabled="applinksEdit.accessRestrictionOption !== 'groups'" options="(user.username || user.email) for user in allUsers" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ 'appstore.installDialog.groups' | tr }}: <multiselect name="accessGroupsSelect" class="input-sm stretch" ng-model="applinksEdit.accessRestriction.groups" ng-disabled="applinksEdit.accessRestrictionOption !== 'groups'" options="group.name for group in allGroups" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="applinksEditForm.$invalid || applinksEdit.busyEdit || applinks.busyRemove"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger pull-left" ng-click="applinksEdit.remove()" ng-disabled="applinksEdit.busyRemove || applinksEdit.busyEdit"><i class="fa fa-circle-notch fa-spin" ng-show="applinksEdit.busyRemove"></i> {{ 'app.editApplinkDialog.deleteAction' | tr }}</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
||||
<button type="button" class="btn btn-success" ng-click="applinksEdit.submit()" ng-disabled="applinksEditForm.$invalid || applinksEdit.busyRemove || applinksEdit.busyEdit"><i class="fa fa-circle-notch fa-spin" ng-show="applinksEdit.busyEdit"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content content-large">
|
||||
|
||||
<!-- Workaround for select-all issue, see commit message -->
|
||||
<div style="font-size: 1px;"> </div>
|
||||
|
||||
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length === 0 && user.isAtLeastAdmin">
|
||||
<div class="col-md-12" style="text-align: center;">
|
||||
<br/><br/><br/><br/>
|
||||
<h1><i class="fa fa-cloud-download fa-fw"></i> {{ 'apps.noApps.title' | tr }}</h1>
|
||||
<br/></br>
|
||||
<h3 ng-bind-html="'apps.noApps.description' | tr:{ appStoreLink: '#/appstore' }"></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length === 0 && !user.isAtLeastAdmin">
|
||||
<div class="col-md-12" style="text-align: center;">
|
||||
<br/><br/><br/><br/>
|
||||
<h1>{{ 'apps.noAccess.title' | tr }}</h1>
|
||||
<br/></br>
|
||||
<h3>{{ 'apps.noAccess.description' | tr }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="view-header" ng-show="installedApps.length > 0">
|
||||
{{ 'apps.title' | tr }}
|
||||
<div class="view-header-search-bar">
|
||||
<form class="form-inline">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="{{ 'apps.searchPlaceholder' | tr }} ( / )" id="appSearch" ng-model="appSearch"/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-class="{ 'active': showFilter }" ng-click="toggleFilter()"><i class="fas fa-filter"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
<button class="btn btn-default" style="margin-left: 10px;" type="button" ng-click="toggleView()"><i class="fas" ng-class="{ 'fa-list': view === VIEWS.GRID, 'fa-grip': view === VIEWS.LIST }"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<div ng-show="showFilter" class="view-header-filter-bar">
|
||||
<form class="form-inline">
|
||||
<multiselect ng-model="selectedGroup" ng-show="user.isAtLeastAdmin && groups.length > 1" ms-header="{{ selectedGroup.name }}" options="group.name for group in groups" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
<multiselect ng-model="selectedState" ng-show="user.isAtLeastAdmin" ms-header="{{ 'apps.stateFilterHeader' | tr }}" ms-selected="{{ selectedState }}" options="state.label for state in states" data-multiple="false"></multiselect>
|
||||
<multiselect ng-model="selectedTags" ng-show="user.isAtLeastAdmin && tags.length > 0" ms-header="{{ 'apps.tagsFilterHeaderAll' | tr }}" ms-selected="{{ 'apps.tagsFilterHeader' | tr:{ tags: selectedTags.join(', ') } }}" options="tag for tag in tags" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
<multiselect ng-model="selectedDomain" data-compare-by="domain" ms-selected="{{ selectedDomain.domain }}" options="domain.domain for domain in filterDomains" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
<!-- <button class="btn btn-primary" ng-disabled="!selectedTags.length && !selectedState.state && selectedGroup._unset && selectedDomain._alldomains" ng-click="clearAllFilter()">{{ 'apps.filter.clearAll' | tr }}</button> -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
|
||||
<div class="app-grid" ng-show="view === VIEWS.GRID">
|
||||
<div class="grid-item" ng-class="{ 'stopped': app.runState === 'stopped' }" ng-repeat="app in installedApps | selectedGroupAccessFilter:selectedGroup | selectedStateFilter:selectedState | selectedTagFilter:selectedTags | selectedDomainFilter:selectedDomain | appSearchFilter:appSearch | orderBy:orderByFilter">
|
||||
<div class="grid-item-content" uib-tooltip="{{ app.fqdn }}" tooltip-append-to-body="true">
|
||||
<a ng-show="app.type !== APP_TYPES.LINK && isOperator(app)" ng-href="#/app/{{ app.id}}/info" class="btn btn-lg btn-default grid-item-action"><i class="fas fa-cog"></i></a>
|
||||
<div ng-show="app.type === APP_TYPES.LINK && isOperator(app)" ng-click="applinksEdit.show(app)" class="btn btn-lg btn-default grid-item-action"><i class="fas fa-cog"></i></div>
|
||||
<a ng-href="{{ app | applicationLink }}" ng-click="onAppClick(app, $event)" target="_blank">
|
||||
<div class="grid-item-top">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center" style="padding-left: 5px; padding-right: 5px;">
|
||||
<img ng-src="{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" class="app-icon"/>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center">
|
||||
<div class="grid-item-top-title" data-fittext>{{ app.label || app.subdomain || app.fqdn }}</div>
|
||||
<div class="text-muted status" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden" uib-tooltip="{{ app | appProgressMessage }}">
|
||||
{{ app | installationStateLabel }}
|
||||
</div>
|
||||
<div class="status" ng-style="{ 'visibility': isOperator(app) && (app | installationActive) ? 'visible' : 'hidden' }">
|
||||
<div class="progress progress-striped active">
|
||||
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.progress }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="usermanagement-indicator" ng-show="app.type !== APP_TYPES.LINK">
|
||||
<i class="fa-brands fa-openid" ng-show="app.ssoAuth && app.manifest.addons.oidc" uib-tooltip="{{ 'apps.auth.openid' | tr }}" tooltip-placement="right"></i>
|
||||
<i class="fas fa-user" ng-show="app.ssoAuth && (!app.manifest.addons.oidc && !app.manifest.addons.email)" uib-tooltip="{{ 'apps.auth.sso' | tr }}" tooltip-placement="right"></i>
|
||||
<i class="far fa-user" ng-show="!app.ssoAuth && !app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.nosso' | tr }}" tooltip-placement="right"></i>
|
||||
<i class="fas fa-envelope" ng-show="app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.email' | tr }}" tooltip-placement="right"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- we check the version here because the box updater does not know when an app gets updated -->
|
||||
<!-- update info is available to app users. but we should show update indicator only for operators since normal users cannot update -->
|
||||
<div class="app-update-badge" ng-click="showAppConfigure(app, 'updates')" ng-show="isOperator(app) && config.update[app.id].manifest.version && config.update[app.id].manifest.version !== app.manifest.version && (app | installSuccess) && !(app.error || app.runState === 'stopped')" uib-tooltip="Update Available">
|
||||
<i class="fa fa-arrow-up fa-inverse"></i>
|
||||
</div>
|
||||
|
||||
<div class="app-checklist-badge" ng-click="showAppConfigure(app, 'info')" ng-show="pendingChecklistItems(app)">
|
||||
{{ pendingChecklistItems(app) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app-list card card-large" ng-show="view === VIEWS.LIST">
|
||||
<table class="table table-hover" style="margin: 0;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 32px" class="hand" ng-click="setOrderBy('status')"><i ng-show="orderBy === 'status'" class="fas fa-arrow-{{ orderByReverse ? 'up' : 'down' }}-long"></i></th>
|
||||
<th style="width: 32px"> </th>
|
||||
<th style="width: 35%" class="hand" ng-click="setOrderBy('location')">{{ 'app.display.label' | tr }} <i ng-show="orderBy === 'location'" class="fas fa-arrow-{{ orderByReverse ? 'up' : 'down' }}-long"></i></th>
|
||||
<th style="width: 30%" class="hand hide-mobile" ng-click="setOrderBy('app')">App Title<i ng-show="orderBy === 'app'" class="fas fa-arrow-{{ orderByReverse ? 'up' : 'down' }}-long"></i></th>
|
||||
<th style="width: 32px" class="hide-mobile"> </th>
|
||||
<th style="width: 32px" class="hand hide-mobile text-center" ng-click="setOrderBy('sso')"><i class="fas fa-user-lock"></i> <i ng-show="orderBy === 'sso'" class="fas fa-arrow-{{ orderByReverse ? 'up' : 'down' }}-long"></i></th>
|
||||
<th style="width:160px" class="text-right">{{ 'main.actions' | tr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="app-list-item" ng-repeat="app in installedApps | selectedGroupAccessFilter:selectedGroup | selectedStateFilter:selectedState | selectedTagFilter:selectedTags | selectedDomainFilter:selectedDomain | appSearchFilter:appSearch | orderBy:orderByFilter:orderByReverse" uib-tooltip="{{ app | appProgressMessage }}">
|
||||
<td class="elide-table-cell">
|
||||
<i class="fa fa-circle" ng-class="app | installationStateClass" uib-tooltip="{{ app | installationStateLabel }}"></i>
|
||||
</td>
|
||||
<td class="elide-table-cell app-list-app-link-cell">
|
||||
<a ng-href="{{ app | applicationLink }}" ng-click="onAppClick(app, $event)" target="_blank" class="app-list-app-link">
|
||||
<img ng-src="{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" class="app-list-item-icon"/>
|
||||
</a>
|
||||
</td>
|
||||
<td class="elide-table-cell app-list-app-link-cell">
|
||||
<a ng-href="{{ app | applicationLink }}" ng-click="onAppClick(app, $event)" target="_blank" class="app-list-app-link">
|
||||
<span style="font-size: 16px;">{{ app.label || app.subdomain || app.fqdn }}</span><br/>
|
||||
<span class="text-muted text-small">{{ app.fqdn.indexOf('http') === 0 ? app.fqdn : 'https://'+app.fqdn }}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="elide-table-cell hide-mobile">{{ app.manifest.title || 'App Link' }}</td>
|
||||
<td class="elide-table-cell hide-mobile text-center">
|
||||
<a class="badge badge-danger" ng-show="pendingChecklistItems(app)" ng-href="#/app/{{ app.id}}/info">{{ pendingChecklistItems(app) }}</a>
|
||||
</td>
|
||||
<td class="elide-table-cell hide-mobile text-center">
|
||||
<div ng-show="app.type !== APP_TYPES.LINK">
|
||||
<i class="fa-brands fa-openid" ng-show="app.ssoAuth && app.manifest.addons.oidc" uib-tooltip="{{ 'apps.auth.openid' | tr }}"></i>
|
||||
<i class="fas fa-user" ng-show="app.ssoAuth && (!app.manifest.addons.oidc && !app.manifest.addons.email)" uib-tooltip="{{ 'apps.auth.sso' | tr }}"></i>
|
||||
<i class="far fa-user" ng-show="!app.ssoAuth && !app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.nosso' | tr }}"></i>
|
||||
<i class="fas fa-envelope" ng-show="app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.email' | tr }}"></i>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right" style="vertical-align: middle; white-space: nowrap;">
|
||||
<span ng-show="isOperator(app)">
|
||||
<a class="btn btn-xs btn-success" style="padding: 1px 7px;" ng-show="config.update[app.id].manifest.version && config.update[app.id].manifest.version !== app.manifest.version && (app | installSuccess) && !(app.error || app.runState === 'stopped')" ng-href="#/app/{{ app.id}}/updates" uib-tooltip="Update Available"><i class="fa fa-arrow-up"></i></a>
|
||||
|
||||
<div class="btn-group btn-group-xs" role="group">
|
||||
<a class="btn btn-xs btn-default" ng-show="app.type !== APP_TYPES.LINK" ng-href="{{ '/logs.html?appId=' + app.id }}" target="_blank" tooltip-append-to-body="true" uib-tooltip="{{ 'app.logsActionTooltip' | tr }}"><i class="fas fa-align-left"></i></a>
|
||||
<a class="btn btn-xs btn-default" ng-show="app.type !== APP_TYPES.PROXIED && app.type !== APP_TYPES.LINK" ng-href="{{ '/terminal.html?id=' + app.id }}" target="_blank" tooltip-append-to-body="true" uib-tooltip="{{ 'app.terminalActionTooltip' | tr }}"><i class="fa fa-terminal"></i></a>
|
||||
<a class="btn btn-xs btn-default" ng-show="app.manifest.addons.localstorage" ng-href="{{ '/filemanager.html#/home/app/' + app.id }}" target="_blank" tooltip-append-to-body="true" uib-tooltip="{{ 'app.filemanagerActionTooltip' | tr }}"><i class="fas fa-folder"></i></a>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-xs btn-default" ng-show="app.type === APP_TYPES.LINK" ng-click="applinksEdit.show(app)" uib-tooltip="Configure Applink"><i class="fa fa-cog"></i></button>
|
||||
<a class="btn btn-xs btn-default" ng-show="app.type !== APP_TYPES.LINK" ng-href="#/app/{{ app.id}}/info" uib-tooltip="Configure App"><i class="fa fa-cog"></i></a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
<div>
|
||||
{{ 'apps.apps.count' | tr:{ count: (installedApps | selectedGroupAccessFilter:selectedGroup | selectedStateFilter:selectedState | selectedTagFilter:selectedTags | selectedDomainFilter:selectedDomain | appSearchFilter:appSearch).length } }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,389 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular:false */
|
||||
/* global $:false */
|
||||
/* global APP_TYPES */
|
||||
/* global onAppClick */
|
||||
|
||||
angular.module('Application').controller('AppsController', ['$scope', '$translate', '$interval', '$location', 'Client', function ($scope, $translate, $interval, $location, Client) {
|
||||
var ALL_DOMAINS_DOMAIN = { _alldomains: true, domain: 'All Domains' }; // dummy record for the single select filter
|
||||
var GROUP_ACCESS_UNSET = { _unset: true, name: 'Select Group' }; // dummy record for the single select filter
|
||||
|
||||
$scope.installedApps = Client.getInstalledApps();
|
||||
$scope.tags = Client.getAppTags();
|
||||
$scope.states = [
|
||||
{ state: '', label: 'All States' },
|
||||
{ state: 'running', label: 'Running' },
|
||||
{ state: 'stopped', label: 'Stopped' },
|
||||
{ state: 'update_available', label: 'Update Available' },
|
||||
{ state: 'not_responding', label: 'Not Responding' }
|
||||
];
|
||||
$scope.selectedState = $scope.states[0];
|
||||
$scope.selectedTags = [];
|
||||
$scope.selectedGroup = GROUP_ACCESS_UNSET;
|
||||
$scope.selectedDomain = ALL_DOMAINS_DOMAIN;
|
||||
$scope.filterDomains = [ ALL_DOMAINS_DOMAIN ];
|
||||
$scope.config = Client.getConfig();
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.domains = [];
|
||||
$scope.appSearch = '';
|
||||
$scope.groups = [ GROUP_ACCESS_UNSET ];
|
||||
$scope.APP_TYPES = APP_TYPES;
|
||||
$scope.showFilter = false;
|
||||
$scope.filterActive = false;
|
||||
|
||||
$scope.VIEWS = {
|
||||
GRID: 'grid',
|
||||
LIST: 'list'
|
||||
};
|
||||
$scope.view = $scope.VIEWS.GRID;
|
||||
|
||||
$scope.orderBy = 'location'; // or app, status, sso
|
||||
$scope.orderByReverse = false;
|
||||
|
||||
$scope.allUsers = [];
|
||||
$scope.allGroups = [];
|
||||
|
||||
$translate(['apps.stateFilterHeader', 'apps.domainsFilterHeader', 'apps.groupsFilterHeader', 'app.states.running', 'app.states.stopped', 'app.states.notResponding', 'app.states.updateAvailable']).then(function (tr) {
|
||||
if (tr['apps.domainsFilterHeader']) ALL_DOMAINS_DOMAIN.domain = tr['apps.domainsFilterHeader'];
|
||||
if (tr['apps.groupsFilterHeader']) GROUP_ACCESS_UNSET.name = tr['apps.groupsFilterHeader'];
|
||||
if (tr['apps.stateFilterHeader']) $scope.states[0].label = tr['apps.stateFilterHeader'];
|
||||
if (tr['app.states.running']) $scope.states[1].label = tr['app.states.running'];
|
||||
if (tr['app.states.stopped']) $scope.states[2].label = tr['app.states.stopped'];
|
||||
if (tr['app.states.notResponding']) $scope.states[4].label = tr['app.states.notResponding'];
|
||||
if (tr['app.states.updateAvailable']) $scope.states[3].label = tr['app.states.updateAvailable'];
|
||||
});
|
||||
|
||||
$scope.pendingChecklistItems = function (app) {
|
||||
if (!app.checklist) return 0;
|
||||
return Object.keys(app.checklist).filter(function (key) { return !app.checklist[key].acknowledged; }).length;
|
||||
};
|
||||
|
||||
$scope.setOrderBy = function (by) {
|
||||
if (by === $scope.orderBy) {
|
||||
$scope.orderByReverse = !$scope.orderByReverse;
|
||||
} else {
|
||||
$scope.orderBy = by;
|
||||
$scope.orderByReverse = false;
|
||||
}
|
||||
|
||||
localStorage.appsOrderBy = by;
|
||||
if ($scope.orderByReverse) localStorage.appsOrderByReverse = true;
|
||||
else localStorage.removeItem('appsOrderByReverse');
|
||||
};
|
||||
|
||||
// for sorting/grouping
|
||||
$scope.orderByFilter = function (item) {
|
||||
if ($scope.orderBy === 'app') return item.manifest.title || 'App Link';
|
||||
if ($scope.orderBy === 'status') return item.installationState + '-' + item.runState;
|
||||
if ($scope.orderBy === 'sso') {
|
||||
if (item.ssoAuth && item.manifest.addons.oidc) return 'oidc';
|
||||
if (item.ssoAuth && (!item.manifest.addons.oidc && !item.manifest.addons.email)) return 'sso';
|
||||
if (item.manifest.addons.email) return 'email';
|
||||
return '';
|
||||
}
|
||||
return item.label || item.fqdn;
|
||||
};
|
||||
|
||||
$scope.setView = function (view) {
|
||||
if (view !== $scope.VIEWS.LIST && view !== $scope.VIEWS.GRID) return;
|
||||
|
||||
$scope.view = view;
|
||||
localStorage.appsView = view;
|
||||
};
|
||||
|
||||
$scope.toggleView = function () {
|
||||
$scope.view = $scope.view === $scope.VIEWS.GRID ? $scope.VIEWS.LIST : $scope.VIEWS.GRID;
|
||||
localStorage.appsView = $scope.view;
|
||||
};
|
||||
|
||||
$scope.toggleFilter = function () {
|
||||
$scope.showFilter = !$scope.showFilter;
|
||||
|
||||
if ($scope.showFilter) localStorage.appsShowFilter = true;
|
||||
else localStorage.removeItem('appsShowFilter');
|
||||
|
||||
// clear on hide
|
||||
if (!$scope.showFilter) {
|
||||
$scope.selectedState = $scope.states[0];
|
||||
$scope.selectedTags = [];
|
||||
$scope.selectedGroup = GROUP_ACCESS_UNSET;
|
||||
$scope.selectedDomain = ALL_DOMAINS_DOMAIN;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('selectedTags', function (newVal, oldVal) {
|
||||
if (newVal === oldVal) return;
|
||||
|
||||
localStorage.selectedTags = newVal.join(',');
|
||||
});
|
||||
|
||||
$scope.$watch('selectedState', function (newVal, oldVal) {
|
||||
if (newVal === oldVal) return;
|
||||
|
||||
if (newVal === $scope.states[0]) localStorage.removeItem('selectedState');
|
||||
else localStorage.selectedState = newVal.state;
|
||||
});
|
||||
|
||||
$scope.$watch('selectedGroup', function (newVal, oldVal) {
|
||||
if (newVal === oldVal) return;
|
||||
|
||||
if (newVal === GROUP_ACCESS_UNSET) localStorage.removeItem('selectedGroup');
|
||||
else localStorage.selectedGroup = newVal.id;
|
||||
});
|
||||
|
||||
$scope.$watch('selectedDomain', function (newVal, oldVal) {
|
||||
if (newVal === oldVal) return;
|
||||
|
||||
if (newVal._alldomains) localStorage.removeItem('selectedDomain');
|
||||
else localStorage.selectedDomain = newVal.domain;
|
||||
});
|
||||
|
||||
$scope.onAppClick = function (app, $event) { onAppClick(app, $event, $scope.isOperator(app), $scope); };
|
||||
|
||||
$scope.appPostInstallConfirm = {
|
||||
app: {},
|
||||
message: '',
|
||||
|
||||
show: function (app) {
|
||||
$scope.appPostInstallConfirm.app = app;
|
||||
$scope.appPostInstallConfirm.message = app.manifest.postInstallMessage;
|
||||
|
||||
$('#appsPostInstallConfirmModal').modal('show');
|
||||
|
||||
return false; // prevent propagation and default
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.appPostInstallConfirm.app.pendingPostInstallConfirmation = false;
|
||||
delete localStorage['confirmPostInstall_' + $scope.appPostInstallConfirm.app.id];
|
||||
|
||||
$('#appsPostInstallConfirmModal').modal('hide');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.applinksEdit = {
|
||||
error: {},
|
||||
busyEdit: false,
|
||||
busyRemove: false,
|
||||
applink: {},
|
||||
id: '',
|
||||
upstreamUri: '',
|
||||
label: '',
|
||||
tags: '',
|
||||
accessRestrictionOption: '',
|
||||
accessRestriction: { users: [], groups: [] },
|
||||
icon: { data: null },
|
||||
|
||||
iconUrl: function () {
|
||||
if ($scope.applinksEdit.icon.data === '__original__') { // user clicked reset
|
||||
// https://png-pixel.com/ white pixel placeholder
|
||||
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=';
|
||||
} else if ($scope.applinksEdit.icon.data) { // user uploaded icon
|
||||
return $scope.applinksEdit.icon.data;
|
||||
} else { // current icon
|
||||
return $scope.applinksEdit.applink.iconUrl;
|
||||
}
|
||||
},
|
||||
|
||||
resetCustomIcon: function () {
|
||||
$scope.applinksEdit.icon.data = '__original__';
|
||||
},
|
||||
|
||||
showCustomIconSelector: function () {
|
||||
$('#applinksEditIconFileInput').click();
|
||||
},
|
||||
|
||||
isAccessRestrictionValid: function () {
|
||||
return !!($scope.applinksEdit.accessRestriction.users.length || $scope.applinksEdit.accessRestriction.groups.length);
|
||||
},
|
||||
|
||||
show: function (applink) {
|
||||
$scope.applinksEdit.error = {};
|
||||
$scope.applinksEdit.busyEdit = false;
|
||||
$scope.applinksEdit.busyRemove = false;
|
||||
$scope.applinksEdit.applink = applink;
|
||||
$scope.applinksEdit.id = applink.id;
|
||||
$scope.applinksEdit.upstreamUri = applink.upstreamUri;
|
||||
$scope.applinksEdit.label = applink.label;
|
||||
$scope.applinksEdit.accessRestrictionOption = applink.accessRestriction ? 'groups' : 'any';
|
||||
$scope.applinksEdit.accessRestriction = { users: [], groups: [] };
|
||||
$scope.applinksEdit.icon = { data: null };
|
||||
|
||||
var userSet, groupSet;
|
||||
if (applink.accessRestriction) {
|
||||
userSet = {};
|
||||
applink.accessRestriction.users.forEach(function (uid) { userSet[uid] = true; });
|
||||
$scope.allUsers.forEach(function (u) { if (userSet[u.id] === true) $scope.applinksEdit.accessRestriction.users.push(u); });
|
||||
|
||||
groupSet = {};
|
||||
if (applink.accessRestriction.groups) applink.accessRestriction.groups.forEach(function (gid) { groupSet[gid] = true; });
|
||||
$scope.allGroups.forEach(function (g) { if (groupSet[g.id] === true) $scope.applinksEdit.accessRestriction.groups.push(g); });
|
||||
}
|
||||
|
||||
// translate for tag-input
|
||||
$scope.applinksEdit.tags = applink.tags ? applink.tags.join(' ') : '';
|
||||
|
||||
$scope.applinksEditForm.$setUntouched();
|
||||
$scope.applinksEditForm.$setPristine();
|
||||
|
||||
$('#applinksEditModal').modal('show');
|
||||
|
||||
return false; // prevent propagation and default
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.applinksEdit.busyEdit = true;
|
||||
$scope.applinksEdit.error = {};
|
||||
|
||||
var accessRestriction = null;
|
||||
if ($scope.applinksEdit.accessRestrictionOption === 'groups') {
|
||||
accessRestriction = { users: [], groups: [] };
|
||||
accessRestriction.users = $scope.applinksEdit.accessRestriction.users.map(function (u) { return u.id; });
|
||||
accessRestriction.groups = $scope.applinksEdit.accessRestriction.groups.map(function (g) { return g.id; });
|
||||
}
|
||||
|
||||
var data = {
|
||||
upstreamUri: $scope.applinksEdit.upstreamUri,
|
||||
label: $scope.applinksEdit.label,
|
||||
accessRestriction: accessRestriction,
|
||||
tags: $scope.applinksEdit.tags.split(' ').map(function (t) { return t.trim(); }).filter(function (t) { return !!t; })
|
||||
};
|
||||
|
||||
if ($scope.applinksEdit.icon.data === '__original__') { // user reset the icon
|
||||
data.icon = '';
|
||||
} else if ($scope.applinksEdit.icon.data) { // user loaded custom icon
|
||||
data.icon = $scope.applinksEdit.icon.data.replace(/^data:image\/[a-z]+;base64,/, '');
|
||||
}
|
||||
|
||||
Client.updateApplink($scope.applinksEdit.id, data, function (error) {
|
||||
$scope.applinksEdit.busyEdit = false;
|
||||
|
||||
if (error && error.statusCode === 400 && error.message.includes('upstreamUri')) {
|
||||
$scope.applinksEdit.error.upstreamUri = error.message;
|
||||
$scope.applinksEditForm.$setUntouched();
|
||||
$scope.applinksEditForm.$setPristine();
|
||||
return;
|
||||
}
|
||||
if (error) return console.error('Failed to update applink', error);
|
||||
|
||||
Client.refreshInstalledApps();
|
||||
|
||||
$('#applinksEditModal').modal('hide');
|
||||
});
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
$scope.applinksEdit.busyRemove = true;
|
||||
|
||||
Client.removeApplink($scope.applinksEdit.id, function (error) {
|
||||
$scope.applinksEdit.busyRemove = false;
|
||||
|
||||
if (error) return console.error('Failed to remove applink', error);
|
||||
|
||||
Client.refreshInstalledApps();
|
||||
|
||||
$('#applinksEditModal').modal('hide');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showAppConfigure = function (app, view) {
|
||||
$location.path('/app/' + app.id + '/' + view);
|
||||
};
|
||||
|
||||
$scope.isOperator = function (app) {
|
||||
return app.accessLevel === 'operator' || app.accessLevel === 'admin';
|
||||
};
|
||||
|
||||
Client.onReady(function () {
|
||||
setTimeout(function () { $('#appSearch').focus(); }, 1);
|
||||
|
||||
// refresh the new list immediately when switching from another view (appstore)
|
||||
Client.refreshInstalledApps(function () {
|
||||
var refreshAppsTimer = $interval(Client.refreshInstalledApps.bind(Client, function () {}), 5000);
|
||||
$scope.$on('$destroy', function () {
|
||||
$interval.cancel(refreshAppsTimer);
|
||||
});
|
||||
});
|
||||
|
||||
$scope.setView(localStorage.appsView);
|
||||
|
||||
$scope.orderBy = localStorage.appsOrderBy || 'location';
|
||||
$scope.orderByReverse = !!localStorage.appsOrderByReverse;
|
||||
$scope.showFilter = !!localStorage.appsShowFilter;
|
||||
|
||||
if (!$scope.user.isAtLeastAdmin) return;
|
||||
|
||||
// load local settings and apply tag filter
|
||||
if (localStorage.selectedTags) {
|
||||
if (!$scope.tags.length) localStorage.removeItem('selectedTags');
|
||||
else $scope.selectedTags = localStorage.selectedTags.split(',');
|
||||
}
|
||||
|
||||
if (localStorage.selectedState) $scope.selectedState = $scope.states.find(function (s) { return s.state === localStorage.selectedState; }) || $scope.states[0];
|
||||
|
||||
Client.getGroups(function (error, result) {
|
||||
if (error) Client.error(error);
|
||||
|
||||
$scope.groups = [ GROUP_ACCESS_UNSET ].concat(result);
|
||||
$scope.allGroups = result;
|
||||
|
||||
if (localStorage.selectedGroup) $scope.selectedGroup = $scope.groups.find(function (g) { return g.id === localStorage.selectedGroup; }) || GROUP_ACCESS_UNSET;
|
||||
});
|
||||
|
||||
Client.getDomains(function (error, result) {
|
||||
if (error) Client.error(error);
|
||||
|
||||
$scope.domains = result;
|
||||
$scope.filterDomains = [ALL_DOMAINS_DOMAIN].concat(result);
|
||||
|
||||
if (localStorage.selectedDomain) $scope.selectedDomain = $scope.filterDomains.find(function (d) { return d.domain === localStorage.selectedDomain; }) || ALL_DOMAINS_DOMAIN;
|
||||
});
|
||||
|
||||
Client.getAllUsers(function (error, users) {
|
||||
if (error) Client.error(error);
|
||||
|
||||
$scope.allUsers = users;
|
||||
});
|
||||
});
|
||||
|
||||
$('#applinksEditIconFileInput').get(0).onchange = function (event) {
|
||||
var fr = new FileReader();
|
||||
fr.onload = function () {
|
||||
$scope.$apply(function () {
|
||||
// var file = event.target.files[0];
|
||||
$scope.applinksEdit.icon.data = fr.result;
|
||||
});
|
||||
};
|
||||
fr.readAsDataURL(event.target.files[0]);
|
||||
};
|
||||
|
||||
// setup all the dialog focus handling
|
||||
['applinksAddModal', 'applinksEditModal'].forEach(function (id) {
|
||||
$('#' + id).on('shown.bs.modal', function () {
|
||||
$(this).find('autofocus]:first').focus();
|
||||
});
|
||||
});
|
||||
|
||||
$('.collapse').on('shown.bs.collapse', function(){
|
||||
$(this).parent().find('.fa-angle-right').removeClass('fa-angle-right').addClass('fa-angle-down');
|
||||
}).on('hidden.bs.collapse', function(){
|
||||
$(this).parent().find('.fa-angle-down').removeClass('fa-angle-down').addClass('fa-angle-right');
|
||||
});
|
||||
|
||||
$('.modal-backdrop').remove();
|
||||
|
||||
function keyboardHandler(event) {
|
||||
if (event.key === '/') {
|
||||
document.getElementById('appSearch').focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', keyboardHandler);
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
document.removeEventListener('keydown', keyboardHandler);
|
||||
});
|
||||
}]);
|
||||
@@ -1,481 +0,0 @@
|
||||
|
||||
<!-- Modal install app -->
|
||||
<div class="modal fade appstore-install" id="appInstallModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<img ng-src="{{appInstall.app.iconUrl}}" onerror="this.onerror=null; this.src='img/appicon_fallback.png'" class="app-icon"/>
|
||||
<h3 class="appstore-install-title">{{ appInstall.app.manifest.title }}</h3>
|
||||
<br/>
|
||||
<span class="appstore-install-meta"><a href="{{ appInstall.app.manifest.website }}" target="_blank">{{ appInstall.app.manifest.author }}</a></span>
|
||||
<br/>
|
||||
<span class="appstore-install-meta">{{ 'appstore.installDialog.lastUpdated' | tr:{ date: (appInstall.app.creationDate | prettyDate) } }}</span>
|
||||
<br/>
|
||||
<span class="appstore-install-meta">{{ 'appstore.installDialog.memoryRequirement' | tr:{ size: (appInstall.app.manifest.memoryLimit | prettyBinarySize:'256 MB') } }}</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="collapse" id="collapseInstallForm" data-toggle="false">
|
||||
<form role="form" name="appInstallForm" ng-submit="appInstall.submit()" autocomplete="off">
|
||||
<div class="has-error text-center" ng-show="appInstall.error.other" ng-bind-html="appInstall.error.other"></div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (appInstallForm.location.$dirty && appInstallForm.location.$invalid) || (!appInstallForm.location.$dirty && appInstall.error.location) }">
|
||||
<label class="control-label" for="appInstallLocationInput">{{ 'appstore.installDialog.location' | tr }}</label>
|
||||
<div class="input-group form-inline">
|
||||
<input type="text" class="form-control" ng-model="appInstall.subdomain" id="appInstallLocationInput" name="location" placeholder="{{ 'appstore.installDialog.locationPlaceholder' | tr }}" autofocus>
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>{{ '.' + appInstall.domain.domain }}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li ng-repeat="domain in domains">
|
||||
<a href="" ng-click="appInstall.domain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="appInstall.error.location" class="text-small">{{ appInstall.error.location }}</div>
|
||||
</div>
|
||||
|
||||
<p class="text-small text-warning" ng-show="appInstall.domain.provider === 'noop' || appInstall.domain.provider === 'manual'" ng-bind-html="'appstore.installDialog.manualWarning' | tr:{ location: ((appInstall.subdomain ? appInstall.subdomain + '.' : '') + appInstall.domain.domain) }"></p>
|
||||
|
||||
<div class="has-error text-center" ng-show="appInstall.error.secondaryDomain">{{ appInstall.error.secondaryDomain }}</div>
|
||||
<div ng-repeat="(env, info) in appInstall.app.manifest.httpPorts">
|
||||
<ng-form name="secondaryDomainInfo_form">
|
||||
<div class="form-group" ng-class="{ 'has-error': (!secondaryDomainInfo_form.itemName{{$index}}.$dirty && appInstall.error.secondaryDomain) || (secondaryDomainInfo_form.itemName{{$index}}.$dirty && secondaryDomainInfo_form.itemName{{$index}}.$invalid) }">
|
||||
<label class="control-label" for="secondaryDomainInput{{env}}">
|
||||
{{ info.title }}
|
||||
<sup>
|
||||
<a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{info.description}}"><i class="fa fa-question-circle"></i></a>
|
||||
</sup>
|
||||
</label>
|
||||
|
||||
<div class="input-group form-inline">
|
||||
<input type="text" class="form-control" ng-model="appInstall.secondaryDomains[env].subdomain" name="location{{$index}}" placeholder="{{ 'appstore.installDialog.locationPlaceholder' | tr }}" autofocus>
|
||||
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>.{{ appInstall.secondaryDomains[env].domain.domain }}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li ng-repeat="domain in domains">
|
||||
<a href="" ng-click="appInstall.secondaryDomains[env].domain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
|
||||
<div class="has-error text-center" ng-show="appInstall.error.port">{{ appInstall.error.port }}</div>
|
||||
<div ng-repeat="(env, info) in appInstall.portInfo">
|
||||
<ng-form name="portInfo_form">
|
||||
<div class="form-group" ng-class="{ 'has-error': (!appInstallForm.itemName{{$index}}.$dirty && appInstall.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
|
||||
<label class="control-label" for="inputPortInfo{{env}}"><input type="checkbox" ng-model="appInstall.portsEnabled[env]">
|
||||
{{ info.title }}
|
||||
<sup>
|
||||
<a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{info.description}}. {{info.portCount >=1 ? (info.portCount + ' ports. ') : ''}}"><i class="fa fa-question-circle"></i></a>
|
||||
</sup>
|
||||
<small style="padding-left: 5px;" ng-show="info.readOnly">{{ 'appstore.installDialog.portReadOnly' | tr }}</small>
|
||||
</label>
|
||||
<input type="number" class="form-control" ng-model="appInstall.ports[env]" ng-disabled="!appInstall.portsEnabled[env]" ng-readonly="info.readOnly" id="inputPortInfo{{env}}" later-name="itemName{{$index}}" min="{{hostPortMin}}" max="{{hostPortMax}}" required>
|
||||
<p class="text-small text-warning text-bold" ng-show="appInstall.domain.provider === 'cloudflare'">{{ 'appstore.installDialog.cloudflarePortWarning' | tr }} </p>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="isProxyApp(appInstall.app)">
|
||||
<label class="control-label" for="appInstallUpstreamUriInput">Upstream URI</label>
|
||||
<input type="text" class="form-control" ng-model="appInstall.upstreamUri" id="appInstallUpstreamUriInput" name="upstreamUri" ng-required="isProxyApp(appInstall.app)">
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="appInstall.app.manifest.addons.email">
|
||||
<label class="control-label">{{ 'appstore.installDialog.userManagement' | tr }}</label>
|
||||
<p>{{ 'appstore.installDialog.userManagementMailbox' | tr }}
|
||||
<span ng-bind-html="'appstore.installDialog.configuredForCloudronEmail' | tr:{ emailDocsLink: 'https://docs.cloudron.io/email/' }">
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" ng-show="!appInstall.customAuth && !appInstall.app.manifest.addons.email">{{ 'appstore.installDialog.userManagement' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#access-restriction" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<label class="control-label" ng-show="appInstall.customAuth || appInstall.app.manifest.addons.email">{{ 'app.accessControl.userManagement.dashboardVisibility' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
|
||||
<p ng-show="appInstall.customAuth || appInstall.app.manifest.addons.email">{{ 'appstore.installDialog.userManagementNone' | tr }}</p>
|
||||
<div class="radio" ng-show="appInstall.optionalSso">
|
||||
<label>
|
||||
<input type="radio" ng-model="appInstall.accessRestrictionOption" value="nosso"> {{ 'appstore.installDialog.userManagementLeaveToApp' | tr }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="appInstall.accessRestrictionOption" value="any">
|
||||
<span ng-show="!appInstall.customAuth">{{ 'appstore.installDialog.userManagementAllUsers' | tr }}</span>
|
||||
<span ng-show="appInstall.customAuth">{{ 'app.accessControl.userManagement.visibleForAllUsers' | tr }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="appInstall.accessRestrictionOption" value="groups">
|
||||
<span ng-show="!appInstall.customAuth">{{ 'appstore.installDialog.userManagementSelectUsers' | tr }}</span>
|
||||
<span ng-show="appInstall.customAuth">{{ 'app.accessControl.userManagement.visibleForSelected' | tr }}</span>
|
||||
<span class="label label-danger" ng-show="appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()">{{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin-left: 20px;">
|
||||
<div class="col-md-5">
|
||||
{{ 'appstore.installDialog.users' | tr }}:
|
||||
<multiselect ng-model="appInstall.accessRestriction.users" ng-disabled="appInstall.accessRestrictionOption !== 'groups'" options="(user.username || user.email) for user in users" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
{{ 'appstore.installDialog.groups' | tr }}:
|
||||
<multiselect ng-model="appInstall.accessRestriction.groups" ng-disabled="appInstall.accessRestrictionOption !== 'groups'" options="group.name for group in groups" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="(appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()) || !appInstall.accessRestrictionOption || appInstallForm.$invalid || busy"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="collapse" id="collapseMediaLinksCarousel" data-toggle="false">
|
||||
<div ng-repeat="mediaLink in appInstall.mediaLinks" class="slick-item" style="background-image: url('{{mediaLink}}');" ng-show="appInstall.mediaLinks.length == 1"></div>
|
||||
<slick init-onload="true" current-index="0" autoplay="true" arrows="false" autoplay-speed="2000" data="appInstall.mediaLinks" ng-show="appInstall.mediaLinks.length > 1">
|
||||
<div ng-repeat="mediaLink in appInstall.mediaLinks" class="slick-item" style="background-image: url('{{mediaLink}}');"></div>
|
||||
</slick>
|
||||
<br/>
|
||||
<div class="appstore-install-description">
|
||||
<p ng-show="appInstall.app.manifest.upstreamVersion">{{ 'appstore.installDialog.titleAndVersion' | tr:{ title: appInstall.app.manifest.title, version: appInstall.app.manifest.upstreamVersion } }}</p>
|
||||
<div ng-bind-html="appInstall.app.manifest.description | markdown2html"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse" id="collapseResourceConstraint" data-toggle="false">
|
||||
<h4 class="text-danger">{{ 'appstore.installDialog.lowOnResources' | tr }}<sup><a ng-href="https://docs.cloudron.io/apps/#low-resource-warning" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></h4>
|
||||
<p>{{ 'appstore.installDialog.pleaseUpgradeServer' | tr }}</p>
|
||||
</div>
|
||||
<div class="collapse" id="collapseSubscriptionRequired" data-toggle="false">
|
||||
<p>{{ 'appstore.installDialog.subscriptionRequired' | tr }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
||||
<button type="button" class="btn btn-success" ng-click="openSubscriptionSetup()" ng-show="appInstall.state === 'subscriptionRequired'">{{ 'appstore.installDialog.setupSubscriptionAction' | tr }}</button>
|
||||
<button type="button" class="btn btn-danger" ng-show="appInstall.state === 'resourceConstraint'" ng-click="appInstall.showForm(true)">{{ 'appstore.installDialog.installAnywayAction' | tr }}</button>
|
||||
<button type="button" class="btn btn-success" ng-show="appInstall.state === 'appInfo'" ng-click="appInstall.showForm()">{{ 'appstore.installDialog.installAction' | tr }}</button>
|
||||
<button type="button" class="btn btn-success" ng-show="appInstall.state === 'installForm'" ng-click="appInstall.submit()" ng-disabled="(appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()) || !appInstall.accessRestrictionOption || appInstallForm.$invalid || appInstall.busy"><i class="fa fa-circle-notch fa-spin" ng-show="appInstall.busy"></i> {{ 'appstore.installDialog.doInstallAction' | tr:{ dnsOverwrite: appInstall.needsOverwrite } }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal app not found -->
|
||||
<div class="modal fade" id="appNotFoundModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'appstore.appNotFoundDialog.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body" ng-bind-html="'appstore.appNotFoundDialog.description' | tr:{ appId: appNotFound.appId, version: appNotFound.version }"></div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal applinks add -->
|
||||
<div class="modal fade" id="applinksAddModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'app.addApplinkDialog.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="applinksAddForm" role="form" ng-submit="applinksAdd.submit()" autocomplete="off">
|
||||
<div class="form-group" ng-class="{ 'has-error': (applinksAddForm.upstreamUri.$dirty && applinksAddForm.upstreamUri.$invalid) || (!applinksAddForm.upstreamUri.$dirty && applinksAdd.error.upstreamUri) }">
|
||||
<label class="control-label">{{ 'app.applinks.upstreamUri' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="applinksAdd.upstreamUri" name="upstreamUri" id="inputUpstreamUri" autofocus autocomplete="off" required>
|
||||
<span class="text-danger" ng-show="applinksAdd.error.upstreamUri">{{ applinksAdd.error.upstreamUri }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'app.applinks.label' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="applinksAdd.label" name="label" id="inputLabel" autocomplete="off" placeholder="Leave empty for autodetection">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'app.display.tags' | tr }}</label>
|
||||
<tag-input class="form-control" placeholder="{{ 'app.display.tagsPlaceholder' | tr }}" taglist="applinksAdd.tags" name="tags" uib-tooltip="{{ 'app.display.tagsTooltip' | tr }}"></tag-input>
|
||||
</div>
|
||||
|
||||
<label class="control-label">{{ 'app.accessControl.userManagement.dashboardVisibility' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="applinksAdd.accessRestrictionOption" value="any">
|
||||
<span>{{ 'app.accessControl.userManagement.visibleForAllUsers' | tr }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="applinksAdd.accessRestrictionOption" value="groups">
|
||||
<span>{{ 'app.accessControl.userManagement.visibleForSelected' | tr }}</span>
|
||||
<span class="label label-danger" ng-show="applinksAdd.accessRestrictionOption === 'groups' && !applinksAdd.isAccessRestrictionValid()">{{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin-left: 20px; display: flex;">
|
||||
<div>
|
||||
{{ 'appstore.installDialog.users' | tr }}: <multiselect name="accessUsersSelect" class="input-sm stretch" ng-model="applinksAdd.accessRestriction.users" ng-disabled="applinksAdd.accessRestrictionOption !== 'groups'" options="(user.username || user.email) for user in users" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ 'appstore.installDialog.groups' | tr }}: <multiselect name="accessGroupsSelect" class="input-sm stretch" ng-model="applinksAdd.accessRestriction.groups" ng-disabled="applinksAdd.accessRestrictionOption !== 'groups'" options="group.name for group in groups" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="applinksAddForm.$invalid || applinksAdd.busy"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
||||
<button type="button" class="btn btn-success" ng-click="applinksAdd.submit()" ng-disabled="applinksAddForm.$invalid || applinksAdd.busy"><i class="fa fa-circle-notch fa-spin" ng-show="applinksAdd.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="!ready" class="loading-banner">
|
||||
<h1><i class="fa fa-circle-notch fa-spin"></i></h1>
|
||||
</div>
|
||||
|
||||
<!-- appstore login -->
|
||||
<div ng-show="ready && !validSubscription" class="container card card-small appstore-login ng-cloak">
|
||||
<div class="col-md-12 text-center">
|
||||
<h1 ng-show="appstoreLogin.setupType === 'signup'">{{ 'appstore.accountDialog.titleSignUp' | tr }}</h1>
|
||||
<h1 ng-show="appstoreLogin.setupType === 'login'">{{ 'appstore.accountDialog.titleLogin' | tr }}</h1>
|
||||
<h1 ng-show="appstoreLogin.setupType === 'setupToken'">{{ 'appstore.accountDialog.titleToken' | tr }}</h1>
|
||||
</div>
|
||||
<div class="col-md-12 text-center">
|
||||
<p>{{ 'appstore.accountDialog.description' | tr }}</p>
|
||||
</div>
|
||||
<div class="col-md-12" style="margin-bottom: 10px;">
|
||||
<small class="text-danger" ng-show="appstoreLogin.error.generic">{{ appstoreLogin.error.generic }}</small>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<br/>
|
||||
|
||||
<div ng-show="appstoreLogin.setupType === 'signup'">
|
||||
<form name="appstoreSignupForm" role="form" novalidate ng-submit="appstoreLogin.submit()" autocomplete="off">
|
||||
<input type="password" style="display: none;">
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': (appstoreSignupForm.email.$dirty && appstoreSignupForm.email.$invalid) || appstoreLogin.error.generic }">
|
||||
<label class="control-label">{{ 'appstore.accountDialog.email' | tr }}</label>
|
||||
<input type="email" class="form-control" ng-model="appstoreLogin.email" id="inputAppstoreSignupEmail" name="email" required autofocus>
|
||||
<div class="control-label" ng-show="(!appstoreSignupForm.email.$dirty && appstoreLogin.error.email) || (appstoreSignupForm.email.$dirty && appstoreSignupForm.email.$invalid) || appstoreLogin.error.email">
|
||||
<small class="text-danger" ng-show="appstoreLogin.error.email">{{ appstoreLogin.error.email }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': (!appstoreSignupForm.password.$dirty && appstoreLogin.error.signupPassword) || (appstoreSignupForm.password.$dirty && appstoreSignupForm.password.$invalid) || appstoreLogin.error.generic }">
|
||||
<label class="control-label">{{ 'appstore.accountDialog.password' | tr }}</label>
|
||||
<input type="password" class="form-control" ng-model="appstoreLogin.password" id="inputAppstoreSignupPassword" name="password" required password-reveal>
|
||||
<div class="control-label" ng-show="(!appstoreSignupForm.password.$dirty && appstoreLogin.error.signupPassword) || (appstoreSignupForm.password.$dirty && appstoreSignupForm.password.$invalid)">
|
||||
<small ng-show="!appstoreSignupForm.password.$dirty && appstoreLogin.error.signupPassword">{{ 'appstore.accountDialog.errorWrongPassword' | tr }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="appstoreLogin.termsAccepted" ng-required="true"><span ng-bind-html="'appstore.accountDialog.licenseCheckbox' | tr:{ licenseLink: 'https://cloudron.io/legal/license.html' }"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<center>
|
||||
<button type="submit" class="btn btn-lg btn-success" ng-disabled="appstoreSignupForm.$invalid || appstoreLogin.busy">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="appstoreLogin.busy"></i> {{ 'appstore.accountDialog.createAccountAction' | tr }}
|
||||
</button>
|
||||
</center>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div ng-show="appstoreLogin.setupType === 'login'">
|
||||
<form name="appstoreLoginForm" role="form" novalidate ng-submit="appstoreLogin.submit()" autocomplete="off">
|
||||
<input type="password" style="display: none;">
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': (appstoreLoginForm.email.$dirty && appstoreLoginForm.email.$invalid) || appstoreLogin.error.generic }">
|
||||
<label class="control-label">{{ 'appstore.accountDialog.email' | tr }}</label>
|
||||
<input type="email" class="form-control" ng-model="appstoreLogin.email" name="email" required autofocus>
|
||||
<div class="control-label" ng-show="(!appstoreLoginForm.email.$dirty && appstoreLogin.error.email) || (appstoreLoginForm.email.$dirty && appstoreLoginForm.email.$invalid) || appstoreLogin.error.email">
|
||||
<small class="text-danger" ng-show="appstoreLogin.error.email">{{ appstoreLogin.error.email }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': (!appstoreLoginForm.password.$dirty && appstoreLogin.error.loginPassword) || (appstoreLoginForm.password.$dirty && appstoreLoginForm.password.$invalid) || appstoreLogin.error.generic }">
|
||||
<label class="control-label">{{ 'appstore.accountDialog.password' | tr }}</label>
|
||||
<input type="password" class="form-control" ng-model="appstoreLogin.password" id="inputAppstoreLoginPassword" name="password" required password-reveal>
|
||||
<div class="control-label" ng-show="(!appstoreLoginForm.password.$dirty && appstoreLogin.error.loginPassword) || (appstoreLoginForm.password.$dirty && appstoreLoginForm.password.$invalid)">
|
||||
<small ng-show="!appstoreLoginForm.password.$dirty && appstoreLogin.error.loginPassword">{{ 'appstore.accountDialog.errorWrongPassword' | tr }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': appstoreLogin.error.totpToken }">
|
||||
<label class="control-label">{{ 'appstore.accountDialog.2faToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="appstoreLogin.totpToken" id="inputAppstoreLoginTotpToken" name="totpToken">
|
||||
<div class="control-label" ng-show="appstoreLogin.error.totpToken">
|
||||
<small ng-show="appstoreLogin.error.totpToken">{{ appstoreLogin.error.totpToken }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="appstoreLogin.termsAccepted" ng-required="true"><span ng-bind-html="'appstore.accountDialog.licenseCheckbox' | tr:{ licenseLink: 'https://cloudron.io/legal/license.html' }"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<center>
|
||||
<button type="submit" class="btn btn-lg btn-success" ng-disabled="appstoreLoginForm.$invalid || appstoreLogin.busy">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="appstoreLogin.busy"></i> {{ 'appstore.accountDialog.loginAction' | tr }}
|
||||
</button>
|
||||
</center>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div ng-show="appstoreLogin.setupType === 'setupToken'">
|
||||
<form name="appstoreSetupTokenForm" role="form" novalidate ng-submit="appstoreLogin.submit()" autocomplete="off">
|
||||
<input type="password" style="display: none;">
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': appstoreLogin.error.setupToken }">
|
||||
<label class="control-label">{{ 'appstore.accountDialog.setupToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="appstoreLogin.setupToken" id="inputAppstoreSetupToken" name="setupToken" ng-required="true">
|
||||
<div class="control-label" ng-show="appstoreLogin.error.setupToken">
|
||||
<small ng-show="appstoreLogin.error.setupToken">{{ appstoreLogin.error.setupToken }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="appstoreLogin.termsAccepted" ng-required="true"><span ng-bind-html="'appstore.accountDialog.licenseCheckbox' | tr:{ licenseLink: 'https://cloudron.io/legal/license.html' }"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<center>
|
||||
<button type="submit" class="btn btn-lg btn-success" ng-disabled="appstoreSetupTokenForm.$invalid || appstoreLogin.busy">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="appstoreLogin.busy"></i> {{ 'appstore.accountDialog.setupWithTokenAction' | tr }}
|
||||
</button>
|
||||
</center>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<center>
|
||||
<a href="" ng-click="appstoreLogin.setupType = 'signup'" ng-show="appstoreLogin.setupType === 'login'">{{ 'appstore.accountDialog.switchToSignUpAction' | tr }}</a>
|
||||
<a href="" ng-click="appstoreLogin.setupType = 'login'" ng-show="appstoreLogin.setupType === 'signup' || appstoreLogin.setupType === 'setupToken'">{{ 'appstore.accountDialog.switchToLoginAction' | tr }}</a>
|
||||
<span ng-show="appstoreLogin.setupType !== 'setupToken'"> or <a href="" ng-click="appstoreLogin.setupType = 'setupToken'">Use a setup token</a></span>
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- give more vertical spacing so the login form does not appear clipped -->
|
||||
<div ng-show="ready && !validSubscription">
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<div class="appstore-layout">
|
||||
|
||||
<div ng-show="ready && validSubscription" class="ng-cloak appstore-toolbar">
|
||||
<div class="dropdown">
|
||||
<button class="btn dropdown-toggle" type="button" data-toggle="dropdown" ng-class="{ 'btn-primary': '' !== category && 'recent' !== category && 'new' !== category }">
|
||||
{{ categoryButtonLabel(category) }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="" ng-click="showCategory('');"><i class="fas fa-home fa-fw"></i> {{ 'appstore.category.all' | tr }}</a></li>
|
||||
<li><a href="" ng-click="showCategory('new');"><i class="fas fa-rss fa-fw"></i> {{ 'appstore.category.newApps' | tr }}</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li ng-repeat="category in categories | orderBy:'label'"><a href="" ng-click="showCategory(category.id);"><i class="{{ category.icon }} fa-fw"></i> {{ category.label }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="btn dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
<i class="{{ userManagementFilterOption.icon }} fa-fw"></i>
|
||||
{{ 'appstore.ssofilter.label' | tr }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="option in userManagementFilterOptions" ng-class="{ 'active': userManagementFilterOption.id && userManagementFilterOption.id === option.id }"><a href="" ng-click="applyUserMangamentFilter(option);"><i class="{{ option.icon }} fa-fw"></i> {{ option.label }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<input type="text" id="appstoreSearch" class="form-control" style="width: auto; flex-grow: 1;" placeholder="{{ 'appstore.searchPlaceholder' | tr }}" ng-model="searchString" ng-change="search()" autofocus>
|
||||
<button type="button" class="btn btn-default" ng-click="openAppProxy()"><i class="fas fa-exchange-alt"></i> {{ 'apps.addAppproxyAction' | tr }}</a></a>
|
||||
<button type="button" class="btn btn-default" ng-click="applinksAdd.show()"><i class="fas fa-link"></i> {{ 'apps.addApplinkAction' | tr }}</button>
|
||||
</div>
|
||||
|
||||
<div ng-show="ready && validSubscription" class="ng-cloak appstore-grid">
|
||||
<div class="text-center" ng-hide="apps.length || popularApps.length">
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<h3 class="text-muted">{{ 'appstore.noAppsFound' | tr }}</h3>
|
||||
<br/>
|
||||
<a href="https://forum.cloudron.io/category/5/app-requests" target="_blank">{{ 'appstore.appMissing' | tr }}</a>
|
||||
</div>
|
||||
<div class="" ng-show="category === '' && popularApps.length">
|
||||
<div class="row-no-margin">
|
||||
<div class="col-sm-12">
|
||||
<h2>{{ 'appstore.category.popular' | tr }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-no-margin">
|
||||
<div class="col-sm-3 appstore-item" ng-repeat="app in popularApps | userManagementFilter:userManagementFilterOption">
|
||||
<div class="appstore-item-content highlight" ng-click="gotoApp(app)" ng-class="{ 'appstore-item-content-testing': app.releaseState === 'unstable' }">
|
||||
<span class="badge badge-danger appstore-item-badge-testing" ng-show="app.releaseState === 'unstable'">{{ 'appstore.unstable' | tr }}</span>
|
||||
<div class="appstore-item-content-icon col-same-height">
|
||||
<img ng-src="{{app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-icon"/>
|
||||
</div>
|
||||
<div class="col-same-height">
|
||||
<h4 class="appstore-item-content-title">{{ app.manifest.title }}</h4>
|
||||
<div class="appstore-item-content-tagline text-muted">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="" ng-show="apps.length">
|
||||
<div class="row-no-margin" ng-show="!category && !searchString">
|
||||
<div class="col-sm-12">
|
||||
<h2>{{ 'appstore.category.all' | tr }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-no-margin">
|
||||
<div class="col-sm-3 appstore-item" ng-repeat="app in apps | userManagementFilter:userManagementFilterOption | orderBy:'-priority' ">
|
||||
<div class="appstore-item-content highlight" ng-click="gotoApp(app)" ng-class="{ 'appstore-item-content-testing': app.releaseState === 'unstable' }">
|
||||
<span class="badge badge-danger appstore-item-badge-testing" ng-show="app.releaseState === 'unstable'">{{ 'appstore.unstable' | tr }}</span>
|
||||
<div class="appstore-item-content-icon col-same-height">
|
||||
<img ng-src="{{app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-icon"/>
|
||||
</div>
|
||||
<div class="appstore-item-content-description col-same-height">
|
||||
<h4 class="appstore-item-content-title">{{ app.manifest.title }}</h4>
|
||||
<div class="appstore-item-content-tagline text-muted">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,787 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular */
|
||||
/* global $ */
|
||||
/* global async */
|
||||
/* global ERROR */
|
||||
/* global RSTATES */
|
||||
/* global moment */
|
||||
|
||||
angular.module('Application').controller('AppStoreController', ['$scope', '$translate', '$location', '$timeout', '$routeParams', 'Client', function ($scope, $translate, $location, $timeout, $routeParams, Client) {
|
||||
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
|
||||
|
||||
$scope.HOST_PORT_MIN = 1;
|
||||
$scope.HOST_PORT_MAX = 65535;
|
||||
|
||||
$scope.ready = false;
|
||||
$scope.apps = [];
|
||||
$scope.popularApps = [];
|
||||
$scope.config = Client.getConfig();
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.users = [];
|
||||
$scope.groups = [];
|
||||
$scope.domains = [];
|
||||
$scope.category = '';
|
||||
$scope.cachedCategory = ''; // used to cache the selected category while searching
|
||||
$scope.searchString = '';
|
||||
$scope.validSubscription = false;
|
||||
$scope.subscription = {};
|
||||
$scope.memory = null; // { memory, swap }
|
||||
|
||||
$scope.showView = function (view) {
|
||||
$('#appInstallModal').off('hidden.bs.modal');
|
||||
|
||||
// wait for dialog to be fully closed to avoid modal behavior breakage when moving to a different view already
|
||||
$('.modal').on('hidden.bs.modal', function () {
|
||||
$scope.appInstall.reset();
|
||||
$('.modal').off('hidden.bs.modal');
|
||||
$location.path(view).search({});
|
||||
});
|
||||
|
||||
$('.modal').modal('hide');
|
||||
};
|
||||
|
||||
// If new categories added make sure the translation below exists
|
||||
$scope.categories = [
|
||||
{ id: 'analytics', icon: 'fa fa-chart-line', label: 'Analytics'},
|
||||
{ id: 'automation', icon: 'fa fa-robot', label: 'Automation'},
|
||||
{ id: 'blog', icon: 'fa fa-font', label: 'Blog'},
|
||||
{ id: 'chat', icon: 'fa fa-comments', label: 'Chat'},
|
||||
{ id: 'crm', icon: 'fab fa-connectdevelop', label: 'CRM'},
|
||||
{ id: 'document', icon: 'fa fa-file-word', label: 'Documents'},
|
||||
{ id: 'email', icon: 'fa fa-envelope', label: 'Email'},
|
||||
{ id: 'federated', icon: 'fa fa-project-diagram', label: 'Federated'},
|
||||
{ id: 'finance', icon: 'fa fa-hand-holding-usd', label: 'Finance'},
|
||||
{ id: 'forum', icon: 'fa fa-users', label: 'Forum'},
|
||||
{ id: 'fun', icon: 'fa fa-gamepad', label: 'Fun'},
|
||||
{ id: 'gallery', icon: 'fa fa-images', label: 'Gallery'},
|
||||
{ id: 'game', icon: 'fa fa-gamepad', label: 'Games'},
|
||||
{ id: 'git', icon: 'fa fa-code-branch', label: 'Code Hosting'},
|
||||
{ id: 'hosting', icon: 'fa fa-server', label: 'Web Hosting'},
|
||||
{ id: 'learning', icon: 'fas fa-graduation-cap', label: 'Learning'},
|
||||
{ id: 'media', icon: 'fas fa-photo-video', label: 'Media'},
|
||||
{ id: 'no-code', icon: 'fas fa-code', label: 'No-code'},
|
||||
{ id: 'notes', icon: 'fa fa-sticky-note', label: 'Notes'},
|
||||
{ id: 'project', icon: 'fas fa-project-diagram', label: 'Project Management'},
|
||||
{ id: 'sync', icon: 'fa fa-sync-alt', label: 'File Sync'},
|
||||
{ id: 'voip', icon: 'fa fa-headset', label: 'VoIP'},
|
||||
{ id: 'vpn', icon: 'fa fa-user-secret', label: 'VPN'},
|
||||
{ id: 'wiki', icon: 'fab fa-wikipedia-w', label: 'Wiki'},
|
||||
];
|
||||
|
||||
// Translation IDs are generated as "appstore.category.<categoryId>"
|
||||
$translate($scope.categories.map(function (c) { return 'appstore.category.' + c.id; })).then(function (tr) {
|
||||
Object.keys(tr).forEach(function (key) {
|
||||
if (key === tr[key]) return; // missing translation use default label
|
||||
|
||||
var category = $scope.categories.find(function (c) { return key.endsWith(c.id); });
|
||||
if (category) category.label = tr[key];
|
||||
});
|
||||
});
|
||||
|
||||
$scope.categoryButtonLabel = function (category) {
|
||||
var categoryLabel = $translate.instant('appstore.categoryLabel');
|
||||
|
||||
if (category === '') return $translate.instant('appstore.category.all');
|
||||
if (category === 'new') return $translate.instant('appstore.category.newApps');
|
||||
|
||||
var tmp = $scope.categories.find(function (c) { return c.id === category; });
|
||||
if (tmp) return tmp.label;
|
||||
|
||||
return categoryLabel;
|
||||
};
|
||||
|
||||
$scope.isProxyApp = function (app) {
|
||||
if (!app) return false;
|
||||
|
||||
return app.id === 'io.cloudron.builtin.appproxy';
|
||||
};
|
||||
|
||||
$scope.userManagementFilterOptions = [
|
||||
{ id: '', icon: '', label: $translate.instant('appstore.ssofilter.all') },
|
||||
{ id: 'sso', icon: 'fas fa-user', label: $translate.instant('apps.auth.sso') },
|
||||
{ id: 'nosso', icon: 'far fa-user', label: $translate.instant('apps.auth.nosso') },
|
||||
{ id: 'email', icon: 'fas fa-envelope', label: $translate.instant('apps.auth.email') },
|
||||
];
|
||||
$scope.userManagementFilterOption = $scope.userManagementFilterOptions[0];
|
||||
|
||||
$scope.userManagementFilterOptionIsActive = function (option) {
|
||||
return option.id === $scope.userManagementFilterOption.id;
|
||||
};
|
||||
|
||||
$scope.applyUserMangamentFilter = function (option) {
|
||||
$scope.userManagementFilterOption = option;
|
||||
};
|
||||
|
||||
$scope.appInstall = {
|
||||
busy: false,
|
||||
state: 'appInfo',
|
||||
error: {},
|
||||
app: {},
|
||||
needsOverwrite: false,
|
||||
subdomain: '',
|
||||
domain: null, // object and not the string
|
||||
secondaryDomains: {},
|
||||
ports: {},
|
||||
portsEnabled: {},
|
||||
mediaLinks: [],
|
||||
keyFile: null,
|
||||
keyFileName: '',
|
||||
accessRestrictionOption: '',
|
||||
accessRestriction: { users: [], groups: [] },
|
||||
customAuth: false,
|
||||
optionalSso: false,
|
||||
subscriptionErrorMesssage: '',
|
||||
upstreamUri: '',
|
||||
|
||||
isAccessRestrictionValid: function () {
|
||||
var tmp = $scope.appInstall.accessRestriction;
|
||||
return !!(tmp.users.length || tmp.groups.length);
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
$scope.appInstall.app = {};
|
||||
$scope.appInstall.error = {};
|
||||
$scope.appInstall.needsOverwrite = false;
|
||||
$scope.appInstall.subdomain = '';
|
||||
$scope.appInstall.domain = null;
|
||||
$scope.appInstall.secondaryDomains = {};
|
||||
$scope.appInstall.ports = {};
|
||||
$scope.appInstall.state = 'appInfo';
|
||||
$scope.appInstall.mediaLinks = [];
|
||||
$scope.appInstall.keyFile = null;
|
||||
$scope.appInstall.keyFileName = '';
|
||||
$scope.appInstall.accessRestrictionOption = '';
|
||||
$scope.appInstall.accessRestriction = { users: [], groups: [] };
|
||||
$scope.appInstall.optionalSso = false;
|
||||
$scope.appInstall.customAuth = false;
|
||||
$scope.appInstall.subscriptionErrorMesssage = '';
|
||||
$scope.appInstall.upstreamUri = '';
|
||||
|
||||
$('#collapseInstallForm').collapse('hide');
|
||||
$('#collapseResourceConstraint').collapse('hide');
|
||||
$('#collapseSubscriptionRequired').collapse('hide');
|
||||
$('#collapseMediaLinksCarousel').collapse('show');
|
||||
|
||||
if ($scope.appInstallForm) {
|
||||
$scope.appInstallForm.$setPristine();
|
||||
$scope.appInstallForm.$setUntouched();
|
||||
}
|
||||
},
|
||||
|
||||
showForm: function (force) {
|
||||
var app = $scope.appInstall.app;
|
||||
var DEFAULT_MEMORY_LIMIT = 1024 * 1024 * 256;
|
||||
|
||||
var needed = app.manifest.memoryLimit || DEFAULT_MEMORY_LIMIT; // RAM
|
||||
var used = Client.getInstalledApps().reduce(function (prev, cur) {
|
||||
if (cur.runState === RSTATES.STOPPED) return prev;
|
||||
|
||||
return prev + (cur.memoryLimit || cur.manifest.memoryLimit || DEFAULT_MEMORY_LIMIT);
|
||||
}, 0);
|
||||
var totalMemory = $scope.memory.memory * 2;
|
||||
var available = (totalMemory || 0) - used;
|
||||
|
||||
var enoughResourcesAvailable = (available - needed) >= 0;
|
||||
|
||||
if (enoughResourcesAvailable || force) {
|
||||
$scope.appInstall.state = 'installForm';
|
||||
$('#collapseMediaLinksCarousel').collapse('hide');
|
||||
$('#collapseResourceConstraint').collapse('hide');
|
||||
$('#collapseInstallForm').collapse('show');
|
||||
$('#appInstallLocationInput').focus();
|
||||
} else {
|
||||
$scope.appInstall.state = 'resourceConstraint';
|
||||
$('#collapseMediaLinksCarousel').collapse('hide');
|
||||
$('#collapseResourceConstraint').collapse('show');
|
||||
}
|
||||
},
|
||||
|
||||
show: function (app) { // this is an appstore app object!
|
||||
$scope.appInstall.reset();
|
||||
|
||||
// make a copy to work with in case the app object gets updated while polling
|
||||
angular.copy(app, $scope.appInstall.app);
|
||||
|
||||
$scope.appInstall.mediaLinks = $scope.appInstall.app.manifest.mediaLinks || [];
|
||||
$scope.appInstall.domain = $scope.domains.find(function (d) { return $scope.config.adminDomain === d.domain; }); // pre-select the adminDomain
|
||||
|
||||
$scope.appInstall.secondaryDomains = {};
|
||||
var httpPorts = $scope.appInstall.app.manifest.httpPorts || {};
|
||||
for (var env2 in httpPorts) {
|
||||
$scope.appInstall.secondaryDomains[env2] = {
|
||||
subdomain: httpPorts[env2].defaultValue || '',
|
||||
domain: $scope.appInstall.domain
|
||||
};
|
||||
}
|
||||
|
||||
$scope.appInstall.portInfo = angular.extend({}, $scope.appInstall.app.manifest.tcpPorts, $scope.appInstall.app.manifest.udpPorts); // Portbinding map only for information
|
||||
$scope.appInstall.ports = {}; // This holds the env:port pair
|
||||
$scope.appInstall.portsEnabled = {}; // This holds the enabled/disabled flag
|
||||
|
||||
var manifest = app.manifest;
|
||||
$scope.appInstall.optionalSso = !!manifest.optionalSso;
|
||||
$scope.appInstall.customAuth = !(manifest.addons['ldap'] || manifest.addons['oidc'] || manifest.addons['proxyAuth']);
|
||||
|
||||
$scope.appInstall.accessRestrictionOption = $scope.groups.length ? '' : 'any'; // make the user select an ACL conciously if groups are used
|
||||
$scope.appInstall.accessRestriction = { users: [], groups: [] };
|
||||
|
||||
// set default ports
|
||||
var allPorts = angular.extend({}, $scope.appInstall.app.manifest.tcpPorts, $scope.appInstall.app.manifest.udpPorts);
|
||||
for (var env in allPorts) {
|
||||
$scope.appInstall.ports[env] = allPorts[env].defaultValue || 0;
|
||||
$scope.appInstall.portsEnabled[env] = true;
|
||||
}
|
||||
|
||||
$('#appInstallModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.appInstall.busy = true;
|
||||
$scope.appInstall.error.other = null;
|
||||
$scope.appInstall.error.location = null;
|
||||
$scope.appInstall.error.port = null;
|
||||
|
||||
var secondaryDomains = {};
|
||||
for (var env2 in $scope.appInstall.secondaryDomains) {
|
||||
secondaryDomains[env2] = {
|
||||
subdomain: $scope.appInstall.secondaryDomains[env2].subdomain,
|
||||
domain: $scope.appInstall.secondaryDomains[env2].domain.domain
|
||||
};
|
||||
}
|
||||
|
||||
// only use enabled ports from ports
|
||||
var finalPorts = {};
|
||||
for (var env in $scope.appInstall.ports) {
|
||||
if ($scope.appInstall.portsEnabled[env]) {
|
||||
finalPorts[env] = $scope.appInstall.ports[env];
|
||||
}
|
||||
}
|
||||
|
||||
var finalAccessRestriction = null;
|
||||
if ($scope.appInstall.accessRestrictionOption === 'groups') {
|
||||
finalAccessRestriction = { users: [], groups: [] };
|
||||
finalAccessRestriction.users = $scope.appInstall.accessRestriction.users.map(function (u) { return u.id; });
|
||||
finalAccessRestriction.groups = $scope.appInstall.accessRestriction.groups.map(function (g) { return g.id; });
|
||||
}
|
||||
|
||||
var data = {
|
||||
overwriteDns: $scope.appInstall.needsOverwrite,
|
||||
subdomain: $scope.appInstall.subdomain || '',
|
||||
domain: $scope.appInstall.domain.domain,
|
||||
secondaryDomains: secondaryDomains,
|
||||
ports: finalPorts,
|
||||
accessRestriction: finalAccessRestriction,
|
||||
sso: !$scope.appInstall.optionalSso ? undefined : ($scope.appInstall.accessRestrictionOption !== 'nosso'),
|
||||
};
|
||||
|
||||
if ($scope.appInstall.upstreamUri) {
|
||||
data.upstreamUri = $scope.appInstall.upstreamUri;
|
||||
data.upstreamUri = data.upstreamUri.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
var domains = [];
|
||||
domains.push({ subdomain: data.subdomain, domain: data.domain, type: 'primary' });
|
||||
var canInstall = true;
|
||||
|
||||
async.eachSeries(domains, function (domain, callback) {
|
||||
if (data.overwriteDns) return callback();
|
||||
|
||||
Client.checkDNSRecords(domain.domain, domain.subdomain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
var message;
|
||||
if (result.error) {
|
||||
if (result.error.reason === ERROR.ACCESS_DENIED) {
|
||||
message = 'DNS credentials for ' + domain.domain + ' are invalid. Update it in Domains & Certs view';
|
||||
if (domain.type === 'primary') {
|
||||
$scope.appInstall.error.location = message;
|
||||
} else {
|
||||
$scope.appInstall.error.secondaryDomain = message;
|
||||
}
|
||||
} else {
|
||||
if (domain.type === 'primary') {
|
||||
$scope.appInstall.error.location = result.error.message;
|
||||
} else {
|
||||
$scope.appInstall.error.secondaryDomain = message;
|
||||
}
|
||||
}
|
||||
canInstall = false;
|
||||
} else if (result.needsOverwrite) {
|
||||
message = 'DNS Record already exists. Confirm that the domain is not in use for services external to Cloudron';
|
||||
if (data.type === 'primary') {
|
||||
$scope.appInstall.error.location = message;
|
||||
} else {
|
||||
$scope.appInstall.error.secondaryDomain = message;
|
||||
}
|
||||
$scope.appInstall.needsOverwrite = true;
|
||||
canInstall = false;
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
}, function (error) {
|
||||
if (error) {
|
||||
$scope.appInstall.busy = false;
|
||||
return Client.error(error);
|
||||
}
|
||||
|
||||
if (!canInstall) {
|
||||
$scope.appInstall.busy = false;
|
||||
$scope.appInstallForm.location.$setPristine();
|
||||
$('#appInstallLocationInput').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
Client.installApp($scope.appInstall.app.id, $scope.appInstall.app.manifest, data, function (error, newAppId) {
|
||||
if (error) {
|
||||
var errorMessage = error.message.toLowerCase();
|
||||
|
||||
if (error.statusCode === 402) {
|
||||
$scope.appInstall.state = 'subscriptionRequired';
|
||||
$scope.appInstall.subscriptionErrorMesssage = error.message;
|
||||
$('#collapseMediaLinksCarousel').collapse('hide');
|
||||
$('#collapseResourceConstraint').collapse('hide');
|
||||
$('#collapseInstallForm').collapse('hide');
|
||||
$('#collapseSubscriptionRequired').collapse('show');
|
||||
} else if (error.statusCode === 409) {
|
||||
if (errorMessage.indexOf('port') !== -1) {
|
||||
$scope.appInstall.error.port = error.message;
|
||||
} else if (errorMessage.indexOf('location') !== -1) {
|
||||
if (errorMessage.indexOf('primary') !== -1) {
|
||||
$scope.appInstall.error.location = error.message;
|
||||
$scope.appInstallForm.location.$setPristine();
|
||||
$('#appInstallLocationInput').focus();
|
||||
} else {
|
||||
$scope.appInstall.error.secondaryDomain = error.message;
|
||||
}
|
||||
} else {
|
||||
$scope.appInstall.error.other = error.message;
|
||||
}
|
||||
} else if (error.statusCode === 400) {
|
||||
$scope.appInstall.error.other = error.message;
|
||||
} else {
|
||||
$scope.appInstall.error.other = error.message;
|
||||
}
|
||||
|
||||
$scope.appInstall.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.appInstall.busy = false;
|
||||
|
||||
// stash new app id for later
|
||||
$scope.appInstall.app.id = newAppId;
|
||||
|
||||
// we track the postinstall confirmation for the current user's browser
|
||||
// TODO later we might want to have a notification db to track the state across admins and browsers
|
||||
if ($scope.appInstall.app.manifest.postInstallMessage) {
|
||||
localStorage['confirmPostInstall_' + $scope.appInstall.app.id] = true;
|
||||
}
|
||||
|
||||
$scope.showView('/apps');
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.appNotFound = {
|
||||
appId: '',
|
||||
version: ''
|
||||
};
|
||||
|
||||
$scope.appstoreLogin = {
|
||||
busy: false,
|
||||
error: {},
|
||||
email: '',
|
||||
password: '',
|
||||
totpToken: '',
|
||||
setupType: 'login',
|
||||
termsAccepted: false,
|
||||
setupToken: '',
|
||||
|
||||
submit: function () {
|
||||
$scope.appstoreLogin.error = {};
|
||||
$scope.appstoreLogin.busy = true;
|
||||
|
||||
var func = $scope.appstoreLogin.setupToken ? Client.registerCloudronWithSetupToken.bind(null, $scope.appstoreLogin.setupToken) : Client.registerCloudron.bind(null, $scope.appstoreLogin.email, $scope.appstoreLogin.password, $scope.appstoreLogin.totpToken, $scope.appstoreLogin.setupType === 'register');
|
||||
func(function (error) {
|
||||
if (error) {
|
||||
$scope.appstoreLogin.busy = false;
|
||||
|
||||
if (error.statusCode === 409) {
|
||||
$scope.appstoreLogin.error.email = 'An account with this email already exists';
|
||||
$scope.appstoreLogin.password = '';
|
||||
$scope.appstoreSignupForm.email.$setPristine();
|
||||
$scope.appstoreSignupForm.password.$setPristine();
|
||||
$('#inputAppstoreLoginEmail').focus();
|
||||
} else if (error.statusCode === 412) {
|
||||
if (error.message.indexOf('TOTP token missing') !== -1) {
|
||||
$scope.appstoreLogin.error.totpToken = 'A 2FA token is required';
|
||||
setTimeout(function () { $('#inputAppstoreLoginTotpToken').focus(); }, 0);
|
||||
} else if (error.message.indexOf('TOTP token invalid') !== -1) {
|
||||
$scope.appstoreLogin.error.totpToken = 'Wrong 2FA token';
|
||||
$scope.appstoreLogin.totpToken = '';
|
||||
setTimeout(function () { $('#inputAppstoreLoginTotpToken').focus(); }, 0);
|
||||
} else {
|
||||
$scope.appstoreLogin.error.loginPassword = 'Wrong email or password';
|
||||
$scope.appstoreLogin.password = '';
|
||||
$('#inputAppstoreLoginPassword').focus();
|
||||
$scope.appstoreLoginForm.password.$setPristine();
|
||||
}
|
||||
} else if (error.statusCode === 424) {
|
||||
if (error.message === 'wrong user') {
|
||||
$scope.appstoreLogin.error.generic = 'Wrong cloudron.io account';
|
||||
$scope.appstoreLogin.email = '';
|
||||
$scope.appstoreLogin.password = '';
|
||||
$scope.appstoreLoginForm.email.$setPristine();
|
||||
$scope.appstoreLoginForm.password.$setPristine();
|
||||
$scope.appstoreSignupForm.email.$setPristine();
|
||||
$scope.appstoreSignupForm.password.$setPristine();
|
||||
$('#inputAppstoreLoginEmail').focus();
|
||||
} else {
|
||||
console.error(error);
|
||||
$scope.appstoreLogin.error.generic = error.message;
|
||||
}
|
||||
} else if (error.statusCode === 402) {
|
||||
$scope.appstoreLogin.error.setupToken = 'Invalid or expired setup token';
|
||||
$scope.appstoreLogin.setupToken = '';
|
||||
$scope.appstoreSetupTokenForm.setupToken.$setPristine();
|
||||
$('#inputAppstoreSetupToken').focus();
|
||||
} else {
|
||||
console.error(error);
|
||||
$scope.appstoreLogin.error.generic = error.message || 'Please retry later';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// do a full re-init of the view now that we have a subscription
|
||||
init();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// TODO does not support testing apps in search
|
||||
$scope.search = function () {
|
||||
if (!$scope.searchString) return $scope.showCategory($scope.cachedCategory);
|
||||
|
||||
$scope.category = '';
|
||||
|
||||
Client.getAppstoreAppsFast(function (error, apps) {
|
||||
if (error) return $timeout($scope.search, 1000);
|
||||
|
||||
var token = $scope.searchString.toUpperCase();
|
||||
|
||||
$scope.popularApps = [];
|
||||
$scope.apps = apps.filter(function (app) {
|
||||
// on searches we give highe priority if title or tagline matches
|
||||
app.priority = 0;
|
||||
|
||||
if (app.manifest.title.toUpperCase().indexOf(token) !== -1) {
|
||||
app.priority = 2;
|
||||
return true;
|
||||
}
|
||||
if (app.manifest.tagline.toUpperCase().indexOf(token) !== -1) {
|
||||
app.priority = 1;
|
||||
return true;
|
||||
}
|
||||
if (app.manifest.id.toUpperCase().indexOf(token) !== -1) return true;
|
||||
if (app.manifest.description.toUpperCase().indexOf(token) !== -1) return true;
|
||||
if (app.manifest.tags.join().toUpperCase().indexOf(token) !== -1) return true;
|
||||
return false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function filterForNewApps(apps) {
|
||||
var minApps = apps.length < 12 ? apps.length : 12; // prevent endless loop
|
||||
var tmp = [];
|
||||
var i = 0;
|
||||
|
||||
do {
|
||||
var offset = moment().subtract(i++, 'days');
|
||||
tmp = apps.filter(function (app) { return moment(app.publishedAt).isAfter(offset); });
|
||||
} while(tmp.length < minApps);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
function filterForRecentlyUpdatedApps(apps) {
|
||||
var minApps = apps.length < 12 ? apps.length : 12; // prevent endless loop
|
||||
var tmp = [];
|
||||
var i = 0;
|
||||
|
||||
do {
|
||||
var offset = moment().subtract(i++, 'days');
|
||||
tmp = apps.filter(function (app) { return moment(app.creationDate).isAfter(offset); }); // creationDate here is from appstore's appversions table
|
||||
} while(tmp.length < minApps);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
$scope.showCategory = function (category) {
|
||||
$scope.category = category;
|
||||
|
||||
$scope.cachedCategory = $scope.category;
|
||||
|
||||
Client.getAppstoreAppsFast(function (error, apps) {
|
||||
if (error) return $timeout($scope.showCategory.bind(null, category), 1000);
|
||||
|
||||
if (!$scope.category) {
|
||||
$scope.apps = apps.slice(0).filter(function (app) { return !app.featured; }).sort(function (a1, a2) { return a1.manifest.title.localeCompare(a2.manifest.title); });
|
||||
$scope.popularApps = apps.slice(0).filter(function (app) { return app.featured; }).sort(function (a1, a2) { return a2.ranking - a1.ranking; });
|
||||
} else if ($scope.category === 'new') {
|
||||
$scope.apps = filterForNewApps(apps);
|
||||
} else if ($scope.category === 'recent') {
|
||||
$scope.apps = filterForRecentlyUpdatedApps(apps);
|
||||
} else {
|
||||
$scope.apps = apps.filter(function (app) {
|
||||
return app.manifest.tags.some(function (tag) { return $scope.category.toUpperCase() === tag.toUpperCase(); }); // reverse sort;
|
||||
}).sort(function (a1, a2) { return a2.ranking - a1.ranking; });
|
||||
}
|
||||
|
||||
// ensure we scroll to top
|
||||
document.getElementById('ng-view').scrollTop = 0;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.openSubscriptionSetup = function () {
|
||||
Client.getSubscription(function (error, subscription) {
|
||||
if (error) return console.error('Unable to get subscription.', error);
|
||||
|
||||
Client.openSubscriptionSetup(subscription);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showAppNotFound = function (appId, version) {
|
||||
$scope.appNotFound.appId = appId;
|
||||
$scope.appNotFound.version = version || 'latest';
|
||||
|
||||
$('#appNotFoundModal').modal('show');
|
||||
};
|
||||
|
||||
$scope.gotoApp = function (app) {
|
||||
$location.path('/appstore/' + app.manifest.id, false).search({ version : app.manifest.version });
|
||||
};
|
||||
|
||||
$scope.openAppProxy = function () {
|
||||
$location.path('/appstore/io.cloudron.builtin.appproxy', false).search({});
|
||||
};
|
||||
|
||||
$scope.applinksAdd = {
|
||||
error: {},
|
||||
busy: false,
|
||||
upstreamUri: '',
|
||||
label: '',
|
||||
tags: '',
|
||||
accessRestrictionOption: 'any',
|
||||
accessRestriction: { users: [], groups: [] },
|
||||
|
||||
isAccessRestrictionValid: function () {
|
||||
return !!($scope.applinksAdd.accessRestriction.users.length || $scope.applinksAdd.accessRestriction.groups.length);
|
||||
},
|
||||
|
||||
show: function () {
|
||||
$scope.applinksAdd.error = {};
|
||||
$scope.applinksAdd.busy = false;
|
||||
$scope.applinksAdd.upstreamUri = '';
|
||||
$scope.applinksAdd.label = '';
|
||||
$scope.applinksAdd.tags = '';
|
||||
|
||||
$scope.applinksAddForm.$setUntouched();
|
||||
$scope.applinksAddForm.$setPristine();
|
||||
|
||||
$('#applinksAddModal').modal('show');
|
||||
|
||||
return false; // prevent propagation and default
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
if (!$scope.applinksAdd.upstreamUri) return;
|
||||
|
||||
$scope.applinksAdd.busy = true;
|
||||
$scope.applinksAdd.error = {};
|
||||
|
||||
var accessRestriction = null;
|
||||
if ($scope.applinksAdd.accessRestrictionOption === 'groups') {
|
||||
accessRestriction = { users: [], groups: [] };
|
||||
accessRestriction.users = $scope.applinksAdd.accessRestriction.users.map(function (u) { return u.id; });
|
||||
accessRestriction.groups = $scope.applinksAdd.accessRestriction.groups.map(function (g) { return g.id; });
|
||||
}
|
||||
|
||||
var data = {
|
||||
upstreamUri: $scope.applinksAdd.upstreamUri,
|
||||
label: $scope.applinksAdd.label,
|
||||
accessRestriction: accessRestriction,
|
||||
tags: $scope.applinksAdd.tags.split(' ').map(function (t) { return t.trim(); }).filter(function (t) { return !!t; })
|
||||
};
|
||||
|
||||
Client.addApplink(data, function (error) {
|
||||
$scope.applinksAdd.busy = false;
|
||||
|
||||
if (error && error.statusCode === 400 && error.message.includes('upstreamUri')) {
|
||||
$scope.applinksAdd.error.upstreamUri = error.message;
|
||||
$scope.applinksAddForm.$setUntouched();
|
||||
$scope.applinksAddForm.$setPristine();
|
||||
return;
|
||||
}
|
||||
if (error) return console.error('Failed to add applink', error);
|
||||
|
||||
$scope.showView('/apps');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function hashChangeListener() {
|
||||
// event listener is called from DOM not angular, need to use $apply
|
||||
$scope.$apply(function () {
|
||||
var appId = $location.path().slice('/appstore/'.length);
|
||||
var version = $location.search().version;
|
||||
|
||||
if (appId) {
|
||||
Client.getAppstoreAppByIdAndVersion(appId, version || 'latest', function (error, result) {
|
||||
if (error) {
|
||||
$scope.showAppNotFound(appId, version);
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.appInstall.show(result);
|
||||
});
|
||||
} else {
|
||||
$scope.appInstall.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fetchUsers() {
|
||||
Client.getAllUsers(function (error, users) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return $timeout(fetchUsers, 5000);
|
||||
}
|
||||
|
||||
$scope.users = users;
|
||||
});
|
||||
}
|
||||
|
||||
function fetchGroups() {
|
||||
Client.getGroups(function (error, groups) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return $timeout(fetchGroups, 5000);
|
||||
}
|
||||
|
||||
$scope.groups = groups;
|
||||
});
|
||||
}
|
||||
|
||||
function fetchMemory() {
|
||||
Client.memory(function (error, memory) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return $timeout(fetchMemory, 5000);
|
||||
}
|
||||
|
||||
$scope.memory = memory;
|
||||
});
|
||||
}
|
||||
|
||||
function getSubscription(callback) {
|
||||
var validSubscription = false;
|
||||
|
||||
Client.getSubscription(function (error, subscription) {
|
||||
if (error) {
|
||||
if (error.statusCode === 412) { // not registered yet
|
||||
validSubscription = false;
|
||||
} else if (error.statusCode === 402) { // invalid token, license error
|
||||
validSubscription = false;
|
||||
} else { // 424/external error?
|
||||
return callback(error);
|
||||
}
|
||||
} else {
|
||||
validSubscription = true;
|
||||
$scope.subscription = subscription;
|
||||
}
|
||||
|
||||
// clear busy state when a login/signup was performed
|
||||
$scope.appstoreLogin.busy = false;
|
||||
|
||||
// also update the root controller status
|
||||
if ($scope.$parent) $scope.$parent.updateSubscriptionStatus();
|
||||
|
||||
callback(null, validSubscription);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
Client.getAppstoreAppsFast(function (error) {
|
||||
if (error && error.statusCode === 402) {
|
||||
$scope.validSubscription = false;
|
||||
$scope.ready = true;
|
||||
return;
|
||||
} else if (error) {
|
||||
console.error('Failed to get apps. Will retry.', error);
|
||||
$timeout(init, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.showCategory('');
|
||||
|
||||
getSubscription(function (error, validSubscription) {
|
||||
if (error) console.error('Failed to get subscription.', error);
|
||||
|
||||
// autofocus login
|
||||
if (!validSubscription) setTimeout(function () { $('[name=appstoreLoginForm]').find('[autofocus]:first').focus(); }, 1000);
|
||||
|
||||
$scope.validSubscription = validSubscription;
|
||||
$scope.ready = true;
|
||||
|
||||
// refresh everything in background
|
||||
Client.getAppstoreApps(function (error) { if (error) console.error('Failed to fetch apps.', error); });
|
||||
Client.refreshConfig(); // refresh domain, user, group limit etc
|
||||
fetchUsers();
|
||||
fetchGroups();
|
||||
fetchMemory();
|
||||
|
||||
// domains is required since we populate the dropdown with domains[0]
|
||||
Client.getDomains(function (error, result) {
|
||||
if (error) return console.error('Error getting domains.', error);
|
||||
|
||||
$scope.domains = result;
|
||||
|
||||
// show install app dialog immediately if an app id was passed in the query
|
||||
// hashChangeListener calls $apply, so make sure we don't double digest here
|
||||
setTimeout(hashChangeListener, 1);
|
||||
|
||||
setTimeout(function () { $('#appstoreSearch').focus(); }, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Client.onReady(init);
|
||||
|
||||
$('#appInstallModal').on('hidden.bs.modal', function () {
|
||||
// clear the appid and version in the search bar when dialog is cancelled
|
||||
$scope.$apply(function () {
|
||||
$location.path('/appstore', false).search({ }); // 'false' means do not reload
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', hashChangeListener);
|
||||
|
||||
$scope.$on('$destroy', function handler() {
|
||||
window.removeEventListener('hashchange', hashChangeListener);
|
||||
});
|
||||
|
||||
// setup all the dialog focus handling
|
||||
['appInstallModal'].forEach(function (id) {
|
||||
$('#' + id).on('shown.bs.modal', function () {
|
||||
$(this).find('[autofocus]:first').focus();
|
||||
});
|
||||
});
|
||||
|
||||
$('.modal-backdrop').remove();
|
||||
}]);
|
||||
@@ -1,816 +0,0 @@
|
||||
<!-- Modal details -->
|
||||
<div class="modal fade" id="backupDetailsModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" style="width: 750px">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'backups.backupDetails.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-2 text-muted">{{ 'backups.backupDetails.id' | tr }}:</div>
|
||||
<div class="col-xs-10 text-right">{{ backupDetails.backup.id }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-2 text-muted">{{ 'backups.backupEdit.label' | tr }}:</div>
|
||||
<div class="col-xs-10 text-right">{{ backupDetails.backup.label }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-2 text-muted">{{ 'backups.backupEdit.remotePath' | tr }}:</div>
|
||||
<div class="col-xs-10 text-right">{{ backupDetails.backup.remotePath }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-2 text-muted">{{ 'backups.backupDetails.date' | tr }}:</div>
|
||||
<div class="col-xs-10 text-right">{{ backupDetails.backup.creationTime | prettyLongDate }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-2 text-muted">{{ 'backups.backupDetails.version' | tr }}:</div>
|
||||
<div class="col-xs-10 text-right">v{{ backupDetails.backup.packageVersion }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-2 text-muted">{{ 'backups.backupDetails.format' | tr }}:</div>
|
||||
<div class="col-xs-10 text-right">{{ backupDetails.backup.format }}</div>
|
||||
</div>
|
||||
<br/>
|
||||
<p class="text-muted">{{ 'backups.backupDetails.list' | tr:{ appCount: backupDetails.backup.contents.length } }}:</p>
|
||||
<span ng-repeat="content in backupDetails.backup.contents | orderBy:['label','fqdn']">
|
||||
<a ng-if="content.fqdn" ng-href="/#/app/{{content.id}}/backups">{{ content.label || content.fqdn }}</a>
|
||||
<a ng-if="!content.fqdn" ng-href="/#/eventlog?search={{content.id}}">{{ content.id }}</a>
|
||||
<span ng-hide="$last">,</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal edit individual backup (label and retention sec) -->
|
||||
<div class="modal fade" id="editBackupModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'backups.backupEdit.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="editBackupForm" role="form" novalidate ng-submit="editBackup.submit()" autocomplete="off">
|
||||
<p class="has-error text-center" ng-show="editBackup.error">{{ editBackup.error }}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputBackupLabel">{{ 'backups.backupEdit.label' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="editBackup.label" id="inputBackupLabel" name="label" ng-disabled="editBackup.busy" placeholder="" autofocus>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="editBackup.persist">{{ 'backups.backupEdit.preserved.description' | tr }}</input>
|
||||
|
||||
<sup><a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{ 'backups.backupEdit.preserved.tooltip' | tr: { appsLength: editBackup.backup.contents.length} }}"><i class="fa fa-question-circle"></i></a></sup>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="editBackup.submit()" ng-disabled="editBackupForm.$invalid || editBackup.busy"><i class="fa fa-circle-notch fa-spin" ng-show="editBackup.busy"></i><span> {{ 'main.dialog.save' | tr }}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal backup failed -->
|
||||
<div class="modal fade" id="createBackupFailedModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'backups.backupFailed.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ createBackup.errorMessage }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cleanup backups info -->
|
||||
<div class="modal fade" id="cleanupBackupsModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'backups.cleanupBackups.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">{{ 'backups.cleanupBackups.description' | tr }}</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="cleanupBackups.start()">{{ 'backups.cleanupBackups.cleanupNow' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal backup schedule config -->
|
||||
<div class="modal fade" id="backupPolicyModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'backups.configureBackupSchedule.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="backupPolicyForm" role="form" novalidate ng-submit="backupPolicy.submit()" autocomplete="off">
|
||||
<p class="has-error text-center" ng-show="backupPolicy.error">{{ backupPolicy.error.generic }}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="backupSchedule">{{ 'backups.configureBackupSchedule.schedule' | tr }}</label>
|
||||
<p ng-bind-html="'backups.configureBackupSchedule.scheduleDescription' | tr"></p>
|
||||
|
||||
<div class="row" style="margin-left: 20px;">
|
||||
<div class="col-md-5" ng-class="{ 'has-error': !backupPolicy.days.length }">
|
||||
{{ 'backups.configureBackupSchedule.days' | tr }}: <multiselect id="backupSchedule" class="input-sm stretch" ng-model="backupPolicy.days" options="a.name for a in cronDays" data-multiple="true" ng-required></multiselect>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5" ng-class="{ 'has-error': !backupPolicy.hours.length }">
|
||||
{{ 'backups.configureBackupSchedule.hours' | tr }}: <multiselect class="input-sm stretch" ng-model="backupPolicy.hours" options="a.name for a in cronHours" data-multiple="true"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="backupRetention">{{ 'backups.configureBackupSchedule.retentionPolicy' | tr }}</label>
|
||||
<select class="form-control" id="backupRetention" ng-model="backupPolicy.retention" ng-options="a.value as a.name for a in backupRetentions"></select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer ">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="backupPolicy.submit()" ng-disabled="!backupPolicy.valid() || backupPolicy.busy"><i class="fa fa-circle-notch fa-spin" ng-show="backupPolicy.busy"></i><span> {{ 'main.dialog.save' | tr }}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal backup config -->
|
||||
<div class="modal fade" id="configureBackupModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'backups.configureBackupStorage.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="configureBackupForm" role="form" novalidate ng-submit="configureBackup.submit()" autocomplete="off">
|
||||
<p class="has-error text-center" ng-show="configureBackup.error">{{ configureBackup.error.generic }}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="storageProviderProvider">{{ 'backups.configureBackupStorage.provider' | tr }} <sup><a ng-href="https://docs.cloudron.io/backups/#storage-providers" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<p class="small text-info" ng-show="backupConfig.provider !== configureBackup.provider">Backups in the old storage location have to be removed manually.</p>
|
||||
<select class="form-control" id="storageProviderProvider" ng-model="configureBackup.provider" ng-options="a.value as a.name for a in storageProviders" ng-change=configureBackup.clearProviderFields()></select>
|
||||
</div>
|
||||
|
||||
<!-- Noop -->
|
||||
<div class="form-group" ng-show="configureBackup.provider === 'noop'">
|
||||
<p class="has-error">{{ 'backups.configureBackupStorage.noopNote' | tr }}</p>
|
||||
</div>
|
||||
|
||||
<!-- mountpoint -->
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.mountPoint || (configureBackupForm.mountPoint.$dirty && !configureBackup.mountPoint) }" ng-show="configureBackup.provider === 'mountpoint'">
|
||||
<label class="control-label" for="inputConfigureMountPoint">{{ 'backups.configureBackupStorage.mountPoint' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.mountPoint" id="inputConfigureMountPoint" name="mountPoint" ng-disabled="configureBackup.busy" placeholder="/mnt/backups" ng-required="configureBackup.provider === 'mountpoint'">
|
||||
<p ng-show="configureBackup.provider === 'mountpoint'" ng-bind-html="'backups.configureBackupStorage.mountPointDescription' | tr:{ providerDocsLink: 'https://docs.cloudron.io/backups/#'+configureBackup.provider }"></p>
|
||||
</div>
|
||||
|
||||
<!-- CIFS/NFS/SSHFS -->
|
||||
<div class="form-group" ng-show="configureBackup.provider === 'cifs' || configureBackup.provider === 'nfs' || configureBackup.provider === 'sshfs'">
|
||||
<label class="control-label" for="configureBackupHost">{{ 'backups.configureBackupStorage.server' | tr }} ({{ configureBackup.provider }})</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.host" id="configureBackupHost" name="host" ng-disabled="configureBackup.busy" placeholder="Server IP or hostname" ng-required="configureBackup.provider === 'cifs' || configureBackup.provider === 'nfs' || configureBackup.provider === 'sshfs'">
|
||||
</div>
|
||||
|
||||
<!-- CIFS -->
|
||||
<div class="checkbox" ng-show="configureBackup.provider === 'cifs'">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="configureBackup.mountOptions.seal">{{ 'backups.configureBackupStorage.cifsSealSupport' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- CIFS/NFS/SSHFS -->
|
||||
<div class="form-group" ng-show="configureBackup.provider === 'cifs' || configureBackup.provider === 'nfs' || configureBackup.provider === 'sshfs'">
|
||||
<label class="control-label" for="configureBackupRemoteDir">{{ 'backups.configureBackupStorage.remoteDirectory' | tr }} ({{ configureBackup.provider }})</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.remoteDir" id="configureBackupRemoteDir" name="remoteDir" ng-disabled="configureBackup.busy" placeholder="/share" ng-required="configureBackup.provider === 'cifs' || configureBackup.provider === 'nfs' || configureBackup.provider === 'sshfs'">
|
||||
</div>
|
||||
|
||||
<!-- CIFS -->
|
||||
<div class="form-group" ng-show="configureBackup.provider === 'cifs'">
|
||||
<label class="control-label" for="configureBackupUsername">{{ 'backups.configureBackupStorage.username' | tr }} ({{ configureBackup.provider }})</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.username" id="configureBackupUsername" name="cifsUsername" ng-disabled="configureBackup.busy">
|
||||
</div>
|
||||
|
||||
<!-- CIFS -->
|
||||
<div class="form-group" ng-show="configureBackup.provider === 'cifs'">
|
||||
<label class="control-label" for="configureBackupPassword">{{ 'backups.configureBackupStorage.password' | tr }} ({{ configureBackup.provider }})</label>
|
||||
<input type="password" class="form-control" ng-model="configureBackup.mountOptions.password" id="configureBackupPassword" name="cifsPassword" ng-disabled="configureBackup.busy" password-reveal>
|
||||
</div>
|
||||
|
||||
<!-- EXT4/XFS -->
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.diskPath || !configureBackup.mountOptions.diskPath }" ng-show="configureBackup.provider === 'xfs' || configureBackup.provider === 'ext4'">
|
||||
<label class="control-label" for="inputConfigureDiskPath">{{ 'backups.configureBackupStorage.diskPath' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.diskPath" id="inputConfigureDiskPath" name="diskPath" ng-disabled="configureBackup.busy" placeholder="/dev/disk/by-uuid/uuid" ng-required="configureBackup.provider === 'xfs' || configureBackup.provider === 'ext4'">
|
||||
</div>
|
||||
|
||||
<!-- Disk -->
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.diskPath || !configureBackup.mountOptions.diskPath }" ng-show="configureBackup.provider === 'disk'">
|
||||
<label class="control-label">{{ 'backups.configureBackupStorage.diskPath' | tr }}</label>
|
||||
<select class="form-control" ng-model="configureBackup.disk" ng-options="item as item.label for item in configureBackup.blockDevices track by item.path" ng-required="configureBackup.provider === 'disk'"></select>
|
||||
</div>
|
||||
|
||||
<!-- SSHFS -->
|
||||
<div class="form-group" ng-show="configureBackup.provider === 'sshfs'">
|
||||
<label class="control-label" for="configureBackupPort">{{ 'backups.configureBackupStorage.port' | tr }}</label>
|
||||
<input type="number" class="form-control" ng-model="configureBackup.mountOptions.port" id="configureBackupPort" name="port" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'sshfs'">
|
||||
</div>
|
||||
|
||||
<!-- SSHFS -->
|
||||
<div class="form-group" ng-show="configureBackup.provider === 'sshfs'">
|
||||
<label class="control-label" for="configureBackupUser">{{ 'backups.configureBackupStorage.user' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.user" id="configureBackupUser" name="user" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'sshfs'">
|
||||
</div>
|
||||
|
||||
<!-- SSHFS -->
|
||||
<div class="form-group" ng-show="configureBackup.provider === 'sshfs'">
|
||||
<label class="control-label" for="configureBackupPrivateKey">{{ 'backups.configureBackupStorage.privateKey' | tr }}</label>
|
||||
<textarea class="form-control" ng-model="configureBackup.mountOptions.privateKey" id="configureBackupPrivateKey" name="privateKey" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'sshfs'"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Filesystem -->
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.backupFolder }" ng-show="configureBackup.provider === 'filesystem'">
|
||||
<label class="control-label" for="inputConfigureBackupFolder">{{ 'backups.configureBackupStorage.localDirectory' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.backupFolder" id="inputConfigureBackupFolder" name="backupFolder" ng-disabled="configureBackup.busy" placeholder="Directory for backups" ng-required="configureBackup.provider === 'filesystem'">
|
||||
</div>
|
||||
|
||||
<!-- Filesystem/SSHFS/CIFS/NFS/EXT4/mountpoint -->
|
||||
<div class="checkbox" ng-show="configureBackup.provider === 'filesystem' || mountlike(configureBackup.provider)">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="configureBackup.useHardlinks">{{ 'backups.configureBackupStorage.hardlinksLabel' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- CIFS/mountpoint -->
|
||||
<div class="checkbox" ng-show="configureBackup.provider === 'mountpoint' || configureBackup.provider === 'cifs'">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="configureBackup.preserveAttributes">{{ 'backups.configureBackupStorage.preserveAttributesLabel' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- mountpoint -->
|
||||
<div class="checkbox" ng-show="configureBackup.provider === 'mountpoint'">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="configureBackup.chown">{{ 'backups.configureBackupStorage.chown' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- S3/Minio/SOS/GCS/UpCloud/B2/R2 -->
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.endpoint }" ng-show="configureBackup.provider === 'minio' || configureBackup.provider === 'upcloud-objectstorage' || configureBackup.provider === 'backblaze-b2' || configureBackup.provider === 'cloudflare-r2' || configureBackup.provider === 's3-v4-compat' || configureBackup.provider === 'idrive-e2'">
|
||||
<label class="control-label" for="inputConfigureBackupEndpoint">{{ 'backups.configureBackupStorage.s3Endpoint' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.endpoint" id="inputConfigureBackupEndpoint" name="endpoint" ng-disabled="configureBackup.busy" placeholder="URL" ng-required="configureBackup.provider === 'minio' || configureBackup.provider === 'upcloud-objectstorage' || configureBackup.provider === 'backblaze-b2' || configureBackup.provider === 'cloudflare-r2' || configureBackup.provider === 's3-v4-compat' || configureBackup.provider === 'idrive-e2'">
|
||||
</div>
|
||||
|
||||
<div class="checkbox" ng-show="configureBackup.provider === 'minio' || configureBackup.provider === 's3-v4-compat'" >
|
||||
<label>
|
||||
<input type="checkbox" ng-model="configureBackup.acceptSelfSignedCerts">{{ 'backups.configureBackupStorage.acceptSelfSignedCerts' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.bucket }" ng-show="s3like(configureBackup.provider) || configureBackup.provider === 'gcs'">
|
||||
<label class="control-label" for="inputConfigureBackupBucket">{{ 'backups.configureBackupStorage.bucketName' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.bucket" id="inputConfigureBackupBucket" name="bucket" ng-disabled="configureBackup.busy" ng-required="s3like(configureBackup.provider)">
|
||||
</div>
|
||||
|
||||
<!-- S3/Minio/SOS/GCS/SSHFS/CIFS/NFS/B2 -->
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.prefix }" ng-show="configureBackup.provider !== 'filesystem' && configureBackup.provider !== 'noop'">
|
||||
<label class="control-label" for="inputConfigureBackupPrefix">{{ 'backups.configureBackupStorage.prefix' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.prefix" id="inputConfigureBackupPrefix" name="prefix" ng-disabled="configureBackup.busy" placeholder="Prefix for backup file names">
|
||||
</div>
|
||||
|
||||
<!-- S3/Minio/SOS/GCS -->
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 's3'">
|
||||
<label class="control-label" for="inputConfigureBackupS3Region">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupS3Region" ng-model="configureBackup.region" ng-options="a.value as a.name for a in s3Regions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 's3'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 's3-v4-compat'">
|
||||
<label class="control-label" for="inputConfigureBackupS3V4CompatRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<input class="form-control" type="text" name="region" id="inputConfigureBackupS3V4CompatRegion" ng-model="configureBackup.region" ng-disabled="configureBackup.busy" placeholder="Leave empty to use us-east-1 as default"></input>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'digitalocean-spaces'">
|
||||
<label class="control-label" for="inputConfigureBackupDORegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupDORegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in doSpacesRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'digitalocean-spaces'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'hetzner-objectstorage'">
|
||||
<label class="control-label" for="inputConfigureBackupHetznerRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupHetznerRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in hetznerRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'hetzner-objectstorage'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'exoscale-sos'">
|
||||
<label class="control-label" for="inputConfigureBackupExoscaleRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupExoscaleRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in exoscaleSosRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'exoscale-sos'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'wasabi'">
|
||||
<label class="control-label" for="inputConfigureBackupWasabiRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupWasabiRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in wasabiRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'wasabi'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'scaleway-objectstorage'">
|
||||
<label class="control-label" for="inputConfigureBackupScalewayRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupScalewayRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in scalewayRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'scaleway-objectstorage'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'linode-objectstorage'">
|
||||
<label class="control-label" for="inputConfigureBackupLinodeRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupLinodeRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in linodeRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'linode-objectstorage'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'ovh-objectstorage'">
|
||||
<label class="control-label" for="inputConfigureBackupOvhRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupOvhRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in ovhRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'ovh-objectstorage'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'ionos-objectstorage'">
|
||||
<label class="control-label" for="inputConfigureBackupIonosRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupIonosRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in ionosRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'ionos-objectstorage'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'vultr-objectstorage'">
|
||||
<label class="control-label" for="inputConfigureBackupVultrRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupVultrRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in vultrRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'vultr-objectstorage'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'contabo-objectstorage'">
|
||||
<label class="control-label" for="inputConfigureBackupContaboRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
||||
<select class="form-control" name="region" id="inputConfigureBackupContaboRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in contaboRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'contabo-objectstorage'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.accessKeyId }" ng-show="s3like(configureBackup.provider)">
|
||||
<label class="control-label" for="inputConfigureBackupAccessKeyId">{{ 'backups.configureBackupStorage.s3AccessKeyId' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.accessKeyId" id="inputConfigureBackupAccessKeyId" name="accessKeyId" ng-disabled="configureBackup.busy" ng-required="s3like(configureBackup.provider)">
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.secretAccessKey }" ng-show="s3like(configureBackup.provider)">
|
||||
<label class="control-label" for="inputConfigureBackupSecretAccessKey">{{ 'backups.configureBackupStorage.s3SecretAccessKey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="configureBackup.secretAccessKey" id="inputConfigureBackupSecretAccessKey" name="secretAccessKey" ng-disabled="configureBackup.busy" ng-required="s3like(configureBackup.provider)">
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.gcsKeyInput }" ng-show="configureBackup.provider === 'gcs'">
|
||||
<label class="control-label" for="gcsKeyInput">{{ 'backups.configureBackupStorage.gcsServiceKey' | tr }}</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="file" id="gcsKeyFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Service Account Key" ng-model="configureBackup.gcsKey.keyFileName" id="gcsKeyInput" name="cert" onclick="getElementById('gcsKeyFileInput').click();" style="cursor: pointer;" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'gcs'">
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('gcsKeyFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="configureBackup.provider !== 'noop'">
|
||||
<label class="control-label" for="storageFormat">{{ 'backups.configureBackupStorage.format' | tr }} <sup><a ng-href="https://docs.cloudron.io/backups/#backup-formats" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<p class="small text-info" ng-show="backupConfig.format !== configureBackup.format">{{ 'backups.configureBackupStorage.formatChangeNote' | tr }}</p>
|
||||
<p class="small text-info" ng-show="configureBackup.format === 'rsync' && (s3like(configureBackup.provider) || configureBackup.provider === 'gcs')">{{ 'backups.configureBackupStorage.s3LikeNote' | tr }} <sup><a ng-href="https://docs.cloudron.io/backups/#amazon-s3" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></p>
|
||||
<select class="form-control" id="storageFormat" ng-model="configureBackup.format" ng-options="a.value as a.name for a in formats"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.password }" ng-show="configureBackup.provider !== 'noop'">
|
||||
<label class="control-label" for="inputConfigureBackupPassword">{{ 'backups.configureBackupStorage.encryptionPassword' | tr }} <sup><a ng-href="https://docs.cloudron.io/backups/#encryption" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<p class="small">{{ 'backups.configureBackupStorage.encryptionDescription' | tr }}</p>
|
||||
<input type="text" class="form-control" name="encryptionPassword" ng-model="configureBackup.password" id="inputConfigureBackupPassword" ng-disabled="configureBackup.busy" placeholder="{{ 'backups.configureBackupStorage.encryptionPasswordPlaceholder' | tr }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="configureBackup.password && configureBackup.password !== SECRET_PLACEHOLDER" ng-class="{ 'has-error': (configureBackupForm.encryptionPassword.$dirty && configureBackup.password !== configureBackup.passwordRepeat) }">
|
||||
<label class="control-label" for="inputConfigureBackupPasswordRepeat">{{ 'backups.configureBackupStorage.encryptionPasswordRepeat' | tr }}</label>
|
||||
<input id="inputConfigureBackupPasswordRepeat" type="text" class="form-control" name="passwordRepeat" ng-model="configureBackup.passwordRepeat" ng-disabled="configureBackup.busy">
|
||||
</div>
|
||||
|
||||
<div class="checkbox" ng-show="configureBackup.password !== '' && configureBackup.format === 'rsync'">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="configureBackup.encryptedFilenames">{{ 'backups.configureBackupStorage.encryptFilenames' | tr }}</input>
|
||||
<sup><a ng-href="https://docs.cloudron.io/backups/#filenames" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<a href="" ng-click="configureBackup.advancedVisible = true" ng-hide="configureBackup.advancedVisible">{{ 'backups.configureBackupStorage.advancedSettings' | tr }}</a>
|
||||
<div uib-collapse="!configureBackup.advancedVisible">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="sliderConfigureBackupMemoryLimit">{{ 'backups.configureBackupStorage.memoryLimit' | tr }}: <b>{{ configureBackup.memoryLimit | prettyBinarySize:'1024 MB' }}</b></label>
|
||||
<p class="small">{{ 'backups.configureBackupStorage.memoryLimitDescription' | tr }}</p>
|
||||
<input type="range" id="sliderConfigureBackupMemoryLimit" ng-model="configureBackup.memoryLimit" step="{{ 256*1024*1024 }}" min="{{ MIN_MEMORY_LIMIT }}" max="{{ MAX_MEMORY_LIMIT }}" />
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="s3like(configureBackup.provider)">
|
||||
<label class="control-label" for="sliderConfigureBackupUploadPartSize">{{ 'backups.configureBackupStorage.uploadPartSize' | tr }}: <b>{{ configureBackup.uploadPartSize | prettyBinarySize:'Default (50 MiB)' }}</b></label>
|
||||
<p class="small">{{ 'backups.configureBackupStorage.uploadPartSizeDescription' | tr }}</p>
|
||||
<input type="range" id="sliderConfigureBackupUploadPartSize" ng-model="configureBackup.uploadPartSize" list="uploadPartSizeTicks" step="{{ 1024*1024 }}" min="{{ 1024*1024 }}" max="{{ 1024*1024*1024 }}" />
|
||||
<datalist id="uploadPartSizeTicks">
|
||||
<option value="{{ 1024*1024 }}"></option>
|
||||
<option value="{{ 64*1024*1024 }}"></option>
|
||||
<option value="{{ 128*1024*1024 }}"></option>
|
||||
<option value="{{ 256*1024*1024 }}"></option>
|
||||
<option value="{{ 512*1024*1024 }}"></option>
|
||||
<option value="{{ 1024*1024*1024 }}"></option>
|
||||
</datalist>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="configureBackup.format === 'rsync' && configureBackup.provider !== 'noop'">
|
||||
<label class="control-label" for="sliderConfigureBackupSyncConcurrency">{{ 'backups.configureBackupStorage.uploadConcurrency' | tr }}: <b>{{ configureBackup.syncConcurrency }}</b></label>
|
||||
<p class="small">{{ 'backups.configureBackupStorage.uploadConcurrencyDescription' | tr }}</p>
|
||||
<input type="range" id="sliderConfigureBackupSyncConcurrency" ng-model="configureBackup.syncConcurrency" step="10" min="10" max="200" />
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="configureBackup.format === 'rsync' && (s3like(configureBackup.provider) || configureBackup.provider === 'gcs')">
|
||||
<label class="control-label" for="sliderConfigureBackupDownloadConcurrency">{{ 'backups.configureBackupStorage.downloadConcurrency' | tr }}: <b>{{ configureBackup.downloadConcurrency }}</b></label>
|
||||
<p class="small">{{ 'backups.configureBackupStorage.downloadConcurrencyDescription' | tr }}</p>
|
||||
<input type="range" id="sliderConfigureBackupDownloadConcurrency" ng-model="configureBackup.downloadConcurrency" step="10" min="10" max="200" />
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="configureBackup.format === 'rsync' && (s3like(configureBackup.provider) || configureBackup.provider === 'gcs')">
|
||||
<label class="control-label" for="sliderConfigureBackupCopyConcurrency">{{ 'backups.configureBackupStorage.copyConcurrency' | tr }}: <b>{{ configureBackup.copyConcurrency }}</b></label>
|
||||
<p class="small">{{ 'backups.configureBackupStorage.copyConcurrencyDescription' | tr }}
|
||||
<span ng-show="configureBackup.provider === 'digitalocean-spaces'">{{ 'backups.configureBackupStorage.copyConcurrencyDigitalOceanNote' | tr }}</span>
|
||||
</p>
|
||||
<input type="range" id="sliderConfigureBackupCopyConcurrency" ng-model="configureBackup.copyConcurrency" step="10" min="10" max="500" />
|
||||
</div>
|
||||
|
||||
</div> <!-- advanced -->
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="configureBackupForm.$invalid || (configureBackup.password !== SECRET_PLACEHOLDER && configureBackup.password !== configureBackup.passwordRepeat)"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer ">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="configureBackup.submit()" ng-disabled="configureBackupForm.$invalid || configureBackup.busy || (configureBackup.password !== SECRET_PLACEHOLDER && configureBackup.password !== configureBackup.passwordRepeat)"><i class="fa fa-circle-notch fa-spin" ng-show="configureBackup.busy"></i><span> {{ 'main.dialog.save' | tr }}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal archive restore -->
|
||||
<div class="modal fade" id="restoreArchiveModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'backups.restoreArchiveDialog.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="padding: 0 15px">
|
||||
<p ng-bind-html="'backups.restoreArchiveDialog.description' | tr:{ appId: archiveRestore.manifest.id, fqdn: archiveRestore.fqdn, creationTime: (archiveRestore.archive.creationTime | prettyLongDate) }"></p>
|
||||
<form role="form" ng-submit="archiveRestore.submit()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group" ng-class="{ 'has-error': archiveRestore.error.location.fqdn === archiveRestore.subdomain + '.' + archiveRestore.domain.domain }">
|
||||
<label class="control-label" for="cloneLocationInput">{{ 'app.cloneDialog.location' | tr }}</label>
|
||||
<div ng-show="archiveRestore.error.location.fqdn === archiveRestore.subdomain + '.' + archiveRestore.domain.domain"><small>{{ archiveRestore.error.location.message }}</small></div>
|
||||
<div class="input-group form-inline">
|
||||
<input type="text" class="form-control" ng-model="archiveRestore.subdomain" id="cloneLocationInput" name="location" placeholder="{{ 'appstore.installDialog.locationPlaceholder' | tr }}" autofocus>
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>{{ '.' + archiveRestore.domain.domain }}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li ng-repeat="domain in domains">
|
||||
<a href="" ng-click="archiveRestore.domain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="has-error text-center" ng-show="archiveRestore.error.secondaryDomain">{{ archiveRestore.error.secondaryDomain }}</div>
|
||||
<div ng-repeat="(env, info) in archiveRestore.manifest.httpPorts">
|
||||
<ng-form name="secondaryDomainInfo_form">
|
||||
<div class="form-group" ng-class="{ 'has-error': (!secondaryDomainInfo_form.itemName{{$index}}.$dirty && archiveRestore.error.secondaryDomain) || (secondaryDomainInfo_form.itemName{{$index}}.$dirty && secondaryDomainInfo_form.itemName{{$index}}.$invalid) || (archiveRestore.error.location.fqdn === archiveRestore.secondaryDomains[env].subdomain + '.' + archiveRestore.secondaryDomains[env].domain.domain) }">
|
||||
<label class="control-label" for="secondaryDomainInput{{env}}">
|
||||
{{ info.title }}
|
||||
<sup>
|
||||
<a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{info.description}}"><i class="fa fa-question-circle"></i></a>
|
||||
</sup>
|
||||
</label>
|
||||
|
||||
<div ng-show="archiveRestore.error.location.fqdn === archiveRestore.secondaryDomains[env].subdomain + '.' + archiveRestore.secondaryDomains[env].domain.domain"><small>{{ archiveRestore.error.location.message }}</small></div>
|
||||
<div class="input-group form-inline">
|
||||
<input type="text" class="form-control" ng-model="archiveRestore.secondaryDomains[env].subdomain" name="location{{$index}}" placeholder="{{ 'app.location.locationPlaceholder' | tr }}" autofocus>
|
||||
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>.{{ archiveRestore.secondaryDomains[env].domain.domain }}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li ng-repeat="domain in domains">
|
||||
<a href="" ng-click="archiveRestore.secondaryDomains[env].domain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
|
||||
<p class="text-small text-warning" ng-show="archiveRestore.domain.provider === 'noop' || archiveRestore.domain.provider === 'manual'" ng-bind-html="'appstore.installDialog.manualWarning' | tr:{ location: ((archiveRestore.subdomain ? archiveRestore.subdomain + '.' : '') + archiveRestore.domain.domain) }"></p>
|
||||
|
||||
<div class="has-error text-center" ng-show="archiveRestore.error.port">{{ archiveRestore.error.port }}</div>
|
||||
<div ng-repeat="(env, info) in archiveRestore.portInfo">
|
||||
<ng-form name="portInfo_form">
|
||||
<div class="form-group" ng-class="{ 'has-error': (!archiveRestore.itemName{{$index}}.$dirty && archiveRestore.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
|
||||
<label class="control-label" for="inputPortInfo{{env}}"><input type="checkbox" ng-model="archiveRestore.portsEnabled[env]">
|
||||
{{ info.title }}
|
||||
<sup>
|
||||
<a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{info.description}}"><i class="fa fa-question-circle"></i></a>
|
||||
</sup>
|
||||
<small style="padding-left: 5px;" ng-show="info.readOnly">{{ 'appstore.installDialog.portReadOnly' | tr }}</small>
|
||||
</label>
|
||||
<input type="number" class="form-control" ng-model="archiveRestore.ports[env]" ng-disabled="!archiveRestore.portsEnabled[env]" ng-readonly="info.readOnly" id="inputPortInfo{{env}}" later-name="itemName{{$index}}" min="{{hostPortMin}}" max="{{hostPortMax}}" required>
|
||||
<p class="text-small text-warning text-bold" ng-show="archiveRestore.domain.provider === 'cloudflare'">{{ 'appstore.installDialog.cloudflarePortWarning' | tr }} </p>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</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-success" ng-click="archiveRestore.submit()" ng-disabled="archiveRestore.busy"><i class="fas fa-history" ng-hide="archiveRestore.busy"></i><i class="fa fa-circle-notch fa-spin" ng-show="archiveRestore.busy"></i> {{ 'backups.restoreArchiveDialog.restoreAction' | tr:{ dnsOverwrite: archiveRestore.needsOverwrite } }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal delete archive -->
|
||||
<div class="modal fade" id="archiveDeleteModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'backups.deleteArchiveDialog.title' | tr:{ appTitle: archiveDelete.title, fqdn: archiveDelete.fqdn } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ 'backups.deleteArchiveDialog.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="archiveDelete.submit()" ng-disabled="archiveDelete.busy"><i class="fa fa-circle-notch fa-spin" ng-show="archiveDelete.busy"></i> {{ 'backups.deleteArchive.deleteAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<h1 class="section-header">{{ 'backups.title' | tr }}</h1>
|
||||
|
||||
<div class="text-left">
|
||||
<h3>{{ 'backups.location.title' | tr }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;">
|
||||
<p>{{ 'backups.location.description' | tr }}
|
||||
<span ng-show="manualBackupApps.length">
|
||||
{{ 'backups.location.disabledList' | tr }}
|
||||
<span ng-repeat="app in manualBackupApps">
|
||||
<a ng-href="/#/app/{{app.id}}/backups">{{app.label || app.fqdn}}</a><span ng-hide="$last">,</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p ng-show="backupConfig.provider === 'noop'" class="text-danger" ng-bind-html="'backups.check.noop' | tr | markdown2html"></p>
|
||||
<p ng-show="backupConfig.provider === 'filesystem'" class="text-danger" ng-bind-html="'backups.check.sameDisk' | tr | markdown2html"></p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<span class="text-muted">{{ 'backups.location.provider' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span>{{ prettyProviderName(backupConfig.provider) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="backupConfig.provider !== 'noop'">
|
||||
<div class="col-xs-6">
|
||||
<span class="text-muted">{{ 'backups.location.location' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right no-wrap">
|
||||
<span ng-show="backupConfig.provider === 'filesystem'">{{ backupConfig.backupFolder }}</span>
|
||||
<span ng-show="mountlike(backupConfig.provider)">
|
||||
<i class="fa fa-circle" ng-style="{ color: mountStatus.state === 'active' ? '#27CE65' : '#d9534f' }" ng-show="mountStatus" uib-tooltip="{{ mountStatus.message }}"></i>
|
||||
<span ng-show="backupConfig.provider === 'disk' || backupConfig.provider === 'filesystem' || backupConfig.provider === 'ext4' || backupConfig.provider === 'xfs' || backupConfig.provider === 'mountpoint'">{{ backupConfig.mountOptions.diskPath || backupConfig.mountPoint }}{{ (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
||||
<span ng-show="backupConfig.provider === 'cifs' || backupConfig.provider === 'nfs' || backupConfig.provider === 'sshfs'">{{ backupConfig.mountOptions.host }}:{{ backupConfig.mountOptions.remoteDir }}{{ (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
||||
</span>
|
||||
|
||||
<span ng-show="backupConfig.provider !== 's3' && backupConfig.provider !== 'minio' && (s3like(backupConfig.provider) || backupConfig.provider === 'gcs')">{{ backupConfig.bucket + (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
||||
<span ng-show="backupConfig.provider === 's3'">{{ backupConfig.region + ' ' + backupConfig.bucket + (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
||||
<span ng-show="backupConfig.provider === 'minio'">{{ backupConfig.endpoint + ' ' + backupConfig.bucket + (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="backupConfig.endpoint && backupConfig.provider !== 'minio'">
|
||||
<div class="col-xs-3">
|
||||
<span class="text-muted">{{ 'backups.location.endpoint' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-9 text-right">
|
||||
<span>{{ backupConfig.endpoint || backupConfig.region }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<span class="text-muted">{{ 'backups.location.format' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span>{{ backupConfig.format }} <i class="fas fa-lock" ng-show="backupConfig.password" ></i></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-show="user.isAtLeastOwner" ng-click="configureBackup.show()">{{ 'backups.location.configure' | tr }}</button>
|
||||
<button class="btn btn-outline btn-default pull-right" ng-show="user.isAtLeastOwner && mountlike(backupConfig.provider)" ng-disabled="remount.busy" ng-click="remount.submit()"><i class="fa fa-circle-notch fa-spin" ng-show="remount.busy"></i> {{ 'backups.location.remount' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="section-header">
|
||||
{{ 'backups.schedule.title' | tr }}
|
||||
<!-- <a class="btn btn-sm btn-default pull-right" ng-href="/logs.html?taskId={{cleanupBackups.taskId}}" target="_blank" uib-tooltip="{{ 'backups.logs.showLogs' | tr }}"><i class="fas fa-align-left"></i></a> -->
|
||||
<div class="btn-group btn-group-sm pull-right">
|
||||
<button type="button" class="btn btn-small btn-default dropdown-toggle" ng-show="cleanupTasks.length" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" uib-tooltip="{{ 'backups.logs.showLogs' | tr }}">
|
||||
<i class="fas fa-align-left"></i> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="task in cleanupTasks">
|
||||
<a ng-href="/logs.html?taskId={{task.id}}" target="_blank" class="text-right">
|
||||
{{ task.ts | prettyLongDate }} <i class="fa" style="margin-left: 20px" ng-class="{ 'status-active fa-check-circle': !task.active && task.success, 'fa-circle-notch fa-spin': task.active, 'status-error fa-times-circle': !task.active && !task.success }"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;">
|
||||
<p ng-bind-html=" 'backups.schedule.description' | tr "></p>
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<span class="text-muted">{{ 'backups.schedule.schedule' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-8 text-right" style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">
|
||||
<span>{{ prettyBackupSchedule(backupPolicy.currentPolicy.schedule) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<span class="text-muted">{{ 'backups.schedule.retentionPolicy' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-8 text-right">
|
||||
<span>{{ prettyBackupRetention(backupPolicy.currentPolicy.retention) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-default" ng-click="cleanupBackups.ask()" ng-disabled="cleanupBackups.busy" style="margin-right: 5px"><i class="fa fa-circle-notch fa-spin" ng-show="cleanupBackups.busy"></i> {{ 'backups.listing.cleanupBackups' | tr }}</button>
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-show="user.isAtLeastOwner" ng-click="backupPolicy.show()">{{ 'backups.schedule.configure' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="section-header">
|
||||
{{ 'backups.listing.title' | tr }}
|
||||
<div class="btn-group btn-group-sm pull-right">
|
||||
<button type="button" class="btn btn-small btn-default dropdown-toggle" ng-show="backupTasks.length" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" uib-tooltip="{{ 'backups.logs.showLogs' | tr }}">
|
||||
<i class="fas fa-align-left"></i> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="task in backupTasks">
|
||||
<a ng-href="/logs.html?taskId={{task.id}}" target="_blank" class="text-right">
|
||||
{{ task.ts | prettyLongDate }} <i class="fa" style="margin-left: 20px" ng-class="{ 'status-active fa-check-circle': !task.active && task.success, 'fa-circle-notch fa-spin': task.active, 'status-error fa-times-circle': !task.active && !task.success }"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
<div class="card card-large">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p ng-show="!backups.length">{{ 'backups.listing.noBackups' | tr }}</p>
|
||||
|
||||
<table class="table table-hover" style="margin: 0;" ng-hide="!backups.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20px"></th>
|
||||
<th>{{ 'backups.listing.version' | tr }}</th>
|
||||
<th>{{ 'main.table.date' | tr }}</th>
|
||||
<th class="hide-mobile">{{ 'backups.listing.contents' | tr }}</th>
|
||||
<th class="text-right">{{ 'main.actions' | tr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="backup in backups">
|
||||
<td><i class="fas fa-archive" ng-show="backup.preserveSecs === -1" uib-tooltip="{{ 'backups.listing.tooltipPreservedBackup' | tr }}"></i></td>
|
||||
<td ng-click="backupDetails.show(backup)" class="hand">v{{ backup.packageVersion }}</td>
|
||||
<td ng-click="backupDetails.show(backup)" class="hand">{{ backup.creationTime | prettyLongDate }} <b ng-show="backup.label">({{ backup.label }})</b></td>
|
||||
<td ng-click="backupDetails.show(backup)" class="hand hide-mobile">
|
||||
<span ng-show="!backup.contents.length">{{ 'backups.listing.noApps' | tr }}</span>
|
||||
<span ng-show="backup.contents.length">{{ 'backups.listing.appCount' | tr:{ appCount: backup.contents.length } }}</span>
|
||||
</td>
|
||||
<td class="text-right no-wrap">
|
||||
<button class="btn btn-xs btn-default" ng-click="editBackup.show(backup)" uib-tooltip="{{ 'backups.listing.tooltipEditBackup' | tr }}"><i class="fa fa-pencil-alt"></i></button>
|
||||
<button class="btn btn-xs btn-default" ng-click="downloadConfig(backup)" uib-tooltip="{{ 'backups.listing.tooltipDownloadBackupConfig' | tr }}"><i class="fas fa-file-alt"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="row" ng-show="createBackup.busy">
|
||||
<div class="col-md-12" style="margin-bottom: 10px;">
|
||||
<div class="progress progress-striped active animateMe">
|
||||
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ createBackup.percent }}%"></div>
|
||||
</div>
|
||||
<p>{{ createBackup.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="!createBackup.busy && !createBackup.active && createBackup.errorMessage">
|
||||
<div class="col-md-12">
|
||||
<p class="has-error">{{ createBackup.errorMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary" ng-click="createBackup.startBackup()" ng-show="!createBackup.busy">{{ 'backups.listing.backupNow' | tr }}</button>
|
||||
<button class="btn btn-outline btn-danger" ng-click="createBackup.stopTask()" ng-show="createBackup.busy">{{ 'backups.listing.stopTask' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="section-header">
|
||||
{{ 'backups.archives.title' | tr }}
|
||||
</h3>
|
||||
|
||||
<div class="card card-large">
|
||||
<p ng-bind-html=" 'backups.archive.description' | tr "></p>
|
||||
|
||||
<div class="grid-item-top">
|
||||
<div class="row ng-hide" ng-show="!archiveList.ready">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2><i class="fa fa-circle-notch fa-spin"></i></h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row animateMeOpacity ng-hide" ng-show="archiveList.ready">
|
||||
<div class="col-lg-12">
|
||||
<table class="table table-hover" style="margin: 0;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%"></th> <!-- icon -->
|
||||
<th style="width: 35%">{{ 'backups.archives.location' | tr }}</th>
|
||||
<th style="width: 35%" class="hide-mobile">{{ 'backups.archives.info' | tr }}</th>
|
||||
<th style="width: 20%">{{ 'main.table.date' | tr }}</th>
|
||||
<th style="width: 5%" class="text-right hide-mobile">{{ 'main.actions' | tr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="archive in archiveList.archives">
|
||||
<td>
|
||||
<img ng-src="{{ archive.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" height="48" width="48"/>
|
||||
</td>
|
||||
<!-- for pre-8.2 backups, appConfig can be null -->
|
||||
<td class="hand elide-table-cell" style="text-overflow: ellipsis; white-space: nowrap;" ng-click="archiveRestore.show(archive)">
|
||||
{{ archive.appConfig ? archive.appConfig.fqdn : '-' }}
|
||||
</td>
|
||||
<td class="hand elide-table-cell hide-mobile" style="text-overflow: ellipsis; white-space: nowrap;" ng-click="archiveRestore.show(archive)">
|
||||
<span uib-tooltip="{{ archive.manifest.id }}@{{ archive.manifest.version }}">{{ archive.manifest.title }}</span>
|
||||
</td>
|
||||
<td class="hand elide-table-cell" style="text-overflow: ellipsis; white-space: nowrap;" ng-click="archiveRestore.show(archive)">
|
||||
{{ archive.creationTime | prettyDate }}
|
||||
</td>
|
||||
<td class="text-right no-wrap hide-mobile" style="vertical-align: middle;">
|
||||
<button class="btn btn-xs btn-default" ng-click="archiveRestore.show(archive)" uib-tooltip="Restore from Archive"><i class="fas fa-history"></i></button>
|
||||
<button class="btn btn-xs btn-default" ng-click="downloadConfig(archive, true)" uib-tooltip="{{ 'backups.listing.tooltipDownloadBackupConfig' | tr }}"><i class="fas fa-file-alt"></i></button>
|
||||
<button class="btn btn-xs btn-danger" ng-click="archiveDelete.ask(archive)" uib-tooltip="Delete Archive"><i class="fa fa-trash-alt"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,98 +0,0 @@
|
||||
<!-- Modal change avatar -->
|
||||
<div class="modal fade" id="avatarChangeModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'branding.changeLogo.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body branding-avatar-selector">
|
||||
<img id="previewAvatar" width="128" height="128" ng-src="{{ avatarChange.avatarUrl() }}"/>
|
||||
<input type="file" id="avatarFileInput" style="display: none" accept="image/*"/>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="grid">
|
||||
<div class="item" ng-repeat="avatar in avatarChange.availableAvatars" style="background-image: url('{{avatar.data || avatar.url}}');" ng-click="avatarChange.setPreviewAvatar(avatar)"></div>
|
||||
<div class="item add" ng-click="avatarChange.showCustomAvatarSelector()"></div>
|
||||
</div>
|
||||
</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-success" ng-click="avatarChange.setAvatar()"> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<h1 class="section-header">{{ 'branding.title' | tr }}</h1>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form role="form" name="aboutForm" ng-submit="about.submit()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group" ng-class="{ 'has-error': about.error.cloudronName }">
|
||||
<label class="control-label">{{ 'branding.cloudronName' | tr }}</label>
|
||||
<div class="control-label" ng-show="about.error.cloudronName">{{about.error.cloudronName}}</div>
|
||||
<input type="text" class="form-control" id="inputCloudronName" name="name" ng-model="about.cloudronName" ng-minlength="1" maxlength="64" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<label class="control-label">{{ 'branding.logo' | tr }}</label>
|
||||
</div>
|
||||
<div class="branding-avatar" ng-click="avatarChange.showChangeAvatar()">
|
||||
<img ng-src="{{ about.avatarUrl() }}"/>
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<label class="control-label">{{ 'branding.backgroundImage' | tr }}</label>
|
||||
<div class="branding-background" ng-click="background.selectNew()">
|
||||
<img ng-src="{{ background.url() }}" onerror="this.src = '/img/background-image-placeholder.svg'"/>
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
<a href="" ng-show="!background.cleared" ng-click="background.clear()">{{ 'branding.clearBackgroundImage' | tr }}</a>
|
||||
<input type="file" id="backgroundFileInput" style="display: none" accept="image/*"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="(!about.avatar && !aboutForm.$dirty) || aboutForm.$invalid || about.busy"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="about.submit()" ng-disabled="false && (!about.avatar && !aboutForm.$dirty) || aboutForm.$invalid || about.busy"><i class="fa fa-circle-notch fa-spin" ng-show="about.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h3 class="section-header">{{ 'branding.footer.title' | tr }}</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form role="form" name="footerForm" autocomplete="off">
|
||||
<p>{{ 'branding.footer.description' | tr }} <sup><a ng-href="https://docs.cloudron.io/branding/#footer" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></p>
|
||||
<textarea name="footer" class="form-control" ng-model="footer.content" ng-disabled="footer.busy"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="footer.submit()" ng-disabled="!footerForm.$dirty || footerForm.$invalid || footer.busy"><i class="fa fa-circle-notch fa-spin" ng-show="footer.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,319 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular:false */
|
||||
/* global $:false */
|
||||
|
||||
angular.module('Application').controller('BrandingController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
|
||||
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
|
||||
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.config = Client.getConfig();
|
||||
|
||||
$scope.openSubscriptionSetup = function () {
|
||||
Client.openSubscriptionSetup($scope.$parent.subscription);
|
||||
};
|
||||
|
||||
$scope.avatarChange = {
|
||||
avatar: null, // { file, data, url }
|
||||
|
||||
availableAvatars: [{
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/logo.png',
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/logo-green.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/logo-orange.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/logo-darkblue.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/logo-red.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/logo-yellow.png'
|
||||
}, {
|
||||
file: null,
|
||||
data: null,
|
||||
url: '/img/avatars/logo-black.png'
|
||||
}],
|
||||
|
||||
avatarUrl: function () {
|
||||
if ($scope.avatarChange.avatar) {
|
||||
return $scope.avatarChange.avatar.data || $scope.avatarChange.avatar.url;
|
||||
} else {
|
||||
return Client.avatar;
|
||||
}
|
||||
},
|
||||
|
||||
getBlobFromImg: function (img, callback) {
|
||||
var size = 512;
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
|
||||
var imageDimensionRatio = img.width / img.height;
|
||||
var canvasDimensionRatio = canvas.width / canvas.height;
|
||||
var renderableHeight, renderableWidth, xStart, yStart;
|
||||
|
||||
if (imageDimensionRatio > canvasDimensionRatio) {
|
||||
renderableHeight = canvas.height;
|
||||
renderableWidth = img.width * (renderableHeight / img.height);
|
||||
xStart = (canvas.width - renderableWidth) / 2;
|
||||
yStart = 0;
|
||||
} else if (imageDimensionRatio < canvasDimensionRatio) {
|
||||
renderableWidth = canvas.width;
|
||||
renderableHeight = img.height * (renderableWidth / img.width);
|
||||
xStart = 0;
|
||||
yStart = (canvas.height - renderableHeight) / 2;
|
||||
} else {
|
||||
renderableHeight = canvas.height;
|
||||
renderableWidth = canvas.width;
|
||||
xStart = 0;
|
||||
yStart = 0;
|
||||
}
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(img, xStart, yStart, renderableWidth, renderableHeight);
|
||||
|
||||
canvas.toBlob(callback);
|
||||
},
|
||||
|
||||
setPreviewAvatar: function (avatar) {
|
||||
$scope.avatarChange.avatar = avatar;
|
||||
},
|
||||
|
||||
showChangeAvatar: function () {
|
||||
$scope.avatarChange.avatar = $scope.about.avatar;
|
||||
$('#avatarChangeModal').modal('show');
|
||||
},
|
||||
|
||||
showCustomAvatarSelector: function () {
|
||||
$('#avatarFileInput').click();
|
||||
},
|
||||
|
||||
setAvatar: function () {
|
||||
if (angular.equals($scope.about.avatar, $scope.avatarChange.avatar)) return $('#avatarChangeModal').modal('hide'); // nothing changed
|
||||
|
||||
$scope.about.avatar = $scope.avatarChange.avatar;
|
||||
|
||||
// get the blob now, we cannot get it if dialog is hidden
|
||||
var img = document.getElementById('previewAvatar');
|
||||
$scope.avatarChange.getBlobFromImg(img, function (blob) {
|
||||
$scope.about.avatarBlob = blob;
|
||||
|
||||
$('#avatarChangeModal').modal('hide');
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
$('#avatarFileInput').get(0).onchange = function (event) {
|
||||
var fr = new FileReader();
|
||||
fr.onload = function () {
|
||||
$scope.$apply(function () {
|
||||
var tmp = {
|
||||
file: event.target.files[0],
|
||||
data: fr.result,
|
||||
url: null
|
||||
};
|
||||
|
||||
$scope.avatarChange.availableAvatars.push(tmp);
|
||||
$scope.avatarChange.setPreviewAvatar(tmp);
|
||||
});
|
||||
};
|
||||
fr.readAsDataURL(event.target.files[0]);
|
||||
};
|
||||
|
||||
$scope.background = {
|
||||
enabled: false,
|
||||
file: null,
|
||||
src: null,
|
||||
cleared: false,
|
||||
newImageFile: null,
|
||||
cacheBusting: Date.now(),
|
||||
|
||||
url() {
|
||||
if ($scope.background.cleared) return '/img/background-image-placeholder.svg';
|
||||
else if ($scope.background.src) return $scope.background.src;
|
||||
else return `${Client.apiOrigin}/api/v1/cloudron/background?${$scope.background.cacheBusting}`;
|
||||
},
|
||||
|
||||
selectNew() {
|
||||
document.getElementById('backgroundFileInput').click();
|
||||
},
|
||||
|
||||
submit(callback) {
|
||||
if ($scope.background.cleared) {
|
||||
Client.changeCloudronBackground(null, callback);
|
||||
} else if ($scope.background.newImageFile) {
|
||||
Client.changeCloudronBackground($scope.background.newImageFile, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
clear() {
|
||||
$scope.background.cleared = true;
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('backgroundFileInput').onchange = function (event) {
|
||||
const fr = new FileReader();
|
||||
fr.onload = function () {
|
||||
const image = new Image();
|
||||
image.onload = function () {
|
||||
// convert and scale to webp max 4k
|
||||
const maxWidth = 4096;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
|
||||
if (image.naturalWidth > maxWidth) {
|
||||
canvas.width = maxWidth;
|
||||
canvas.height = (image.naturalHeight / image.naturalWidth) * maxWidth;
|
||||
} else {
|
||||
canvas.width = image.naturalWidth;
|
||||
canvas.height = image.naturalHeight;
|
||||
}
|
||||
|
||||
canvas.getContext('2d').drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob((blob) => {
|
||||
$scope.$apply(function () {
|
||||
const myImage = new File([blob], 'background.webp', { type: blob.type });
|
||||
|
||||
$scope.background.cleared = false;
|
||||
$scope.background.newImageFile = myImage;
|
||||
$scope.background.src = URL.createObjectURL(myImage);
|
||||
});
|
||||
}, 'image/webp');
|
||||
|
||||
$scope.background.file = event.target.files[0];
|
||||
};
|
||||
|
||||
image.src = fr.result;
|
||||
};
|
||||
fr.readAsDataURL(event.target.files[0]);
|
||||
};
|
||||
|
||||
$scope.about = {
|
||||
busy: false,
|
||||
error: {},
|
||||
cloudronName: '',
|
||||
avatar: null,
|
||||
avatarBlob: null,
|
||||
|
||||
avatarUrl() {
|
||||
if ($scope.about.avatar) {
|
||||
return $scope.about.avatar.data || $scope.about.avatar.url;
|
||||
} else {
|
||||
return Client.avatar;
|
||||
}
|
||||
},
|
||||
|
||||
refresh() {
|
||||
$scope.about.cloudronName = $scope.config.cloudronName;
|
||||
$scope.about.avatar = null;
|
||||
|
||||
Client.hasCloudronBackground(function (error, result) {
|
||||
if (error) return console.error('Failed to get background state.', error);
|
||||
|
||||
$scope.background.enabled = result;
|
||||
$scope.background.file = null;
|
||||
$scope.background.src = null;
|
||||
$scope.background.newImageFile = null;
|
||||
$scope.background.cacheBusting = Date.now();
|
||||
});
|
||||
},
|
||||
|
||||
submit() {
|
||||
$scope.about.error.name = null;
|
||||
$scope.about.busy = true;
|
||||
|
||||
var NOOP = function (next) { return next(); };
|
||||
var changeCloudronName = $scope.about.cloudronName !== $scope.config.cloudronName ? Client.changeCloudronName.bind(null, $scope.about.cloudronName) : NOOP;
|
||||
|
||||
changeCloudronName(function (error) {
|
||||
if (error) {
|
||||
$scope.about.busy = false;
|
||||
if (error.statusCode === 400) {
|
||||
$scope.about.error.cloudronName = error.message || 'Invalid name';
|
||||
$('#inputCloudronName').focus();
|
||||
} else {
|
||||
console.error('Unable to change name.', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var changeAvatar = $scope.about.avatar ? Client.changeCloudronAvatar.bind(null, $scope.about.avatarBlob) : NOOP;
|
||||
|
||||
changeAvatar(function (error) {
|
||||
if (error) {
|
||||
$scope.about.busy = false;
|
||||
console.error('Unable to change avatar.', error);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.background.submit(function (error) {
|
||||
if (error) {
|
||||
$scope.about.busy = false;
|
||||
console.error('Unable to change background.', error);
|
||||
return;
|
||||
}
|
||||
|
||||
Client.refreshConfig(function () {
|
||||
if ($scope.about.avatar) Client.resetAvatar();
|
||||
|
||||
$scope.aboutForm.$setPristine();
|
||||
$scope.about.avatar = null;
|
||||
$scope.about.refresh();
|
||||
|
||||
$scope.about.busy = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.footer = {
|
||||
content: '',
|
||||
busy: false,
|
||||
|
||||
refresh: function () {
|
||||
Client.getFooter(function (error, result) {
|
||||
if (error) return console.error('Failed to get footer.', error);
|
||||
|
||||
$scope.footer.content = result;
|
||||
});
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.footer.busy = true;
|
||||
|
||||
Client.setFooter($scope.footer.content.trim(), function (error) {
|
||||
if (error) return console.error('Failed to set footer.', error);
|
||||
|
||||
Client.refreshConfig(function () {
|
||||
$scope.footer.busy = false;
|
||||
$scope.footerForm.$setPristine();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Client.onReady(function () {
|
||||
$scope.about.refresh();
|
||||
$scope.footer.refresh();
|
||||
});
|
||||
|
||||
$('.modal-backdrop').remove();
|
||||
}]);
|
||||
@@ -1,535 +0,0 @@
|
||||
<!-- Modal subscription -->
|
||||
<div class="modal fade" id="subscriptionRequiredModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'domains.subscriptionRequired.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p ng-bind-html="'domains.subscriptionRequired.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-success" ng-click="openSubscriptionSetup()">{{ 'domains.subscriptionRequired.setupAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal domain add/configure -->
|
||||
<div class="modal fade" id="domainConfigureModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" ng-show="domainConfigure.adding">{{ 'domains.domainDialog.addTitle' | tr }}</h4>
|
||||
<h4 class="modal-title" ng-hide="domainConfigure.adding">{{ 'domains.domainDialog.editTitle' | tr:{ domain: domainConfigure.domain.domain } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p ng-show="domainConfigure.adding" ng-bind-html="'domains.domainDialog.addDescription' | tr"></p>
|
||||
|
||||
<form name="domainConfigureForm" role="form" novalidate ng-submit="domainConfigure.submit()" autocomplete="off">
|
||||
<p class="has-error text-center" ng-show="domainConfigure.error">{{ domainConfigure.error }}</p>
|
||||
|
||||
<div class="form-group" ng-show="domainConfigure.adding">
|
||||
<label class="control-label">{{ 'domains.domainDialog.domain' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.newDomain" name="newDomain" ng-disabled="domainConfigure.busy" placeholder="example.com" ng-required="domainConfigure.adding" autofocus>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'domains.domainDialog.provider' | tr }} <sup><a ng-href="https://docs.cloudron.io/domains/#dns-providers" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<select class="form-control" ng-model="domainConfigure.provider" ng-options="a.value as a.name for a in dnsProvider" ng-change="domainConfigure.setDefaultTlsProvider()"></select>
|
||||
</div>
|
||||
|
||||
<!-- Route53 -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'route53'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.route53AccessKeyId' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.accessKeyId" name="accessKeyId" ng-disabled="domainConfigure.busy" ng-minlength="16" ng-maxlength="32" ng-required="domainConfigure.provider === 'route53'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'route53'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.route53SecretAccessKey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.secretAccessKey" name="secretAccessKey" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'route53'">
|
||||
</div>
|
||||
|
||||
<!-- Google Cloud DNS -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'gcdns'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.gcdnsServiceAccountKey' | tr }}</label>
|
||||
<div class="input-group">
|
||||
<input type="file" id="gcdnsKeyFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Service Account Key" ng-model="domainConfigure.gcdnsKey.keyFileName" id="gcdnsKeyInput" name="cert" onclick="getElementById('gcdnsKeyFileInput').click();" style="cursor: pointer;" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'gcdns'">
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('gcdnsKeyFileInput').click();"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DigitalOcean -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'digitalocean'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.digitalOceanToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.digitalOceanToken" name="digitalOceanToken" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'digitalocean'">
|
||||
</div>
|
||||
|
||||
<!-- Gandi -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'gandi'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.gandiTokenType' | tr }}</label>
|
||||
<select class="form-control" ng-model="domainConfigure.gandiTokenType">
|
||||
<option value="ApiKey">{{ 'domains.domainDialog.gandiTokenTypeApiKey' | tr }}</option>
|
||||
<option value="PAT">{{ 'domains.domainDialog.gandiTokenTypePAT' | tr }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'gandi'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.gandiApiKey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.gandiApiKey" name="gandiApiKey" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'gandi'">
|
||||
</div>
|
||||
|
||||
<!-- GoDaddy -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'godaddy'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.goDaddyApiKey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.godaddyApiKey" name="apiKey" ng-disabled="domainConfigure.busy" ng-minlength="1" ng-required="domainConfigure.provider === 'godaddy'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'godaddy'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.goDaddyApiSecret' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.godaddyApiSecret" name="apiSecret" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'godaddy'">
|
||||
</div>
|
||||
|
||||
<!-- Netcup -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'netcup'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.netcupCustomerNumber' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.netcupCustomerNumber" name="netcupCustomerNumber" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'netcup'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'netcup'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.netcupApiKey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.netcupApiKey" name="netcupApiKey" ng-disabled="domainConfigure.busy" ng-minlength="1" ng-required="domainConfigure.provider === 'netcup'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'netcup'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.netcupApiPassword' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.netcupApiPassword" name="netcupApiPassword" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'netcup'">
|
||||
</div>
|
||||
|
||||
<!-- OVH -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'ovh'">
|
||||
<label class="control-label" for="inputConfigureOvhEndpoint">{{ 'domains.domainDialog.ovhEndpoint' | tr }}</label>
|
||||
<select class="form-control" name="endpoint" id="inputConfigureOvhEndpoint" ng-model="domainConfigure.ovhEndpoint" ng-options="a.value as a.name for a in ovhEndpoints" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'ovh'"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'ovh'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.ovhConsumerKey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.ovhConsumerKey" name="ovhConsumerKey" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'ovh'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'ovh'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.ovhAppKey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.ovhAppKey" name="ovhAppKey" ng-disabled="domainConfigure.busy" ng-minlength="1" ng-required="domainConfigure.provider === 'ovh'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'ovh'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.ovhAppSecret' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.ovhAppSecret" name="ovhAppSecret" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'ovh'">
|
||||
</div>
|
||||
|
||||
<!-- Porkbun -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'porkbun'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.porkbunApikey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.porkbunApikey" name="porkbunApikey" ng-disabled="domainConfigure.busy" ng-minlength="1" ng-required="domainConfigure.provider === 'porkbun'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'porkbun'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.porkbunSecretapikey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.porkbunSecretapikey" name="porkbunSecretapikey" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'porkbun'">
|
||||
</div>
|
||||
|
||||
<!-- Cloudflare -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'cloudflare'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.cloudflareTokenType' | tr }}</label>
|
||||
<select class="form-control" ng-model="domainConfigure.cloudflareTokenType">
|
||||
<option value="GlobalApiKey">{{ 'domains.domainDialog.cloudflareTokenTypeGlobalApiKey' | tr }}</option>
|
||||
<option value="ApiToken">{{ 'domains.domainDialog.cloudflareTokenTypeApiToken' | tr }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'cloudflare'">
|
||||
<label class="control-label" ng-show="domainConfigure.cloudflareTokenType === 'GlobalApiKey'">{{ 'domains.domainDialog.cloudflareTokenTypeGlobalApiKey' | tr }}</label>
|
||||
<label class="control-label" ng-show="domainConfigure.cloudflareTokenType === 'ApiToken'">{{ 'domains.domainDialog.cloudflareTokenTypeApiToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.cloudflareToken" name="cloudflareToken" ng-required="domainConfigure.provider === 'cloudflare'" ng-disabled="domainConfigure.busy">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'cloudflare' && domainConfigure.cloudflareTokenType === 'GlobalApiKey'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.cloudflareEmail' | tr }}</label>
|
||||
<input type="email" class="form-control" ng-model="domainConfigure.cloudflareEmail" name="cloudflareEmail" ng-required="domainConfigure.provider === 'cloudflare' && domainConfigure.cloudflareTokenType === 'GlobalApiKey'" ng-disabled="domainConfigure.busy">
|
||||
</div>
|
||||
|
||||
<div class="checkbox" ng-show="domainConfigure.provider === 'cloudflare'">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="domainConfigure.cloudflareDefaultProxyStatus"> {{ 'domains.domainDialog.cloudflareDefaultProxyStatus' | tr }}
|
||||
<sup><a ng-href="https://docs.cloudron.io/domains/#cloudflare-dns" class="help" target="_blank" tabIndex="-1"><i class="fa fa-question-circle"></i></a></sup>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Linode -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'linode'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.linodeToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.linodeToken" name="linodeToken" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'linode'">
|
||||
</div>
|
||||
|
||||
<!-- Bunny -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'bunny'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.bunnyAccessKey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.bunnyAccessKey" name="bunnyAccessKey" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'bunny'">
|
||||
</div>
|
||||
|
||||
<!-- dnsimple -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'dnsimple'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.dnsimpleAccessToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.dnsimpleAccessToken" name="dnsimpleAccessToken" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'dnsimple'">
|
||||
</div>
|
||||
|
||||
<!-- Hetzner -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'hetzner'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.hetznerToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.hetznerToken" name="hetznerToken" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'hetzner'">
|
||||
</div>
|
||||
|
||||
<!-- Vultr -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'vultr'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.vultrToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.vultrToken" name="vultrToken" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'vultr'">
|
||||
</div>
|
||||
|
||||
<!-- deSEC -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'desec'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.deSecToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.deSecToken" name="deSecToken" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'desec'">
|
||||
</div>
|
||||
|
||||
<!-- Name.com -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'namecom'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.nameComUsername' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.nameComUsername" name="nameComUsername" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'namecom'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'namecom'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.nameComApiToken' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.nameComToken" name="nameComToken" ng-disabled="domainConfigure.busy" ng-minlength="1" ng-required="domainConfigure.provider === 'namecom'">
|
||||
</div>
|
||||
|
||||
<!-- Namecheap -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'namecheap'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.namecheapUsername' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.namecheapUsername" name="namecheapUsername" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'namecheap'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'namecheap'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.namecheapApiKey' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.namecheapApiKey" name="namecheapApiKey" ng-disabled="domainConfigure.busy" ng-minlength="1" ng-required="domainConfigure.provider === 'namecheap'">
|
||||
</div>
|
||||
|
||||
<p class="small text-info text-bold" ng-show="domainConfigure.provider === 'namecheap'" ng-bind-html="'domains.domainDialog.namecheapInfo' | tr"></p>
|
||||
|
||||
<!-- INWX -->
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'inwx'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.inwxUsername' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.inwxUsername" name="inwxUsername" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'inwx'">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'inwx'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.inwxPassword' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.inwxPassword" name="inwxPassword" ng-disabled="domainConfigure.busy" ng-minlength="1" ng-required="domainConfigure.provider === 'inwx'">
|
||||
</div>
|
||||
|
||||
<p class="small text-info text-bold" ng-show="domainConfigure.provider === 'wildcard'" ng-bind-html="'domains.domainDialog.wildcardInfo' | tr:{ domain: domainConfigure.adding ? domainConfigure.newDomain : domainConfigure.domain.domain }"></p>
|
||||
<p class="small text-info text-bold" ng-show="domainConfigure.provider === 'manual'" ng-bind-html="'domains.domainDialog.manualInfo' | tr"></p>
|
||||
<p class="small text-info text-bold" ng-show="needsPort80(domainConfigure.provider, domainConfigure.tlsConfig.provider)" ng-bind-html="'domains.domainDialog.letsEncryptInfo' | tr"></p>
|
||||
|
||||
<a href="" ng-click="domainConfigure.advancedVisible = true" ng-hide="domainConfigure.advancedVisible">{{ 'domains.domainDialog.advancedAction' | tr }}</a>
|
||||
<div uib-collapse="!domainConfigure.advancedVisible">
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'domains.domainDialog.zoneName' | tr }} <sup><a ng-href="https://docs.cloudron.io/domains/#zone-name" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<input type="text" class="form-control" ng-model="domainConfigure.zoneName" name="zoneName" ng-disabled="domainConfigure.busy">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'domains.domainDialog.certProvider' | tr }} <sup><a ng-href="https://docs.cloudron.io/certificates/#certificate-providers" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<select class="form-control" ng-model="domainConfigure.tlsConfig.provider" ng-options="a.value as a.name for a in tlsProvider"></select>
|
||||
</div>
|
||||
|
||||
<!-- Fallback certificate -->
|
||||
<div ng-show="domainConfigure.tlsConfig.provider !== 'fallback'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.fallbackCert' | tr }}</label>
|
||||
<p ng-bind-html="'domains.domainDialog.fallbackCertInfo' | tr"></p>
|
||||
</div>
|
||||
|
||||
<div ng-show="domainConfigure.tlsConfig.provider === 'fallback'">
|
||||
<label class="control-label">{{ 'domains.domainDialog.fallbackCertCustomCert' | tr }}</label>
|
||||
<p ng-bind-html="'domains.domainDialog.fallbackCertCustomCertInfo' | tr:{ customCertLink: 'https://docs.cloudron.io/certificates/#custom-certificates' }"></p>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': (!fallbackCert.cert.$dirty && fallbackCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="fallbackCertFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="{{ 'domains.domainDialog.fallbackCertCertificatePlaceholder' | tr }}" ng-model="domainConfigure.fallbackCert.certificateFileName" name="cert" onclick="getElementById('fallbackCertFileInput').click();" style="cursor: pointer;" ng-disabled="domainConfigure.busy">
|
||||
<span class="input-group-addon"><i class="fa fa-upload" onclick="getElementById('fallbackCertFileInput').click();"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (!fallbackCert.key.$dirty && fallbackCert.error) }">
|
||||
<div class="input-group">
|
||||
<input type="file" id="fallbackKeyFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="{{ 'domains.domainDialog.fallbackCertKeyPlaceholder' | tr }}" ng-model="domainConfigure.fallbackCert.keyFileName" id="fallbackKeyInput" name="key" onclick="getElementById('fallbackKeyFileInput').click();" style="cursor: pointer;" ng-disabled="domainConfigure.busy">
|
||||
<span class="input-group-addon"><i class="fa fa-upload" onclick="getElementById('fallbackKeyFileInput').click();"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- advanced -->
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="domainConfigureForm.$invalid || domainConfigure.busy"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer ">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="domainConfigure.submit()" ng-disabled="domainConfigureForm.$invalid || domainConfigure.busy"><i class="fa fa-circle-notch fa-spin" ng-show="domainConfigure.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal domain wellknown -->
|
||||
<div class="modal fade" id="domainWellKnownModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'domains.domainWellKnown.title' | tr:{ domain: domainWellKnown.domain.domain } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p ng-bind-html="'domains.domainDialog.wellKnownDescription' | tr:{ domain: domainWellKnown.domain.domain, docsLink: 'https://docs.cloudron.io/domains/#well-known-locations' }"></p>
|
||||
|
||||
<form name="domainWellKnownForm" role="form" novalidate ng-submit="domainWellKnown.submit()" autocomplete="off">
|
||||
<p class="has-error text-center" ng-show="domainWellKnown.error">{{ domainWellKnown.error }}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'domains.domainDialog.matrixHostname' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainWellKnown.matrixHostname" name="matrixHostname" ng-disabled="domainWellKnown.busy">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'domains.domainDialog.mastodonHostname' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainWellKnown.mastodonHostname" name="mastodonHostname" ng-disabled="domainWellKnown.busy">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'domains.domainDialog.jitsiHostname' | tr }}</label>
|
||||
<input type="text" class="form-control" ng-model="domainWellKnown.jitsiHostname" name="jitsiHostname" ng-disabled="domainWellKnown.busy">
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="domainWellKnownForm.$invalid || domainWellKnown.busy"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer ">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="domainWellKnown.submit()" ng-disabled="domainWellKnownForm.$invalid || domainWellKnown.busy"><i class="fa fa-circle-notch fa-spin" ng-show="domainWellKnown.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal domain remove -->
|
||||
<div class="modal fade" id="domainRemoveModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'domains.removeDialog.title' | tr:{ domain: domainRemove.domain.domain } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p ng-bind-html="'domains.removeDialog.description' | tr:{ domain: domainRemove.domain.domain }"></p>
|
||||
<br/>
|
||||
<span class="has-error" ng-show="domainRemove.error">{{ domainRemove.error }}</span>
|
||||
</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="domainRemove.submit()" ng-disabled="domainRemove.busy"><i class="fa fa-circle-notch fa-spin" ng-show="domainRemove.busy"></i> {{ 'domains.removeDialog.removeAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h1 class="section-header">
|
||||
{{ 'domains.title' | tr }}
|
||||
<button class="btn btn-primary btn-outline" ng-show="domains.length <= pageSize" ng-click="domainAdd.show()"><i class="fa fa-plus"></i> {{ 'domains.addDomain' | tr }}</button>
|
||||
</h1>
|
||||
|
||||
<div class="users-toolbar" ng-show="domains.length > pageSize">
|
||||
<input type="text" id="domainSearchInput" class="form-control" style="max-width: 350px;" ng-model="domainSearchString" placeholder="{{ 'main.searchPlaceholder' | tr }}"/>
|
||||
<div style="flex-grow: 1;"></div>
|
||||
<button class="btn btn-primary btn-outline pull-right" ng-click="domainAdd.show()"><i class="fa fa-plus"></i> {{ 'domains.addDomain' | tr }}</button>
|
||||
</div>
|
||||
<div class="card card-large">
|
||||
<div class="row ng-hide" ng-show="!ready">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2><i class="fa fa-circle-notch fa-spin"></i></h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row animateMeOpacity ng-hide" ng-show="ready">
|
||||
<div class="col-lg-12">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'domains.domain' | tr }}</th>
|
||||
<th class="text-left hidden-xs hidden-sm">{{ 'domains.provider' | tr }}</th>
|
||||
<th style="width: 100px" class="text-right">{{ 'main.actions' | tr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="domain in domains | filter:domainSearchString | limitTo:pageSize:((currentPage-1)*pageSize)">
|
||||
<td class="elide-table-cell hand" ng-click="domainConfigure.show(domain)">
|
||||
{{ domain.domain }}
|
||||
</td>
|
||||
<td class="text-left elide-table-cell hidden-xs hidden-sm hand" ng-click="domainConfigure.show(domain)">
|
||||
{{ prettyProviderName(domain) }}
|
||||
</td>
|
||||
<td class="text-right no-wrap" style="vertical-align: bottom">
|
||||
<button class="btn btn-xs btn-default" ng-click="domainWellKnown.show(domain)" uib-tooltip="{{ 'domains.tooltipWellKnown' | tr }}"><i class="fa fa-atlas"></i></button>
|
||||
<button class="btn btn-xs btn-default" ng-click="domainConfigure.show(domain)" uib-tooltip="{{ 'domains.tooltipEdit' | tr }}"><i class="fa fa-pencil-alt"></i></button>
|
||||
<button class="btn btn-xs btn-danger" ng-click="domainRemove.show(domain)" uib-tooltip="{{ 'domains.tooltipRemove' | tr }}" ng-disabled="domain.domain === config.adminDomain"><i class="far fa-trash-alt"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pull-left">
|
||||
{{ 'domains.count' | tr:{ count: (domains | filter:domainSearchString).length } }}
|
||||
</div>
|
||||
<div class="pull-right" ng-show="domains.length > pageSize">
|
||||
<button class="btn btn-default btn-outline btn-xs" ng-click="showPrevPage()" ng-class="{ 'btn-primary': currentPage > 1 }" ng-disabled="currentPage <= 1"><i class="fa fa-angle-double-left"></i> {{ 'main.pagination.prev' | tr }}</button>
|
||||
<span style="margin: 0 5px; line-height: 1.5; font-size: 12px;">{{ currentPage }}</span>
|
||||
<button class="btn btn-default btn-outline btn-xs" ng-click="showNextPage()" ng-class="{ 'btn-primary': (domains | filter:domainSearchString).length > (currentPage * pageSize) }" ng-disabled="(domains | filter:domainSearchString).length <= (currentPage * pageSize)">{{ 'main.pagination.next' | tr }} <i class="fa fa-angle-double-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="section-header">
|
||||
{{ 'domains.renewCerts.title' | tr }}
|
||||
<div class="btn-group btn-group-sm pull-right">
|
||||
<button type="button" class="btn btn-small btn-default dropdown-toggle" ng-show="renewCerts.tasks.length" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" uib-tooltip="{{ 'domains.renewCerts.showLogsAction' | tr }}">
|
||||
<i class="fas fa-align-left"></i> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="task in renewCerts.tasks">
|
||||
<a ng-href="/logs.html?taskId={{task.id}}" target="_blank" class="text-right">
|
||||
{{ task.ts | prettyLongDate }} <i class="fa" style="margin-left: 20px" ng-class="{ 'status-active fa-check-circle': !task.active && task.success, 'fa-circle-notch fa-spin': task.active, 'status-error fa-times-circle': !task.active && !task.success }"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p ng-bind-html="'domains.renewCerts.description' | tr"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="margin-bottom: 10px;">
|
||||
<div ng-show="renewCerts.busy" class="progress progress-striped active animateMe">
|
||||
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ renewCerts.percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p ng-show="renewCerts.busy">{{ renewCerts.message }}</p>
|
||||
<p ng-hide="renewCerts.busy">
|
||||
<div class="has-error" ng-show="!renewCerts.active">{{ renewCerts.errorMessage }}</div>
|
||||
</p>
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="renewCerts.renew()" ng-disabled="renewCerts.busy">{{ 'domains.renewCerts.renewAllAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="section-header">
|
||||
{{ 'domains.syncDns.title' | tr }}
|
||||
<div class="btn-group btn-group-sm pull-right">
|
||||
<button type="button" class="btn btn-small btn-default dropdown-toggle" ng-show="syncDns.tasks.length" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" uib-tooltip="{{ 'domains.renewCerts.showLogsAction' | tr }}">
|
||||
<i class="fas fa-align-left"></i> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="task in syncDns.tasks">
|
||||
<a ng-href="/logs.html?taskId={{task.id}}" target="_blank" class="text-right">
|
||||
{{ task.ts | prettyLongDate }} <i class="fa" style="margin-left: 20px" ng-class="{ 'status-active fa-check-circle': !task.active && task.success, 'fa-circle-notch fa-spin': task.active, 'status-error fa-times-circle': !task.active && !task.success }"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p ng-bind-html="'domains.syncDns.description' | tr"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="margin-bottom: 10px;">
|
||||
<div ng-show="syncDns.busy" class="progress progress-striped active animateMe">
|
||||
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ syncDns.percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p ng-show="syncDns.busy">{{ syncDns.message }}</p>
|
||||
<p ng-hide="syncDns.busy">
|
||||
<div class="has-error" ng-show="!syncDns.active">{{ syncDns.errorMessage }}</div>
|
||||
</p>
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="syncDns.sync()" ng-disabled="syncDns.busy">{{ 'domains.syncDns.syncAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="section-header">
|
||||
{{ 'domains.changeDashboardDomain.title' | tr }}
|
||||
<div class="btn-group btn-group-sm pull-right">
|
||||
<button type="button" class="btn btn-small btn-default dropdown-toggle" ng-show="changeDashboard.tasks.length" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" uib-tooltip="{{ 'domains.renewCerts.showLogsAction' | tr }}">
|
||||
<i class="fas fa-align-left"></i> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="task in changeDashboard.tasks">
|
||||
<a ng-href="/logs.html?taskId={{task.id}}" target="_blank" class="text-right">
|
||||
{{ task.ts | prettyLongDate }} <i class="fa" style="margin-left: 20px" ng-class="{ 'status-active fa-check-circle': !task.active && task.success, 'fa-circle-notch fa-spin': task.active, 'status-error fa-times-circle': !task.active && !task.success }"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<p ng-bind-html="'domains.changeDashboardDomain.description' | tr"></p>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" style="background-color: white;">my.</span>
|
||||
<select class="form-control pull-right" style="display: inline-block;" ng-model="changeDashboard.selectedDomain" ng-options="a.domain for a in domains"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="margin-bottom: 10px;">
|
||||
<div ng-show="changeDashboard.busy" class="progress progress-striped active animateMe">
|
||||
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ changeDashboard.percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<p ng-show="changeDashboard.busy">{{ changeDashboard.message }}</p>
|
||||
<p ng-hide="changeDashboard.busy">
|
||||
<div class="has-error" ng-show="!changeDashboard.active">{{ changeDashboard.errorMessage }}</div>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-right">
|
||||
<button class="btn btn-outline btn-primary" ng-click="changeDashboard.change()" ng-hide="changeDashboard.busy" ng-disabled="changeDashboard.selectedDomain.domain === changeDashboard.adminDomain.domain">{{ 'domains.changeDashboardDomain.changeAction' | tr }}</button>
|
||||
<button class="btn btn-outline btn-danger" ng-click="changeDashboard.stop()" ng-show="changeDashboard.busy" style="margin-right: 10px">{{ 'domains.changeDashboardDomain.cancelAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,800 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* global async */
|
||||
/* global angular */
|
||||
/* global $, TASK_TYPES, ENDPOINTS_OVH */
|
||||
|
||||
angular.module('Application').controller('DomainsController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
|
||||
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
|
||||
|
||||
$scope.config = Client.getConfig();
|
||||
$scope.domains = [];
|
||||
$scope.ready = false;
|
||||
$scope.domainSearchString = '';
|
||||
$scope.pageSize = localStorage.cloudronPageSize || 10;
|
||||
$scope.currentPage = 1;
|
||||
|
||||
$scope.showNextPage = function () {
|
||||
$scope.currentPage++;
|
||||
};
|
||||
|
||||
$scope.showPrevPage = function () {
|
||||
if ($scope.currentPage > 1) $scope.currentPage--;
|
||||
else $scope.currentPage = 1;
|
||||
};
|
||||
|
||||
$scope.translationLinks = {
|
||||
linodeDocsLink: 'https://docs.cloudron.io/domains/#linode-dns',
|
||||
customCertLink: 'https://docs.cloudron.io/certificates/#custom-certificates'
|
||||
};
|
||||
|
||||
$scope.openSubscriptionSetup = function () {
|
||||
Client.openSubscriptionSetup($scope.$parent.subscription);
|
||||
};
|
||||
|
||||
// currently, validation of wildcard with various provider is done server side
|
||||
$scope.tlsProvider = [
|
||||
{ name: 'Let\'s Encrypt Prod', value: 'letsencrypt-prod' },
|
||||
{ name: 'Let\'s Encrypt Prod - Wildcard', value: 'letsencrypt-prod-wildcard' },
|
||||
{ name: 'Let\'s Encrypt Staging', value: 'letsencrypt-staging' },
|
||||
{ name: 'Let\'s Encrypt Staging - Wildcard', value: 'letsencrypt-staging-wildcard' },
|
||||
{ name: 'Custom Wildcard Certificate', value: 'fallback' },
|
||||
];
|
||||
|
||||
// keep in sync with setup.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' },
|
||||
{ name: 'GoDaddy', value: 'godaddy' },
|
||||
{ name: 'Google Cloud DNS', value: 'gcdns' },
|
||||
{ name: 'Hetzner', value: 'hetzner' },
|
||||
{ name: 'INWX', value: 'inwx' },
|
||||
{ name: 'Linode', value: 'linode' },
|
||||
{ name: 'Name.com', value: 'namecom' },
|
||||
{ name: 'Namecheap', value: 'namecheap' },
|
||||
{ name: 'Netcup', value: 'netcup' },
|
||||
{ name: 'OVH', value: 'ovh' },
|
||||
{ name: 'Porkbun', value: 'porkbun' },
|
||||
{ name: 'Vultr', value: 'vultr' },
|
||||
{ name: 'Wildcard', value: 'wildcard' },
|
||||
{ name: 'Manual (not recommended)', value: 'manual' },
|
||||
{ name: 'No-op (only for development)', value: 'noop' }
|
||||
];
|
||||
|
||||
$scope.prettyProviderName = function (domain) {
|
||||
switch (domain.provider) {
|
||||
case 'bunny': return 'Bunny';
|
||||
case 'route53': return 'AWS Route53';
|
||||
case 'cloudflare': return 'Cloudflare';
|
||||
case 'desec': return 'deSEC';
|
||||
case 'digitalocean': return 'DigitalOcean';
|
||||
case 'dnsimple': return 'dnsimple';
|
||||
case 'gandi': return 'Gandi LiveDNS';
|
||||
case 'hetzner': return 'Hetzner DNS';
|
||||
case 'inwx': return 'INWX';
|
||||
case 'linode': return 'Linode';
|
||||
case 'namecom': return 'Name.com';
|
||||
case 'namecheap': return 'Namecheap';
|
||||
case 'netcup': return 'Netcup';
|
||||
case 'ovh': return 'OVH';
|
||||
case 'gcdns': return 'Google Cloud';
|
||||
case 'godaddy': return 'GoDaddy';
|
||||
case 'vultr': return 'Vultr';
|
||||
case 'manual': return 'Manual';
|
||||
case 'porkbun': return 'Porkbun';
|
||||
case 'wildcard': return 'Wildcard';
|
||||
case 'noop': return 'No-op';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.ovhEndpoints = ENDPOINTS_OVH;
|
||||
|
||||
$scope.needsPort80 = function (dnsProvider, tlsProvider) {
|
||||
return ((dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') &&
|
||||
(tlsProvider === 'letsencrypt-prod' || tlsProvider === 'letsencrypt-staging'));
|
||||
};
|
||||
|
||||
function readFileLocally(obj, file, fileName) {
|
||||
return function (event) {
|
||||
$scope.$apply(function () {
|
||||
obj[file] = null;
|
||||
obj[fileName] = event.target.files[0].name;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (result) {
|
||||
if (!result.target || !result.target.result) return console.error('Unable to read local file');
|
||||
obj[file] = result.target.result;
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function refreshDomains(callback) {
|
||||
var domains = [ ];
|
||||
|
||||
Client.getDomains(function (error, results) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
async.eachSeries(results, function (result, iteratorDone) {
|
||||
Client.getDomain(result.domain, function (error, domain) {
|
||||
if (error) return iteratorDone(error);
|
||||
|
||||
domains.push(domain);
|
||||
|
||||
iteratorDone();
|
||||
});
|
||||
}, function (error) {
|
||||
angular.copy(domains, $scope.domains);
|
||||
|
||||
$scope.changeDashboard.selectedDomain = $scope.changeDashboard.adminDomain = $scope.domains.find(function (d) { return d.domain === $scope.config.adminDomain; });
|
||||
|
||||
if (error) console.error(error);
|
||||
if (callback) callback(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$scope.domainAdd = {
|
||||
show: function () {
|
||||
$scope.domainConfigure.show();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.domainWellKnown = {
|
||||
busy: false,
|
||||
error: null,
|
||||
|
||||
domain: null,
|
||||
mastodonHostname: '',
|
||||
matrixHostname: '',
|
||||
jitsiHostname: '',
|
||||
|
||||
reset: function () {
|
||||
$scope.domainWellKnown.busy = false;
|
||||
$scope.domainWellKnown.error = null;
|
||||
$scope.domainWellKnown.domain = null;
|
||||
|
||||
$scope.domainWellKnown.matrixHostname = '';
|
||||
$scope.domainWellKnown.mastodonHostname = '';
|
||||
$scope.domainWellKnown.jitsiHostname = '';
|
||||
},
|
||||
|
||||
show: function (domain) {
|
||||
$scope.domainWellKnown.reset();
|
||||
|
||||
$scope.domainWellKnown.domain = domain;
|
||||
|
||||
try {
|
||||
if (domain.wellKnown && domain.wellKnown['matrix/server']) {
|
||||
$scope.domainWellKnown.matrixHostname = JSON.parse(domain.wellKnown['matrix/server'])['m.server'];
|
||||
}
|
||||
if (domain.wellKnown && domain.wellKnown['host-meta']) {
|
||||
$scope.domainWellKnown.mastodonHostname = domain.wellKnown['host-meta'].match(new RegExp('template="https://(.*?)/'))[1];
|
||||
}
|
||||
if (domain.wellKnown && domain.wellKnown['matrix/client']) {
|
||||
let parsed = JSON.parse(domain.wellKnown['matrix/client']);
|
||||
if (parsed['im.vector.riot.jitsi'] && parsed['im.vector.riot.jitsi']['preferredDomain']) {
|
||||
$scope.domainWellKnown.jitsiHostname = parsed['im.vector.riot.jitsi']['preferredDomain'];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
$('#domainWellKnownModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.domainWellKnown.busy = true;
|
||||
$scope.domainWellKnown.error = null;
|
||||
|
||||
var wellKnown = {};
|
||||
if ($scope.domainWellKnown.matrixHostname) {
|
||||
wellKnown['matrix/server'] = JSON.stringify({ 'm.server': $scope.domainWellKnown.matrixHostname });
|
||||
// https://matrix.org/docs/spec/client_server/latest#get-well-known-matrix-client
|
||||
wellKnown['matrix/client'] = JSON.stringify({
|
||||
'm.homeserver': {
|
||||
'base_url': 'https://' + $scope.domainWellKnown.matrixHostname
|
||||
},
|
||||
'im.vector.riot.jitsi': {
|
||||
'preferredDomain': $scope.domainWellKnown.jitsiHostname
|
||||
}
|
||||
});
|
||||
} else if ($scope.domainWellKnown.jitsiHostname) { // only if matrixHostname is not set
|
||||
wellKnown['matrix/client'] = JSON.stringify({
|
||||
'im.vector.riot.jitsi': {
|
||||
'preferredDomain': $scope.domainWellKnown.jitsiHostname
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ($scope.domainWellKnown.mastodonHostname) {
|
||||
wellKnown['host-meta'] = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
+ '<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">\n'
|
||||
+ '<Link rel="lrdd" type="application/xrd+xml" template="https://' + $scope.domainWellKnown.mastodonHostname + '/.well-known/webfinger?resource={uri}"/>\n'
|
||||
+ '</XRD>';
|
||||
}
|
||||
|
||||
Client.updateDomainWellKnown($scope.domainWellKnown.domain.domain, wellKnown, function (error) {
|
||||
$scope.domainWellKnown.busy = false;
|
||||
if (error) {
|
||||
$scope.domainWellKnown.error = error.message;
|
||||
return;
|
||||
}
|
||||
|
||||
$('#domainWellKnownModal').modal('hide');
|
||||
$scope.domainWellKnown.reset();
|
||||
refreshDomains();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// We reused configure also for adding domains to avoid much code duplication
|
||||
$scope.domainConfigure = {
|
||||
adding: false,
|
||||
error: null,
|
||||
busy: false,
|
||||
domain: null,
|
||||
advancedVisible: false,
|
||||
|
||||
// form model
|
||||
newDomain: '',
|
||||
accessKeyId: '',
|
||||
secretAccessKey: '',
|
||||
gcdnsKey: { keyFileName: '', content: '' },
|
||||
digitalOceanToken: '',
|
||||
gandiApiKey: '',
|
||||
gandiTokenType: 'PAT',
|
||||
godaddyApiKey: '',
|
||||
godaddyApiSecret: '',
|
||||
cloudflareToken: '',
|
||||
cloudflareEmail: '',
|
||||
cloudflareDefaultProxyStatus: false,
|
||||
cloudflareTokenType: 'GlobalApiKey',
|
||||
linodeToken: '',
|
||||
bunnyAccessKey: '',
|
||||
dnsimpleAccessToken: '',
|
||||
hetznerToken: '',
|
||||
vultrToken: '',
|
||||
deSecToken: '',
|
||||
nameComToken: '',
|
||||
nameComUsername: '',
|
||||
namecheapUsername: '',
|
||||
namecheapApiKey: '',
|
||||
netcupCustomerNumber: '',
|
||||
netcupApiKey: '',
|
||||
netcupApiPassword: '',
|
||||
ovhEndpoint: 'ovh-eu',
|
||||
ovhConsumerKey: '',
|
||||
ovhAppKey: '',
|
||||
ovhAppSecret: '',
|
||||
porkbunSecretapikey: '',
|
||||
porkbunApikey: '',
|
||||
inwxUsername: '',
|
||||
inwxPassword: '',
|
||||
|
||||
provider: 'route53',
|
||||
zoneName: '',
|
||||
|
||||
tlsConfig: {
|
||||
provider: 'letsencrypt-prod-wildcard'
|
||||
},
|
||||
|
||||
fallbackCert: {
|
||||
certificateFile: null,
|
||||
certificateFileName: '',
|
||||
keyFile: null,
|
||||
keyFileName: ''
|
||||
},
|
||||
|
||||
setDefaultTlsProvider: function () {
|
||||
var dnsProvider = $scope.domainConfigure.provider;
|
||||
// wildcard LE won't work without automated DNS
|
||||
if (dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') {
|
||||
$scope.domainConfigure.tlsConfig.provider = 'letsencrypt-prod';
|
||||
} else {
|
||||
$scope.domainConfigure.tlsConfig.provider = 'letsencrypt-prod-wildcard';
|
||||
}
|
||||
},
|
||||
|
||||
show: function (domain) {
|
||||
$scope.domainConfigure.reset();
|
||||
|
||||
if (domain) {
|
||||
$scope.domainConfigure.domain = domain;
|
||||
$scope.domainConfigure.accessKeyId = domain.config.accessKeyId;
|
||||
$scope.domainConfigure.secretAccessKey = domain.config.secretAccessKey;
|
||||
|
||||
$scope.domainConfigure.gcdnsKey.keyFileName = '';
|
||||
$scope.domainConfigure.gcdnsKey.content = '';
|
||||
if (domain.provider === 'gcdns') {
|
||||
$scope.domainConfigure.gcdnsKey.keyFileName = domain.config.credentials && domain.config.credentials.client_email;
|
||||
|
||||
$scope.domainConfigure.gcdnsKey.content = JSON.stringify({
|
||||
project_id: domain.config.projectId,
|
||||
client_email: domain.config.credentials.client_email,
|
||||
private_key: domain.config.credentials.private_key
|
||||
});
|
||||
}
|
||||
$scope.domainConfigure.digitalOceanToken = domain.provider === 'digitalocean' ? domain.config.token : '';
|
||||
$scope.domainConfigure.linodeToken = domain.provider === 'linode' ? domain.config.token : '';
|
||||
$scope.domainConfigure.bunnyAccessKey = domain.provider === 'bunny' ? domain.config.accessKey : '';
|
||||
$scope.domainConfigure.dnsimpleAccessToken = domain.provider === 'dnsimple' ? domain.config.accessToken : '';
|
||||
$scope.domainConfigure.hetznerToken = domain.provider === 'hetzner' ? domain.config.token : '';
|
||||
$scope.domainConfigure.vultrToken = domain.provider === 'vultr' ? domain.config.token : '';
|
||||
$scope.domainConfigure.deSecToken = domain.provider === 'desec' ? domain.config.token : '';
|
||||
$scope.domainConfigure.cloudflareToken = domain.provider === 'cloudflare' ? domain.config.token : '';
|
||||
$scope.domainConfigure.cloudflareEmail = domain.provider === 'cloudflare' ? domain.config.email : '';
|
||||
$scope.domainConfigure.cloudflareTokenType = domain.provider === 'cloudflare' ? domain.config.tokenType : 'GlobalApiKey';
|
||||
$scope.domainConfigure.cloudflareDefaultProxyStatus = domain.provider === 'cloudflare' ? !!domain.config.defaultProxyStatus : false;
|
||||
|
||||
$scope.domainConfigure.gandiApiKey = domain.provider === 'gandi' ? domain.config.token : '';
|
||||
$scope.domainConfigure.gandiTokenType = domain.provider === 'gandi' ? domain.config.tokenType : 'ApiKey';
|
||||
|
||||
$scope.domainConfigure.godaddyApiKey = domain.provider === 'godaddy' ? domain.config.apiKey : '';
|
||||
$scope.domainConfigure.godaddyApiSecret = domain.provider === 'godaddy' ? domain.config.apiSecret : '';
|
||||
|
||||
$scope.domainConfigure.nameComToken = domain.provider === 'namecom' ? domain.config.token : '';
|
||||
$scope.domainConfigure.nameComUsername = domain.provider === 'namecom' ? domain.config.username : '';
|
||||
|
||||
$scope.domainConfigure.namecheapApiKey = domain.provider === 'namecheap' ? domain.config.token : '';
|
||||
$scope.domainConfigure.namecheapUsername = domain.provider === 'namecheap' ? domain.config.username : '';
|
||||
|
||||
$scope.domainConfigure.inwxUsername = domain.provider === 'inwx' ? domain.config.username : '';
|
||||
$scope.domainConfigure.inwxPassword = domain.provider === 'inwx' ? domain.config.password : '';
|
||||
|
||||
$scope.domainConfigure.netcupCustomerNumber = domain.provider === 'netcup' ? domain.config.customerNumber : '';
|
||||
$scope.domainConfigure.netcupApiKey = domain.provider === 'netcup' ? domain.config.apiKey : '';
|
||||
$scope.domainConfigure.netcupApiPassword = domain.provider === 'netcup' ? domain.config.apiPassword : '';
|
||||
|
||||
$scope.domainConfigure.ovhEndpoint = domain.provider === 'ovh' ? domain.config.endpoint : '';
|
||||
$scope.domainConfigure.ovhConsumerKey = domain.provider === 'ovh' ? domain.config.consumerKey : '';
|
||||
$scope.domainConfigure.ovhAppKey = domain.provider === 'ovh' ? domain.config.appKey : '';
|
||||
$scope.domainConfigure.ovhAppSecret = domain.provider === 'ovh' ? domain.config.appSecret : '';
|
||||
|
||||
$scope.domainConfigure.porkbunApikey = domain.provider === 'porkbun' ? domain.config.apikey : '';
|
||||
$scope.domainConfigure.porkbunSecretapikey = domain.provider === 'porkbun' ? domain.config.secretapikey : '';
|
||||
|
||||
$scope.domainConfigure.provider = domain.provider;
|
||||
|
||||
$scope.domainConfigure.tlsConfig.provider = domain.tlsConfig.provider;
|
||||
if (domain.tlsConfig.provider.indexOf('letsencrypt') === 0) {
|
||||
if (domain.tlsConfig.wildcard) $scope.domainConfigure.tlsConfig.provider += '-wildcard';
|
||||
}
|
||||
$scope.domainConfigure.zoneName = domain.zoneName;
|
||||
} else {
|
||||
$scope.domainConfigure.adding = true;
|
||||
}
|
||||
|
||||
$('#domainConfigureModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.domainConfigure.busy = true;
|
||||
$scope.domainConfigure.error = null;
|
||||
|
||||
var provider = $scope.domainConfigure.provider;
|
||||
|
||||
var data = {};
|
||||
|
||||
if (provider === 'route53') {
|
||||
data.accessKeyId = $scope.domainConfigure.accessKeyId;
|
||||
data.secretAccessKey = $scope.domainConfigure.secretAccessKey;
|
||||
} else if (provider === 'gcdns') {
|
||||
try {
|
||||
var serviceAccountKey = JSON.parse($scope.domainConfigure.gcdnsKey.content);
|
||||
data.projectId = serviceAccountKey.project_id;
|
||||
data.credentials = {
|
||||
client_email: serviceAccountKey.client_email,
|
||||
private_key: serviceAccountKey.private_key
|
||||
};
|
||||
|
||||
if (!data.projectId || !data.credentials || !data.credentials.client_email || !data.credentials.private_key) {
|
||||
throw new Error('One or more fields are missing in the JSON');
|
||||
}
|
||||
} catch (e) {
|
||||
$scope.domainConfigure.error = 'Cannot parse Google Service Account Key: ' + e.message;
|
||||
$scope.domainConfigure.busy = false;
|
||||
return;
|
||||
}
|
||||
} else if (provider === 'digitalocean') {
|
||||
data.token = $scope.domainConfigure.digitalOceanToken;
|
||||
} else if (provider === 'linode') {
|
||||
data.token = $scope.domainConfigure.linodeToken;
|
||||
} else if (provider === 'bunny') {
|
||||
data.accessKey = $scope.domainConfigure.bunnyAccessKey;
|
||||
} else if (provider === 'dnsimple') {
|
||||
data.accessToken = $scope.domainConfigure.dnsimpleAccessToken;
|
||||
} else if (provider === 'hetzner') {
|
||||
data.token = $scope.domainConfigure.hetznerToken;
|
||||
} else if (provider === 'vultr') {
|
||||
data.token = $scope.domainConfigure.vultrToken;
|
||||
} else if (provider === 'desec') {
|
||||
data.token = $scope.domainConfigure.deSecToken;
|
||||
} else if (provider === 'gandi') {
|
||||
data.token = $scope.domainConfigure.gandiApiKey;
|
||||
data.tokenType = $scope.domainConfigure.gandiTokenType;
|
||||
} else if (provider === 'godaddy') {
|
||||
data.apiKey = $scope.domainConfigure.godaddyApiKey;
|
||||
data.apiSecret = $scope.domainConfigure.godaddyApiSecret;
|
||||
} else if (provider === 'cloudflare') {
|
||||
data.token = $scope.domainConfigure.cloudflareToken;
|
||||
data.email = $scope.domainConfigure.cloudflareEmail;
|
||||
data.tokenType = $scope.domainConfigure.cloudflareTokenType;
|
||||
data.defaultProxyStatus = $scope.domainConfigure.cloudflareDefaultProxyStatus;
|
||||
} else if (provider === 'namecom') {
|
||||
data.token = $scope.domainConfigure.nameComToken;
|
||||
data.username = $scope.domainConfigure.nameComUsername;
|
||||
} else if (provider === 'namecheap') {
|
||||
data.token = $scope.domainConfigure.namecheapApiKey;
|
||||
data.username = $scope.domainConfigure.namecheapUsername;
|
||||
} else if (provider === 'inwx') {
|
||||
data.username = $scope.domainConfigure.inwxUsername;
|
||||
data.password = $scope.domainConfigure.inwxPassword;
|
||||
} else if (provider === 'netcup') {
|
||||
data.customerNumber = $scope.domainConfigure.netcupCustomerNumber;
|
||||
data.apiKey = $scope.domainConfigure.netcupApiKey;
|
||||
data.apiPassword = $scope.domainConfigure.netcupApiPassword;
|
||||
} else if (provider === 'ovh') {
|
||||
data.endpoint = $scope.domainConfigure.ovhEndpoint;
|
||||
data.consumerKey = $scope.domainConfigure.ovhConsumerKey;
|
||||
data.appKey = $scope.domainConfigure.ovhAppKey;
|
||||
data.appSecret = $scope.domainConfigure.ovhAppSecret;
|
||||
} else if (provider === 'porkbun') {
|
||||
data.apikey = $scope.domainConfigure.porkbunApikey;
|
||||
data.secretapikey = $scope.domainConfigure.porkbunSecretapikey;
|
||||
}
|
||||
|
||||
var fallbackCertificate = null;
|
||||
if ($scope.domainConfigure.fallbackCert.certificateFile && $scope.domainConfigure.fallbackCert.keyFile) {
|
||||
fallbackCertificate = {
|
||||
cert: $scope.domainConfigure.fallbackCert.certificateFile,
|
||||
key: $scope.domainConfigure.fallbackCert.keyFile
|
||||
};
|
||||
}
|
||||
|
||||
var tlsConfig = {
|
||||
provider: $scope.domainConfigure.tlsConfig.provider,
|
||||
wildcard: false
|
||||
};
|
||||
if ($scope.domainConfigure.tlsConfig.provider.indexOf('-wildcard') !== -1) {
|
||||
tlsConfig.provider = tlsConfig.provider.replace('-wildcard', '');
|
||||
tlsConfig.wildcard = true;
|
||||
}
|
||||
|
||||
// choose the right api, since we reuse this for adding and configuring domains
|
||||
var func;
|
||||
if ($scope.domainConfigure.adding) func = Client.addDomain.bind(Client, $scope.domainConfigure.newDomain, $scope.domainConfigure.zoneName, provider, data, fallbackCertificate, tlsConfig);
|
||||
else func = Client.updateDomainConfig.bind(Client, $scope.domainConfigure.domain.domain, $scope.domainConfigure.zoneName, provider, data, fallbackCertificate, tlsConfig);
|
||||
|
||||
func(function (error) {
|
||||
$scope.domainConfigure.busy = false;
|
||||
if (error) {
|
||||
$scope.domainConfigure.error = error.message;
|
||||
return;
|
||||
}
|
||||
|
||||
$('#domainConfigureModal').modal('hide');
|
||||
$scope.domainConfigure.reset();
|
||||
|
||||
refreshDomains();
|
||||
});
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
$scope.domainConfigure.adding = false;
|
||||
$scope.domainConfigure.advancedVisible = false;
|
||||
$scope.domainConfigure.newDomain = '';
|
||||
|
||||
$scope.domainConfigure.busy = false;
|
||||
$scope.domainConfigure.error = null;
|
||||
|
||||
$scope.domainConfigure.provider = '';
|
||||
$scope.domainConfigure.accessKeyId = '';
|
||||
$scope.domainConfigure.secretAccessKey = '';
|
||||
$scope.domainConfigure.gcdnsKey.keyFileName = '';
|
||||
$scope.domainConfigure.gcdnsKey.content = '';
|
||||
$scope.domainConfigure.digitalOceanToken = '';
|
||||
$scope.domainConfigure.gandiApiKey = '';
|
||||
$scope.domainConfigure.gandiTokenType = 'PAT';
|
||||
$scope.domainConfigure.godaddyApiKey = '';
|
||||
$scope.domainConfigure.godaddyApiSecret = '';
|
||||
$scope.domainConfigure.cloudflareToken = '';
|
||||
$scope.domainConfigure.cloudflareEmail = '';
|
||||
$scope.domainConfigure.cloudflareTokenType = 'GlobalApiKey';
|
||||
$scope.domainConfigure.cloudflareDefaultProxyStatus = false;
|
||||
$scope.domainConfigure.nameComToken = '';
|
||||
$scope.domainConfigure.nameComUsername = '';
|
||||
$scope.domainConfigure.namecheapApiKey = '';
|
||||
$scope.domainConfigure.namecheapUsername = '';
|
||||
$scope.domainConfigure.inwxUsername = '';
|
||||
$scope.domainConfigure.inwxPassword = '';
|
||||
$scope.domainConfigure.netcupCustomerNumber = '';
|
||||
$scope.domainConfigure.netcupApiKey = '';
|
||||
$scope.domainConfigure.netcupApiPassword = '';
|
||||
$scope.domainConfigure.ovhEndpoint = '';
|
||||
$scope.domainConfigure.ovhConsumerKey = '';
|
||||
$scope.domainConfigure.ovhAppKey = '';
|
||||
$scope.domainConfigure.ovhAppSecret = '';
|
||||
$scope.domainConfigure.porkbunApikey = '';
|
||||
$scope.domainConfigure.porkbunSecretapikey = '';
|
||||
$scope.domainConfigure.vultrToken = '';
|
||||
$scope.domainConfigure.deSecToken = '';
|
||||
|
||||
$scope.domainConfigure.tlsConfig.provider = 'letsencrypt-prod';
|
||||
$scope.domainConfigure.zoneName = '';
|
||||
|
||||
$scope.domainConfigureForm.$setPristine();
|
||||
$scope.domainConfigureForm.$setUntouched();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.renewCerts = {
|
||||
busy: false,
|
||||
percent: 0,
|
||||
message: '',
|
||||
errorMessage: '',
|
||||
tasks: [],
|
||||
|
||||
refreshTasks: function () {
|
||||
Client.getTasksByType(TASK_TYPES.TASK_CHECK_CERTS, function (error, tasks) {
|
||||
if (error) return console.error(error);
|
||||
$scope.renewCerts.tasks = tasks.slice(0, 10);
|
||||
if ($scope.renewCerts.tasks.length && $scope.renewCerts.tasks[0].active) $scope.renewCerts.updateStatus();
|
||||
});
|
||||
},
|
||||
|
||||
updateStatus: function () {
|
||||
var taskId = $scope.renewCerts.tasks[0].id;
|
||||
|
||||
Client.getTask(taskId, function (error, data) {
|
||||
if (error) return window.setTimeout($scope.renewCerts.updateStatus, 5000);
|
||||
|
||||
if (!data.active) {
|
||||
$scope.renewCerts.busy = false;
|
||||
$scope.renewCerts.message = '';
|
||||
$scope.renewCerts.percent = 100; // indicates that 'result' is valid
|
||||
$scope.renewCerts.errorMessage = data.success ? '' : data.error.message;
|
||||
|
||||
$scope.renewCerts.refreshTasks(); // update the tasks list dropdown
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.renewCerts.busy = true;
|
||||
$scope.renewCerts.percent = data.percent;
|
||||
$scope.renewCerts.message = data.message;
|
||||
window.setTimeout($scope.renewCerts.updateStatus, 500);
|
||||
});
|
||||
},
|
||||
|
||||
renew: function () {
|
||||
$scope.renewCerts.busy = true;
|
||||
$scope.renewCerts.percent = 0;
|
||||
$scope.renewCerts.message = '';
|
||||
$scope.renewCerts.errorMessage = '';
|
||||
|
||||
// always rebuild the nginx configs when triggered via the UI. we assume user is clicking this because something is wrong
|
||||
Client.renewCerts({ rebuild: true }, function (error /*, taskId */) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
$scope.renewCerts.errorMessage = error.message;
|
||||
$scope.renewCerts.busy = false;
|
||||
} else {
|
||||
$scope.renewCerts.refreshTasks();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.syncDns = {
|
||||
busy: false,
|
||||
percent: 0,
|
||||
message: '',
|
||||
errorMessage: '',
|
||||
tasks: [],
|
||||
|
||||
refreshTasks: function () {
|
||||
Client.getTasksByType(TASK_TYPES.TASK_SYNC_DNS_RECORDS, function (error, tasks) {
|
||||
if (error) return console.error(error);
|
||||
$scope.syncDns.tasks = tasks.slice(0, 10);
|
||||
if ($scope.syncDns.tasks.length && $scope.syncDns.tasks[0].active) $scope.syncDns.updateStatus();
|
||||
});
|
||||
},
|
||||
|
||||
updateStatus: function () {
|
||||
var taskId = $scope.syncDns.tasks[0].id;
|
||||
Client.getTask(taskId, function (error, data) {
|
||||
if (error) return window.setTimeout($scope.syncDns.updateStatus, 5000);
|
||||
|
||||
if (!data.active) {
|
||||
$scope.syncDns.busy = false;
|
||||
$scope.syncDns.message = '';
|
||||
$scope.syncDns.percent = 100; // indicates that 'result' is valid
|
||||
$scope.syncDns.errorMessage = data.success ? '' : data.error.message;
|
||||
|
||||
$scope.syncDns.refreshTasks(); // update the tasks list dropdown
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.syncDns.busy = true;
|
||||
$scope.syncDns.percent = data.percent;
|
||||
$scope.syncDns.message = data.message;
|
||||
window.setTimeout($scope.syncDns.updateStatus, 500);
|
||||
});
|
||||
},
|
||||
|
||||
sync: function () {
|
||||
$scope.syncDns.busy = true;
|
||||
$scope.syncDns.percent = 0;
|
||||
$scope.syncDns.message = '';
|
||||
$scope.syncDns.errorMessage = '';
|
||||
|
||||
Client.setDnsRecords({}, function (error /*, taskId */) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
$scope.syncDns.errorMessage = error.message;
|
||||
$scope.syncDns.busy = false;
|
||||
} else {
|
||||
$scope.syncDns.refreshTasks();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.domainRemove = {
|
||||
busy: false,
|
||||
error: null,
|
||||
domain: null,
|
||||
|
||||
show: function (domain) {
|
||||
$scope.domainRemove.reset();
|
||||
|
||||
$scope.domainRemove.domain = domain;
|
||||
|
||||
$('#domainRemoveModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.domainRemove.busy = true;
|
||||
$scope.domainRemove.error = null;
|
||||
|
||||
Client.removeDomain($scope.domainRemove.domain.domain, function (error) {
|
||||
if (error && (error.statusCode === 403 || error.statusCode === 409)) {
|
||||
$scope.domainRemove.error = error.message;
|
||||
} else if (error) {
|
||||
Client.error(error);
|
||||
} else {
|
||||
$('#domainRemoveModal').modal('hide');
|
||||
$scope.domainRemove.reset();
|
||||
|
||||
refreshDomains();
|
||||
}
|
||||
|
||||
$scope.domainRemove.busy = false;
|
||||
});
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
$scope.domainRemove.busy = false;
|
||||
$scope.domainRemove.error = null;
|
||||
$scope.domainRemove.domain = null;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.changeDashboard = {
|
||||
busy: false,
|
||||
percent: 0,
|
||||
message: '',
|
||||
errorMessage: '',
|
||||
taskId: '',
|
||||
selectedDomain: null,
|
||||
adminDomain: null,
|
||||
tasks: [],
|
||||
|
||||
refreshTasks: function () {
|
||||
Client.getTasksByType(TASK_TYPES.TASK_PREPARE_DASHBOARD_LOCATION, function (error, tasks) {
|
||||
if (error) return console.error(error);
|
||||
$scope.changeDashboard.tasks = tasks.slice(0, 10);
|
||||
if ($scope.changeDashboard.tasks.length && $scope.changeDashboard.tasks[0].active) $scope.changeDashboard.updateStatus();
|
||||
});
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
Client.stopTask($scope.changeDashboard.taskId, function (error) {
|
||||
if (error) console.error(error);
|
||||
$scope.changeDashboard.busy = false;
|
||||
$scope.changeDashboard.refreshTasks();
|
||||
});
|
||||
},
|
||||
|
||||
updateStatus: function () {
|
||||
if (!$scope.changeDashboard.busy) return; // task got stopped
|
||||
|
||||
Client.getTask($scope.changeDashboard.taskId, function (error, data) {
|
||||
if (error) return window.setTimeout($scope.changeDashboard.updateStatus, 5000);
|
||||
|
||||
if (!data.active) {
|
||||
$scope.changeDashboard.busy = false;
|
||||
$scope.changeDashboard.message = '';
|
||||
$scope.changeDashboard.percent = 100; // indicates that 'result' is valid
|
||||
$scope.changeDashboard.errorMessage = data.success ? '' : data.error.message;
|
||||
|
||||
if (!$scope.changeDashboard.errorMessage) $scope.changeDashboard.setDashboardDomain();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.changeDashboard.busy = true;
|
||||
$scope.changeDashboard.percent = data.percent;
|
||||
$scope.changeDashboard.message = data.message;
|
||||
window.setTimeout($scope.changeDashboard.updateStatus, 500);
|
||||
});
|
||||
},
|
||||
|
||||
setDashboardDomain: function () {
|
||||
Client.setDashboardDomain($scope.changeDashboard.selectedDomain.domain, function (error) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
$scope.changeDashboard.errorMessage = error.message;
|
||||
|
||||
$scope.changeDashboard.busy = false;
|
||||
} else {
|
||||
window.location.href = 'https://my.' + $scope.changeDashboard.selectedDomain.domain;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
change: function () {
|
||||
$scope.changeDashboard.busy = true;
|
||||
$scope.changeDashboard.message = 'Preparing dashboard domain';
|
||||
$scope.changeDashboard.percent = 0;
|
||||
$scope.changeDashboard.errorMessage = '';
|
||||
|
||||
Client.prepareDashboardDomain($scope.changeDashboard.selectedDomain.domain, function (error, taskId) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
$scope.changeDashboard.errorMessage = error.message;
|
||||
|
||||
$scope.changeDashboard.busy = false;
|
||||
} else {
|
||||
$scope.changeDashboard.taskId = taskId;
|
||||
$scope.changeDashboard.refreshTasks();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Client.onReady(function () {
|
||||
refreshDomains(function (error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.ready = true;
|
||||
});
|
||||
|
||||
$scope.renewCerts.refreshTasks();
|
||||
$scope.syncDns.refreshTasks();
|
||||
$scope.changeDashboard.refreshTasks();
|
||||
});
|
||||
|
||||
document.getElementById('gcdnsKeyFileInput').onchange = readFileLocally($scope.domainConfigure.gcdnsKey, 'content', 'keyFileName');
|
||||
document.getElementById('fallbackCertFileInput').onchange = readFileLocally($scope.domainConfigure.fallbackCert, 'certificateFile', 'certificateFileName');
|
||||
document.getElementById('fallbackKeyFileInput').onchange = readFileLocally($scope.domainConfigure.fallbackCert, 'keyFile', 'keyFileName');
|
||||
|
||||
// setup all the dialog focus handling
|
||||
['domainConfigureModal', 'domainRemoveModal'].forEach(function (id) {
|
||||
$('#' + id).on('shown.bs.modal', function () {
|
||||
$(this).find('[autofocus]:first').focus();
|
||||
});
|
||||
});
|
||||
|
||||
$('.modal-backdrop').remove();
|
||||
}]);
|
||||
@@ -1,734 +0,0 @@
|
||||
<!-- Modal subscription -->
|
||||
<div class="modal fade" id="subscriptionRequiredModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'email.subscriptionDialog.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ 'email.subscriptionDialog.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-success" ng-click="openSubscriptionSetup()">{{ 'email.subscriptionDialog.setupAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal enable email -->
|
||||
<div class="modal fade" id="enableEmailModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'email.enableEmailDialog.title' | tr:{ domain: domain.domain } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p ng-bind-html="'email.enableEmailDialog.description' | tr:{ domain: domain.domain, requiredPortsDocsLink: 'https://docs.cloudron.io/email/#required-ports' }"></p>
|
||||
<p class="text-warning" ng-show="domain.provider === 'noop' || domain.provider === 'manual'" ng-bind-html="'email.enableEmailDialog.noProviderInfo' | tr"></p>
|
||||
<p class="text-danger" ng-show="adminDomain.provider === 'cloudflare'" ng-bind-html="'email.enableEmailDialog.cloudflareInfo' | tr:{ adminDomain: config.adminDomain, mailFqdn: config.mailFqdn }"></p>
|
||||
<div ng-hide="domain.provider === 'noop' || domain.provider === 'manual'">
|
||||
<p>
|
||||
<label class="control-label">
|
||||
<input type="checkbox" ng-model="incomingEmail.setupDns"> {{ 'email.enableEmailDialog.setupDnsCheckbox' | tr }}
|
||||
</label>
|
||||
</p>
|
||||
<span ng-bind-html="'email.enableEmailDialog.setupDnsInfo' | tr:{ importEmailDocsLink: 'https://docs.cloudron.io/guides/import-email' }"></span>
|
||||
</div>
|
||||
</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-success" ng-click="incomingEmail.enable()">{{ 'email.enableEmailDialog.enableAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal disable email -->
|
||||
<div class="modal fade" id="disableEmailModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'email.disableEmailDialog.title' | tr:{ domain: domain.domain } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div ng-bind-html="'email.disableEmailDialog.description' | tr:{ domain: domain.domain }"></div>
|
||||
<br/>
|
||||
</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="incomingEmail.disable()">{{ 'email.disableEmailDialog.disableAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal add mailbox -->
|
||||
<div class="modal fade" id="mailboxAddModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'email.addMailboxDialog.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="mailboxadd_form" role="form" ng-submit="mailboxes.add.submit()" autocomplete="off">
|
||||
<input type="password" style="display: none;">
|
||||
<div class="form-group" ng-class="{ 'has-error': mailboxes.add.error }">
|
||||
<label class="control-label">{{ 'email.addMailboxDialog.name' | tr }}</label>
|
||||
<div class="control-label" ng-show="mailboxes.add.error">
|
||||
<small>{{ mailboxes.add.error.message }}</small>
|
||||
</div>
|
||||
<div class="input-group form-inline" style="margin-top: 10px;">
|
||||
<input type="text" class="form-control" ng-model="mailboxes.add.name" required autofocus autocomplete="off"/>
|
||||
<div class="input-group-addon">@{{ domain.domain }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'email.addMailboxDialog.owner' | tr }}</label>
|
||||
<div class="control-label">
|
||||
<multiselect ng-model="mailboxes.add.owner" options="o.display for o in owners" data-compare-by="name" data-header-key="header" data-divider-key="divider" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
<input class="hide" type="submit" ng-disabled="mailboxadd_form.$invalid || mailboxes.add.busy || !mailboxes.add.owner"/>
|
||||
</form>
|
||||
</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-success" ng-click="mailboxes.add.submit()" ng-disabled="mailboxadd_form.$invalid || mailboxes.add.busy || !mailboxes.add.owner"><i class="fa fa-circle-notch fa-spin" ng-show="mailboxes.add.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal edit mailbox -->
|
||||
<div class="modal fade" id="mailboxEditModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'email.editMailboxDialog.title' | tr:{ name: mailboxes.edit.name, domain: domain.domain } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'email.editMailboxDialog.owner' | tr }}</label>
|
||||
<div class="control-label">
|
||||
<multiselect ng-model="mailboxes.edit.owner" options="o.display for o in owners" data-compare-by="name" data-header-key="header" data-divider-key="divider" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group aliases">
|
||||
<label class="control-label">{{ 'email.editMailboxDialog.aliases' | tr }}</label>
|
||||
<div class="has-error" ng-show="mailboxes.edit.error">{{ mailboxes.edit.error.message }}</div>
|
||||
|
||||
<div class="row" ng-repeat="alias in mailboxes.edit.aliases | orderBy:'reversedSortingNotation'">
|
||||
<div class="col col-lg-11">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" ng-model="alias.name">
|
||||
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
|
||||
<span>@{{ alias.domain }}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li ng-repeat="incomingDomain in incomingDomains">
|
||||
<a href="" ng-click="alias.domain = incomingDomain.domain">{{ incomingDomain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-lg-1">
|
||||
<button class="btn btn-danger btn-sm" ng-click="mailboxes.edit.delAlias($event, alias)"><i class="far fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="mailboxes.edit.aliases.length === 0">
|
||||
{{ 'email.editMailboxDialog.noAliases' | tr }} <a href="" ng-click="mailboxes.edit.addAlias($event)">{{ 'email.editMailboxDialog.addAliasAction' | tr }}</a>
|
||||
</div>
|
||||
<div ng-show="mailboxes.edit.aliases.length > 0" style="margin-top: 5px;">
|
||||
<a href="" ng-click="mailboxes.edit.addAlias($event)">{{ 'email.editMailboxDialog.addAnotherAliasAction' | tr }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="storageQuota">
|
||||
<input id="storageQuota" type="checkbox" ng-model="mailboxes.edit.storageQuotaEnabled">
|
||||
{{ 'email.editMailboxDialog.enableStorageQuota' | tr }} <b ng-hide="!mailboxes.edit.storageQuotaEnabled">: {{ mailboxes.edit.storageQuota | prettyDecimalSize }}</b>
|
||||
</input>
|
||||
</label>
|
||||
<input type="range" id="storageQuota" ng-disabled="!mailboxes.edit.storageQuotaEnabled" ng-model="mailboxes.edit.storageQuota" step="500000000" min="{{ storageQuotaTicks[0] }}" max="{{ storageQuotaTicks[storageQuotaTicks.length-1] }}" list="storageQuotaTicks" />
|
||||
<datalist id="storageQuotaTicks">
|
||||
<option ng-repeat="quota in storageQuotaTicks" value="{{ quota }}"></option>
|
||||
</datalist>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="mailboxes.edit.enablePop3"> {{ 'email.updateMailboxDialog.enablePop3' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="mailboxes.edit.active"> {{ 'email.updateMailboxDialog.activeCheckbox' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
</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-success" ng-click="mailboxes.edit.submit()" ng-disabled="mailboxes.edit.busy || !mailboxes.edit.owner"><i class="fa fa-circle-notch fa-spin" ng-show="mailboxes.edit.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal remove mailbox -->
|
||||
<div class="modal fade" id="mailboxRemoveModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'email.deleteMailboxDialog.title' | tr:{ name: mailboxes.remove.mailbox.name, domain: domain.domain } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div ng-bind-html="'email.deleteMailboxDialog.description' | tr"></div>
|
||||
<br/>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="mailboxes.remove.deleteMails">{{ 'email.deleteMailboxDialog.purgeMailboxCheckbox' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
</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="mailboxes.remove.submit()" ng-disabled="mailboxes.remove.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailboxes.remove.busy"></i> {{ 'email.deleteMailboxDialog.deleteAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal add mailinglist -->
|
||||
<div class="modal fade" id="mailinglistAddModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'email.addMailinglistDialog.title' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="mailinglistadd_form" role="form" ng-submit="mailinglists.add.submit()" autocomplete="off">
|
||||
<input type="password" style="display: none;">
|
||||
<div class="form-group" ng-class="{ 'has-error': mailinglists.add.error.name }">
|
||||
<label class="control-label">{{ 'email.addMailinglistDialog.name' | tr }}</label>
|
||||
<div class="control-label" ng-show="mailinglists.add.error.name"><small>{{ mailinglists.add.error.name }}</small></div>
|
||||
<div class="input-group form-inline" style="margin-top: 10px;">
|
||||
<input type="text" class="form-control" ng-model="mailinglists.add.name" required autofocus autocomplete="off"/>
|
||||
<div class="input-group-addon">@{{ domain.domain }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">{{ 'email.addMailinglistDialog.members' | tr }}</label><br/>
|
||||
<div class="has-error control-label" ng-show="mailinglists.add.error.members"><small>{{ mailinglists.add.error.members }}</small></div>
|
||||
<textarea ng-model="mailinglists.add.membersTxt" class="form-control" rows="5"></textarea>
|
||||
<small>{{ 'email.addMailinglistDialog.membersInfo' | tr }}</small>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="mailinglists.add.membersOnly">{{ 'email.addMailinglistDialog.membersOnlyCheckbox' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
<input class="hide" type="submit" ng-disabled="mailinglistadd_form.$invalid || mailinglists.add.membersTxt.length === 0 || mailinglists.add.busy"/>
|
||||
</form>
|
||||
</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-success" ng-click="mailinglists.add.submit()" ng-disabled="mailinglistadd_form.$invalid || mailinglists.add.membersTxt.length === 0 || mailinglists.add.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailinglists.add.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal edit mailinglist -->
|
||||
<div class="modal fade" id="mailinglistEditModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'email.editMailinglistDialog.title' | tr:{ name: mailinglists.edit.name, domain: domain.domain } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="mailinglistedit_form" role="form" ng-submit="mailinglists.edit.submit()" autocomplete="off">
|
||||
<input type="password" style="display: none;">
|
||||
<div class="form-group" ng-class="{ 'has-error': mailinglists.edit.error.members }">
|
||||
<label class="control-label">{{ 'email.addMailinglistDialog.members' | tr }}</label><br/>
|
||||
<div class="has-error control-label" ng-show="mailinglists.edit.error.members"><small>{{ mailinglists.edit.error.members }}</small></div>
|
||||
<textarea ng-model="mailinglists.edit.membersTxt" class="form-control" rows="5" autofocus></textarea>
|
||||
<small>{{ 'email.addMailinglistDialog.membersInfo' | tr }}</small>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="mailinglists.edit.membersOnly">{{ 'email.addMailinglistDialog.membersOnlyCheckbox' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="mailinglists.edit.active"> {{ 'email.updateMailinglistDialog.activeCheckbox' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
<input class="hide" type="submit" ng-disabled="mailinglistedit_form.$invalid || mailinglists.edit.busy"/>
|
||||
</form>
|
||||
</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-success" ng-click="mailinglists.edit.submit()" ng-disabled="mailinglistedit_form.$invalid || mailinglists.edit.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailinglists.edit.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal remove mailinglist -->
|
||||
<div class="modal fade" id="mailinglistRemoveModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'email.deleteMailinglistDialog.title' | tr:{ name: mailinglists.remove.list.name, domain: domain.domain } }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p ng-bind-html="'email.deleteMailinglistDialog.description' | tr:{ name: mailinglists.remove.list.name, domain: domain.domain }"></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="mailinglists.remove.submit()" ng-disabled="mailinglists.remove.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailinglist.remove.busy"></i> {{ 'email.deleteMailinglistDialog.deleteAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal how to connect -->
|
||||
<div class="modal fade" id="howToConnectInfoModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>{{ 'email.howToConnectInfoModal' | tr }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p ng-bind-html=" 'email.incoming.howToConnectDescription' | tr:{ domain: domain.domain } "></p>
|
||||
|
||||
<p><b>{{ 'email.incoming.incomingUserInfo' | tr }}</b><br/><i>mailboxname</i>@{{ domain.domain }}</p>
|
||||
<p><b>{{ 'email.incoming.incomingPasswordInfo' | tr }}</b><br/>{{ 'email.incoming.incomingPasswordUsage' | tr }}</p>
|
||||
<p><b>{{ 'email.incoming.incomingServerInfo' | tr }}</b><br/>{{ 'email.incoming.server' | tr }}: <span ng-click-select>{{config.mailFqdn}}</span><br/>{{ 'email.incoming.port' | tr }}: 993 (TLS)</p>
|
||||
<p><b>{{ 'email.incoming.outgointServerInfo' | tr }}</b><br/>{{ 'email.incoming.server' | tr }}: <span ng-click-select>{{config.mailFqdn}}</span><br/>{{ 'email.incoming.port' | tr }}: 587 (STARTTLS) or 465 (TLS)</p>
|
||||
<p><b>{{ 'email.incoming.sieveServerInfo' | tr }}</b><br/>{{ 'email.incoming.server' | tr }}: <span ng-click-select>{{config.mailFqdn}}</span><br/>{{ 'email.incoming.port' | tr }}: 4190 (STARTTLS)</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="!ready" class="loading-banner">
|
||||
<h1><i class="fa fa-circle-notch fa-spin"></i></h1>
|
||||
</div>
|
||||
|
||||
<div class="content" ng-show="ready">
|
||||
<a href="/#/email" class="back-to-view-link"><i class="fas fa-arrow-left"></i> {{ 'email.backAction' | tr }}</a>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="text-left">
|
||||
<h3>
|
||||
{{ 'email.config.title' | tr:{ domain: domain.domain } }}
|
||||
|
||||
<div class="dropdown pull-right" style="display: inline-block">
|
||||
<button class="btn btn-sm btn-default dropdown-toggle" type="button" data-toggle="dropdown" uib-tooltip="{{ 'app.docsActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom">
|
||||
<i class="fas fa-book"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li><a href="https://docs.cloudron.io/email/" target="_blank">{{ 'app.docsAction' | tr }}</a></li>
|
||||
<li ng-class="{ 'disabled': !domain.mailConfig.enabled }"><a href="" ng-click="domain.mailConfig.enabled ? howToConnectInfo.show() : null">{{ 'email.config.clientConfiguration' | tr }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<uib-tabset active="activeTab">
|
||||
<uib-tab index="'mailboxes'" select="setView('mailboxes')" heading="{{ 'email.incoming.tabTitle' | tr }}">
|
||||
<div class="card card-large" style="margin-bottom: 15px;">
|
||||
<h4>{{ 'email.incoming.title' | tr }}</h4>
|
||||
|
||||
<p ng-show="domain.mailConfig.enabled">{{ 'email.incoming.enabled' | tr }}</p>
|
||||
<p ng-hide="domain.mailConfig.enabled">{{ 'email.incoming.disabled' | tr }}</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button class="pull-right" ng-class="domain.mailConfig.enabled ? 'btn btn-danger' : 'btn btn-primary'" ng-click="incomingEmail.toggleEmailEnabled()" ng-disabled="incomingEmail.busy" ng-show="user.isAtLeastAdmin">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="incomingEmail.busy"></i>
|
||||
{{ domain.mailConfig.enabled ? ('email.incoming.disableAction' | tr) : ('email.incoming.enableAction' | tr) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="text-left">
|
||||
<h3 style="margin-bottom: 15px;">{{ 'email.incoming.mailboxes.title' | tr }}
|
||||
<button class="btn btn-primary btn-outline pull-right" ng-click="mailboxes.add.show()" ng-disabled="!domain.mailConfig.enabled" tooltip-enable="!domain.mailConfig.enabled" uib-tooltip="{{ 'email.incoming.mailboxes.disabledTooltip' | tr }}"><i class="fa fa-inbox"></i> {{ 'email.incoming.mailboxes.addAction' | tr }}</button>
|
||||
<input class="form-control pull-right" style="width: 200px;" placeholder="{{ 'main.searchPlaceholder' | tr }}" type="text" ng-model="mailboxes.search" ng-model-options="{ debounce: 1000 }" ng-change="mailboxes.updateFilter()" />
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="card card-large" style="margin-bottom: 15px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'email.incoming.mailboxes.name' | tr }}</th>
|
||||
<th>{{ 'email.incoming.mailboxes.owner' | tr }}</th>
|
||||
<th>{{ 'email.incoming.mailboxes.aliases' | tr }}</th>
|
||||
<th>{{ 'email.incoming.mailboxes.usage' | tr }}</th>
|
||||
<th class="text-right">{{ 'main.actions' | tr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="mailbox in mailboxes.mailboxes | filter:mailboxes.search" ng-class="{'text-muted': !mailbox.active}">
|
||||
<td class="hand" ng-click="mailboxes.edit.show(mailbox)">
|
||||
{{ mailbox.name }}
|
||||
</td>
|
||||
<td class="hand" ng-click="mailboxes.edit.show(mailbox)">
|
||||
{{ mailbox.ownerDisplayName }}
|
||||
</td>
|
||||
<td class="hand elide-table-cell" ng-click="mailboxes.edit.show(mailbox)">
|
||||
<span ng-repeat="alias in mailbox.aliases"> {{ alias.name + '@' + alias.domain }}</span>
|
||||
</td>
|
||||
<td class="hand no-wrap" ng-click="mailboxes.edit.show(mailbox)">
|
||||
<span ng-show="mailUsage !== null">
|
||||
{{ mailUsage[mailbox.fullName].quotaValue | prettyDecimalSize }} <span ng-show="mailUsage[mailbox.fullName].quotaLimit">/ {{ mailUsage[mailbox.fullName].quotaLimit | prettyDecimalSize }}</span>
|
||||
</span>
|
||||
<span ng-show="mailUsage === null">
|
||||
{{ 'main.loadingPlaceholder' | tr }} ...
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right no-wrap">
|
||||
<button class="btn btn-xs btn-default" ng-click="mailboxes.edit.show(mailbox)"><i class="fa fa-pencil-alt"></i></button>
|
||||
<button class="btn btn-xs btn-danger" ng-click="mailboxes.remove.show(mailbox)"><i class="far fa-trash-alt"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-default btn-outline btn-xs" ng-click="mailboxes.showPrevPage()" ng-class="{ 'btn-primary': mailboxes.currentPage > 1 }" ng-disabled="mailboxes.busy || mailboxes.currentPage <= 1"><i class="fa fa-angle-double-left"></i> {{ 'main.pagination.prev' | tr }}</button>
|
||||
<button class="btn btn-default btn-outline btn-xs" ng-click="mailboxes.showNextPage()" ng-class="{ 'btn-primary': mailboxes.perPage <= mailboxes.mailboxes.length }" ng-disabled="mailboxes.busy || mailboxes.perPage > mailboxes.mailboxes.length">{{ 'main.pagination.next' | tr }} <i class="fa fa-angle-double-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="text-left">
|
||||
<h3 style="margin-bottom: 15px;">{{ 'email.incoming.mailinglists.title' | tr }}
|
||||
<button class="btn btn-primary btn-outline pull-right" ng-click="mailinglists.add.show()" ng-disabled="!domain.mailConfig.enabled" tooltip-enable="!domain.mailConfig.enabled" uib-tooltip="{{ 'email.incoming.mailboxes.disabledTooltip' | tr }}"><i class="fa fa-list"></i> {{ 'email.incoming.mailboxes.addAction' | tr }}</button>
|
||||
<input class="form-control pull-right" style="width: 200px;" placeholder="{{ 'main.searchPlaceholder' | tr }}" type="text" ng-model="mailinglists.search" ng-model-options="{ debounce: 1000 }" ng-change="mailinglists.updateFilter()" />
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="card card-large" style="margin-bottom: 15px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{{ 'email.incoming.mailinglists.description' | tr }}
|
||||
<br/>
|
||||
<br/>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 0.5%;"></th>
|
||||
<th>{{ 'email.incoming.mailinglists.name' | tr }}</th>
|
||||
<th>{{ 'email.incoming.mailinglists.members' | tr }}</th>
|
||||
<th class="text-right">{{ 'main.actions' | tr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="list in mailinglists.mailinglists | filter:mailinglists.search | orderBy:'name'" ng-class="{'text-muted': !list.active}">
|
||||
<td>
|
||||
<i class="fas fa-door-closed" ng-show="list.membersOnly" uib-tooltip="{{ 'email.incoming.mailinglists.membersOnlyTooltip' | tr }}"></i>
|
||||
<i class="fas fa-door-open" ng-show="!list.membersOnly" uib-tooltip="{{ 'email.incoming.mailinglists.everyoneTooltip' | tr }}"></i>
|
||||
</td>
|
||||
<td class="hand" ng-click="mailinglists.edit.show(list)">
|
||||
{{ list.name }}
|
||||
</td>
|
||||
<td class="hand" ng-click="mailinglists.edit.show(list)">
|
||||
{{ list.members.join(', ') }}
|
||||
</td>
|
||||
<td class="text-right no-wrap">
|
||||
<button class="btn btn-xs btn-default" ng-click="mailinglists.edit.show(list)"><i class="fa fa-pencil-alt"></i></button>
|
||||
<button class="btn btn-xs btn-danger" ng-click="mailinglists.remove.show(list)"><i class="far fa-trash-alt"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-default btn-outline btn-xs" ng-click="mailinglists.showPrevPage()" ng-class="{ 'btn-primary': mailinglists.currentPage > 1 }" ng-disabled="mailinglists.busy || mailinglists.currentPage <= 1"><i class="fa fa-angle-double-left"></i> {{ 'main.pagination.prev' | tr }}</button>
|
||||
<button class="btn btn-default btn-outline btn-xs" ng-click="mailinglists.showNextPage()" ng-class="{ 'btn-primary': mailinglists.perPage <= mailinglists.mailinglists.length }" ng-disabled="mailinglists.busy || mailinglists.perPage > mailinglists.mailinglists.length">{{ 'main.pagination.next' | tr }} <i class="fa fa-angle-double-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="text-left">
|
||||
<h3>{{ 'email.incoming.catchall.title' | tr }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="card card-large" style="margin-bottom: 15px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12" ng-bind-html=" 'email.incoming.catchall.description' | tr "></div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<multiselect ng-model="catchall.mailboxes" options="mailbox.display for mailbox in catchall.availableMailboxes" data-compare-by="display" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
<button class="btn btn-outline btn-primary" ng-click="catchall.submit()" ng-disabled="catchall.busy || !domain.mailConfig.enabled" tooltip-enable="!domain.mailConfig.enabled" uib-tooltip="{{ 'email.incoming.mailboxes.disabledTooltip' | tr }}">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="catchall.busy"></i> {{ 'email.incoming.catchall.saveAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab index="'outbound'" ng-if="user.isAtLeastAdmin" select="setView('outbound')" heading="{{ 'email.outbound.tabTitle' | tr }}">
|
||||
<div class="card card-large" style="margin-bottom: 15px;">
|
||||
<h4>{{ 'email.outbound.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/email/#relay-outbound-mails" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12" ng-bind-html=" 'email.outbound.description' | tr "></div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<select class="form-control" style="width: 50%;" ng-model="mailRelay.preset" ng-options="a.name for a in mailRelayPresets track by a.provider" ng-change="mailRelay.presetChanged()"></select>
|
||||
</div>
|
||||
|
||||
<p class="small text-danger" ng-show="mailRelay.preset.provider === 'noop'">
|
||||
<span ng-if="domain.domain === config.adminDomain">{{ 'email.outbound.noopAdminDomainWarning' | tr }}</span>
|
||||
<span ng-if="domain.domain !== config.adminDomain">{{ 'email.outbound.noopNonAdminDomainWarning' | tr }}</span>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="usesExternalServer(mailRelay.preset.provider)">
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<form name="mailRelayForm" role="form" ng-submit="mailRelay.submit()" autocomplete="off" novalidate>
|
||||
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.host.$dirty && mailRelayForm.host.$invalid) }">
|
||||
<label class="control-label">{{ 'email.outbound.mailRelay.host' | tr }}</label>
|
||||
<div class="control-label" ng-show="(!mailRelayForm.host.$dirty && mailRelay.error.host) || (mailRelayForm.host.$dirty && mailRelayForm.host.$invalid)">
|
||||
<small ng-show="!mailRelayForm.host.$dirty && mailRelay.error.host">{{ mailRelay.error.host }}</small>
|
||||
</div>
|
||||
<input type="text" class="form-control" ng-model="mailRelay.relay.host" name="host" required>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.port.$dirty && mailRelayForm.port.$invalid) }">
|
||||
<label class="control-label">{{ 'email.outbound.mailRelay.port' | tr }}</label>
|
||||
<div class="control-label" ng-show="(!mailRelayForm.port.$dirty && mailRelay.error.port) || (mailRelayForm.port.$dirty && mailRelayForm.port.$invalid)">
|
||||
<small ng-show="!mailRelayForm.port.$dirty && mailRelay.error.port">{{ mailRelay.error.port }}</small>
|
||||
</div>
|
||||
<input type="number" class="form-control" ng-model="mailRelay.relay.port" name="port" required>
|
||||
</div>
|
||||
|
||||
<div class="checkbox" ng-show="mailRelay.relay.provider === 'external-smtp' || mailRelay.relay.provider === 'external-smtp-noauth'" >
|
||||
<label>
|
||||
<input type="checkbox" ng-model="mailRelay.relay.acceptSelfSignedCerts">{{ 'email.outbound.mailRelay.selfsignedCheckbox' | tr }}</input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Postmark, Sendgrid, SparkPost -->
|
||||
<div ng-show="usesTokenAuth(mailRelay.relay.provider)" class="form-group" ng-class="{ 'has-error': (mailRelayForm.serverApiToken.$dirty && mailRelayForm.serverApiToken.$invalid) }">
|
||||
<label class="control-label">{{ 'email.outbound.mailRelay.apiTokenOrKey' | tr }}</label>
|
||||
<div class="control-label" ng-show="(!mailRelayForm.serverApiToken.$dirty && mailRelay.error.serverApiToken) || (mailRelayForm.serverApiToken.$dirty && mailRelayForm.serverApiToken.$invalid)">
|
||||
<small ng-show="!mailRelayForm.serverApiToken.$dirty && mailRelay.error.serverApiToken">{{ mailRelay.error.serverApiToken }}</small>
|
||||
</div>
|
||||
<input type="text" class="form-control" ng-model="mailRelay.relay.serverApiToken" name="serverApiToken" ng-required="usesTokenAuth(mailRelay.relay.provider)">
|
||||
</div>
|
||||
|
||||
<!-- Other -->
|
||||
<div ng-show="usesPasswordAuth(mailRelay.relay.provider)" class="form-group" ng-class="{ 'has-error': (mailRelayForm.username.$dirty && mailRelayForm.username.$invalid) }">
|
||||
<label class="control-label">{{ 'email.outbound.mailRelay.username' | tr }}</label>
|
||||
<div class="control-label" ng-show="(!mailRelayForm.username.$dirty && mailRelay.error.username) || (mailRelayForm.username.$dirty && mailRelayForm.username.$invalid)">
|
||||
<small ng-show="!mailRelayForm.username.$dirty && mailRelay.error.username">{{ mailRelay.error.username }}</small>
|
||||
</div>
|
||||
<input type="text" class="form-control" ng-model="mailRelay.relay.username" name="username" ng-required="usesPasswordAuth(mailRelay.relay.provider)">
|
||||
</div>
|
||||
|
||||
<div ng-show="usesPasswordAuth(mailRelay.relay.provider)" class="form-group" ng-class="{ 'has-error': (mailRelayForm.password.$dirty && mailRelayForm.password.$invalid) }">
|
||||
<label class="control-label">{{ 'email.outbound.mailRelay.password' | tr }}</label>
|
||||
<div class="control-label" ng-show="(!mailRelayForm.password.$dirty && mailRelay.error.password) || (mailRelayForm.password.$dirty && mailRelayForm.password.$invalid)">
|
||||
<small ng-show="!mailRelayForm.password.$dirty && mailRelay.error.password">{{ mailRelay.error.password }}</small>
|
||||
</div>
|
||||
<input type="password" class="form-control" ng-model="mailRelay.relay.password" name="password" ng-required="usesPasswordAuth(mailRelay.relay.provider)" password-reveal>
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="mailRelayForm.$invalid"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button class="btn btn-primary pull-right" ng-click="mailRelay.submit()" ng-disabled="(usesExternalServer(mailRelay.preset.provider) && (!mailRelayForm.$dirty || mailRelayForm.$invalid)) || mailRelay.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailRelay.busy"></i> {{ 'email.outbound.mailRelay.saveAction' | tr }}</button>
|
||||
|
||||
<span class="has-error text-center" ng-show="mailRelay.error">{{ mailRelay.error }}</span>
|
||||
<span class="text-success text-center text-bold" ng-show="mailRelay.success">{{ 'email.outbound.mailRelay.saveSuccess' | tr }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="mailRelay.preset.spfDoc">
|
||||
<br/>
|
||||
<div class="col-md-12">
|
||||
<span class="text-info" ng-bind-html="'email.outbound.mailRelay.spfDocInfo' | tr:{ name: mailRelay.preset.name, spfDocsLink: mailRelay.preset.spfDoc }"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab index="'settings'" select="setView('settings')" heading="{{ 'email.settings.tabTitle' | tr }}">
|
||||
<div class="card card-large" style="margin-bottom: 15px;">
|
||||
<h4>{{ 'email.masquerading.title' | tr }}</h4>
|
||||
<p ng-bind-html=" 'email.masquerading.description' | tr "></p>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="pull-right" ng-class="domain.mailConfig.mailFromValidation ? 'btn btn-danger' : 'btn btn-primary'" ng-disabled="mailFromValidation.busy" ng-click="mailFromValidation.submit()">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="mailFromValidation.busy"></i> {{ domain.mailConfig.mailFromValidation ? ('email.masquerading.enableAction' | tr) : ('email.masquerading.disableAction' | tr) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-large">
|
||||
<h4>{{ 'email.signature.title' | tr }}</h4>
|
||||
<p ng-bind-html=" 'email.signature.description' | tr "></p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form role="form" name="bannerForm" ng-submit="banner.submit()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="control-label" style="width: 100%">{{ 'email.signature.plainTextFormat' | tr }}</label>
|
||||
<textarea ng-model="banner.text" class="form-control" rows="4"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" style="width: 100%">{{ 'email.signature.htmlFormat' | tr }}</label>
|
||||
<textarea ng-model="banner.html" class="form-control" rows="4"></textarea>
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="banner.$invalid || banner.busy"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="banner.submit()" ng-disabled="banner.$invalid || banner.busy">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="banner.busy"></i> {{ 'email.signature.saveAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab index="'status'" ng-if="user.isAtLeastAdmin" select="setView('status')" heading="{{ 'email.status.tabTitle' | tr }}">
|
||||
<!-- nothing to show if incoming mail is disabled and using a relay -->
|
||||
<div class="card card-large" style="margin-bottom: 15px;" ng-hide="!domain.mailConfig.enabled && domain.mailConfig.relay.provider !== 'cloudron-smtp'">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>{{ 'email.dnsStatus.title' | tr }}
|
||||
<button class="btn btn-xs btn-primary btn-outline pull-right" ng-click="incomingEmail.setDnsRecords()">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="incomingEmail.setupDnsBusy"></i> {{ 'email.dnsStatus.reSetupAction' | tr }}
|
||||
</button>
|
||||
</h4>
|
||||
<span ng-bind-html="'email.dnsStatus.description' | tr:{ emailDnsDocsLink:'https://docs.cloudron.io/email/#dns-records'}"></span>
|
||||
<br/>
|
||||
<br/>
|
||||
<div ng-repeat="record in expectedDnsRecordsTypes">
|
||||
<div class="row" ng-if="expectedDnsRecords[record.value].expected">
|
||||
<div class="col-xs-12">
|
||||
<p class="text-muted">
|
||||
<i ng-hide="refreshBusy" ng-class="expectedDnsRecords[record.value].status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i>
|
||||
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_dns_{{ record.value }}">{{ record.name }} record</a>
|
||||
<button class="btn btn-xs btn-default" ng-click="refreshStatus()" ng-disabled="refreshBusy" ng-show="!expectedDnsRecords[record.value].status"><i class="fa fa-sync-alt" ng-class="{ 'fa-pulse': refreshBusy }"></i></button>
|
||||
</p>
|
||||
<div id="collapse_dns_{{ record.value }}" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<p ng-show="record.name === 'MX' && domain.provider === 'namecheap'">{{ 'email.dnsStatus.namecheapInfo' | tr }} <sup><a ng-href="https://docs.cloudron.io/domains/#namecheap-dns" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></p>
|
||||
<p ng-show="record.name === 'PTR4' || record.name === 'PTR6'">{{ 'email.dnsStatus.ptrInfo' | tr }} <sup><a ng-href="https://docs.cloudron.io/email/#ptr-record" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></p>
|
||||
<p ng-show="expectedDnsRecords[record.value].name">{{ 'email.dnsStatus.hostname' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].name }}</tt></b></p>
|
||||
<p ng-hide="expectedDnsRecords[record.value].name">{{ 'email.dnsStatus.domain' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].domain }}</tt></b></p>
|
||||
<p>{{ 'email.dnsStatus.type' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].type }}</tt></b></p>
|
||||
<p style="overflow: auto; white-space: nowrap;">{{ 'email.dnsStatus.expected' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].expected }}</tt></b></p>
|
||||
<p style="overflow: auto; white-space: nowrap;">{{ 'email.dnsStatus.current' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].value ? expectedDnsRecords[record.value].value : ('['+('email.dnsStatus.recordNotSet' | tr)+']') }}</tt></b></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-large" style="margin-bottom: 15px;" ng-if="domain.mailConfig.relay.provider !== 'noop'">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>{{ 'email.smtpStatus.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/email/#smtp-status" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<p class="text-muted">
|
||||
<i ng-hide="refreshBusy" ng-class="domain.mailStatus.relay.status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i>
|
||||
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_outbound_smtp">
|
||||
{{ domain.mailConfig.relay.provider === 'cloudron-smtp' ? ('email.smtpStatus.outboudDirect' | tr) : ('email.smtpStatus.outboudRelay' | tr) }}
|
||||
</a>
|
||||
<button class="btn btn-xs btn-default" ng-click="refreshStatus()" ng-disabled="refreshBusy" ng-show="!domain.mailStatus.relay.status"><i class="fa fa-sync-alt" ng-class="{ 'fa-pulse': refreshBusy }"></i></button>
|
||||
</p>
|
||||
<div id="collapse_outbound_smtp" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<p><b> {{ domain.mailStatus.relay.value }} </b> </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="domain.mailConfig.relay.provider === 'cloudron-smtp'">
|
||||
<div class="col-xs-12">
|
||||
<p class="text-muted">
|
||||
<i ng-hide="refreshBusy" ng-class="domain.mailStatus.rbl.status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i>
|
||||
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_rbl">{{ 'email.smtpStatus.blacklistCheck' | tr }}</a>
|
||||
<button class="btn btn-xs btn-default" ng-click="refreshStatus()" ng-disabled="refreshBusy" ng-show="!domain.mailStatus.rbl.status"><i class="fa fa-sync-alt" ng-class="{ 'fa-pulse': refreshBusy }"></i></button>
|
||||
</p>
|
||||
<div id="collapse_rbl" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<div ng-show="domain.mailStatus.rbl.servers.length" ng-bind-html="'email.smtpStatus.blacklisted' | tr:{ ip: domain.mailStatus.rbl.ip }"></div>
|
||||
<div ng-hide="domain.mailStatus.rbl.servers.length" ng-bind-html="'email.smtpStatus.notBlacklisted' | tr:{ ip: domain.mailStatus.rbl.ip }"></div>
|
||||
<div ng-repeat="server in domain.mailStatus.rbl.servers">
|
||||
<a ng-href="{{server.site}}" target="_blank">{{ server.name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
@@ -1,898 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* global angular */
|
||||
/* global $ */
|
||||
/* global async */
|
||||
|
||||
angular.module('Application').controller('EmailController', ['$scope', '$location', '$translate', '$timeout', '$route', '$routeParams', 'Client', function ($scope, $location, $translate, $timeout, $route, $routeParams, Client) {
|
||||
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastMailManager) $location.path('/'); });
|
||||
|
||||
$scope.user = Client.getUserInfo();
|
||||
|
||||
// Avoid full reload on path change
|
||||
// https://stackoverflow.com/a/22614334
|
||||
// reloadOnUrl: false in $routeProvider did not work!
|
||||
var lastRoute = $route.current;
|
||||
$scope.$on('$locationChangeSuccess', function (/* event */) {
|
||||
if (lastRoute.$$route.originalPath === $route.current.$$route.originalPath) {
|
||||
$route.current = lastRoute;
|
||||
}
|
||||
});
|
||||
|
||||
var domainName = $routeParams.domain;
|
||||
if (!domainName) return $location.path('/email');
|
||||
|
||||
$scope.setView = function (view, setAlways) {
|
||||
if (!setAlways && !$scope.ready) return;
|
||||
if ($scope.view === view) return;
|
||||
|
||||
$route.updateParams({ view: view });
|
||||
$scope.view = view;
|
||||
$scope.activeTab = view;
|
||||
};
|
||||
|
||||
$scope.ready = false;
|
||||
$scope.refreshBusy = true;
|
||||
$scope.client = Client;
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.config = Client.getConfig();
|
||||
$scope.apps = Client.getInstalledApps();
|
||||
$scope.owners = []; // users + groups
|
||||
$scope.incomingDomains = [];
|
||||
$scope.domain = null;
|
||||
$scope.adminDomain = null;
|
||||
$scope.mailUsage = null;
|
||||
$scope.storageQuotaTicks = [ 500*1000*1000, 5*1000*1000*1000, 15*1000*1000*1000, 50*1000*1000*1000, 100*1000*1000*1000 ];
|
||||
|
||||
$scope.expectedDnsRecords = {
|
||||
mx: {},
|
||||
dkim: {},
|
||||
spf: {},
|
||||
dmarc: {},
|
||||
ptr4: {},
|
||||
ptr6: {}
|
||||
};
|
||||
|
||||
$scope.expectedDnsRecordsTypes = [
|
||||
{ name: 'MX', value: 'mx' },
|
||||
{ name: 'DKIM', value: 'dkim' },
|
||||
{ name: 'SPF', value: 'spf' },
|
||||
{ name: 'DMARC', value: 'dmarc' },
|
||||
{ name: 'PTR4', value: 'ptr4' },
|
||||
{ name: 'PTR6', value: 'ptr6' }
|
||||
|
||||
];
|
||||
|
||||
$scope.openSubscriptionSetup = function () {
|
||||
Client.openSubscriptionSetup($scope.$parent.subscription);
|
||||
};
|
||||
|
||||
function updateMailUsage(mailboxName, quotaLimit) {
|
||||
if (!$scope.mailUsage) $scope.mailUsage = {};
|
||||
if (!$scope.mailUsage[mailboxName]) $scope.mailUsage[mailboxName] = {};
|
||||
$scope.mailUsage[mailboxName].quotaLimit = quotaLimit;
|
||||
}
|
||||
|
||||
function refreshMailUsage() {
|
||||
Client.getMailUsage($scope.domain.domain, function (error, usage) {
|
||||
if (error) console.error(error);
|
||||
|
||||
$scope.mailUsage = usage || null; // if mail server is down, don't stop the listing
|
||||
});
|
||||
}
|
||||
|
||||
$scope.catchall = {
|
||||
mailboxes: [],
|
||||
availableMailboxes: [],
|
||||
busy: false,
|
||||
|
||||
submit: function () {
|
||||
$scope.catchall.busy = true;
|
||||
|
||||
var addresses = $scope.catchall.mailboxes.map(function (m) { return m.name + '@' + m.domain; });
|
||||
|
||||
Client.setCatchallAddresses($scope.domain.domain, addresses, function (error) {
|
||||
if (error) console.error('Unable to add catchall address.', error);
|
||||
|
||||
$timeout(function () { $scope.catchall.busy = false; }, 2000); // otherwise, it's too fast
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function () {
|
||||
var allMailboxes = [];
|
||||
async.eachSeries($scope.incomingDomains, function (domain, iteratorDone) {
|
||||
|
||||
Client.listMailboxes(domain.domain, '', 1, 1000, function (error, result) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
result.forEach(function (r) { r.display = r.name + '@' + r.domain; });
|
||||
|
||||
allMailboxes = allMailboxes.concat(result);
|
||||
iteratorDone();
|
||||
});
|
||||
}, function () {
|
||||
$scope.catchall.availableMailboxes = allMailboxes;
|
||||
|
||||
$scope.catchall.mailboxes = $scope.domain.mailConfig.catchAll.map(function (address) {
|
||||
return allMailboxes.find(function (m) { return m.display === address; });
|
||||
}).filter(function (m) { return !!m; }); // remove not found addresses
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.mailinglists = {
|
||||
busy: false,
|
||||
mailinglists: [],
|
||||
search: '',
|
||||
currentPage: 1,
|
||||
perPage: 10,
|
||||
|
||||
add: {
|
||||
busy: false,
|
||||
error: {},
|
||||
name: '',
|
||||
membersTxt: '',
|
||||
membersOnly: false,
|
||||
|
||||
reset: function () {
|
||||
$scope.mailinglists.add.busy = false;
|
||||
$scope.mailinglists.add.error = {};
|
||||
$scope.mailinglists.add.name = '';
|
||||
$scope.mailinglists.add.membersTxt = '';
|
||||
},
|
||||
|
||||
show: function () {
|
||||
$scope.mailinglists.add.reset();
|
||||
$('#mailinglistAddModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.mailinglists.add.busy = true;
|
||||
|
||||
var members = $scope.mailinglists.add.membersTxt
|
||||
.split(/[\n,]/)
|
||||
.map(function (m) { return m.trim(); })
|
||||
.filter(function (m) { return m.length !== 0; });
|
||||
|
||||
Client.addMailingList($scope.domain.domain, $scope.mailinglists.add.name, members, $scope.mailinglists.add.membersOnly, function (error) {
|
||||
$scope.mailinglists.add.busy = false;
|
||||
$scope.mailinglists.add.error = {};
|
||||
|
||||
if (error) {
|
||||
if (error.statusCode === 400 && error.message.indexOf('member') !== -1) {
|
||||
$scope.mailinglists.add.error.members = error.message;
|
||||
} else {
|
||||
$scope.mailinglists.add.error.name = error.message;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.mailinglists.add.reset();
|
||||
$scope.mailinglists.refresh();
|
||||
|
||||
$('#mailinglistAddModal').modal('hide');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
edit: {
|
||||
busy: false,
|
||||
error: {},
|
||||
name: '',
|
||||
membersTxt: '',
|
||||
membersOnly: false,
|
||||
active: true,
|
||||
|
||||
show: function (list) {
|
||||
$scope.mailinglists.edit.name = list.name;
|
||||
$scope.mailinglists.edit.membersTxt = list.members.sort().join('\n');
|
||||
$scope.mailinglists.edit.membersOnly = list.membersOnly;
|
||||
$scope.mailinglists.edit.active = list.active;
|
||||
|
||||
$('#mailinglistEditModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.mailinglists.edit.busy = true;
|
||||
|
||||
var members = $scope.mailinglists.edit.membersTxt.split(/[\n,]/)
|
||||
.map(function (m) { return m.trim(); })
|
||||
.filter(function (m) { return m.length !== 0; });
|
||||
|
||||
Client.updateMailingList($scope.domain.domain, $scope.mailinglists.edit.name, members, $scope.mailinglists.edit.membersOnly, $scope.mailinglists.edit.active, function (error) {
|
||||
$scope.mailinglists.edit.busy = false;
|
||||
$scope.mailinglists.edit.error = {};
|
||||
|
||||
if (error) {
|
||||
$scope.mailinglists.edit.error.members = error.message;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.mailinglists.refresh();
|
||||
|
||||
$('#mailinglistEditModal').modal('hide');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
remove: {
|
||||
busy: false,
|
||||
list: null,
|
||||
|
||||
show: function (list) {
|
||||
$scope.mailinglists.remove.list = list;
|
||||
|
||||
$('#mailinglistRemoveModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.mailinglists.remove.busy = true;
|
||||
|
||||
Client.removeMailingList($scope.domain.domain, $scope.mailinglists.remove.list.name, function (error) {
|
||||
$scope.mailinglists.remove.busy = false;
|
||||
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.mailinglists.remove.list = null;
|
||||
$scope.mailinglists.refresh();
|
||||
|
||||
$('#mailinglistRemoveModal').modal('hide');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function (callback) {
|
||||
callback = typeof callback === 'function' ? callback : function (error) { if (error) return console.error(error); };
|
||||
|
||||
Client.listMailingLists($scope.domain.domain, $scope.mailinglists.search, $scope.mailinglists.currentPage, $scope.mailinglists.perPage, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
$scope.mailinglists.mailinglists = result;
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
|
||||
showNextPage: function () {
|
||||
$scope.mailinglists.currentPage++;
|
||||
$scope.mailinglists.refresh();
|
||||
},
|
||||
|
||||
showPrevPage: function () {
|
||||
if ($scope.mailinglists.currentPage > 1) $scope.mailinglists.currentPage--;
|
||||
else $scope.mailinglists.currentPage = 1;
|
||||
$scope.mailinglists.refresh();
|
||||
},
|
||||
|
||||
updateFilter: function (fresh) {
|
||||
if (fresh) $scope.mailinglists.currentPage = 1;
|
||||
$scope.mailinglists.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.mailFromValidation = {
|
||||
busy: false,
|
||||
|
||||
submit: function () {
|
||||
$scope.mailFromValidation.busy = true;
|
||||
|
||||
Client.setMailFromValidation($scope.domain.domain, !$scope.domain.mailConfig.mailFromValidation, function (error) {
|
||||
if (error) {
|
||||
$scope.mailFromValidation.busy = false;
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
// give sometime for the mail container to restart
|
||||
$timeout(function () {
|
||||
$scope.mailFromValidation.busy = false;
|
||||
$scope.refreshDomain();
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.banner = {
|
||||
busy: false,
|
||||
text: '',
|
||||
html: '',
|
||||
|
||||
submit: function () {
|
||||
$scope.banner.busy = true;
|
||||
|
||||
Client.setMailBanner($scope.domain.domain, { text: $scope.banner.text, html: $scope.banner.html }, function (error) {
|
||||
if (error) {
|
||||
$scope.banner.busy = false;
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
// give sometime for the mail container to restart
|
||||
$timeout(function () {
|
||||
$scope.banner.busy = false;
|
||||
$scope.refreshDomain();
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.incomingEmail = {
|
||||
busy: false,
|
||||
setupDns: true,
|
||||
setupDnsBusy: false,
|
||||
|
||||
toggleEmailEnabled: function () {
|
||||
if ($scope.domain.mailConfig.enabled) {
|
||||
$('#disableEmailModal').modal('show');
|
||||
} else {
|
||||
$('#enableEmailModal').modal('show');
|
||||
}
|
||||
},
|
||||
|
||||
setDnsRecords: function (callback) {
|
||||
$scope.incomingEmail.setupDnsBusy = true;
|
||||
|
||||
Client.setDnsRecords({ domain: $scope.domain.domain, type: 'mail' }, function (error) {
|
||||
if (error) console.error(error);
|
||||
|
||||
$timeout(function () { $scope.incomingEmail.setupDnsBusy = false; }, 2000); // otherwise, it's too fast
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
},
|
||||
|
||||
enable: function () {
|
||||
$('#enableEmailModal').modal('hide');
|
||||
|
||||
$scope.incomingEmail.busy = true;
|
||||
|
||||
Client.enableMailForDomain($scope.domain.domain, true , function (error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.reconfigureEmailApps();
|
||||
|
||||
let maybeSetupDns = $scope.incomingEmail.setupDns ? $scope.incomingEmail.setDnsRecords : function (next) { next(); };
|
||||
|
||||
maybeSetupDns(function (error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
$timeout(function () {
|
||||
$scope.refreshDomain();
|
||||
$scope.incomingEmail.busy = false;
|
||||
}, 5000); // wait for mail container to restart. it cannot get IP otherwise while refreshing
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
$('#disableEmailModal').modal('hide');
|
||||
|
||||
$scope.incomingEmail.busy = true;
|
||||
|
||||
Client.enableMailForDomain($scope.domain.domain, false , function (error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.reconfigureEmailApps();
|
||||
|
||||
$timeout(function () {
|
||||
$scope.refreshDomain();
|
||||
$scope.incomingEmail.busy = false;
|
||||
}, 5000); // wait for mail container to restart. it cannot get IP otherwise while refreshing
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.mailboxes = {
|
||||
mailboxes: [],
|
||||
search: '',
|
||||
currentPage: 1,
|
||||
perPage: 10,
|
||||
|
||||
add: {
|
||||
error: null,
|
||||
busy: false,
|
||||
name: '',
|
||||
owner: null,
|
||||
|
||||
reset: function () {
|
||||
$scope.mailboxes.add.busy = false;
|
||||
$scope.mailboxes.add.error = null;
|
||||
$scope.mailboxes.add.name = '';
|
||||
$scope.mailboxes.add.owner = null;
|
||||
},
|
||||
|
||||
show: function () {
|
||||
$scope.mailboxes.add.reset();
|
||||
$('#mailboxAddModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.mailboxes.add.busy = true;
|
||||
|
||||
Client.addMailbox($scope.domain.domain, $scope.mailboxes.add.name, $scope.mailboxes.add.owner.id, $scope.mailboxes.add.owner.type, function (error) {
|
||||
if (error) {
|
||||
$scope.mailboxes.add.busy = false;
|
||||
$scope.mailboxes.add.error = error;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.mailboxes.refresh();
|
||||
$scope.catchall.refresh();
|
||||
|
||||
$('#mailboxAddModal').modal('hide');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
edit: {
|
||||
busy: false,
|
||||
error: null,
|
||||
name: '',
|
||||
owner: null,
|
||||
aliases: [],
|
||||
aliasesOriginal: [],
|
||||
active: true,
|
||||
enablePop3: false,
|
||||
storageQuota: 0,
|
||||
storageQuotaEnabled: false,
|
||||
|
||||
addAlias: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
$scope.mailboxes.edit.aliases.push({
|
||||
name: '',
|
||||
domain: domainName,
|
||||
reversedSortingNotation: 'z'.repeat(100) // quick and dirty to ensure newly added are on bottom
|
||||
});
|
||||
},
|
||||
|
||||
delAlias: function (event, alias) {
|
||||
event.preventDefault();
|
||||
|
||||
var index = $scope.mailboxes.edit.aliases.findIndex(function (a) { return (a.name+a.domain) === (alias.name+alias.domain); });
|
||||
if (index === -1) return;
|
||||
|
||||
$scope.mailboxes.edit.aliases.splice(index, 1);
|
||||
},
|
||||
|
||||
show: function (mailbox) {
|
||||
$scope.mailboxes.edit.name = mailbox.name;
|
||||
$scope.mailboxes.edit.owner = mailbox.owner; // this can be null if mailbox had no owner
|
||||
$scope.mailboxes.edit.aliases = angular.copy(mailbox.aliases, []).map(function (a) { a.reversedSortingNotation = a.domain + '@' + a.name; return a; });
|
||||
$scope.mailboxes.edit.active = mailbox.active;
|
||||
$scope.mailboxes.edit.enablePop3 = mailbox.enablePop3;
|
||||
$scope.mailboxes.edit.storageQuotaEnabled = !!mailbox.storageQuota;
|
||||
$scope.mailboxes.edit.storageQuota = mailbox.storageQuota || $scope.storageQuotaTicks[0];
|
||||
|
||||
// make a copy for later comparison
|
||||
angular.copy($scope.mailboxes.edit.aliases, $scope.mailboxes.edit.aliasesOriginal);
|
||||
|
||||
$('#mailboxEditModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.mailboxes.edit.busy = true;
|
||||
|
||||
var data = {
|
||||
ownerId: $scope.mailboxes.edit.owner.id,
|
||||
ownerType: $scope.mailboxes.edit.owner.type,
|
||||
active: $scope.mailboxes.edit.active,
|
||||
enablePop3: $scope.mailboxes.edit.enablePop3,
|
||||
storageQuota: $scope.mailboxes.edit.storageQuotaEnabled ? parseInt($scope.mailboxes.edit.storageQuota) : 0,
|
||||
messagesQuota: 0
|
||||
};
|
||||
|
||||
// $scope.mailboxes.edit.owner is expected to be validated by the UI
|
||||
Client.updateMailbox($scope.domain.domain, $scope.mailboxes.edit.name, data, function (error) {
|
||||
if (error) {
|
||||
$scope.mailboxes.edit.error = error;
|
||||
$scope.mailboxes.edit.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
function done() {
|
||||
updateMailUsage($scope.mailboxes.edit.name + '@' + $scope.domain.domain, $scope.mailboxes.edit.storageQuotaEnabled ? $scope.mailboxes.edit.storageQuota : 0); // hack to avoid refresh
|
||||
|
||||
$scope.mailboxes.edit.busy = false;
|
||||
$scope.mailboxes.edit.error = null;
|
||||
$scope.mailboxes.edit.name = '';
|
||||
$scope.mailboxes.edit.owner = null;
|
||||
$scope.mailboxes.edit.aliases = [];
|
||||
$scope.mailboxes.edit.storageQuota = 0;
|
||||
$scope.mailboxes.edit.storageQuotaEnabled = false;
|
||||
$scope.mailboxes.refresh();
|
||||
|
||||
$('#mailboxEditModal').modal('hide');
|
||||
}
|
||||
|
||||
// skip if nothing has changed
|
||||
if (angular.equals($scope.mailboxes.edit.aliases, $scope.mailboxes.edit.aliasesOriginal)) return done();
|
||||
|
||||
Client.setAliases($scope.mailboxes.edit.name, $scope.domain.domain, $scope.mailboxes.edit.aliases, function (error) {
|
||||
if (error) {
|
||||
$scope.mailboxes.edit.error = error;
|
||||
$scope.mailboxes.edit.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
remove: {
|
||||
busy: false,
|
||||
mailbox: null,
|
||||
deleteMails: true,
|
||||
|
||||
show: function (mailbox) {
|
||||
$scope.mailboxes.remove.mailbox = mailbox;
|
||||
|
||||
$('#mailboxRemoveModal').modal('show');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.mailboxes.remove.busy = true;
|
||||
|
||||
Client.removeMailbox($scope.domain.domain, $scope.mailboxes.remove.mailbox.name, $scope.mailboxes.remove.deleteMails, function (error) {
|
||||
$scope.mailboxes.remove.busy = false;
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.mailboxes.remove.mailbox = null;
|
||||
$scope.mailboxes.refresh(function (error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.catchall.refresh();
|
||||
|
||||
$('#mailboxRemoveModal').modal('hide');
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function (callback) {
|
||||
callback = typeof callback === 'function' ? callback : function (error) { if (error) return console.error(error); };
|
||||
|
||||
Client.listMailboxes($scope.domain.domain, $scope.mailboxes.search, $scope.mailboxes.currentPage, $scope.mailboxes.perPage, function (error, mailboxes) {
|
||||
if (error) return callback(error);
|
||||
|
||||
mailboxes.forEach(function (m) {
|
||||
m.fullName = m.name + '@' + m.domain; // to make it simple for the ui
|
||||
m.owner = $scope.owners.find(function (o) { return o.id === m.ownerId; }); // owner may not exist
|
||||
m.ownerDisplayName = m.owner ? m.owner.display : ''; // this meta property is set when we get the user list
|
||||
});
|
||||
|
||||
$scope.mailboxes.mailboxes = mailboxes;
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
|
||||
showNextPage: function () {
|
||||
$scope.mailboxes.currentPage++;
|
||||
$scope.mailboxes.refresh();
|
||||
},
|
||||
|
||||
showPrevPage: function () {
|
||||
if ($scope.mailboxes.currentPage > 1) $scope.mailboxes.currentPage--;
|
||||
else $scope.mailboxes.currentPage = 1;
|
||||
$scope.mailboxes.refresh();
|
||||
},
|
||||
|
||||
updateFilter: function (fresh) {
|
||||
if (fresh) $scope.mailboxes.currentPage = 1;
|
||||
$scope.mailboxes.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.mailRelayPresets = [
|
||||
{ provider: 'cloudron-smtp', name: 'Built-in SMTP server' },
|
||||
{ provider: 'external-smtp', name: 'External SMTP server', host: '', port: 587 },
|
||||
{ provider: 'external-smtp-noauth', name: 'External SMTP server (No Authentication)', host: '', port: 587 },
|
||||
{ provider: 'ses-smtp', name: 'Amazon SES', host: 'email-smtp.us-east-1.amazonaws.com', port: 587, spfDoc: 'https://docs.aws.amazon.com/ses/latest/DeveloperGuide/spf.html' },
|
||||
{ provider: 'elasticemail-smtp', name: 'Elastic Email', host: 'smtp.elasticemail.com', port: 587, spfDoc: 'https://elasticemail.com/blog/marketing_tips/common-spf-errors' },
|
||||
{ provider: 'google-smtp', name: 'Google', host: 'smtp.gmail.com', port: 587, spfDoc: 'https://support.google.com/a/answer/33786?hl=en' },
|
||||
{ provider: 'mailgun-smtp', name: 'Mailgun', host: 'smtp.mailgun.org', port: 587, spfDoc: 'https://www.mailgun.com/blog/white-labeling-dns-records-your-customers-tips-tricks' },
|
||||
{ provider: 'mailjet-smtp', name: 'Mailjet', host: '', port: 587, spfDoc: 'https://app.mailjet.com/docs/spf-dkim-guide' },
|
||||
{ provider: 'office365-legacy-smtp', name: 'Office 365', host: 'smtp-legacy.office365.com', port: 587 }, // uses "login" AUTH instead of "plain"
|
||||
{ provider: 'postmark-smtp', name: 'Postmark', host: 'smtp.postmarkapp.com', port: 587, spfDoc: 'https://postmarkapp.com/support/article/1092-how-do-i-set-up-spf-for-postmark' },
|
||||
{ provider: 'sendgrid-smtp', name: 'SendGrid', host: 'smtp.sendgrid.net', port: 587, username: 'apikey', spfDoc: 'https://sendgrid.com/docs/ui/account-and-settings/spf-records/' },
|
||||
{ provider: 'sparkpost-smtp', name: 'SparkPost', host: 'smtp.sparkpostmail.com', port: 587, username: 'SMTP_Injection', spfDoc: 'https://www.sparkpost.com/resources/email-explained/spf-sender-policy-framework/' },
|
||||
{ provider: 'noop', name: 'Disable' },
|
||||
];
|
||||
|
||||
$scope.usesTokenAuth = function (provider) {
|
||||
return provider === 'postmark-smtp' || provider === 'sendgrid-smtp' || provider === 'sparkpost-smtp';
|
||||
};
|
||||
|
||||
$scope.usesExternalServer = function (provider) {
|
||||
return provider !== 'cloudron-smtp' && provider !== 'noop';
|
||||
};
|
||||
|
||||
$scope.usesPasswordAuth = function (provider) {
|
||||
return provider === 'external-smtp'
|
||||
|| provider === 'ses-smtp'
|
||||
|| provider === 'google-smtp'
|
||||
|| provider === 'mailgun-smtp'
|
||||
|| provider === 'elasticemail-smtp'
|
||||
|| provider === 'office365-legacy-smtp'
|
||||
|| provider === 'mailjet-smtp';
|
||||
};
|
||||
|
||||
$scope.mailRelay = {
|
||||
error: null,
|
||||
success: false,
|
||||
busy: false,
|
||||
preset: $scope.mailRelayPresets[0],
|
||||
|
||||
// form data to be set on load
|
||||
relay: {
|
||||
provider: 'cloudron-smtp',
|
||||
host: '',
|
||||
port: 25,
|
||||
username: '',
|
||||
password: '',
|
||||
serverApiToken: '',
|
||||
acceptSelfSignedCerts: false
|
||||
},
|
||||
|
||||
presetChanged: function () {
|
||||
$scope.mailRelay.error = null;
|
||||
|
||||
$scope.mailRelay.relay.provider = $scope.mailRelay.preset.provider;
|
||||
$scope.mailRelay.relay.host = $scope.mailRelay.preset.host;
|
||||
$scope.mailRelay.relay.port = $scope.mailRelay.preset.port;
|
||||
$scope.mailRelay.relay.username = '';
|
||||
$scope.mailRelay.relay.password = '';
|
||||
$scope.mailRelay.relay.serverApiToken = '';
|
||||
$scope.mailRelay.relay.acceptSelfSignedCerts = false;
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.mailRelay.error = null;
|
||||
$scope.mailRelay.busy = true;
|
||||
$scope.mailRelay.success = false;
|
||||
|
||||
var data = {
|
||||
provider: $scope.mailRelay.relay.provider,
|
||||
host: $scope.mailRelay.relay.host,
|
||||
port: $scope.mailRelay.relay.port,
|
||||
acceptSelfSignedCerts: $scope.mailRelay.relay.acceptSelfSignedCerts,
|
||||
forceFromAddress: false
|
||||
};
|
||||
|
||||
// fill in provider specific username/password usage
|
||||
if (data.provider === 'postmark-smtp') {
|
||||
data.username = $scope.mailRelay.relay.serverApiToken;
|
||||
data.password = $scope.mailRelay.relay.serverApiToken;
|
||||
data.forceFromAddress = true; // postmark requires the "From:" in mail to be a Sender Signature
|
||||
} else if (data.provider === 'sendgrid-smtp') {
|
||||
data.username = 'apikey';
|
||||
data.password = $scope.mailRelay.relay.serverApiToken;
|
||||
} else if (data.provider === 'sparkpost-smtp') {
|
||||
data.username = 'SMTP_Injection';
|
||||
data.password = $scope.mailRelay.relay.serverApiToken;
|
||||
} else {
|
||||
data.username = $scope.mailRelay.relay.username;
|
||||
data.password = $scope.mailRelay.relay.password;
|
||||
}
|
||||
|
||||
Client.setMailRelay($scope.domain.domain, data, function (error) {
|
||||
|
||||
if (error) {
|
||||
$scope.mailRelay.error = error.message;
|
||||
$scope.mailRelay.busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.domain.relay = data;
|
||||
|
||||
// let the mail server restart, otherwise we get "Error getting IP" errors during refresh
|
||||
$timeout(function () {
|
||||
$scope.mailRelay.busy = false;
|
||||
$scope.mailRelay.success = true;
|
||||
$scope.refreshDomain();
|
||||
|
||||
// clear success indicator after 5sec
|
||||
$timeout(function () { $scope.mailRelay.success = false; }, 5000);
|
||||
}, 5000);
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function resetDnsRecords() {
|
||||
$scope.expectedDnsRecordsTypes.forEach(function (record) {
|
||||
var type = record.value;
|
||||
$scope.expectedDnsRecords[type] = {};
|
||||
|
||||
$('#collapse_dns_' + type).collapse('hide');
|
||||
});
|
||||
|
||||
$('#collapse_outbound_smtp').collapse('hide');
|
||||
$('#collapse_rbl').collapse('hide');
|
||||
}
|
||||
|
||||
function showExpectedDnsRecords() {
|
||||
// open the record details if they are not correct
|
||||
$scope.expectedDnsRecordsTypes.forEach(function (record) {
|
||||
var type = record.value;
|
||||
$scope.expectedDnsRecords[type] = $scope.domain.mailStatus.dns[type] || {};
|
||||
|
||||
if (!$scope.expectedDnsRecords[type].status) {
|
||||
$('#collapse_dns_' + type).collapse('show');
|
||||
}
|
||||
});
|
||||
|
||||
if (!$scope.domain.mailStatus.relay.status) {
|
||||
$('#collapse_outbound_smtp').collapse('show');
|
||||
}
|
||||
|
||||
if (!$scope.domain.mailStatus.rbl.status) {
|
||||
$('#collapse_rbl').collapse('show');
|
||||
}
|
||||
}
|
||||
|
||||
$scope.selectDomain = function () {
|
||||
$location.path('/email/' + $scope.domain.domain, false);
|
||||
};
|
||||
|
||||
// this is required because we need to rewrite the CLOUDRON_MAIL_SERVER_HOST env var
|
||||
$scope.reconfigureEmailApps = function () {
|
||||
var installedApps = Client.getInstalledApps();
|
||||
for (var i = 0; i < installedApps.length; i++) {
|
||||
if (!installedApps[i].manifest.addons.email) continue;
|
||||
|
||||
Client.repairApp(installedApps[i].id, { }, function (error) {
|
||||
if (error) console.error(error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.refreshDomain = function () {
|
||||
$scope.refreshBusy = true;
|
||||
|
||||
resetDnsRecords();
|
||||
|
||||
Client.getMailConfigForDomain(domainName, function (error, mailConfig) {
|
||||
if (error && error.statusCode === 404) return $location.path('/email');
|
||||
if (error) {
|
||||
$scope.refreshBusy = false;
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
// pre-fill the form
|
||||
$scope.mailRelay.relay.provider = mailConfig.relay.provider;
|
||||
$scope.mailRelay.relay.host = mailConfig.relay.host;
|
||||
$scope.mailRelay.relay.port = mailConfig.relay.port;
|
||||
$scope.mailRelay.relay.acceptSelfSignedCerts = !!mailConfig.relay.acceptSelfSignedCerts;
|
||||
$scope.mailRelay.relay.username = '';
|
||||
$scope.mailRelay.relay.password = '';
|
||||
$scope.mailRelay.relay.serverApiToken = '';
|
||||
|
||||
if (mailConfig.relay.provider === 'postmark-smtp') {
|
||||
$scope.mailRelay.relay.serverApiToken = mailConfig.relay.username;
|
||||
} else if (mailConfig.relay.provider === 'sendgrid-smtp') {
|
||||
$scope.mailRelay.relay.serverApiToken = mailConfig.relay.password;
|
||||
} else if (mailConfig.relay.provider === 'sparkpost-smtp') {
|
||||
$scope.mailRelay.relay.serverApiToken = mailConfig.relay.password;
|
||||
} else {
|
||||
$scope.mailRelay.relay.username = mailConfig.relay.username;
|
||||
$scope.mailRelay.relay.password = mailConfig.relay.password;
|
||||
}
|
||||
|
||||
for (var i = 0; i < $scope.mailRelayPresets.length; i++) {
|
||||
if ($scope.mailRelayPresets[i].provider === mailConfig.relay.provider) {
|
||||
$scope.mailRelay.preset = $scope.mailRelayPresets[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.banner.text = mailConfig.banner.text || '';
|
||||
$scope.banner.html = mailConfig.banner.html || '';
|
||||
|
||||
// amend to selected domain to be available for the UI
|
||||
$scope.domain.mailConfig = mailConfig;
|
||||
$scope.domain.mailStatus = {};
|
||||
|
||||
refreshMailUsage();
|
||||
$scope.mailboxes.refresh();
|
||||
$scope.mailinglists.refresh();
|
||||
$scope.catchall.refresh();
|
||||
|
||||
// we will fetch the status without blocking the ui
|
||||
Client.getMailStatusForDomain($scope.domain.domain, function (error, mailStatus) {
|
||||
$scope.refreshBusy = false;
|
||||
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.domain.mailStatus = mailStatus;
|
||||
|
||||
showExpectedDnsRecords();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.refreshStatus = function () {
|
||||
$scope.refreshBusy = true;
|
||||
|
||||
Client.getMailStatusForDomain($scope.domain.domain, function (error, mailStatus) {
|
||||
if (error) {
|
||||
$scope.refreshBusy = false;
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
// overwrite the selected domain status to be available for the UI
|
||||
$scope.domain.mailStatus = mailStatus;
|
||||
|
||||
showExpectedDnsRecords();
|
||||
|
||||
$scope.refreshBusy = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.howToConnectInfo = {
|
||||
show: function () {
|
||||
$('#howToConnectInfoModal').modal('show');
|
||||
}
|
||||
};
|
||||
|
||||
Client.onReady(function () {
|
||||
$scope.isAdminDomain = $scope.config.adminDomain === domainName;
|
||||
|
||||
Client.getAllUsers(function (error, users) {
|
||||
if (error) return console.error('Unable to get user listing.', error);
|
||||
|
||||
// ensure we have a display value available
|
||||
$scope.owners.push({ header: true, display: $translate.instant('email.mailboxboxDialog.usersHeader') });
|
||||
users.forEach(function (u) {
|
||||
$scope.owners.push({ display: u.username || u.email, id: u.id, type: 'user' });
|
||||
});
|
||||
|
||||
Client.getGroups(function (error, groups) {
|
||||
if (error) return console.error('Unable to get group listing.', error);
|
||||
|
||||
$scope.owners.push({ header: true, display: $translate.instant('email.mailboxboxDialog.groupsHeader') });
|
||||
groups.forEach(function (g) {
|
||||
$scope.owners.push({ display: g.name, id: g.id, type: 'group' });
|
||||
});
|
||||
|
||||
$scope.owners.push({ header: true, display: $translate.instant('email.mailboxboxDialog.appsHeader') });
|
||||
Client.getInstalledApps().forEach(function (a) {
|
||||
if (a.manifest.addons && a.manifest.addons.recvmail) $scope.owners.push({ display: a.label || a.fqdn, id: a.id, type: 'app' });
|
||||
});
|
||||
|
||||
Client.getDomains(function (error, result) {
|
||||
if (error) return console.error('Unable to list domains.', error);
|
||||
|
||||
$scope.domain = result.filter(function (d) { return d.domain === domainName; })[0];
|
||||
$scope.adminDomain = result.filter(function (d) { return d.domain === $scope.config.adminDomain; })[0];
|
||||
|
||||
async.eachSeries(result, function (domain, iteratorDone) {
|
||||
Client.getMailConfigForDomain(domain.domain, function (error, mailConfig) {
|
||||
if (error) return console.error('Failed to fetch mail config for domain', domain.domain, error);
|
||||
|
||||
if (mailConfig.enabled) $scope.incomingDomains.push(domain);
|
||||
iteratorDone();
|
||||
});
|
||||
}, function iteratorDone(error) {
|
||||
if (error) return console.error(error);
|
||||
|
||||
$scope.refreshDomain(); // this calls catchall.refresh() which in turn relies on incomingDomains
|
||||
|
||||
$scope.setView($routeParams.view || 'mailboxes', true /* always set */);
|
||||
$scope.ready = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// setup all the dialog focus handling
|
||||
['mailboxAddModal', 'mailboxEditModal', 'mailinglistEditModal', 'mailinglistAddModal'].forEach(function (id) {
|
||||
$('#' + id).on('shown.bs.modal', function () {
|
||||
$(this).find('[autofocus]:first').focus();
|
||||
});
|
||||
});
|
||||
|
||||
$('.modal-backdrop').remove();
|
||||
}]);
|
||||
@@ -1,100 +0,0 @@
|
||||
<div>
|
||||
<a href="/#/email" class="back-to-view-link"><i class="fas fa-arrow-left"></i> {{ 'email.backAction' | tr }}</a>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<h1>
|
||||
{{ 'emails.eventlog.title' | tr }}
|
||||
|
||||
<a class="btn btn-default btn-outline pull-right" href="/logs.html?id=mail" target="_blank">{{ 'main.action.logs' | tr }}</a>
|
||||
<a class="btn btn-default btn-outline pull-right" href="#/emails-queue">{{ 'emails.action.queue' | tr }}</a>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<div class="maillog-filter">
|
||||
<input class="form-control" style="width: 200px;" placeholder="{{ 'main.searchPlaceholder' | tr }}" type="text" ng-model="activity.search" ng-model-options="{ debounce: 1000 }" ng-change="activity.updateFilter(true)" />
|
||||
<multiselect ng-model="activity.selectedTypes" ms-header="{{ 'emails.typeFilterHeader' | tr }}" options="a.name for a in activityTypes" data-multiple="true" ng-change="activity.updateFilter(true)" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
<select class="form-control" ng-model="activity.pageItems" ng-options="a.name for a in pageItemCount" ng-change="activity.updateFilter(true)"></select>
|
||||
</div>
|
||||
<div class="pagination pull-right">
|
||||
<button class="btn btn-default btn-outline" ng-click="activity.refresh()"><i class="fas fa-sync-alt" ng-class="{ 'fa-spin': busyRefresh }"></i></button>
|
||||
<button class="btn btn-default btn-outline" ng-click="activity.showPrevPage()" ng-disabled="activity.busy || activity.currentPage <= 1"><i class="fa fa-angle-double-left"></i> {{ 'main.pagination.prev' | tr }}</button>
|
||||
<button class="btn btn-default btn-outline" ng-click="activity.showNextPage()" ng-disabled="activity.busy || activity.perPage > activity.eventLogs.length">{{ 'main.pagination.next' | tr }} <i class="fa fa-angle-double-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<div class="card card-block" style="max-width: 100%">
|
||||
<div>
|
||||
<center ng-show="activity.busy"><h2><i class="fa fa-circle-notch fa-spin"></i></h2></center>
|
||||
<table ng-hide="activity.busy" class="table table-hover" style="margin: 0; table-layout:fixed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%"><!-- Icon --></th>
|
||||
<th style="width: 15%">{{ 'emails.eventlog.time' | tr }}</th>
|
||||
<th style="width: 25%">{{ 'emails.eventlog.mailFrom' | tr }}</th>
|
||||
<th style="width: 25%">{{ 'emails.eventlog.rcptTo' | tr }}</th>
|
||||
<th style="width: 30%">{{ 'emails.eventlog.details' | tr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ng-hide="activity.eventLogs.length">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">
|
||||
<br>
|
||||
<br>
|
||||
{{ 'emails.eventlog.empty' | tr }}
|
||||
<br>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody ng-show="activity.eventLogs.length" ng-repeat="eventlog in activity.eventLogs">
|
||||
<tr ng-click="activity.showEventLogDetails(eventlog)" class="hand">
|
||||
<td class="no-wrap">
|
||||
<i class="fas fa-arrow-circle-left" ng-show="eventlog.type === 'delivered'" uib-tooltip="{{ 'emails.eventlog.type.outgoing' | tr }}"></i>
|
||||
<i class="fas fa-history" ng-show="eventlog.type === 'deferred'" uib-tooltip="{{ 'emails.eventlog.type.deferred' | tr }}"></i>
|
||||
<i class="fas fa-arrow-circle-right" ng-show="eventlog.type === 'received'" uib-tooltip="{{ 'emails.eventlog.type.incoming' | tr }}"></i>
|
||||
<i class="fas fa-align-justify" ng-show="eventlog.type === 'queued' && eventlog.spamStatus.indexOf('Yes,') !== 0" uib-tooltip="{{ 'emails.eventlog.type.queued' | tr }}"></i>
|
||||
<i class="fas fa-trash" ng-show="eventlog.type === 'queued' && eventlog.spamStatus.indexOf('Yes,') === 0" uib-tooltip="{{ 'emails.eventlog.type.queued' | tr }}"></i>
|
||||
<i class="fas fa-minus-circle" ng-show="eventlog.type === 'denied'" uib-tooltip="{{ 'emails.eventlog.type.denied' | tr }}"></i>
|
||||
<i class="fas fa-hand-paper" ng-show="eventlog.type === 'bounce'" uib-tooltip="{{ 'emails.eventlog.type.bounce' | tr }}"></i>
|
||||
<i class="fas fa-filter" ng-show="eventlog.type === 'spam-learn'" uib-tooltip="{{ 'emails.eventlog.type.spamFilterTrained' | tr }}"></i>
|
||||
<i class="fas fa-fill-drip" ng-show="eventlog.type === 'quota'" uib-tooltip="{{ 'emails.eventlog.type.quota' | tr }}"></i>
|
||||
</td>
|
||||
<td class="no-wrap"><span uib-tooltip="{{ eventlog.ts | prettyLongDate }}" class="arrow">{{ eventlog.ts | prettyDate }}</span></td>
|
||||
<td class="elide-table-cell">{{ (eventlog.mailFrom | prettyEmailAddresses) || '-' }}</td>
|
||||
<td class="elide-table-cell">{{ (eventlog.rcptTo | prettyEmailAddresses) || eventlog.mailbox || '-' }}</td>
|
||||
<td>
|
||||
<span ng-show="eventlog.type === 'bounce'">{{ 'emails.eventlog.type.bounceInfo' | tr }}. {{ eventlog.message || eventlog.reason }}</span>
|
||||
<span ng-show="eventlog.type === 'deferred'">{{ 'emails.eventlog.type.deferredInfo' | tr: { delay:eventlog.delay } }}. {{ eventlog.message || eventlog.reason }} </span>
|
||||
<span ng-show="eventlog.type === 'queued'">
|
||||
<span ng-show="eventlog.direction === 'inbound'">{{ 'emails.eventlog.type.inboundInfo' | tr }}</span>
|
||||
<span ng-show="eventlog.direction === 'outbound'">{{ 'emails.eventlog.type.outboundInfo' | tr }}</span>
|
||||
</span>
|
||||
<span ng-show="eventlog.type === 'received'">{{ 'emails.eventlog.type.receivedInfo' | tr }}</span>
|
||||
<span ng-show="eventlog.type === 'delivered'">{{ 'emails.eventlog.type.deliveredInfo' | tr }}</span>
|
||||
<span ng-show="eventlog.type === 'denied'">{{ 'emails.eventlog.type.deniedInfo' | tr }}. {{ eventlog.message || eventlog.reason }} </span>
|
||||
<span ng-show="eventlog.type === 'spam-learn'">{{ 'emails.eventlog.type.spamFilterTrainedInfo' | tr }}</span>
|
||||
<span ng-show="eventlog.type === 'quota'">
|
||||
<span ng-show="eventlog.quotaPercent > 0">{{ 'emails.eventlog.type.overQuotaInfo' | tr: { mailbox: eventlog.mailbox, quotaPercent: eventlog.quotaPercent } }}</span>
|
||||
<span ng-show="eventlog.quotaPercent < 0">{{ 'emails.eventlog.type.underQuotaInfo' | tr: { mailbox: eventlog.mailbox, quotaPercent: -eventlog.quotaPercent } }}</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-show="activity.activeEventLog === eventlog">
|
||||
<td colspan="6">
|
||||
<pre class="eventlog-details">{{ eventlog | json }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||