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