mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
595 Commits
filter-ref
...
eslint
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba6ac2e32e | ||
|
|
1498656a43 | ||
|
|
70a2a3993b | ||
|
|
5152d5de12 | ||
|
|
2999e539b3 | ||
|
|
8ce432256c | ||
|
|
ddbfbac802 | ||
|
|
1d6a7ee1fa | ||
|
|
0d97f0447a | ||
|
|
36a13b60fe | ||
|
|
64f4fcf829 | ||
|
|
1ff7c84d82 | ||
|
|
da623d4d34 | ||
|
|
affa492ce3 | ||
|
|
5ae3a5b5ee | ||
|
|
5716ab2445 | ||
|
|
a66a04456b | ||
|
|
e97a78eaeb | ||
|
|
57f41a908a | ||
|
|
f42291a428 | ||
|
|
4fa9adfc7d | ||
|
|
3e2e581e52 | ||
|
|
2a3c34af95 | ||
|
|
170b79e4cf | ||
|
|
c15b8bb951 | ||
|
|
17c47f7d89 | ||
|
|
a406287215 | ||
|
|
a73b050a4d | ||
|
|
224fb2e887 | ||
|
|
96d4131614 | ||
|
|
cda00d5238 | ||
|
|
dea385ad44 | ||
|
|
21973401fb | ||
|
|
d153ad9bf7 | ||
|
|
2ce8ba6295 | ||
|
|
572130d349 | ||
|
|
f0cb049266 | ||
|
|
cab5af6645 | ||
|
|
e77b923dc1 | ||
|
|
8325c06ca3 | ||
|
|
b66f56bc12 | ||
|
|
2de2926d79 | ||
|
|
fd13e91aac | ||
|
|
cf7d4c7720 | ||
|
|
b1a76e97ce | ||
|
|
7d8036e3eb | ||
|
|
c7344a7624 | ||
|
|
42253e4e50 | ||
|
|
64acf372d6 | ||
|
|
80f4238618 | ||
|
|
2fe54c46c1 | ||
|
|
266b046b9e | ||
|
|
f311841df2 | ||
|
|
26319ff3c8 | ||
|
|
e719bb3b70 | ||
|
|
d0255c1e33 | ||
|
|
52f4c74908 | ||
|
|
eeb6904c2d | ||
|
|
6fd37c62b8 | ||
|
|
16b761e498 | ||
|
|
9b1e702c64 | ||
|
|
f10fa9a48e | ||
|
|
de91346155 | ||
|
|
629a442ff7 | ||
|
|
be54d782e5 | ||
|
|
029c1b0704 | ||
|
|
36cfb7e20f | ||
|
|
02046a525e | ||
|
|
c9a998da0b | ||
|
|
7fc192e466 | ||
|
|
eae8b79b2e | ||
|
|
08c6cf31a0 | ||
|
|
dfb7f2320c | ||
|
|
c53b14f88f | ||
|
|
c5680f6c26 | ||
|
|
d2a1385123 | ||
|
|
feda1b0426 | ||
|
|
b97cc46a1e | ||
|
|
41a8101b54 | ||
|
|
676a2b18b3 | ||
|
|
df7477929b | ||
|
|
c1ebe14c50 | ||
|
|
2b478e7a13 | ||
|
|
3bb1d93f3e | ||
|
|
703c4b7685 | ||
|
|
d7e9776a3c | ||
|
|
3cb29b63fe | ||
|
|
e7f4a36ec9 | ||
|
|
be78862e49 | ||
|
|
64ae5eeb89 | ||
|
|
78bdc42534 | ||
|
|
68c81734e8 | ||
|
|
bf081d1ebe | ||
|
|
3de407842e | ||
|
|
a4a9e45fe0 | ||
|
|
f9c9480434 | ||
|
|
eb644ad2f2 | ||
|
|
5186e193a8 | ||
|
|
55d947fb39 | ||
|
|
77f1262ff5 | ||
|
|
47bf8f9c89 | ||
|
|
4ca4f28d1c | ||
|
|
b6565ce2bb | ||
|
|
0704e1f556 | ||
|
|
87c16d7bc3 | ||
|
|
cc66820e7b | ||
|
|
a478f60a39 | ||
|
|
5637f12d3a | ||
|
|
27c28b17af | ||
|
|
c55b169488 | ||
|
|
9b584d69ff | ||
|
|
05c090445a | ||
|
|
597aeb74f4 | ||
|
|
b7191a9c2e | ||
|
|
c686030014 | ||
|
|
eed1078f06 | ||
|
|
051a3c43b2 | ||
|
|
a152813535 | ||
|
|
decea5acfc | ||
|
|
4c2dfb0f92 | ||
|
|
a24bf077ce | ||
|
|
2d82f50789 | ||
|
|
3e13776252 | ||
|
|
463bb32872 | ||
|
|
99dcee80cd | ||
|
|
c418dc41dd | ||
|
|
c0678c410d | ||
|
|
081359f7e0 | ||
|
|
cc0cfe4f8c | ||
|
|
dc87905f05 | ||
|
|
9c31047d52 | ||
|
|
91d196ddea | ||
|
|
78db5cab39 | ||
|
|
689e72e5ec | ||
|
|
bc9e97f67c | ||
|
|
19550c23ed | ||
|
|
2d09df2d87 | ||
|
|
2dfebdd2e4 | ||
|
|
ebec143c9c | ||
|
|
aa68a6316a | ||
|
|
27a37581e4 | ||
|
|
091622f8cf | ||
|
|
87caae077c | ||
|
|
a146691773 | ||
|
|
9fce103b11 | ||
|
|
2aa0ae89fc | ||
|
|
0a16f6bf44 | ||
|
|
0f49e6e100 | ||
|
|
d954b4a5df | ||
|
|
e762778fc6 | ||
|
|
4cced50857 | ||
|
|
26c900d8e2 | ||
|
|
63461343ba | ||
|
|
931abc522b | ||
|
|
054592eed3 | ||
|
|
82b2307b51 | ||
|
|
b1dba73842 | ||
|
|
babbf5f8a6 | ||
|
|
39c7da79ab | ||
|
|
938bd32915 | ||
|
|
f8e1ace311 | ||
|
|
05e2e22e1d | ||
|
|
9054c49b3e | ||
|
|
5d5468603f | ||
|
|
4c6979cfa1 | ||
|
|
714a4be2b0 | ||
|
|
b935443f96 | ||
|
|
d3a3f01f20 | ||
|
|
56b0fea04a | ||
|
|
2605140166 | ||
|
|
8b9611e145 | ||
|
|
21057038d1 | ||
|
|
e587d4ba19 | ||
|
|
14da201c8d | ||
|
|
f5584a5037 | ||
|
|
157f8e95d7 | ||
|
|
fb83126f37 | ||
|
|
964def0c45 | ||
|
|
650d7cc939 | ||
|
|
bfb4c46bd0 | ||
|
|
149c8c763d | ||
|
|
fdbcace48c | ||
|
|
3cfe0517a8 | ||
|
|
9a32a47146 | ||
|
|
fd83a71a56 | ||
|
|
e9bdbb3414 | ||
|
|
3b65d1f4d6 | ||
|
|
ed0f92f480 | ||
|
|
6cc0f9deaf | ||
|
|
06be3d2f58 | ||
|
|
da8045271f | ||
|
|
cfc45efadf | ||
|
|
6b92440c74 | ||
|
|
c2d3123263 | ||
|
|
3a277db0d5 | ||
|
|
f084058b67 | ||
|
|
9dbe33bfcc | ||
|
|
085e1a5d76 | ||
|
|
9a3fa080ea | ||
|
|
db3d1e90f2 | ||
|
|
c02d8d8d8c | ||
|
|
1fad1d7c29 | ||
|
|
ef610e104a | ||
|
|
8f7bec00d4 | ||
|
|
ae2a70eb64 | ||
|
|
e56e6a7f8d | ||
|
|
1141d47df7 | ||
|
|
9dea2c3bdd | ||
|
|
9b465badda | ||
|
|
073f84fe00 | ||
|
|
c6850f5a0b | ||
|
|
636de9cd57 | ||
|
|
79ace6d183 | ||
|
|
366b195152 | ||
|
|
bc687bdcdd | ||
|
|
bc205a8686 | ||
|
|
e095715a52 | ||
|
|
e634ae0c1e | ||
|
|
ee080271e5 | ||
|
|
f07346a719 | ||
|
|
ace95bf75a | ||
|
|
4c53dc15e3 | ||
|
|
ac286e0903 | ||
|
|
6595202093 | ||
|
|
1ff5150765 | ||
|
|
13be411fc0 | ||
|
|
9d596c00da | ||
|
|
6a285df796 | ||
|
|
a053cc53eb | ||
|
|
14647f6bd5 | ||
|
|
bc1d629576 | ||
|
|
c19b4e7040 | ||
|
|
fca2c74817 | ||
|
|
737c785e73 | ||
|
|
25edb45e10 | ||
|
|
17308692b0 | ||
|
|
6785e62edd | ||
|
|
a1c761f618 | ||
|
|
29686f8167 | ||
|
|
235fd6722e | ||
|
|
7286825f82 | ||
|
|
e344c6d3d1 | ||
|
|
2751beae1d | ||
|
|
f1919d3964 | ||
|
|
de261142e5 | ||
|
|
0bfc28fedc | ||
|
|
a59e4cb76b | ||
|
|
ebb9c58758 | ||
|
|
ee2df1a132 | ||
|
|
b33dc0ff69 | ||
|
|
80c3bb9dda | ||
|
|
283fbda8ee | ||
|
|
a67c70396f | ||
|
|
3aeb22a253 | ||
|
|
070031d703 | ||
|
|
a30104fe06 | ||
|
|
76677233df | ||
|
|
e1054c2572 | ||
|
|
e3e684352f | ||
|
|
f9472a1308 | ||
|
|
2113022d11 | ||
|
|
f21cb43b5b | ||
|
|
5e7285fa66 | ||
|
|
ff1195c837 | ||
|
|
2f5f147ab4 | ||
|
|
f4005ee351 | ||
|
|
549e41119c | ||
|
|
0f76e075ce | ||
|
|
7212b93836 | ||
|
|
aca335aa8f | ||
|
|
1053f52b2b | ||
|
|
4ef8a8d0f9 | ||
|
|
8d183ceee6 | ||
|
|
d2a58fbe97 | ||
|
|
9b115da0be | ||
|
|
e46133d678 | ||
|
|
c9bf2e3bf1 | ||
|
|
2c16b5ab7d | ||
|
|
f7339723cd | ||
|
|
c2e04e94e2 | ||
|
|
bbb2f8d154 | ||
|
|
4ab9e8b398 | ||
|
|
d0e6153758 | ||
|
|
f308407f68 | ||
|
|
93ba752e8c | ||
|
|
230d345406 | ||
|
|
7e51da56f6 | ||
|
|
cab9fd08fa | ||
|
|
461f69b11e | ||
|
|
ad7a5bddcb | ||
|
|
a64feacb5c | ||
|
|
a27b51ea94 | ||
|
|
a0f7324c01 | ||
|
|
c3b0302cbe | ||
|
|
0c264bcd32 | ||
|
|
abc7a43d1b | ||
|
|
05e9282d7f | ||
|
|
75d2a01a5a | ||
|
|
8d02eddfc6 | ||
|
|
33d38611e2 | ||
|
|
31335ee6b5 | ||
|
|
6eb228d0b8 | ||
|
|
0ef82aef01 | ||
|
|
f644ac117f | ||
|
|
08ddfb1017 | ||
|
|
68724478da | ||
|
|
ca621bf41c | ||
|
|
c470abf36c | ||
|
|
39bd722cf5 | ||
|
|
3851827afb | ||
|
|
cd5c333d97 | ||
|
|
fee16520df | ||
|
|
025648482e | ||
|
|
89a3d3a6ac | ||
|
|
e4da9c5212 | ||
|
|
1e61fec929 | ||
|
|
6a76665c79 | ||
|
|
2d3d0449e5 | ||
|
|
fee5132e7e | ||
|
|
2dbb91ba24 | ||
|
|
9a37c4b031 | ||
|
|
d3d64979e0 | ||
|
|
c6efe40f3b | ||
|
|
d0ace48aeb | ||
|
|
faf0641503 | ||
|
|
4fcc066971 | ||
|
|
e37347a611 | ||
|
|
95dbeaddba | ||
|
|
5a12e70742 | ||
|
|
ab0b7bb6ec | ||
|
|
c9874df43f | ||
|
|
594a44392d | ||
|
|
5b9232c785 | ||
|
|
06b7f5c236 | ||
|
|
f20f833720 | ||
|
|
91c37a2a9e | ||
|
|
937879aa68 | ||
|
|
df77fdac74 | ||
|
|
28917b3fcb | ||
|
|
853715b17c | ||
|
|
5bdc381925 | ||
|
|
c499df2812 | ||
|
|
5ef5dbf562 | ||
|
|
332aa47e63 | ||
|
|
cb1ddfe0df | ||
|
|
d5bb29f9b4 | ||
|
|
5f851b6936 | ||
|
|
5fa7c5baaa | ||
|
|
557c3f1427 | ||
|
|
a14614b7de | ||
|
|
cc1c968d3c | ||
|
|
1830f031e9 | ||
|
|
5d8dd6ef3c | ||
|
|
d0b63b5c8b | ||
|
|
e94cc8083a | ||
|
|
847f426bc6 | ||
|
|
74bcadb2e7 | ||
|
|
3e255e6dd1 | ||
|
|
eb5cf5a3cb | ||
|
|
df7bd46428 | ||
|
|
e6dadd0673 | ||
|
|
ee8706e75b | ||
|
|
da70359945 | ||
|
|
3622021e57 | ||
|
|
5aeada377a | ||
|
|
d34a493724 | ||
|
|
5f7dcf7433 | ||
|
|
39407795a2 | ||
|
|
63dfb56da3 | ||
|
|
ed8714cc34 | ||
|
|
dedda5b53b | ||
|
|
df2af5858c | ||
|
|
e25d38ee74 | ||
|
|
2b0dd6687d | ||
|
|
22f08a4c47 | ||
|
|
fda11d81c0 | ||
|
|
18e68b3505 | ||
|
|
a8a550809d | ||
|
|
dbacf998ff | ||
|
|
0d41d97b8d | ||
|
|
36ecf78c4c | ||
|
|
3eb0eb4289 | ||
|
|
01c214b51e | ||
|
|
f756df1e5f | ||
|
|
e2a7f7efc5 | ||
|
|
3bf4137db2 | ||
|
|
f592e852f2 | ||
|
|
a5f05376b0 | ||
|
|
89e08614f4 | ||
|
|
631c22aa64 | ||
|
|
0c4ad712b0 | ||
|
|
0e4f54d048 | ||
|
|
80ede00ced | ||
|
|
40b9573324 | ||
|
|
e09f856c92 | ||
|
|
9db5aa56e6 | ||
|
|
f8c43136e9 | ||
|
|
3fb804c4e7 | ||
|
|
dad1f1a464 | ||
|
|
95e9eacc83 | ||
|
|
14b5da110f | ||
|
|
b66cd889c0 | ||
|
|
d323c70a51 | ||
|
|
8962ecf42a | ||
|
|
31f92c3611 | ||
|
|
6bed753de3 | ||
|
|
4017e7ce0e | ||
|
|
ccaaaa1823 | ||
|
|
7d7fa2b8f1 | ||
|
|
2ca57ac8bf | ||
|
|
a75f655f72 | ||
|
|
6ca38a1adb | ||
|
|
46a095569d | ||
|
|
cec38190fd | ||
|
|
ca746d1436 | ||
|
|
2f6133bba0 | ||
|
|
d1f62d3c66 | ||
|
|
db2c6b0ea5 | ||
|
|
a2033b94dd | ||
|
|
d23c62d30a | ||
|
|
6ca5b9572e | ||
|
|
f5f62d905b | ||
|
|
876de677cd | ||
|
|
19cb221f33 | ||
|
|
f0f774ecdb | ||
|
|
e333c2d43b | ||
|
|
4cdb832b34 | ||
|
|
367715c077 | ||
|
|
945eb3d11b | ||
|
|
b4c4273485 | ||
|
|
1c319bb432 | ||
|
|
6fc8bc3628 | ||
|
|
33e9c8fbe7 | ||
|
|
6f4da22874 | ||
|
|
772341a15c | ||
|
|
387fdd7b15 | ||
|
|
16bad74c4d | ||
|
|
054242df60 | ||
|
|
3d4aecb896 | ||
|
|
26c8712fce | ||
|
|
ab090ab016 | ||
|
|
4653711b58 | ||
|
|
9aa1876e70 | ||
|
|
40d9aff56a | ||
|
|
3c88a1b879 | ||
|
|
149baf17ef | ||
|
|
424cfa2094 | ||
|
|
d114c52d6f | ||
|
|
213e2111a5 | ||
|
|
31837ef1f5 | ||
|
|
b906ff1dce | ||
|
|
cc4d6514cc | ||
|
|
005256084e | ||
|
|
67b542d828 | ||
|
|
c147c49465 | ||
|
|
9428d08def | ||
|
|
79f27550df | ||
|
|
21d62eab18 | ||
|
|
f011559cd2 | ||
|
|
4bb5a29963 | ||
|
|
e771172b79 | ||
|
|
9476327a6a | ||
|
|
7c14931bb9 | ||
|
|
2ad245083d | ||
|
|
81da1546d9 | ||
|
|
a48f472c52 | ||
|
|
2a9b3db168 | ||
|
|
bbc95a3a1d | ||
|
|
0bbb35ff43 | ||
|
|
7edd8f197a | ||
|
|
1ad239a9fe | ||
|
|
eeae73b7a4 | ||
|
|
026997a679 | ||
|
|
aa83db3e48 | ||
|
|
5725c6a17a | ||
|
|
79171c93d9 | ||
|
|
5073a15bf1 | ||
|
|
9285e9b401 | ||
|
|
b6adf3fb40 | ||
|
|
164e6062c9 | ||
|
|
1ebc8a9d91 | ||
|
|
3205453608 | ||
|
|
be3d4b2ce0 | ||
|
|
7c8aabe3e8 | ||
|
|
b0c62ed827 | ||
|
|
80a1827455 | ||
|
|
bbb1b2e944 | ||
|
|
f70878317a | ||
|
|
f5184db0fa | ||
|
|
19128a50d2 | ||
|
|
8fbb3774d1 | ||
|
|
441efb3d1a | ||
|
|
180491fa57 | ||
|
|
7269e93967 | ||
|
|
a754ec094b | ||
|
|
d9e097eeb2 | ||
|
|
53d7aab558 | ||
|
|
dcf2584987 | ||
|
|
d18310ec69 | ||
|
|
f61d603e21 | ||
|
|
52a44a8383 | ||
|
|
be848d37a2 | ||
|
|
df3a7e1a99 | ||
|
|
a6e9f7cd1e | ||
|
|
2e29a63f7b | ||
|
|
c153c8c656 | ||
|
|
61cbaaf4f0 | ||
|
|
70aae76ae3 | ||
|
|
972bc9c4cf | ||
|
|
fb902170e2 | ||
|
|
286429b58f | ||
|
|
b799ce08a9 | ||
|
|
b30bb4c95e | ||
|
|
0e3eb81ae4 | ||
|
|
b997520977 | ||
|
|
29675f2624 | ||
|
|
fb83476795 | ||
|
|
0420586a0e | ||
|
|
900f76f8c8 | ||
|
|
082c5732c0 | ||
|
|
20c03c5fd9 | ||
|
|
5adb8f0d38 | ||
|
|
a54037408e | ||
|
|
4c0cf60c42 | ||
|
|
6aac72a226 | ||
|
|
d1424840d8 | ||
|
|
0a90736338 | ||
|
|
6c2144d77e | ||
|
|
e208b7cc78 | ||
|
|
69ff3dfe17 | ||
|
|
29d2abf226 | ||
|
|
1cf7cc4664 | ||
|
|
d29991117a | ||
|
|
0bf8713b60 | ||
|
|
dcc9d4fdf2 | ||
|
|
db32292d33 | ||
|
|
2b66c52907 | ||
|
|
d6b1edb8cb | ||
|
|
c7f62fac65 | ||
|
|
db0aee6c0f | ||
|
|
1f53ecb093 | ||
|
|
d0325f2d7d | ||
|
|
a1461a4019 | ||
|
|
d3096510bd | ||
|
|
8dac1a845a | ||
|
|
da26400aa7 | ||
|
|
c909d02a5d | ||
|
|
58e817cecd | ||
|
|
f9a784422d | ||
|
|
6494ab9a71 | ||
|
|
a3cf038d38 | ||
|
|
99a88ea6d7 | ||
|
|
d46604da53 | ||
|
|
4a05ee22be | ||
|
|
28e809c493 | ||
|
|
610373525b | ||
|
|
e84d1c2960 | ||
|
|
17cb699751 | ||
|
|
c3bf383e89 | ||
|
|
a3e5288737 | ||
|
|
14cf0d9343 | ||
|
|
4ed613bf2c | ||
|
|
f07da41cca | ||
|
|
fe2300146d | ||
|
|
207108dc2d | ||
|
|
b728256c63 | ||
|
|
0e5b71070c | ||
|
|
4c4bc92a14 | ||
|
|
40dccbb382 | ||
|
|
eedb7173db | ||
|
|
917d12d7f0 | ||
|
|
11a9cfcb98 | ||
|
|
172c3dacbe | ||
|
|
a9381e09c1 | ||
|
|
f0816f6fa5 | ||
|
|
ad95971f46 | ||
|
|
bb59a365b7 | ||
|
|
503dcffa87 | ||
|
|
42a91415da | ||
|
|
09c6160a15 | ||
|
|
0638b797b3 | ||
|
|
c6020a8eb6 | ||
|
|
2023f37ab7 | ||
|
|
65f024c3f6 | ||
|
|
06936f0065 | ||
|
|
41a2e2cdbe | ||
|
|
96cfe4c1fd | ||
|
|
0096860584 | ||
|
|
da90c9b733 | ||
|
|
5cef1bacb7 | ||
|
|
9560883954 | ||
|
|
f84a8d6d7a | ||
|
|
97e1688b50 | ||
|
|
f0acbc6eb0 | ||
|
|
07f99b8796 |
@@ -1,28 +0,0 @@
|
||||
*/node_modules/*
|
||||
*/build/*
|
||||
*/coverage/*
|
||||
|
||||
*/dist/*
|
||||
|
||||
*/public/*
|
||||
*/views/*
|
||||
|
||||
*fonts*
|
||||
*logos*
|
||||
|
||||
.*
|
||||
*.png
|
||||
*.sh
|
||||
*.txt
|
||||
*.snap
|
||||
*.enc
|
||||
Dockerfile
|
||||
CHANGELOG
|
||||
LICENSE
|
||||
|
||||
marketing/*/*
|
||||
licenses/*
|
||||
certifications/*
|
||||
ApiReference/public/assets/*
|
||||
JavaScriptSDK/src/cli/server-monitor/out/scripts/prettify/*
|
||||
_test/*
|
||||
216
.eslintrc.json
216
.eslintrc.json
@@ -1,216 +0,0 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 8,
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true,
|
||||
"tsx": true,
|
||||
"spread": true
|
||||
},
|
||||
"sourceType": "module",
|
||||
"project": [
|
||||
"./tsconfig.json"
|
||||
]
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"jquery": true,
|
||||
"es6": true,
|
||||
"jest": true,
|
||||
"jasmine": true
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"jsx-a11y",
|
||||
"progress",
|
||||
"@typescript-eslint",
|
||||
"unused-imports"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"context": true,
|
||||
"before": true,
|
||||
"beforeEach": true,
|
||||
"after": true,
|
||||
"afterEach": true,
|
||||
"it": true,
|
||||
"expect": true,
|
||||
"workbox": true,
|
||||
"importScripts": true,
|
||||
"$TSFixMe": true,
|
||||
"NodeJS": true,
|
||||
"JSX": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"rules": {
|
||||
"no-fallthrough": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-cond-assign": "error",
|
||||
"valid-typeof": "error",
|
||||
"no-func-assign": "error",
|
||||
"curly": "error",
|
||||
"no-extra-semi": "error",
|
||||
"no-else-return": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-octal": "error",
|
||||
"no-extra-bind": "error",
|
||||
"unicode-bom": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"wrap-regex": "error",
|
||||
"wrap-iife": "error",
|
||||
"yield-star-spacing": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-extra-label": "error",
|
||||
"multiline-comment-style": "off",
|
||||
"no-lonely-if": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"eqeqeq": "error",
|
||||
"dot-notation": "off", // Off because it messes up with typescript compiler.
|
||||
"@typescript-eslint/dot-notation": "off", //temp off.
|
||||
"progress/activate": 1,
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"@typescript-eslint/no-empty-interface": [
|
||||
"error",
|
||||
{
|
||||
"allowSingleExtends": true
|
||||
}
|
||||
],
|
||||
// https://www.npmjs.com/package/eslint-plugin-unused-imports
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-extra-non-null-assertion": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^_",
|
||||
"args": "after-used",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"error"
|
||||
],
|
||||
"no-console": "error",
|
||||
"no-undef": "error",
|
||||
"no-empty": "error",
|
||||
"no-control-regex": "off",
|
||||
"prefer-arrow-callback": "error",
|
||||
"constructor-super": "error",
|
||||
"no-case-declarations": "error",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"no-useless-escape": "error",
|
||||
"prettier/prettier": "error",
|
||||
"react/jsx-no-undef": "error",
|
||||
"react/jsx-no-bind": [
|
||||
"error",
|
||||
{
|
||||
"allowArrowFunctions": true,
|
||||
"allowBind": false,
|
||||
"ignoreRefs": false
|
||||
}
|
||||
],
|
||||
"react/no-children-prop": "error",
|
||||
"react/no-deprecated": "error",
|
||||
"react/boolean-prop-naming": "error",
|
||||
"react/no-is-mounted": "error",
|
||||
"react/no-find-dom-node": "error",
|
||||
"one-var-declaration-per-line": "error",
|
||||
"arrow-parens": "error",
|
||||
"arrow-body-style": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"@typescript-eslint/typedef": [
|
||||
"error",
|
||||
{
|
||||
"arrowParameter": true,
|
||||
"variableDeclaration": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/strict-boolean-expressions": "off", //Need to enable this very soon
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"error",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"react/no-did-update-set-state": "error",
|
||||
"react/no-unknown-property": "error",
|
||||
"react/no-unused-prop-types": "error",
|
||||
"react/jsx-no-duplicate-props": "error",
|
||||
"react/no-unused-state": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"react/prop-types": "error",
|
||||
"react/react-in-jsx-scope": "error",
|
||||
"react/no-string-refs": "error",
|
||||
"jsx-a11y/href-no-hash": [
|
||||
0
|
||||
],
|
||||
"react/no-unescaped-entities": "error",
|
||||
"react/display-name": "error",
|
||||
"react/jsx-pascal-case": "error",
|
||||
"array-callback-return": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-promise-executor-return": "error",
|
||||
"capitalized-comments": "off", // this is turned off because come commented code should not be capitalized.
|
||||
"for-direction": "error",
|
||||
"getter-return": "error",
|
||||
"jsx-a11y/anchor-is-valid": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"prefer-const": [
|
||||
"error",
|
||||
{
|
||||
"destructuring": "any",
|
||||
"ignoreReadBeforeAssign": false
|
||||
}
|
||||
],
|
||||
"no-var": "error",
|
||||
"object-curly-spacing": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-unneeded-ternary": "error",
|
||||
"@typescript-eslint/ban-types": [
|
||||
"error",
|
||||
{
|
||||
"types": {
|
||||
"String": true,
|
||||
"Boolean": true,
|
||||
"Number": true,
|
||||
"Symbol": false,
|
||||
"{}": true,
|
||||
"Object": true,
|
||||
"object": true,
|
||||
"Function": true
|
||||
},
|
||||
"extendDefaults": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "18.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
31
.github/workflows/build.yml
vendored
31
.github/workflows/build.yml
vendored
@@ -72,6 +72,22 @@ jobs:
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./App/Dockerfile .
|
||||
|
||||
|
||||
docker-build-copilot:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./Copilot/Dockerfile .
|
||||
|
||||
docker-build-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -179,21 +195,6 @@ jobs:
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./StatusPage/Dockerfile .
|
||||
|
||||
docker-build-infrastructure-agent:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
# build image for mail service
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./InfrastructureAgent/Dockerfile .
|
||||
|
||||
docker-build-test-server:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
language: [ 'javascript', 'typescript', 'go' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
|
||||
26
.github/workflows/compile.yml
vendored
26
.github/workflows/compile.yml
vendored
@@ -91,6 +91,20 @@ jobs:
|
||||
- run: cd CommonUI && npm install --force
|
||||
- run: cd App && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-copilot:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: cd Common && npm install
|
||||
- run: cd Model && npm install
|
||||
- run: cd CommonServer && npm install
|
||||
- run: cd Copilot && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-nginx:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -111,14 +125,10 @@ jobs:
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: cd Common && npm install
|
||||
- run: cd Model && npm install
|
||||
- run: cd CommonServer && npm install
|
||||
- run: cd InfrastructureAgent && npm install && npm run compile && npm run dep-check
|
||||
- uses: actions/checkout@v4
|
||||
# Setup Go
|
||||
- uses: actions/setup-go@v5
|
||||
- run: cd InfrastructureAgent && go build .
|
||||
|
||||
|
||||
compile-admin-dashboard:
|
||||
|
||||
296
.github/workflows/release.yml
vendored
296
.github/workflows/release.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- run: echo "Build number is ${{ steps.buildnumber.outputs.build_number }}"
|
||||
|
||||
github-release:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/release'
|
||||
permissions:
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
helm-chart-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
env:
|
||||
CI_COMMIT_AUTHOR: Continuous Integration
|
||||
steps:
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
git push origin master
|
||||
|
||||
nginx-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
e2e-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
isolated-vm-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -272,7 +272,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
test-server-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -332,7 +332,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
otel-collector-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -394,7 +394,7 @@ jobs:
|
||||
|
||||
|
||||
status-page-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -454,7 +454,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
test-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -514,7 +514,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
ingestor-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -574,7 +574,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
probe-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -635,7 +635,7 @@ jobs:
|
||||
|
||||
|
||||
haraka-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -695,7 +695,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
admin-dashboard-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -756,7 +756,7 @@ jobs:
|
||||
|
||||
|
||||
dashboard-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -816,7 +816,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
app-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -875,8 +875,69 @@ jobs:
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
copilot-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
oneuptime/copilot
|
||||
ghcr.io/oneuptime/copilot
|
||||
tags: |
|
||||
type=raw,value=release,enable=true
|
||||
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}},pattern={{version}},enable=true
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Generate Dockerfile from Dockerfile.tpl
|
||||
run: npm run prerun
|
||||
|
||||
# Build and deploy app.
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
file: ./Copilot/Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
accounts-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -935,39 +996,10 @@ jobs:
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
# We dont need any of this because we are using the npm package
|
||||
# infrastructure-agent-macos-binary-release:
|
||||
# runs-on: macos-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: actions/setup-node@v2
|
||||
# with:
|
||||
# node-version: 21.6.2
|
||||
# - run: cd InfrastructureAgent && bash Scripts/Build/Mac.sh
|
||||
|
||||
# We dont need any of this because we are using the npm package
|
||||
# infrastructure-agent-windows-binary-release:
|
||||
# runs-on: windows-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: actions/setup-node@v2
|
||||
# with:
|
||||
# node-version: 21.6.2
|
||||
# - run: cd InfrastructureAgent && ./Scripts/Build/Windows.ps1
|
||||
|
||||
# We dont need any of this because we are using the npm package
|
||||
# infrastructure-agent-linux-binary-release:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: actions/setup-node@v2
|
||||
# with:
|
||||
# node-version: 21.6.2
|
||||
# - run: cd InfrastructureAgent && bash ./Scripts/Build/Linux.sh
|
||||
|
||||
publish-npm-packages:
|
||||
runs-on: ubuntu-latest
|
||||
needs: generate-build-number
|
||||
needs: [generate-build-number, github-release]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
NPM_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
|
||||
@@ -980,64 +1012,140 @@ jobs:
|
||||
- name: Publish Infrastructure Agent
|
||||
run: bash ./Scripts/NPM/PublishAllPackages.sh
|
||||
|
||||
infrastructure-agent-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
infrastructure-agent-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
oneuptime/infrastructure-agent
|
||||
ghcr.io/oneuptime/infrastructure-agent
|
||||
tags: |
|
||||
type=raw,value=release,enable=true
|
||||
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}},pattern={{version}},enable=true
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
|
||||
- name: Install GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
install-only: true
|
||||
|
||||
- name: Show GoReleaser version
|
||||
run: goreleaser -v
|
||||
|
||||
- name: Run GoReleaser
|
||||
run: cd InfrastructureAgent && export GORELEASER_CURRENT_TAG=7.0.${{needs.generate-build-number.outputs.build_number}} && goreleaser release --clean --snapshot
|
||||
|
||||
# Upload binaries to github release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
InfrastructureAgent/dist/*
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
tag_name: 7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
test-e2e-release-saas:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, infrastructure-agent-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, github-release, generate-build-number, nginx-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Generate Dockerfile from Dockerfile.tpl
|
||||
run: npm run prerun
|
||||
|
||||
# Build and deploy infrastructure-agent.
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.2.0
|
||||
|
||||
test-e2e-release-self-hosted:
|
||||
runs-on: ubuntu-latest
|
||||
# After all the jobs runs
|
||||
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, infrastructure-agent-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, github-release, generate-build-number, nginx-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
file: ./InfrastructureAgent/Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
125
.github/workflows/test-release.yaml
vendored
125
.github/workflows/test-release.yaml
vendored
@@ -876,8 +876,8 @@ jobs:
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
infrastructure-agent-docker-image-deploy:
|
||||
|
||||
copilot-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -886,8 +886,8 @@ jobs:
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
oneuptime/infrastructure
|
||||
ghcr.io/oneuptime/infrastructure
|
||||
oneuptime/copilot
|
||||
ghcr.io/oneuptime/copilot
|
||||
tags: |
|
||||
type=raw,value=test,enable=true
|
||||
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}}-test,pattern={{version}},enable=true
|
||||
@@ -910,7 +910,7 @@ jobs:
|
||||
- name: Generate Dockerfile from Dockerfile.tpl
|
||||
run: npm run prerun
|
||||
|
||||
# Build and deploy infrastructure.
|
||||
# Build and deploy accounts.
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
@@ -928,7 +928,7 @@ jobs:
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
file: ./InfrastructureAgent/Dockerfile
|
||||
file: ./Copilot/Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
@@ -938,11 +938,10 @@ jobs:
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
|
||||
|
||||
test-helm-chart:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ infrastructure-agent-docker-image-deploy, isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy]
|
||||
needs: [copilot-docker-image-deploy, isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
@@ -951,3 +950,111 @@ jobs:
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: cd HelmChart && cd Tests && bash index.sh
|
||||
|
||||
test-e2e-test-saas:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-helm-chart]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/change-release-to-test-tag.sh
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
test-e2e-test-self-hosted:
|
||||
runs-on: ubuntu-latest
|
||||
# After all the jobs runs
|
||||
needs: [test-helm-chart]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/change-release-to-test-tag.sh
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
|
||||
|
||||
12
.github/workflows/test.e2e.yaml
vendored
12
.github/workflows/test.e2e.yaml
vendored
@@ -32,8 +32,10 @@ jobs:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
|
||||
- run: npm run dev
|
||||
- name: Sleep for 2 minutes to wait for server to start
|
||||
run: sleep 120
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
@@ -48,15 +50,11 @@ jobs:
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E/playwright-report
|
||||
./E2E/test-results
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
|
||||
retention-days: 7
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -111,3 +111,5 @@ InfrastructureAgent/daemon.pid
|
||||
App/greenlock/.greenlockrc
|
||||
App/greenlock/greenlock.d/config.json
|
||||
App/greenlock/greenlock.d/config.json.bak
|
||||
Examples/otel-dotnet/bin/Debug/net6.0/Grpc.Core.Api.dll.txt
|
||||
InfrastructureAgent/oneuptime-infrastructure-agent
|
||||
|
||||
@@ -50,4 +50,7 @@ marketing/*/*
|
||||
licenses/*
|
||||
certifications/*
|
||||
ApiReference/public/assets/*
|
||||
JavaScriptSDK/src/cli/server-monitor/out/scripts/prettify/*
|
||||
JavaScriptSDK/src/cli/server-monitor/out/scripts/prettify/*
|
||||
|
||||
|
||||
CommonServer/Tests/TestingUtils/__mocks__/Stripe.mock.ts
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
35
.vscode/launch.json
vendored
35
.vscode/launch.json
vendored
@@ -19,6 +19,28 @@
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug: Copilot Locally",
|
||||
"request": "launch",
|
||||
"localRoot": "${workspaceFolder}/Copilot",
|
||||
"runtimeArgs": [
|
||||
"run-script",
|
||||
"start"
|
||||
],
|
||||
"runtimeExecutable": "npm",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node"
|
||||
},
|
||||
{
|
||||
"name": "Debug Infrastructure Agent",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "./InfrastructureAgent",
|
||||
"args": ["start"],
|
||||
},
|
||||
{
|
||||
"name": "Node.js - Debug Current File",
|
||||
"type": "node",
|
||||
@@ -208,19 +230,6 @@
|
||||
"debug:test"
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Probe: Debug Tests",
|
||||
"type": "node",
|
||||
"restart": true,
|
||||
"autoAttachChildProcesses": true,
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceRoot}/Probe",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run-script",
|
||||
"debug:test"
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Accounts: Debug Local Files",
|
||||
"type": "node",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM node:21.7.2-alpine3.18
|
||||
FROM node:21.7.3-alpine3.18
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
import App from 'CommonServer/Utils/StartServer';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import App from "CommonServer/Utils/StartServer";
|
||||
|
||||
export const APP_NAME: string = 'accounts';
|
||||
export const APP_NAME: string = "accounts";
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error('App Init Failed:');
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error("App Init Failed:");
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
init().catch((err: Error) => {
|
||||
logger.error(err);
|
||||
logger.info('Exiting node process');
|
||||
process.exit(1);
|
||||
logger.error(err);
|
||||
logger.error("Exiting node process");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
8
Accounts/index.d.ts
vendored
8
Accounts/index.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
declare module '*.png';
|
||||
declare module '*.svg';
|
||||
declare module '*.jpg';
|
||||
declare module '*.gif';
|
||||
declare module "*.png";
|
||||
declare module "*.svg";
|
||||
declare module "*.jpg";
|
||||
declare module "*.gif";
|
||||
|
||||
55
Accounts/package-lock.json
generated
55
Accounts/package-lock.json
generated
@@ -16,9 +16,9 @@
|
||||
"express": "^4.19.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"Model": "file:../Model",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"sass-loader": "^13.3.3",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
@@ -267,9 +267,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
|
||||
"integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==",
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
||||
"integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
@@ -2064,9 +2065,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -2075,23 +2076,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.0"
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.22.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
|
||||
"integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
|
||||
"version": "6.23.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
|
||||
"integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.15.3"
|
||||
"@remix-run/router": "1.16.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -2101,12 +2103,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.22.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
|
||||
"integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
|
||||
"version": "6.23.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz",
|
||||
"integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.15.3",
|
||||
"react-router": "6.22.3"
|
||||
"@remix-run/router": "1.16.1",
|
||||
"react-router": "6.23.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -2189,9 +2192,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
||||
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
"express": "^4.19.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"Model": "file:../Model",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"sass-loader": "^13.3.3",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
|
||||
@@ -1,47 +1,44 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import ForgotPasswordPage from "./Pages/ForgotPassword";
|
||||
import LoginPage from "./Pages/Login";
|
||||
import NotFound from "./Pages/NotFound";
|
||||
import RegisterPage from "./Pages/Register";
|
||||
import ResetPasswordPage from "./Pages/ResetPassword";
|
||||
import VerifyEmail from "./Pages/VerifyEmail";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import React, { ReactElement } from "react";
|
||||
import {
|
||||
Routes,
|
||||
Route,
|
||||
useNavigate,
|
||||
useLocation,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import LoginPage from './Pages/Login';
|
||||
import NotFound from './Pages/NotFound';
|
||||
import ForgotPasswordPage from './Pages/ForgotPassword';
|
||||
import RegisterPage from './Pages/Register';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import VerifyEmail from './Pages/VerifyEmail';
|
||||
import ResetPasswordPage from './Pages/ResetPassword';
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
|
||||
function App(): ReactElement {
|
||||
Navigation.setNavigateHook(useNavigate());
|
||||
Navigation.setLocation(useLocation());
|
||||
Navigation.setParams(useParams());
|
||||
Navigation.setNavigateHook(useNavigate());
|
||||
Navigation.setLocation(useLocation());
|
||||
Navigation.setParams(useParams());
|
||||
|
||||
return (
|
||||
<div className="m-auto h-screen">
|
||||
<Routes>
|
||||
<Route path="/accounts" element={<LoginPage />} />
|
||||
<Route path="/accounts/login" element={<LoginPage />} />
|
||||
<Route
|
||||
path="/accounts/forgot-password"
|
||||
element={<ForgotPasswordPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/accounts/reset-password/:token"
|
||||
element={<ResetPasswordPage />}
|
||||
/>
|
||||
<Route path="/accounts/register" element={<RegisterPage />} />
|
||||
<Route
|
||||
path="/accounts/verify-email/:token"
|
||||
element={<VerifyEmail />}
|
||||
/>
|
||||
{/* 👇️ only match this when no other routes match */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="m-auto h-screen">
|
||||
<Routes>
|
||||
<Route path="/accounts" element={<LoginPage />} />
|
||||
<Route path="/accounts/login" element={<LoginPage />} />
|
||||
<Route
|
||||
path="/accounts/forgot-password"
|
||||
element={<ForgotPasswordPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/accounts/reset-password/:token"
|
||||
element={<ResetPasswordPage />}
|
||||
/>
|
||||
<Route path="/accounts/register" element={<RegisterPage />} />
|
||||
<Route path="/accounts/verify-email/:token" element={<VerifyEmail />} />
|
||||
{/* 👇️ only match this when no other routes match */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const Footer: () => JSX.Element = () => {
|
||||
return (
|
||||
<div className="footer">
|
||||
<p>
|
||||
<Link to="/">© OneUptime</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Link to="/">Contact</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Link to="/">Privacy & terms</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="footer">
|
||||
<p>
|
||||
<Link to="/">© OneUptime</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Link to="/">Contact</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Link to="/">Privacy & terms</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import Telemetry from 'CommonUI/src/Utils/Telemetry';
|
||||
import App from "./App";
|
||||
import Telemetry from "CommonUI/src/Utils/Telemetry";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
Telemetry.init({
|
||||
serviceName: 'Accounts',
|
||||
serviceName: "Accounts",
|
||||
});
|
||||
|
||||
const root: any = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
document.getElementById("root") as HTMLElement,
|
||||
);
|
||||
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>,
|
||||
);
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
import React, { useState } from 'react';
|
||||
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import User from 'Model/Models/User';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import { FORGOT_PASSWORD_API_URL } from '../Utils/ApiPaths';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { FORGOT_PASSWORD_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import User from "Model/Models/User";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const ForgotPassword: () => JSX.Element = () => {
|
||||
const apiUrl: URL = FORGOT_PASSWORD_API_URL;
|
||||
const apiUrl: URL = FORGOT_PASSWORD_API_URL;
|
||||
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Forgot your password
|
||||
</h2>
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Forgot your password
|
||||
</h2>
|
||||
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your email and the password reset link will
|
||||
be sent to you.
|
||||
</p>
|
||||
)}
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your email and the password reset link will be sent to
|
||||
you.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
We have emailed you the password reset link. Please do
|
||||
not forget to check spam.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
We have emailed you the password reset link. Please do not forget to
|
||||
check spam.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
name="Forgot Password"
|
||||
id="login-form"
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
onSuccess={() => {
|
||||
setIsSuccess(true);
|
||||
}}
|
||||
submitButtonText={'Send Password Reset Link'}
|
||||
formType={FormType.Create}
|
||||
maxPrimaryButtonWidth={true}
|
||||
footer={
|
||||
<div className="actions pointer text-center mt-4 hover:underline fw-semibold">
|
||||
<p>
|
||||
<Link
|
||||
to={new Route('/accounts/login')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
|
||||
>
|
||||
Return to Sign in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Remember your password?{' '}
|
||||
<Link
|
||||
to={new Route('/accounts/login')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
name="Forgot Password"
|
||||
id="login-form"
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: "Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
onSuccess={() => {
|
||||
setIsSuccess(true);
|
||||
}}
|
||||
submitButtonText={"Send Password Reset Link"}
|
||||
formType={FormType.Create}
|
||||
maxPrimaryButtonWidth={true}
|
||||
footer={
|
||||
<div className="actions pointer text-center mt-4 hover:underline fw-semibold">
|
||||
<p>
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
|
||||
>
|
||||
Return to Sign in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Remember your password?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPassword;
|
||||
|
||||
@@ -1,167 +1,161 @@
|
||||
import React, { useState } from 'react';
|
||||
import User from 'Model/Models/User';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import { LOGIN_API_URL } from '../Utils/ApiPaths';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import LoginUtil from 'CommonUI/src/Utils/Login';
|
||||
import UserUtil from 'CommonUI/src/Utils/User';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import { DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert';
|
||||
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
|
||||
import useAsyncEffect from 'use-async-effect';
|
||||
import { LOGIN_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import Alert, { AlertType } from "CommonUI/src/Components/Alerts/Alert";
|
||||
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import { DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import UiAnalytics from "CommonUI/src/Utils/Analytics";
|
||||
import LoginUtil from "CommonUI/src/Utils/Login";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import UserUtil from "CommonUI/src/Utils/User";
|
||||
import User from "Model/Models/User";
|
||||
import React, { useState } from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = LOGIN_API_URL;
|
||||
const apiUrl: URL = LOGIN_API_URL;
|
||||
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}
|
||||
|
||||
const showSsoMessage: boolean = Boolean(
|
||||
Navigation.getQueryStringByName("sso"),
|
||||
);
|
||||
|
||||
const [showSsoTip, setShowSSOTip] = useState<boolean>(false);
|
||||
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (Navigation.getQueryStringByName("email")) {
|
||||
setInitialValues({
|
||||
email: Navigation.getQueryStringByName("email"),
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const showSsoMessage: boolean = Boolean(
|
||||
Navigation.getQueryStringByName('sso')
|
||||
);
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Join thousands of business that use OneUptime to help them stay online
|
||||
all the time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
const [showSsoTip, setShowSSOTip] = useState<boolean>(false);
|
||||
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (Navigation.getQueryStringByName('email')) {
|
||||
setInitialValues({
|
||||
email: Navigation.getQueryStringByName('email'),
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Join thousands of business that use OneUptime to help them
|
||||
stay online all the time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{showSsoMessage && (
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md mt-8">
|
||||
{' '}
|
||||
<Alert
|
||||
type={AlertType.DANGER}
|
||||
title="You must be logged into OneUptime account to use single sign-on (SSO) for your project. Logging in to OneUptime account and single sign on (SSO) for your project are two separate steps. Please use the form below to log in to your OneUptime account before you use SSO."
|
||||
/>{' '}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="login-form"
|
||||
name="Login"
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: 'jeff@example.com',
|
||||
required: true,
|
||||
disabled: Boolean(
|
||||
initialValues && initialValues['email']
|
||||
),
|
||||
title: 'Email',
|
||||
dataTestId: 'email',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: 'Password',
|
||||
required: true,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
sideLink: {
|
||||
text: 'Forgot password?',
|
||||
url: new Route('/accounts/forgot-password'),
|
||||
openLinkInNewTab: false,
|
||||
},
|
||||
dataTestId: 'password',
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={'Login'}
|
||||
onSuccess={(
|
||||
value: User,
|
||||
miscData: JSONObject | undefined
|
||||
) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture('accounts/login');
|
||||
}
|
||||
|
||||
LoginUtil.login({
|
||||
user: value,
|
||||
token: miscData ? miscData['token'] : undefined,
|
||||
});
|
||||
}}
|
||||
maxPrimaryButtonWidth={true}
|
||||
footer={
|
||||
<div className="actions text-center mt-4 hover:underline fw-semibold">
|
||||
<div>
|
||||
{!showSsoTip && (
|
||||
<div
|
||||
onClick={() => {
|
||||
setShowSSOTip(true);
|
||||
}}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
|
||||
>
|
||||
Use single sign-on (SSO) instead
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSsoTip && (
|
||||
<div className="text-gray-500 text-sm">
|
||||
Please sign in with your SSO
|
||||
provider like Okta, Auth0, Entra ID
|
||||
or any other SAML 2.0 provider.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{' '}
|
||||
<Link
|
||||
to={new Route('/accounts/register')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Register.
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showSsoMessage && (
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md mt-8">
|
||||
{" "}
|
||||
<Alert
|
||||
type={AlertType.DANGER}
|
||||
title="You must be logged into OneUptime account to use single sign-on (SSO) for your project. Logging in to OneUptime account and single sign on (SSO) for your project are two separate steps. Please use the form below to log in to your OneUptime account before you use SSO."
|
||||
/>{" "}
|
||||
</div>
|
||||
);
|
||||
)}
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="login-form"
|
||||
name="Login"
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues["email"]),
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: "Password",
|
||||
required: true,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
sideLink: {
|
||||
text: "Forgot password?",
|
||||
url: new Route("/accounts/forgot-password"),
|
||||
openLinkInNewTab: false,
|
||||
},
|
||||
dataTestId: "password",
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Login"}
|
||||
onSuccess={(value: User, miscData: JSONObject | undefined) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture("accounts/login");
|
||||
}
|
||||
|
||||
LoginUtil.login({
|
||||
user: value,
|
||||
token: miscData ? miscData["token"] : undefined,
|
||||
});
|
||||
}}
|
||||
maxPrimaryButtonWidth={true}
|
||||
footer={
|
||||
<div className="actions text-center mt-4 hover:underline fw-semibold">
|
||||
<div>
|
||||
{!showSsoTip && (
|
||||
<div
|
||||
onClick={() => {
|
||||
setShowSSOTip(true);
|
||||
}}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
|
||||
>
|
||||
Use single sign-on (SSO) instead
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSsoTip && (
|
||||
<div className="text-gray-500 text-sm">
|
||||
Please sign in with your SSO provider like Okta, Auth0,
|
||||
Entra ID or any other SAML 2.0 provider.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/register")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Register.
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Page not found
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Page you are looking for does not exist.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Page not found
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Page you are looking for does not exist.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
|
||||
@@ -1,276 +1,267 @@
|
||||
import React, { useState } from 'react';
|
||||
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import User from 'Model/Models/User';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import LoginUtil from 'CommonUI/src/Utils/Login';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import UserUtil from 'CommonUI/src/Utils/User';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import { BILLING_ENABLED, DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { SIGNUP_API_URL } from '../Utils/ApiPaths';
|
||||
import Fields from 'CommonUI/src/Components/Forms/Types/Fields';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
|
||||
import LocalStorage from 'CommonUI/src/Utils/LocalStorage';
|
||||
import Reseller from 'Model/Models/Reseller';
|
||||
import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import BaseAPI from 'CommonUI/src/Utils/API/API';
|
||||
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import useAsyncEffect from 'use-async-effect';
|
||||
import { SIGNUP_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
|
||||
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import Fields from "CommonUI/src/Components/Forms/Types/Fields";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import { BILLING_ENABLED, DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import BaseAPI from "CommonUI/src/Utils/API/API";
|
||||
import UiAnalytics from "CommonUI/src/Utils/Analytics";
|
||||
import LocalStorage from "CommonUI/src/Utils/LocalStorage";
|
||||
import LoginUtil from "CommonUI/src/Utils/Login";
|
||||
import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import UserUtil from "CommonUI/src/Utils/User";
|
||||
import Reseller from "Model/Models/Reseller";
|
||||
import User from "Model/Models/User";
|
||||
import React, { useState } from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
|
||||
const RegisterPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = SIGNUP_API_URL;
|
||||
const apiUrl: URL = SIGNUP_API_URL;
|
||||
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
|
||||
const [error, setError] = useState<string>('');
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||
|
||||
const [reseller, setResller] = React.useState<Reseller | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [reseller, setResller] = React.useState<Reseller | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}
|
||||
|
||||
type FetchResellerFunction = (resellerId: string) => Promise<void>;
|
||||
|
||||
const fetchReseller: FetchResellerFunction = async (
|
||||
resellerId: string,
|
||||
): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const reseller: ListResult<Reseller> = await ModelAPI.getList<Reseller>({
|
||||
modelType: Reseller,
|
||||
query: {
|
||||
resellerId: resellerId,
|
||||
},
|
||||
limit: 1,
|
||||
skip: 0,
|
||||
select: {
|
||||
hidePhoneNumberOnSignup: true,
|
||||
},
|
||||
sort: {},
|
||||
requestOptions: {},
|
||||
});
|
||||
|
||||
if (reseller.data.length > 0) {
|
||||
setResller(reseller.data[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(BaseAPI.getFriendlyMessage(err));
|
||||
}
|
||||
|
||||
type FetchResellerFunction = (resellerId: string) => Promise<void>;
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const fetchReseller: FetchResellerFunction = async (
|
||||
resellerId: string
|
||||
): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const reseller: ListResult<Reseller> =
|
||||
await ModelAPI.getList<Reseller>({
|
||||
modelType: Reseller,
|
||||
query: {
|
||||
resellerId: resellerId,
|
||||
},
|
||||
limit: 1,
|
||||
skip: 0,
|
||||
select: {
|
||||
hidePhoneNumberOnSignup: true,
|
||||
},
|
||||
sort: {},
|
||||
requestOptions: {},
|
||||
});
|
||||
|
||||
if (reseller.data.length > 0) {
|
||||
setResller(reseller.data[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(BaseAPI.getFriendlyMessage(err));
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
// if promo code is found, please save it in localstorage.
|
||||
if (Navigation.getQueryStringByName('promoCode')) {
|
||||
LocalStorage.setItem(
|
||||
'promoCode',
|
||||
Navigation.getQueryStringByName('promoCode')
|
||||
);
|
||||
}
|
||||
|
||||
if (Navigation.getQueryStringByName('email')) {
|
||||
setInitialValues({
|
||||
email: Navigation.getQueryStringByName('email'),
|
||||
});
|
||||
}
|
||||
|
||||
// if promo code is found, please save it in localstorage.
|
||||
if (Navigation.getQueryStringByName('partnerId')) {
|
||||
await fetchReseller(Navigation.getQueryStringByName('partnerId')!);
|
||||
}
|
||||
}, []);
|
||||
|
||||
let formFields: Fields<User> = [
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: 'jeff@example.com',
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues['email']),
|
||||
title: 'Email',
|
||||
dataTestId: 'email',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'Jeff Smith',
|
||||
required: true,
|
||||
title: 'Full Name',
|
||||
dataTestId: 'name',
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
field: {
|
||||
companyName: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'Acme, Inc.',
|
||||
required: true,
|
||||
title: 'Company Name',
|
||||
dataTestId: 'companyName',
|
||||
},
|
||||
]);
|
||||
|
||||
// If reseller wants to hide phone number on sign up, we hide it.
|
||||
if (!reseller || !reseller.hidePhoneNumberOnSignup) {
|
||||
formFields.push({
|
||||
field: {
|
||||
companyPhoneNumber: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: true,
|
||||
placeholder: '+11234567890',
|
||||
title: 'Phone Number',
|
||||
dataTestId: 'companyPhoneNumber',
|
||||
});
|
||||
}
|
||||
useAsyncEffect(async () => {
|
||||
// if promo code is found, please save it in localstorage.
|
||||
if (Navigation.getQueryStringByName("promoCode")) {
|
||||
LocalStorage.setItem(
|
||||
"promoCode",
|
||||
Navigation.getQueryStringByName("promoCode"),
|
||||
);
|
||||
}
|
||||
|
||||
if (Navigation.getQueryStringByName("email")) {
|
||||
setInitialValues({
|
||||
email: Navigation.getQueryStringByName("email"),
|
||||
});
|
||||
}
|
||||
|
||||
// if promo code is found, please save it in localstorage.
|
||||
if (Navigation.getQueryStringByName("partnerId")) {
|
||||
await fetchReseller(Navigation.getQueryStringByName("partnerId")!);
|
||||
}
|
||||
}, []);
|
||||
|
||||
let formFields: Fields<User> = [
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues["email"]),
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Jeff Smith",
|
||||
required: true,
|
||||
title: "Full Name",
|
||||
dataTestId: "name",
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
placeholder: 'Password',
|
||||
title: 'Password',
|
||||
required: true,
|
||||
dataTestId: 'password',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
confirmPassword: true,
|
||||
} as any,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
toMatchField: 'password',
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
placeholder: 'Confirm Password',
|
||||
title: 'Confirm Password',
|
||||
overrideFieldKey: 'confirmPassword',
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
dataTestId: 'confirmPassword',
|
||||
{
|
||||
field: {
|
||||
companyName: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Acme, Inc.",
|
||||
required: true,
|
||||
title: "Company Name",
|
||||
dataTestId: "companyName",
|
||||
},
|
||||
]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage error={error} />;
|
||||
// If reseller wants to hide phone number on sign up, we hide it.
|
||||
if (!reseller || !reseller.hidePhoneNumberOnSignup) {
|
||||
formFields.push({
|
||||
field: {
|
||||
companyPhoneNumber: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: true,
|
||||
placeholder: "+11234567890",
|
||||
title: "Phone Number",
|
||||
dataTestId: "companyPhoneNumber",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
placeholder: "Password",
|
||||
title: "Password",
|
||||
required: true,
|
||||
dataTestId: "password",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
confirmPassword: true,
|
||||
} as any,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
toMatchField: "password",
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
placeholder: "Confirm Password",
|
||||
title: "Confirm Password",
|
||||
overrideFieldKey: "confirmPassword",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
dataTestId: "confirmPassword",
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Create your OneUptime account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Join thousands of business that use OneUptime to help them
|
||||
stay online all the time.
|
||||
</p>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
No credit card required.
|
||||
</p>
|
||||
</div>
|
||||
if (error) {
|
||||
return <ErrorMessage error={error} />;
|
||||
}
|
||||
|
||||
<div className="mt-8 lg:mx-auto lg:w-full lg:max-w-2xl">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
showAsColumns={reseller ? 1 : 2}
|
||||
name="Register"
|
||||
initialValues={initialValues}
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={formFields}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
const utmParams: Dictionary<string> =
|
||||
UserUtil.getUtmParams();
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
if (
|
||||
utmParams &&
|
||||
Object.keys(utmParams).length > 0
|
||||
) {
|
||||
item.utmSource = utmParams['utmSource'] || '';
|
||||
item.utmMedium = utmParams['utmMedium'] || '';
|
||||
item.utmCampaign =
|
||||
utmParams['utmCampaign'] || '';
|
||||
item.utmTerm = utmParams['utmTerm'] || '';
|
||||
item.utmContent = utmParams['utmContent'] || '';
|
||||
item.utmUrl = utmParams['utmUrl'] || '';
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Create your OneUptime account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Join thousands of business that use OneUptime to help them stay online
|
||||
all the time.
|
||||
</p>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
No credit card required.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
UiAnalytics.capture('utm_event', utmParams);
|
||||
}
|
||||
<div className="mt-8 lg:mx-auto lg:w-full lg:max-w-2xl">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
showAsColumns={reseller ? 1 : 2}
|
||||
name="Register"
|
||||
initialValues={initialValues}
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={formFields}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
const utmParams: Dictionary<string> = UserUtil.getUtmParams();
|
||||
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={'Sign Up'}
|
||||
onSuccess={(
|
||||
value: User,
|
||||
miscData: JSONObject | undefined
|
||||
) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture('accounts/register');
|
||||
}
|
||||
if (utmParams && Object.keys(utmParams).length > 0) {
|
||||
item.utmSource = utmParams["utmSource"] || "";
|
||||
item.utmMedium = utmParams["utmMedium"] || "";
|
||||
item.utmCampaign = utmParams["utmCampaign"] || "";
|
||||
item.utmTerm = utmParams["utmTerm"] || "";
|
||||
item.utmContent = utmParams["utmContent"] || "";
|
||||
item.utmUrl = utmParams["utmUrl"] || "";
|
||||
|
||||
LoginUtil.login({
|
||||
user: value,
|
||||
token: miscData ? miscData['token'] : undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5 text-center text-gray-500">
|
||||
<p className="text-muted mb-0">
|
||||
Already have an account?{' '}
|
||||
<Link
|
||||
to={new Route('/accounts/login')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Log in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
UiAnalytics.capture("utm_event", utmParams);
|
||||
}
|
||||
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Sign Up"}
|
||||
onSuccess={(value: User, miscData: JSONObject | undefined) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture("accounts/register");
|
||||
}
|
||||
|
||||
LoginUtil.login({
|
||||
user: value,
|
||||
token: miscData ? miscData["token"] : undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
<div className="mt-5 text-center text-gray-500">
|
||||
<p className="text-muted mb-0">
|
||||
Already have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Log in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterPage;
|
||||
|
||||
@@ -1,114 +1,114 @@
|
||||
import React, { useState } from 'react';
|
||||
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import User from 'Model/Models/User';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { RESET_PASSWORD_API_URL } from '../Utils/ApiPaths';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import { RESET_PASSWORD_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import User from "Model/Models/User";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const RegisterPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = RESET_PASSWORD_API_URL;
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
const apiUrl: URL = RESET_PASSWORD_API_URL;
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Reset your password
|
||||
</h2>
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Reset your password
|
||||
</h2>
|
||||
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your new password and we will have it
|
||||
updated.{' '}
|
||||
</p>
|
||||
)}
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your new password and we will have it updated.{" "}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Your password has been updated. Please log in.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Your password has been updated. Please log in.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
name="Reset Password"
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
item.resetPasswordToken =
|
||||
Navigation.getLastParam()
|
||||
?.toString()
|
||||
.replace('/', '')
|
||||
.toString() || '';
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
showAsColumns={1}
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
placeholder: 'New Password',
|
||||
title: 'New Password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 6,
|
||||
toMatchField: 'password',
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
placeholder: 'Confirm Password',
|
||||
title: 'Confirm Password',
|
||||
overrideFieldKey: 'confirmPassword',
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={'Reset Password'}
|
||||
onSuccess={() => {
|
||||
setIsSuccess(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
name="Reset Password"
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
item.resetPasswordToken =
|
||||
Navigation.getLastParam()
|
||||
?.toString()
|
||||
.replace("/", "")
|
||||
.toString() || "";
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
showAsColumns={1}
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
placeholder: "New Password",
|
||||
title: "New Password",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
confirmPassword: true,
|
||||
} as any,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
toMatchField: "password",
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
placeholder: "Confirm Password",
|
||||
title: "Confirm Password",
|
||||
overrideFieldKey: "confirmPassword",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Reset Password"}
|
||||
onSuccess={() => {
|
||||
setIsSuccess(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Know your password?{' '}
|
||||
<Link
|
||||
to={new Route('/accounts/login')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Log in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Know your password?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Log in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterPage;
|
||||
|
||||
@@ -1,132 +1,121 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import EmailVerificationToken from 'Model/Models/EmailVerificationToken';
|
||||
import { VERIFY_EMAIL_API_URL } from '../Utils/ApiPaths';
|
||||
import { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import API from 'CommonUI/src/Utils/API/API';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import { VERIFY_EMAIL_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import API from "CommonUI/src/Utils/API/API";
|
||||
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import EmailVerificationToken from "Model/Models/EmailVerificationToken";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const VerifyEmail: () => JSX.Element = () => {
|
||||
const apiUrl: URL = VERIFY_EMAIL_API_URL;
|
||||
const [error, setError] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const apiUrl: URL = VERIFY_EMAIL_API_URL;
|
||||
const [error, setError] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
// Ping an API here.
|
||||
setError('');
|
||||
setIsLoading(true);
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
// Ping an API here.
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// strip data.
|
||||
const emailverificationToken: EmailVerificationToken =
|
||||
new EmailVerificationToken();
|
||||
emailverificationToken.token = new ObjectID(
|
||||
Navigation.getLastParam()?.toString().replace('/', '') || ''
|
||||
);
|
||||
try {
|
||||
// strip data.
|
||||
const emailverificationToken: EmailVerificationToken =
|
||||
new EmailVerificationToken();
|
||||
emailverificationToken.token = new ObjectID(
|
||||
Navigation.getLastParam()?.toString().replace("/", "") || "",
|
||||
);
|
||||
|
||||
await ModelAPI.createOrUpdate<EmailVerificationToken>({
|
||||
model: emailverificationToken,
|
||||
modelType: EmailVerificationToken,
|
||||
formType: FormType.Create,
|
||||
miscDataProps: {},
|
||||
requestOptions: {
|
||||
overrideRequestUrl: apiUrl,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyMessage(err));
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
init().catch((err: Error) => {
|
||||
setError(err.toString());
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
await ModelAPI.createOrUpdate<EmailVerificationToken>({
|
||||
model: emailverificationToken,
|
||||
modelType: EmailVerificationToken,
|
||||
formType: FormType.Create,
|
||||
miscDataProps: {},
|
||||
requestOptions: {
|
||||
overrideRequestUrl: apiUrl,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyMessage(err));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="container-fluid p-0">
|
||||
<div className="row g-0">
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-6">
|
||||
<div className="auth-full-page-content d-flex p-sm-5 p-4">
|
||||
<div className="w-100">
|
||||
<div className="d-flex flex-column h-100">
|
||||
<div className="auth-content my-auto">
|
||||
<div
|
||||
className="mt-4 text-center flex justify-center"
|
||||
style={{ marginBottom: '40px' }}
|
||||
>
|
||||
<img
|
||||
style={{ height: '50px' }}
|
||||
src={`${OneUptimeLogo}`}
|
||||
/>
|
||||
</div>
|
||||
{!error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">
|
||||
Your email is verified.
|
||||
</h5>
|
||||
<p className="text-muted mt-2 mb-0">
|
||||
Thank you for verifying your
|
||||
email. You can now log in to
|
||||
OneUptime.{' '}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
useEffect(() => {
|
||||
init().catch((err: Error) => {
|
||||
setError(err.toString());
|
||||
});
|
||||
}, []);
|
||||
|
||||
{error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">
|
||||
Sorry, something went wrong!
|
||||
</h5>
|
||||
<p className="text-muted mt-2 mb-0">
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0">
|
||||
Return to sign in?{' '}
|
||||
<Link
|
||||
to={
|
||||
new Route(
|
||||
'/accounts/login'
|
||||
)
|
||||
}
|
||||
className="hover:underline text-primary fw-semibold"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="container-fluid p-0">
|
||||
<div className="row g-0">
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-6">
|
||||
<div className="auth-full-page-content d-flex p-sm-5 p-4">
|
||||
<div className="w-100">
|
||||
<div className="d-flex flex-column h-100">
|
||||
<div className="auth-content my-auto">
|
||||
<div
|
||||
className="mt-4 text-center flex justify-center"
|
||||
style={{ marginBottom: "40px" }}
|
||||
>
|
||||
<img
|
||||
style={{ height: "50px" }}
|
||||
src={`${OneUptimeLogo}`}
|
||||
/>
|
||||
</div>
|
||||
{!error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">Your email is verified.</h5>
|
||||
<p className="text-muted mt-2 mb-0">
|
||||
Thank you for verifying your email. You can now log in
|
||||
to OneUptime.{" "}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
{error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">Sorry, something went wrong!</h5>
|
||||
<p className="text-muted mt-2 mb-0">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0">
|
||||
Return to sign in?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="hover:underline text-primary fw-semibold"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyEmail;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { IDENTITY_URL } from 'CommonUI/src/Config';
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { IDENTITY_URL } from "CommonUI/src/Config";
|
||||
|
||||
export const SIGNUP_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/signup')
|
||||
new Route("/signup"),
|
||||
);
|
||||
export const LOGIN_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/login')
|
||||
new Route("/login"),
|
||||
);
|
||||
|
||||
export const FORGOT_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/forgot-password')
|
||||
new Route("/forgot-password"),
|
||||
);
|
||||
|
||||
export const VERIFY_EMAIL_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/verify-email')
|
||||
new Route("/verify-email"),
|
||||
);
|
||||
|
||||
export const RESET_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/reset-password')
|
||||
new Route("/reset-password"),
|
||||
);
|
||||
|
||||
@@ -1,83 +1,84 @@
|
||||
require('ts-loader');
|
||||
require('file-loader');
|
||||
require('style-loader');
|
||||
require('css-loader');
|
||||
require('sass-loader');
|
||||
require("ts-loader");
|
||||
require("file-loader");
|
||||
require("style-loader");
|
||||
require("css-loader");
|
||||
require("sass-loader");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const dotenv = require('dotenv');
|
||||
const express = require('express');
|
||||
const dotenv = require("dotenv");
|
||||
const express = require("express");
|
||||
|
||||
const readEnvFile = (pathToFile) => {
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
const env = {};
|
||||
|
||||
const env = {
|
||||
};
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
return env;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/accounts/dist/",
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/accounts/dist/",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
|
||||
alias: {
|
||||
react: path.resolve("./node_modules/react"),
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'],
|
||||
alias: {
|
||||
react: path.resolve('./node_modules/react'),
|
||||
}
|
||||
},
|
||||
externals: {
|
||||
'react-native-sqlite-storage': 'react-native-sqlite-storage'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process': {
|
||||
'env': {
|
||||
...readEnvFile('/usr/src/app/dev-env/.env')
|
||||
}
|
||||
}
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: 'ts-loader'
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ['style-loader', 'css-loader', "sass-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: 'file-loader'
|
||||
}
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
externals: {
|
||||
"react-native-sqlite-storage": "react-native-sqlite-storage",
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
process: {
|
||||
env: {
|
||||
...readEnvFile("/usr/src/app/dev-env/.env"),
|
||||
},
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use('/accounts/assets', express.static(path.resolve(__dirname, 'public', 'assets')));
|
||||
return middlewares;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: "ts-loader",
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ["style-loader", "css-loader", "sass-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: "file-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
devtool: 'eval-source-map',
|
||||
}
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use(
|
||||
"/accounts/assets",
|
||||
express.static(path.resolve(__dirname, "public", "assets")),
|
||||
);
|
||||
return middlewares;
|
||||
},
|
||||
},
|
||||
devtool: "eval-source-map",
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM node:21.7.2-alpine3.18
|
||||
FROM node:21.7.3-alpine3.18
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import App from 'CommonServer/Utils/StartServer';
|
||||
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import App from "CommonServer/Utils/StartServer";
|
||||
|
||||
export const APP_NAME: string = 'admin';
|
||||
export const APP_NAME: string = "admin";
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
try {
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error('App Init Failed:');
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error("App Init Failed:");
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
init().catch((err: Error) => {
|
||||
logger.error(err);
|
||||
logger.info('Exiting node process');
|
||||
process.exit(1);
|
||||
logger.error(err);
|
||||
logger.error("Exiting node process");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
8
AdminDashboard/index.d.ts
vendored
8
AdminDashboard/index.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
declare module '*.png';
|
||||
declare module '*.svg';
|
||||
declare module '*.jpg';
|
||||
declare module '*.gif';
|
||||
declare module "*.png";
|
||||
declare module "*.svg";
|
||||
declare module "*.jpg";
|
||||
declare module "*.gif";
|
||||
|
||||
164
AdminDashboard/package-lock.json
generated
164
AdminDashboard/package-lock.json
generated
@@ -14,9 +14,9 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"file-loader": "^6.2.0",
|
||||
"Model": "file:../Model",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-router-dom": "^6.22.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"style-loader": "^3.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,21 +35,21 @@
|
||||
}
|
||||
},
|
||||
"../Common": {
|
||||
"name": "common",
|
||||
"name": "@oneuptime/common",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"axios": "^1.6.2",
|
||||
"axios": "^1.6.8",
|
||||
"crypto-js": "^4.1.1",
|
||||
"json5": "^2.2.3",
|
||||
"moment": "^2.29.2",
|
||||
"moment-timezone": "^0.5.40",
|
||||
"posthog-js": "^1.77.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"posthog-js": "^1.130.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"slugify": "^1.6.5",
|
||||
"typeorm": "^0.3.6",
|
||||
"typeorm": "^0.3.20",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -61,40 +61,47 @@
|
||||
}
|
||||
},
|
||||
"../CommonServer": {
|
||||
"name": "common-server",
|
||||
"name": "@oneuptime/common-server",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@clickhouse/client": "^0.2.1",
|
||||
"@elastic/elasticsearch": "^8.1.0",
|
||||
"@clickhouse/client": "^0.2.10",
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@opentelemetry/api": "^1.7.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.40.1",
|
||||
"@opentelemetry/sdk-node": "^0.45.1",
|
||||
"@socket.io/redis-adapter": "^8.2.1",
|
||||
"@opentelemetry/api-logs": "^0.49.1",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.43.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.49.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.49.1",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.49.1",
|
||||
"@opentelemetry/id-generator-aws-xray": "^1.2.1",
|
||||
"@opentelemetry/sdk-logs": "^0.49.1",
|
||||
"@opentelemetry/sdk-metrics": "^1.21.0",
|
||||
"@opentelemetry/sdk-node": "^0.48.0",
|
||||
"@opentelemetry/sdk-trace-node": "^1.21.0",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"axios": "^1.6.2",
|
||||
"bullmq": "^3.6.6",
|
||||
"bullmq": "^5.3.3",
|
||||
"Common": "file:../Common",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"dotenv": "^16.0.0",
|
||||
"ejs": "^3.1.8",
|
||||
"express": "^4.17.3",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2",
|
||||
"ioredis": "^5.3.2",
|
||||
"json2csv": "^5.0.7",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"markdown-it": "^13.0.1",
|
||||
"marked": "^12.0.2",
|
||||
"Model": "file:../Model",
|
||||
"nodemailer": "^6.7.3",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^6.9.10",
|
||||
"pg": "^8.7.3",
|
||||
"socket.io": "^4.7.2",
|
||||
"redis-semaphore": "^5.5.1",
|
||||
"socket.io": "^4.7.4",
|
||||
"stripe": "^10.17.0",
|
||||
"twilio": "^4.19.3",
|
||||
"typeorm": "^0.3.10",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"vm2": "^3.9.14",
|
||||
"winston": "^3.6.0"
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm-extension": "^2.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^6.3.1",
|
||||
@@ -105,8 +112,8 @@
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/json2csv": "^5.0.3",
|
||||
"@types/jsonwebtoken": "^8.5.9",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/node": "^17.0.22",
|
||||
"@types/node-cron": "^3.0.7",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
"jest": "^27.5.1",
|
||||
"jest-mock-extended": "^3.0.5",
|
||||
@@ -114,34 +121,48 @@
|
||||
}
|
||||
},
|
||||
"../CommonUI": {
|
||||
"name": "common-ui",
|
||||
"name": "@oneuptime/common-ui",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.1",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@nivo/core": "^0.86.0",
|
||||
"@nivo/line": "^0.86.0",
|
||||
"@opentelemetry/api": "^1.8.0",
|
||||
"@opentelemetry/context-zone": "^1.23.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.51.0",
|
||||
"@opentelemetry/instrumentation": "^0.51.0",
|
||||
"@opentelemetry/instrumentation-fetch": "^0.51.0",
|
||||
"@opentelemetry/instrumentation-xml-http-request": "^0.51.0",
|
||||
"@opentelemetry/resources": "^1.23.0",
|
||||
"@opentelemetry/sdk-trace-web": "^1.23.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.23.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"Common": "file:../Common",
|
||||
"formik": "^2.2.9",
|
||||
"formik": "^2.4.6",
|
||||
"history": "^5.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"Model": "file:../Model",
|
||||
"moment-timezone": "^0.5.43",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-big-calendar": "^1.8.5",
|
||||
"react-big-calendar": "^1.11.2",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.2.2",
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.13.6",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reactflow": "^11.5.3",
|
||||
"reactflow": "^11.11.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"tippy.js": "^6.3.7",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"use-async-effect": "^2.2.6"
|
||||
@@ -167,12 +188,12 @@
|
||||
}
|
||||
},
|
||||
"../Model": {
|
||||
"name": "model",
|
||||
"name": "@oneuptime/model",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
"typeorm": "^0.3.17"
|
||||
"typeorm": "^0.3.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.4.1",
|
||||
@@ -4009,9 +4030,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz",
|
||||
"integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==",
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
||||
"integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
@@ -16705,9 +16727,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -16900,15 +16922,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.0"
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-error-overlay": {
|
||||
@@ -16936,11 +16958,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.22.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz",
|
||||
"integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==",
|
||||
"version": "6.23.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
|
||||
"integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.15.2"
|
||||
"@remix-run/router": "1.16.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -16950,12 +16973,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.22.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz",
|
||||
"integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==",
|
||||
"version": "6.23.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz",
|
||||
"integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.15.2",
|
||||
"react-router": "6.22.2"
|
||||
"@remix-run/router": "1.16.1",
|
||||
"react-router": "6.23.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -17681,9 +17705,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
||||
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"file-loader": "^6.2.0",
|
||||
"Model": "file:../Model",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"style-loader": "^3.3.4"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,113 +1,103 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Routes,
|
||||
Route as PageRoute,
|
||||
useNavigate,
|
||||
useLocation,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import User from 'CommonUI/src/Utils/User';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { ACCOUNTS_URL, DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import MasterPage from './Components/MasterPage/MasterPage';
|
||||
import RouteMap from './Utils/RouteMap';
|
||||
import PageMap from './Utils/PageMap';
|
||||
import Init from './Pages/Init/Init';
|
||||
import Projects from './Pages/Projects/Index';
|
||||
import Users from './Pages/Users/Index';
|
||||
import Logout from './Pages/Logout/Logout';
|
||||
|
||||
import MasterPage from "./Components/MasterPage/MasterPage";
|
||||
import Init from "./Pages/Init/Init";
|
||||
import Logout from "./Pages/Logout/Logout";
|
||||
import Projects from "./Pages/Projects/Index";
|
||||
import SettingsAPIKey from "./Pages/Settings/APIKey/Index";
|
||||
import SettingsAuthentication from "./Pages/Settings/Authentication/Index";
|
||||
import SettingsCallSMS from "./Pages/Settings/CallSMS/Index";
|
||||
// Settings Pages.
|
||||
import SettingsEmail from './Pages/Settings/Email/Index';
|
||||
import SettingsCallSMS from './Pages/Settings/CallSMS/Index';
|
||||
import SettingsProbes from './Pages/Settings/Probes/Index';
|
||||
import SettingsAuthentication from './Pages/Settings/Authentication/Index';
|
||||
import SettingsAPIKey from './Pages/Settings/APIKey/Index';
|
||||
import SettingsEmail from "./Pages/Settings/Email/Index";
|
||||
import SettingsProbes from "./Pages/Settings/Probes/Index";
|
||||
import Users from "./Pages/Users/Index";
|
||||
import PageMap from "./Utils/PageMap";
|
||||
import RouteMap from "./Utils/RouteMap";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { ACCOUNTS_URL, DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import User from "CommonUI/src/Utils/User";
|
||||
import React from "react";
|
||||
import {
|
||||
Route as PageRoute,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
|
||||
const App: () => JSX.Element = () => {
|
||||
Navigation.setNavigateHook(useNavigate());
|
||||
Navigation.setLocation(useLocation());
|
||||
Navigation.setParams(useParams());
|
||||
Navigation.setNavigateHook(useNavigate());
|
||||
Navigation.setLocation(useLocation());
|
||||
Navigation.setParams(useParams());
|
||||
|
||||
if (!User.isLoggedIn()) {
|
||||
if (Navigation.getQueryStringByName('sso_token')) {
|
||||
Navigation.navigate(
|
||||
URL.fromString(ACCOUNTS_URL.toString()).addQueryParam(
|
||||
'sso',
|
||||
'true'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
Navigation.navigate(URL.fromString(ACCOUNTS_URL.toString()));
|
||||
}
|
||||
if (!User.isLoggedIn()) {
|
||||
if (Navigation.getQueryStringByName("sso_token")) {
|
||||
Navigation.navigate(
|
||||
URL.fromString(ACCOUNTS_URL.toString()).addQueryParam("sso", "true"),
|
||||
);
|
||||
} else {
|
||||
Navigation.navigate(URL.fromString(ACCOUNTS_URL.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!User.isMasterAdmin()) {
|
||||
Navigation.navigate(URL.fromString(DASHBOARD_URL.toString()));
|
||||
}
|
||||
if (!User.isMasterAdmin()) {
|
||||
Navigation.navigate(URL.fromString(DASHBOARD_URL.toString()));
|
||||
}
|
||||
|
||||
return (
|
||||
<MasterPage>
|
||||
<Routes>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.INIT]?.toString() || ''}
|
||||
element={<Init />}
|
||||
/>
|
||||
return (
|
||||
<MasterPage>
|
||||
<Routes>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.INIT]?.toString() || ""}
|
||||
element={<Init />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.PROJECTS]?.toString() || ''}
|
||||
element={<Projects />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.PROJECTS]?.toString() || ""}
|
||||
element={<Projects />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.USERS]?.toString() || ''}
|
||||
element={<Users />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.USERS]?.toString() || ""}
|
||||
element={<Users />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.LOGOUT]?.toString() || ''}
|
||||
element={<Logout />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.LOGOUT]?.toString() || ""}
|
||||
element={<Logout />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS]?.toString() || ''}
|
||||
element={<SettingsAuthentication />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS]?.toString() || ""}
|
||||
element={<SettingsAuthentication />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_SMTP]?.toString() || ''}
|
||||
element={<SettingsEmail />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_SMTP]?.toString() || ""}
|
||||
element={<SettingsEmail />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS]?.toString() ||
|
||||
''
|
||||
}
|
||||
element={<SettingsCallSMS />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_CALL_AND_SMS]?.toString() || ""}
|
||||
element={<SettingsCallSMS />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_PROBES]?.toString() || ''}
|
||||
element={<SettingsProbes />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_PROBES]?.toString() || ""}
|
||||
element={<SettingsProbes />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION]?.toString() ||
|
||||
''
|
||||
}
|
||||
element={<SettingsAuthentication />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_AUTHENTICATION]?.toString() || ""}
|
||||
element={<SettingsAuthentication />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_API_KEY]?.toString() || ''}
|
||||
element={<SettingsAPIKey />}
|
||||
/>
|
||||
</Routes>
|
||||
</MasterPage>
|
||||
);
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_API_KEY]?.toString() || ""}
|
||||
element={<SettingsAPIKey />}
|
||||
/>
|
||||
</Routes>
|
||||
</MasterPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,122 +1,114 @@
|
||||
import React from 'react';
|
||||
import Footer from 'CommonUI/src/Components/Footer/Footer';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import API from 'Common/Utils/API';
|
||||
import { HOST, HTTP_PROTOCOL } from 'CommonUI/src/Config';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import HTTPResponse from 'Common/Types/API/HTTPResponse';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import API from "Common/Utils/API";
|
||||
import Footer from "CommonUI/src/Components/Footer/Footer";
|
||||
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
|
||||
import { HOST, HTTP_PROTOCOL } from "CommonUI/src/Config";
|
||||
import React from "react";
|
||||
|
||||
const DashboardFooter: () => JSX.Element = () => {
|
||||
const [showAboutModal, setShowAboutModal] = React.useState<boolean>(false);
|
||||
const [isAboutModalLoading, setIsAboutModalLoading] =
|
||||
React.useState<boolean>(false);
|
||||
const [versionText, setVersionText] = React.useState<Dictionary<string>>(
|
||||
{}
|
||||
const [showAboutModal, setShowAboutModal] = React.useState<boolean>(false);
|
||||
const [isAboutModalLoading, setIsAboutModalLoading] =
|
||||
React.useState<boolean>(false);
|
||||
const [versionText, setVersionText] = React.useState<Dictionary<string>>({});
|
||||
|
||||
const fetchVersions: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsAboutModalLoading(true);
|
||||
|
||||
try {
|
||||
const verText: Dictionary<string> = {};
|
||||
const apps: Array<{
|
||||
name: string;
|
||||
path: string;
|
||||
}> = [
|
||||
{
|
||||
name: "API",
|
||||
path: "/api",
|
||||
},
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: "/dashboard",
|
||||
},
|
||||
];
|
||||
|
||||
for (const app of apps) {
|
||||
const version: JSONObject = await fetchAppVersion(app.path);
|
||||
verText[app.name] =
|
||||
`${app.name}: ${version["version"]} (${version["commit"]})`;
|
||||
}
|
||||
|
||||
setVersionText(verText);
|
||||
} catch (err) {
|
||||
setVersionText({
|
||||
error: "Version data is not available: " + (err as Error).message,
|
||||
});
|
||||
}
|
||||
|
||||
setIsAboutModalLoading(false);
|
||||
};
|
||||
|
||||
const fetchAppVersion: (appName: string) => Promise<JSONObject> = async (
|
||||
appName: string,
|
||||
): Promise<JSONObject> => {
|
||||
const response: HTTPResponse<JSONObject> = await API.get<JSONObject>(
|
||||
URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`),
|
||||
);
|
||||
|
||||
const fetchVersions: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsAboutModalLoading(true);
|
||||
if (response.data) {
|
||||
return response.data as JSONObject;
|
||||
}
|
||||
throw new BadDataException("Version data is not available");
|
||||
};
|
||||
|
||||
try {
|
||||
const verText: Dictionary<string> = {};
|
||||
const apps: Array<{
|
||||
name: string;
|
||||
path: string;
|
||||
}> = [
|
||||
{
|
||||
name: 'API',
|
||||
path: '/api',
|
||||
},
|
||||
{
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Footer
|
||||
className="bg-white h-16 inset-x-0 bottom-0 px-8"
|
||||
copyright="HackerBay, Inc."
|
||||
links={[
|
||||
{
|
||||
title: "Help and Support",
|
||||
to: URL.fromString("https://oneuptime.com/support"),
|
||||
},
|
||||
{
|
||||
title: "Legal",
|
||||
to: URL.fromString("https://oneuptime.com/legal"),
|
||||
},
|
||||
{
|
||||
title: "Version",
|
||||
onClick: async () => {
|
||||
setShowAboutModal(true);
|
||||
await fetchVersions();
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
for (const app of apps) {
|
||||
const version: JSONObject = await fetchAppVersion(app.path);
|
||||
verText[
|
||||
app.name
|
||||
] = `${app.name}: ${version['version']} (${version['commit']})`;
|
||||
}
|
||||
|
||||
setVersionText(verText);
|
||||
} catch (err) {
|
||||
setVersionText({
|
||||
error:
|
||||
'Version data is not available: ' + (err as Error).message,
|
||||
});
|
||||
}
|
||||
|
||||
setIsAboutModalLoading(false);
|
||||
};
|
||||
|
||||
const fetchAppVersion: (appName: string) => Promise<JSONObject> = async (
|
||||
appName: string
|
||||
): Promise<JSONObject> => {
|
||||
const response: HTTPResponse<JSONObject> = await API.get<JSONObject>(
|
||||
URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`)
|
||||
);
|
||||
|
||||
if (response.data) {
|
||||
return response.data as JSONObject;
|
||||
}
|
||||
throw new BadDataException('Version data is not available');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Footer
|
||||
className="bg-white h-16 inset-x-0 bottom-0 px-8"
|
||||
copyright="HackerBay, Inc."
|
||||
links={[
|
||||
{
|
||||
title: 'Help and Support',
|
||||
to: URL.fromString('https://oneuptime.com/support'),
|
||||
},
|
||||
{
|
||||
title: 'Legal',
|
||||
to: URL.fromString('https://oneuptime.com/legal'),
|
||||
},
|
||||
{
|
||||
title: 'Version',
|
||||
onClick: async () => {
|
||||
setShowAboutModal(true);
|
||||
await fetchVersions();
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showAboutModal ? (
|
||||
<ConfirmModal
|
||||
title={`OneUptime Version`}
|
||||
description={
|
||||
<div>
|
||||
{Object.keys(versionText).map(
|
||||
(key: string, i: number) => {
|
||||
return (
|
||||
<div key={i}>{versionText[key]}</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
isLoading={isAboutModalLoading}
|
||||
submitButtonText={'Close'}
|
||||
onSubmit={() => {
|
||||
return setShowAboutModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
{showAboutModal ? (
|
||||
<ConfirmModal
|
||||
title={`OneUptime Version`}
|
||||
description={
|
||||
<div>
|
||||
{Object.keys(versionText).map((key: string, i: number) => {
|
||||
return <div key={i}>{versionText[key]}</div>;
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
isLoading={isAboutModalLoading}
|
||||
submitButtonText={"Close"}
|
||||
onSubmit={() => {
|
||||
return setShowAboutModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardFooter;
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import Help from './Help';
|
||||
import Header from 'CommonUI/src/Components/Header/Header';
|
||||
import Logo from './Logo';
|
||||
import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import { DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import UserProfile from './UserProfile';
|
||||
import Help from "./Help";
|
||||
import Logo from "./Logo";
|
||||
import UserProfile from "./UserProfile";
|
||||
import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
|
||||
import Header from "CommonUI/src/Components/Header/Header";
|
||||
import { DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const DashboardHeader: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
leftComponents={
|
||||
<>
|
||||
<Logo onClick={() => {}} />
|
||||
</>
|
||||
}
|
||||
centerComponents={
|
||||
<>
|
||||
{/* <SearchBox
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
leftComponents={
|
||||
<>
|
||||
<Logo onClick={() => {}} />
|
||||
</>
|
||||
}
|
||||
centerComponents={
|
||||
<>
|
||||
{/* <SearchBox
|
||||
key={2}
|
||||
selectedProject={props.selectedProject}
|
||||
onChange={(_value: string) => { }}
|
||||
/>{' '} */}
|
||||
</>
|
||||
}
|
||||
rightComponents={
|
||||
<>
|
||||
<Button
|
||||
title="Exit Admin"
|
||||
buttonStyle={ButtonStyleType.NORMAL}
|
||||
onClick={() => {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}}
|
||||
/>
|
||||
<Help />
|
||||
<UserProfile />
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
rightComponents={
|
||||
<>
|
||||
<Button
|
||||
title="Exit Admin"
|
||||
buttonStyle={ButtonStyleType.NORMAL}
|
||||
onClick={() => {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
<Help />
|
||||
<UserProfile />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardHeader;
|
||||
|
||||
@@ -1,60 +1,58 @@
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton';
|
||||
import IconDropdownItem from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownItem';
|
||||
import IconDropdownMenu from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu';
|
||||
import IconDropdownRow from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownRow';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import URL from "Common/Types/API/URL";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
|
||||
import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem";
|
||||
import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu";
|
||||
import IconDropdownRow from "CommonUI/src/Components/Header/IconDropdown/IconDropdownRow";
|
||||
import React, { ReactElement, useState } from "react";
|
||||
|
||||
const Help: () => JSX.Element = (): ReactElement => {
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<HeaderIconDropdownButton
|
||||
icon={IconProp.Help}
|
||||
name="Help"
|
||||
showDropdown={isDropdownVisible}
|
||||
return (
|
||||
<HeaderIconDropdownButton
|
||||
icon={IconProp.Help}
|
||||
name="Help"
|
||||
showDropdown={isDropdownVisible}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
>
|
||||
<IconDropdownMenu>
|
||||
<IconDropdownRow>
|
||||
<IconDropdownItem
|
||||
title="Support Email"
|
||||
icon={IconProp.Email}
|
||||
openInNewTab={true}
|
||||
url={URL.fromString("mailto:support@oneuptime.com")}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
>
|
||||
<IconDropdownMenu>
|
||||
<IconDropdownRow>
|
||||
<IconDropdownItem
|
||||
title="Support Email"
|
||||
icon={IconProp.Email}
|
||||
openInNewTab={true}
|
||||
url={URL.fromString('mailto:support@oneuptime.com')}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
/>
|
||||
<IconDropdownItem
|
||||
title="Chat on Slack"
|
||||
icon={IconProp.Slack}
|
||||
openInNewTab={true}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
url={URL.fromString(
|
||||
'https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ'
|
||||
)}
|
||||
/>
|
||||
<IconDropdownItem
|
||||
title="Request Demo"
|
||||
icon={IconProp.Window}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
openInNewTab={true}
|
||||
url={URL.fromString(
|
||||
'https://oneuptime.com/enterprise/demo'
|
||||
)}
|
||||
/>
|
||||
</IconDropdownRow>
|
||||
</IconDropdownMenu>
|
||||
</HeaderIconDropdownButton>
|
||||
);
|
||||
/>
|
||||
<IconDropdownItem
|
||||
title="Chat on Slack"
|
||||
icon={IconProp.Slack}
|
||||
openInNewTab={true}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
url={URL.fromString(
|
||||
"https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ",
|
||||
)}
|
||||
/>
|
||||
<IconDropdownItem
|
||||
title="Request Demo"
|
||||
icon={IconProp.Window}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
openInNewTab={true}
|
||||
url={URL.fromString("https://oneuptime.com/enterprise/demo")}
|
||||
/>
|
||||
</IconDropdownRow>
|
||||
</IconDropdownMenu>
|
||||
</HeaderIconDropdownButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default Help;
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
// Tailwind
|
||||
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import Image from 'CommonUI/src/Components/Image/Image';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import Route from "Common/Types/API/Route";
|
||||
import Image from "CommonUI/src/Components/Image/Image";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
onClick: () => void;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const Logo: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<div className="relative z-10 flex px-2 lg:px-0">
|
||||
<div className="flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
className="block h-8 w-auto"
|
||||
onClick={() => {
|
||||
props.onClick && props.onClick();
|
||||
}}
|
||||
imageUrl={Route.fromString(`${OneUptimeLogo}`)}
|
||||
alt={'OneUptime'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative z-10 flex px-2 lg:px-0">
|
||||
<div className="flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
className="block h-8 w-auto"
|
||||
onClick={() => {
|
||||
props.onClick && props.onClick();
|
||||
}}
|
||||
imageUrl={Route.fromString(`${OneUptimeLogo}`)}
|
||||
alt={"OneUptime"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton';
|
||||
import Notifications from 'CommonUI/src/Components/Header/Notifications/Notifications';
|
||||
import NotificationItem from 'CommonUI/src/Components/Header/Notifications/NotificationItem';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
|
||||
import NotificationItem from "CommonUI/src/Components/Header/Notifications/NotificationItem";
|
||||
import Notifications from "CommonUI/src/Components/Header/Notifications/Notifications";
|
||||
import React, { ReactElement, useState } from "react";
|
||||
|
||||
const DashboardHeader: () => JSX.Element = (): ReactElement => {
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<HeaderIconDropdownButton
|
||||
name="Notifications"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
showDropdown={isDropdownVisible}
|
||||
icon={IconProp.Notification}
|
||||
badge={4}
|
||||
>
|
||||
<Notifications>
|
||||
<NotificationItem
|
||||
title="Sample Title"
|
||||
description="Sample Description"
|
||||
createdAt={new Date()}
|
||||
icon={IconProp.Home}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
/>
|
||||
</Notifications>
|
||||
</HeaderIconDropdownButton>
|
||||
);
|
||||
return (
|
||||
<HeaderIconDropdownButton
|
||||
name="Notifications"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
showDropdown={isDropdownVisible}
|
||||
icon={IconProp.Notification}
|
||||
badge={4}
|
||||
>
|
||||
<Notifications>
|
||||
<NotificationItem
|
||||
title="Sample Title"
|
||||
description="Sample Description"
|
||||
createdAt={new Date()}
|
||||
icon={IconProp.Home}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
/>
|
||||
</Notifications>
|
||||
</HeaderIconDropdownButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardHeader;
|
||||
|
||||
@@ -1,286 +1,273 @@
|
||||
import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import Field from "CommonUI/src/Components/Forms/Types/Field";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ProjectPicker from "CommonUI/src/Components/Header/ProjectPicker/ProjectPicker";
|
||||
import ModelFormModal from "CommonUI/src/Components/ModelFormModal/ModelFormModal";
|
||||
import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons";
|
||||
import Toggle from "CommonUI/src/Components/Toggle/Toggle";
|
||||
import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config";
|
||||
import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes";
|
||||
import ProjectUtil from "CommonUI/src/Utils/Project";
|
||||
import Project from "Model/Models/Project";
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useState,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import ProjectPicker from 'CommonUI/src/Components/Header/ProjectPicker/ProjectPicker';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import Project from 'Model/Models/Project';
|
||||
import ModelFormModal from 'CommonUI/src/Components/ModelFormModal/ModelFormModal';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import ProjectUtil from 'CommonUI/src/Utils/Project';
|
||||
import { BILLING_ENABLED, getAllEnvVars } from 'CommonUI/src/Config';
|
||||
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
|
||||
import Field from 'CommonUI/src/Components/Forms/Types/Field';
|
||||
import { RadioButton } from 'CommonUI/src/Components/RadioButtons/GroupRadioButtons';
|
||||
import Toggle from 'CommonUI/src/Components/Toggle/Toggle';
|
||||
import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes';
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
projects: Array<Project>;
|
||||
onProjectSelected: (project: Project) => void;
|
||||
showProjectModal: boolean;
|
||||
onProjectModalClose: () => void;
|
||||
projects: Array<Project>;
|
||||
onProjectSelected: (project: Project) => void;
|
||||
showProjectModal: boolean;
|
||||
onProjectModalClose: () => void;
|
||||
}
|
||||
|
||||
const DashboardProjectPicker: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(
|
||||
null
|
||||
);
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
||||
|
||||
const getFooter: GetReactElementFunction = (): ReactElement => {
|
||||
if (!BILLING_ENABLED) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Toggle
|
||||
title="Yearly Plan"
|
||||
value={isSubscriptionPlanYearly}
|
||||
description="(Save 20%)"
|
||||
onChange={(value: boolean) => {
|
||||
setIsSubscriptionPlanYearly(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
|
||||
useState<boolean>(true);
|
||||
|
||||
const [fields, setFields] = useState<Array<Field<Project>>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.showProjectModal) {
|
||||
setShowModal(true);
|
||||
}
|
||||
}, [props.showProjectModal]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentProject: Project | null = ProjectUtil.getCurrentProject();
|
||||
setSelectedProject(currentProject);
|
||||
if (currentProject && props.onProjectSelected) {
|
||||
props.onProjectSelected(currentProject);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProject) {
|
||||
ProjectUtil.setCurrentProject(selectedProject);
|
||||
if (props.onProjectSelected) {
|
||||
props.onProjectSelected(selectedProject);
|
||||
}
|
||||
}
|
||||
}, [selectedProject]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.projects &&
|
||||
props.projects.length > 0 &&
|
||||
!selectedProject &&
|
||||
props.projects[0]
|
||||
) {
|
||||
const currentProject: Project | null =
|
||||
ProjectUtil.getCurrentProject();
|
||||
|
||||
if (!currentProject) {
|
||||
setSelectedProject(props.projects[0]);
|
||||
} else if (
|
||||
props.projects.filter((project: Project) => {
|
||||
return project._id === currentProject._id;
|
||||
}).length > 0
|
||||
) {
|
||||
setSelectedProject(
|
||||
props.projects.filter((project: Project) => {
|
||||
return project._id === currentProject._id;
|
||||
})[0] as Project
|
||||
);
|
||||
} else {
|
||||
setSelectedProject(props.projects[0]);
|
||||
}
|
||||
}
|
||||
}, [props.projects]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshFields();
|
||||
}, [isSubscriptionPlanYearly]);
|
||||
|
||||
const refreshFields: VoidFunction = (): void => {
|
||||
let formFields: Array<Field<Project>> = [
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 4,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'My Project',
|
||||
description: 'Pick a friendly name.',
|
||||
title: 'Project Name',
|
||||
required: true,
|
||||
stepId: BILLING_ENABLED ? 'basic' : undefined,
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = [
|
||||
...formFields,
|
||||
{
|
||||
field: {
|
||||
paymentProviderPlanId: true,
|
||||
},
|
||||
stepId: 'plan',
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
footerElement: getFooter(),
|
||||
fieldType: FormFieldSchemaType.RadioButton,
|
||||
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
|
||||
getAllEnvVars()
|
||||
).map((plan: SubscriptionPlan): RadioButton => {
|
||||
let description: string = plan.isCustomPricing()
|
||||
? `Our sales team will contact you soon.`
|
||||
: `Billed ${
|
||||
isSubscriptionPlanYearly
|
||||
? 'yearly'
|
||||
: 'monthly'
|
||||
}. ${
|
||||
plan.getTrialPeriod() > 0
|
||||
? `Free ${plan.getTrialPeriod()} days trial.`
|
||||
: ''
|
||||
}`;
|
||||
|
||||
if (
|
||||
isSubscriptionPlanYearly &&
|
||||
plan.getYearlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = 'This plan is free, forever. ';
|
||||
}
|
||||
|
||||
if (
|
||||
!isSubscriptionPlanYearly &&
|
||||
plan.getMonthlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = 'This plan is free, forever. ';
|
||||
}
|
||||
|
||||
return {
|
||||
value: isSubscriptionPlanYearly
|
||||
? plan.getYearlyPlanId()
|
||||
: plan.getMonthlyPlanId(),
|
||||
title: plan.getName(),
|
||||
description: description,
|
||||
sideTitle: plan.isCustomPricing()
|
||||
? 'Custom Price'
|
||||
: isSubscriptionPlanYearly
|
||||
? '$' +
|
||||
plan
|
||||
.getYearlySubscriptionAmountInUSD()
|
||||
.toString() +
|
||||
'/mo billed yearly'
|
||||
: '$' +
|
||||
plan
|
||||
.getMonthlySubscriptionAmountInUSD()
|
||||
.toString(),
|
||||
sideDescription: plan.isCustomPricing()
|
||||
? ''
|
||||
: isSubscriptionPlanYearly
|
||||
? `~ $${
|
||||
plan.getYearlySubscriptionAmountInUSD() *
|
||||
12
|
||||
} per user / year`
|
||||
: `/month per user`,
|
||||
};
|
||||
}),
|
||||
title: 'Please select a plan.',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
paymentProviderPromoCode: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'Promo Code (Optional)',
|
||||
description: 'If you have a coupon code, enter it here.',
|
||||
title: 'Promo Code',
|
||||
required: false,
|
||||
stepId: 'plan',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setFields(formFields);
|
||||
};
|
||||
const getFooter: GetReactElementFunction = (): ReactElement => {
|
||||
if (!BILLING_ENABLED) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.projects.length !== 0 && (
|
||||
<ProjectPicker
|
||||
selectedProjectName={selectedProject?.name || ''}
|
||||
selectedProjectIcon={IconProp.Folder}
|
||||
projects={props.projects}
|
||||
onCreateProjectButtonClicked={() => {
|
||||
setShowModal(true);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
onProjectSelected={(project: Project) => {
|
||||
setSelectedProject(project);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showModal ? (
|
||||
<ModelFormModal<Project>
|
||||
modelType={Project}
|
||||
name="Create New Project"
|
||||
title="Create New Project"
|
||||
description="Please create a new OneUptime project to get started."
|
||||
onClose={() => {
|
||||
setShowModal(false);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
submitButtonText="Create Project"
|
||||
onSuccess={(project: Project | null) => {
|
||||
setSelectedProject(project);
|
||||
if (project && props.onProjectSelected) {
|
||||
props.onProjectSelected(project);
|
||||
}
|
||||
setShowModal(false);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
formProps={{
|
||||
name: 'Create New Project',
|
||||
steps: BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
title: 'Basic',
|
||||
id: 'basic',
|
||||
},
|
||||
{
|
||||
title: 'Select Plan',
|
||||
id: 'plan',
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
saveRequestOptions: {
|
||||
isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request
|
||||
},
|
||||
modelType: Project,
|
||||
id: 'create-project-from',
|
||||
fields: [...fields],
|
||||
formType: FormType.Create,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
<Toggle
|
||||
title="Yearly Plan"
|
||||
value={isSubscriptionPlanYearly}
|
||||
description="(Save 20%)"
|
||||
onChange={(value: boolean) => {
|
||||
setIsSubscriptionPlanYearly(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
|
||||
useState<boolean>(true);
|
||||
|
||||
const [fields, setFields] = useState<Array<Field<Project>>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.showProjectModal) {
|
||||
setShowModal(true);
|
||||
}
|
||||
}, [props.showProjectModal]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentProject: Project | null = ProjectUtil.getCurrentProject();
|
||||
setSelectedProject(currentProject);
|
||||
if (currentProject && props.onProjectSelected) {
|
||||
props.onProjectSelected(currentProject);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProject) {
|
||||
ProjectUtil.setCurrentProject(selectedProject);
|
||||
if (props.onProjectSelected) {
|
||||
props.onProjectSelected(selectedProject);
|
||||
}
|
||||
}
|
||||
}, [selectedProject]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.projects &&
|
||||
props.projects.length > 0 &&
|
||||
!selectedProject &&
|
||||
props.projects[0]
|
||||
) {
|
||||
const currentProject: Project | null = ProjectUtil.getCurrentProject();
|
||||
|
||||
if (!currentProject) {
|
||||
setSelectedProject(props.projects[0]);
|
||||
} else if (
|
||||
props.projects.filter((project: Project) => {
|
||||
return project._id === currentProject._id;
|
||||
}).length > 0
|
||||
) {
|
||||
setSelectedProject(
|
||||
props.projects.filter((project: Project) => {
|
||||
return project._id === currentProject._id;
|
||||
})[0] as Project,
|
||||
);
|
||||
} else {
|
||||
setSelectedProject(props.projects[0]);
|
||||
}
|
||||
}
|
||||
}, [props.projects]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshFields();
|
||||
}, [isSubscriptionPlanYearly]);
|
||||
|
||||
const refreshFields: VoidFunction = (): void => {
|
||||
let formFields: Array<Field<Project>> = [
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 4,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "My Project",
|
||||
description: "Pick a friendly name.",
|
||||
title: "Project Name",
|
||||
required: true,
|
||||
stepId: BILLING_ENABLED ? "basic" : undefined,
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = [
|
||||
...formFields,
|
||||
{
|
||||
field: {
|
||||
paymentProviderPlanId: true,
|
||||
},
|
||||
stepId: "plan",
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
footerElement: getFooter(),
|
||||
fieldType: FormFieldSchemaType.RadioButton,
|
||||
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
|
||||
getAllEnvVars(),
|
||||
).map((plan: SubscriptionPlan): RadioButton => {
|
||||
let description: string = plan.isCustomPricing()
|
||||
? `Our sales team will contact you soon.`
|
||||
: `Billed ${isSubscriptionPlanYearly ? "yearly" : "monthly"}. ${
|
||||
plan.getTrialPeriod() > 0
|
||||
? `Free ${plan.getTrialPeriod()} days trial.`
|
||||
: ""
|
||||
}`;
|
||||
|
||||
if (
|
||||
isSubscriptionPlanYearly &&
|
||||
plan.getYearlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = "This plan is free, forever. ";
|
||||
}
|
||||
|
||||
if (
|
||||
!isSubscriptionPlanYearly &&
|
||||
plan.getMonthlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = "This plan is free, forever. ";
|
||||
}
|
||||
|
||||
return {
|
||||
value: isSubscriptionPlanYearly
|
||||
? plan.getYearlyPlanId()
|
||||
: plan.getMonthlyPlanId(),
|
||||
title: plan.getName(),
|
||||
description: description,
|
||||
sideTitle: plan.isCustomPricing()
|
||||
? "Custom Price"
|
||||
: isSubscriptionPlanYearly
|
||||
? "$" +
|
||||
plan.getYearlySubscriptionAmountInUSD().toString() +
|
||||
"/mo billed yearly"
|
||||
: "$" + plan.getMonthlySubscriptionAmountInUSD().toString(),
|
||||
sideDescription: plan.isCustomPricing()
|
||||
? ""
|
||||
: isSubscriptionPlanYearly
|
||||
? `~ $${
|
||||
plan.getYearlySubscriptionAmountInUSD() * 12
|
||||
} per user / year`
|
||||
: `/month per user`,
|
||||
};
|
||||
}),
|
||||
title: "Please select a plan.",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
paymentProviderPromoCode: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Promo Code (Optional)",
|
||||
description: "If you have a coupon code, enter it here.",
|
||||
title: "Promo Code",
|
||||
required: false,
|
||||
stepId: "plan",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setFields(formFields);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.projects.length !== 0 && (
|
||||
<ProjectPicker
|
||||
selectedProjectName={selectedProject?.name || ""}
|
||||
selectedProjectIcon={IconProp.Folder}
|
||||
projects={props.projects}
|
||||
onCreateProjectButtonClicked={() => {
|
||||
setShowModal(true);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
onProjectSelected={(project: Project) => {
|
||||
setSelectedProject(project);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showModal ? (
|
||||
<ModelFormModal<Project>
|
||||
modelType={Project}
|
||||
name="Create New Project"
|
||||
title="Create New Project"
|
||||
description="Please create a new OneUptime project to get started."
|
||||
onClose={() => {
|
||||
setShowModal(false);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
submitButtonText="Create Project"
|
||||
onSuccess={(project: Project | null) => {
|
||||
setSelectedProject(project);
|
||||
if (project && props.onProjectSelected) {
|
||||
props.onProjectSelected(project);
|
||||
}
|
||||
setShowModal(false);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
formProps={{
|
||||
name: "Create New Project",
|
||||
steps: BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
title: "Basic",
|
||||
id: "basic",
|
||||
},
|
||||
{
|
||||
title: "Select Plan",
|
||||
id: "plan",
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
saveRequestOptions: {
|
||||
isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request
|
||||
},
|
||||
modelType: Project,
|
||||
id: "create-project-from",
|
||||
fields: [...fields],
|
||||
formType: FormType.Create,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardProjectPicker;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import SearchBox from 'CommonUI/src/Components/Header/SearchBox';
|
||||
import Project from 'Model/Models/Project';
|
||||
import SearchBox from "CommonUI/src/Components/Header/SearchBox";
|
||||
import Project from "Model/Models/Project";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
onChange: (search: string) => void;
|
||||
selectedProject: Project | null;
|
||||
onChange: (search: string) => void;
|
||||
selectedProject: Project | null;
|
||||
}
|
||||
|
||||
const Search: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
if (!props.selectedProject) {
|
||||
return <></>;
|
||||
}
|
||||
if (!props.selectedProject) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return <SearchBox key={2} onChange={props.onChange} />;
|
||||
return <SearchBox key={2} onChange={props.onChange} />;
|
||||
};
|
||||
|
||||
export default Search;
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
import React, { FunctionComponent, ReactElement, useState } from 'react';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import BlankProfilePic from 'CommonUI/src/Images/users/blank-profile.svg';
|
||||
import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton';
|
||||
import IconDropdownItem from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownItem';
|
||||
import IconDropdownMenu from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import { DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import User from 'CommonUI/src/Utils/User';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
|
||||
import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem";
|
||||
import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu";
|
||||
import { DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import BlankProfilePic from "CommonUI/src/Images/users/blank-profile.svg";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import User from "CommonUI/src/Utils/User";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
|
||||
const DashboardUserProfile: FunctionComponent = (): ReactElement => {
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderIconDropdownButton
|
||||
iconImageUrl={BlankProfilePic}
|
||||
name="User Profile"
|
||||
showDropdown={isDropdownVisible}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
>
|
||||
<IconDropdownMenu>
|
||||
{User.isMasterAdmin() ? (
|
||||
<IconDropdownItem
|
||||
title="Exit Admin"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}}
|
||||
icon={IconProp.ExternalLink}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
return (
|
||||
<>
|
||||
<HeaderIconDropdownButton
|
||||
iconImageUrl={BlankProfilePic}
|
||||
name="User Profile"
|
||||
showDropdown={isDropdownVisible}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
>
|
||||
<IconDropdownMenu>
|
||||
{User.isMasterAdmin() ? (
|
||||
<IconDropdownItem
|
||||
title="Exit Admin"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}}
|
||||
icon={IconProp.ExternalLink}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<IconDropdownItem
|
||||
title="Log out"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
url={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.LOGOUT] as Route
|
||||
)}
|
||||
icon={IconProp.Logout}
|
||||
/>
|
||||
</IconDropdownMenu>
|
||||
</HeaderIconDropdownButton>
|
||||
</>
|
||||
);
|
||||
<IconDropdownItem
|
||||
title="Log out"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
url={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.LOGOUT] as Route,
|
||||
)}
|
||||
icon={IconProp.Logout}
|
||||
/>
|
||||
</IconDropdownMenu>
|
||||
</HeaderIconDropdownButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardUserProfile;
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import MasterPage from 'CommonUI/src/Components/MasterPage/MasterPage';
|
||||
import Footer from '../Footer/Footer';
|
||||
import Header from '../Header/Header';
|
||||
import NavBar from '../NavBar/NavBar';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import TopAlert from 'CommonUI/src/Components/TopAlert/TopAlert';
|
||||
import Footer from "../Footer/Footer";
|
||||
import Header from "../Header/Header";
|
||||
import NavBar from "../NavBar/NavBar";
|
||||
import MasterPage from "CommonUI/src/Components/MasterPage/MasterPage";
|
||||
import TopAlert from "CommonUI/src/Components/TopAlert/TopAlert";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
children: ReactElement | Array<ReactElement>;
|
||||
children: ReactElement | Array<ReactElement>;
|
||||
}
|
||||
|
||||
const DashboardMasterPage: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<div>
|
||||
<TopAlert
|
||||
title="OneUptime Admin Dashboard"
|
||||
description="You can perform your OneUptime server related tasks on this dashboard."
|
||||
/>
|
||||
<MasterPage
|
||||
footer={<Footer />}
|
||||
header={<Header />}
|
||||
navBar={<NavBar />}
|
||||
isLoading={false}
|
||||
error={''}
|
||||
className="flex flex-col h-screen justify-between"
|
||||
>
|
||||
{props.children}
|
||||
</MasterPage>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<TopAlert
|
||||
title="OneUptime Admin Dashboard"
|
||||
description="You can perform your OneUptime server related tasks on this dashboard."
|
||||
/>
|
||||
<MasterPage
|
||||
footer={<Footer />}
|
||||
header={<Header />}
|
||||
navBar={<NavBar />}
|
||||
isLoading={false}
|
||||
error={""}
|
||||
className="flex flex-col h-screen justify-between"
|
||||
>
|
||||
{props.children}
|
||||
</MasterPage>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardMasterPage;
|
||||
|
||||
@@ -1,39 +1,37 @@
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import NavBar from 'CommonUI/src/Components/Navbar/NavBar';
|
||||
import NavBarItem from 'CommonUI/src/Components/Navbar/NavBarItem';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import NavBar from "CommonUI/src/Components/Navbar/NavBar";
|
||||
import NavBarItem from "CommonUI/src/Components/Navbar/NavBarItem";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const DashboardNavbar: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<NavBar>
|
||||
<NavBarItem
|
||||
title="Users"
|
||||
icon={IconProp.User}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USERS] as Route
|
||||
)}
|
||||
></NavBarItem>
|
||||
return (
|
||||
<NavBar>
|
||||
<NavBarItem
|
||||
title="Users"
|
||||
icon={IconProp.User}
|
||||
route={RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route)}
|
||||
></NavBarItem>
|
||||
|
||||
<NavBarItem
|
||||
title="Projects"
|
||||
icon={IconProp.Folder}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.PROJECTS] as Route
|
||||
)}
|
||||
></NavBarItem>
|
||||
<NavBarItem
|
||||
title="Projects"
|
||||
icon={IconProp.Folder}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.PROJECTS] as Route,
|
||||
)}
|
||||
></NavBarItem>
|
||||
|
||||
<NavBarItem
|
||||
title="Settings"
|
||||
icon={IconProp.Settings}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
)}
|
||||
></NavBarItem>
|
||||
</NavBar>
|
||||
);
|
||||
<NavBarItem
|
||||
title="Settings"
|
||||
icon={IconProp.Settings}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
)}
|
||||
></NavBarItem>
|
||||
</NavBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardNavbar;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import Telemetry from 'CommonUI/src/Utils/Telemetry';
|
||||
import App from "./App";
|
||||
import Telemetry from "CommonUI/src/Utils/Telemetry";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
Telemetry.init({
|
||||
serviceName: 'AdminDashboard',
|
||||
serviceName: "AdminDashboard",
|
||||
});
|
||||
|
||||
const root: any = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
document.getElementById("root") as HTMLElement,
|
||||
);
|
||||
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>,
|
||||
);
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
|
||||
import RouteMap from '../../Utils/RouteMap';
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap from "../../Utils/RouteMap";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
||||
|
||||
const Init: FunctionComponent = (): ReactElement => {
|
||||
useEffect(() => {
|
||||
Navigation.navigate(RouteMap[PageMap.USERS]!, {
|
||||
forceNavigate: true,
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
Navigation.navigate(RouteMap[PageMap.USERS]!, {
|
||||
forceNavigate: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Page title={''} breadcrumbLinks={[]}>
|
||||
<PageLoader isVisible={true} />
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page title={""} breadcrumbLinks={[]}>
|
||||
<PageLoader isVisible={true} />
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Init;
|
||||
|
||||
@@ -1,54 +1,49 @@
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
|
||||
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import UserUtil from 'CommonUI/src/Utils/User';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import { ACCOUNTS_URL } from 'CommonUI/src/Config';
|
||||
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
|
||||
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import { ACCOUNTS_URL } from "CommonUI/src/Config";
|
||||
import UiAnalytics from "CommonUI/src/Utils/Analytics";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import UserUtil from "CommonUI/src/Utils/User";
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
||||
|
||||
const Logout: FunctionComponent = (): ReactElement => {
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
const logout: PromiseVoidFunction = async (): Promise<void> => {
|
||||
UiAnalytics.logout();
|
||||
await UserUtil.logout();
|
||||
Navigation.navigate(ACCOUNTS_URL);
|
||||
};
|
||||
const logout: PromiseVoidFunction = async (): Promise<void> => {
|
||||
UiAnalytics.logout();
|
||||
await UserUtil.logout();
|
||||
Navigation.navigate(ACCOUNTS_URL);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
logout().catch((error: Error) => {
|
||||
setError(error.message || error.toString());
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
logout().catch((error: Error) => {
|
||||
setError(error.message || error.toString());
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Logout'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.INIT] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Logout',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.LOGOUT] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{!error ? <PageLoader isVisible={true} /> : <></>}
|
||||
{error ? <ErrorMessage error={error} /> : <></>}
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page
|
||||
title={"Logout"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.INIT] as Route),
|
||||
},
|
||||
{
|
||||
title: "Logout",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.LOGOUT] as Route),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{!error ? <PageLoader isVisible={true} /> : <></>}
|
||||
{error ? <ErrorMessage error={error} /> : <></>}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logout;
|
||||
|
||||
@@ -1,263 +1,251 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import AdminModelAPI from "../../Utils/ModelAPI";
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan";
|
||||
import Field from "CommonUI/src/Components/Forms/Types/Field";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons";
|
||||
import Toggle from "CommonUI/src/Components/Toggle/Toggle";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config";
|
||||
import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import Project from "Model/Models/Project";
|
||||
import User from "Model/Models/User";
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import Project from 'Model/Models/Project';
|
||||
import User from 'Model/Models/User';
|
||||
import { BILLING_ENABLED, getAllEnvVars } from 'CommonUI/src/Config';
|
||||
import Field from 'CommonUI/src/Components/Forms/Types/Field';
|
||||
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
|
||||
import { RadioButton } from 'CommonUI/src/Components/RadioButtons/GroupRadioButtons';
|
||||
import Toggle from 'CommonUI/src/Components/Toggle/Toggle';
|
||||
import AdminModelAPI from '../../Utils/ModelAPI';
|
||||
import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes';
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
const Projects: FunctionComponent = (): ReactElement => {
|
||||
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
|
||||
useState<boolean>(true);
|
||||
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
|
||||
useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
refreshFields();
|
||||
}, [isSubscriptionPlanYearly]);
|
||||
useEffect(() => {
|
||||
refreshFields();
|
||||
}, [isSubscriptionPlanYearly]);
|
||||
|
||||
const refreshFields: VoidFunction = (): void => {
|
||||
let formFields: Array<Field<Project>> = [
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 4,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'My Project',
|
||||
description: 'Pick a friendly name.',
|
||||
title: 'Project Name',
|
||||
required: true,
|
||||
stepId: BILLING_ENABLED ? 'basic' : undefined,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdByUser: true,
|
||||
},
|
||||
title: 'Owner',
|
||||
description:
|
||||
'Who would you like the owner of this project to be? If you leave this blank - you will be the owner of the project',
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
stepId: BILLING_ENABLED ? 'basic' : undefined,
|
||||
dropdownModal: {
|
||||
type: User,
|
||||
labelField: 'email',
|
||||
valueField: '_id',
|
||||
},
|
||||
},
|
||||
];
|
||||
const refreshFields: VoidFunction = (): void => {
|
||||
let formFields: Array<Field<Project>> = [
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 4,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "My Project",
|
||||
description: "Pick a friendly name.",
|
||||
title: "Project Name",
|
||||
required: true,
|
||||
stepId: BILLING_ENABLED ? "basic" : undefined,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdByUser: true,
|
||||
},
|
||||
title: "Owner",
|
||||
description:
|
||||
"Who would you like the owner of this project to be? If you leave this blank - you will be the owner of the project",
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
stepId: BILLING_ENABLED ? "basic" : undefined,
|
||||
dropdownModal: {
|
||||
type: User,
|
||||
labelField: "email",
|
||||
valueField: "_id",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = [
|
||||
...formFields,
|
||||
{
|
||||
field: {
|
||||
paymentProviderPlanId: true,
|
||||
},
|
||||
stepId: 'plan',
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
footerElement: getFooter(),
|
||||
fieldType: FormFieldSchemaType.RadioButton,
|
||||
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
|
||||
getAllEnvVars()
|
||||
).map((plan: SubscriptionPlan): RadioButton => {
|
||||
let description: string = plan.isCustomPricing()
|
||||
? `Our sales team will contact you soon.`
|
||||
: `Billed ${
|
||||
isSubscriptionPlanYearly
|
||||
? 'yearly'
|
||||
: 'monthly'
|
||||
}. ${
|
||||
plan.getTrialPeriod() > 0
|
||||
? `Free ${plan.getTrialPeriod()} days trial.`
|
||||
: ''
|
||||
}`;
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = [
|
||||
...formFields,
|
||||
{
|
||||
field: {
|
||||
paymentProviderPlanId: true,
|
||||
},
|
||||
stepId: "plan",
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
footerElement: getFooter(),
|
||||
fieldType: FormFieldSchemaType.RadioButton,
|
||||
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
|
||||
getAllEnvVars(),
|
||||
).map((plan: SubscriptionPlan): RadioButton => {
|
||||
let description: string = plan.isCustomPricing()
|
||||
? `Our sales team will contact you soon.`
|
||||
: `Billed ${isSubscriptionPlanYearly ? "yearly" : "monthly"}. ${
|
||||
plan.getTrialPeriod() > 0
|
||||
? `Free ${plan.getTrialPeriod()} days trial.`
|
||||
: ""
|
||||
}`;
|
||||
|
||||
if (
|
||||
isSubscriptionPlanYearly &&
|
||||
plan.getYearlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = 'This plan is free, forever. ';
|
||||
}
|
||||
if (
|
||||
isSubscriptionPlanYearly &&
|
||||
plan.getYearlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = "This plan is free, forever. ";
|
||||
}
|
||||
|
||||
if (
|
||||
!isSubscriptionPlanYearly &&
|
||||
plan.getMonthlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = 'This plan is free, forever. ';
|
||||
}
|
||||
if (
|
||||
!isSubscriptionPlanYearly &&
|
||||
plan.getMonthlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = "This plan is free, forever. ";
|
||||
}
|
||||
|
||||
return {
|
||||
value: isSubscriptionPlanYearly
|
||||
? plan.getYearlyPlanId()
|
||||
: plan.getMonthlyPlanId(),
|
||||
title: plan.getName(),
|
||||
description: description,
|
||||
sideTitle: plan.isCustomPricing()
|
||||
? 'Custom Price'
|
||||
: isSubscriptionPlanYearly
|
||||
? '$' +
|
||||
plan
|
||||
.getYearlySubscriptionAmountInUSD()
|
||||
.toString() +
|
||||
'/mo billed yearly'
|
||||
: '$' +
|
||||
plan
|
||||
.getMonthlySubscriptionAmountInUSD()
|
||||
.toString(),
|
||||
sideDescription: plan.isCustomPricing()
|
||||
? ''
|
||||
: isSubscriptionPlanYearly
|
||||
? `~ $${
|
||||
plan.getYearlySubscriptionAmountInUSD() *
|
||||
12
|
||||
} per user / year`
|
||||
: `/month per user`,
|
||||
};
|
||||
}),
|
||||
title: 'Please select a plan.',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
paymentProviderPromoCode: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'Promo Code (Optional)',
|
||||
description: 'If you have a coupon code, enter it here.',
|
||||
title: 'Promo Code',
|
||||
required: false,
|
||||
stepId: 'plan',
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
return {
|
||||
value: isSubscriptionPlanYearly
|
||||
? plan.getYearlyPlanId()
|
||||
: plan.getMonthlyPlanId(),
|
||||
title: plan.getName(),
|
||||
description: description,
|
||||
sideTitle: plan.isCustomPricing()
|
||||
? "Custom Price"
|
||||
: isSubscriptionPlanYearly
|
||||
? "$" +
|
||||
plan.getYearlySubscriptionAmountInUSD().toString() +
|
||||
"/mo billed yearly"
|
||||
: "$" + plan.getMonthlySubscriptionAmountInUSD().toString(),
|
||||
sideDescription: plan.isCustomPricing()
|
||||
? ""
|
||||
: isSubscriptionPlanYearly
|
||||
? `~ $${
|
||||
plan.getYearlySubscriptionAmountInUSD() * 12
|
||||
} per user / year`
|
||||
: `/month per user`,
|
||||
};
|
||||
}),
|
||||
title: "Please select a plan.",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
paymentProviderPromoCode: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Promo Code (Optional)",
|
||||
description: "If you have a coupon code, enter it here.",
|
||||
title: "Promo Code",
|
||||
required: false,
|
||||
stepId: "plan",
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setFields(formFields);
|
||||
};
|
||||
setFields(formFields);
|
||||
};
|
||||
|
||||
const [fields, setFields] = useState<Array<Field<Project>>>([]);
|
||||
const [fields, setFields] = useState<Array<Field<Project>>>([]);
|
||||
|
||||
const getFooter: GetReactElementFunction = (): ReactElement => {
|
||||
if (!BILLING_ENABLED) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Toggle
|
||||
title="Yearly Plan"
|
||||
value={isSubscriptionPlanYearly}
|
||||
description="(Save 20%)"
|
||||
onChange={(value: boolean) => {
|
||||
setIsSubscriptionPlanYearly(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const getFooter: GetReactElementFunction = (): ReactElement => {
|
||||
if (!BILLING_ENABLED) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Projects'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Projects',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.PROJECTS] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ModelTable<Project>
|
||||
modelType={Project}
|
||||
modelAPI={AdminModelAPI}
|
||||
id="projects-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={true}
|
||||
name="Projects"
|
||||
isViewable={false}
|
||||
cardProps={{
|
||||
title: 'Projects',
|
||||
description: 'Here is a list of proejcts in OneUptime.',
|
||||
}}
|
||||
showViewIdButton={true}
|
||||
formSteps={
|
||||
BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
title: 'Basic',
|
||||
id: 'basic',
|
||||
},
|
||||
{
|
||||
title: 'Select Plan',
|
||||
id: 'plan',
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
noItemsMessage={'No projects found.'}
|
||||
formFields={fields}
|
||||
showRefreshButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
<Toggle
|
||||
title="Yearly Plan"
|
||||
value={isSubscriptionPlanYearly}
|
||||
description="(Save 20%)"
|
||||
onChange={(value: boolean) => {
|
||||
setIsSubscriptionPlanYearly(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={"Projects"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Projects",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.PROJECTS] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ModelTable<Project>
|
||||
modelType={Project}
|
||||
modelAPI={AdminModelAPI}
|
||||
id="projects-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={true}
|
||||
name="Projects"
|
||||
isViewable={false}
|
||||
cardProps={{
|
||||
title: "Projects",
|
||||
description: "Here is a list of proejcts in OneUptime.",
|
||||
}}
|
||||
showViewIdButton={true}
|
||||
formSteps={
|
||||
BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
title: "Basic",
|
||||
id: "basic",
|
||||
},
|
||||
{
|
||||
title: "Select Plan",
|
||||
id: "plan",
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
noItemsMessage={"No projects found."}
|
||||
formFields={fields}
|
||||
showRefreshButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Projects;
|
||||
|
||||
@@ -1,102 +1,100 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import GlobalConfig from 'Model/Models/GlobalConfig';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import GlobalConfig from "Model/Models/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'API Key',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_HOST] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="API Key Settings"
|
||||
cardProps={{
|
||||
title: 'Master API Key Settings',
|
||||
description:
|
||||
'This API key has root access to all the resources in all the projects on OneUptime.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit API Key Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
masterApiKey: true,
|
||||
},
|
||||
title: 'Master API Key',
|
||||
fieldType: FormFieldSchemaType.ObjectID,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isMasterApiKeyEnabled: true,
|
||||
},
|
||||
title: 'Enabled',
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
masterApiKey: true,
|
||||
},
|
||||
title: 'Master API Key',
|
||||
description:
|
||||
'This API key has root access to all the resources in all the projects on OneUptime.',
|
||||
fieldType: FieldType.HiddenText,
|
||||
opts: {
|
||||
isCopyable: true,
|
||||
},
|
||||
placeholder: 'API Key not generated yet.',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isMasterApiKeyEnabled: true,
|
||||
},
|
||||
title: 'Enabled',
|
||||
description:
|
||||
'Enable or disable the master API key. If disabled, all requests using this key will fail.',
|
||||
fieldType: FieldType.Boolean,
|
||||
placeholder: 'Not Enabled',
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "API Key",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_HOST] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="API Key Settings"
|
||||
cardProps={{
|
||||
title: "Master API Key Settings",
|
||||
description:
|
||||
"This API key has root access to all the resources in all the projects on OneUptime.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit API Key Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
masterApiKey: true,
|
||||
},
|
||||
title: "Master API Key",
|
||||
fieldType: FormFieldSchemaType.ObjectID,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isMasterApiKeyEnabled: true,
|
||||
},
|
||||
title: "Enabled",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
masterApiKey: true,
|
||||
},
|
||||
title: "Master API Key",
|
||||
description:
|
||||
"This API key has root access to all the resources in all the projects on OneUptime.",
|
||||
fieldType: FieldType.HiddenText,
|
||||
opts: {
|
||||
isCopyable: true,
|
||||
},
|
||||
placeholder: "API Key not generated yet.",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isMasterApiKeyEnabled: true,
|
||||
},
|
||||
title: "Enabled",
|
||||
description:
|
||||
"Enable or disable the master API key. If disabled, all requests using this key will fail.",
|
||||
fieldType: FieldType.Boolean,
|
||||
placeholder: "Not Enabled",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,83 +1,80 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import GlobalConfig from 'Model/Models/GlobalConfig';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import GlobalConfig from "Model/Models/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Authentication',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="Authentication Settings"
|
||||
cardProps={{
|
||||
title: 'Authentication Settings',
|
||||
description:
|
||||
'Authentication Settings for this OneUptime Server instance.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
disableSignup: true,
|
||||
},
|
||||
title: 'Disable Sign Up',
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
description:
|
||||
'Should we disable sign up of new users to OneUptime?',
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
disableSignup: true,
|
||||
},
|
||||
fieldType: FieldType.Boolean,
|
||||
title: 'Disable Sign Up',
|
||||
placeholder: 'No',
|
||||
description:
|
||||
'Should we disable sign up of new users to OneUptime?',
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Authentication",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="Authentication Settings"
|
||||
cardProps={{
|
||||
title: "Authentication Settings",
|
||||
description:
|
||||
"Authentication Settings for this OneUptime Server instance.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
disableSignup: true,
|
||||
},
|
||||
title: "Disable Sign Up",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
description: "Should we disable sign up of new users to OneUptime?",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
disableSignup: true,
|
||||
},
|
||||
fieldType: FieldType.Boolean,
|
||||
title: "Disable Sign Up",
|
||||
placeholder: "No",
|
||||
description:
|
||||
"Should we disable sign up of new users to OneUptime?",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,119 +1,114 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import GlobalConfig from 'Model/Models/GlobalConfig';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import GlobalConfig from "Model/Models/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Calls and SMS',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="Call and SMS Settings"
|
||||
cardProps={{
|
||||
title: 'Twilio Config',
|
||||
description: 'This will be used to make Call and send SMS.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Twilio Config"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
twilioAccountSID: true,
|
||||
},
|
||||
title: 'Twilio Account SID',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
'You can find this in your Twilio console.',
|
||||
placeholder: '',
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioAuthToken: true,
|
||||
},
|
||||
title: 'Twilio Auth Token',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
'You can find this in your Twilio console.',
|
||||
placeholder: '',
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: 'Twilio Phone Number',
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: true,
|
||||
description:
|
||||
'You can find this in your Twilio console.',
|
||||
placeholder: '',
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
twilioAccountSID: true,
|
||||
},
|
||||
title: 'Twilio Account SID',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: 'Twilio Phone Number',
|
||||
fieldType: FieldType.Phone,
|
||||
placeholder: 'None',
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Calls and SMS",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="Call and SMS Settings"
|
||||
cardProps={{
|
||||
title: "Twilio Config",
|
||||
description: "This will be used to make Call and send SMS.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Twilio Config"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
twilioAccountSID: true,
|
||||
},
|
||||
title: "Twilio Account SID",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description: "You can find this in your Twilio console.",
|
||||
placeholder: "",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioAuthToken: true,
|
||||
},
|
||||
title: "Twilio Auth Token",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description: "You can find this in your Twilio console.",
|
||||
placeholder: "",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: "Twilio Phone Number",
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: true,
|
||||
description: "You can find this in your Twilio console.",
|
||||
placeholder: "",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
twilioAccountSID: true,
|
||||
},
|
||||
title: "Twilio Account SID",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: "Twilio Phone Number",
|
||||
fieldType: FieldType.Phone,
|
||||
placeholder: "None",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,427 +1,419 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import GlobalConfig, { EmailServerType } from 'Model/Models/GlobalConfig';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
|
||||
import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
|
||||
import Pill from 'CommonUI/src/Components/Pill/Pill';
|
||||
import { Green, Red } from 'Common/Types/BrandColors';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import { Green, Red } from "Common/Types/BrandColors";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import Pill from "CommonUI/src/Components/Pill/Pill";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import DropdownUtil from "CommonUI/src/Utils/Dropdown";
|
||||
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
import GlobalConfig, { EmailServerType } from "Model/Models/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
const [emailServerType, setemailServerType] =
|
||||
React.useState<EmailServerType>(EmailServerType.Internal);
|
||||
const [emailServerType, setemailServerType] = React.useState<EmailServerType>(
|
||||
EmailServerType.Internal,
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(true);
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(true);
|
||||
|
||||
const [error, setError] = React.useState<string>('');
|
||||
const [error, setError] = React.useState<string>("");
|
||||
|
||||
const fetchItem: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
const fetchItem: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await ModelAPI.getItem<GlobalConfig>({
|
||||
modelType: GlobalConfig,
|
||||
id: ObjectID.getZeroObjectID(),
|
||||
select: {
|
||||
_id: true,
|
||||
emailServerType: true,
|
||||
},
|
||||
});
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await ModelAPI.getItem<GlobalConfig>({
|
||||
modelType: GlobalConfig,
|
||||
id: ObjectID.getZeroObjectID(),
|
||||
select: {
|
||||
_id: true,
|
||||
emailServerType: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (globalConfig) {
|
||||
setemailServerType(
|
||||
globalConfig.emailServerType || EmailServerType.Internal
|
||||
);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchItem().catch((err: Error) => {
|
||||
setError(err.message);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
if (globalConfig) {
|
||||
setemailServerType(
|
||||
globalConfig.emailServerType || EmailServerType.Internal,
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage error={error} />;
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
useEffect(() => {
|
||||
fetchItem().catch((err: Error) => {
|
||||
setError(err.message);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Email Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_SMTP] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
|
||||
<CardModelDetail
|
||||
name="Admin Notification Email"
|
||||
cardProps={{
|
||||
title: "Admin Notification Email",
|
||||
description:
|
||||
"Enter the email address where you would like to receive admin-level notifications.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Email"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
adminNotificationEmail: true,
|
||||
},
|
||||
title: "Admin Notification Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: false,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
adminNotificationEmail: true,
|
||||
},
|
||||
title: "Admin Notification Email",
|
||||
fieldType: FieldType.Email,
|
||||
placeholder: "None",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
|
||||
<CardModelDetail
|
||||
name="Internal SMTP Settings"
|
||||
cardProps={{
|
||||
title: "Email Server Settings",
|
||||
description:
|
||||
"Pick which email server you would like to use to send emails.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Server"
|
||||
onSaveSuccess={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
emailServerType: true,
|
||||
},
|
||||
title: "Email Server Type",
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
dropdownOptions:
|
||||
DropdownUtil.getDropdownOptionsFromEnum(EmailServerType),
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
emailServerType: true,
|
||||
},
|
||||
title: "Email Server Type",
|
||||
fieldType: FieldType.Text,
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
|
||||
{emailServerType === EmailServerType.CustomSMTP ? (
|
||||
<CardModelDetail
|
||||
name="Host Settings"
|
||||
cardProps={{
|
||||
title: "Custom Email and SMTP Settings",
|
||||
description:
|
||||
"If you have not enabled Internal SMTP server to send emails. Please configure your SMTP server here.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit SMTP Config"
|
||||
formSteps={[
|
||||
{
|
||||
title: "SMTP Server",
|
||||
id: "server-info",
|
||||
},
|
||||
{
|
||||
title: "Authentication",
|
||||
id: "authentication",
|
||||
},
|
||||
{
|
||||
title: "Email",
|
||||
id: "email-info",
|
||||
},
|
||||
]}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
smtpHost: true,
|
||||
},
|
||||
title: "Hostname",
|
||||
stepId: "server-info",
|
||||
fieldType: FormFieldSchemaType.Hostname,
|
||||
required: true,
|
||||
placeholder: "smtp.server.com",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPort: true,
|
||||
},
|
||||
title: "Port",
|
||||
stepId: "server-info",
|
||||
fieldType: FormFieldSchemaType.Port,
|
||||
required: true,
|
||||
placeholder: "587",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isSMTPSecure: true,
|
||||
},
|
||||
title: "Use SSL / TLS",
|
||||
stepId: "server-info",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
description:
|
||||
"If you use port 465, please enable this. Do not enable this if you use port 587.",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpUsername: true,
|
||||
},
|
||||
title: "Username",
|
||||
stepId: "authentication",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
placeholder: "emailuser",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPassword: true,
|
||||
},
|
||||
title: "Password",
|
||||
stepId: "authentication",
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: false,
|
||||
placeholder: "Password",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromEmail: true,
|
||||
},
|
||||
title: "Email From",
|
||||
stepId: "email-info",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
description:
|
||||
"This is the display email your team and customers see, when they receive emails from OneUptime.",
|
||||
placeholder: "email@company.com",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromName: true,
|
||||
},
|
||||
title: "From Name",
|
||||
stepId: "email-info",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
"This is the display name your team and customers see, when they receive emails from OneUptime.",
|
||||
placeholder: "Company, Inc.",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
smtpHost: true,
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
title: "SMTP Host",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPort: true,
|
||||
},
|
||||
{
|
||||
title: 'Email Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_SMTP] as Route
|
||||
),
|
||||
title: "SMTP Port",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpUsername: true,
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
title: "SMTP Username",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromEmail: true,
|
||||
},
|
||||
title: "SMTP Email",
|
||||
placeholder: "None",
|
||||
fieldType: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromName: true,
|
||||
},
|
||||
title: "SMTP From Name",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isSMTPSecure: true,
|
||||
},
|
||||
title: "Use SSL/TLS",
|
||||
placeholder: "No",
|
||||
fieldType: FieldType.Boolean,
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<CardModelDetail
|
||||
name="Admin Notification Email"
|
||||
cardProps={{
|
||||
title: 'Admin Notification Email',
|
||||
description:
|
||||
'Enter the email address where you would like to receive admin-level notifications.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Email"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
adminNotificationEmail: true,
|
||||
},
|
||||
title: 'Admin Notification Email',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: false,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
adminNotificationEmail: true,
|
||||
},
|
||||
title: 'Admin Notification Email',
|
||||
fieldType: FieldType.Email,
|
||||
placeholder: 'None',
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
{emailServerType === EmailServerType.Sendgrid ? (
|
||||
<CardModelDetail<GlobalConfig>
|
||||
name="Sendgrid Settings"
|
||||
cardProps={{
|
||||
title: "Sendgrid Settings",
|
||||
description:
|
||||
"Enter your Sendgrid API key to send emails through Sendgrid.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit API Key"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
sendgridApiKey: true,
|
||||
},
|
||||
title: "Sendgrid API Key",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "Sendgrid API Key",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
sendgridFromEmail: true,
|
||||
},
|
||||
title: "From Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
placeholder: "email@yourcompany.com",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
sendgridFromName: true,
|
||||
},
|
||||
title: "From Name",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "Acme, Inc.",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
selectMoreFields: {
|
||||
sendgridFromEmail: true,
|
||||
sendgridFromName: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
sendgridApiKey: true,
|
||||
},
|
||||
title: "",
|
||||
placeholder: "None",
|
||||
getElement: (item: GlobalConfig) => {
|
||||
if (
|
||||
item["sendgridApiKey"] &&
|
||||
item["sendgridFromEmail"] &&
|
||||
item["sendgridFromName"]
|
||||
) {
|
||||
return <Pill text="Enabled" color={Green} />;
|
||||
} else if (!item["sendgridApiKey"]) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the API key."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
} else if (!item["sendgridFromEmail"]) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the From Email."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
} else if (!item["sendgridFromName"]) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the From Name."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
<CardModelDetail
|
||||
name="Internal SMTP Settings"
|
||||
cardProps={{
|
||||
title: 'Email Server Settings',
|
||||
description:
|
||||
'Pick which email server you would like to use to send emails.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Server"
|
||||
onSaveSuccess={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
emailServerType: true,
|
||||
},
|
||||
title: 'Email Server Type',
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
dropdownOptions:
|
||||
DropdownUtil.getDropdownOptionsFromEnum(
|
||||
EmailServerType
|
||||
),
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
emailServerType: true,
|
||||
},
|
||||
title: 'Email Server Type',
|
||||
fieldType: FieldType.Text,
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
|
||||
{emailServerType === EmailServerType.CustomSMTP ? (
|
||||
<CardModelDetail
|
||||
name="Host Settings"
|
||||
cardProps={{
|
||||
title: 'Custom Email and SMTP Settings',
|
||||
description:
|
||||
'If you have not enabled Internal SMTP server to send emails. Please configure your SMTP server here.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit SMTP Config"
|
||||
formSteps={[
|
||||
{
|
||||
title: 'SMTP Server',
|
||||
id: 'server-info',
|
||||
},
|
||||
{
|
||||
title: 'Authentication',
|
||||
id: 'authentication',
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
id: 'email-info',
|
||||
},
|
||||
]}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
smtpHost: true,
|
||||
},
|
||||
title: 'Hostname',
|
||||
stepId: 'server-info',
|
||||
fieldType: FormFieldSchemaType.Hostname,
|
||||
required: true,
|
||||
placeholder: 'smtp.server.com',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPort: true,
|
||||
},
|
||||
title: 'Port',
|
||||
stepId: 'server-info',
|
||||
fieldType: FormFieldSchemaType.Port,
|
||||
required: true,
|
||||
placeholder: '587',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isSMTPSecure: true,
|
||||
},
|
||||
title: 'Use SSL / TLS',
|
||||
stepId: 'server-info',
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
description:
|
||||
'If you use port 465, please enable this. Do not enable this if you use port 587.',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpUsername: true,
|
||||
},
|
||||
title: 'Username',
|
||||
stepId: 'authentication',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
placeholder: 'emailuser',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPassword: true,
|
||||
},
|
||||
title: 'Password',
|
||||
stepId: 'authentication',
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: false,
|
||||
placeholder: 'Password',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromEmail: true,
|
||||
},
|
||||
title: 'Email From',
|
||||
stepId: 'email-info',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
description:
|
||||
'This is the display email your team and customers see, when they receive emails from OneUptime.',
|
||||
placeholder: 'email@company.com',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromName: true,
|
||||
},
|
||||
title: 'From Name',
|
||||
stepId: 'email-info',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
'This is the display name your team and customers see, when they receive emails from OneUptime.',
|
||||
placeholder: 'Company, Inc.',
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
smtpHost: true,
|
||||
},
|
||||
title: 'SMTP Host',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPort: true,
|
||||
},
|
||||
title: 'SMTP Port',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpUsername: true,
|
||||
},
|
||||
title: 'SMTP Username',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromEmail: true,
|
||||
},
|
||||
title: 'SMTP Email',
|
||||
placeholder: 'None',
|
||||
fieldType: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromName: true,
|
||||
},
|
||||
title: 'SMTP From Name',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isSMTPSecure: true,
|
||||
},
|
||||
title: 'Use SSL/TLS',
|
||||
placeholder: 'No',
|
||||
fieldType: FieldType.Boolean,
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{emailServerType === EmailServerType.Sendgrid ? (
|
||||
<CardModelDetail<GlobalConfig>
|
||||
name="Sendgrid Settings"
|
||||
cardProps={{
|
||||
title: 'Sendgrid Settings',
|
||||
description:
|
||||
'Enter your Sendgrid API key to send emails through Sendgrid.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit API Key"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
sendgridApiKey: true,
|
||||
},
|
||||
title: 'Sendgrid API Key',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: 'Sendgrid API Key',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
sendgridFromEmail: true,
|
||||
},
|
||||
title: 'From Email',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
placeholder: 'email@yourcompany.com',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
sendgridFromName: true,
|
||||
},
|
||||
title: 'From Name',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: 'Acme, Inc.',
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
selectMoreFields: {
|
||||
sendgridFromEmail: true,
|
||||
sendgridFromName: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
sendgridApiKey: true,
|
||||
},
|
||||
title: '',
|
||||
placeholder: 'None',
|
||||
getElement: (item: GlobalConfig) => {
|
||||
if (
|
||||
item['sendgridApiKey'] &&
|
||||
item['sendgridFromEmail'] &&
|
||||
item['sendgridFromName']
|
||||
) {
|
||||
return (
|
||||
<Pill
|
||||
text="Enabled"
|
||||
color={Green}
|
||||
/>
|
||||
);
|
||||
} else if (!item['sendgridApiKey']) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the API key."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
} else if (!item['sendgridFromEmail']) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the From Email."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
} else if (!item['sendgridFromName']) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the From Name."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,251 +1,243 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import React, { FunctionComponent, ReactElement, useState } from 'react';
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
|
||||
import Probe from 'Model/Models/Probe';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import { Green, Red } from 'Common/Types/BrandColors';
|
||||
import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble';
|
||||
import ProbeElement from 'CommonUI/src/Components/Probe/Probe';
|
||||
import IsNull from 'Common/Types/BaseDatabase/IsNull';
|
||||
import Banner from 'CommonUI/src/Components/Banner/Banner';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
|
||||
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
|
||||
import AdminModelAPI from '../../../Utils/ModelAPI';
|
||||
import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IsNull from "Common/Types/BaseDatabase/IsNull";
|
||||
import { Green, Red } from "Common/Types/BrandColors";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Banner from "CommonUI/src/Components/Banner/Banner";
|
||||
import { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
|
||||
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import ProbeElement from "CommonUI/src/Components/Probe/Probe";
|
||||
import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import Probe from "Model/Models/Probe";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
const [showKeyModal, setShowKeyModal] = useState<boolean>(false);
|
||||
const [showKeyModal, setShowKeyModal] = useState<boolean>(false);
|
||||
|
||||
const [currentProbe, setCurrentProbe] = useState<Probe | null>(null);
|
||||
const [currentProbe, setCurrentProbe] = useState<Probe | null>(null);
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Global Probes',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_PROBES] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Global Probes",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_PROBES] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
|
||||
<Banner
|
||||
openInNewTab={true}
|
||||
title="Need help with setting up Global Probes?"
|
||||
description="Here is a guide which will help you get set up"
|
||||
link={Route.fromString('/docs/probe/custom-probe')}
|
||||
/>
|
||||
<Banner
|
||||
openInNewTab={true}
|
||||
title="Need help with setting up Global Probes?"
|
||||
description="Here is a guide which will help you get set up"
|
||||
link={Route.fromString("/docs/probe/custom-probe")}
|
||||
/>
|
||||
|
||||
<ModelTable<Probe>
|
||||
modelType={Probe}
|
||||
id="probes-table"
|
||||
name="Settings > Global Probes"
|
||||
isDeleteable={true}
|
||||
isEditable={true}
|
||||
isCreateable={true}
|
||||
cardProps={{
|
||||
title: 'Global Probes',
|
||||
description:
|
||||
'Global Probes help you monitor external resources from different locations around the world.',
|
||||
}}
|
||||
query={{
|
||||
projectId: new IsNull(),
|
||||
isGlobalProbe: true,
|
||||
}}
|
||||
modelAPI={AdminModelAPI}
|
||||
noItemsMessage={'No probes found.'}
|
||||
showRefreshButton={true}
|
||||
onBeforeCreate={(item: Probe) => {
|
||||
item.isGlobalProbe = true;
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: 'internal-probe',
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
<ModelTable<Probe>
|
||||
modelType={Probe}
|
||||
id="probes-table"
|
||||
name="Settings > Global Probes"
|
||||
isDeleteable={true}
|
||||
isEditable={true}
|
||||
isCreateable={true}
|
||||
cardProps={{
|
||||
title: "Global Probes",
|
||||
description:
|
||||
"Global Probes help you monitor external resources from different locations around the world.",
|
||||
}}
|
||||
query={{
|
||||
projectId: new IsNull(),
|
||||
isGlobalProbe: true,
|
||||
}}
|
||||
modelAPI={AdminModelAPI}
|
||||
noItemsMessage={"No probes found."}
|
||||
showRefreshButton={true}
|
||||
onBeforeCreate={(item: Probe) => {
|
||||
item.isGlobalProbe = true;
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "internal-probe",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: 'Description',
|
||||
fieldType: FormFieldSchemaType.LongText,
|
||||
required: true,
|
||||
placeholder:
|
||||
'This probe is to monitor all the internal services.',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
fieldType: FormFieldSchemaType.LongText,
|
||||
required: true,
|
||||
placeholder: "This probe is to monitor all the internal services.",
|
||||
},
|
||||
|
||||
{
|
||||
field: {
|
||||
iconFile: true,
|
||||
},
|
||||
title: 'Probe Logo',
|
||||
fieldType: FormFieldSchemaType.ImageFile,
|
||||
required: false,
|
||||
placeholder: 'Upload logo',
|
||||
},
|
||||
]}
|
||||
selectMoreFields={{
|
||||
key: true,
|
||||
iconFileId: true,
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
title: 'Show ID and Key',
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
onClick: async (
|
||||
item: Probe,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction
|
||||
) => {
|
||||
try {
|
||||
setCurrentProbe(item);
|
||||
setShowKeyModal(true);
|
||||
{
|
||||
field: {
|
||||
iconFile: true,
|
||||
},
|
||||
title: "Probe Logo",
|
||||
fieldType: FormFieldSchemaType.ImageFile,
|
||||
required: false,
|
||||
placeholder: "Upload logo",
|
||||
},
|
||||
]}
|
||||
selectMoreFields={{
|
||||
key: true,
|
||||
iconFileId: true,
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
title: "Show ID and Key",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
onClick: async (
|
||||
item: Probe,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction,
|
||||
) => {
|
||||
try {
|
||||
setCurrentProbe(item);
|
||||
setShowKeyModal(true);
|
||||
|
||||
onCompleteAction();
|
||||
} catch (err) {
|
||||
onCompleteAction();
|
||||
onError(err as Error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: 'Description',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
onCompleteAction();
|
||||
} catch (err) {
|
||||
onCompleteAction();
|
||||
onError(err as Error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
return <ProbeElement probe={item} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: 'Description',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
lastAlive: true,
|
||||
},
|
||||
title: 'Status',
|
||||
type: FieldType.Text,
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
if (
|
||||
item &&
|
||||
item['lastAlive'] &&
|
||||
OneUptimeDate.getNumberOfMinutesBetweenDates(
|
||||
OneUptimeDate.fromString(item['lastAlive']),
|
||||
OneUptimeDate.getCurrentDate()
|
||||
) < 5
|
||||
) {
|
||||
return (
|
||||
<Statusbubble
|
||||
text={'Connected'}
|
||||
color={Green}
|
||||
shouldAnimate={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
return <ProbeElement probe={item} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
lastAlive: true,
|
||||
},
|
||||
title: "Status",
|
||||
type: FieldType.Text,
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
if (
|
||||
item &&
|
||||
item["lastAlive"] &&
|
||||
OneUptimeDate.getNumberOfMinutesBetweenDates(
|
||||
OneUptimeDate.fromString(item["lastAlive"]),
|
||||
OneUptimeDate.getCurrentDate(),
|
||||
) < 5
|
||||
) {
|
||||
return (
|
||||
<Statusbubble
|
||||
text={"Connected"}
|
||||
color={Green}
|
||||
shouldAnimate={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Statusbubble
|
||||
text={'Disconnected'}
|
||||
color={Red}
|
||||
shouldAnimate={false}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showKeyModal && currentProbe ? (
|
||||
<ConfirmModal
|
||||
title={`Probe Key`}
|
||||
description={
|
||||
<div>
|
||||
<span>
|
||||
Here is your probe key. Please keep this a
|
||||
secret.
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>Probe ID: </b>{' '}
|
||||
{currentProbe['_id']?.toString()}
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>Probe Key: </b>{' '}
|
||||
{currentProbe['key']?.toString()}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
submitButtonText={'Close'}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
onSubmit={async () => {
|
||||
setShowKeyModal(false);
|
||||
}}
|
||||
return (
|
||||
<Statusbubble
|
||||
text={"Disconnected"}
|
||||
color={Red}
|
||||
shouldAnimate={false}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showKeyModal && currentProbe ? (
|
||||
<ConfirmModal
|
||||
title={`Probe Key`}
|
||||
description={
|
||||
<div>
|
||||
<span>Here is your probe key. Please keep this a secret.</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>Probe ID: </b> {currentProbe["_id"]?.toString()}
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>Probe Key: </b> {currentProbe["key"]?.toString()}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
submitButtonText={"Close"}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
onSubmit={async () => {
|
||||
setShowKeyModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu';
|
||||
import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem';
|
||||
import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu";
|
||||
import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem";
|
||||
import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection";
|
||||
import React, { ReactElement } from "react";
|
||||
|
||||
const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
|
||||
return (
|
||||
<SideMenu>
|
||||
<SideMenuSection title="Basic">
|
||||
{/* <SideMenuItem
|
||||
return (
|
||||
<SideMenu>
|
||||
<SideMenuSection title="Basic">
|
||||
{/* <SideMenuItem
|
||||
link={{
|
||||
title: 'Host',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
@@ -20,62 +20,62 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
|
||||
}}
|
||||
icon={IconProp.Globe}
|
||||
/> */}
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'Authentication',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Lock}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Authentication",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Lock}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Notifications">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'Emails',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_SMTP] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Email}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'Call and SMS',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuSection title="Notifications">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Emails",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_SMTP] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Email}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Call and SMS",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Monitoring">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'Global Probes',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_PROBES] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Signal}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuSection title="API and Integrations">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'API Key',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_API_KEY] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Code}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
</SideMenu>
|
||||
);
|
||||
<SideMenuSection title="Monitoring">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Global Probes",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_PROBES] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Signal}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuSection title="API and Integrations">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "API Key",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_API_KEY] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Code}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
</SideMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardSideMenu;
|
||||
|
||||
@@ -1,127 +1,218 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import User from 'Model/Models/User';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import { ErrorFunction } from "Common/Types/FunctionTypes";
|
||||
import { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
|
||||
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import API from "CommonUI/src/Utils/API/API";
|
||||
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import User from "Model/Models/User";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
|
||||
const Users: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<Page
|
||||
title={'Users'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
const [showConfirmVerifyEmailModal, setShowConfirmVerifyEmailModal] =
|
||||
useState<boolean>(false);
|
||||
const [selectedUser, setSelectedUser] = useState<User | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [isConfimModalLoading, setIsConfirmModalLoading] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [refreshItemsTrigger, setRefreshItemsTrigger] =
|
||||
useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={"Users"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Users",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ModelTable<User>
|
||||
modelType={User}
|
||||
id="users-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
showViewIdButton={true}
|
||||
refreshToggle={refreshItemsTrigger}
|
||||
isCreateable={true}
|
||||
name="Users"
|
||||
isViewable={false}
|
||||
cardProps={{
|
||||
title: "Users",
|
||||
description: "Here is a list of users in OneUptime.",
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
title: "Verify Email",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
isVisible: (item: User) => {
|
||||
return !item.isEmailVerified;
|
||||
},
|
||||
onClick: async (
|
||||
item: User,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction,
|
||||
) => {
|
||||
try {
|
||||
setSelectedUser(item);
|
||||
setShowConfirmVerifyEmailModal(true);
|
||||
|
||||
onCompleteAction();
|
||||
} catch (err) {
|
||||
onCompleteAction();
|
||||
onError(err as Error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
noItemsMessage={"No users found."}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: "Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
placeholder: "email@company.com",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: "Password",
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
required: true,
|
||||
placeholder: "Password",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Full Name",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "John Smith",
|
||||
},
|
||||
]}
|
||||
showRefreshButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Full Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: "Email",
|
||||
type: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Full Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: "Email",
|
||||
type: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isEmailVerified: true,
|
||||
},
|
||||
title: "Email Verified",
|
||||
type: FieldType.Boolean,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{error ? (
|
||||
<ConfirmModal
|
||||
title={`Error`}
|
||||
description={error}
|
||||
submitButtonText={"Close"}
|
||||
onSubmit={async () => {
|
||||
setError(null);
|
||||
}}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{showConfirmVerifyEmailModal && selectedUser ? (
|
||||
<ConfirmModal
|
||||
title={`Verify Email`}
|
||||
description={`Are you sure you want to verify the email - ${selectedUser.email}?`}
|
||||
isLoading={isConfimModalLoading}
|
||||
submitButtonText={"Verify Email"}
|
||||
onClose={async () => {
|
||||
setShowConfirmVerifyEmailModal(false);
|
||||
setSelectedUser(null);
|
||||
}}
|
||||
onSubmit={async () => {
|
||||
try {
|
||||
setIsConfirmModalLoading(true);
|
||||
await ModelAPI.updateById<User>({
|
||||
modelType: User,
|
||||
id: selectedUser.id!,
|
||||
data: {
|
||||
isEmailVerified: true,
|
||||
},
|
||||
{
|
||||
title: 'Users',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USERS] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ModelTable<User>
|
||||
modelType={User}
|
||||
id="users-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
showViewIdButton={true}
|
||||
isCreateable={true}
|
||||
name="Users"
|
||||
isViewable={false}
|
||||
cardProps={{
|
||||
title: 'Users',
|
||||
description: 'Here is a list of users in OneUptime.',
|
||||
}}
|
||||
noItemsMessage={'No users found.'}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
placeholder: 'email@company.com',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: 'Password',
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
required: true,
|
||||
placeholder: 'Password',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Full Name',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: 'John Smith',
|
||||
},
|
||||
]}
|
||||
showRefreshButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Full Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
type: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Full Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
type: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyMessage(err as Error));
|
||||
}
|
||||
|
||||
setRefreshItemsTrigger(!refreshItemsTrigger);
|
||||
setIsConfirmModalLoading(false);
|
||||
setShowConfirmVerifyEmailModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Users;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
|
||||
export default class AdminModelAPI extends ModelAPI {
|
||||
public static override getCommonHeaders(): Dictionary<string> {
|
||||
return {};
|
||||
}
|
||||
public static override getCommonHeaders(): Dictionary<string> {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
enum PageMap {
|
||||
INIT = 'INIT',
|
||||
HOME = 'HOME',
|
||||
LOGOUT = 'LOGOUT',
|
||||
SETTINGS = 'SETTINGS',
|
||||
USERS = 'USERS',
|
||||
PROJECTS = 'PROJECTS',
|
||||
INIT = "INIT",
|
||||
HOME = "HOME",
|
||||
LOGOUT = "LOGOUT",
|
||||
SETTINGS = "SETTINGS",
|
||||
USERS = "USERS",
|
||||
PROJECTS = "PROJECTS",
|
||||
|
||||
SETTINGS_HOST = 'SETTINGS_HOST',
|
||||
SETTINGS_SMTP = 'SETTINGS_SMTP',
|
||||
SETTINGS_CALL_AND_SMS = 'SETTINGS_CALL_AND_SMS',
|
||||
SETTINGS_PROBES = 'SETTINGS_PROBES',
|
||||
SETTINGS_AUTHENTICATION = 'SETTINGS_AUTHENTICATION',
|
||||
SETTINGS_API_KEY = 'SETTINGS_API_KEY',
|
||||
SETTINGS_HOST = "SETTINGS_HOST",
|
||||
SETTINGS_SMTP = "SETTINGS_SMTP",
|
||||
SETTINGS_CALL_AND_SMS = "SETTINGS_CALL_AND_SMS",
|
||||
SETTINGS_PROBES = "SETTINGS_PROBES",
|
||||
SETTINGS_AUTHENTICATION = "SETTINGS_AUTHENTICATION",
|
||||
SETTINGS_API_KEY = "SETTINGS_API_KEY",
|
||||
}
|
||||
|
||||
export default PageMap;
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import PageMap from './PageMap';
|
||||
import RouteParams from './RouteParams';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import PageMap from "./PageMap";
|
||||
import RouteParams from "./RouteParams";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
|
||||
const RouteMap: Dictionary<Route> = {
|
||||
[PageMap.INIT]: new Route(`/admin`),
|
||||
[PageMap.HOME]: new Route(`/admin`),
|
||||
[PageMap.LOGOUT]: new Route(`/admin/logout`),
|
||||
[PageMap.SETTINGS]: new Route(`/admin/settings/host`),
|
||||
[PageMap.PROJECTS]: new Route(`/admin/projects`),
|
||||
[PageMap.USERS]: new Route(`/admin/users`),
|
||||
[PageMap.SETTINGS_HOST]: new Route(`/admin/settings/host`),
|
||||
[PageMap.SETTINGS_SMTP]: new Route(`/admin/settings/smtp`),
|
||||
[PageMap.SETTINGS_CALL_AND_SMS]: new Route(`/admin/settings/call-and-sms`),
|
||||
[PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`),
|
||||
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
|
||||
`/admin/settings/authentication`
|
||||
),
|
||||
[PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`),
|
||||
[PageMap.INIT]: new Route(`/admin`),
|
||||
[PageMap.HOME]: new Route(`/admin`),
|
||||
[PageMap.LOGOUT]: new Route(`/admin/logout`),
|
||||
[PageMap.SETTINGS]: new Route(`/admin/settings/host`),
|
||||
[PageMap.PROJECTS]: new Route(`/admin/projects`),
|
||||
[PageMap.USERS]: new Route(`/admin/users`),
|
||||
[PageMap.SETTINGS_HOST]: new Route(`/admin/settings/host`),
|
||||
[PageMap.SETTINGS_SMTP]: new Route(`/admin/settings/smtp`),
|
||||
[PageMap.SETTINGS_CALL_AND_SMS]: new Route(`/admin/settings/call-and-sms`),
|
||||
[PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`),
|
||||
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
|
||||
`/admin/settings/authentication`,
|
||||
),
|
||||
[PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`),
|
||||
};
|
||||
|
||||
export class RouteUtil {
|
||||
public static populateRouteParams(
|
||||
route: Route,
|
||||
props?: {
|
||||
modelId?: ObjectID;
|
||||
subModelId?: ObjectID;
|
||||
}
|
||||
): Route {
|
||||
// populate projectid
|
||||
public static populateRouteParams(
|
||||
route: Route,
|
||||
props?: {
|
||||
modelId?: ObjectID;
|
||||
subModelId?: ObjectID;
|
||||
},
|
||||
): Route {
|
||||
// populate projectid
|
||||
|
||||
const tempRoute: Route = new Route(route.toString());
|
||||
const tempRoute: Route = new Route(route.toString());
|
||||
|
||||
if (props && props.modelId) {
|
||||
route = tempRoute.addRouteParam(
|
||||
RouteParams.ModelID,
|
||||
props.modelId.toString()
|
||||
);
|
||||
}
|
||||
|
||||
if (props && props.subModelId) {
|
||||
route = tempRoute.addRouteParam(
|
||||
RouteParams.SubModelID,
|
||||
props.subModelId.toString()
|
||||
);
|
||||
}
|
||||
|
||||
return tempRoute;
|
||||
if (props && props.modelId) {
|
||||
route = tempRoute.addRouteParam(
|
||||
RouteParams.ModelID,
|
||||
props.modelId.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
if (props && props.subModelId) {
|
||||
route = tempRoute.addRouteParam(
|
||||
RouteParams.SubModelID,
|
||||
props.subModelId.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return tempRoute;
|
||||
}
|
||||
}
|
||||
|
||||
export default RouteMap;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
enum RouteParams {
|
||||
ModelID = ':id',
|
||||
SubModelID = ':subModelId',
|
||||
ModelID = ":id",
|
||||
SubModelID = ":subModelId",
|
||||
}
|
||||
|
||||
export default RouteParams;
|
||||
|
||||
@@ -1,82 +1,84 @@
|
||||
require('ts-loader');
|
||||
require('file-loader');
|
||||
require('style-loader');
|
||||
require('css-loader');
|
||||
require('sass-loader');
|
||||
require("ts-loader");
|
||||
require("file-loader");
|
||||
require("style-loader");
|
||||
require("css-loader");
|
||||
require("sass-loader");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const dotenv = require('dotenv');
|
||||
const express = require('express');
|
||||
const dotenv = require("dotenv");
|
||||
const express = require("express");
|
||||
|
||||
const readEnvFile = (pathToFile) => {
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
const env = {};
|
||||
|
||||
const env = {};
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
return env;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/admin/dist/",
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/admin/dist/",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
|
||||
alias: {
|
||||
react: path.resolve("./node_modules/react"),
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'],
|
||||
alias: {
|
||||
react: path.resolve('./node_modules/react'),
|
||||
}
|
||||
},
|
||||
externals: {
|
||||
'react-native-sqlite-storage': 'react-native-sqlite-storage'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process': {
|
||||
'env': {
|
||||
...readEnvFile('/usr/src/app/dev-env/.env')
|
||||
}
|
||||
}
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: 'ts-loader'
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ['style-loader', 'css-loader', "sass-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: 'file-loader'
|
||||
}
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
externals: {
|
||||
"react-native-sqlite-storage": "react-native-sqlite-storage",
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
process: {
|
||||
env: {
|
||||
...readEnvFile("/usr/src/app/dev-env/.env"),
|
||||
},
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use('/admin/assets', express.static(path.resolve(__dirname, 'public', 'assets')));
|
||||
return middlewares;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: "ts-loader",
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ["style-loader", "css-loader", "sass-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: "file-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
devtool: 'eval-source-map',
|
||||
}
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use(
|
||||
"/admin/assets",
|
||||
express.static(path.resolve(__dirname, "public", "assets")),
|
||||
);
|
||||
return middlewares;
|
||||
},
|
||||
},
|
||||
devtool: "eval-source-map",
|
||||
};
|
||||
|
||||
@@ -1,89 +1,83 @@
|
||||
import AuthenticationServiceHandler from "./Service/Authentication";
|
||||
import DataTypeServiceHandler from "./Service/DataType";
|
||||
import ErrorServiceHandler from "./Service/Errors";
|
||||
import IntroductionServiceHandler from "./Service/Introduction";
|
||||
import ModelServiceHandler from "./Service/Model";
|
||||
import PageNotFoundServiceHandler from "./Service/PageNotFound";
|
||||
import PaginationServiceHandler from "./Service/Pagination";
|
||||
import PermissionServiceHandler from "./Service/Permissions";
|
||||
import StatusServiceHandler from "./Service/Status";
|
||||
import { StaticPath } from "./Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "./Utils/Resources";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import FeatureSet from "CommonServer/Types/FeatureSet";
|
||||
import Express, {
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressStatic,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import ResourceUtil, { ModelDocumentation } from './Utils/Resources';
|
||||
import IntroductionServiceHandler from './Service/Introduction';
|
||||
import ErrorServiceHandler from './Service/Errors';
|
||||
import PermissionServiceHandler from './Service/Permissions';
|
||||
import AuthenticationServiceHandler from './Service/Authentication';
|
||||
import PageNotFoundServiceHandler from './Service/PageNotFound';
|
||||
import ModelServiceHandler from './Service/Model';
|
||||
import PaginationServiceHandler from './Service/Pagination';
|
||||
import StatusServiceHandler from './Service/Status';
|
||||
import DataTypeServiceHandler from './Service/DataType';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import { StaticPath } from './Utils/Config';
|
||||
import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressStatic,
|
||||
} from "CommonServer/Utils/Express";
|
||||
|
||||
const APIReferenceFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
init: async (): Promise<void> => {
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.use('/reference', ExpressStatic(StaticPath, { maxAge: 2592000 }));
|
||||
app.use("/reference", ExpressStatic(StaticPath, { maxAge: 2592000 }));
|
||||
|
||||
// Index page
|
||||
app.get(
|
||||
['/reference'],
|
||||
(_req: ExpressRequest, res: ExpressResponse) => {
|
||||
return res.redirect('/reference/introduction');
|
||||
}
|
||||
);
|
||||
// Index page
|
||||
app.get(["/reference"], (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
return res.redirect("/reference/introduction");
|
||||
});
|
||||
|
||||
app.get(
|
||||
['/reference/page-not-found'],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
);
|
||||
app.get(
|
||||
["/reference/page-not-found"],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
// All Pages
|
||||
app.get(
|
||||
['/reference/:page'],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
const page: string | undefined = req.params['page'];
|
||||
// All Pages
|
||||
app.get(
|
||||
["/reference/:page"],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
const page: string | undefined = req.params["page"];
|
||||
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
|
||||
if (req.params['page'] === 'permissions') {
|
||||
return PermissionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'authentication') {
|
||||
return AuthenticationServiceHandler.executeResponse(
|
||||
req,
|
||||
res
|
||||
);
|
||||
} else if (req.params['page'] === 'pagination') {
|
||||
return PaginationServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'errors') {
|
||||
return ErrorServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'introduction') {
|
||||
return IntroductionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'status') {
|
||||
return StatusServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'data-types') {
|
||||
return DataTypeServiceHandler.executeResponse(req, res);
|
||||
} else if (currentResource) {
|
||||
return ModelServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
// page not found
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
);
|
||||
if (req.params["page"] === "permissions") {
|
||||
return PermissionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "authentication") {
|
||||
return AuthenticationServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "pagination") {
|
||||
return PaginationServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "errors") {
|
||||
return ErrorServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "introduction") {
|
||||
return IntroductionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "status") {
|
||||
return StatusServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "data-types") {
|
||||
return DataTypeServiceHandler.executeResponse(req, res);
|
||||
} else if (currentResource) {
|
||||
return ModelServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
// page not found
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
app.get('/reference/*', (req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
});
|
||||
},
|
||||
app.get("/reference/*", (req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default APIReferenceFeatureSet;
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageTitle = 'Authentication';
|
||||
pageDescription =
|
||||
'Learn how to authenticate requests with OneUptime API';
|
||||
pageTitle = "Authentication";
|
||||
pageDescription = "Learn how to authenticate requests with OneUptime API";
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +1,126 @@
|
||||
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import LocalFile from 'CommonServer/Utils/LocalFile';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { CodeExamplesPath, ViewsPath } from '../Utils/Config';
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import LocalCache from "CommonServer/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
import LocalFile from "CommonServer/Utils/LocalFile";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
const pageData: any = {};
|
||||
|
||||
pageData.selectCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'select',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/Select.md`
|
||||
);
|
||||
}
|
||||
pageData.selectCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"select",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/Select.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.sortCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"sort",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/Sort.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/EqualTo.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToOrNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to-or-null",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/EqualToOrNull.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.sortCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'sort',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/Sort.md`
|
||||
);
|
||||
}
|
||||
pageData.greaterThanCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/GreaterThan.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'equal-to',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/EqualTo.md`
|
||||
);
|
||||
}
|
||||
pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than-or-equal",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/GreaterThanOrEqual.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToOrNullCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'equal-to-or-null',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/EqualToOrNull.md`
|
||||
);
|
||||
}
|
||||
pageData.lessThanCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThan.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.greaterThanCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'greater-than',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/GreaterThan.md`
|
||||
);
|
||||
}
|
||||
pageData.lessThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThanOrEqual.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'greater-than-or-equal',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/GreaterThanOrEqual.md`
|
||||
);
|
||||
}
|
||||
pageData.isNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"is-null",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/IsNull.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.notNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-null",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/NotNull.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.notEqualToCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-equals",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/NotEqualTo.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.lessThanCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'less-than',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThan.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.lessThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'less-than-or-equal',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThanOrEqual.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.isNullCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'is-null',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/IsNull.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.notNullCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'not-null',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/NotNull.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.notEqualToCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'not-equals',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/NotEqualTo.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: 'data-types',
|
||||
pageTitle: 'Data Types',
|
||||
pageDescription:
|
||||
'Data Types that can be used to interact with OneUptime API',
|
||||
resources: Resources,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
res.status(200);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: "data-types",
|
||||
pageTitle: "Data Types",
|
||||
pageDescription:
|
||||
"Data Types that can be used to interact with OneUptime API",
|
||||
resources: Resources,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageTitle = 'Errors';
|
||||
pageDescription = 'Learn more about how we return errors from API';
|
||||
pageTitle = "Errors";
|
||||
pageDescription = "Learn more about how we return errors from API";
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const FeaturedResources: Array<ModelDocumentation> =
|
||||
ResourceUtil.getFeaturedResources();
|
||||
ResourceUtil.getFeaturedResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageData.featuredResources = FeaturedResources;
|
||||
pageTitle = 'Introduction';
|
||||
pageDescription = 'API Reference for OneUptime';
|
||||
pageData.featuredResources = FeaturedResources;
|
||||
pageTitle = "Introduction";
|
||||
pageDescription = "API Reference for OneUptime";
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,244 +1,238 @@
|
||||
import { ColumnAccessControl } from 'Common/Types/BaseDatabase/AccessControl';
|
||||
import { getTableColumns } from 'Common/Types/Database/TableColumn';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import PageNotFoundServiceHandler from "./PageNotFound";
|
||||
import { AppApiRoute } from "Common/ServiceRoute";
|
||||
import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl";
|
||||
import { getTableColumns } from "Common/Types/Database/TableColumn";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Permission, {
|
||||
PermissionHelper,
|
||||
PermissionProps,
|
||||
} from 'Common/Types/Permission';
|
||||
import { AppApiRoute } from 'Common/ServiceRoute';
|
||||
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import LocalFile from 'CommonServer/Utils/LocalFile';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import PageNotFoundServiceHandler from './PageNotFound';
|
||||
import { CodeExamplesPath, ViewsPath } from '../Utils/Config';
|
||||
PermissionHelper,
|
||||
PermissionProps,
|
||||
} from "Common/Types/Permission";
|
||||
import LocalCache from "CommonServer/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
import LocalFile from "CommonServer/Utils/LocalFile";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
|
||||
const PermissionDictionary: Dictionary<PermissionProps> =
|
||||
PermissionHelper.getAllPermissionPropsAsDictionary();
|
||||
PermissionHelper.getAllPermissionPropsAsDictionary();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
let page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
let page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
|
||||
if (!currentResource) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
// Resource Page.
|
||||
pageTitle = currentResource.name;
|
||||
pageDescription = currentResource.description;
|
||||
|
||||
page = 'model';
|
||||
|
||||
const tableColumns: any = getTableColumns(currentResource.model);
|
||||
|
||||
for (const key in tableColumns) {
|
||||
const accessControl: ColumnAccessControl | null =
|
||||
currentResource.model.getColumnAccessControlFor(key);
|
||||
|
||||
if (!accessControl) {
|
||||
// remove columns with no access
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
accessControl?.create.length === 0 &&
|
||||
accessControl?.read.length === 0 &&
|
||||
accessControl?.update.length === 0
|
||||
) {
|
||||
// remove columns with no access
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
tableColumns[key].permissions = accessControl;
|
||||
}
|
||||
|
||||
delete tableColumns['deletedAt'];
|
||||
delete tableColumns['deletedByUserId'];
|
||||
delete tableColumns['deletedByUser'];
|
||||
delete tableColumns['version'];
|
||||
|
||||
pageData.title = currentResource.model.singularName;
|
||||
pageData.description = currentResource.model.tableDescription;
|
||||
pageData.columns = tableColumns;
|
||||
pageData.tablePermissions = {
|
||||
read: currentResource.model.readRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
}
|
||||
),
|
||||
update: currentResource.model.updateRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
}
|
||||
),
|
||||
delete: currentResource.model.deleteRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
}
|
||||
),
|
||||
create: currentResource.model.createRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
pageData.listRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'list-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ListRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.itemRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'item-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ItemRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.itemResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'item-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ItemResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.countRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'count-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CountRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.countResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'count-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CountResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.updateRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'update-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/UpdateRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.updateResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'update-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/UpdateResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.createRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'create-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CreateRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.createResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'create-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CreateResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.deleteRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'delete-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/DeleteRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.deleteResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'delete-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/DeleteResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.listResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'list-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ListResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.exampleObjectID = ObjectID.generate();
|
||||
|
||||
pageData.apiPath =
|
||||
AppApiRoute.toString() +
|
||||
currentResource.model.crudApiPath?.toString();
|
||||
|
||||
pageData.isMasterAdminApiDocs =
|
||||
currentResource.model.isMasterAdminApiDocs;
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
|
||||
if (!currentResource) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
// Resource Page.
|
||||
pageTitle = currentResource.name;
|
||||
pageDescription = currentResource.description;
|
||||
|
||||
page = "model";
|
||||
|
||||
const tableColumns: any = getTableColumns(currentResource.model);
|
||||
|
||||
for (const key in tableColumns) {
|
||||
const accessControl: ColumnAccessControl | null =
|
||||
currentResource.model.getColumnAccessControlFor(key);
|
||||
|
||||
if (!accessControl) {
|
||||
// remove columns with no access
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
accessControl?.create.length === 0 &&
|
||||
accessControl?.read.length === 0 &&
|
||||
accessControl?.update.length === 0
|
||||
) {
|
||||
// remove columns with no access
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
tableColumns[key].permissions = accessControl;
|
||||
}
|
||||
|
||||
delete tableColumns["deletedAt"];
|
||||
delete tableColumns["deletedByUserId"];
|
||||
delete tableColumns["deletedByUser"];
|
||||
delete tableColumns["version"];
|
||||
|
||||
pageData.title = currentResource.model.singularName;
|
||||
pageData.description = currentResource.model.tableDescription;
|
||||
pageData.columns = tableColumns;
|
||||
pageData.tablePermissions = {
|
||||
read: currentResource.model.readRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
},
|
||||
),
|
||||
update: currentResource.model.updateRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
},
|
||||
),
|
||||
delete: currentResource.model.deleteRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
},
|
||||
),
|
||||
create: currentResource.model.createRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
pageData.listRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-request",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/Model/ListRequest.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.itemRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-request",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/Model/ItemRequest.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.itemResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ItemResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.countRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CountRequest.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.countResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CountResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.updateRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/UpdateRequest.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.updateResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/UpdateResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.createRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CreateRequest.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.createResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CreateResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.deleteRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/DeleteRequest.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.deleteResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/DeleteResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.listResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ListResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.exampleObjectID = ObjectID.generate();
|
||||
|
||||
pageData.apiPath =
|
||||
AppApiRoute.toString() + currentResource.model.crudApiPath?.toString();
|
||||
|
||||
pageData.isMasterAdminApiDocs = currentResource.model.isMasterAdminApiDocs;
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: '404',
|
||||
pageTitle: 'Page Not Found',
|
||||
pageDescription: "Page you're looking for is not found.",
|
||||
resources: Resources,
|
||||
pageData: {},
|
||||
});
|
||||
}
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: "404",
|
||||
pageTitle: "Page Not Found",
|
||||
pageDescription: "Page you're looking for is not found.",
|
||||
resources: Resources,
|
||||
pageData: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import LocalFile from 'CommonServer/Utils/LocalFile';
|
||||
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
|
||||
import { CodeExamplesPath, ViewsPath } from '../Utils/Config';
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import LocalCache from "CommonServer/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
import LocalFile from "CommonServer/Utils/LocalFile";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageTitle = 'Pagination';
|
||||
pageDescription = 'Learn how to paginate requests with OneUptime API';
|
||||
pageTitle = "Pagination";
|
||||
pageDescription = "Learn how to paginate requests with OneUptime API";
|
||||
|
||||
pageData.responseCode = await LocalCache.getOrSetString(
|
||||
'pagination',
|
||||
'response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Pagination/Response.md`
|
||||
);
|
||||
}
|
||||
pageData.responseCode = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Pagination/Response.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.requestCode = await LocalCache.getOrSetString(
|
||||
'pagination',
|
||||
'request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Pagination/Request.md`
|
||||
);
|
||||
}
|
||||
pageData.requestCode = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Pagination/Request.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import { PermissionHelper, PermissionProps } from 'Common/Types/Permission';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { PermissionHelper, PermissionProps } from "Common/Types/Permission";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageTitle = 'Permissions';
|
||||
pageDescription = 'Learn how permissions work with OneUptime';
|
||||
pageTitle = "Permissions";
|
||||
pageDescription = "Learn how permissions work with OneUptime";
|
||||
|
||||
pageData.permissions = PermissionHelper.getAllPermissionProps().filter(
|
||||
(i: PermissionProps) => {
|
||||
return i.isAssignableToTenant;
|
||||
}
|
||||
);
|
||||
pageData.permissions = PermissionHelper.getAllPermissionProps().filter(
|
||||
(i: PermissionProps) => {
|
||||
return i.isAssignableToTenant;
|
||||
},
|
||||
);
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
res.status(200);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: 'status',
|
||||
pageTitle: 'Status',
|
||||
pageDescription: '200 - Success',
|
||||
resources: Resources,
|
||||
pageData: {},
|
||||
});
|
||||
}
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
res.status(200);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: "status",
|
||||
pageTitle: "Status",
|
||||
pageDescription: "200 - Success",
|
||||
resources: Resources,
|
||||
pageData: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const ViewsPath: string = '/usr/src/app/FeatureSet/ApiReference/views';
|
||||
export const StaticPath: string = '/usr/src/app/FeatureSet/ApiReference/Static';
|
||||
export const ViewsPath: string = "/usr/src/app/FeatureSet/ApiReference/views";
|
||||
export const StaticPath: string = "/usr/src/app/FeatureSet/ApiReference/Static";
|
||||
export const CodeExamplesPath: string =
|
||||
'/usr/src/app/FeatureSet/ApiReference/CodeExamples';
|
||||
"/usr/src/app/FeatureSet/ApiReference/CodeExamples";
|
||||
|
||||
@@ -1,74 +1,73 @@
|
||||
import BaseModel from 'Common/Models/BaseModel';
|
||||
import Models from 'Model/Models/Index';
|
||||
import ArrayUtil from 'Common/Types/ArrayUtil';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig';
|
||||
import BaseModel from "Common/Models/BaseModel";
|
||||
import ArrayUtil from "Common/Types/ArrayUtil";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { IsBillingEnabled } from "CommonServer/EnvironmentConfig";
|
||||
import Models from "Model/Models/Index";
|
||||
|
||||
export interface ModelDocumentation {
|
||||
name: string;
|
||||
path: string;
|
||||
model: BaseModel;
|
||||
description: string;
|
||||
name: string;
|
||||
path: string;
|
||||
model: BaseModel;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class ResourceUtil {
|
||||
public static getResources(): Array<ModelDocumentation> {
|
||||
const resources: Array<ModelDocumentation> = Models.filter(
|
||||
(model: typeof BaseModel) => {
|
||||
const modelInstance: BaseModel = new model();
|
||||
let showDocs: boolean = modelInstance.enableDocumentation;
|
||||
public static getResources(): Array<ModelDocumentation> {
|
||||
const resources: Array<ModelDocumentation> = Models.filter(
|
||||
(model: typeof BaseModel) => {
|
||||
const modelInstance: BaseModel = new model();
|
||||
let showDocs: boolean = modelInstance.enableDocumentation;
|
||||
|
||||
if (modelInstance.isMasterAdminApiDocs && IsBillingEnabled) {
|
||||
showDocs = false;
|
||||
}
|
||||
|
||||
return showDocs;
|
||||
}
|
||||
)
|
||||
.map((model: typeof BaseModel) => {
|
||||
const modelInstance: BaseModel = new model();
|
||||
|
||||
return {
|
||||
name: modelInstance.singularName!,
|
||||
path: modelInstance.getAPIDocumentationPath(),
|
||||
model: modelInstance,
|
||||
description: modelInstance.tableDescription!,
|
||||
};
|
||||
})
|
||||
.sort(ArrayUtil.sortByFieldName('name'));
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
public static getFeaturedResources(): Array<ModelDocumentation> {
|
||||
const featuredResources: Array<string> = [
|
||||
'Monitor',
|
||||
'Scheduled Maintenance Event',
|
||||
'Status Page',
|
||||
'Incident',
|
||||
'Team',
|
||||
'On-Call Duty',
|
||||
'Label',
|
||||
'Team Member',
|
||||
];
|
||||
|
||||
return ResourceUtil.getResources().filter(
|
||||
(resource: ModelDocumentation) => {
|
||||
return featuredResources.includes(resource.name);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static getResourceDictionaryByPath(): Dictionary<ModelDocumentation> {
|
||||
const dict: Dictionary<ModelDocumentation> = {};
|
||||
|
||||
const resources: Array<ModelDocumentation> =
|
||||
ResourceUtil.getResources();
|
||||
|
||||
for (const resource of resources) {
|
||||
dict[resource.path] = resource;
|
||||
if (modelInstance.isMasterAdminApiDocs && IsBillingEnabled) {
|
||||
showDocs = false;
|
||||
}
|
||||
|
||||
return dict;
|
||||
return showDocs;
|
||||
},
|
||||
)
|
||||
.map((model: typeof BaseModel) => {
|
||||
const modelInstance: BaseModel = new model();
|
||||
|
||||
return {
|
||||
name: modelInstance.singularName!,
|
||||
path: modelInstance.getAPIDocumentationPath(),
|
||||
model: modelInstance,
|
||||
description: modelInstance.tableDescription!,
|
||||
};
|
||||
})
|
||||
.sort(ArrayUtil.sortByFieldName("name"));
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
public static getFeaturedResources(): Array<ModelDocumentation> {
|
||||
const featuredResources: Array<string> = [
|
||||
"Monitor",
|
||||
"Scheduled Maintenance Event",
|
||||
"Status Page",
|
||||
"Incident",
|
||||
"Team",
|
||||
"On-Call Duty",
|
||||
"Label",
|
||||
"Team Member",
|
||||
];
|
||||
|
||||
return ResourceUtil.getResources().filter(
|
||||
(resource: ModelDocumentation) => {
|
||||
return featuredResources.includes(resource.name);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public static getResourceDictionaryByPath(): Dictionary<ModelDocumentation> {
|
||||
const dict: Dictionary<ModelDocumentation> = {};
|
||||
|
||||
const resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
for (const resource of resources) {
|
||||
dict[resource.path] = resource;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,9 @@ Depending on your usage and budget, you can choose from different system require
|
||||
|
||||
|
||||
#### Prerequisites for Single-Server Deployment
|
||||
|
||||
Installation tutorial: [https://youtu.be/j1SWmMW2oL4](https://youtu.be/j1SWmMW2oL4)
|
||||
|
||||
Before you start the deployment process, please make sure you have:
|
||||
|
||||
- A server running Debian, Ubuntu, or RHEL derivative
|
||||
@@ -47,7 +50,10 @@ If you don't like to use npm or do not have it installed, run this instead:
|
||||
|
||||
```
|
||||
# Read env vars from config.env file and run docker-compose up.
|
||||
export $(grep -v '^#' config.env | xargs) && docker compose up --remove-orphans -d
|
||||
(export $(grep -v '^#' config.env | xargs) && docker compose up --remove-orphans -d)
|
||||
|
||||
# Use sudo if you're having permission issues with binding ports.
|
||||
sudo bash -c "(export $(grep -v '^#' config.env | xargs) && docker compose up --remove-orphans -d)"
|
||||
```
|
||||
|
||||
To update:
|
||||
|
||||
27
App/FeatureSet/Docs/Content/monitor/custom-code-monitor.md
Normal file
27
App/FeatureSet/Docs/Content/monitor/custom-code-monitor.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Custom Code Monitor
|
||||
|
||||
Custom Code Monitor allows you to write custom scripts to monitor your applications. You can use this feature to monitor your applications in a way that is not possible with the existing monitors. For example, you can have multi-step API requests.
|
||||
|
||||
#### Example
|
||||
|
||||
The following example shows how to use a Synthetic Monitor:
|
||||
|
||||
```javascript
|
||||
// You can use axios module.
|
||||
|
||||
await axios.get('https://api.example.com/');
|
||||
|
||||
// Axios Documentation here: https://axios-http.com/docs/intro
|
||||
|
||||
return {
|
||||
data: 'Hello World' // return any data you like here.
|
||||
};
|
||||
```
|
||||
|
||||
### Things to consider
|
||||
|
||||
- You can use `console.log` to log the data in the console. This will be available in the logs section of the monitor.
|
||||
- You can return the data from the script using the `return` statement.
|
||||
- This is a JavaScript script, so you can use all the JavaScript features in the script.
|
||||
- You can use `axios` module to make HTTP requests in the script. You can use it to make API calls from the script.
|
||||
- Timeout for the script is 2 minutes. If the script takes more than 2 mins, it will be terminated.
|
||||
34
App/FeatureSet/Docs/Content/monitor/monitor-secrets.md
Normal file
34
App/FeatureSet/Docs/Content/monitor/monitor-secrets.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Monitor Secrets
|
||||
|
||||
You can use secrets to store sensitive information that you want to use in your monitoring checks. Secrets are encrypted and stored securely.
|
||||
|
||||
### Adding a secret
|
||||
|
||||
To add a secret, please go to OneUptime Dashboard -> Project Settings -> Monitor Secrets -> Create Monitor Secret.
|
||||
|
||||

|
||||
|
||||
You can select which monitors have access to the secret. In this case we added `ApiKey` secret and selected monitors to have access to it.
|
||||
|
||||
**Please note**: Secrets are encrypted and stored securely. If you lose the secret, you will need to create a new secret. You cannot view or update the secret after its saved.
|
||||
|
||||
### Using a secret
|
||||
|
||||
You can use secrets in the following monitoring types:
|
||||
|
||||
- API (in request headers, request body, and URL)
|
||||
- Website, IP, Port, Ping, SSL Certificate (in URL)
|
||||
- Synthetic Monitor, Custom Code Monitor (in the code)
|
||||
|
||||
|
||||

|
||||
|
||||
To use a secret, add `{{monitorSecrets.SECRET_NAME}}` in the field where you want to use the secret. For example, in this case we added `{{monitorSecrets.ApiKey}}` in the Requets Header field.
|
||||
|
||||
|
||||
### Monitor Secret Permissions
|
||||
|
||||
You can select which monitors have access to the secret. You can also update the permissions at any time. So, if you want to add a new monitor to have access to the secret, you can do so by updating the permissions.
|
||||
|
||||
|
||||
|
||||
71
App/FeatureSet/Docs/Content/monitor/synthetic-monitor.md
Normal file
71
App/FeatureSet/Docs/Content/monitor/synthetic-monitor.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Synthetic Monitor
|
||||
|
||||
Synthetic monitoring is a way to proactively monitor your applications by simulating user interactions. You can create a synthetic monitor to check the availability and performance of your applications from different locations around the world.
|
||||
|
||||
#### Example
|
||||
|
||||
The following example shows how to use a Synthetic Monitor:
|
||||
|
||||
```javascript
|
||||
// You can use axios module, and page object from Playwright here.
|
||||
// Page Object is a class that represents a single page in a browser.
|
||||
|
||||
await page.goto('https://playwright.dev/');
|
||||
|
||||
// Playwright Documentation here: https://playwright.dev/docs/intro
|
||||
|
||||
// Here are some of the variables that you can use in the context of the monitored object:
|
||||
|
||||
console.log(browserType) // This will list the browser type in the current run context - Chromium, Firefox, Webkit
|
||||
|
||||
console.log(screenSizeType) // This will list the screen size type in the current run context - Mobile, Tablet, Desktop
|
||||
|
||||
// Playwright page object belongs to that specific browser context, so you can use it to interact with the browser.
|
||||
|
||||
// To take screenshots,
|
||||
|
||||
const screenshots = {};
|
||||
|
||||
screenshots['screenshot-name'] = await page.screenshot(); // you can save multiple screenshots and have them with different names.
|
||||
|
||||
// when you want to return a value, use return statement with data as a prop. You can also add screenshots in the screenshots array.
|
||||
|
||||
return {
|
||||
data: 'Hello World',
|
||||
screenshots: screenshots
|
||||
};
|
||||
```
|
||||
|
||||
### Use of Playwright
|
||||
|
||||
We use Playwright to simulate user interactions. You can use Playwright `page` object to interact with the browser and perform actions like clicking buttons, filling forms, and taking screenshots.
|
||||
|
||||
### Screenshots
|
||||
|
||||
You can take screenshots of the page at any point in the script. You can take multiple screenshots and return them in the screenshots array. These screenshots will be available in the OneUptime Dashboard for that specific monitor.
|
||||
|
||||
```javascript
|
||||
|
||||
// To take screenshots,
|
||||
|
||||
const screenshots = {};
|
||||
|
||||
screenshots['screenshot-name'] = await page.screenshot();
|
||||
|
||||
return {
|
||||
data: 'Hello World',
|
||||
screenshots: screenshots
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Things to consider
|
||||
|
||||
- You only have `page` object available in the context of the script. This is from Playwright Page class. You can use it to run all the interactions with the browser.
|
||||
- You can use `console.log` to log the data in the console. This will be available in the logs section of the monitor.
|
||||
- You can return the data from the script using the `return` statement. You can also return screenshots in the screenshots array.
|
||||
- You can use `browserType` and `screenSizeType` variables to get the browser type and screen size type in the current run context. Feel free to use them in your script if you like.
|
||||
- This is a JavaScript script, so you can use all the JavaScript features in the script.
|
||||
- You can use `axios` module to make HTTP requests in the script. You can use it to make API calls from the script.
|
||||
- If you are using oneuptime.com, you will always have the latest version of Playwright & browsers available in the context of the script. If you're self-hosting, please make sure you update the probes to have the latest version of Playwright and the browsers.
|
||||
- Timeout for the script is 2 minutes. If the script takes more than 2 mins, it will be terminated.
|
||||
@@ -1,93 +1,85 @@
|
||||
import 'ejs';
|
||||
import { ContentPath, StaticPath, ViewsPath } from "./Utils/Config";
|
||||
import DocsNav, { NavGroup, NavLink } from "./Utils/Nav";
|
||||
import DocsRender from "./Utils/Render";
|
||||
import FeatureSet from "CommonServer/Types/FeatureSet";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressStatic,
|
||||
ExpressApplication,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import { ContentPath, StaticPath, ViewsPath } from './Utils/Config';
|
||||
import DocsNav, { NavGroup, NavLink } from './Utils/Nav';
|
||||
import LocalFile from 'CommonServer/Utils/LocalFile';
|
||||
import DocsRender from './Utils/Render';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressStatic,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import LocalFile from "CommonServer/Utils/LocalFile";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import "ejs";
|
||||
|
||||
const DocsFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
init: async (): Promise<void> => {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.get('/docs', (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.redirect('/docs/introduction/getting-started');
|
||||
});
|
||||
app.get("/docs", (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.redirect("/docs/introduction/getting-started");
|
||||
});
|
||||
|
||||
app.get(
|
||||
'/docs/:categorypath/:pagepath',
|
||||
async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fullPath: string =
|
||||
`${_req.params['categorypath']}/${_req.params['pagepath']}`.toLowerCase();
|
||||
app.get(
|
||||
"/docs/:categorypath/:pagepath",
|
||||
async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fullPath: string =
|
||||
`${_req.params["categorypath"]}/${_req.params["pagepath"]}`.toLowerCase();
|
||||
|
||||
// read file from Content folder.
|
||||
let contentInMarkdown: string = await LocalFile.read(
|
||||
`${ContentPath}/${fullPath}.md`
|
||||
);
|
||||
// read file from Content folder.
|
||||
let contentInMarkdown: string = await LocalFile.read(
|
||||
`${ContentPath}/${fullPath}.md`,
|
||||
);
|
||||
|
||||
// remove first line from content because we dont want to show title in content. Title is already in nav.
|
||||
// remove first line from content because we dont want to show title in content. Title is already in nav.
|
||||
|
||||
contentInMarkdown = contentInMarkdown
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
.join('\n');
|
||||
contentInMarkdown = contentInMarkdown.split("\n").slice(1).join("\n");
|
||||
|
||||
const renderedContent: string = await DocsRender.render(
|
||||
contentInMarkdown
|
||||
);
|
||||
const renderedContent: string =
|
||||
await DocsRender.render(contentInMarkdown);
|
||||
|
||||
const currentCategory: NavGroup | undefined = DocsNav.find(
|
||||
(category: NavGroup) => {
|
||||
return category.links.find((link: NavLink) => {
|
||||
return link.url
|
||||
.toLocaleLowerCase()
|
||||
.includes(fullPath);
|
||||
});
|
||||
}
|
||||
);
|
||||
const currentCategory: NavGroup | undefined = DocsNav.find(
|
||||
(category: NavGroup) => {
|
||||
return category.links.find((link: NavLink) => {
|
||||
return link.url.toLocaleLowerCase().includes(fullPath);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const currrentNavLink: NavLink | undefined =
|
||||
currentCategory?.links.find((link: NavLink) => {
|
||||
return link.url
|
||||
.toLocaleLowerCase()
|
||||
.includes(fullPath);
|
||||
});
|
||||
const currrentNavLink: NavLink | undefined =
|
||||
currentCategory?.links.find((link: NavLink) => {
|
||||
return link.url.toLocaleLowerCase().includes(fullPath);
|
||||
});
|
||||
|
||||
if (!currentCategory || !currrentNavLink) {
|
||||
// render not found.
|
||||
if (!currentCategory || !currrentNavLink) {
|
||||
// render not found.
|
||||
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/NotFound`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/NotFound`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
|
||||
res.render(`${ViewsPath}/Index`, {
|
||||
nav: DocsNav,
|
||||
content: renderedContent,
|
||||
category: currentCategory,
|
||||
link: currrentNavLink,
|
||||
githubPath: fullPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
res.status(500);
|
||||
return res.render(`${ViewsPath}/ServerError`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
res.render(`${ViewsPath}/Index`, {
|
||||
nav: DocsNav,
|
||||
content: renderedContent,
|
||||
category: currentCategory,
|
||||
link: currrentNavLink,
|
||||
githubPath: fullPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
res.status(500);
|
||||
return res.render(`${ViewsPath}/ServerError`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.use('/docs/static', ExpressStatic(StaticPath));
|
||||
},
|
||||
app.use("/docs/static", ExpressStatic(StaticPath));
|
||||
},
|
||||
};
|
||||
|
||||
export default DocsFeatureSet;
|
||||
|
||||
BIN
App/FeatureSet/Docs/Static/images/CreateMonitorSecret.png
Normal file
BIN
App/FeatureSet/Docs/Static/images/CreateMonitorSecret.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 285 KiB |
BIN
App/FeatureSet/Docs/Static/images/UsingMonitorSecret.png
Normal file
BIN
App/FeatureSet/Docs/Static/images/UsingMonitorSecret.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
@@ -1,54 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check if this is debian based
|
||||
|
||||
# Check if system supports apt-get
|
||||
|
||||
if [ -x "$(command -v apt-get)" ]; then
|
||||
# Update apt-get
|
||||
sudo apt-get update
|
||||
# Install build-essential
|
||||
sudo apt-get install build-essential -y
|
||||
fi
|
||||
|
||||
# Check if system supports yum
|
||||
if [ -x "$(command -v yum)" ]; then
|
||||
# Update yum
|
||||
sudo yum update
|
||||
# Install build-essential
|
||||
sudo yum install gcc-c++ make -y
|
||||
fi
|
||||
|
||||
# Check if system supports apk
|
||||
if [ -x "$(command -v apk)" ]; then
|
||||
# Update apk
|
||||
sudo apk update
|
||||
# Install build-essential
|
||||
sudo apk add build-base
|
||||
fi
|
||||
|
||||
|
||||
# Install NVM
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
|
||||
|
||||
# Export to path
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
|
||||
|
||||
|
||||
# Refresh bash
|
||||
source ~/.bashrc
|
||||
|
||||
# Install latest Node.js via NVM
|
||||
nvm install node
|
||||
|
||||
# Make this nodejs version the default
|
||||
nvm alias default node
|
||||
|
||||
# Use the default version
|
||||
nvm use default
|
||||
|
||||
# Now install
|
||||
npm install -g tsx
|
||||
npm install -g @oneuptime/infrastructure-agent
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [-b bindir] [-d]"
|
||||
echo " -b sets the directory for the binary installation, default is ./bin"
|
||||
echo " -d enables debug mode"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Default parameters
|
||||
BINDIR=/usr/bin
|
||||
DEBUG=0
|
||||
|
||||
# Parse command-line options
|
||||
while getopts "b:d" opt; do
|
||||
case ${opt} in
|
||||
b )
|
||||
BINDIR=$OPTARG
|
||||
;;
|
||||
d )
|
||||
set -x
|
||||
DEBUG=1
|
||||
;;
|
||||
\? )
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "Installing to ${BINDIR}"
|
||||
mkdir -p "${BINDIR}"
|
||||
|
||||
# Detect platform and architecture
|
||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
ARCH=$(uname -m)
|
||||
case $ARCH in
|
||||
x86_64)
|
||||
ARCH=amd64
|
||||
;;
|
||||
aarch64)
|
||||
ARCH=arm64
|
||||
;;
|
||||
*arm*)
|
||||
ARCH=arm
|
||||
;;
|
||||
*)
|
||||
echo "Architecture $ARCH is not supported"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Fetch the latest release tag from GitHub
|
||||
REPO="oneuptime/oneuptime"
|
||||
API_URL="https://api.github.com/repos/${REPO}/releases/latest"
|
||||
TAG=$(curl -s ${API_URL} | grep '"tag_name":' | sed -E 's/.*"tag_name": "([^"]+)".*/\1/')
|
||||
|
||||
if [ "$TAG" = "" ]; then
|
||||
echo "Failed to find the latest release. Please check your internet connection or GitHub API limits."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Fetching the latest release: $TAG"
|
||||
|
||||
# Construct the URL for the binary release
|
||||
URL="https://github.com/${REPO}/releases/download/${TAG}/oneuptime-infrastructure-agent_${OS}_${ARCH}.tar.gz"
|
||||
|
||||
# Download and extract the binary
|
||||
curl -sL "${URL}" | tar xz -C "${BINDIR}"
|
||||
|
||||
# Check if the binary is executable
|
||||
if [ ! -x "${BINDIR}/oneuptime-infrastructure-agent" ]; then
|
||||
echo "Failed to install oneuptime-infrastructure-agent"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "oneuptime-infrastructure-agent installed successfully to ${BINDIR}. Please configure the agent using 'oneuptime-infrastructure-agent configure'."
|
||||
@@ -1,3 +1,3 @@
|
||||
export const ViewsPath: string = '/usr/src/app/FeatureSet/Docs/Views';
|
||||
export const StaticPath: string = '/usr/src/app/FeatureSet/Docs/Static';
|
||||
export const ContentPath: string = '/usr/src/app/FeatureSet/Docs/Content';
|
||||
export const ViewsPath: string = "/usr/src/app/FeatureSet/Docs/Views";
|
||||
export const StaticPath: string = "/usr/src/app/FeatureSet/Docs/Static";
|
||||
export const ContentPath: string = "/usr/src/app/FeatureSet/Docs/Content";
|
||||
|
||||
@@ -1,63 +1,75 @@
|
||||
export interface NavLink {
|
||||
title: string;
|
||||
url: string;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface NavGroup {
|
||||
title: string;
|
||||
links: NavLink[];
|
||||
title: string;
|
||||
links: NavLink[];
|
||||
}
|
||||
|
||||
const DocsNav: NavGroup[] = [
|
||||
{
|
||||
title: 'Introduction',
|
||||
links: [
|
||||
{
|
||||
title: 'Getting Started',
|
||||
url: '/docs/introduction/getting-started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Installation',
|
||||
links: [
|
||||
{
|
||||
title: 'Local Development',
|
||||
url: '/docs/installation/local-development',
|
||||
},
|
||||
{
|
||||
title: 'Docker Compose',
|
||||
url: '/docs/installation/docker-compose',
|
||||
},
|
||||
{
|
||||
title: 'Kubernetes and Helm',
|
||||
url: 'https://artifacthub.io/packages/helm/oneuptime/oneuptime',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Monitor',
|
||||
links: [
|
||||
{
|
||||
title: 'JavaScript Expressions',
|
||||
url: '/docs/monitor/javascript-expression',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Probe',
|
||||
links: [
|
||||
{ title: 'Custom Probes', url: '/docs/probe/custom-probe' },
|
||||
{ title: 'IP Addresses', url: '/docs/probe/ip-address' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Telemetry',
|
||||
links: [
|
||||
{ title: 'OpenTelemetry', url: '/docs/telemetry/open-telemetry' },
|
||||
{ title: 'Fluentd', url: '/docs/telemetry/fluentd' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Introduction",
|
||||
links: [
|
||||
{
|
||||
title: "Getting Started",
|
||||
url: "/docs/introduction/getting-started",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Installation",
|
||||
links: [
|
||||
{
|
||||
title: "Local Development",
|
||||
url: "/docs/installation/local-development",
|
||||
},
|
||||
{
|
||||
title: "Docker Compose",
|
||||
url: "/docs/installation/docker-compose",
|
||||
},
|
||||
{
|
||||
title: "Kubernetes and Helm",
|
||||
url: "https://artifacthub.io/packages/helm/oneuptime/oneuptime",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Monitor",
|
||||
links: [
|
||||
{
|
||||
title: "Custom Code Monitor",
|
||||
url: "/docs/monitor/custom-code-monitor",
|
||||
},
|
||||
{
|
||||
title: "Synthetic Monitor",
|
||||
url: "/docs/monitor/synthetic-monitor",
|
||||
},
|
||||
{
|
||||
title: "JavaScript Expressions",
|
||||
url: "/docs/monitor/javascript-expression",
|
||||
},
|
||||
{
|
||||
title: "Monitor Secrets",
|
||||
url: "/docs/monitor/monitor-secrets",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Probe",
|
||||
links: [
|
||||
{ title: "Custom Probes", url: "/docs/probe/custom-probe" },
|
||||
{ title: "IP Addresses", url: "/docs/probe/ip-address" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Telemetry",
|
||||
links: [
|
||||
{ title: "OpenTelemetry", url: "/docs/telemetry/open-telemetry" },
|
||||
{ title: "Fluentd", url: "/docs/telemetry/fluentd" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default DocsNav;
|
||||
|
||||
@@ -1,50 +1,7 @@
|
||||
import { Renderer, marked } from 'marked';
|
||||
import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown";
|
||||
|
||||
export default class DocsRender {
|
||||
public static async render(markdownContent: string): Promise<string> {
|
||||
const renderer: Renderer = this.getBlogRenderer();
|
||||
|
||||
return await marked(markdownContent, {
|
||||
renderer: renderer,
|
||||
});
|
||||
}
|
||||
|
||||
private static getBlogRenderer(): Renderer {
|
||||
const renderer: Renderer = new Renderer();
|
||||
|
||||
renderer.paragraph = function (text) {
|
||||
return `<p class="mt-2 mb-2 leading-8 text-gray-600">${text}</p>`;
|
||||
};
|
||||
|
||||
renderer.blockquote = function (quote) {
|
||||
return `<blockquote class="p-4 pt-1 pb-1 my-4 border-s-4 border-indigo-500">
|
||||
<div class="leading-8 text-gray-600">${quote}</div>
|
||||
</blockquote>`;
|
||||
};
|
||||
|
||||
renderer.image = function (href, _title, text) {
|
||||
return `<img src="${href}" alt="${text}" class="rounded-md shadow-md" />`;
|
||||
};
|
||||
|
||||
renderer.code = function (code, language) {
|
||||
return `<pre class="language-${language} rounded-md"><code class="language-${language} rounded-md">${code}</code></pre>`;
|
||||
};
|
||||
|
||||
renderer.heading = function (text, level) {
|
||||
if (level === 1) {
|
||||
return `<h1 class="my-5 mt-8 text-4xl font-bold tracking-tight text-gray-800">${text}</h1>`;
|
||||
} else if (level === 2) {
|
||||
return `<h2 class="my-5 mt-8 text-3xl font-bold tracking-tight text-gray-800">${text}</h2>`;
|
||||
} else if (level === 3) {
|
||||
return `<h3 class="my-5 mt-8 text-2xl font-bold tracking-tight text-gray-800">${text}</h3>`;
|
||||
} else if (level === 4) {
|
||||
return `<h4 class="my-5 mt-8 text-xl font-bold tracking-tight text-gray-800">${text}</h4>`;
|
||||
} else if (level === 5) {
|
||||
return `<h5 class="my-5 mt-8 text-lg font-bold tracking-tight text-gray-800">${text}</h5>`;
|
||||
}
|
||||
return `<h6 class="my-5 tracking-tight font-bold text-gray-800">${text}</h6>`;
|
||||
};
|
||||
|
||||
return renderer;
|
||||
}
|
||||
public static async render(markdownContent: string): Promise<string> {
|
||||
return Markdown.convertToHTML(markdownContent, MarkdownContentType.Docs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +1,88 @@
|
||||
import BlogPostUtil, { BlogPost, BlogPostHeader } from "../Utils/BlogPost";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import NotFoundUtil from "../Utils/NotFound";
|
||||
import ServerErrorUtil from "../Utils/ServerError";
|
||||
import Text from "Common/Types/Text";
|
||||
import Express, {
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import BlogPostUtil, { BlogPost, BlogPostHeader } from '../Utils/BlogPost';
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import ServerErrorUtil from '../Utils/ServerError';
|
||||
import NotFoundUtil from '../Utils/NotFound';
|
||||
import Text from 'Common/Types/Text';
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.get(
|
||||
'/blog/post/:file',
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fileName: string = req.params['file'] as string;
|
||||
"/blog/post/:file",
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fileName: string = req.params["file"] as string;
|
||||
|
||||
const blogPost: BlogPost | null = await BlogPostUtil.getBlogPost(
|
||||
fileName
|
||||
);
|
||||
const blogPost: BlogPost | null =
|
||||
await BlogPostUtil.getBlogPost(fileName);
|
||||
|
||||
if (!blogPost) {
|
||||
return NotFoundUtil.renderNotFound(res);
|
||||
}
|
||||
if (!blogPost) {
|
||||
return NotFoundUtil.renderNotFound(res);
|
||||
}
|
||||
|
||||
res.render(`${ViewsPath}/Blog/Post`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPost: blogPost,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
res.render(`${ViewsPath}/Blog/Post`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPost: blogPost,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// List all blog posts with tag
|
||||
|
||||
app.get(
|
||||
'/blog/tag/:tagName',
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const tagName: string = req.params['tagName'] as string;
|
||||
"/blog/tag/:tagName",
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const tagName: string = req.params["tagName"] as string;
|
||||
|
||||
const blogPosts: Array<BlogPostHeader> =
|
||||
await BlogPostUtil.getBlogPostList(tagName);
|
||||
const blogPosts: Array<BlogPostHeader> =
|
||||
await BlogPostUtil.getBlogPostList(tagName);
|
||||
|
||||
res.render(`${ViewsPath}/Blog/ListByTag`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPosts: blogPosts,
|
||||
tagName: Text.fromDashesToPascalCase(tagName),
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
res.render(`${ViewsPath}/Blog/ListByTag`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPosts: blogPosts,
|
||||
tagName: Text.fromDashesToPascalCase(tagName),
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// main blog page
|
||||
app.get('/blog', async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const blogPosts: Array<BlogPostHeader> =
|
||||
await BlogPostUtil.getBlogPostList();
|
||||
app.get("/blog", async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const blogPosts: Array<BlogPostHeader> =
|
||||
await BlogPostUtil.getBlogPostList();
|
||||
|
||||
res.render(`${ViewsPath}/Blog/List`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPosts: blogPosts,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
res.render(`${ViewsPath}/Blog/List`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPosts: blogPosts,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
||||
(function (e, t) {
|
||||
var n = e.amplitude || { _q: [], _iq: {} }; var r = t.createElement("script")
|
||||
; r.type = "text/javascript"
|
||||
; r.integrity = "sha384-vYYnQ3LPdp/RkQjoKBTGSq0X5F73gXU3G2QopHaIfna0Ct1JRWzwrmEz115NzOta"
|
||||
; r.crossOrigin = "anonymous"; r.async = true
|
||||
; r.src = "https://cdn.amplitude.com/libs/amplitude-5.8.0-min.gz.js"
|
||||
; r.onload = function () {
|
||||
if (!e.amplitude.runQueuedFunctions) {
|
||||
console.log("[Amplitude] Error: could not load SDK")
|
||||
}
|
||||
}
|
||||
; var i = t.getElementsByTagName("script")[0]; i.parentNode.insertBefore(r, i)
|
||||
; function s(e, t) {
|
||||
e.prototype[t] = function () {
|
||||
this._q.push([t].concat(Array.prototype.slice.call(arguments, 0))); return this
|
||||
}
|
||||
}
|
||||
var o = function () { this._q = []; return this }
|
||||
; var a = ["add", "append", "clearAll", "prepend", "set", "setOnce", "unset"]
|
||||
; for (var u = 0; u < a.length; u++) { s(o, a[u]) } n.Identify = o; var c = function () {
|
||||
this._q = []
|
||||
; return this
|
||||
}
|
||||
; var l = ["setProductId", "setQuantity", "setPrice", "setRevenueType", "setEventProperties"]
|
||||
; for (var p = 0; p < l.length; p++) { s(c, l[p]) } n.Revenue = c
|
||||
; var d = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "enableTracking", "setGlobalUserProperties", "identify", "clearUserProperties", "setGroup", "logRevenueV2", "regenerateDeviceId", "groupIdentify", "onInit", "logEventWithTimestamp", "logEventWithGroups", "setSessionId", "resetSessionId"]
|
||||
; function v(e) {
|
||||
function t(t) {
|
||||
e[t] = function () {
|
||||
e._q.push([t].concat(Array.prototype.slice.call(arguments, 0)))
|
||||
}
|
||||
}
|
||||
for (var n = 0; n < d.length; n++) { t(d[n]) }
|
||||
} v(n); n.getInstance = function (e) {
|
||||
e = (!e || e.length === 0 ? "$default_instance" : e).toLowerCase()
|
||||
; if (!n._iq.hasOwnProperty(e)) { n._iq[e] = { _q: [] }; v(n._iq[e]) } return n._iq[e]
|
||||
}
|
||||
; e.amplitude = n
|
||||
})(window, document);
|
||||
|
||||
amplitude.getInstance().init("802d95003af23aad17ed068b6cfdeb2b", null, {
|
||||
// include referrer information in amplitude.
|
||||
saveEvents: true,
|
||||
includeUtm: true,
|
||||
includeReferrer: true,
|
||||
includeGclid: true
|
||||
});
|
||||
@@ -1,94 +1,95 @@
|
||||
function openTab(evt, tabName) {
|
||||
// Declare all variables
|
||||
let i;
|
||||
// Declare all variables
|
||||
let i;
|
||||
|
||||
// Get all elements with class="tabcontent" and hide them
|
||||
const tabcontent = document.getElementsByClassName('tabcontent');
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].className = tabcontent[i].className.replace(' active', '');
|
||||
}
|
||||
// Get all elements with class="tabcontent" and hide them
|
||||
const tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].className = tabcontent[i].className.replace(" active", "");
|
||||
}
|
||||
|
||||
// Get all elements with class="tablinks" and remove the class "active"
|
||||
const tablinks = document.getElementsByClassName('tablinks');
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(' active', '');
|
||||
}
|
||||
// Get all elements with class="tablinks" and remove the class "active"
|
||||
const tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||
}
|
||||
|
||||
// Show the current tab, and add an "active" class to the link that opened the tab
|
||||
// Show the current tab, and add an "active" class to the link that opened the tab
|
||||
|
||||
document.getElementById(tabName).className += ' active';
|
||||
evt.currentTarget.className += ' active';
|
||||
document.getElementById(tabName).className += " active";
|
||||
evt.currentTarget.className += " active";
|
||||
|
||||
setTimeout(() => document.getElementById(tabName + '1').parentNode.click(), 200);
|
||||
setTimeout(
|
||||
() => document.getElementById(tabName + "1").parentNode.click(),
|
||||
200,
|
||||
);
|
||||
}
|
||||
function openTooltip(name) {
|
||||
// Declare all variables
|
||||
let i;
|
||||
const element = document.getElementById(name);
|
||||
// Declare all variables
|
||||
let i;
|
||||
const element = document.getElementById(name);
|
||||
|
||||
const elclass = element.className;
|
||||
const elclass = element.className;
|
||||
|
||||
const tooltip = document.getElementsByClassName('tooltiptext');
|
||||
for (i = 0; i < tooltip.length; i++) {
|
||||
tooltip[i].className = tooltip[i].className.replace(' active', '');
|
||||
}
|
||||
if (elclass.indexOf('active') > -1) {
|
||||
|
||||
element.className = element.className.replace(' active', '');
|
||||
}
|
||||
else {
|
||||
|
||||
element.classList.add('active');
|
||||
}
|
||||
const tooltip = document.getElementsByClassName("tooltiptext");
|
||||
for (i = 0; i < tooltip.length; i++) {
|
||||
tooltip[i].className = tooltip[i].className.replace(" active", "");
|
||||
}
|
||||
if (elclass.indexOf("active") > -1) {
|
||||
element.className = element.className.replace(" active", "");
|
||||
} else {
|
||||
element.classList.add("active");
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
animateHTML().init();
|
||||
const tooltext = document.getElementsByClassName('tooltiptext');
|
||||
for (let i = 0; i < tooltext.length; i++) {
|
||||
animateHTML().init();
|
||||
const tooltext = document.getElementsByClassName("tooltiptext");
|
||||
for (let i = 0; i < tooltext.length; i++) {
|
||||
tooltext[i].onclick = function (e) {
|
||||
e.stopPropagation();
|
||||
};
|
||||
}
|
||||
|
||||
tooltext[i].onclick = function (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
document.getElementsByTagName("body")[0].onclick = function (e) {
|
||||
if (
|
||||
e.target.className !== "popover-dot" &&
|
||||
e.target.className !== "tooltiptext" &&
|
||||
e.target.className !== "tablinks active"
|
||||
) {
|
||||
const tooltip = document.getElementsByClassName("tooltiptext");
|
||||
for (let i = 0; i < tooltip.length; i++) {
|
||||
tooltip[i].className = tooltip[i].className.replace(" active", "");
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementsByTagName('body')[0].onclick = function (e) {
|
||||
|
||||
if (e.target.className !== 'popover-dot' && e.target.className !== 'tooltiptext' && e.target.className !== 'tablinks active') {
|
||||
const tooltip = document.getElementsByClassName('tooltiptext');
|
||||
for (let i = 0; i < tooltip.length; i++) {
|
||||
tooltip[i].className = tooltip[i].className.replace(' active', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const animateHTML = function () {
|
||||
let elem, windowHeight;
|
||||
const init = function () {
|
||||
elem = document.getElementById('Statuspage');
|
||||
windowHeight = window.innerHeight;
|
||||
_addEventHandlers();
|
||||
let elem, windowHeight;
|
||||
const init = function () {
|
||||
elem = document.getElementById("Statuspage");
|
||||
windowHeight = window.innerHeight;
|
||||
_addEventHandlers();
|
||||
};
|
||||
const _addEventHandlers = function () {
|
||||
window.addEventListener("scroll", _checkPosition);
|
||||
window.addEventListener("resize", init);
|
||||
};
|
||||
const _checkPosition = function () {
|
||||
if (!elem) {
|
||||
return;
|
||||
}
|
||||
const _addEventHandlers = function () {
|
||||
window.addEventListener('scroll', _checkPosition)
|
||||
window.addEventListener('resize', init)
|
||||
}
|
||||
const _checkPosition = function () {
|
||||
if (!elem) {
|
||||
return;
|
||||
}
|
||||
const posFromTop = elem.getBoundingClientRect().top;
|
||||
const posFromTop = elem.getBoundingClientRect().top;
|
||||
|
||||
if (posFromTop - windowHeight <= -400) {
|
||||
|
||||
document.getElementById('Statuspage1').parentNode.click();
|
||||
window.removeEventListener('scroll', _checkPosition);
|
||||
window.removeEventListener('resize', init);
|
||||
return;
|
||||
}
|
||||
if (posFromTop - windowHeight <= -400) {
|
||||
document.getElementById("Statuspage1").parentNode.click();
|
||||
window.removeEventListener("scroll", _checkPosition);
|
||||
window.removeEventListener("resize", init);
|
||||
return;
|
||||
}
|
||||
return {
|
||||
init: init
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
init: init,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This is basicaly meant to get a cookie by name
|
||||
var getCookiebyName = function (name) {
|
||||
var pair = document.cookie.match(new RegExp(name + '=([^;]+)'));
|
||||
return !!pair ? pair[1] : null;
|
||||
};
|
||||
var pair = document.cookie.match(new RegExp(name + "=([^;]+)"));
|
||||
return pair ? pair[1] : null;
|
||||
};
|
||||
|
||||
@@ -1,69 +1,49 @@
|
||||
!(function () {
|
||||
function n(n, e) {
|
||||
$(".hidden", n)
|
||||
.eq(e)
|
||||
.css({
|
||||
transitionDelay: Math.random() + Math.random() + "s",
|
||||
transitionDuration: 2 * Math.random() + 0.2 + "s",
|
||||
}),
|
||||
$(".hidden", n).eq(e).attr("class", "shown");
|
||||
}
|
||||
|
||||
! function () {
|
||||
function n(n, e) {
|
||||
|
||||
$('.hidden', n)
|
||||
.eq(e)
|
||||
.css({
|
||||
transitionDelay: Math.random() + Math.random() + 's',
|
||||
transitionDuration: 2 * Math.random() + .2 + 's'
|
||||
|
||||
}), $('.hidden', n)
|
||||
.eq(e)
|
||||
.attr('class', 'shown')
|
||||
function e(n, e) {
|
||||
if (n.hasClass("is-visible")) {
|
||||
const a = $(".shown", n).eq(e);
|
||||
a.attr("class", "hidden"),
|
||||
setTimeout(function () {
|
||||
a.attr("class", "shown");
|
||||
}, 3e3);
|
||||
}
|
||||
|
||||
function e(n, e) {
|
||||
if (n.hasClass('is-visible')) {
|
||||
|
||||
const a = $('.shown', n)
|
||||
.eq(e);
|
||||
a.attr('class', 'hidden'), setTimeout(function () {
|
||||
a.attr('class', 'shown')
|
||||
}, 3e3)
|
||||
}
|
||||
}
|
||||
|
||||
$(".card").each(function (e, a) {
|
||||
if (window.IntersectionObserver)
|
||||
(a.observer = new IntersectionObserver((e) => {
|
||||
e.forEach((e) => {
|
||||
if (e.isIntersecting || e.intersectionRatio > 0) {
|
||||
$(a).addClass("is-visible");
|
||||
|
||||
for (let t = $(".hidden", a).length; t >= 0; t--) n(a, t);
|
||||
} else $(a).removeClass("is-visible");
|
||||
});
|
||||
})),
|
||||
a.observer.observe(a);
|
||||
else {
|
||||
$(a).addClass("is-visible");
|
||||
|
||||
for (let t = $(".hidden", a).length; t >= 0; t--) n(a, t);
|
||||
}
|
||||
|
||||
$('.card')
|
||||
.each(function (e, a) {
|
||||
if (window.IntersectionObserver) a.observer = new IntersectionObserver(e => {
|
||||
e.forEach(e => {
|
||||
if (e.isIntersecting || e.intersectionRatio > 0) {
|
||||
|
||||
$(a)
|
||||
.addClass('is-visible');
|
||||
|
||||
for (let t = $('.hidden', a)
|
||||
.length; t >= 0; t--) n(a, t)
|
||||
|
||||
} else $(a)
|
||||
.removeClass('is-visible')
|
||||
})
|
||||
}), a.observer.observe(a);
|
||||
else {
|
||||
|
||||
$(a)
|
||||
.addClass('is-visible');
|
||||
|
||||
for (let t = $('.hidden', a)
|
||||
.length; t >= 0; t--) n(a, t)
|
||||
}
|
||||
}), setInterval(function () {
|
||||
|
||||
let n = $('.card')
|
||||
|
||||
.eq(Math.floor(Math.random() * $('.card')
|
||||
.length));
|
||||
|
||||
e(n, Math.floor(Math.random() * $('.shown', n)
|
||||
.length));
|
||||
|
||||
n = $('.card')
|
||||
|
||||
.eq(Math.floor(Math.random() * $('.card')
|
||||
.length));
|
||||
|
||||
e(n, Math.floor(Math.random() * $('.shown', n)
|
||||
.length))
|
||||
}, 600)
|
||||
}();
|
||||
}),
|
||||
setInterval(function () {
|
||||
let n = $(".card").eq(Math.floor(Math.random() * $(".card").length));
|
||||
|
||||
e(n, Math.floor(Math.random() * $(".shown", n).length));
|
||||
|
||||
n = $(".card").eq(Math.floor(Math.random() * $(".card").length));
|
||||
|
||||
e(n, Math.floor(Math.random() * $(".shown", n).length));
|
||||
}, 600);
|
||||
})();
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
|
||||
let accountsUrl = window.location.origin+'/accounts';
|
||||
let backendUrl = window.location.hostname==='localhost'? 'http://localhost:3002': window.location.origin+'/api'
|
||||
|
||||
let accountsUrl = window.location.origin + "/accounts";
|
||||
let backendUrl =
|
||||
window.location.hostname === "localhost"
|
||||
? "http://localhost:3002"
|
||||
: window.location.origin + "/api";
|
||||
|
||||
//eslint-disable-next-line
|
||||
function loginUrl(extra) {
|
||||
if (extra) {
|
||||
window.location.href = `${accountsUrl}/login${extra}`;
|
||||
}
|
||||
else {
|
||||
window.location.href = `${accountsUrl}/login`;
|
||||
}
|
||||
if (extra) {
|
||||
window.location.href = `${accountsUrl}/login${extra}`;
|
||||
} else {
|
||||
window.location.href = `${accountsUrl}/login`;
|
||||
}
|
||||
}
|
||||
//eslint-disable-next-line
|
||||
function registerUrl(params) {
|
||||
if (params) {
|
||||
window.location.href = `${accountsUrl}/register${params}`;
|
||||
}
|
||||
else {
|
||||
window.location.href = `${accountsUrl}/register`;
|
||||
}
|
||||
if (params) {
|
||||
window.location.href = `${accountsUrl}/register${params}`;
|
||||
} else {
|
||||
window.location.href = `${accountsUrl}/register`;
|
||||
}
|
||||
}
|
||||
//eslint-disable-next-line
|
||||
function formUrl() {
|
||||
return `${backendUrl}/lead/`;
|
||||
return `${backendUrl}/lead/`;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
test('two plus two is four', () => {
|
||||
expect(2 + 2).toBe(4);
|
||||
test("two plus two is four", () => {
|
||||
expect(2 + 2).toBe(4);
|
||||
});
|
||||
|
||||
@@ -1,489 +1,447 @@
|
||||
import HTTPResponse from 'Common/Types/API/HTTPResponse';
|
||||
import API from 'Common/Utils/API';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { marked, Renderer } from 'marked';
|
||||
import { JSONArray, JSONObject, JSONObjectOrArray } from 'Common/Types/JSON';
|
||||
import BaseModel from 'Common/Models/BaseModel';
|
||||
import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel';
|
||||
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
|
||||
import JSONFunctions from 'Common/Types/JSONFunctions';
|
||||
import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import Text from 'Common/Types/Text';
|
||||
import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel";
|
||||
import BaseModel from "Common/Models/BaseModel";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONArray, JSONObject, JSONObjectOrArray } from "Common/Types/JSON";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import Text from "Common/Types/Text";
|
||||
import API from "Common/Utils/API";
|
||||
import LocalCache from "CommonServer/Infrastructure/LocalCache";
|
||||
import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown";
|
||||
|
||||
export interface BlogPostAuthor {
|
||||
username: string;
|
||||
githubUrl: string;
|
||||
profileImageUrl: string;
|
||||
name: string;
|
||||
username: string;
|
||||
githubUrl: string;
|
||||
profileImageUrl: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface BlogPostBaseProps {
|
||||
title: string;
|
||||
description: string;
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
formattedPostDate: string;
|
||||
fileName: string;
|
||||
tags: string[];
|
||||
postDate: string;
|
||||
blogUrl: string;
|
||||
formattedPostDate: string;
|
||||
fileName: string;
|
||||
tags: string[];
|
||||
postDate: string;
|
||||
blogUrl: string;
|
||||
}
|
||||
|
||||
export interface BlogPostHeader extends BlogPostBaseProps {
|
||||
authorGitHubUsername: string;
|
||||
authorGitHubUsername: string;
|
||||
}
|
||||
|
||||
export interface BlogPost extends BlogPostBaseProps {
|
||||
htmlBody: string;
|
||||
markdownBody: string;
|
||||
socialMediaImageUrl: string;
|
||||
author: BlogPostAuthor | null;
|
||||
htmlBody: string;
|
||||
markdownBody: string;
|
||||
socialMediaImageUrl: string;
|
||||
author: BlogPostAuthor | null;
|
||||
}
|
||||
|
||||
const GitHubRawUrl: string =
|
||||
'https://raw.githubusercontent.com/oneuptime/blog/master';
|
||||
"https://raw.githubusercontent.com/oneuptime/blog/master";
|
||||
|
||||
export default class BlogPostUtil {
|
||||
public static async getBlogPostList(
|
||||
tagName?: string | undefined
|
||||
): Promise<BlogPostHeader[]> {
|
||||
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/Blogs.json`);
|
||||
public static async getBlogPostList(
|
||||
tagName?: string | undefined,
|
||||
): Promise<BlogPostHeader[]> {
|
||||
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/Blogs.json`);
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
let jsonContent: string | JSONArray =
|
||||
(fileData.data as string | JSONArray) || [];
|
||||
|
||||
if (typeof jsonContent === 'string') {
|
||||
jsonContent = JSONFunctions.parseJSONArray(jsonContent);
|
||||
}
|
||||
|
||||
const blogs: Array<JSONObject> = JSONFunctions.deserializeArray(
|
||||
jsonContent as Array<JSONObject>
|
||||
).reverse(); // reverse so new content comes first
|
||||
|
||||
const resultList: Array<BlogPostHeader> = [];
|
||||
|
||||
for (const blog of blogs) {
|
||||
const fileName: string = blog['post'] as string;
|
||||
const formattedPostDate: string =
|
||||
this.getFormattedPostDateFromFileName(fileName);
|
||||
const postDate: string = this.getPostDateFromFileName(fileName);
|
||||
|
||||
resultList.push({
|
||||
title: blog['title'] as string,
|
||||
description: blog['description'] as string,
|
||||
fileName,
|
||||
formattedPostDate,
|
||||
postDate,
|
||||
tags: blog['tags'] as string[],
|
||||
authorGitHubUsername: blog['authorGitHubUsername'] as string,
|
||||
blogUrl: `/blog/post/${fileName}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (tagName) {
|
||||
return resultList.filter((blog: BlogPostHeader) => {
|
||||
return blog.tags
|
||||
.map((item: string) => {
|
||||
return Text.replaceAll(item.toLowerCase(), ' ', '-');
|
||||
})
|
||||
.includes(tagName);
|
||||
});
|
||||
}
|
||||
|
||||
return resultList;
|
||||
if (fileData.isFailure()) {
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
public static async getBlogPost(
|
||||
fileName: string
|
||||
): Promise<BlogPost | null> {
|
||||
let blogPost: BlogPost | null = this.getBlogPostFromCache(fileName);
|
||||
let jsonContent: string | JSONArray =
|
||||
(fileData.data as string | JSONArray) || [];
|
||||
|
||||
// if (blogPost) {
|
||||
// return Promise.resolve(blogPost);
|
||||
// }
|
||||
|
||||
blogPost = await this.getBlogPostFromGitHub(fileName);
|
||||
|
||||
// save this to cache
|
||||
LocalCache.setJSON(
|
||||
'blog',
|
||||
fileName,
|
||||
JSONFunctions.serialize(blogPost as any)
|
||||
);
|
||||
|
||||
return blogPost;
|
||||
if (typeof jsonContent === "string") {
|
||||
jsonContent = JSONFunctions.parseJSONArray(jsonContent);
|
||||
}
|
||||
|
||||
public static async getNameOfGitHubUser(username: string): Promise<string> {
|
||||
const fileUrl: URL = URL.fromString(
|
||||
`https://api.github.com/users/${username}`
|
||||
);
|
||||
const blogs: Array<JSONObject> = JSONFunctions.deserializeArray(
|
||||
jsonContent as Array<JSONObject>,
|
||||
).reverse(); // reverse so new content comes first
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
const resultList: Array<BlogPostHeader> = [];
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
for (const blog of blogs) {
|
||||
const fileName: string = blog["post"] as string;
|
||||
const formattedPostDate: string =
|
||||
this.getFormattedPostDateFromFileName(fileName);
|
||||
const postDate: string = this.getPostDateFromFileName(fileName);
|
||||
|
||||
const name: string =
|
||||
(fileData.data as JSONObject)?.['name']?.toString() || '';
|
||||
return name;
|
||||
resultList.push({
|
||||
title: blog["title"] as string,
|
||||
description: blog["description"] as string,
|
||||
fileName,
|
||||
formattedPostDate,
|
||||
postDate,
|
||||
tags: blog["tags"] as string[],
|
||||
authorGitHubUsername: blog["authorGitHubUsername"] as string,
|
||||
blogUrl: `/blog/post/${fileName}`,
|
||||
});
|
||||
}
|
||||
|
||||
public static async getGitHubMarkdownFileContent(
|
||||
githubPath: string
|
||||
): Promise<string | null> {
|
||||
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/${githubPath}`);
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
if ((fileData as HTTPErrorResponse).statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
const markdownContent: string =
|
||||
(fileData.data as JSONObject)?.['data']?.toString() || '';
|
||||
return markdownContent;
|
||||
if (tagName) {
|
||||
return resultList.filter((blog: BlogPostHeader) => {
|
||||
return blog.tags
|
||||
.map((item: string) => {
|
||||
return Text.replaceAll(item.toLowerCase(), " ", "-");
|
||||
})
|
||||
.includes(tagName);
|
||||
});
|
||||
}
|
||||
|
||||
public static async getTags(): Promise<string[]> {
|
||||
// check if tags are in cache
|
||||
let tags: string[] = LocalCache.getJSON(
|
||||
'blog-tags',
|
||||
'tags'
|
||||
) as string[];
|
||||
return resultList;
|
||||
}
|
||||
|
||||
if (tags && tags.length > 0) {
|
||||
return tags;
|
||||
}
|
||||
public static async getBlogPost(fileName: string): Promise<BlogPost | null> {
|
||||
let blogPost: BlogPost | null = this.getBlogPostFromCache(fileName);
|
||||
|
||||
tags = await this.getAllTagsFromGitHub();
|
||||
// if (blogPost) {
|
||||
// return Promise.resolve(blogPost);
|
||||
// }
|
||||
|
||||
// save this to cache
|
||||
blogPost = await this.getBlogPostFromGitHub(fileName);
|
||||
|
||||
LocalCache.setJSON(
|
||||
'blog-tags',
|
||||
'tags',
|
||||
JSONFunctions.serialize(tags as any)
|
||||
);
|
||||
// save this to cache
|
||||
LocalCache.setJSON(
|
||||
"blog",
|
||||
fileName,
|
||||
JSONFunctions.serialize(blogPost as any),
|
||||
);
|
||||
|
||||
return tags;
|
||||
return blogPost;
|
||||
}
|
||||
|
||||
public static async getNameOfGitHubUser(username: string): Promise<string> {
|
||||
const fileUrl: URL = URL.fromString(
|
||||
`https://api.github.com/users/${username}`,
|
||||
);
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
public static async getAllTagsFromGitHub(): Promise<string[]> {
|
||||
const tagsMarkdownContent: string | null =
|
||||
await this.getGitHubMarkdownFileContent('Tags.md');
|
||||
const name: string =
|
||||
(fileData.data as JSONObject)?.["name"]?.toString() || "";
|
||||
return name;
|
||||
}
|
||||
|
||||
if (!tagsMarkdownContent) {
|
||||
return [];
|
||||
}
|
||||
public static async getGitHubMarkdownFileContent(
|
||||
githubPath: string,
|
||||
): Promise<string | null> {
|
||||
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/${githubPath}`);
|
||||
|
||||
const tags: Array<string> = tagsMarkdownContent
|
||||
.split('\n')
|
||||
.map((tag: string) => {
|
||||
return tag.trim();
|
||||
})
|
||||
.filter((tag: string) => {
|
||||
return tag.startsWith('-');
|
||||
})
|
||||
.map((tag: string) => {
|
||||
return tag.replace('-', '').trim();
|
||||
});
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
return tags;
|
||||
if (fileData.isFailure()) {
|
||||
if ((fileData as HTTPErrorResponse).statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
public static async getBlogPostFromGitHub(
|
||||
fileName: string
|
||||
): Promise<BlogPost | null> {
|
||||
const fileUrl: URL = URL.fromString(
|
||||
`${GitHubRawUrl}/posts/${fileName}/README.md`
|
||||
);
|
||||
const markdownContent: string =
|
||||
(fileData.data as JSONObject)?.["data"]?.toString() || "";
|
||||
return markdownContent;
|
||||
}
|
||||
|
||||
const postDate: string = this.getPostDateFromFileName(fileName);
|
||||
const formattedPostDate: string =
|
||||
this.getFormattedPostDateFromFileName(fileName);
|
||||
public static async getTags(): Promise<string[]> {
|
||||
// check if tags are in cache
|
||||
let tags: string[] = LocalCache.getJSON("blog-tags", "tags") as string[];
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
if ((fileData as HTTPErrorResponse).statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
let markdownContent: string =
|
||||
(fileData.data as JSONObject)?.['data']?.toString() || '';
|
||||
|
||||
const blogPostAuthor: BlogPostAuthor | null =
|
||||
await this.getAuthorFromFileContent(markdownContent);
|
||||
|
||||
const title: string = this.getTitleFromFileContent(markdownContent);
|
||||
const description: string =
|
||||
this.getDescriptionFromFileContent(markdownContent);
|
||||
const tags: Array<string> =
|
||||
this.getTagsFromFileContent(markdownContent);
|
||||
|
||||
markdownContent = this.getPostFromMarkdown(markdownContent);
|
||||
|
||||
const renderer: Renderer = this.getBlogRenderer();
|
||||
|
||||
const htmlBody: string = await marked(markdownContent, {
|
||||
renderer: renderer,
|
||||
});
|
||||
|
||||
const blogPost: BlogPost = {
|
||||
title,
|
||||
description,
|
||||
author: blogPostAuthor,
|
||||
htmlBody,
|
||||
markdownBody: markdownContent,
|
||||
fileName,
|
||||
tags,
|
||||
postDate,
|
||||
formattedPostDate,
|
||||
socialMediaImageUrl: `${GitHubRawUrl}/posts/${fileName}/social-media.png`,
|
||||
blogUrl: `https://oneuptime.com/blog/post/${fileName}`, // this has to be oneuptime.com because its used in twitter cards and faceboomk cards. Please dont change this.
|
||||
};
|
||||
|
||||
return blogPost;
|
||||
if (tags && tags.length > 0) {
|
||||
return tags;
|
||||
}
|
||||
|
||||
private static getPostDateFromFileName(fileName: string): string {
|
||||
const year: string | undefined = fileName.split('-')[0];
|
||||
const month: string | undefined = fileName.split('-')[1];
|
||||
const day: string | undefined = fileName.split('-')[2];
|
||||
tags = await this.getAllTagsFromGitHub();
|
||||
|
||||
if (!year || !month || !day) {
|
||||
throw new BadDataException('Invalid file name');
|
||||
}
|
||||
// save this to cache
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
LocalCache.setJSON(
|
||||
"blog-tags",
|
||||
"tags",
|
||||
JSONFunctions.serialize(tags as any),
|
||||
);
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
public static async getAllTagsFromGitHub(): Promise<string[]> {
|
||||
const tagsMarkdownContent: string | null =
|
||||
await this.getGitHubMarkdownFileContent("Tags.md");
|
||||
|
||||
if (!tagsMarkdownContent) {
|
||||
return [];
|
||||
}
|
||||
|
||||
private static getFormattedPostDateFromFileName(fileName: string): string {
|
||||
// file name is of the format YYYY-MM-DD-Title.md
|
||||
const year: string | undefined = fileName.split('-')[0];
|
||||
const month: string | undefined = fileName.split('-')[1];
|
||||
const day: string | undefined = fileName.split('-')[2];
|
||||
const tags: Array<string> = tagsMarkdownContent
|
||||
.split("\n")
|
||||
.map((tag: string) => {
|
||||
return tag.trim();
|
||||
})
|
||||
.filter((tag: string) => {
|
||||
return tag.startsWith("-");
|
||||
})
|
||||
.map((tag: string) => {
|
||||
return tag.replace("-", "").trim();
|
||||
});
|
||||
|
||||
if (!year || !month || !day) {
|
||||
throw new BadDataException('Invalid file name');
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
const date: Date = OneUptimeDate.getDateFromYYYYMMDD(year, month, day);
|
||||
return OneUptimeDate.getDateAsLocalFormattedString(date, true);
|
||||
public static async getBlogPostFromGitHub(
|
||||
fileName: string,
|
||||
): Promise<BlogPost | null> {
|
||||
const fileUrl: URL = URL.fromString(
|
||||
`${GitHubRawUrl}/posts/${fileName}/README.md`,
|
||||
);
|
||||
|
||||
const postDate: string = this.getPostDateFromFileName(fileName);
|
||||
const formattedPostDate: string =
|
||||
this.getFormattedPostDateFromFileName(fileName);
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
if ((fileData as HTTPErrorResponse).statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
private static getBlogRenderer(): Renderer {
|
||||
const renderer: Renderer = new Renderer();
|
||||
let markdownContent: string =
|
||||
(fileData.data as JSONObject)?.["data"]?.toString() || "";
|
||||
|
||||
renderer.paragraph = function (text) {
|
||||
return `<p class="mt-2 mb-2 leading-8 text-gray-600">${text}</p>`;
|
||||
};
|
||||
const blogPostAuthor: BlogPostAuthor | null =
|
||||
await this.getAuthorFromFileContent(markdownContent);
|
||||
|
||||
renderer.blockquote = function (quote) {
|
||||
return `<blockquote class="p-4 pt-1 pb-1 my-4 border-s-4 border-indigo-500">
|
||||
<div class="leading-8 text-gray-600">${quote}</div>
|
||||
</blockquote>`;
|
||||
};
|
||||
const title: string = this.getTitleFromFileContent(markdownContent);
|
||||
const description: string =
|
||||
this.getDescriptionFromFileContent(markdownContent);
|
||||
const tags: Array<string> = this.getTagsFromFileContent(markdownContent);
|
||||
|
||||
renderer.code = function (code, language) {
|
||||
return `<pre class="language-${language} rounded-md"><code class="language-${language} rounded-md">${code}</code></pre>`;
|
||||
};
|
||||
markdownContent = this.getPostFromMarkdown(markdownContent);
|
||||
|
||||
renderer.heading = function (text, level) {
|
||||
if (level === 1) {
|
||||
return `<h1 class="my-5 mt-8 text-4xl font-bold tracking-tight text-gray-800">${text}</h1>`;
|
||||
} else if (level === 2) {
|
||||
return `<h2 class="my-5 mt-8 text-3xl font-bold tracking-tight text-gray-800">${text}</h2>`;
|
||||
} else if (level === 3) {
|
||||
return `<h3 class="my-5 mt-8 text-2xl font-bold tracking-tight text-gray-800">${text}</h3>`;
|
||||
} else if (level === 4) {
|
||||
return `<h4 class="my-5 mt-8 text-xl font-bold tracking-tight text-gray-800">${text}</h4>`;
|
||||
} else if (level === 5) {
|
||||
return `<h5 class="my-5 mt-8 text-lg font-bold tracking-tight text-gray-800">${text}</h5>`;
|
||||
}
|
||||
return `<h6 class="my-5 tracking-tight font-bold text-gray-800">${text}</h6>`;
|
||||
};
|
||||
const htmlBody: string = await Markdown.convertToHTML(
|
||||
markdownContent,
|
||||
MarkdownContentType.Blog,
|
||||
);
|
||||
|
||||
return renderer;
|
||||
const blogPost: BlogPost = {
|
||||
title,
|
||||
description,
|
||||
author: blogPostAuthor,
|
||||
htmlBody,
|
||||
markdownBody: markdownContent,
|
||||
fileName,
|
||||
tags,
|
||||
postDate,
|
||||
formattedPostDate,
|
||||
socialMediaImageUrl: `${GitHubRawUrl}/posts/${fileName}/social-media.png`,
|
||||
blogUrl: `https://oneuptime.com/blog/post/${fileName}`, // this has to be oneuptime.com because its used in twitter cards and faceboomk cards. Please dont change this.
|
||||
};
|
||||
|
||||
return blogPost;
|
||||
}
|
||||
|
||||
private static getPostDateFromFileName(fileName: string): string {
|
||||
const year: string | undefined = fileName.split("-")[0];
|
||||
const month: string | undefined = fileName.split("-")[1];
|
||||
const day: string | undefined = fileName.split("-")[2];
|
||||
|
||||
if (!year || !month || !day) {
|
||||
throw new BadDataException("Invalid file name");
|
||||
}
|
||||
|
||||
private static getPostFromMarkdown(markdownContent: string): string {
|
||||
const authorLine: string | undefined = markdownContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('Author:');
|
||||
});
|
||||
const titleLine: string | undefined = markdownContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('#');
|
||||
});
|
||||
const descriptionLine: string | undefined =
|
||||
markdownContent.split('\n').find((line: string) => {
|
||||
return line.startsWith('Description:');
|
||||
}) || '';
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
const tagsLine: string | undefined =
|
||||
markdownContent.split('\n').find((line: string) => {
|
||||
return line.startsWith('Tags:');
|
||||
}) || '';
|
||||
private static getFormattedPostDateFromFileName(fileName: string): string {
|
||||
// file name is of the format YYYY-MM-DD-Title.md
|
||||
const year: string | undefined = fileName.split("-")[0];
|
||||
const month: string | undefined = fileName.split("-")[1];
|
||||
const day: string | undefined = fileName.split("-")[2];
|
||||
|
||||
if (!authorLine && !titleLine && !descriptionLine && !tagsLine) {
|
||||
return markdownContent;
|
||||
}
|
||||
|
||||
const lines: string[] = markdownContent.split('\n');
|
||||
|
||||
if (authorLine) {
|
||||
const authorLineIndex: number = lines.indexOf(authorLine);
|
||||
lines.splice(authorLineIndex, 1);
|
||||
}
|
||||
|
||||
if (titleLine) {
|
||||
const titleLineIndex: number = lines.indexOf(titleLine);
|
||||
lines.splice(titleLineIndex, 1);
|
||||
}
|
||||
|
||||
if (descriptionLine) {
|
||||
const descriptionLineIndex: number = lines.indexOf(descriptionLine);
|
||||
lines.splice(descriptionLineIndex, 1);
|
||||
}
|
||||
|
||||
if (tagsLine) {
|
||||
const tagsLineIndex: number = lines.indexOf(tagsLine);
|
||||
lines.splice(tagsLineIndex, 1);
|
||||
}
|
||||
|
||||
return lines.join('\n').trim();
|
||||
if (!year || !month || !day) {
|
||||
throw new BadDataException("Invalid file name");
|
||||
}
|
||||
|
||||
public static getBlogPostFromCache(fileName: string): BlogPost | null {
|
||||
const blogPost: BlogPost | null = LocalCache.getJSON(
|
||||
'blog',
|
||||
fileName
|
||||
) as BlogPost | null;
|
||||
return blogPost;
|
||||
const date: Date = OneUptimeDate.getDateFromYYYYMMDD(year, month, day);
|
||||
return OneUptimeDate.getDateAsLocalFormattedString(date, true);
|
||||
}
|
||||
|
||||
private static getPostFromMarkdown(markdownContent: string): string {
|
||||
const authorLine: string | undefined = markdownContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("Author:");
|
||||
});
|
||||
const titleLine: string | undefined = markdownContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("#");
|
||||
});
|
||||
const descriptionLine: string | undefined =
|
||||
markdownContent.split("\n").find((line: string) => {
|
||||
return line.startsWith("Description:");
|
||||
}) || "";
|
||||
|
||||
const tagsLine: string | undefined =
|
||||
markdownContent.split("\n").find((line: string) => {
|
||||
return line.startsWith("Tags:");
|
||||
}) || "";
|
||||
|
||||
if (!authorLine && !titleLine && !descriptionLine && !tagsLine) {
|
||||
return markdownContent;
|
||||
}
|
||||
|
||||
public static getTitleFromFileContent(fileContent: string): string {
|
||||
// title is the first line that stars with "#"
|
||||
const lines: string[] = markdownContent.split("\n");
|
||||
|
||||
const titleLine: string =
|
||||
fileContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('#');
|
||||
})
|
||||
?.replace('#', '') || 'OneUptime Blog';
|
||||
|
||||
return titleLine;
|
||||
if (authorLine) {
|
||||
const authorLineIndex: number = lines.indexOf(authorLine);
|
||||
lines.splice(authorLineIndex, 1);
|
||||
}
|
||||
|
||||
public static getTagsFromFileContent(fileContent: string): string[] {
|
||||
// tags is the first line that starts with "Tags:"
|
||||
|
||||
const tagsLine: string | undefined =
|
||||
fileContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('Tags:');
|
||||
})
|
||||
?.replace('Tags:', '') || '';
|
||||
|
||||
return tagsLine.split(',').map((tag: string) => {
|
||||
return tag.trim();
|
||||
});
|
||||
if (titleLine) {
|
||||
const titleLineIndex: number = lines.indexOf(titleLine);
|
||||
lines.splice(titleLineIndex, 1);
|
||||
}
|
||||
|
||||
public static getDescriptionFromFileContent(fileContent: string): string {
|
||||
// description is the first line that starts with ">"
|
||||
|
||||
const descriptionLine: string | undefined =
|
||||
fileContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('Description:');
|
||||
})
|
||||
?.replace('Description:', '') || '';
|
||||
|
||||
return descriptionLine;
|
||||
if (descriptionLine) {
|
||||
const descriptionLineIndex: number = lines.indexOf(descriptionLine);
|
||||
lines.splice(descriptionLineIndex, 1);
|
||||
}
|
||||
|
||||
public static async getAuthorFromFileContent(
|
||||
fileContent: string
|
||||
): Promise<BlogPostAuthor | null> {
|
||||
// author line is in this format: Author: [username](githubUrl)
|
||||
|
||||
const authorLine: string | undefined = fileContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('Author:');
|
||||
});
|
||||
const authorUsername: string | undefined = authorLine
|
||||
?.split('[')[1]
|
||||
?.split(']')[0];
|
||||
const authorGitHubUrl: string | undefined = authorLine
|
||||
?.split('(')[1]
|
||||
?.split(')')[0];
|
||||
const authorProfileImageUrl: string = `https://avatars.githubusercontent.com/${authorUsername}`;
|
||||
|
||||
if (!authorUsername || !authorGitHubUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
username: authorUsername,
|
||||
githubUrl: authorGitHubUrl,
|
||||
profileImageUrl: authorProfileImageUrl,
|
||||
name: await this.getNameOfGitHubUser(authorUsername),
|
||||
};
|
||||
if (tagsLine) {
|
||||
const tagsLineIndex: number = lines.indexOf(tagsLine);
|
||||
lines.splice(tagsLineIndex, 1);
|
||||
}
|
||||
|
||||
return lines.join("\n").trim();
|
||||
}
|
||||
|
||||
public static getBlogPostFromCache(fileName: string): BlogPost | null {
|
||||
const blogPost: BlogPost | null = LocalCache.getJSON(
|
||||
"blog",
|
||||
fileName,
|
||||
) as BlogPost | null;
|
||||
return blogPost;
|
||||
}
|
||||
|
||||
public static getTitleFromFileContent(fileContent: string): string {
|
||||
// title is the first line that stars with "#"
|
||||
|
||||
const titleLine: string =
|
||||
fileContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("#");
|
||||
})
|
||||
?.replace("#", "") || "OneUptime Blog";
|
||||
|
||||
return titleLine;
|
||||
}
|
||||
|
||||
public static getTagsFromFileContent(fileContent: string): string[] {
|
||||
// tags is the first line that starts with "Tags:"
|
||||
|
||||
const tagsLine: string | undefined =
|
||||
fileContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("Tags:");
|
||||
})
|
||||
?.replace("Tags:", "") || "";
|
||||
|
||||
return tagsLine.split(",").map((tag: string) => {
|
||||
return tag.trim();
|
||||
});
|
||||
}
|
||||
|
||||
public static getDescriptionFromFileContent(fileContent: string): string {
|
||||
// description is the first line that starts with ">"
|
||||
|
||||
const descriptionLine: string | undefined =
|
||||
fileContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("Description:");
|
||||
})
|
||||
?.replace("Description:", "") || "";
|
||||
|
||||
return descriptionLine;
|
||||
}
|
||||
|
||||
public static async getAuthorFromFileContent(
|
||||
fileContent: string,
|
||||
): Promise<BlogPostAuthor | null> {
|
||||
// author line is in this format: Author: [username](githubUrl)
|
||||
|
||||
const authorLine: string | undefined = fileContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("Author:");
|
||||
});
|
||||
const authorUsername: string | undefined = authorLine
|
||||
?.split("[")[1]
|
||||
?.split("]")[0];
|
||||
const authorGitHubUrl: string | undefined = authorLine
|
||||
?.split("(")[1]
|
||||
?.split(")")[0];
|
||||
const authorProfileImageUrl: string = `https://avatars.githubusercontent.com/${authorUsername}`;
|
||||
|
||||
if (!authorUsername || !authorGitHubUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
username: authorUsername,
|
||||
githubUrl: authorGitHubUrl,
|
||||
profileImageUrl: authorProfileImageUrl,
|
||||
name: await this.getNameOfGitHubUser(authorUsername),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export const ViewsPath: string = '/usr/src/app/FeatureSet/Home/Views';
|
||||
export const StaticPath: string = '/usr/src/app/FeatureSet/Home/Static';
|
||||
export const ViewsPath: string = "/usr/src/app/FeatureSet/Home/Views";
|
||||
export const StaticPath: string = "/usr/src/app/FeatureSet/Home/Static";
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from './Config';
|
||||
import { ViewsPath } from "./Config";
|
||||
import { ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
export default class NotFoundUtil {
|
||||
public static renderNotFound(res: ExpressResponse): void {
|
||||
res.status(404);
|
||||
res.render(`${ViewsPath}/not-found.ejs`, {
|
||||
footerCards: false,
|
||||
support: false,
|
||||
cta: false,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
});
|
||||
}
|
||||
public static renderNotFound(res: ExpressResponse): void {
|
||||
res.status(404);
|
||||
res.render(`${ViewsPath}/not-found.ejs`, {
|
||||
footerCards: false,
|
||||
support: false,
|
||||
cta: false,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
import { ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from './Config';
|
||||
import { ViewsPath } from "./Config";
|
||||
import { ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
export default class ServerErrorUtil {
|
||||
public static renderServerError(res: ExpressResponse): void {
|
||||
res.status(500);
|
||||
res.render(`${ViewsPath}/server-error.ejs`, {
|
||||
footerCards: false,
|
||||
support: false,
|
||||
cta: false,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
});
|
||||
}
|
||||
public static renderServerError(res: ExpressResponse): void {
|
||||
res.status(500);
|
||||
res.render(`${ViewsPath}/server-error.ejs`, {
|
||||
footerCards: false,
|
||||
support: false,
|
||||
cta: false,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,79 +1,79 @@
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ResellerService from "CommonServer/Services/ResellerService";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import Reseller from 'Model/Models/Reseller';
|
||||
import ResellerService from 'CommonServer/Services/ResellerService';
|
||||
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import Reseller from "Model/Models/Reseller";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post(
|
||||
'/reseller/auth/:resellerid',
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const resellerId: string | undefined = req.params['resellerid'];
|
||||
"/reseller/auth/:resellerid",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const resellerId: string | undefined = req.params["resellerid"];
|
||||
|
||||
if (!resellerId) {
|
||||
throw new BadDataException('Reseller ID not found');
|
||||
}
|
||||
if (!resellerId) {
|
||||
throw new BadDataException("Reseller ID not found");
|
||||
}
|
||||
|
||||
const username: string = req.body['username'];
|
||||
const password: string = req.body['password'];
|
||||
const username: string = req.body["username"];
|
||||
const password: string = req.body["password"];
|
||||
|
||||
if (!username) {
|
||||
throw new BadDataException('Username not found');
|
||||
}
|
||||
if (!username) {
|
||||
throw new BadDataException("Username not found");
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
throw new BadDataException('Password not found');
|
||||
}
|
||||
if (!password) {
|
||||
throw new BadDataException("Password not found");
|
||||
}
|
||||
|
||||
// get the reseller user.
|
||||
const reseller: Reseller | null = await ResellerService.findOneBy({
|
||||
query: {
|
||||
resellerId: resellerId,
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
resellerId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
// get the reseller user.
|
||||
const reseller: Reseller | null = await ResellerService.findOneBy({
|
||||
query: {
|
||||
resellerId: resellerId,
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
resellerId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!reseller) {
|
||||
throw new BadDataException(
|
||||
'Reseller not found or username and password is incorrect'
|
||||
);
|
||||
}
|
||||
if (!reseller) {
|
||||
throw new BadDataException(
|
||||
"Reseller not found or username and password is incorrect",
|
||||
);
|
||||
}
|
||||
|
||||
// if found then generate a token and return it.
|
||||
// if found then generate a token and return it.
|
||||
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: { resellerId: resellerId },
|
||||
expiresInSeconds: OneUptimeDate.getDayInSeconds(365),
|
||||
});
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: { resellerId: resellerId },
|
||||
expiresInSeconds: OneUptimeDate.getDayInSeconds(365),
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
access: token,
|
||||
});
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
access: token,
|
||||
});
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user