mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
1337 Commits
probe-prox
...
files-note
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ff0861d68 | ||
|
|
fcf919c70b | ||
|
|
f0f3d32d31 | ||
|
|
444e8f17b6 | ||
|
|
3aabf44b4e | ||
|
|
c11fcc3c8e | ||
|
|
52519c9af8 | ||
|
|
2483cf9499 | ||
|
|
634e21b13c | ||
|
|
aad933b9eb | ||
|
|
9356f2964e | ||
|
|
aae70ead3b | ||
|
|
8a482dce10 | ||
|
|
9fdf46889c | ||
|
|
40ca9dc04c | ||
|
|
74937f2208 | ||
|
|
c02ab56477 | ||
|
|
3f99b9680f | ||
|
|
b08c39037d | ||
|
|
f7cc3c00da | ||
|
|
ac4286935a | ||
|
|
90a0b2e4a8 | ||
|
|
9b22c48d27 | ||
|
|
9c9dad5da0 | ||
|
|
e986f74025 | ||
|
|
deb2e81b21 | ||
|
|
0f8b322892 | ||
|
|
23c7de3ecd | ||
|
|
ad144a6240 | ||
|
|
debfef0388 | ||
|
|
bb85c9f8c8 | ||
|
|
25ab1cdbf9 | ||
|
|
44b8a9ddc9 | ||
|
|
c388ff9550 | ||
|
|
321d1680e6 | ||
|
|
6c0e9f0fed | ||
|
|
99349ecb30 | ||
|
|
258bbbd9cf | ||
|
|
1094a07fc6 | ||
|
|
14a5671645 | ||
|
|
5a41c66953 | ||
|
|
af605fce4c | ||
|
|
f8ef6c69fe | ||
|
|
e1848f44f7 | ||
|
|
825bd39dda | ||
|
|
b99905dfe8 | ||
|
|
a4bf40a2c1 | ||
|
|
711998b048 | ||
|
|
132e044c07 | ||
|
|
8ecc307451 | ||
|
|
c85c29989f | ||
|
|
95726e0f21 | ||
|
|
adc15992e9 | ||
|
|
58d83a2a80 | ||
|
|
5461cd4502 | ||
|
|
478465a65b | ||
|
|
bef57b784d | ||
|
|
601eed0e70 | ||
|
|
dc1215f28b | ||
|
|
f617330484 | ||
|
|
888d8d6fbf | ||
|
|
79a9bb2229 | ||
|
|
96f0b111c1 | ||
|
|
044ec492da | ||
|
|
6d4462c969 | ||
|
|
0578f9f1be | ||
|
|
ebbddbd797 | ||
|
|
6cdaaf1489 | ||
|
|
93e62befbb | ||
|
|
bccd2f484b | ||
|
|
c01fc9ee64 | ||
|
|
be9dcbd01e | ||
|
|
48f85df04c | ||
|
|
fc08578ff2 | ||
|
|
ea9a245b82 | ||
|
|
f49b1995df | ||
|
|
351fc4828b | ||
|
|
04cb7da6d6 | ||
|
|
c6e2f41351 | ||
|
|
e524dfda6d | ||
|
|
30b6353714 | ||
|
|
29be9b399f | ||
|
|
36cdeec916 | ||
|
|
a80b7ba88c | ||
|
|
b31d1076b8 | ||
|
|
788e82497c | ||
|
|
3409b0f66e | ||
|
|
cafed35795 | ||
|
|
69ae1eb310 | ||
|
|
4335c46e3e | ||
|
|
863f1ae82e | ||
|
|
0755c15886 | ||
|
|
72ed43563e | ||
|
|
15ded6cd46 | ||
|
|
9e238dc660 | ||
|
|
9f3ccc7d74 | ||
|
|
9e5db22235 | ||
|
|
e31417c5bf | ||
|
|
bf6e97c35d | ||
|
|
28073ba819 | ||
|
|
4909a5c980 | ||
|
|
c4adc24562 | ||
|
|
597344483a | ||
|
|
05d9b79ba2 | ||
|
|
af14edb175 | ||
|
|
0255bd37d0 | ||
|
|
2e6b463bd7 | ||
|
|
95e355ff7d | ||
|
|
cccdcdaf93 | ||
|
|
9256f8b4bd | ||
|
|
b27acbfd38 | ||
|
|
ad9771f222 | ||
|
|
20a3eab3a0 | ||
|
|
fbe198f0c0 | ||
|
|
bb48776e02 | ||
|
|
0f92342742 | ||
|
|
6ed41b87dd | ||
|
|
12364415aa | ||
|
|
1a3301e715 | ||
|
|
8be7b68faf | ||
|
|
47f9d3914e | ||
|
|
8a1afbe7dc | ||
|
|
87ac6f3106 | ||
|
|
2df32e4cdd | ||
|
|
2e2adffe17 | ||
|
|
2a15cf8676 | ||
|
|
d249579c1c | ||
|
|
05681b108b | ||
|
|
36867a0b8c | ||
|
|
0e5a832628 | ||
|
|
46f69fdde5 | ||
|
|
70e6c0abe1 | ||
|
|
c5938956af | ||
|
|
e9bfe74b5d | ||
|
|
55085a5e6c | ||
|
|
9cb48a41e7 | ||
|
|
dd8179c0a7 | ||
|
|
f9f84d4104 | ||
|
|
827663675d | ||
|
|
0e8d7f2d6b | ||
|
|
0dd9dfb505 | ||
|
|
b3c68df77e | ||
|
|
06d0510667 | ||
|
|
059b7db474 | ||
|
|
ff17d990d1 | ||
|
|
2e6658542b | ||
|
|
04a4d6e4de | ||
|
|
2f595fe490 | ||
|
|
303554d644 | ||
|
|
2ca45b143d | ||
|
|
b5722256c5 | ||
|
|
0d231a6132 | ||
|
|
ecc1d841e2 | ||
|
|
cddf534449 | ||
|
|
a4ba662211 | ||
|
|
604bc33fb3 | ||
|
|
a1ae1bee89 | ||
|
|
775b8846c7 | ||
|
|
3837208023 | ||
|
|
b45910a22e | ||
|
|
c787d7eca0 | ||
|
|
9771c4cd69 | ||
|
|
4471d6bec4 | ||
|
|
95e24d9ae0 | ||
|
|
f8a7330f79 | ||
|
|
ae177c920f | ||
|
|
9faa38454d | ||
|
|
a4198ec409 | ||
|
|
24d184debf | ||
|
|
f307c904b0 | ||
|
|
2d20b7fd13 | ||
|
|
fd738b23d0 | ||
|
|
deffa6489d | ||
|
|
e5f1d5553e | ||
|
|
d2ee3c5409 | ||
|
|
9a1ecd7fe0 | ||
|
|
b41d8ab5ab | ||
|
|
b809e1c43b | ||
|
|
743f8721f3 | ||
|
|
c0aa2b7905 | ||
|
|
4ac5819e6a | ||
|
|
158663c44b | ||
|
|
3d2bcfa579 | ||
|
|
21984c8684 | ||
|
|
ad63d18f0a | ||
|
|
e5af008079 | ||
|
|
3e72b2a9a4 | ||
|
|
6d66c6c369 | ||
|
|
9544dc2a6c | ||
|
|
a22e3b63e0 | ||
|
|
5f1f0cde4a | ||
|
|
3025880d6d | ||
|
|
00994b56c5 | ||
|
|
d56fd8bf69 | ||
|
|
1229d5d204 | ||
|
|
c4f1f4e711 | ||
|
|
7b8986b011 | ||
|
|
46e6176e6a | ||
|
|
61f9572956 | ||
|
|
3adc6901da | ||
|
|
d7f1bfb52a | ||
|
|
53968e681c | ||
|
|
3b8c854744 | ||
|
|
669ed2580c | ||
|
|
2f29c2e24c | ||
|
|
f0a2f454e2 | ||
|
|
d8206e12de | ||
|
|
f84434ada4 | ||
|
|
d5fbe0443e | ||
|
|
2732cd65ed | ||
|
|
7624523446 | ||
|
|
5851286548 | ||
|
|
123d9b07bc | ||
|
|
9edc6ac428 | ||
|
|
72fc633bf1 | ||
|
|
3264322054 | ||
|
|
d8fedc0b19 | ||
|
|
fc7cc5fe7f | ||
|
|
5b4eb72521 | ||
|
|
d84cfe9b09 | ||
|
|
0e8926a786 | ||
|
|
12ff3062de | ||
|
|
30aad2866f | ||
|
|
3de636ab9e | ||
|
|
a1bf9cbaae | ||
|
|
12c800b81f | ||
|
|
677e687662 | ||
|
|
93719d67be | ||
|
|
7d23209198 | ||
|
|
4461127a36 | ||
|
|
8326bf2c9e | ||
|
|
be9d2f6beb | ||
|
|
214dae6204 | ||
|
|
71c845d94e | ||
|
|
87d709dd05 | ||
|
|
25332f99fd | ||
|
|
1ac6e71f7e | ||
|
|
f1efd65ada | ||
|
|
bc338f41c7 | ||
|
|
39153735b5 | ||
|
|
ae9a78f1f4 | ||
|
|
f224ad6092 | ||
|
|
1abb8bc83f | ||
|
|
46704b7c5a | ||
|
|
143d91ceab | ||
|
|
ea58cacd1b | ||
|
|
5e19849ac8 | ||
|
|
f7c05645a9 | ||
|
|
1c1a48b78f | ||
|
|
13860be56d | ||
|
|
38c29664ea | ||
|
|
df1507b314 | ||
|
|
65c999b5fc | ||
|
|
803d0436ca | ||
|
|
b98e7f13a5 | ||
|
|
0785f11abe | ||
|
|
f0d9f7c594 | ||
|
|
dc9463f73d | ||
|
|
37c8e8b6b6 | ||
|
|
763dfaa1c9 | ||
|
|
c2e0d402d5 | ||
|
|
cdc1526fbf | ||
|
|
13ebd34e8f | ||
|
|
3b97c23039 | ||
|
|
7c15424565 | ||
|
|
6817443d9a | ||
|
|
f40a6395a6 | ||
|
|
81eb735aab | ||
|
|
923339710b | ||
|
|
1f9ec3011c | ||
|
|
668093b09c | ||
|
|
f39f51e8ee | ||
|
|
0bff616ca9 | ||
|
|
709b9b8343 | ||
|
|
aa93252407 | ||
|
|
b4e7ffce43 | ||
|
|
1053d22d3f | ||
|
|
a37bdb303d | ||
|
|
860af1bbf3 | ||
|
|
d5e72c1af2 | ||
|
|
6cc6fc4bf1 | ||
|
|
f890f24650 | ||
|
|
d077e55241 | ||
|
|
a4b7c99b8a | ||
|
|
671be425ae | ||
|
|
d2d7a51842 | ||
|
|
1142a20d64 | ||
|
|
8d0d7dc759 | ||
|
|
cedf06ba42 | ||
|
|
b347e18749 | ||
|
|
50e9a53547 | ||
|
|
d0de004498 | ||
|
|
65c4998048 | ||
|
|
5e7a3795c7 | ||
|
|
cf83319a90 | ||
|
|
87dc9d88d0 | ||
|
|
444cf040a6 | ||
|
|
2754657a6f | ||
|
|
38ca6b1e9e | ||
|
|
f481ef4f5e | ||
|
|
ad9adca473 | ||
|
|
819bd54a1f | ||
|
|
e212079b4a | ||
|
|
bb09dafbcc | ||
|
|
60c472cc09 | ||
|
|
0e272f0f31 | ||
|
|
f5de74611d | ||
|
|
03d157b850 | ||
|
|
da21cfc1ff | ||
|
|
2781bf0583 | ||
|
|
69b16c1c85 | ||
|
|
727f009d79 | ||
|
|
65d916f349 | ||
|
|
4373c7b49c | ||
|
|
34737fbba4 | ||
|
|
90fcfd1c7e | ||
|
|
b55320f02c | ||
|
|
34dc078197 | ||
|
|
2eacc90714 | ||
|
|
9d93d59f91 | ||
|
|
d84039e621 | ||
|
|
4eb46cf8a0 | ||
|
|
1ef27b7f52 | ||
|
|
412bd370df | ||
|
|
1131b80a52 | ||
|
|
8b55f5c348 | ||
|
|
159e5c4023 | ||
|
|
4970538d43 | ||
|
|
d7ca021d52 | ||
|
|
27eff7f415 | ||
|
|
50543ec7bf | ||
|
|
94c39408ed | ||
|
|
7219e1850f | ||
|
|
3180ed7149 | ||
|
|
79f32b80c8 | ||
|
|
cebfde6bf2 | ||
|
|
ca644d9dc7 | ||
|
|
ca4d9cb176 | ||
|
|
56204e02a9 | ||
|
|
c8e8a6d687 | ||
|
|
5bd2204eee | ||
|
|
72a31ed268 | ||
|
|
ee188dd050 | ||
|
|
3807aad63b | ||
|
|
055ec956fd | ||
|
|
e7767e59d1 | ||
|
|
f643e907b4 | ||
|
|
2eec57befd | ||
|
|
dd653f8deb | ||
|
|
f403c6a9e9 | ||
|
|
35f9b7f5c4 | ||
|
|
3c487ff9b9 | ||
|
|
41fca346b9 | ||
|
|
91b54ced67 | ||
|
|
ebdd97b8e9 | ||
|
|
8eb1eac629 | ||
|
|
a075b3c4dd | ||
|
|
738f901a51 | ||
|
|
683a8f5a58 | ||
|
|
160eba1ea4 | ||
|
|
89b65d1e02 | ||
|
|
caf709a38a | ||
|
|
d57433e4a0 | ||
|
|
5e2aa4e622 | ||
|
|
6cb51dd54b | ||
|
|
53ea843bdc | ||
|
|
71eeaf7ecd | ||
|
|
1d0168fcc6 | ||
|
|
99c3d440c5 | ||
|
|
5959ce728f | ||
|
|
0cb9e382a6 | ||
|
|
0c6d561b7c | ||
|
|
4291a76dd4 | ||
|
|
05b1f0ea82 | ||
|
|
b943505b1d | ||
|
|
1ed236eb91 | ||
|
|
44795182c9 | ||
|
|
50267534e0 | ||
|
|
f60836a4dc | ||
|
|
303294bb5e | ||
|
|
1212a8e4be | ||
|
|
25c626d2d4 | ||
|
|
ff6f9c89fa | ||
|
|
7f6e905c74 | ||
|
|
2cc64838aa | ||
|
|
3de5c8da8c | ||
|
|
7403ffa053 | ||
|
|
fa473474a2 | ||
|
|
20cbcf9a74 | ||
|
|
e6fd4b8304 | ||
|
|
4a3a743dcd | ||
|
|
6f1e2234d3 | ||
|
|
07189b4567 | ||
|
|
b1bc02cec4 | ||
|
|
6f7795aa31 | ||
|
|
50ee87c86f | ||
|
|
a75c6b6a43 | ||
|
|
f6168c969e | ||
|
|
2b0f9f2e7a | ||
|
|
14377c68fe | ||
|
|
9176fa2c9b | ||
|
|
4f29fef5f6 | ||
|
|
f28c7695ab | ||
|
|
bc234deb0e | ||
|
|
546b4a4fb3 | ||
|
|
1300c4e667 | ||
|
|
988d5d327c | ||
|
|
f2510a7b89 | ||
|
|
a2f16ca0eb | ||
|
|
1448288395 | ||
|
|
734481df6c | ||
|
|
43c534b76a | ||
|
|
1b593403b6 | ||
|
|
54e60cc380 | ||
|
|
28d9879dbd | ||
|
|
22c417ac92 | ||
|
|
401926c792 | ||
|
|
90bc4de84a | ||
|
|
93fd2a9ed7 | ||
|
|
231451e359 | ||
|
|
3073891a38 | ||
|
|
2b62d31e8b | ||
|
|
bc43e6e6ea | ||
|
|
2204ebde26 | ||
|
|
57a8547e14 | ||
|
|
53a70e1b93 | ||
|
|
3eb72ef7f9 | ||
|
|
2a471133c5 | ||
|
|
34e92679b2 | ||
|
|
8b0c2a7320 | ||
|
|
353d894394 | ||
|
|
09e4f0ff97 | ||
|
|
247c4bc637 | ||
|
|
72e2edd49d | ||
|
|
1be494169d | ||
|
|
3796053403 | ||
|
|
f316bfb9fd | ||
|
|
40f9613bd3 | ||
|
|
a3bb9f003f | ||
|
|
70714b2f21 | ||
|
|
a62543bff0 | ||
|
|
826a2006d0 | ||
|
|
a4075fe349 | ||
|
|
eb70a923f3 | ||
|
|
11d39898d3 | ||
|
|
8e5907d523 | ||
|
|
c9e57fcb19 | ||
|
|
8c6bc331a4 | ||
|
|
20129e606a | ||
|
|
55e0eede68 | ||
|
|
ffa603503d | ||
|
|
18429caabe | ||
|
|
ccbfef1cfe | ||
|
|
4789a15ce7 | ||
|
|
9633307de0 | ||
|
|
ee5c2a0f33 | ||
|
|
5c3b181507 | ||
|
|
6bbf2f866c | ||
|
|
60e4b51ec9 | ||
|
|
f244f872d4 | ||
|
|
3e910c1308 | ||
|
|
2b0067fd17 | ||
|
|
cb23b6b55b | ||
|
|
4406d52307 | ||
|
|
03b6bec6d0 | ||
|
|
ff0bd88b02 | ||
|
|
94290c77db | ||
|
|
003e44d331 | ||
|
|
b4cf798246 | ||
|
|
e607367809 | ||
|
|
82a7b5794c | ||
|
|
37c1674029 | ||
|
|
cd2a5222b8 | ||
|
|
875d6c5df9 | ||
|
|
7eea90a55a | ||
|
|
c401b86a2e | ||
|
|
e96f781157 | ||
|
|
73cc5f8f9e | ||
|
|
0ee9876de9 | ||
|
|
b76a75479d | ||
|
|
79b9cf4c06 | ||
|
|
a6da59c966 | ||
|
|
b84695feb9 | ||
|
|
b77973441d | ||
|
|
27e9c07c57 | ||
|
|
cbf8684d8c | ||
|
|
87057757a5 | ||
|
|
23b587f0f6 | ||
|
|
81c7a4eeb7 | ||
|
|
549dbfd6c7 | ||
|
|
a2eac673eb | ||
|
|
086a0a661d | ||
|
|
da0620eafa | ||
|
|
ca90ab0db4 | ||
|
|
94dacc20db | ||
|
|
7e887bd4cd | ||
|
|
b50dfcdf1c | ||
|
|
e2bc0ea4aa | ||
|
|
7d018b94d3 | ||
|
|
9fb7a70dc9 | ||
|
|
a37b3fc0b3 | ||
|
|
c06ef5ddfc | ||
|
|
0fb7174e94 | ||
|
|
84a7cd976d | ||
|
|
f7c8c00f04 | ||
|
|
51c3fcd3ca | ||
|
|
74c3dde7f1 | ||
|
|
6bf45f6f31 | ||
|
|
4de4ad8022 | ||
|
|
e263900115 | ||
|
|
34aaa34fb3 | ||
|
|
9f72a8e554 | ||
|
|
8dfabfd96f | ||
|
|
14cdc3ea86 | ||
|
|
cd6abe63ea | ||
|
|
aeb6d53b9d | ||
|
|
efa7224718 | ||
|
|
a2c406d7cc | ||
|
|
1933e37beb | ||
|
|
0b0336f9ea | ||
|
|
9304079a1c | ||
|
|
1e2dcf332b | ||
|
|
ca860f54a8 | ||
|
|
513e4146ed | ||
|
|
43d31ddbe9 | ||
|
|
18231f42aa | ||
|
|
60291cc218 | ||
|
|
3658f0349e | ||
|
|
02133165d8 | ||
|
|
837b841352 | ||
|
|
10e344dad5 | ||
|
|
848bfb358f | ||
|
|
2ca1c64532 | ||
|
|
d9a79eafbd | ||
|
|
a1dc16f4b6 | ||
|
|
674613c0d6 | ||
|
|
e4e095798a | ||
|
|
2e07185584 | ||
|
|
144001981a | ||
|
|
cd096f66ea | ||
|
|
036b29da51 | ||
|
|
d0d59147ae | ||
|
|
565fbe6cd3 | ||
|
|
12c3f9b25c | ||
|
|
02cffe9f8b | ||
|
|
9c52e8966f | ||
|
|
9257a949fe | ||
|
|
7528ed8e0c | ||
|
|
98b6f3eac3 | ||
|
|
a2561b3ae8 | ||
|
|
368f5c6bbc | ||
|
|
0e2e30b0a9 | ||
|
|
2664a24875 | ||
|
|
40e486669f | ||
|
|
ae64cbc718 | ||
|
|
e14f691cc4 | ||
|
|
89c607a530 | ||
|
|
90f4e7418f | ||
|
|
79fed2da09 | ||
|
|
1dd41c103f | ||
|
|
988e85affc | ||
|
|
2810516987 | ||
|
|
679c1971a2 | ||
|
|
3abdbb7ef9 | ||
|
|
0d647f5dc1 | ||
|
|
bd1df491a2 | ||
|
|
feb872d05c | ||
|
|
64b6b99a21 | ||
|
|
8046c244b1 | ||
|
|
6928316ba0 | ||
|
|
36c74642f2 | ||
|
|
5a992e99c8 | ||
|
|
ff9230f878 | ||
|
|
9afa861ff2 | ||
|
|
6d7486e76d | ||
|
|
8529012b19 | ||
|
|
f704bd47a3 | ||
|
|
239f2fc34e | ||
|
|
5d5183b08e | ||
|
|
7cad0fab0f | ||
|
|
98e0d55ee3 | ||
|
|
2d8f0d7a58 | ||
|
|
643303cd7a | ||
|
|
9ee0e0f3cb | ||
|
|
dc692203be | ||
|
|
648c51d007 | ||
|
|
0e5b106333 | ||
|
|
40e9ea2ab6 | ||
|
|
2157e228b9 | ||
|
|
5c5534adb8 | ||
|
|
379297cd7e | ||
|
|
b3730e9708 | ||
|
|
2fbc44d5c3 | ||
|
|
12f05937af | ||
|
|
5ea440492b | ||
|
|
57b851a498 | ||
|
|
00f806b077 | ||
|
|
975c20a788 | ||
|
|
948e2d93c1 | ||
|
|
6de3c93745 | ||
|
|
1f63110561 | ||
|
|
8a94c35450 | ||
|
|
fdc97284a5 | ||
|
|
792ecfdbdc | ||
|
|
121b01dc08 | ||
|
|
1aa49071eb | ||
|
|
66bef1284a | ||
|
|
1526f708de | ||
|
|
53198e4486 | ||
|
|
a66bf2df2a | ||
|
|
34422721c3 | ||
|
|
b5008c2363 | ||
|
|
1cbab2be08 | ||
|
|
1bdcfd71f7 | ||
|
|
792271b146 | ||
|
|
3de5fbd35c | ||
|
|
6f8dc1ed59 | ||
|
|
92309d8fb2 | ||
|
|
d3070975cb | ||
|
|
69c0da5b17 | ||
|
|
1736690f01 | ||
|
|
78a92905e9 | ||
|
|
801c1b4ccb | ||
|
|
703b1dca51 | ||
|
|
e5ef97abd9 | ||
|
|
3053856990 | ||
|
|
b8e82e2801 | ||
|
|
2187fe63f0 | ||
|
|
b1f842f9e1 | ||
|
|
43e03fb61c | ||
|
|
2327ab84c2 | ||
|
|
93034b6018 | ||
|
|
e45022b5cb | ||
|
|
c022b70e6d | ||
|
|
5615ba2df7 | ||
|
|
268960bc5b | ||
|
|
508a713ecf | ||
|
|
36e50b591a | ||
|
|
05c1f95ba4 | ||
|
|
9d355691ae | ||
|
|
73c58186b6 | ||
|
|
eb8d3e4dfd | ||
|
|
987f30e5c7 | ||
|
|
b7a0dbf81b | ||
|
|
7368a0eb7c | ||
|
|
ef0b6f3e14 | ||
|
|
af8691f61d | ||
|
|
f7aee2e253 | ||
|
|
0a1b74d911 | ||
|
|
ab48da447d | ||
|
|
1cc0630939 | ||
|
|
5391ff4688 | ||
|
|
f6d96676fe | ||
|
|
cf02842ab1 | ||
|
|
b63fcf6b99 | ||
|
|
36069c1b4e | ||
|
|
d2846decce | ||
|
|
60a8a3f052 | ||
|
|
e2d15dc2e7 | ||
|
|
7cdefdeccd | ||
|
|
684b8822af | ||
|
|
231bc47942 | ||
|
|
965a497be3 | ||
|
|
f50a7fb99b | ||
|
|
50a5e75d1a | ||
|
|
84e838a055 | ||
|
|
6d6c78e974 | ||
|
|
778d5b7c6b | ||
|
|
8051146f41 | ||
|
|
86a359a230 | ||
|
|
c16dac65cc | ||
|
|
437c9ecdbc | ||
|
|
bf4eec2bdf | ||
|
|
08367f3c7f | ||
|
|
f5d077956a | ||
|
|
ca74005262 | ||
|
|
52c936935e | ||
|
|
2951600ed9 | ||
|
|
d12c8c778c | ||
|
|
77d4527a00 | ||
|
|
1ef3353155 | ||
|
|
2c635c0d1e | ||
|
|
44799f4625 | ||
|
|
4bc6e625d2 | ||
|
|
9ff773dd81 | ||
|
|
bf2c2afbb8 | ||
|
|
3efa3b374f | ||
|
|
f76b004e2a | ||
|
|
cb535bd114 | ||
|
|
9b09082d13 | ||
|
|
5ac6e7ba7b | ||
|
|
044183dbd7 | ||
|
|
fb13609c37 | ||
|
|
1c602b1ad3 | ||
|
|
1affb76085 | ||
|
|
0e02083dec | ||
|
|
a442b02337 | ||
|
|
3ab47328f6 | ||
|
|
57ff8c007b | ||
|
|
c83d1babcd | ||
|
|
1bc96d04fa | ||
|
|
e03a9b52ae | ||
|
|
a307a68ec1 | ||
|
|
1032f01278 | ||
|
|
83286aa34d | ||
|
|
543757a2b5 | ||
|
|
d0bbb12f92 | ||
|
|
066e785a53 | ||
|
|
2f46eadcd4 | ||
|
|
c472555f55 | ||
|
|
83c9255b98 | ||
|
|
1748a198c7 | ||
|
|
a4841f4b6e | ||
|
|
1c750d274e | ||
|
|
2dda68c462 | ||
|
|
6d5bc111ba | ||
|
|
0e1c8df7ab | ||
|
|
4b55cb3d84 | ||
|
|
eb66b57939 | ||
|
|
b53119d249 | ||
|
|
95c2db5ace | ||
|
|
8c4bc48c44 | ||
|
|
28f3b98d44 | ||
|
|
742da27d87 | ||
|
|
e2ba7dff8d | ||
|
|
3f6a58a087 | ||
|
|
3f2005bd34 | ||
|
|
651ba4247e | ||
|
|
ba71cf5980 | ||
|
|
442a4b935b | ||
|
|
5caf5e8991 | ||
|
|
b5e4545193 | ||
|
|
ce4cc70815 | ||
|
|
426dda60fe | ||
|
|
c6be5a9ebf | ||
|
|
eb19b8926b | ||
|
|
955a3f784d | ||
|
|
756dc88289 | ||
|
|
c116f9adc1 | ||
|
|
324f29edc3 | ||
|
|
03064308ef | ||
|
|
9dd4ac6f6b | ||
|
|
8b8f1ba530 | ||
|
|
31e77d2208 | ||
|
|
c6e78a3264 | ||
|
|
c474d3b1e5 | ||
|
|
3afbbfae5a | ||
|
|
92c3c619ee | ||
|
|
b65e28684e | ||
|
|
dabf1464f2 | ||
|
|
9f618acc31 | ||
|
|
3dd4caeae9 | ||
|
|
1345693175 | ||
|
|
b5a422e8aa | ||
|
|
122773b0ba | ||
|
|
cfc1d5d820 | ||
|
|
18d1df86d5 | ||
|
|
4b65366080 | ||
|
|
74bb6acd62 | ||
|
|
a47b0fe21d | ||
|
|
349e64e3a2 | ||
|
|
0f64b49f9c | ||
|
|
1f8d4967ee | ||
|
|
e8b580ab4c | ||
|
|
866183d95a | ||
|
|
ca64843de0 | ||
|
|
d7427b9abe | ||
|
|
77537999b0 | ||
|
|
263c341792 | ||
|
|
45b1dcbb7e | ||
|
|
43e344dfb1 | ||
|
|
e5b2e70ce2 | ||
|
|
602383a720 | ||
|
|
c10e691bf5 | ||
|
|
ab7c8c1df9 | ||
|
|
72236b025f | ||
|
|
1c5c9ee3ad | ||
|
|
352c923e74 | ||
|
|
d3ebbf1c88 | ||
|
|
411a455d54 | ||
|
|
4570d617b2 | ||
|
|
305094a8b2 | ||
|
|
b02d1da202 | ||
|
|
1407a0fce9 | ||
|
|
836fb91cbe | ||
|
|
4c819ca906 | ||
|
|
3bd7fb7c59 | ||
|
|
99e7193961 | ||
|
|
db10bba4d3 | ||
|
|
31a425d34d | ||
|
|
7870406295 | ||
|
|
77de0a1116 | ||
|
|
f2da31a6f9 | ||
|
|
d36577069f | ||
|
|
4cdf959a01 | ||
|
|
62adb3fd76 | ||
|
|
5bc6e67c21 | ||
|
|
d7e86a56e6 | ||
|
|
c2b569c13a | ||
|
|
6d6555396a | ||
|
|
13018c8169 | ||
|
|
82f462785b | ||
|
|
9f21725949 | ||
|
|
82bee44933 | ||
|
|
b67c702d4d | ||
|
|
e5017908ae | ||
|
|
eff87e1705 | ||
|
|
9fabdbbb2b | ||
|
|
56908674bb | ||
|
|
9fe3fb041e | ||
|
|
46d3bf527b | ||
|
|
06e11aa8d7 | ||
|
|
acd9a87694 | ||
|
|
9a4f799145 | ||
|
|
edd067f79f | ||
|
|
36b6b8423a | ||
|
|
81c558dc21 | ||
|
|
a23d585ec6 | ||
|
|
c193bd71d7 | ||
|
|
b1791602c8 | ||
|
|
e98059e4ee | ||
|
|
371ba8f414 | ||
|
|
a271ba2cd9 | ||
|
|
a337065c4f | ||
|
|
22479a6f9e | ||
|
|
29051c3010 | ||
|
|
aaa29c87ca | ||
|
|
0385dc7ac8 | ||
|
|
d55e354a07 | ||
|
|
20716b7c7e | ||
|
|
86143d1585 | ||
|
|
214915528b | ||
|
|
9b24ff50ec | ||
|
|
9caeb34f63 | ||
|
|
368f33db24 | ||
|
|
5d7a18cbe2 | ||
|
|
19f663d0fd | ||
|
|
d857024fbe | ||
|
|
027d766e03 | ||
|
|
e7599c2202 | ||
|
|
703b525310 | ||
|
|
534882bc17 | ||
|
|
bdb2170663 | ||
|
|
dffb11b304 | ||
|
|
4e5386fccf | ||
|
|
6c11fbf850 | ||
|
|
f713eb7546 | ||
|
|
0d1f07b5ae | ||
|
|
ffcaaf213f | ||
|
|
b05a0619fb | ||
|
|
cbd4d26189 | ||
|
|
1f24a79a8a | ||
|
|
94227a103d | ||
|
|
cd1bf5befe | ||
|
|
e50e75b009 | ||
|
|
9656fbdae4 | ||
|
|
1004251175 | ||
|
|
5c70aea851 | ||
|
|
25d5cc2a47 | ||
|
|
b6802bf949 | ||
|
|
5af14af52a | ||
|
|
fffe84526e | ||
|
|
e776186070 | ||
|
|
0f4f974d04 | ||
|
|
22ecea6381 | ||
|
|
d9ad2e3036 | ||
|
|
42e60213c6 | ||
|
|
6817a0759a | ||
|
|
963e2ffc6d | ||
|
|
5c2c50ecc6 | ||
|
|
1bb4e77075 | ||
|
|
1f22c231d6 | ||
|
|
4b3baa9ad3 | ||
|
|
503728345f | ||
|
|
958b6bf72c | ||
|
|
6bb5ec12f5 | ||
|
|
30b2b63b9b | ||
|
|
6c37290f49 | ||
|
|
b19302f8b9 | ||
|
|
acd0629791 | ||
|
|
3fa1aebceb | ||
|
|
1d6a645241 | ||
|
|
922e30f162 | ||
|
|
73beb80056 | ||
|
|
72fd74862c | ||
|
|
13d403ecf8 | ||
|
|
30bca619dd | ||
|
|
f969adba9c | ||
|
|
b42abc6e42 | ||
|
|
7ec183f9e9 | ||
|
|
d33c739372 | ||
|
|
d8e12daec5 | ||
|
|
2a54cfc527 | ||
|
|
c0241a2e20 | ||
|
|
e32d7cb368 | ||
|
|
4dec1290e8 | ||
|
|
04f6493a6d | ||
|
|
f113e84aa5 | ||
|
|
57f764b92a | ||
|
|
c4c7d10d16 | ||
|
|
00ba94f372 | ||
|
|
f85f41ffa9 | ||
|
|
21dfcdfa63 | ||
|
|
c818decfc8 | ||
|
|
1386fef470 | ||
|
|
4c0cbc17a2 | ||
|
|
3c42447b41 | ||
|
|
e31f616dc1 | ||
|
|
cfbb65f7ae | ||
|
|
52c42dae1e | ||
|
|
d838d377a0 | ||
|
|
3a6f8b4c95 | ||
|
|
1039bd9f0b | ||
|
|
5f84c7195c | ||
|
|
9597f66ab1 | ||
|
|
1562f8ee6a | ||
|
|
11b0477cd6 | ||
|
|
174694e040 | ||
|
|
f5664116b9 | ||
|
|
0eb502b77a | ||
|
|
1460521dc0 | ||
|
|
e73c8bca16 | ||
|
|
a0fef8df3d | ||
|
|
1821377dfa | ||
|
|
ac2c501058 | ||
|
|
3ce3d1ee65 | ||
|
|
ad17e49177 | ||
|
|
18389a0c31 | ||
|
|
9d04975759 | ||
|
|
3bb9b3d78b | ||
|
|
ddfae282e6 | ||
|
|
02663bb33a | ||
|
|
6cb1a49128 | ||
|
|
4bcddf860c | ||
|
|
d8d4593d38 | ||
|
|
f76381525f | ||
|
|
91abb2318b | ||
|
|
3a9e695336 | ||
|
|
e270c5d70a | ||
|
|
b40e88e8ec | ||
|
|
2173ed288a | ||
|
|
82669c8f23 | ||
|
|
b093a730ab | ||
|
|
8700068468 | ||
|
|
13e322944b | ||
|
|
62aecc6e9f | ||
|
|
61eca28545 | ||
|
|
c289027efc | ||
|
|
ecdae56ffa | ||
|
|
005536633c | ||
|
|
d1ec1d6936 | ||
|
|
6f68629f29 | ||
|
|
8af4bece10 | ||
|
|
18a0d6ab26 | ||
|
|
6d73bb8a12 | ||
|
|
85b0f47be1 | ||
|
|
cc3596fc8e | ||
|
|
654367dbd8 | ||
|
|
e5ccdc1a56 | ||
|
|
84389212f6 | ||
|
|
5b01c202eb | ||
|
|
153ee4fc20 | ||
|
|
03e176794e | ||
|
|
9783f4897c | ||
|
|
928f6457bc | ||
|
|
b628bd3ad1 | ||
|
|
dba0d69f63 | ||
|
|
64c203259a | ||
|
|
25ec5c8df0 | ||
|
|
22270c62f4 | ||
|
|
bb7176252c | ||
|
|
97f62b1458 | ||
|
|
5eb333ccfc | ||
|
|
bd05afb0a7 | ||
|
|
96e6780e7b | ||
|
|
5e8ed144ae | ||
|
|
588ff245ec | ||
|
|
02cef807ea | ||
|
|
041ffdfbe0 | ||
|
|
e2c362a5b3 | ||
|
|
000ff4ad45 | ||
|
|
064e16cc6f | ||
|
|
fbc6b8fa48 | ||
|
|
4e0935873d | ||
|
|
35a40a431e | ||
|
|
d24c245b4a | ||
|
|
99f59f2f1e | ||
|
|
c558eb578f | ||
|
|
97380a5410 | ||
|
|
462f40680e | ||
|
|
8a11dbe35b | ||
|
|
d2c1467a07 | ||
|
|
ff57061190 | ||
|
|
63f1034f4a | ||
|
|
87ddec9e6c | ||
|
|
b2ea52e549 | ||
|
|
92bb753cbf | ||
|
|
7428e75643 | ||
|
|
0a2ed040f0 | ||
|
|
ded0aba399 | ||
|
|
0658248535 | ||
|
|
6cf2d842ad | ||
|
|
4a6bf95ef3 | ||
|
|
af7835fc8a | ||
|
|
5e7ed2be73 | ||
|
|
11894d0ba5 | ||
|
|
8bb92bbeb3 | ||
|
|
a50af21ce0 | ||
|
|
df29ed2b78 | ||
|
|
58cba788fc | ||
|
|
760044f603 | ||
|
|
fb099a1c3e | ||
|
|
32692bc939 | ||
|
|
c514b9cb74 | ||
|
|
5d44943958 | ||
|
|
df4138e5d3 | ||
|
|
51777709f3 | ||
|
|
f7416bb0a6 | ||
|
|
43b6fdf45d | ||
|
|
818995a6ae | ||
|
|
38fa1ba9f8 | ||
|
|
b862ac113f | ||
|
|
a2526a5a99 | ||
|
|
4124e7e9f7 | ||
|
|
05d3e73b83 | ||
|
|
907f0149d8 | ||
|
|
54f4c63a51 | ||
|
|
80388adb59 | ||
|
|
ca0792a7f7 | ||
|
|
7c1e42cff9 | ||
|
|
e9904c0e74 | ||
|
|
dd9d296e62 | ||
|
|
c0005618fe | ||
|
|
feb503b448 | ||
|
|
d2c411c9d6 | ||
|
|
562e930ca0 | ||
|
|
e97b2904e8 | ||
|
|
fd07d70575 | ||
|
|
3e6c8b3f68 | ||
|
|
ad772c4a5b | ||
|
|
5396c0c9cf | ||
|
|
c6e6ae5be0 | ||
|
|
82397fec5a | ||
|
|
6301e24c02 | ||
|
|
23b3a4d9dd | ||
|
|
e6c158b2b5 | ||
|
|
5a4f2e0744 | ||
|
|
545335a74d | ||
|
|
5ab49052fe | ||
|
|
2985b7675d | ||
|
|
49cd04c5d5 | ||
|
|
2a23d1a962 | ||
|
|
13f19adc13 | ||
|
|
3e79bbf55f | ||
|
|
b400e89a3b | ||
|
|
fc8362e7e5 | ||
|
|
8b209e82d5 | ||
|
|
6fb4fdf698 | ||
|
|
9551e64b16 | ||
|
|
232f938d2c | ||
|
|
0dc24b36e6 | ||
|
|
a77eaf214f | ||
|
|
5ab08fbfcb | ||
|
|
a73c2eb32c | ||
|
|
8f9e6d5bec | ||
|
|
790e26d608 | ||
|
|
79b96bcce8 | ||
|
|
592a7f893e | ||
|
|
7408f9c204 | ||
|
|
307de42434 | ||
|
|
d1ae7f67c4 | ||
|
|
b4616885b2 | ||
|
|
7fb7f3719f | ||
|
|
d1d1d1935d | ||
|
|
5b54f66821 | ||
|
|
e99a954387 | ||
|
|
93fd9ce3cc | ||
|
|
3a3e510b11 | ||
|
|
cbaf1edf89 | ||
|
|
94b21b5fc3 | ||
|
|
f60cc4c199 | ||
|
|
81b1f120ee | ||
|
|
48a0036023 | ||
|
|
9925a6a5a5 | ||
|
|
e5ca287dcb | ||
|
|
1a5962c430 | ||
|
|
ba5a664fca | ||
|
|
f7d05c642f | ||
|
|
877810219c | ||
|
|
fed8c9a261 | ||
|
|
896d36b16b | ||
|
|
a9c6d565f4 | ||
|
|
ae58a33456 | ||
|
|
d689d7a12a | ||
|
|
f28ada8994 | ||
|
|
4d9de1d326 | ||
|
|
405b28ee91 | ||
|
|
245aeac4a9 | ||
|
|
6b8f8db991 | ||
|
|
6c06c6682a | ||
|
|
f48da38ae1 | ||
|
|
29de1dfbb2 | ||
|
|
9063bd145f | ||
|
|
436a393cb1 | ||
|
|
a7f6e264f5 | ||
|
|
777c2a36bf | ||
|
|
c005448103 | ||
|
|
54f7be2c62 | ||
|
|
1f52b91bc5 | ||
|
|
d54bafd5d1 | ||
|
|
7728e84f34 | ||
|
|
a5808eac5e | ||
|
|
5fdb54a8d9 | ||
|
|
f02a4de88d | ||
|
|
49b2999d4f | ||
|
|
60c1caa11f | ||
|
|
3eb21895d8 | ||
|
|
2d9527e94d | ||
|
|
9f26e6f75d | ||
|
|
7890698f7d | ||
|
|
e738db0b24 | ||
|
|
67524ee869 | ||
|
|
0e5c0d1509 | ||
|
|
cdff9338d9 | ||
|
|
074926ee41 | ||
|
|
b0ef6e23a3 | ||
|
|
1ab6bc5af9 | ||
|
|
04dba20871 | ||
|
|
1cd5c927f6 | ||
|
|
6513222c3b | ||
|
|
498abd5251 | ||
|
|
fc46a81eb8 | ||
|
|
c3fb6e9f32 | ||
|
|
3aba42b965 | ||
|
|
5651a43b22 | ||
|
|
929792ef6a | ||
|
|
e13cdc4523 | ||
|
|
8dc0535dac | ||
|
|
dc4541739a | ||
|
|
a4a3da7e2e | ||
|
|
21432e1416 | ||
|
|
2f568f1319 | ||
|
|
50bdd592b4 | ||
|
|
50be2a666c | ||
|
|
3938637b84 | ||
|
|
3ed9e21271 | ||
|
|
63e1266e2b | ||
|
|
a552812711 | ||
|
|
ad07ab75fe | ||
|
|
c8deffebb0 | ||
|
|
67a3ea5109 | ||
|
|
6728cc0458 | ||
|
|
f84ab2474f | ||
|
|
5c8ce04eed | ||
|
|
3064aa0364 | ||
|
|
9625f1381c | ||
|
|
6ecd3ad166 | ||
|
|
8e54cac86e | ||
|
|
cc52bb76d1 | ||
|
|
4c037f54f4 | ||
|
|
b869628d4a | ||
|
|
0fbeb503ad | ||
|
|
a302e4dc6c | ||
|
|
00c8783137 | ||
|
|
11211f4a62 | ||
|
|
d29750d66e | ||
|
|
7dc590dab4 | ||
|
|
1d0ed64c1a | ||
|
|
0cf3884be4 | ||
|
|
165f5608e6 | ||
|
|
f2b8cfbffb | ||
|
|
6084e15f20 | ||
|
|
b1db4187de | ||
|
|
20ce8a8c74 | ||
|
|
39200249d1 | ||
|
|
27533125e4 | ||
|
|
99dd421329 | ||
|
|
4184894f27 | ||
|
|
a7a00dc0fa | ||
|
|
9340f69789 | ||
|
|
ba33bc0c23 | ||
|
|
b8cac60c6e | ||
|
|
3a5d5253d0 | ||
|
|
64010b0348 | ||
|
|
84ca2ff311 | ||
|
|
c0becebadc | ||
|
|
6ef99fd890 | ||
|
|
a55f2f7842 | ||
|
|
0aae7877c7 | ||
|
|
8d6cc37f7a | ||
|
|
1a0f7eb1e7 | ||
|
|
6ed65ed3ef | ||
|
|
2ac342e26a | ||
|
|
fe80d6b1ff | ||
|
|
a68254be6d | ||
|
|
49a9e355fe | ||
|
|
7091e35393 | ||
|
|
34cc8a43ab | ||
|
|
75333ef36c | ||
|
|
d4b3f1b60b | ||
|
|
318d20a5a5 | ||
|
|
44b9c33e5c | ||
|
|
317a17cbab | ||
|
|
6d2cb53760 | ||
|
|
7ddc4be319 | ||
|
|
604776551b | ||
|
|
26b085030d | ||
|
|
e1046d2424 | ||
|
|
cf2a7b9dfa | ||
|
|
55f4c0b65d | ||
|
|
5100fbda52 | ||
|
|
9e36188975 | ||
|
|
26c2d41dfa | ||
|
|
a511a433b1 | ||
|
|
cc581e91b5 | ||
|
|
3fd95fe8aa | ||
|
|
6f2455c265 | ||
|
|
de5b32a609 | ||
|
|
155b0d90f1 | ||
|
|
3da5e12a0d | ||
|
|
8accdc6bd4 | ||
|
|
22a10702ac | ||
|
|
a013c86fae | ||
|
|
361626d21f | ||
|
|
6615ac63d7 | ||
|
|
655611b28d | ||
|
|
9bd45ecd14 | ||
|
|
53c9babb83 | ||
|
|
ccc0b0142b | ||
|
|
4a2f7f68cb | ||
|
|
de994e10de | ||
|
|
2ff22ca079 | ||
|
|
e6b8f60977 | ||
|
|
b823b5924a | ||
|
|
a58ddd94d5 | ||
|
|
275e15ce96 | ||
|
|
1e2a30823c | ||
|
|
a326e7084e | ||
|
|
2c1d20f680 | ||
|
|
95bd2db0dd | ||
|
|
3ca875254c | ||
|
|
7f1f78dad6 | ||
|
|
a0d6468aee | ||
|
|
914c9bc58e | ||
|
|
b38031e9f7 | ||
|
|
487ca71f84 | ||
|
|
67cd8e7db6 | ||
|
|
f44017d710 | ||
|
|
78240b906b | ||
|
|
2ef0b3be27 | ||
|
|
0792d8367a | ||
|
|
920397cead | ||
|
|
42c18e94ab | ||
|
|
533f7eb238 | ||
|
|
e2f16e85f1 | ||
|
|
c98e6b8471 | ||
|
|
c16c13fd89 | ||
|
|
c8ce0e8819 | ||
|
|
9e98f6acdb | ||
|
|
a7a5b15dde | ||
|
|
3ebb5217a2 | ||
|
|
f570ffe1e3 | ||
|
|
ae94bf6d7c | ||
|
|
d9a6e465bb | ||
|
|
020b171b77 | ||
|
|
afc4932c28 | ||
|
|
324851c57e | ||
|
|
380ecfa096 | ||
|
|
5f9f73ceaa | ||
|
|
038ca4a920 | ||
|
|
d15629da0f | ||
|
|
363bbf9dea | ||
|
|
6f0a0c8e38 | ||
|
|
a75a62c708 | ||
|
|
db76d716b9 | ||
|
|
b0abbf64b4 | ||
|
|
3a432cf8e6 | ||
|
|
5c7d18e3ed | ||
|
|
2590850ffa | ||
|
|
0eeb80e16e | ||
|
|
e1cfe24a24 | ||
|
|
4e4f3a889d | ||
|
|
ede7ae103d | ||
|
|
075c0fb6bd | ||
|
|
5ebdb1ef7d | ||
|
|
387dbf332e | ||
|
|
9681e1dc88 | ||
|
|
fb29014480 | ||
|
|
1a5c2efc59 | ||
|
|
3e31e44ed5 | ||
|
|
9e69d69429 | ||
|
|
a108deac0f | ||
|
|
c767f14bf1 | ||
|
|
d69485c436 | ||
|
|
67a5bdb7b8 | ||
|
|
6504731025 | ||
|
|
773692081c | ||
|
|
51c6234966 | ||
|
|
fac6e9a1fe | ||
|
|
86e5d85d55 | ||
|
|
1c592435e9 | ||
|
|
02fed5bd6e | ||
|
|
dd724fcc6e | ||
|
|
6ba26bcb82 | ||
|
|
799ab3220d | ||
|
|
f73f2fb732 | ||
|
|
43d6ead92c | ||
|
|
4c053b3f31 | ||
|
|
c026e411cf | ||
|
|
65a9e32db1 | ||
|
|
0b15e97e08 | ||
|
|
bd74b96596 | ||
|
|
990d3ea750 | ||
|
|
30665a1907 | ||
|
|
04db4289fa | ||
|
|
01f4c030a7 | ||
|
|
d060ed8b64 | ||
|
|
413240733e | ||
|
|
b0799093dd | ||
|
|
0ecdc775db | ||
|
|
82065c20b1 | ||
|
|
3dcd1ee604 | ||
|
|
f63c69e6a6 | ||
|
|
6ba793e871 | ||
|
|
7afd243992 | ||
|
|
4f58155719 | ||
|
|
553adc4aef | ||
|
|
f668a626d7 | ||
|
|
e3bd534295 | ||
|
|
6aa5c3b314 | ||
|
|
3bc4f7267d | ||
|
|
a7021cf045 | ||
|
|
2709e1d976 | ||
|
|
8ec9d2a930 | ||
|
|
224c225789 | ||
|
|
85dae7a307 | ||
|
|
332a479c22 |
17
.github/instructions/instructions.md
vendored
Normal file
17
.github/instructions/instructions.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
applyTo: '**'
|
||||
---
|
||||
|
||||
# Building and Compiling
|
||||
|
||||
If you would like to compile or build any project. Please cd into the directory and run the following command:
|
||||
|
||||
```
|
||||
npm run compile
|
||||
```
|
||||
|
||||
Ths will make sure there are no type / syntax errors.
|
||||
|
||||
# Typescript Types.
|
||||
|
||||
Please do not use "any" types. Please create proper types where required.
|
||||
181
.github/workflows/build.yml
vendored
181
.github/workflows/build.yml
vendored
@@ -19,7 +19,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
@@ -27,7 +31,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./Accounts/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./Accounts/Dockerfile .
|
||||
|
||||
docker-build-isolated-vm:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -38,7 +42,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
@@ -46,7 +54,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./IsolatedVM/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./IsolatedVM/Dockerfile .
|
||||
|
||||
docker-build-home:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -57,7 +65,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
@@ -65,7 +77,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./Home/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./Home/Dockerfile .
|
||||
|
||||
docker-build-worker:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -76,7 +88,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
@@ -84,7 +100,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./Worker/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./Worker/Dockerfile .
|
||||
|
||||
docker-build-workflow:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -95,7 +111,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
@@ -103,7 +123,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./Workflow/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./Workflow/Dockerfile .
|
||||
|
||||
docker-build-api-reference:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -114,7 +134,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
@@ -122,7 +146,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./APIReference/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./APIReference/Dockerfile .
|
||||
|
||||
docker-build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -133,7 +157,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
@@ -141,7 +169,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./Docs/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./Docs/Dockerfile .
|
||||
|
||||
|
||||
docker-build-otel-collector:
|
||||
@@ -153,7 +181,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
@@ -161,7 +193,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./OTelCollector/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./OTelCollector/Dockerfile .
|
||||
|
||||
docker-build-app:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -172,7 +204,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
|
||||
# build image for accounts service
|
||||
@@ -181,7 +217,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./App/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./App/Dockerfile .
|
||||
|
||||
|
||||
docker-build-copilot:
|
||||
@@ -193,7 +229,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
@@ -201,7 +241,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./Copilot/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./Copilot/Dockerfile .
|
||||
|
||||
docker-build-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -212,7 +252,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
|
||||
# build image for accounts service
|
||||
@@ -221,7 +265,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./E2E/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./E2E/Dockerfile .
|
||||
|
||||
docker-build-admin-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -232,7 +276,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for home
|
||||
- name: build docker image
|
||||
@@ -240,7 +288,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./AdminDashboard/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./AdminDashboard/Dockerfile .
|
||||
|
||||
docker-build-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -251,7 +299,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for home
|
||||
- name: build docker image
|
||||
@@ -259,7 +311,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./Dashboard/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./Dashboard/Dockerfile .
|
||||
|
||||
docker-build-probe:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -270,7 +322,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
@@ -278,7 +334,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./Probe/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./Probe/Dockerfile .
|
||||
|
||||
docker-build-probe-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -289,7 +345,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
@@ -297,7 +357,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./ProbeIngest/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./ProbeIngest/Dockerfile .
|
||||
|
||||
docker-build-server-monitor-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -308,7 +368,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
@@ -316,9 +380,9 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./ServerMonitorIngest/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./ServerMonitorIngest/Dockerfile .
|
||||
|
||||
docker-build-open-telemetry-ingest:
|
||||
docker-build-telemetry:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
@@ -327,7 +391,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
@@ -335,7 +403,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./OpenTelemetryIngest/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./Telemetry/Dockerfile .
|
||||
|
||||
docker-build-incoming-request-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -346,7 +414,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
@@ -354,26 +426,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./IncomingRequestIngest/Dockerfile .
|
||||
|
||||
docker-build-fluent-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./FluentIngest/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./IncomingRequestIngest/Dockerfile .
|
||||
|
||||
docker-build-status-page:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -384,7 +437,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for home
|
||||
- name: build docker image
|
||||
@@ -392,7 +449,7 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./StatusPage/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./StatusPage/Dockerfile .
|
||||
|
||||
docker-build-test-server:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -403,7 +460,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for mail service
|
||||
- name: build docker image
|
||||
@@ -411,4 +472,4 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build -f ./TestServer/Dockerfile .
|
||||
command: sudo docker build --no-cache -f ./TestServer/Dockerfile .
|
||||
|
||||
26
.github/workflows/compile.yml
vendored
26
.github/workflows/compile.yml
vendored
@@ -319,7 +319,7 @@ jobs:
|
||||
max_attempts: 3
|
||||
command: cd ServerMonitorIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-open-telemetry-ingest:
|
||||
compile-telemetry:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
@@ -329,12 +329,12 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Open Telemetry Ingest
|
||||
- name: Compile Telemetry
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd OpenTelemetryIngest && npm install && npm run compile && npm run dep-check
|
||||
command: cd Telemetry && npm install && npm run compile && npm run dep-check
|
||||
|
||||
|
||||
compile-incoming-request-ingest:
|
||||
@@ -354,24 +354,6 @@ jobs:
|
||||
max_attempts: 3
|
||||
command: cd IncomingRequestIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-fluent-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Fluent Ingest
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd FluentIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
|
||||
compile-status-page:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -422,4 +404,4 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd MCP && npm install && npm run compile && npm run dep-check
|
||||
command: cd MCP && npm update @oneuptime/common && npm install && npm run compile && npm run dep-check
|
||||
49
.github/workflows/npm-audit-fix.yml
vendored
Normal file
49
.github/workflows/npm-audit-fix.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: NPM Audit Fix
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
npm-audit-fix:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Run npm audit fix across packages
|
||||
run: npm run audit-fix
|
||||
|
||||
- name: Detect changes
|
||||
id: changes
|
||||
run: |
|
||||
if git status --porcelain | grep .; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: "chore: npm audit fix"
|
||||
title: "chore: npm audit fix"
|
||||
body: |
|
||||
Automated npm audit fix run.
|
||||
Workflow: ${{ github.workflow }}
|
||||
Run ID: ${{ github.run_id }}
|
||||
branch: chore/npm-audit-fix
|
||||
delete-branch: true
|
||||
1623
.github/workflows/release.yml
vendored
1623
.github/workflows/release.yml
vendored
File diff suppressed because it is too large
Load Diff
1398
.github/workflows/test-release.yaml
vendored
1398
.github/workflows/test-release.yaml
vendored
File diff suppressed because it is too large
Load Diff
20
.github/workflows/test.fluent-ingest.yaml
vendored
20
.github/workflows/test.fluent-ingest.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Fluent Ingest Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'hotfix-*' # excludes hotfix branches
|
||||
- 'release'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd FluentIngest && npm install && npm run test
|
||||
@@ -1,4 +1,4 @@
|
||||
name: OpenTelemetryIngest Test
|
||||
name: Telemetry Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -17,5 +17,6 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd OpenTelemetryIngest && npm install && npm run test
|
||||
- run: cd Common && npm install
|
||||
- run: cd Telemetry && npm install && npm run test
|
||||
|
||||
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@@ -205,8 +205,8 @@
|
||||
},
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/OpenTelemetryIngest",
|
||||
"name": "OpenTelemetryIngest: Debug with Docker",
|
||||
"localRoot": "${workspaceFolder}/Telemetry",
|
||||
"name": "Telemetry: Debug with Docker",
|
||||
"port": 9938,
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"request": "attach",
|
||||
@@ -217,20 +217,6 @@
|
||||
"restart": true,
|
||||
"autoAttachChildProcesses": true
|
||||
},
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/FluentIngest",
|
||||
"name": "Fluent Ingest: Debug with Docker",
|
||||
"port": 9937,
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"restart": true,
|
||||
"autoAttachChildProcesses": true
|
||||
},
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/IsolatedVM",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
@@ -14,9 +14,12 @@ RUN npm config set fetch-retry-maxtimeout 600000
|
||||
|
||||
ARG GIT_SHA
|
||||
ARG APP_VERSION
|
||||
ARG IS_ENTERPRISE_EDITION=false
|
||||
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV IS_ENTERPRISE_EDITION=${IS_ENTERPRISE_EDITION}
|
||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Retrieve resources documentation
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
@@ -16,7 +17,7 @@ export default class ServiceHandler {
|
||||
|
||||
// Extract page parameter from request
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
|
||||
// Set default page title and description for the authentication page
|
||||
pageTitle = "Authentication";
|
||||
|
||||
@@ -4,6 +4,7 @@ import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
@@ -12,9 +13,9 @@ export default class ServiceHandler {
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
const pageData: any = {};
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
|
||||
pageData.selectCode = await LocalCache.getOrSetString(
|
||||
pageData["selectCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"select",
|
||||
async () => {
|
||||
@@ -22,7 +23,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.sortCode = await LocalCache.getOrSetString(
|
||||
pageData["sortCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"sort",
|
||||
async () => {
|
||||
@@ -30,7 +31,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToCode = await LocalCache.getOrSetString(
|
||||
pageData["equalToCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to",
|
||||
async () => {
|
||||
@@ -38,7 +39,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToOrNullCode = await LocalCache.getOrSetString(
|
||||
pageData["equalToOrNullCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to-or-null",
|
||||
async () => {
|
||||
@@ -48,7 +49,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.greaterThanCode = await LocalCache.getOrSetString(
|
||||
pageData["greaterThanCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than",
|
||||
async () => {
|
||||
@@ -58,7 +59,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
pageData["greaterThanOrEqualCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than-or-equal",
|
||||
async () => {
|
||||
@@ -68,7 +69,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.lessThanCode = await LocalCache.getOrSetString(
|
||||
pageData["lessThanCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than",
|
||||
async () => {
|
||||
@@ -78,7 +79,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.lessThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
pageData["lessThanOrEqualCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
@@ -88,7 +89,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.includesCode = await LocalCache.getOrSetString(
|
||||
pageData["includesCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"includes",
|
||||
async () => {
|
||||
@@ -98,7 +99,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.lessThanOrNullCode = await LocalCache.getOrSetString(
|
||||
pageData["lessThanOrNullCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
@@ -108,7 +109,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.greaterThanOrNullCode = await LocalCache.getOrSetString(
|
||||
pageData["greaterThanOrNullCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
@@ -118,7 +119,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.isNullCode = await LocalCache.getOrSetString(
|
||||
pageData["isNullCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"is-null",
|
||||
async () => {
|
||||
@@ -126,7 +127,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.notNullCode = await LocalCache.getOrSetString(
|
||||
pageData["notNullCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-null",
|
||||
async () => {
|
||||
@@ -134,7 +135,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.notEqualToCode = await LocalCache.getOrSetString(
|
||||
pageData["notEqualToCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-equals",
|
||||
async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Fetch a list of resources used in the application
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
@@ -17,7 +18,7 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the 'page' parameter from the request
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
|
||||
// Set the default page title and description
|
||||
pageTitle = "Errors";
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Get all resources and featured resources from ResourceUtil
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
@@ -20,10 +21,10 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the requested page from the URL parameters
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
|
||||
// Set featured resources for the page
|
||||
pageData.featuredResources = FeaturedResources;
|
||||
pageData["featuredResources"] = FeaturedResources;
|
||||
|
||||
// Set page title and description
|
||||
pageTitle = "Introduction";
|
||||
|
||||
@@ -3,7 +3,10 @@ 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 {
|
||||
getTableColumns,
|
||||
TableColumnMetadata,
|
||||
} from "Common/Types/Database/TableColumn";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Permission, {
|
||||
@@ -33,7 +36,7 @@ export default class ServiceHandler {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
let page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
|
||||
// Check if page is provided
|
||||
if (!page) {
|
||||
@@ -56,7 +59,9 @@ export default class ServiceHandler {
|
||||
page = "model";
|
||||
|
||||
// Get table columns for current resource
|
||||
const tableColumns: any = getTableColumns(currentResource.model);
|
||||
const tableColumns: Dictionary<TableColumnMetadata> = getTableColumns(
|
||||
currentResource.model,
|
||||
);
|
||||
|
||||
// Filter out columns with no access
|
||||
for (const key in tableColumns) {
|
||||
@@ -77,12 +82,14 @@ export default class ServiceHandler {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tableColumns[key].hideColumnInDocumentation) {
|
||||
if (tableColumns[key] && tableColumns[key]!.hideColumnInDocumentation) {
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
tableColumns[key].permissions = accessControl;
|
||||
if (tableColumns[key]) {
|
||||
(tableColumns[key] as any).permissions = accessControl;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unnecessary columns
|
||||
@@ -92,11 +99,11 @@ export default class ServiceHandler {
|
||||
delete tableColumns["version"];
|
||||
|
||||
// Set page data
|
||||
pageData.title = currentResource.model.singularName;
|
||||
pageData.description = currentResource.model.tableDescription;
|
||||
pageData.columns = tableColumns;
|
||||
pageData["title"] = currentResource.model.singularName;
|
||||
pageData["description"] = currentResource.model.tableDescription;
|
||||
pageData["columns"] = tableColumns;
|
||||
|
||||
pageData.tablePermissions = {
|
||||
pageData["tablePermissions"] = {
|
||||
read: currentResource.model.readRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
@@ -120,7 +127,7 @@ export default class ServiceHandler {
|
||||
};
|
||||
|
||||
// Cache the list request data
|
||||
pageData.listRequest = await LocalCache.getOrSetString(
|
||||
pageData["listRequest"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-request",
|
||||
async () => {
|
||||
@@ -130,7 +137,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the item request data
|
||||
pageData.itemRequest = await LocalCache.getOrSetString(
|
||||
pageData["itemRequest"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-request",
|
||||
async () => {
|
||||
@@ -140,7 +147,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the item response data
|
||||
pageData.itemResponse = await LocalCache.getOrSetString(
|
||||
pageData["itemResponse"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-response",
|
||||
async () => {
|
||||
@@ -152,7 +159,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the count request data
|
||||
pageData.countRequest = await LocalCache.getOrSetString(
|
||||
pageData["countRequest"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-request",
|
||||
async () => {
|
||||
@@ -164,7 +171,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the count response data
|
||||
pageData.countResponse = await LocalCache.getOrSetString(
|
||||
pageData["countResponse"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-response",
|
||||
async () => {
|
||||
@@ -175,7 +182,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.updateRequest = await LocalCache.getOrSetString(
|
||||
pageData["updateRequest"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-request",
|
||||
async () => {
|
||||
@@ -186,7 +193,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.updateResponse = await LocalCache.getOrSetString(
|
||||
pageData["updateResponse"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-response",
|
||||
async () => {
|
||||
@@ -197,7 +204,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.createRequest = await LocalCache.getOrSetString(
|
||||
pageData["createRequest"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-request",
|
||||
async () => {
|
||||
@@ -208,7 +215,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.createResponse = await LocalCache.getOrSetString(
|
||||
pageData["createResponse"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-response",
|
||||
async () => {
|
||||
@@ -219,7 +226,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.deleteRequest = await LocalCache.getOrSetString(
|
||||
pageData["deleteRequest"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-request",
|
||||
async () => {
|
||||
@@ -230,7 +237,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.deleteResponse = await LocalCache.getOrSetString(
|
||||
pageData["deleteResponse"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-response",
|
||||
async () => {
|
||||
@@ -242,7 +249,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Get list response from cache or set it if it's not available
|
||||
pageData.listResponse = await LocalCache.getOrSetString(
|
||||
pageData["listResponse"] = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-response",
|
||||
async () => {
|
||||
@@ -254,14 +261,15 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Generate a unique ID for the example object
|
||||
pageData.exampleObjectID = ObjectID.generate();
|
||||
pageData["exampleObjectID"] = ObjectID.generate();
|
||||
|
||||
// Construct the API path for the current resource
|
||||
pageData.apiPath =
|
||||
pageData["apiPath"] =
|
||||
AppApiRoute.toString() + currentResource.model.crudApiPath?.toString();
|
||||
|
||||
// Check if the current resource is a master admin API
|
||||
pageData.isMasterAdminApiDocs = currentResource.model.isMasterAdminApiDocs;
|
||||
pageData["isMasterAdminApiDocs"] =
|
||||
currentResource.model.isMasterAdminApiDocs;
|
||||
|
||||
// Render the index page with the required data
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Fetch a list of resources used in the application
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
@@ -22,7 +23,7 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the 'page' parameter from the request
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {
|
||||
const pageData: Dictionary<unknown> = {
|
||||
hostUrl: new URL(HttpProtocol, Host).toString(),
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources(); // Get all resources from ResourceUtil
|
||||
|
||||
@@ -15,14 +16,14 @@ export default class ServiceHandler {
|
||||
let pageTitle: string = ""; // Initialize page title
|
||||
let pageDescription: string = ""; // Initialize page description
|
||||
const page: string | undefined = req.params["page"]; // Get the page parameter from the request
|
||||
const pageData: any = {}; // Initialize page data object
|
||||
const pageData: Dictionary<unknown> = {}; // Initialize page data object
|
||||
|
||||
// Set page title and description
|
||||
pageTitle = "Pagination";
|
||||
pageDescription = "Learn how to paginate requests with OneUptime API";
|
||||
|
||||
// Get response and request code from LocalCache or LocalFile
|
||||
pageData.responseCode = await LocalCache.getOrSetString(
|
||||
pageData["responseCode"] = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"response",
|
||||
async () => {
|
||||
@@ -33,7 +34,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData.requestCode = await LocalCache.getOrSetString(
|
||||
pageData["requestCode"] = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"request",
|
||||
async () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { PermissionHelper, PermissionProps } from "Common/Types/Permission";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
@@ -17,14 +18,14 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the requested page
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
|
||||
// Set page title and description
|
||||
pageTitle = "Permissions";
|
||||
pageDescription = "Learn how permissions work with OneUptime";
|
||||
|
||||
// Filter permissions to only include those assignable to tenants
|
||||
pageData.permissions = PermissionHelper.getAllPermissionProps().filter(
|
||||
pageData["permissions"] = PermissionHelper.getAllPermissionProps().filter(
|
||||
(i: PermissionProps) => {
|
||||
return i.isAssignableToTenant;
|
||||
},
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"watch": ["./","../Common/Server", "../Common/Types", "../Common/Utils", "../Common/Models"],
|
||||
"ext": "ts,json,tsx,env,js,jsx,hbs",
|
||||
"ext": "ts,tsx",
|
||||
"ignore": [
|
||||
"./node_modules/**",
|
||||
"./public/**",
|
||||
"./bin/**",
|
||||
"./build/**",
|
||||
"greenlock.d/*"
|
||||
],
|
||||
"watchOptions": {"useFsEvents": false, "interval": 500},
|
||||
"env": {"TS_NODE_TRANSPILE_ONLY": "1", "TS_NODE_FILES": "false"},
|
||||
"exec": "node --inspect=0.0.0.0:9229 -r ts-node/register/transpile-only Index.ts"
|
||||
"exec": "node -r ts-node/register/transpile-only Index.ts"
|
||||
}
|
||||
328
APIReference/package-lock.json
generated
328
APIReference/package-lock.json
generated
@@ -31,7 +31,7 @@
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.52.1",
|
||||
"@opentelemetry/api-logs": "^0.206.0",
|
||||
"@opentelemetry/context-zone": "^1.25.1",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.52.1",
|
||||
@@ -49,7 +49,9 @@
|
||||
"@opentelemetry/sdk-trace-web": "^1.25.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.26.0",
|
||||
"@remixicon/react": "^4.2.0",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
@@ -58,14 +60,17 @@
|
||||
"@types/web-push": "^3.6.4",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"axios": "^1.7.2",
|
||||
"bullmq": "^5.3.3",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^1.12.0",
|
||||
"botbuilder": "^4.23.3",
|
||||
"bullmq": "^5.61.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
@@ -79,11 +84,11 @@
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^6.9.10",
|
||||
"nodemailer": "^7.0.7",
|
||||
"otpauth": "^9.3.1",
|
||||
"pg": "^8.7.3",
|
||||
"playwright": "^1.50.0",
|
||||
"posthog-js": "^1.139.6",
|
||||
"playwright": "^1.56.0",
|
||||
"posthog-js": "^1.275.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
@@ -98,7 +103,7 @@
|
||||
"react-router-dom": "^6.24.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-syntax-highlighter": "^16.0.0",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"recharts": "^2.12.7",
|
||||
@@ -113,13 +118,13 @@
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm": "^0.3.26",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"universal-cookie": "^7.2.1",
|
||||
"use-async-effect": "^2.2.6",
|
||||
"uuid": "^8.3.2",
|
||||
"web-push": "^3.6.7",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
@@ -242,89 +247,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
|
||||
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.23.4",
|
||||
"chalk": "^2.4.2"
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
|
||||
@@ -500,19 +436,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
||||
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -527,109 +465,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz",
|
||||
"integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
|
||||
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.6",
|
||||
"@babel/types": "^7.23.6"
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
|
||||
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
|
||||
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
|
||||
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.5"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -800,14 +657,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/parser": "^7.27.2",
|
||||
"@babel/types": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -835,14 +693,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
|
||||
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
|
||||
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.23.4",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1759,21 +1617,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -2003,10 +1863,11 @@
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@@ -2075,9 +1936,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
@@ -2224,9 +2086,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -2243,10 +2106,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -2541,6 +2405,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@@ -3238,7 +3103,8 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
@@ -3397,12 +3263,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.2",
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3683,10 +3550,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@@ -4106,20 +3974,12 @@
|
||||
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<%- include('../partials/code', {title: "Example Pagination Request", requestUrl: "/api/monitors/get-list?skip=0&limit=3", requestType: "GET", code: pageData.requestCode }) -%>
|
||||
<%- include('../partials/code', {title: "Example Pagination Request", requestUrl: "/api/monitors/get-list?skip=0&limit=3", requestType: "POST", code: pageData.requestCode }) -%>
|
||||
<%- include('../partials/code', {title: "Example Pagination Response" , requestUrl: "", requestType: "", code: pageData.responseCode }) -%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
@@ -14,9 +14,12 @@ RUN npm config set fetch-retry-maxtimeout 600000
|
||||
|
||||
ARG GIT_SHA
|
||||
ARG APP_VERSION
|
||||
ARG IS_ENTERPRISE_EDITION=false
|
||||
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV IS_ENTERPRISE_EDITION=${IS_ENTERPRISE_EDITION}
|
||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"watch": ["./","../Common/UI", "../Common/Types", "../Common/Utils", "../Common/Models"],
|
||||
"ext": "ts,json,tsx,env,js,jsx,hbs",
|
||||
"ext": "ts,tsx",
|
||||
"ignore": [
|
||||
"./node_modules/**",
|
||||
"./public/**",
|
||||
"./bin/**",
|
||||
"./public/**",
|
||||
"./public/dist/**",
|
||||
"./build/*",
|
||||
|
||||
99
Accounts/package-lock.json
generated
99
Accounts/package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"use-async-effect": "^2.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,7 +35,7 @@
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.52.1",
|
||||
"@opentelemetry/api-logs": "^0.206.0",
|
||||
"@opentelemetry/context-zone": "^1.25.1",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.52.1",
|
||||
@@ -53,7 +53,9 @@
|
||||
"@opentelemetry/sdk-trace-web": "^1.25.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.26.0",
|
||||
"@remixicon/react": "^4.2.0",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
@@ -62,14 +64,17 @@
|
||||
"@types/web-push": "^3.6.4",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"axios": "^1.7.2",
|
||||
"bullmq": "^5.3.3",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^1.12.0",
|
||||
"botbuilder": "^4.23.3",
|
||||
"bullmq": "^5.61.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
@@ -83,11 +88,11 @@
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^6.9.10",
|
||||
"nodemailer": "^7.0.7",
|
||||
"otpauth": "^9.3.1",
|
||||
"pg": "^8.7.3",
|
||||
"playwright": "^1.50.0",
|
||||
"posthog-js": "^1.139.6",
|
||||
"playwright": "^1.56.0",
|
||||
"posthog-js": "^1.275.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
@@ -102,7 +107,7 @@
|
||||
"react-router-dom": "^6.24.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-syntax-highlighter": "^16.0.0",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"recharts": "^2.12.7",
|
||||
@@ -117,13 +122,13 @@
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm": "^0.3.26",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"universal-cookie": "^7.2.1",
|
||||
"use-async-effect": "^2.2.6",
|
||||
"uuid": "^8.3.2",
|
||||
"web-push": "^3.6.7",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
@@ -343,9 +348,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
||||
"integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==",
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -521,21 +526,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -682,9 +689,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@@ -703,10 +710,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -793,6 +801,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@@ -957,12 +966,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.23.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
|
||||
"integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==",
|
||||
"version": "6.30.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
||||
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.16.1"
|
||||
"@remix-run/router": "1.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -972,13 +981,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"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==",
|
||||
"version": "6.30.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
|
||||
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.16.1",
|
||||
"react-router": "6.23.1"
|
||||
"@remix-run/router": "1.23.0",
|
||||
"react-router": "6.30.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -1008,6 +1017,16 @@
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
@@ -1020,15 +1039,6 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier/node_modules/semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -1046,6 +1056,7 @@
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"use-async-effect": "^2.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
LOGIN_API_URL,
|
||||
VERIFY_TWO_FACTOR_AUTH_API_URL,
|
||||
VERIFY_TOTP_AUTH_API_URL,
|
||||
GENERATE_WEBAUTHN_AUTH_OPTIONS_API_URL,
|
||||
VERIFY_WEBAUTHN_AUTH_API_URL,
|
||||
} from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
@@ -10,19 +12,23 @@ import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchem
|
||||
import Link from "Common/UI/Components/Link/Link";
|
||||
import { DASHBOARD_URL } from "Common/UI/Config";
|
||||
import OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import EditionLabel from "Common/UI/Components/EditionLabel/EditionLabel";
|
||||
import UiAnalytics from "Common/UI/Utils/Analytics";
|
||||
import LoginUtil from "Common/UI/Utils/Login";
|
||||
import UserTwoFactorAuth from "Common/Models/DatabaseModels/UserTwoFactorAuth";
|
||||
import UserTotpAuth from "Common/Models/DatabaseModels/UserTotpAuth";
|
||||
import UserWebAuthn from "Common/Models/DatabaseModels/UserWebAuthn";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import UserUtil from "Common/UI/Utils/User";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import React from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
import StaticModelList from "Common/UI/Components/ModelList/StaticModelList";
|
||||
import BasicForm from "Common/UI/Components/Forms/BasicForm";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import Base64 from "Common/Utils/Base64";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import ComponentLoader from "Common/UI/Components/ComponentLoader/ComponentLoader";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = LOGIN_API_URL;
|
||||
@@ -36,14 +42,32 @@ const LoginPage: () => JSX.Element = () => {
|
||||
const [showTwoFactorAuth, setShowTwoFactorAuth] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const [twoFactorAuthList, setTwoFactorAuthList] = React.useState<
|
||||
UserTwoFactorAuth[]
|
||||
>([]);
|
||||
const [totpAuthList, setTotpAuthList] = React.useState<UserTotpAuth[]>([]);
|
||||
|
||||
const [selectedTwoFactorAuth, setSelectedTwoFactorAuth] = React.useState<
|
||||
UserTwoFactorAuth | undefined
|
||||
const [webAuthnList, setWebAuthnList] = React.useState<UserWebAuthn[]>([]);
|
||||
|
||||
const [selectedTotpAuth, setSelectedTotpAuth] = React.useState<
|
||||
UserTotpAuth | undefined
|
||||
>(undefined);
|
||||
|
||||
const [selectedWebAuthn, setSelectedWebAuthn] = React.useState<
|
||||
UserWebAuthn | undefined
|
||||
>(undefined);
|
||||
|
||||
type TwoFactorMethod = {
|
||||
type: "totp" | "webauthn";
|
||||
item: UserTotpAuth | UserWebAuthn;
|
||||
};
|
||||
|
||||
const twoFactorMethods: TwoFactorMethod[] = [
|
||||
...totpAuthList.map((item: UserTotpAuth) => {
|
||||
return { type: "totp" as const, item };
|
||||
}),
|
||||
...webAuthnList.map((item: UserWebAuthn) => {
|
||||
return { type: "webauthn" as const, item };
|
||||
}),
|
||||
];
|
||||
|
||||
const [isTwoFactorAuthLoading, setIsTwoFactorAuthLoading] =
|
||||
React.useState<boolean>(false);
|
||||
const [twofactorAuthError, setTwoFactorAuthError] =
|
||||
@@ -57,6 +81,96 @@ const LoginPage: () => JSX.Element = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (selectedWebAuthn) {
|
||||
setIsTwoFactorAuthLoading(true);
|
||||
try {
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: GENERATE_WEBAUTHN_AUTH_OPTIONS_API_URL,
|
||||
data: {
|
||||
data: {
|
||||
email: initialValues["email"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result instanceof HTTPErrorResponse) {
|
||||
throw result;
|
||||
}
|
||||
|
||||
const data: any = result.data as any;
|
||||
|
||||
// Convert base64url strings back to Uint8Array
|
||||
data.options.challenge = Base64.base64UrlToUint8Array(
|
||||
data.options.challenge,
|
||||
);
|
||||
if (data.options.allowCredentials) {
|
||||
data.options.allowCredentials.forEach((cred: any) => {
|
||||
cred.id = Base64.base64UrlToUint8Array(cred.id);
|
||||
});
|
||||
}
|
||||
|
||||
// Use WebAuthn API
|
||||
const credential: PublicKeyCredential =
|
||||
(await navigator.credentials.get({
|
||||
publicKey: data.options,
|
||||
})) as PublicKeyCredential;
|
||||
|
||||
const assertionResponse: AuthenticatorAssertionResponse =
|
||||
credential.response as AuthenticatorAssertionResponse;
|
||||
|
||||
// Verify
|
||||
const verifyResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: VERIFY_WEBAUTHN_AUTH_API_URL,
|
||||
data: {
|
||||
data: {
|
||||
...initialValues,
|
||||
challenge: data.challenge,
|
||||
credential: {
|
||||
id: credential.id,
|
||||
rawId: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(credential.rawId),
|
||||
),
|
||||
response: {
|
||||
authenticatorData: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.authenticatorData),
|
||||
),
|
||||
clientDataJSON: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.clientDataJSON),
|
||||
),
|
||||
signature: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.signature),
|
||||
),
|
||||
userHandle: assertionResponse.userHandle
|
||||
? Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.userHandle),
|
||||
)
|
||||
: null,
|
||||
},
|
||||
type: credential.type,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (verifyResult instanceof HTTPErrorResponse) {
|
||||
throw verifyResult;
|
||||
}
|
||||
|
||||
const user: User = User.fromJSON(
|
||||
verifyResult.data as JSONObject,
|
||||
User,
|
||||
) as User;
|
||||
const miscData: JSONObject = {};
|
||||
|
||||
login(user as User, miscData);
|
||||
} catch (error) {
|
||||
setTwoFactorAuthError(API.getFriendlyErrorMessage(error as Error));
|
||||
}
|
||||
setIsTwoFactorAuthLoading(false);
|
||||
}
|
||||
}, [selectedWebAuthn]);
|
||||
|
||||
type LoginFunction = (user: User, miscData: JSONObject) => void;
|
||||
|
||||
const login: LoginFunction = (user: User, miscData: JSONObject): void => {
|
||||
@@ -79,6 +193,9 @@ const LoginPage: () => JSX.Element = () => {
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<div className="mt-4 flex justify-center">
|
||||
<EditionLabel />
|
||||
</div>
|
||||
{!showTwoFactorAuth && (
|
||||
<>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
@@ -156,16 +273,23 @@ const LoginPage: () => JSX.Element = () => {
|
||||
) => {
|
||||
if (
|
||||
miscData &&
|
||||
(miscData as JSONObject)["twoFactorAuth"] === true
|
||||
((((miscData as JSONObject)["totpAuthList"] as JSONArray)
|
||||
?.length || 0) > 0 ||
|
||||
(((miscData as JSONObject)["webAuthnList"] as JSONArray)
|
||||
?.length || 0) > 0)
|
||||
) {
|
||||
const twoFactorAuthList: Array<UserTwoFactorAuth> =
|
||||
UserTwoFactorAuth.fromJSONArray(
|
||||
(miscData as JSONObject)[
|
||||
"twoFactorAuthList"
|
||||
] as JSONArray,
|
||||
UserTwoFactorAuth,
|
||||
const totpAuthList: Array<UserTotpAuth> =
|
||||
UserTotpAuth.fromJSONArray(
|
||||
(miscData as JSONObject)["totpAuthList"] as JSONArray,
|
||||
UserTotpAuth,
|
||||
);
|
||||
setTwoFactorAuthList(twoFactorAuthList);
|
||||
const webAuthnList: Array<UserWebAuthn> =
|
||||
UserWebAuthn.fromJSONArray(
|
||||
(miscData as JSONObject)["webAuthnList"] as JSONArray,
|
||||
UserWebAuthn,
|
||||
);
|
||||
setTotpAuthList(totpAuthList);
|
||||
setWebAuthnList(webAuthnList);
|
||||
setShowTwoFactorAuth(true);
|
||||
return;
|
||||
}
|
||||
@@ -187,19 +311,53 @@ const LoginPage: () => JSX.Element = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{showTwoFactorAuth && !selectedTwoFactorAuth && (
|
||||
<StaticModelList<UserTwoFactorAuth>
|
||||
titleField="name"
|
||||
descriptionField=""
|
||||
selectedItems={[]}
|
||||
list={twoFactorAuthList}
|
||||
onClick={(item: UserTwoFactorAuth) => {
|
||||
setSelectedTwoFactorAuth(item);
|
||||
}}
|
||||
/>
|
||||
{showTwoFactorAuth && !selectedTotpAuth && !selectedWebAuthn && (
|
||||
<div className="space-y-4">
|
||||
{twoFactorMethods.map(
|
||||
(method: TwoFactorMethod, index: number) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="cursor-pointer p-4 border border-gray-300 rounded-lg hover:bg-gray-50"
|
||||
onClick={() => {
|
||||
if (method.type === "totp") {
|
||||
setSelectedTotpAuth(method.item as UserTotpAuth);
|
||||
} else {
|
||||
setSelectedWebAuthn(method.item as UserWebAuthn);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="font-medium">
|
||||
{(method.item as any).name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{method.type === "totp"
|
||||
? "Authenticator App"
|
||||
: "Security Key"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showTwoFactorAuth && selectedTwoFactorAuth && (
|
||||
{showTwoFactorAuth && selectedWebAuthn && (
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-medium mb-4">
|
||||
Authenticating with Security Key
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mb-4">
|
||||
Please follow the instructions on your security key device.
|
||||
</div>
|
||||
{isTwoFactorAuthLoading && <ComponentLoader />}
|
||||
{twofactorAuthError && (
|
||||
<ErrorMessage message={twofactorAuthError} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showTwoFactorAuth && selectedTotpAuth && (
|
||||
<BasicForm
|
||||
id="two-factor-auth-form"
|
||||
name="Two Factor Auth"
|
||||
@@ -225,14 +383,17 @@ const LoginPage: () => JSX.Element = () => {
|
||||
try {
|
||||
const code: string = data["code"] as string;
|
||||
const twoFactorAuthId: string =
|
||||
selectedTwoFactorAuth.id?.toString() as string;
|
||||
selectedTotpAuth!.id?.toString() as string;
|
||||
|
||||
const result: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.post(VERIFY_TWO_FACTOR_AUTH_API_URL, {
|
||||
await API.post({
|
||||
url: VERIFY_TOTP_AUTH_API_URL,
|
||||
data: {
|
||||
...initialValues,
|
||||
code: code,
|
||||
twoFactorAuthId: twoFactorAuthId,
|
||||
data: {
|
||||
...initialValues,
|
||||
code: code,
|
||||
twoFactorAuthId: twoFactorAuthId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -261,7 +422,7 @@ const LoginPage: () => JSX.Element = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-10 text-center">
|
||||
{!selectedTwoFactorAuth && (
|
||||
{!selectedTotpAuth && !selectedWebAuthn && (
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
@@ -272,11 +433,12 @@ const LoginPage: () => JSX.Element = () => {
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{selectedTwoFactorAuth ? (
|
||||
{selectedTotpAuth || selectedWebAuthn ? (
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
<Link
|
||||
onClick={() => {
|
||||
setSelectedTwoFactorAuth(undefined);
|
||||
setSelectedTotpAuth(undefined);
|
||||
setSelectedWebAuthn(undefined);
|
||||
}}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
|
||||
@@ -42,12 +42,12 @@ const LoginPage: () => JSX.Element = () => {
|
||||
try {
|
||||
// get sso config by email.
|
||||
const listResult: HTTPErrorResponse | HTTPResponse<JSONArray> =
|
||||
await API.get(
|
||||
URL.fromString(apiUrl.toString()).addQueryParam(
|
||||
await API.get({
|
||||
url: URL.fromString(apiUrl.toString()).addQueryParam(
|
||||
"email",
|
||||
email.toString(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
if (listResult instanceof HTTPErrorResponse) {
|
||||
throw listResult;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { IDENTITY_URL } from "Common/UI/Config";
|
||||
import { IDENTITY_URL, APP_API_URL } from "Common/UI/Config";
|
||||
|
||||
export const SIGNUP_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route("/signup"),
|
||||
@@ -9,9 +9,17 @@ export const LOGIN_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route("/login"),
|
||||
);
|
||||
|
||||
export const VERIFY_TWO_FACTOR_AUTH_API_URL: URL = URL.fromURL(
|
||||
export const VERIFY_TOTP_AUTH_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route("/verify-totp-auth"),
|
||||
);
|
||||
|
||||
export const GENERATE_WEBAUTHN_AUTH_OPTIONS_API_URL: URL = URL.fromURL(
|
||||
APP_API_URL,
|
||||
).addRoute(new Route("/user-webauthn/generate-authentication-options"));
|
||||
|
||||
export const VERIFY_WEBAUTHN_AUTH_API_URL: URL = URL.fromURL(
|
||||
IDENTITY_URL,
|
||||
).addRoute(new Route("/verify-two-factor-auth"));
|
||||
).addRoute(new Route("/verify-webauthn-auth"));
|
||||
|
||||
export const SERVICE_PROVIDER_LOGIN_URL: URL = URL.fromURL(
|
||||
IDENTITY_URL,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
@@ -14,9 +14,12 @@ RUN npm config set fetch-retry-maxtimeout 600000
|
||||
|
||||
ARG GIT_SHA
|
||||
ARG APP_VERSION
|
||||
ARG IS_ENTERPRISE_EDITION=false
|
||||
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV IS_ENTERPRISE_EDITION=${IS_ENTERPRISE_EDITION}
|
||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
|
||||
@@ -1,13 +1,74 @@
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
|
||||
import Express, {
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import App from "Common/Server/Utils/StartServer";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import UserMiddleware from "Common/Server/Middleware/UserAuthorization";
|
||||
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
|
||||
import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import "ejs";
|
||||
import JSONWebTokenData from "Common/Types/JsonWebTokenData";
|
||||
|
||||
export const APP_NAME: string = "admin";
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
type EnsureMasterAdminAccessFunction = (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
) => Promise<JSONObject>;
|
||||
|
||||
const ensureMasterAdminAccess: EnsureMasterAdminAccessFunction = async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<JSONObject> => {
|
||||
try {
|
||||
const accessToken: string | undefined =
|
||||
UserMiddleware.getAccessTokenFromExpressRequest(req);
|
||||
|
||||
if (!accessToken) {
|
||||
Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthorizedException(
|
||||
"Unauthorized: Only master admins can access the admin dashboard.",
|
||||
),
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
const authData: JSONWebTokenData = JSONWebToken.decode(accessToken);
|
||||
|
||||
if (!authData.isMasterAdmin) {
|
||||
Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthorizedException(
|
||||
"Unauthorized: Only master admins can access the admin dashboard.",
|
||||
),
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthorizedException(
|
||||
"Unauthorized: Only master admins can access the admin dashboard.",
|
||||
),
|
||||
);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// init the app
|
||||
@@ -19,6 +80,7 @@ const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
getVariablesToRenderIndexPage: ensureMasterAdminAccess,
|
||||
});
|
||||
|
||||
// add default routes
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"watch": ["./","../Common/UI", "../Common/Types", "../Common/Utils", "../Common/Models"],
|
||||
"ext": "ts,json,tsx,env,js,jsx,hbs",
|
||||
"ext": "ts,tsx",
|
||||
"ignore": [
|
||||
"./node_modules/**",
|
||||
"./public/**",
|
||||
"./bin/**",
|
||||
"./public/**",
|
||||
"./public/dist/**",
|
||||
"./build/*",
|
||||
|
||||
100
AdminDashboard/package-lock.json
generated
100
AdminDashboard/package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
"react-router-dom": "^6.30.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.35",
|
||||
@@ -34,7 +34,7 @@
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.52.1",
|
||||
"@opentelemetry/api-logs": "^0.206.0",
|
||||
"@opentelemetry/context-zone": "^1.25.1",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.52.1",
|
||||
@@ -52,7 +52,9 @@
|
||||
"@opentelemetry/sdk-trace-web": "^1.25.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.26.0",
|
||||
"@remixicon/react": "^4.2.0",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
@@ -61,14 +63,17 @@
|
||||
"@types/web-push": "^3.6.4",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"axios": "^1.7.2",
|
||||
"bullmq": "^5.3.3",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^1.12.0",
|
||||
"botbuilder": "^4.23.3",
|
||||
"bullmq": "^5.61.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
@@ -82,11 +87,11 @@
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^6.9.10",
|
||||
"nodemailer": "^7.0.7",
|
||||
"otpauth": "^9.3.1",
|
||||
"pg": "^8.7.3",
|
||||
"playwright": "^1.50.0",
|
||||
"posthog-js": "^1.139.6",
|
||||
"playwright": "^1.56.0",
|
||||
"posthog-js": "^1.275.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
@@ -101,7 +106,7 @@
|
||||
"react-router-dom": "^6.24.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-syntax-highlighter": "^16.0.0",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"recharts": "^2.12.7",
|
||||
@@ -116,13 +121,13 @@
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm": "^0.3.26",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"universal-cookie": "^7.2.1",
|
||||
"use-async-effect": "^2.2.6",
|
||||
"uuid": "^8.3.2",
|
||||
"web-push": "^3.6.7",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
@@ -342,9 +347,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
||||
"integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==",
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -490,21 +495,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -600,9 +607,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -619,10 +627,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -709,6 +718,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@@ -936,12 +946,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.23.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
|
||||
"integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==",
|
||||
"version": "6.30.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
||||
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.16.1"
|
||||
"@remix-run/router": "1.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -951,13 +961,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"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==",
|
||||
"version": "6.30.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
|
||||
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.16.1",
|
||||
"react-router": "6.23.1"
|
||||
"@remix-run/router": "1.23.0",
|
||||
"react-router": "6.30.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -987,6 +997,16 @@
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
@@ -999,15 +1019,6 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier/node_modules/semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -1025,6 +1036,7 @@
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
"react-router-dom": "^6.30.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev-build": "NODE_ENV=development node esbuild.config.js",
|
||||
|
||||
@@ -5,6 +5,7 @@ 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";
|
||||
import SettingsWhatsApp from "./Pages/Settings/WhatsApp/Index";
|
||||
// Settings Pages.
|
||||
import SettingsEmail from "./Pages/Settings/Email/Index";
|
||||
import SettingsProbes from "./Pages/Settings/Probes/Index";
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
} from "react-router-dom";
|
||||
import UserView from "./Pages/Users/View/Index";
|
||||
import UserDelete from "./Pages/Users/View/Delete";
|
||||
import UserSettings from "./Pages/Users/View/Settings";
|
||||
import ProjectView from "./Pages/Projects/View/Index";
|
||||
import ProjectDelete from "./Pages/Projects/View/Delete";
|
||||
|
||||
@@ -70,6 +72,11 @@ const App: () => JSX.Element = () => {
|
||||
element={<UserView />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.USER_SETTINGS]?.toString() || ""}
|
||||
element={<UserSettings />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.USER_DELETE]?.toString() || ""}
|
||||
element={<UserDelete />}
|
||||
@@ -105,6 +112,11 @@ const App: () => JSX.Element = () => {
|
||||
element={<SettingsCallSMS />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_WHATSAPP]?.toString() || ""}
|
||||
element={<SettingsWhatsApp />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_PROBES]?.toString() || ""}
|
||||
element={<SettingsProbes />}
|
||||
|
||||
@@ -54,9 +54,9 @@ const DashboardFooter: () => JSX.Element = () => {
|
||||
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 response: HTTPResponse<JSONObject> = await API.get<JSONObject>({
|
||||
url: URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`),
|
||||
});
|
||||
|
||||
if (response.data) {
|
||||
return response.data as JSONObject;
|
||||
|
||||
@@ -3,6 +3,7 @@ import Logo from "./Logo";
|
||||
import UserProfile from "./UserProfile";
|
||||
import Button, { ButtonStyleType } from "Common/UI/Components/Button/Button";
|
||||
import Header from "Common/UI/Components/Header/Header";
|
||||
import EditionLabel from "Common/UI/Components/EditionLabel/EditionLabel";
|
||||
import { DASHBOARD_URL } from "Common/UI/Config";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
@@ -27,6 +28,7 @@ const DashboardHeader: FunctionComponent = (): ReactElement => {
|
||||
}
|
||||
rightComponents={
|
||||
<>
|
||||
<EditionLabel className="mr-3 hidden md:inline-flex" />
|
||||
<Button
|
||||
title="Exit Admin"
|
||||
buttonStyle={ButtonStyleType.NORMAL}
|
||||
|
||||
@@ -50,6 +50,15 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "WhatsApp",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_WHATSAPP] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.WhatsApp}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Monitoring">
|
||||
|
||||
491
AdminDashboard/src/Pages/Settings/WhatsApp/Index.tsx
Normal file
491
AdminDashboard/src/Pages/Settings/WhatsApp/Index.tsx
Normal file
@@ -0,0 +1,491 @@
|
||||
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 "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "Common/UI/Components/Page/Page";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import GlobalConfig from "Common/Models/DatabaseModels/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
import Card from "Common/UI/Components/Card/Card";
|
||||
import MarkdownViewer from "Common/UI/Components/Markdown.tsx/MarkdownViewer";
|
||||
import BasicForm from "Common/UI/Components/Forms/BasicForm";
|
||||
import Alert, { AlertType } from "Common/UI/Components/Alerts/Alert";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import { APP_API_URL } from "Common/UI/Config";
|
||||
import WhatsAppTemplateMessages, {
|
||||
WhatsAppTemplateId,
|
||||
WhatsAppTemplateIds,
|
||||
WhatsAppTemplateLanguage,
|
||||
} from "Common/Types/WhatsApp/WhatsAppTemplates";
|
||||
|
||||
type ToFriendlyName = (value: string) => string;
|
||||
|
||||
const toFriendlyName: ToFriendlyName = (value: string): string => {
|
||||
return value
|
||||
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
||||
.replace(/_/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
};
|
||||
|
||||
type ExtractTemplateVariables = (template: string) => Array<string>;
|
||||
|
||||
const extractTemplateVariables: ExtractTemplateVariables = (
|
||||
template: string,
|
||||
): Array<string> => {
|
||||
const matches: RegExpMatchArray | null = template.match(/\{\{(.*?)\}\}/g);
|
||||
|
||||
if (!matches) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const uniqueVariables: Set<string> = new Set<string>();
|
||||
|
||||
for (const match of matches) {
|
||||
const variable: string = match.replace("{{", "").replace("}}", "").trim();
|
||||
|
||||
if (variable) {
|
||||
uniqueVariables.add(variable);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(uniqueVariables).sort((a: string, b: string) => {
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
};
|
||||
|
||||
type BuildWhatsAppSetupMarkdown = () => string;
|
||||
|
||||
const buildWhatsAppSetupMarkdown: BuildWhatsAppSetupMarkdown = (): string => {
|
||||
const templateKeys: Array<keyof typeof WhatsAppTemplateIds> = Object.keys(
|
||||
WhatsAppTemplateIds,
|
||||
) as Array<keyof typeof WhatsAppTemplateIds>;
|
||||
|
||||
const appApiBaseUrl: string = APP_API_URL.toString().replace(/\/$/, "");
|
||||
const primaryWebhookUrl: string = `${appApiBaseUrl}/notification/whatsapp/webhook`;
|
||||
|
||||
const description: string =
|
||||
"Follow these steps to connect Meta WhatsApp with OneUptime so notifications can be delivered via WhatsApp.";
|
||||
|
||||
const prerequisitesList: Array<string> = [
|
||||
"Meta Business Manager admin access for the WhatsApp Business Account.",
|
||||
"A WhatsApp Business phone number approved for API messaging.",
|
||||
"Admin access to OneUptime with permission to edit global notification settings.",
|
||||
"A webhook verify token string that you'll configure identically in Meta and OneUptime.",
|
||||
];
|
||||
|
||||
const setupStepsList: Array<string> = [
|
||||
"Sign in to the [Meta Business Manager](https://business.facebook.com/) with admin access to your WhatsApp Business Account.",
|
||||
"From **Business Settings → Accounts → WhatsApp Accounts**, create or select the account that owns your sender phone number.",
|
||||
"In Buisness Portfolio, create a system user and assign it to the WhatsApp Business Account with the role of **Admin**.",
|
||||
"Generate a token for this system user and this will be your long-lived access token. Make sure to select the **whatsapp_business_management** and **whatsapp_business_messaging** permissions when generating the token.",
|
||||
"Paste the access token, phone number ID, and webhook verify token into the **Meta WhatsApp Settings** card above, then save.",
|
||||
"For the **Business Account ID**, go to **Business Settings → Business Info** (or **Business Settings → WhatsApp Accounts → Settings**) and copy the **WhatsApp Business Account ID** value.",
|
||||
"To locate the **App ID** and **App Secret**, open [Meta for Developers](https://developers.facebook.com/apps/), select your WhatsApp app, then navigate to **Settings → Basic**. The App ID is shown at the top; click **Show** next to **App Secret** to reveal and copy it.",
|
||||
"Create each template listed below in the Meta WhatsApp Manager. Make sure the template name, language, and variables match exactly. You can however change the content to your preference. Please make sure it's approved by Meta.",
|
||||
"Send a test notification from OneUptime to confirm that WhatsApp delivery succeeds.",
|
||||
];
|
||||
|
||||
const prerequisitesMarkdown: string = prerequisitesList
|
||||
.map((item: string) => {
|
||||
return `- ${item}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const setupStepsMarkdown: string = setupStepsList
|
||||
.map((item: string, index: number) => {
|
||||
return `${index + 1}. ${item}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const tableRows: string = templateKeys
|
||||
.map((enumKey: keyof typeof WhatsAppTemplateIds) => {
|
||||
const templateId: WhatsAppTemplateId = WhatsAppTemplateIds[enumKey];
|
||||
const friendlyName: string = toFriendlyName(enumKey.toString());
|
||||
const templateMessage: string = WhatsAppTemplateMessages[templateId];
|
||||
const language: string = WhatsAppTemplateLanguage[templateId] || "en";
|
||||
const variables: Array<string> =
|
||||
extractTemplateVariables(templateMessage);
|
||||
const variableList: string =
|
||||
variables.length > 0
|
||||
? variables
|
||||
.map((variable: string) => {
|
||||
return `\`${variable}\``;
|
||||
})
|
||||
.join(", ")
|
||||
: "_None_";
|
||||
|
||||
return `| ${friendlyName} | \`${templateId}\` | ${language} | ${variableList} |`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const templateBodies: string = templateKeys
|
||||
.map((enumKey: keyof typeof WhatsAppTemplateIds) => {
|
||||
const templateId: WhatsAppTemplateId = WhatsAppTemplateIds[enumKey];
|
||||
const friendlyName: string = toFriendlyName(enumKey.toString());
|
||||
const templateMessage: string = WhatsAppTemplateMessages[templateId];
|
||||
const language: string = WhatsAppTemplateLanguage[templateId] || "en";
|
||||
const variables: Array<string> =
|
||||
extractTemplateVariables(templateMessage);
|
||||
const variableMarkdown: string =
|
||||
variables.length > 0
|
||||
? variables
|
||||
.map((variable: string) => {
|
||||
return `- \`${variable}\``;
|
||||
})
|
||||
.join("\n")
|
||||
: "_None_";
|
||||
const variablesHeading: string = variables.length
|
||||
? `**Variables (${variables.length})**`
|
||||
: "**Variables**";
|
||||
|
||||
return [
|
||||
`#### ${friendlyName}`,
|
||||
"",
|
||||
`**Template Name:** \`${templateId}\``,
|
||||
`**Language:** ${language}`,
|
||||
"",
|
||||
variablesHeading,
|
||||
variableMarkdown,
|
||||
"",
|
||||
"**Body**",
|
||||
"```plaintext",
|
||||
templateMessage,
|
||||
"```",
|
||||
"",
|
||||
"---",
|
||||
].join("\n");
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const templateSummaryTable: string = [
|
||||
"| Friendly Name | Template Name | Language | Variables |",
|
||||
"| --- | --- | --- | --- |",
|
||||
tableRows,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
|
||||
const webhookSection: string = [
|
||||
"### Configure Meta Webhook Subscription",
|
||||
"1. In the OneUptime Admin Dashboard, open **Settings → WhatsApp → Meta WhatsApp Settings** and enter a strong value in **Webhook Verify Token**. Save the form so the encrypted token is stored in Global Config.",
|
||||
"2. Keep that verify token handy—Meta does not generate one for you. You'll paste the exact same value when configuring the callback.",
|
||||
"3. In [Meta for Developers](https://developers.facebook.com/apps/), select your WhatsApp app and navigate to **WhatsApp → Configuration → Webhooks**.",
|
||||
`4. Click **Configure**, then supply one of the following callback URLs when Meta asks for your endpoint:\n - \`${primaryWebhookUrl}\`\n `,
|
||||
"5. Paste the verify token from step 1 into Meta's **Verify Token** field and submit. Meta will call the callback URL and expect that value to match before it approves the subscription.",
|
||||
"6. After verification succeeds, subscribe to the **messages** field (and any other WhatsApp webhook categories you need) so delivery status updates are forwarded to OneUptime.",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
|
||||
return [
|
||||
description,
|
||||
"### Prerequisites",
|
||||
prerequisitesMarkdown,
|
||||
"### Setup Steps",
|
||||
setupStepsMarkdown,
|
||||
webhookSection,
|
||||
"### Required WhatsApp Templates",
|
||||
templateSummaryTable,
|
||||
"### Template Bodies",
|
||||
"> Copy the exact template body below—including punctuation and spacing—when creating each template inside Meta. The variables list shows every placeholder that must be configured in WhatsApp Manager.",
|
||||
templateBodies,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
};
|
||||
|
||||
const whatsappSetupMarkdown: string = buildWhatsAppSetupMarkdown();
|
||||
|
||||
const SettingsWhatsApp: FunctionComponent = (): ReactElement => {
|
||||
const [isSendingTest, setIsSendingTest] = useState<boolean>(false);
|
||||
const [testError, setTestError] = useState<string>("");
|
||||
const [testSuccess, setTestSuccess] = useState<string>("");
|
||||
|
||||
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: "WhatsApp",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_WHATSAPP] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
<CardModelDetail
|
||||
name="Meta WhatsApp Settings"
|
||||
cardProps={{
|
||||
title: "Meta WhatsApp Settings",
|
||||
description:
|
||||
"Configure Meta WhatsApp credentials. These values are used to send WhatsApp notifications from OneUptime.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Meta WhatsApp Config"
|
||||
formSteps={[
|
||||
{
|
||||
title: "Credentials",
|
||||
id: "meta-credentials",
|
||||
},
|
||||
{
|
||||
title: "Meta App",
|
||||
id: "meta-app",
|
||||
},
|
||||
]}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAccessToken: true,
|
||||
},
|
||||
title: "Access Token",
|
||||
stepId: "meta-credentials",
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: true,
|
||||
description:
|
||||
"Long-lived access token generated in the Meta WhatsApp Business Platform.",
|
||||
placeholder: "EAAG...",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppPhoneNumberId: true,
|
||||
},
|
||||
title: "Phone Number ID",
|
||||
stepId: "meta-credentials",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
"The WhatsApp Business phone number ID associated with your sending number.",
|
||||
placeholder: "123456789012345",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppBusinessAccountId: true,
|
||||
},
|
||||
title: "Business Account ID",
|
||||
stepId: "meta-credentials",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
description:
|
||||
"Optional Business Account ID that owns the WhatsApp templates.",
|
||||
placeholder: "123456789012345",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppWebhookVerifyToken: true,
|
||||
},
|
||||
title: "Webhook Verify Token",
|
||||
stepId: "meta-credentials",
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: false,
|
||||
description:
|
||||
"Secret token configured in Meta to validate webhook subscription requests.",
|
||||
placeholder: "Webhook verify token",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAppId: true,
|
||||
},
|
||||
title: "App ID",
|
||||
stepId: "meta-app",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
description:
|
||||
"Optional Facebook App ID tied to your WhatsApp integration.",
|
||||
placeholder: "987654321098765",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAppSecret: true,
|
||||
},
|
||||
title: "App Secret",
|
||||
stepId: "meta-app",
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: false,
|
||||
description:
|
||||
"Optional Facebook App Secret used for webhook signature verification.",
|
||||
placeholder: "Facebook App Secret",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config-meta-whatsapp",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAccessToken: true,
|
||||
},
|
||||
title: "Access Token",
|
||||
fieldType: FieldType.HiddenText,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppPhoneNumberId: true,
|
||||
},
|
||||
title: "Phone Number ID",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppBusinessAccountId: true,
|
||||
},
|
||||
title: "Business Account ID",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppWebhookVerifyToken: true,
|
||||
},
|
||||
title: "Webhook Verify Token",
|
||||
fieldType: FieldType.HiddenText,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAppId: true,
|
||||
},
|
||||
title: "App ID",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAppSecret: true,
|
||||
},
|
||||
title: "App Secret",
|
||||
fieldType: FieldType.HiddenText,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Card
|
||||
title="Send Test WhatsApp Message"
|
||||
description="Send a test WhatsApp template message to confirm your Meta configuration."
|
||||
>
|
||||
{testSuccess ? (
|
||||
<Alert
|
||||
type={AlertType.SUCCESS}
|
||||
title={testSuccess}
|
||||
className="mb-4"
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<BasicForm
|
||||
id="send-test-whatsapp-form"
|
||||
name="Send Test WhatsApp Message"
|
||||
isLoading={isSendingTest}
|
||||
error={testError || ""}
|
||||
submitButtonText="Send Test Message"
|
||||
maxPrimaryButtonWidth={true}
|
||||
initialValues={{
|
||||
phoneNumber: "",
|
||||
}}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
phoneNumber: true,
|
||||
},
|
||||
title: "Recipient WhatsApp Number",
|
||||
description:
|
||||
"Enter the full international phone number (including country code) that should receive the test message.",
|
||||
placeholder: "+11234567890",
|
||||
required: true,
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]}
|
||||
onSubmit={async (
|
||||
values: JSONObject,
|
||||
onSubmitSuccessful?: () => void,
|
||||
) => {
|
||||
const toPhone: string = String(values["phoneNumber"] || "").trim();
|
||||
|
||||
if (!toPhone) {
|
||||
setTestSuccess("");
|
||||
setTestError(
|
||||
"Please enter a WhatsApp number before sending a test message.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSendingTest(true);
|
||||
setTestError("");
|
||||
setTestSuccess("");
|
||||
|
||||
try {
|
||||
const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
||||
await API.post({
|
||||
url: URL.fromString(APP_API_URL.toString()).addRoute(
|
||||
"/notification/whatsapp/test",
|
||||
),
|
||||
data: {
|
||||
toPhone,
|
||||
templateKey: WhatsAppTemplateIds.TestNotification,
|
||||
templateLanguageCode:
|
||||
WhatsAppTemplateLanguage[
|
||||
WhatsAppTemplateIds.TestNotification
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw response;
|
||||
}
|
||||
|
||||
if (response.isFailure()) {
|
||||
throw new Error("Failed to send test WhatsApp message.");
|
||||
}
|
||||
|
||||
setTestSuccess(
|
||||
"Test WhatsApp message sent successfully. Check the recipient device to confirm delivery.",
|
||||
);
|
||||
|
||||
if (onSubmitSuccessful) {
|
||||
onSubmitSuccessful();
|
||||
}
|
||||
} catch (err) {
|
||||
setTestError(API.getFriendlyMessage(err));
|
||||
} finally {
|
||||
setIsSendingTest(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="Meta WhatsApp Setup Guide"
|
||||
description="Steps to connect Meta WhatsApp and the templates you must provision."
|
||||
>
|
||||
<MarkdownViewer text={whatsappSetupMarkdown} />
|
||||
</Card>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsWhatsApp;
|
||||
94
AdminDashboard/src/Pages/Users/View/Settings.tsx
Normal file
94
AdminDashboard/src/Pages/Users/View/Settings.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import SideMenuComponent from "./SideMenu";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import ModelPage from "Common/UI/Components/Page/ModelPage";
|
||||
import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
|
||||
const UserSettings: FunctionComponent = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
|
||||
|
||||
return (
|
||||
<ModelPage<User>
|
||||
modelId={modelId}
|
||||
modelNameField="email"
|
||||
modelType={User}
|
||||
title={"User"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Users",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route),
|
||||
},
|
||||
{
|
||||
title: "User",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USER_VIEW] as Route,
|
||||
{
|
||||
modelId: modelId,
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USER_SETTINGS] as Route,
|
||||
{
|
||||
modelId: modelId,
|
||||
},
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<SideMenuComponent modelId={modelId} />}
|
||||
>
|
||||
<CardModelDetail<User>
|
||||
name="user-master-admin-settings"
|
||||
cardProps={{
|
||||
title: "Master Admin Access",
|
||||
description:
|
||||
"Grant or revoke master admin access for this user. Master admins can manage every project and workspace.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Update Access"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
isMasterAdmin: true,
|
||||
},
|
||||
title: "Master Admin",
|
||||
description:
|
||||
"Enable to give this user full access to the entire platform.",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: User,
|
||||
id: "user-master-admin-settings-detail",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
isMasterAdmin: true,
|
||||
},
|
||||
title: "Master Admin",
|
||||
fieldType: FieldType.Boolean,
|
||||
placeholder: "No",
|
||||
},
|
||||
],
|
||||
modelId: modelId,
|
||||
}}
|
||||
/>
|
||||
</ModelPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserSettings;
|
||||
@@ -30,6 +30,18 @@ const SideMenuComponent: FunctionComponent<SideMenuProps> = (
|
||||
}}
|
||||
icon={IconProp.Info}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USER_SETTINGS] as Route,
|
||||
{
|
||||
modelId: props.modelId,
|
||||
},
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Settings}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Advanced">
|
||||
|
||||
@@ -6,6 +6,7 @@ enum PageMap {
|
||||
|
||||
USERS = "USERS",
|
||||
USER_VIEW = "USER_VIEW",
|
||||
USER_SETTINGS = "USER_SETTINGS",
|
||||
USER_DELETE = "USER_DELETE",
|
||||
|
||||
PROJECTS = "PROJECTS",
|
||||
@@ -15,6 +16,7 @@ enum PageMap {
|
||||
SETTINGS_HOST = "SETTINGS_HOST",
|
||||
SETTINGS_SMTP = "SETTINGS_SMTP",
|
||||
SETTINGS_CALL_AND_SMS = "SETTINGS_CALL_AND_SMS",
|
||||
SETTINGS_WHATSAPP = "SETTINGS_WHATSAPP",
|
||||
SETTINGS_PROBES = "SETTINGS_PROBES",
|
||||
SETTINGS_AUTHENTICATION = "SETTINGS_AUTHENTICATION",
|
||||
SETTINGS_API_KEY = "SETTINGS_API_KEY",
|
||||
|
||||
@@ -18,6 +18,9 @@ const RouteMap: Dictionary<Route> = {
|
||||
|
||||
[PageMap.USERS]: new Route(`/admin/users`),
|
||||
[PageMap.USER_VIEW]: new Route(`/admin/users/${RouteParams.ModelID}`),
|
||||
[PageMap.USER_SETTINGS]: new Route(
|
||||
`/admin/users/${RouteParams.ModelID}/settings`,
|
||||
),
|
||||
[PageMap.USER_DELETE]: new Route(
|
||||
`/admin/users/${RouteParams.ModelID}/delete`,
|
||||
),
|
||||
@@ -25,6 +28,7 @@ const RouteMap: Dictionary<Route> = {
|
||||
[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_WHATSAPP]: new Route(`/admin/settings/whatsapp`),
|
||||
[PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`),
|
||||
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
|
||||
`/admin/settings/authentication`,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
@@ -14,9 +14,12 @@ RUN npm config set fetch-retry-maxtimeout 600000
|
||||
|
||||
ARG GIT_SHA
|
||||
ARG APP_VERSION
|
||||
ARG IS_ENTERPRISE_EDITION=false
|
||||
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV IS_ENTERPRISE_EDITION=${IS_ENTERPRISE_EDITION}
|
||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import BaseAPI from "Common/Server/API/BaseAPI";
|
||||
import BaseAnalyticsAPI from "Common/Server/API/BaseAnalyticsAPI";
|
||||
import BillingAPI from "Common/Server/API/BillingAPI";
|
||||
import BillingInvoiceAPI from "Common/Server/API/BillingInvoiceAPI";
|
||||
import BillingPaymentMethodAPI from "Common/Server/API/BillingPaymentMethodAPI";
|
||||
import CopilotCodeRepositoryAPI from "Common/Server/API/CopilotCodeRepositoryAPI";
|
||||
@@ -13,9 +14,11 @@ import TelemetryAPI from "Common/Server/API/TelemetryAPI";
|
||||
import ProbeAPI from "Common/Server/API/ProbeAPI";
|
||||
import ProjectAPI from "Common/Server/API/ProjectAPI";
|
||||
import ProjectSsoAPI from "Common/Server/API/ProjectSSO";
|
||||
import WhatsAppLogAPI from "./WhatsAppLogAPI";
|
||||
|
||||
// Import API
|
||||
import ResellerPlanAPI from "Common/Server/API/ResellerPlanAPI";
|
||||
import EnterpriseLicenseAPI from "Common/Server/API/EnterpriseLicenseAPI";
|
||||
import MonitorAPI from "Common/Server/API/MonitorAPI";
|
||||
import ShortLinkAPI from "Common/Server/API/ShortLinkAPI";
|
||||
import StatusPageAPI from "Common/Server/API/StatusPageAPI";
|
||||
@@ -23,12 +26,14 @@ import WorkspaceNotificationRuleAPI from "Common/Server/API/WorkspaceNotificatio
|
||||
import StatusPageDomainAPI from "Common/Server/API/StatusPageDomainAPI";
|
||||
import StatusPageSubscriberAPI from "Common/Server/API/StatusPageSubscriberAPI";
|
||||
import UserCallAPI from "Common/Server/API/UserCallAPI";
|
||||
import UserTwoFactorAuthAPI from "Common/Server/API/UserTwoFactorAuthAPI";
|
||||
import UserTotpAuthAPI from "Common/Server/API/UserTotpAuthAPI";
|
||||
import UserWebAuthnAPI from "Common/Server/API/UserWebAuthnAPI";
|
||||
import MonitorTest from "Common/Models/DatabaseModels/MonitorTest";
|
||||
// User Notification methods.
|
||||
import UserEmailAPI from "Common/Server/API/UserEmailAPI";
|
||||
import UserNotificationLogTimelineAPI from "Common/Server/API/UserOnCallLogTimelineAPI";
|
||||
import UserSMSAPI from "Common/Server/API/UserSmsAPI";
|
||||
import UserWhatsAppAPI from "Common/Server/API/UserWhatsAppAPI";
|
||||
import UserPushAPI from "Common/Server/API/UserPushAPI";
|
||||
import ApiKeyPermissionService, {
|
||||
Service as ApiKeyPermissionServiceType,
|
||||
@@ -94,6 +99,9 @@ import IncidentInternalNoteService, {
|
||||
import IncidentNoteTemplateService, {
|
||||
Service as IncidentNoteTemplateServiceType,
|
||||
} from "Common/Server/Services/IncidentNoteTemplateService";
|
||||
import IncidentPostmortemTemplateService, {
|
||||
Service as IncidentPostmortemTemplateServiceType,
|
||||
} from "Common/Server/Services/IncidentPostmortemTemplateService";
|
||||
import TableViewService, {
|
||||
Service as TableViewServiceType,
|
||||
} from "Common/Server/Services/TableViewService";
|
||||
@@ -137,9 +145,6 @@ import LogService, {
|
||||
LogService as LogServiceType,
|
||||
} from "Common/Server/Services/LogService";
|
||||
|
||||
import TelemetryAttributeService, {
|
||||
TelemetryAttributeService as TelemetryAttributeServiceType,
|
||||
} from "Common/Server/Services/TelemetryAttributeService";
|
||||
import CopilotActionTypePriorityService, {
|
||||
Service as CopilotActionTypePriorityServiceType,
|
||||
} from "Common/Server/Services/CopilotActionTypePriorityService";
|
||||
@@ -327,6 +332,9 @@ import TeamMemberService, {
|
||||
import TeamPermissionService, {
|
||||
Service as TeamPermissionServiceType,
|
||||
} from "Common/Server/Services/TeamPermissionService";
|
||||
import TeamComplianceSettingService, {
|
||||
TeamComplianceSettingService as TeamComplianceSettingServiceType,
|
||||
} from "Common/Server/Services/TeamComplianceSettingService";
|
||||
import TeamService, {
|
||||
Service as TeamServiceType,
|
||||
} from "Common/Server/Services/TeamService";
|
||||
@@ -373,6 +381,7 @@ import TelemetryExceptionService, {
|
||||
import ExceptionInstanceService, {
|
||||
ExceptionInstanceService as ExceptionInstanceServiceType,
|
||||
} from "Common/Server/Services/ExceptionInstanceService";
|
||||
import AcmeChallengeAPI from "Common/Server/API/AcmeChallengeAPI";
|
||||
|
||||
import FeatureSet from "Common/Server/Types/FeatureSet";
|
||||
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
|
||||
@@ -403,6 +412,7 @@ import Incident from "Common/Models/DatabaseModels/Incident";
|
||||
import IncidentCustomField from "Common/Models/DatabaseModels/IncidentCustomField";
|
||||
import IncidentInternalNote from "Common/Models/DatabaseModels/IncidentInternalNote";
|
||||
import IncidentNoteTemplate from "Common/Models/DatabaseModels/IncidentNoteTemplate";
|
||||
import IncidentPostmortemTemplate from "Common/Models/DatabaseModels/IncidentPostmortemTemplate";
|
||||
import IncidentOwnerTeam from "Common/Models/DatabaseModels/IncidentOwnerTeam";
|
||||
import IncidentOwnerUser from "Common/Models/DatabaseModels/IncidentOwnerUser";
|
||||
import IncidentPublicNote from "Common/Models/DatabaseModels/IncidentPublicNote";
|
||||
@@ -469,6 +479,7 @@ import StatusPageSSO from "Common/Models/DatabaseModels/StatusPageSso";
|
||||
import Team from "Common/Models/DatabaseModels/Team";
|
||||
import TeamMember from "Common/Models/DatabaseModels/TeamMember";
|
||||
import TeamPermission from "Common/Models/DatabaseModels/TeamPermission";
|
||||
import TeamComplianceSetting from "Common/Models/DatabaseModels/TeamComplianceSetting";
|
||||
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
|
||||
import TelemetryUsageBilling from "Common/Models/DatabaseModels/TelemetryUsageBilling";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
@@ -481,7 +492,6 @@ import WorkflowVariable from "Common/Models/DatabaseModels/WorkflowVariable";
|
||||
import ProbeOwnerTeam from "Common/Models/DatabaseModels/ProbeOwnerTeam";
|
||||
import ProbeOwnerUser from "Common/Models/DatabaseModels/ProbeOwnerUser";
|
||||
import ServiceCatalogDependency from "Common/Models/DatabaseModels/ServiceCatalogDependency";
|
||||
import TelemetryAttribute from "Common/Models/AnalyticsModels/TelemetryAttribute";
|
||||
import ExceptionInstance from "Common/Models/AnalyticsModels/ExceptionInstance";
|
||||
import TelemetyException from "Common/Models/DatabaseModels/TelemetryException";
|
||||
import CopilotActionTypePriority from "Common/Models/DatabaseModels/CopilotActionTypePriority";
|
||||
@@ -521,6 +531,7 @@ import ScheduledMaintenanceFeedService, {
|
||||
} from "Common/Server/Services/ScheduledMaintenanceFeedService";
|
||||
|
||||
import SlackAPI from "Common/Server/API/SlackAPI";
|
||||
import MicrosoftTeamsAPI from "Common/Server/API/MicrosoftTeamsAPI";
|
||||
|
||||
import WorkspaceProjectAuthToken from "Common/Models/DatabaseModels/WorkspaceProjectAuthToken";
|
||||
import WorkspaceProjectAuthTokenService, {
|
||||
@@ -550,10 +561,7 @@ import MetricTypeService, {
|
||||
import MetricType from "Common/Models/DatabaseModels/MetricType";
|
||||
|
||||
import OnCallDutyPolicyAPI from "Common/Server/API/OnCallDutyPolicyAPI";
|
||||
|
||||
// OnCallDutyPolicyOwnerTeam
|
||||
// OnCallDutyPolicyOwnerUser
|
||||
// OnCallDutyPolicyFeed
|
||||
import TeamComplianceAPI from "Common/Server/API/TeamComplianceAPI";
|
||||
|
||||
import OnCallDutyPolicyFeed from "Common/Models/DatabaseModels/OnCallDutyPolicyFeed";
|
||||
import OnCallDutyPolicyFeedService, {
|
||||
@@ -609,10 +617,7 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAnalyticsAPI<TelemetryAttribute, TelemetryAttributeServiceType>(
|
||||
TelemetryAttribute,
|
||||
TelemetryAttributeService,
|
||||
).getRouter(),
|
||||
new AcmeChallengeAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, OpenAPI.getRouter());
|
||||
@@ -1167,6 +1172,14 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<TeamComplianceSetting, TeamComplianceSettingServiceType>(
|
||||
TeamComplianceSetting,
|
||||
TeamComplianceSettingService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<MonitorStatus, MonitorStatusServiceType>(
|
||||
@@ -1386,6 +1399,17 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncidentPostmortemTemplate,
|
||||
IncidentPostmortemTemplateServiceType
|
||||
>(
|
||||
IncidentPostmortemTemplate,
|
||||
IncidentPostmortemTemplateService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
@@ -1526,6 +1550,11 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new BaseAPI<SmsLog, SmsLogServiceType>(SmsLog, SmsLogService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new WhatsAppLogAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<PushNotificationLog, PushNotificationLogServiceType>(
|
||||
@@ -1581,7 +1610,6 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
MonitorTimelineStatusService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new ShortLinkAPI().getRouter());
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new MonitorAPI().getRouter());
|
||||
app.use(
|
||||
@@ -1595,6 +1623,12 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new OnCallDutyPolicyAPI().getRouter(),
|
||||
);
|
||||
|
||||
// TeamComplianceAPI
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new TeamComplianceAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new WorkspaceNotificationRuleAPI().getRouter(),
|
||||
@@ -1618,7 +1652,15 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new ResellerPlanAPI().getRouter(),
|
||||
);
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new EnterpriseLicenseAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new SlackAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new MicrosoftTeamsAPI().getRouter(),
|
||||
);
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new GlobalConfigAPI().getRouter(),
|
||||
@@ -1646,10 +1688,18 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserCallAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new UserTwoFactorAuthAPI().getRouter(),
|
||||
new UserTotpAuthAPI().getRouter(),
|
||||
);
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new UserWebAuthnAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserEmailAPI().getRouter());
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserSMSAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new UserWhatsAppAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserPushAPI().getRouter());
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new ProbeAPI().getRouter());
|
||||
|
||||
@@ -1670,6 +1720,8 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new BillingInvoiceAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new BillingAPI().getRouter());
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
|
||||
14
App/FeatureSet/BaseAPI/WhatsAppLogAPI.ts
Normal file
14
App/FeatureSet/BaseAPI/WhatsAppLogAPI.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import BaseAPI from "Common/Server/API/BaseAPI";
|
||||
import WhatsAppLog from "Common/Models/DatabaseModels/WhatsAppLog";
|
||||
import WhatsAppLogService, {
|
||||
Service as WhatsAppLogServiceType,
|
||||
} from "Common/Server/Services/WhatsAppLogService";
|
||||
|
||||
export default class WhatsAppLogAPI extends BaseAPI<
|
||||
WhatsAppLog,
|
||||
WhatsAppLogServiceType
|
||||
> {
|
||||
public constructor() {
|
||||
super(WhatsAppLog, WhatsAppLogService);
|
||||
}
|
||||
}
|
||||
@@ -24,23 +24,71 @@ import AccessTokenService from "Common/Server/Services/AccessTokenService";
|
||||
import EmailVerificationTokenService from "Common/Server/Services/EmailVerificationTokenService";
|
||||
import MailService from "Common/Server/Services/MailService";
|
||||
import UserService from "Common/Server/Services/UserService";
|
||||
import UserTwoFactorAuthService from "Common/Server/Services/UserTwoFactorAuthService";
|
||||
import UserTotpAuthService from "Common/Server/Services/UserTotpAuthService";
|
||||
import UserSessionService, {
|
||||
SessionMetadata,
|
||||
} from "Common/Server/Services/UserSessionService";
|
||||
import CookieUtil from "Common/Server/Utils/Cookie";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
extractDeviceInfo,
|
||||
getClientIp,
|
||||
headerValueToString,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import TwoFactorAuth from "Common/Server/Utils/TwoFactorAuth";
|
||||
import TotpAuth from "Common/Server/Utils/TotpAuth";
|
||||
import EmailVerificationToken from "Common/Models/DatabaseModels/EmailVerificationToken";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import UserTwoFactorAuth from "Common/Models/DatabaseModels/UserTwoFactorAuth";
|
||||
import UserSession from "Common/Models/DatabaseModels/UserSession";
|
||||
import UserTotpAuth from "Common/Models/DatabaseModels/UserTotpAuth";
|
||||
import UserWebAuthn from "Common/Models/DatabaseModels/UserWebAuthn";
|
||||
import UserWebAuthnService from "Common/Server/Services/UserWebAuthnService";
|
||||
import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;
|
||||
|
||||
type FinalizeUserLoginInput = {
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
user: User;
|
||||
isGlobalLogin: boolean;
|
||||
};
|
||||
|
||||
const finalizeUserLogin: (
|
||||
data: FinalizeUserLoginInput,
|
||||
) => Promise<SessionMetadata> = async (
|
||||
data: FinalizeUserLoginInput,
|
||||
): Promise<SessionMetadata> => {
|
||||
const { req, res, user, isGlobalLogin } = data;
|
||||
|
||||
const sessionMetadata: SessionMetadata =
|
||||
await UserSessionService.createSession({
|
||||
userId: user.id!,
|
||||
isGlobalLogin,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
});
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user,
|
||||
isGlobalLogin,
|
||||
sessionId: sessionMetadata.session.id!,
|
||||
refreshToken: sessionMetadata.refreshToken,
|
||||
refreshTokenExpiresAt: sessionMetadata.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return sessionMetadata;
|
||||
};
|
||||
|
||||
router.post(
|
||||
"/signup",
|
||||
async (
|
||||
@@ -183,9 +231,9 @@ router.post(
|
||||
if (savedUser) {
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(savedUser.id!);
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
await finalizeUserLogin({
|
||||
req,
|
||||
res,
|
||||
user: savedUser,
|
||||
isGlobalLogin: true,
|
||||
});
|
||||
@@ -485,6 +533,127 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/refresh-token",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req);
|
||||
|
||||
if (!refreshToken) {
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException(
|
||||
"Refresh token missing. Please login again.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const session: UserSession | null =
|
||||
await UserSessionService.findActiveSessionByRefreshToken(refreshToken);
|
||||
|
||||
if (!session || !session.id) {
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
session.refreshTokenExpiresAt &&
|
||||
OneUptimeDate.hasExpired(session.refreshTokenExpiresAt)
|
||||
) {
|
||||
await UserSessionService.revokeSessionById(session.id, {
|
||||
reason: "Refresh token expired",
|
||||
});
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (!session.userId) {
|
||||
await UserSessionService.revokeSessionById(session.id, {
|
||||
reason: "Session missing user",
|
||||
});
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
const user: User | null = await UserService.findOneById({
|
||||
id: session.userId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
isMasterAdmin: true,
|
||||
profilePictureId: true,
|
||||
timezone: true,
|
||||
enableTwoFactorAuth: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
await UserSessionService.revokeSessionById(session.id, {
|
||||
reason: "User not found",
|
||||
});
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Account no longer exists."),
|
||||
);
|
||||
}
|
||||
|
||||
const additionalInfo: JSONObject = (session.additionalInfo ||
|
||||
{}) as JSONObject;
|
||||
const isGlobalLogin: boolean =
|
||||
typeof additionalInfo["isGlobalLogin"] === "boolean"
|
||||
? (additionalInfo["isGlobalLogin"] as boolean)
|
||||
: true;
|
||||
|
||||
const renewedSession: SessionMetadata =
|
||||
await UserSessionService.renewSessionWithNewRefreshToken({
|
||||
session,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
});
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user,
|
||||
isGlobalLogin,
|
||||
sessionId: renewedSession.session.id!,
|
||||
refreshToken: renewedSession.refreshToken,
|
||||
refreshTokenExpiresAt: renewedSession.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/logout",
|
||||
async (
|
||||
@@ -493,6 +662,15 @@ router.post(
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req);
|
||||
|
||||
if (refreshToken) {
|
||||
await UserSessionService.revokeSessionByRefreshToken(refreshToken, {
|
||||
reason: "User logout",
|
||||
});
|
||||
}
|
||||
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
@@ -503,7 +681,7 @@ router.post(
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/verify-two-factor-auth",
|
||||
"/verify-totp-auth",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
@@ -513,7 +691,25 @@ router.post(
|
||||
req: req,
|
||||
res: res,
|
||||
next: next,
|
||||
verifyTwoFactorAuth: true,
|
||||
verifyTotpAuth: true,
|
||||
verifyWebAuthn: false,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/verify-webauthn-auth",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
return login({
|
||||
req: req,
|
||||
res: res,
|
||||
next: next,
|
||||
verifyTotpAuth: false,
|
||||
verifyWebAuthn: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -529,62 +725,99 @@ router.post(
|
||||
req: req,
|
||||
res: res,
|
||||
next: next,
|
||||
verifyTwoFactorAuth: false,
|
||||
verifyTotpAuth: false,
|
||||
verifyWebAuthn: false,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
type FetchTwoFactorAuthListFunction = (
|
||||
userId: ObjectID,
|
||||
) => Promise<Array<UserTwoFactorAuth>>;
|
||||
type FetchTotpAuthListFunction = (userId: ObjectID) => Promise<{
|
||||
totpAuthList: Array<UserTotpAuth>;
|
||||
webAuthnList: Array<UserWebAuthn>;
|
||||
}>;
|
||||
|
||||
const fetchTwoFactorAuthList: FetchTwoFactorAuthListFunction = async (
|
||||
const fetchTotpAuthList: FetchTotpAuthListFunction = async (
|
||||
userId: ObjectID,
|
||||
): Promise<Array<UserTwoFactorAuth>> => {
|
||||
const twoFactorAuthList: Array<UserTwoFactorAuth> =
|
||||
await UserTwoFactorAuthService.findBy({
|
||||
query: {
|
||||
userId: userId,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
userId: true,
|
||||
name: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
): Promise<{
|
||||
totpAuthList: Array<UserTotpAuth>;
|
||||
webAuthnList: Array<UserWebAuthn>;
|
||||
}> => {
|
||||
const totpAuthList: Array<UserTotpAuth> = await UserTotpAuthService.findBy({
|
||||
query: {
|
||||
userId: userId,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
userId: true,
|
||||
name: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return twoFactorAuthList;
|
||||
const webAuthnList: Array<UserWebAuthn> = await UserWebAuthnService.findBy({
|
||||
query: {
|
||||
userId: userId,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
userId: true,
|
||||
name: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
totpAuthList: totpAuthList || [],
|
||||
webAuthnList: webAuthnList || [],
|
||||
};
|
||||
};
|
||||
|
||||
type LoginFunction = (options: {
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
next: NextFunction;
|
||||
verifyTwoFactorAuth: boolean;
|
||||
verifyTotpAuth: boolean;
|
||||
verifyWebAuthn: boolean;
|
||||
}) => Promise<void>;
|
||||
|
||||
const login: LoginFunction = async (options: {
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
next: NextFunction;
|
||||
verifyTwoFactorAuth: boolean;
|
||||
verifyTotpAuth: boolean;
|
||||
verifyWebAuthn: boolean;
|
||||
}): Promise<void> => {
|
||||
const req: ExpressRequest = options.req;
|
||||
const res: ExpressResponse = options.res;
|
||||
const next: NextFunction = options.next;
|
||||
const verifyTwoFactorAuth: boolean = options.verifyTwoFactorAuth;
|
||||
const verifyTotpAuth: boolean = options.verifyTotpAuth;
|
||||
const verifyWebAuthn: boolean = options.verifyWebAuthn;
|
||||
|
||||
try {
|
||||
const data: JSONObject = req.body["data"];
|
||||
|
||||
logger.debug("Login request data: " + JSON.stringify(req.body, null, 2));
|
||||
|
||||
const user: User = BaseModel.fromJSON(data as JSONObject, User) as User;
|
||||
|
||||
if (!user.email || !user.password) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Email and password are required."),
|
||||
);
|
||||
}
|
||||
|
||||
await user.password?.hashValue(EncryptionSecret);
|
||||
|
||||
const alreadySavedUser: User | null = await UserService.findOneBy({
|
||||
@@ -638,13 +871,21 @@ const login: LoginFunction = async (options: {
|
||||
);
|
||||
}
|
||||
|
||||
if (alreadySavedUser.enableTwoFactorAuth && !verifyTwoFactorAuth) {
|
||||
if (
|
||||
alreadySavedUser.enableTwoFactorAuth &&
|
||||
!verifyTotpAuth &&
|
||||
!verifyWebAuthn
|
||||
) {
|
||||
// If two factor auth is enabled then we will send the user to the two factor auth page.
|
||||
|
||||
const twoFactorAuthList: Array<UserTwoFactorAuth> =
|
||||
await fetchTwoFactorAuthList(alreadySavedUser.id!);
|
||||
const { totpAuthList, webAuthnList } = await fetchTotpAuthList(
|
||||
alreadySavedUser.id!,
|
||||
);
|
||||
|
||||
if (!twoFactorAuthList || twoFactorAuthList.length === 0) {
|
||||
if (
|
||||
(!totpAuthList || totpAuthList.length === 0) &&
|
||||
(!webAuthnList || webAuthnList.length === 0)
|
||||
) {
|
||||
const errorMessage: string = IsBillingEnabled
|
||||
? "Two Factor Authentication is enabled but no two factor auth is setup. Please contact OneUptime support for help."
|
||||
: "Two Factor Authentication is enabled but no two factor auth is setup. Please contact your server admin to disable two factor auth for this account.";
|
||||
@@ -656,69 +897,76 @@ const login: LoginFunction = async (options: {
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEntityResponse(req, res, user, User, {
|
||||
return Response.sendEntityResponse(req, res, alreadySavedUser, User, {
|
||||
miscData: {
|
||||
twoFactorAuthList: UserTwoFactorAuth.toJSONArray(
|
||||
twoFactorAuthList,
|
||||
UserTwoFactorAuth,
|
||||
),
|
||||
twoFactorAuth: true,
|
||||
totpAuthList: UserTotpAuth.toJSONArray(totpAuthList, UserTotpAuth),
|
||||
webAuthnList: UserWebAuthn.toJSONArray(webAuthnList, UserWebAuthn),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (verifyTwoFactorAuth) {
|
||||
// code from req
|
||||
const code: string = data["code"] as string;
|
||||
const twoFactorAuthId: string = data["twoFactorAuthId"] as string;
|
||||
if (verifyTotpAuth || verifyWebAuthn) {
|
||||
if (verifyTotpAuth) {
|
||||
// code from req
|
||||
const code: string = data["code"] as string;
|
||||
const twoFactorAuthId: string = data["twoFactorAuthId"] as string;
|
||||
|
||||
const twoFactorAuth: UserTwoFactorAuth | null =
|
||||
await UserTwoFactorAuthService.findOneBy({
|
||||
query: {
|
||||
_id: twoFactorAuthId,
|
||||
userId: alreadySavedUser.id!,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twoFactorSecret: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
const totpAuth: UserTotpAuth | null =
|
||||
await UserTotpAuthService.findOneBy({
|
||||
query: {
|
||||
_id: twoFactorAuthId,
|
||||
userId: alreadySavedUser.id!,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twoFactorSecret: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!totpAuth) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Invalid two factor auth id."),
|
||||
);
|
||||
}
|
||||
|
||||
const isVerified: boolean = TotpAuth.verifyToken({
|
||||
token: code,
|
||||
secret: totpAuth.twoFactorSecret!,
|
||||
email: alreadySavedUser.email!,
|
||||
});
|
||||
|
||||
if (!twoFactorAuth) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Invalid two factor auth id."),
|
||||
);
|
||||
if (!isVerified) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Invalid code."),
|
||||
);
|
||||
}
|
||||
} else if (verifyWebAuthn) {
|
||||
const expectedChallenge: string = data["challenge"] as string;
|
||||
const credential: any = data["credential"];
|
||||
|
||||
await UserWebAuthnService.verifyAuthentication({
|
||||
userId: alreadySavedUser.id!.toString(),
|
||||
challenge: expectedChallenge,
|
||||
credential: credential,
|
||||
});
|
||||
}
|
||||
|
||||
const isVerified: boolean = TwoFactorAuth.verifyToken({
|
||||
token: code,
|
||||
secret: twoFactorAuth.twoFactorSecret!,
|
||||
email: alreadySavedUser.email!,
|
||||
});
|
||||
|
||||
if (!isVerified) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Invalid code."),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
} // Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(alreadySavedUser.id!);
|
||||
|
||||
if (alreadySavedUser.password.toString() === user.password!.toString()) {
|
||||
logger.info("User logged in: " + alreadySavedUser.email?.toString());
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
await finalizeUserLogin({
|
||||
req,
|
||||
res,
|
||||
user: alreadySavedUser,
|
||||
isGlobalLogin: true,
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,9 @@ import AccessTokenService from "Common/Server/Services/AccessTokenService";
|
||||
import ProjectSSOService from "Common/Server/Services/ProjectSsoService";
|
||||
import TeamMemberService from "Common/Server/Services/TeamMemberService";
|
||||
import UserService from "Common/Server/Services/UserService";
|
||||
import UserSessionService, {
|
||||
SessionMetadata,
|
||||
} from "Common/Server/Services/UserSessionService";
|
||||
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
|
||||
import Select from "Common/Server/Types/Database/Select";
|
||||
import CookieUtil from "Common/Server/Utils/Cookie";
|
||||
@@ -28,6 +31,9 @@ import Express, {
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
extractDeviceInfo,
|
||||
getClientIp,
|
||||
headerValueToString,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
@@ -40,12 +46,20 @@ import Name from "Common/Types/Name";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
// This route is used to get the SSO config for the user.
|
||||
// when the user logs in from OneUptime and not from the IDP.
|
||||
const ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;
|
||||
|
||||
/*
|
||||
* This route is used to get the SSO config for the user.
|
||||
* when the user logs in from OneUptime and not from the IDP.
|
||||
*/
|
||||
|
||||
router.get(
|
||||
"/service-provider-login",
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!req.query["email"]) {
|
||||
return Response.sendErrorResponse(
|
||||
@@ -150,7 +164,11 @@ router.get(
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
||||
Response.sendErrorResponse(req, res, err as Exception);
|
||||
if (err instanceof Exception) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return next(new ServerException());
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -160,7 +178,7 @@ router.get(
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
_next: NextFunction,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!req.params["projectId"]) {
|
||||
@@ -236,22 +254,42 @@ router.get(
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
||||
Response.sendErrorResponse(req, res, err as Exception);
|
||||
if (err instanceof Exception) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return next(new ServerException());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/idp-login/:projectId/:projectSsoId",
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
return await loginUserWithSso(req, res);
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
await loginUserWithSso(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/idp-login/:projectId/:projectSsoId",
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
return await loginUserWithSso(req, res);
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
await loginUserWithSso(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -434,8 +472,10 @@ const loginUserWithSso: LoginUserWithSsoFunction = async (
|
||||
isNewUser = true;
|
||||
}
|
||||
|
||||
// If he does not then add him to teams that he should belong and log in.
|
||||
// This should never happen because email is verified before he logs in with SSO.
|
||||
/*
|
||||
* If he does not then add him to teams that he should belong and log in.
|
||||
* This should never happen because email is verified before he logs in with SSO.
|
||||
*/
|
||||
if (!alreadySavedUser.isEmailVerified && !isNewUser) {
|
||||
await AuthenticationEmail.sendVerificationEmail(alreadySavedUser!);
|
||||
|
||||
@@ -507,15 +547,31 @@ const loginUserWithSso: LoginUserWithSsoFunction = async (
|
||||
expressResponse: res,
|
||||
});
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(alreadySavedUser.id!);
|
||||
|
||||
const sessionMetadata: SessionMetadata =
|
||||
await UserSessionService.createSession({
|
||||
userId: alreadySavedUser.id!,
|
||||
isGlobalLogin: false,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
additionalInfo: {
|
||||
projectId: projectId.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user: alreadySavedUser,
|
||||
isGlobalLogin: false,
|
||||
sessionId: sessionMetadata.session.id!,
|
||||
refreshToken: sessionMetadata.refreshToken,
|
||||
refreshTokenExpiresAt: sessionMetadata.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(alreadySavedUser.id!);
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
|
||||
@@ -6,30 +6,93 @@ import URL from "Common/Types/API/URL";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PositiveNumber from "Common/Types/PositiveNumber";
|
||||
import DatabaseConfig from "Common/Server/DatabaseConfig";
|
||||
import { EncryptionSecret } from "Common/Server/EnvironmentConfig";
|
||||
import MailService from "Common/Server/Services/MailService";
|
||||
import StatusPagePrivateUserService from "Common/Server/Services/StatusPagePrivateUserService";
|
||||
import StatusPageService from "Common/Server/Services/StatusPageService";
|
||||
import StatusPagePrivateUserSessionService, {
|
||||
SessionMetadata as StatusPageSessionMetadata,
|
||||
} from "Common/Server/Services/StatusPagePrivateUserSessionService";
|
||||
import CookieUtil from "Common/Server/Utils/Cookie";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
extractDeviceInfo,
|
||||
getClientIp,
|
||||
headerValueToString,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import StatusPage from "Common/Models/DatabaseModels/StatusPage";
|
||||
import StatusPagePrivateUser from "Common/Models/DatabaseModels/StatusPagePrivateUser";
|
||||
import StatusPagePrivateUserSession from "Common/Models/DatabaseModels/StatusPagePrivateUserSession";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;
|
||||
|
||||
type FinalizeStatusPageLoginInput = {
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
user: StatusPagePrivateUser;
|
||||
};
|
||||
|
||||
const finalizeStatusPageLogin: (data: FinalizeStatusPageLoginInput) => Promise<{
|
||||
sessionMetadata: StatusPageSessionMetadata;
|
||||
accessToken: string;
|
||||
}> = async (
|
||||
data: FinalizeStatusPageLoginInput,
|
||||
): Promise<{
|
||||
sessionMetadata: StatusPageSessionMetadata;
|
||||
accessToken: string;
|
||||
}> => {
|
||||
const { req, res, user } = data;
|
||||
|
||||
if (!user.projectId) {
|
||||
throw new BadDataException(
|
||||
"Status page user is missing associated projectId.",
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.statusPageId) {
|
||||
throw new BadDataException(
|
||||
"Status page user is missing associated statusPageId.",
|
||||
);
|
||||
}
|
||||
|
||||
const sessionMetadata: StatusPageSessionMetadata =
|
||||
await StatusPagePrivateUserSessionService.createSession({
|
||||
projectId: user.projectId,
|
||||
statusPageId: user.statusPageId,
|
||||
statusPagePrivateUserId: user.id!,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
});
|
||||
|
||||
const accessToken: string = CookieUtil.setStatusPagePrivateUserCookie({
|
||||
expressResponse: res,
|
||||
user,
|
||||
statusPageId: user.statusPageId,
|
||||
sessionId: sessionMetadata.session.id!,
|
||||
refreshToken: sessionMetadata.refreshToken,
|
||||
refreshTokenExpiresAt: sessionMetadata.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return {
|
||||
sessionMetadata,
|
||||
accessToken,
|
||||
};
|
||||
};
|
||||
|
||||
router.post(
|
||||
"/logout/:statuspageid",
|
||||
async (
|
||||
@@ -46,7 +109,20 @@ router.post(
|
||||
req.params["statuspageid"].toString(),
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId)); // remove the cookie.
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req, statusPageId);
|
||||
|
||||
if (refreshToken) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionByRefreshToken(
|
||||
refreshToken,
|
||||
{
|
||||
reason: "User logged out",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(res, CookieUtil.getRefreshTokenKey(statusPageId));
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
@@ -55,6 +131,198 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/refresh-token/:statuspageid",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const statusPageIdParam: string | undefined = req.params["statuspageid"];
|
||||
|
||||
if (!statusPageIdParam) {
|
||||
throw new BadDataException("Status Page ID is required.");
|
||||
}
|
||||
|
||||
const statusPageId: ObjectID = new ObjectID(statusPageIdParam.toString());
|
||||
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req, statusPageId);
|
||||
|
||||
if (!refreshToken) {
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException(
|
||||
"Refresh token missing. Please login again.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const session: StatusPagePrivateUserSession | null =
|
||||
await StatusPagePrivateUserSessionService.findActiveSessionByRefreshToken(
|
||||
refreshToken,
|
||||
);
|
||||
|
||||
if (!session || !session.id || !session.statusPageId) {
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (session.statusPageId.toString() !== statusPageId.toString()) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionById(
|
||||
session.id,
|
||||
{
|
||||
reason: "Status page mismatch",
|
||||
},
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
session.refreshTokenExpiresAt &&
|
||||
OneUptimeDate.hasExpired(session.refreshTokenExpiresAt)
|
||||
) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionById(
|
||||
session.id,
|
||||
{
|
||||
reason: "Refresh token expired",
|
||||
},
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (!session.statusPagePrivateUserId) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionById(
|
||||
session.id,
|
||||
{
|
||||
reason: "Session missing user",
|
||||
},
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
const user: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneById({
|
||||
id: session.statusPagePrivateUserId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionById(
|
||||
session.id,
|
||||
{
|
||||
reason: "User not found",
|
||||
},
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Account no longer exists."),
|
||||
);
|
||||
}
|
||||
|
||||
const renewedSession: StatusPageSessionMetadata =
|
||||
await StatusPagePrivateUserSessionService.renewSessionWithNewRefreshToken(
|
||||
{
|
||||
session,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
},
|
||||
);
|
||||
|
||||
const accessToken: string = CookieUtil.setStatusPagePrivateUserCookie({
|
||||
expressResponse: res,
|
||||
user,
|
||||
statusPageId: user.statusPageId!,
|
||||
sessionId: renewedSession.session.id!,
|
||||
refreshToken: renewedSession.refreshToken,
|
||||
refreshTokenExpiresAt: renewedSession.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return Response.sendEntityResponse(
|
||||
req,
|
||||
res,
|
||||
user,
|
||||
StatusPagePrivateUser,
|
||||
{
|
||||
miscData: {
|
||||
token: accessToken,
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/forgot-password",
|
||||
async (
|
||||
@@ -376,6 +644,7 @@ router.post(
|
||||
password: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -383,31 +652,38 @@ router.post(
|
||||
});
|
||||
|
||||
if (alreadySavedUser) {
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30),
|
||||
),
|
||||
const { accessToken } = await finalizeStatusPageLogin({
|
||||
req,
|
||||
res,
|
||||
user: alreadySavedUser,
|
||||
});
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
|
||||
token,
|
||||
{
|
||||
httpOnly: true,
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
||||
},
|
||||
);
|
||||
const sanitizedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneById({
|
||||
id: alreadySavedUser.id!,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!sanitizedUser && (alreadySavedUser as any).password) {
|
||||
delete (alreadySavedUser as any).password;
|
||||
}
|
||||
|
||||
return Response.sendEntityResponse(
|
||||
req,
|
||||
res,
|
||||
alreadySavedUser,
|
||||
sanitizedUser || alreadySavedUser,
|
||||
StatusPagePrivateUser,
|
||||
{
|
||||
miscData: {
|
||||
token: token,
|
||||
token: accessToken,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
OneUptimeRequest,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
@@ -19,7 +20,6 @@ import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import {
|
||||
formatUserForSCIM,
|
||||
generateServiceProviderConfig,
|
||||
logSCIMOperation,
|
||||
} from "../Utils/SCIMUtils";
|
||||
import Text from "Common/Types/Text";
|
||||
import HashedString from "Common/Types/HashedString";
|
||||
@@ -30,12 +30,14 @@ const router: ExpressRouter = Express.getRouter();
|
||||
router.get(
|
||||
"/status-page-scim/v2/:statusPageScimId/ServiceProviderConfig",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logSCIMOperation(
|
||||
"ServiceProviderConfig",
|
||||
"status-page",
|
||||
req.params["statusPageScimId"]!,
|
||||
logger.debug(
|
||||
`Status Page SCIM ServiceProviderConfig - scimId: ${req.params["statusPageScimId"]!}`,
|
||||
);
|
||||
|
||||
const serviceProviderConfig: JSONObject = generateServiceProviderConfig(
|
||||
@@ -47,7 +49,7 @@ router.get(
|
||||
return Response.sendJsonObjectResponse(req, res, serviceProviderConfig);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(req, res, err as BadRequestException);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -56,7 +58,11 @@ router.get(
|
||||
router.get(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Users list request for statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
@@ -73,17 +79,54 @@ router.get(
|
||||
parseInt(req.query["count"] as string) || 100,
|
||||
LIMIT_PER_PROJECT,
|
||||
);
|
||||
const filter: string = req.query["filter"] as string;
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Users - statusPageId: ${statusPageId}, startIndex: ${startIndex}, count: ${count}`,
|
||||
`Status Page SCIM Users - statusPageId: ${statusPageId}, startIndex: ${startIndex}, count: ${count}, filter: ${filter || "none"}`,
|
||||
);
|
||||
|
||||
// Build query for status page users
|
||||
const query: any = {
|
||||
statusPageId: statusPageId,
|
||||
};
|
||||
|
||||
// Handle SCIM filter for userName
|
||||
if (filter) {
|
||||
const emailMatch: RegExpMatchArray | null = filter.match(
|
||||
/userName eq "([^"]+)"/i,
|
||||
);
|
||||
if (emailMatch) {
|
||||
const email: string = emailMatch[1]!;
|
||||
logger.debug(
|
||||
`Status Page SCIM Users list - statusPageScimId: ${req.params["statusPageScimId"]!}, filter by email: ${email}`,
|
||||
);
|
||||
|
||||
if (email) {
|
||||
if (Email.isValid(email)) {
|
||||
query.email = new Email(email);
|
||||
logger.debug(
|
||||
`Status Page SCIM Users list - statusPageScimId: ${req.params["statusPageScimId"]!}, filtering by email: ${email}`,
|
||||
);
|
||||
} else {
|
||||
logger.debug(
|
||||
`Status Page SCIM Users list - statusPageScimId: ${req.params["statusPageScimId"]!}, invalid email format in filter: ${email}`,
|
||||
);
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: 0,
|
||||
startIndex: startIndex,
|
||||
itemsPerPage: 0,
|
||||
Resources: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all private users for this status page
|
||||
const statusPageUsers: Array<StatusPagePrivateUser> =
|
||||
await StatusPagePrivateUserService.findBy({
|
||||
query: {
|
||||
statusPageId: statusPageId,
|
||||
},
|
||||
query: query,
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
@@ -130,7 +173,7 @@ router.get(
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(req, res, err as BadRequestException);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -139,7 +182,11 @@ router.get(
|
||||
router.get(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Get individual user request for userId: ${req.params["userId"]}, statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
@@ -197,7 +244,7 @@ router.get(
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(req, res, err as BadRequestException);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -206,7 +253,11 @@ router.get(
|
||||
router.post(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Create user request for statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
@@ -299,7 +350,7 @@ router.post(
|
||||
return Response.sendJsonObjectResponse(req, res, createdUser);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(req, res, err as BadRequestException);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -308,7 +359,11 @@ router.post(
|
||||
router.put(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Update user request for userId: ${req.params["userId"]}, statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
@@ -455,7 +510,7 @@ router.put(
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(req, res, err as BadRequestException);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -464,7 +519,11 @@ router.put(
|
||||
router.delete(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Delete user request for userId: ${req.params["userId"]}, statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
@@ -528,7 +587,7 @@ router.delete(
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(req, res, err as BadRequestException);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import SSOUtil from "../Utils/SSO";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import Email from "Common/Types/Email";
|
||||
import BadRequestException from "Common/Types/Exception/BadRequestException";
|
||||
import Exception from "Common/Types/Exception/Exception";
|
||||
@@ -8,9 +7,11 @@ import ServerException from "Common/Types/Exception/ServerException";
|
||||
import HashedString from "Common/Types/HashedString";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PositiveNumber from "Common/Types/PositiveNumber";
|
||||
import { Host, HttpProtocol } from "Common/Server/EnvironmentConfig";
|
||||
import StatusPagePrivateUserService from "Common/Server/Services/StatusPagePrivateUserService";
|
||||
import StatusPagePrivateUserSessionService, {
|
||||
SessionMetadata as StatusPageSessionMetadata,
|
||||
} from "Common/Server/Services/StatusPagePrivateUserSessionService";
|
||||
import StatusPageService from "Common/Server/Services/StatusPageService";
|
||||
import StatusPageSsoService from "Common/Server/Services/StatusPageSsoService";
|
||||
import CookieUtil from "Common/Server/Utils/Cookie";
|
||||
@@ -19,8 +20,10 @@ import Express, {
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
extractDeviceInfo,
|
||||
getClientIp,
|
||||
headerValueToString,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import StatusPagePrivateUser from "Common/Models/DatabaseModels/StatusPagePrivateUser";
|
||||
@@ -30,6 +33,8 @@ import xml2js from "xml2js";
|
||||
// Initialize Express router.
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;
|
||||
|
||||
// Define a GET route for SSO in a status page context.
|
||||
router.get(
|
||||
"/status-page-sso/:statusPageId/:statusPageSsoId",
|
||||
@@ -115,7 +120,11 @@ router.get(
|
||||
|
||||
router.post(
|
||||
"/status-page-idp-login/:statusPageId/:statusPageSsoId",
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const samlResponseBase64: string = req.body.SAMLResponse;
|
||||
|
||||
@@ -281,24 +290,30 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30),
|
||||
),
|
||||
if (!alreadySavedUser.projectId) {
|
||||
alreadySavedUser.projectId = projectId;
|
||||
}
|
||||
|
||||
const sessionMetadata: StatusPageSessionMetadata =
|
||||
await StatusPagePrivateUserSessionService.createSession({
|
||||
projectId: alreadySavedUser.projectId!,
|
||||
statusPageId: statusPageId,
|
||||
statusPagePrivateUserId: alreadySavedUser.id!,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
});
|
||||
|
||||
const token: string = CookieUtil.setStatusPagePrivateUserCookie({
|
||||
expressResponse: res,
|
||||
user: alreadySavedUser,
|
||||
statusPageId: statusPageId,
|
||||
sessionId: sessionMetadata.session.id!,
|
||||
refreshToken: sessionMetadata.refreshToken,
|
||||
refreshTokenExpiresAt: sessionMetadata.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
|
||||
token,
|
||||
|
||||
{
|
||||
httpOnly: true,
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
||||
},
|
||||
);
|
||||
|
||||
// get status page URL.
|
||||
const statusPageURL: string =
|
||||
await StatusPageService.getStatusPageFirstURL(statusPageId);
|
||||
@@ -312,7 +327,11 @@ router.post(
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
Response.sendErrorResponse(req, res, new ServerException());
|
||||
if (err instanceof Exception) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return next(new ServerException());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -35,6 +35,8 @@ export default class AuthenticationEmail {
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
logger.debug("Sending verification email");
|
||||
|
||||
MailService.sendMail({
|
||||
toEmail: user.email!,
|
||||
subject: "Please verify email.",
|
||||
@@ -50,8 +52,13 @@ export default class AuthenticationEmail {
|
||||
).toString(),
|
||||
homeUrl: new URL(httpProtocol, host).toString(),
|
||||
},
|
||||
}).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
logger.debug("Verification email sent");
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
logger.debug("Error sending verification email");
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +227,27 @@ export const generateUsersListResponse: (
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate SCIM ListResponse for groups
|
||||
*/
|
||||
export const generateGroupsListResponse: (
|
||||
groups: JSONObject[],
|
||||
startIndex: number,
|
||||
totalResults: number,
|
||||
) => JSONObject = (
|
||||
groups: JSONObject[],
|
||||
startIndex: number,
|
||||
totalResults: number,
|
||||
): JSONObject => {
|
||||
return {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: totalResults,
|
||||
startIndex: startIndex,
|
||||
itemsPerPage: groups.length,
|
||||
Resources: groups,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse query parameters for SCIM list requests
|
||||
*/
|
||||
@@ -242,23 +263,3 @@ export const parseSCIMQueryParams: (req: ExpressRequest) => {
|
||||
|
||||
return { startIndex, count };
|
||||
};
|
||||
|
||||
/**
|
||||
* Log SCIM operation with consistent format
|
||||
*/
|
||||
export const logSCIMOperation: (
|
||||
operation: string,
|
||||
scimType: "project" | "status-page",
|
||||
scimId: string,
|
||||
details?: string,
|
||||
) => void = (
|
||||
operation: string,
|
||||
scimType: "project" | "status-page",
|
||||
scimId: string,
|
||||
details?: string,
|
||||
): void => {
|
||||
const logPrefix: string =
|
||||
scimType === "project" ? "Project SCIM" : "Status Page SCIM";
|
||||
const message: string = `${logPrefix} ${operation} - scimId: ${scimId}${details ? `, ${details}` : ""}`;
|
||||
logger.debug(message);
|
||||
};
|
||||
|
||||
@@ -184,15 +184,17 @@ export default class SSOUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get displayName attribute.
|
||||
// {
|
||||
// "$": {
|
||||
// "Name": "http://schemas.microsoft.com/identity/claims/displayname"
|
||||
// },
|
||||
// "AttributeValue": [
|
||||
// "Nawaz Dhandala"
|
||||
// ]
|
||||
// },
|
||||
/*
|
||||
* get displayName attribute.
|
||||
* {
|
||||
* "$": {
|
||||
* "Name": "http://schemas.microsoft.com/identity/claims/displayname"
|
||||
* },
|
||||
* "AttributeValue": [
|
||||
* "Nawaz Dhandala"
|
||||
* ]
|
||||
* },
|
||||
*/
|
||||
|
||||
for (let i: number = 0; i < samlAttribute.length; i++) {
|
||||
const attribute: JSONObject = samlAttribute[i] as JSONObject;
|
||||
|
||||
@@ -12,6 +12,7 @@ import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
@@ -22,139 +23,146 @@ const router: ExpressRouter = Express.getRouter();
|
||||
router.post(
|
||||
"/make-call",
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
|
||||
await CallService.makeCall(body["callRequest"] as CallRequest, {
|
||||
projectId: body["projectId"] as ObjectID,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
customTwilioConfig: body["customTwilioConfig"] as any,
|
||||
incidentId: (body["incidentId"] as ObjectID) || undefined,
|
||||
alertId: (body["alertId"] as ObjectID) || undefined,
|
||||
scheduledMaintenanceId:
|
||||
(body["scheduledMaintenanceId"] as ObjectID) || undefined,
|
||||
statusPageId: (body["statusPageId"] as ObjectID) || undefined,
|
||||
statusPageAnnouncementId:
|
||||
(body["statusPageAnnouncementId"] as ObjectID) || undefined,
|
||||
userId: (body["userId"] as ObjectID) || undefined,
|
||||
onCallPolicyId: (body["onCallPolicyId"] as ObjectID) || undefined,
|
||||
onCallPolicyEscalationRuleId:
|
||||
(body["onCallPolicyEscalationRuleId"] as ObjectID) || undefined,
|
||||
onCallDutyPolicyExecutionLogTimelineId:
|
||||
(body["onCallDutyPolicyExecutionLogTimelineId"] as ObjectID) ||
|
||||
undefined,
|
||||
onCallScheduleId: (body["onCallScheduleId"] as ObjectID) || undefined,
|
||||
teamId: (body["teamId"] as ObjectID) || undefined,
|
||||
});
|
||||
await CallService.makeCall(body["callRequest"] as CallRequest, {
|
||||
projectId: body["projectId"] as ObjectID,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
customTwilioConfig: body["customTwilioConfig"] as any,
|
||||
incidentId: (body["incidentId"] as ObjectID) || undefined,
|
||||
alertId: (body["alertId"] as ObjectID) || undefined,
|
||||
scheduledMaintenanceId:
|
||||
(body["scheduledMaintenanceId"] as ObjectID) || undefined,
|
||||
statusPageId: (body["statusPageId"] as ObjectID) || undefined,
|
||||
statusPageAnnouncementId:
|
||||
(body["statusPageAnnouncementId"] as ObjectID) || undefined,
|
||||
userId: (body["userId"] as ObjectID) || undefined,
|
||||
onCallPolicyId: (body["onCallPolicyId"] as ObjectID) || undefined,
|
||||
onCallPolicyEscalationRuleId:
|
||||
(body["onCallPolicyEscalationRuleId"] as ObjectID) || undefined,
|
||||
onCallDutyPolicyExecutionLogTimelineId:
|
||||
(body["onCallDutyPolicyExecutionLogTimelineId"] as ObjectID) ||
|
||||
undefined,
|
||||
onCallScheduleId: (body["onCallScheduleId"] as ObjectID) || undefined,
|
||||
teamId: (body["teamId"] as ObjectID) || undefined,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
router.post(
|
||||
"/test",
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const callSMSConfigId: ObjectID = new ObjectID(
|
||||
body["callSMSConfigId"] as string,
|
||||
);
|
||||
const callSMSConfigId: ObjectID = new ObjectID(
|
||||
body["callSMSConfigId"] as string,
|
||||
);
|
||||
|
||||
const config: ProjectCallSMSConfig | null =
|
||||
await ProjectCallSMSConfigService.findOneById({
|
||||
id: callSMSConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPrimaryPhoneNumber: true,
|
||||
twilioSecondaryPhoneNumbers: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
const config: ProjectCallSMSConfig | null =
|
||||
await ProjectCallSMSConfigService.findOneById({
|
||||
id: callSMSConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPrimaryPhoneNumber: true,
|
||||
twilioSecondaryPhoneNumbers: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"call and sms config not found for id" + callSMSConfigId.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"call and sms config not found for id" + callSMSConfigId.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const toPhone: Phone = new Phone(body["toPhone"] as string);
|
||||
const toPhone: Phone = new Phone(body["toPhone"] as string);
|
||||
|
||||
if (!toPhone) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("toPhone is required"),
|
||||
);
|
||||
}
|
||||
if (!toPhone) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("toPhone is required"),
|
||||
);
|
||||
}
|
||||
|
||||
// if any of the twilio config is missing, we will not send make the call
|
||||
// if any of the twilio config is missing, we will not send make the call
|
||||
|
||||
if (!config.twilioAccountSID) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAccountSID is required"),
|
||||
);
|
||||
}
|
||||
if (!config.twilioAccountSID) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAccountSID is required"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioAuthToken) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAuthToken is required"),
|
||||
);
|
||||
}
|
||||
if (!config.twilioAuthToken) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAuthToken is required"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioPrimaryPhoneNumber) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioPrimaryPhoneNumber is required"),
|
||||
);
|
||||
}
|
||||
if (!config.twilioPrimaryPhoneNumber) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioPrimaryPhoneNumber is required"),
|
||||
);
|
||||
}
|
||||
|
||||
const twilioConfig: TwilioConfig | undefined =
|
||||
ProjectCallSMSConfigService.toTwilioConfig(config);
|
||||
const twilioConfig: TwilioConfig | undefined =
|
||||
ProjectCallSMSConfigService.toTwilioConfig(config);
|
||||
|
||||
try {
|
||||
if (!twilioConfig) {
|
||||
throw new BadDataException("twilioConfig is undefined");
|
||||
try {
|
||||
if (!twilioConfig) {
|
||||
throw new BadDataException("twilioConfig is undefined");
|
||||
}
|
||||
|
||||
const testCallRequest: CallRequest = {
|
||||
data: [
|
||||
{
|
||||
sayMessage: "This is a test call from OneUptime.",
|
||||
},
|
||||
],
|
||||
to: toPhone,
|
||||
};
|
||||
|
||||
await CallService.makeCall(testCallRequest, {
|
||||
projectId: config.projectId,
|
||||
customTwilioConfig: twilioConfig,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
throw new BadDataException(
|
||||
"Error making test call. Please check the twilio logs for more details",
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
const testCallRequest: CallRequest = {
|
||||
data: [
|
||||
{
|
||||
sayMessage: "This is a test call from OneUptime.",
|
||||
},
|
||||
],
|
||||
to: toPhone,
|
||||
};
|
||||
|
||||
await CallService.makeCall(testCallRequest, {
|
||||
projectId: config.projectId,
|
||||
customTwilioConfig: twilioConfig,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"Error making test call. Please check the twilio logs for more details",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -11,6 +11,7 @@ import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
|
||||
@@ -19,70 +20,74 @@ const router: ExpressRouter = Express.getRouter();
|
||||
router.post(
|
||||
"/send",
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const mail: EmailMessage = {
|
||||
templateType: body["templateType"] as EmailTemplateType,
|
||||
toEmail: new Email(body["toEmail"] as string),
|
||||
subject: body["subject"] as string,
|
||||
vars: body["vars"] as Dictionary<string>,
|
||||
body: (body["body"] as string) || "",
|
||||
};
|
||||
const mail: EmailMessage = {
|
||||
templateType: body["templateType"] as EmailTemplateType,
|
||||
toEmail: new Email(body["toEmail"] as string),
|
||||
subject: body["subject"] as string,
|
||||
vars: body["vars"] as Dictionary<string>,
|
||||
body: (body["body"] as string) || "",
|
||||
};
|
||||
|
||||
let mailServer: EmailServer | undefined = undefined;
|
||||
let mailServer: EmailServer | undefined = undefined;
|
||||
|
||||
if (hasMailServerSettingsInBody(body)) {
|
||||
mailServer = MailService.getEmailServer(req.body);
|
||||
if (hasMailServerSettingsInBody(body)) {
|
||||
mailServer = MailService.getEmailServer(req.body);
|
||||
}
|
||||
|
||||
await MailService.send(mail, {
|
||||
projectId: body["projectId"]
|
||||
? new ObjectID(body["projectId"] as string)
|
||||
: undefined,
|
||||
emailServer: mailServer,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
incidentId: body["incidentId"]
|
||||
? new ObjectID(body["incidentId"].toString())
|
||||
: undefined,
|
||||
alertId: body["alertId"]
|
||||
? new ObjectID(body["alertId"].toString())
|
||||
: undefined,
|
||||
scheduledMaintenanceId: body["scheduledMaintenanceId"]
|
||||
? new ObjectID(body["scheduledMaintenanceId"].toString())
|
||||
: undefined,
|
||||
statusPageId: body["statusPageId"]
|
||||
? new ObjectID(body["statusPageId"].toString())
|
||||
: undefined,
|
||||
statusPageAnnouncementId: body["statusPageAnnouncementId"]
|
||||
? new ObjectID(body["statusPageAnnouncementId"].toString())
|
||||
: undefined,
|
||||
userId: body["userId"]
|
||||
? new ObjectID(body["userId"].toString())
|
||||
: undefined,
|
||||
onCallPolicyId: body["onCallPolicyId"]
|
||||
? new ObjectID(body["onCallPolicyId"].toString())
|
||||
: undefined,
|
||||
onCallPolicyEscalationRuleId: body["onCallPolicyEscalationRuleId"]
|
||||
? new ObjectID(body["onCallPolicyEscalationRuleId"].toString())
|
||||
: undefined,
|
||||
onCallDutyPolicyExecutionLogTimelineId: body[
|
||||
"onCallDutyPolicyExecutionLogTimelineId"
|
||||
]
|
||||
? new ObjectID(
|
||||
body["onCallDutyPolicyExecutionLogTimelineId"].toString(),
|
||||
)
|
||||
: undefined,
|
||||
onCallScheduleId: body["onCallScheduleId"]
|
||||
? new ObjectID(body["onCallScheduleId"].toString())
|
||||
: undefined,
|
||||
teamId: body["teamId"]
|
||||
? new ObjectID(body["teamId"].toString())
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
await MailService.send(mail, {
|
||||
projectId: body["projectId"]
|
||||
? new ObjectID(body["projectId"] as string)
|
||||
: undefined,
|
||||
emailServer: mailServer,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
incidentId: body["incidentId"]
|
||||
? new ObjectID(body["incidentId"].toString())
|
||||
: undefined,
|
||||
alertId: body["alertId"]
|
||||
? new ObjectID(body["alertId"].toString())
|
||||
: undefined,
|
||||
scheduledMaintenanceId: body["scheduledMaintenanceId"]
|
||||
? new ObjectID(body["scheduledMaintenanceId"].toString())
|
||||
: undefined,
|
||||
statusPageId: body["statusPageId"]
|
||||
? new ObjectID(body["statusPageId"].toString())
|
||||
: undefined,
|
||||
statusPageAnnouncementId: body["statusPageAnnouncementId"]
|
||||
? new ObjectID(body["statusPageAnnouncementId"].toString())
|
||||
: undefined,
|
||||
userId: body["userId"]
|
||||
? new ObjectID(body["userId"].toString())
|
||||
: undefined,
|
||||
onCallPolicyId: body["onCallPolicyId"]
|
||||
? new ObjectID(body["onCallPolicyId"].toString())
|
||||
: undefined,
|
||||
onCallPolicyEscalationRuleId: body["onCallPolicyEscalationRuleId"]
|
||||
? new ObjectID(body["onCallPolicyEscalationRuleId"].toString())
|
||||
: undefined,
|
||||
onCallDutyPolicyExecutionLogTimelineId: body[
|
||||
"onCallDutyPolicyExecutionLogTimelineId"
|
||||
]
|
||||
? new ObjectID(
|
||||
body["onCallDutyPolicyExecutionLogTimelineId"].toString(),
|
||||
)
|
||||
: undefined,
|
||||
onCallScheduleId: body["onCallScheduleId"]
|
||||
? new ObjectID(body["onCallScheduleId"].toString())
|
||||
: undefined,
|
||||
teamId: body["teamId"]
|
||||
? new ObjectID(body["teamId"].toString())
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
@@ -15,50 +16,54 @@ const router: ExpressRouter = Express.getRouter();
|
||||
router.post(
|
||||
"/send",
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
|
||||
// Support both new devices format and legacy deviceTokens/deviceNames format
|
||||
let devices: Array<{ token: string; name?: string }> = [];
|
||||
// Support both new devices format and legacy deviceTokens/deviceNames format
|
||||
let devices: Array<{ token: string; name?: string }> = [];
|
||||
|
||||
if (body["devices"]) {
|
||||
// New format: devices as array of objects
|
||||
devices = body["devices"] as Array<{ token: string; name?: string }>;
|
||||
} else {
|
||||
throw new Error("Invalid request format: 'devices' array is required.");
|
||||
if (body["devices"]) {
|
||||
// New format: devices as array of objects
|
||||
devices = body["devices"] as Array<{ token: string; name?: string }>;
|
||||
} else {
|
||||
throw new Error("Invalid request format: 'devices' array is required.");
|
||||
}
|
||||
|
||||
await PushService.send(
|
||||
{
|
||||
devices,
|
||||
deviceType: (body["deviceType"] as any) || "web",
|
||||
message: body["message"] as any,
|
||||
},
|
||||
{
|
||||
projectId: (body["projectId"] as ObjectID) || undefined,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
incidentId: (body["incidentId"] as ObjectID) || undefined,
|
||||
alertId: (body["alertId"] as ObjectID) || undefined,
|
||||
scheduledMaintenanceId:
|
||||
(body["scheduledMaintenanceId"] as ObjectID) || undefined,
|
||||
statusPageId: (body["statusPageId"] as ObjectID) || undefined,
|
||||
statusPageAnnouncementId:
|
||||
(body["statusPageAnnouncementId"] as ObjectID) || undefined,
|
||||
userId: (body["userId"] as ObjectID) || undefined,
|
||||
onCallPolicyId: (body["onCallPolicyId"] as ObjectID) || undefined,
|
||||
onCallPolicyEscalationRuleId:
|
||||
(body["onCallPolicyEscalationRuleId"] as ObjectID) || undefined,
|
||||
onCallDutyPolicyExecutionLogTimelineId:
|
||||
(body["onCallDutyPolicyExecutionLogTimelineId"] as ObjectID) ||
|
||||
undefined,
|
||||
onCallScheduleId: (body["onCallScheduleId"] as ObjectID) || undefined,
|
||||
teamId: (body["teamId"] as ObjectID) || undefined,
|
||||
},
|
||||
);
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
await PushService.send(
|
||||
{
|
||||
devices,
|
||||
deviceType: (body["deviceType"] as any) || "web",
|
||||
message: body["message"] as any,
|
||||
},
|
||||
{
|
||||
projectId: (body["projectId"] as ObjectID) || undefined,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
incidentId: (body["incidentId"] as ObjectID) || undefined,
|
||||
alertId: (body["alertId"] as ObjectID) || undefined,
|
||||
scheduledMaintenanceId:
|
||||
(body["scheduledMaintenanceId"] as ObjectID) || undefined,
|
||||
statusPageId: (body["statusPageId"] as ObjectID) || undefined,
|
||||
statusPageAnnouncementId:
|
||||
(body["statusPageAnnouncementId"] as ObjectID) || undefined,
|
||||
userId: (body["userId"] as ObjectID) || undefined,
|
||||
onCallPolicyId: (body["onCallPolicyId"] as ObjectID) || undefined,
|
||||
onCallPolicyEscalationRuleId:
|
||||
(body["onCallPolicyEscalationRuleId"] as ObjectID) || undefined,
|
||||
onCallDutyPolicyExecutionLogTimelineId:
|
||||
(body["onCallDutyPolicyExecutionLogTimelineId"] as ObjectID) ||
|
||||
undefined,
|
||||
onCallScheduleId: (body["onCallScheduleId"] as ObjectID) || undefined,
|
||||
teamId: (body["teamId"] as ObjectID) || undefined,
|
||||
},
|
||||
);
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
@@ -21,130 +22,141 @@ const router: ExpressRouter = Express.getRouter();
|
||||
router.post(
|
||||
"/send",
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
|
||||
await SmsService.sendSms(body["to"] as Phone, body["message"] as string, {
|
||||
projectId: body["projectId"] as ObjectID,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
customTwilioConfig: body["customTwilioConfig"] as any,
|
||||
incidentId: (body["incidentId"] as ObjectID) || undefined,
|
||||
alertId: (body["alertId"] as ObjectID) || undefined,
|
||||
scheduledMaintenanceId:
|
||||
(body["scheduledMaintenanceId"] as ObjectID) || undefined,
|
||||
statusPageId: (body["statusPageId"] as ObjectID) || undefined,
|
||||
statusPageAnnouncementId:
|
||||
(body["statusPageAnnouncementId"] as ObjectID) || undefined,
|
||||
userId: (body["userId"] as ObjectID) || undefined,
|
||||
onCallPolicyId: (body["onCallPolicyId"] as ObjectID) || undefined,
|
||||
onCallPolicyEscalationRuleId:
|
||||
(body["onCallPolicyEscalationRuleId"] as ObjectID) || undefined,
|
||||
onCallDutyPolicyExecutionLogTimelineId:
|
||||
(body["onCallDutyPolicyExecutionLogTimelineId"] as ObjectID) ||
|
||||
undefined,
|
||||
onCallScheduleId: (body["onCallScheduleId"] as ObjectID) || undefined,
|
||||
teamId: (body["teamId"] as ObjectID) || undefined,
|
||||
});
|
||||
await SmsService.sendSms(body["to"] as Phone, body["message"] as string, {
|
||||
projectId: body["projectId"] as ObjectID,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
customTwilioConfig: body["customTwilioConfig"] as any,
|
||||
incidentId: (body["incidentId"] as ObjectID) || undefined,
|
||||
alertId: (body["alertId"] as ObjectID) || undefined,
|
||||
scheduledMaintenanceId:
|
||||
(body["scheduledMaintenanceId"] as ObjectID) || undefined,
|
||||
statusPageId: (body["statusPageId"] as ObjectID) || undefined,
|
||||
statusPageAnnouncementId:
|
||||
(body["statusPageAnnouncementId"] as ObjectID) || undefined,
|
||||
userId: (body["userId"] as ObjectID) || undefined,
|
||||
onCallPolicyId: (body["onCallPolicyId"] as ObjectID) || undefined,
|
||||
onCallPolicyEscalationRuleId:
|
||||
(body["onCallPolicyEscalationRuleId"] as ObjectID) || undefined,
|
||||
onCallDutyPolicyExecutionLogTimelineId:
|
||||
(body["onCallDutyPolicyExecutionLogTimelineId"] as ObjectID) ||
|
||||
undefined,
|
||||
onCallScheduleId: (body["onCallScheduleId"] as ObjectID) || undefined,
|
||||
teamId: (body["teamId"] as ObjectID) || undefined,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
router.post(
|
||||
"/test",
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const callSMSConfigId: ObjectID = new ObjectID(
|
||||
body["callSMSConfigId"] as string,
|
||||
);
|
||||
const callSMSConfigId: ObjectID = new ObjectID(
|
||||
body["callSMSConfigId"] as string,
|
||||
);
|
||||
|
||||
const config: ProjectCallSMSConfig | null =
|
||||
await ProjectCallSMSConfigService.findOneById({
|
||||
id: callSMSConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPrimaryPhoneNumber: true,
|
||||
twilioSecondaryPhoneNumbers: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
const config: ProjectCallSMSConfig | null =
|
||||
await ProjectCallSMSConfigService.findOneById({
|
||||
id: callSMSConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPrimaryPhoneNumber: true,
|
||||
twilioSecondaryPhoneNumbers: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"call and sms config not found for id" + callSMSConfigId.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"call and sms config not found for id" + callSMSConfigId.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const toPhone: Phone = new Phone(body["toPhone"] as string);
|
||||
const toPhone: Phone = new Phone(body["toPhone"] as string);
|
||||
|
||||
if (!toPhone) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("toPhone is required"),
|
||||
);
|
||||
}
|
||||
if (!toPhone) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("toPhone is required"),
|
||||
);
|
||||
}
|
||||
|
||||
// if any of the twilio config is missing, we will not send make the call
|
||||
// if any of the twilio config is missing, we will not send make the call
|
||||
|
||||
if (!config.twilioAccountSID) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAccountSID is required"),
|
||||
);
|
||||
}
|
||||
if (!config.twilioAccountSID) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAccountSID is required"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioAuthToken) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAuthToken is required"),
|
||||
);
|
||||
}
|
||||
if (!config.twilioAuthToken) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAuthToken is required"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioPrimaryPhoneNumber) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioPrimaryPhoneNumber is required"),
|
||||
);
|
||||
}
|
||||
if (!config.twilioPrimaryPhoneNumber) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioPrimaryPhoneNumber is required"),
|
||||
);
|
||||
}
|
||||
|
||||
const twilioConfig: TwilioConfig | undefined =
|
||||
ProjectCallSMSConfigService.toTwilioConfig(config);
|
||||
const twilioConfig: TwilioConfig | undefined =
|
||||
ProjectCallSMSConfigService.toTwilioConfig(config);
|
||||
|
||||
try {
|
||||
if (!twilioConfig) {
|
||||
throw new BadDataException("twilioConfig is undefined");
|
||||
try {
|
||||
if (!twilioConfig) {
|
||||
throw new BadDataException("twilioConfig is undefined");
|
||||
}
|
||||
|
||||
await SmsService.sendSms(
|
||||
toPhone,
|
||||
"This is a test SMS from OneUptime.",
|
||||
{
|
||||
projectId: config.projectId,
|
||||
customTwilioConfig: twilioConfig,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
throw new BadDataException(
|
||||
"Failed to send test SMS. Please check the twilio logs for more details.",
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
await SmsService.sendSms(toPhone, "This is a test SMS from OneUptime.", {
|
||||
projectId: config.projectId,
|
||||
customTwilioConfig: twilioConfig,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"Failed to send test SMS. Please check the twilio logs for more details.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -11,6 +11,7 @@ import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
@@ -18,87 +19,92 @@ import ProjectSmtpConfig from "Common/Models/DatabaseModels/ProjectSmtpConfig";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
router.post(
|
||||
"/test",
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const smtpConfigId: ObjectID = new ObjectID(body["smtpConfigId"] as string);
|
||||
const smtpConfigId: ObjectID = new ObjectID(
|
||||
body["smtpConfigId"] as string,
|
||||
);
|
||||
|
||||
const config: ProjectSmtpConfig | null =
|
||||
await ProjectSMTPConfigService.findOneById({
|
||||
id: smtpConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
hostname: true,
|
||||
port: true,
|
||||
username: true,
|
||||
password: true,
|
||||
fromEmail: true,
|
||||
fromName: true,
|
||||
secure: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
const config: ProjectSmtpConfig | null =
|
||||
await ProjectSMTPConfigService.findOneById({
|
||||
id: smtpConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
hostname: true,
|
||||
port: true,
|
||||
username: true,
|
||||
password: true,
|
||||
fromEmail: true,
|
||||
fromName: true,
|
||||
secure: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"smtp-config not found for id" + smtpConfigId.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"smtp-config not found for id" + smtpConfigId.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const toEmail: Email = new Email(body["toEmail"] as string);
|
||||
const toEmail: Email = new Email(body["toEmail"] as string);
|
||||
|
||||
if (!toEmail) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("toEmail is required"),
|
||||
);
|
||||
}
|
||||
if (!toEmail) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("toEmail is required"),
|
||||
);
|
||||
}
|
||||
|
||||
const mail: EmailMessage = {
|
||||
templateType: EmailTemplateType.SMTPTest,
|
||||
toEmail: new Email(body["toEmail"] as string),
|
||||
subject: "Test Email from OneUptime",
|
||||
vars: {},
|
||||
body: "",
|
||||
};
|
||||
const mail: EmailMessage = {
|
||||
templateType: EmailTemplateType.SMTPTest,
|
||||
toEmail: new Email(body["toEmail"] as string),
|
||||
subject: "Test Email from OneUptime",
|
||||
vars: {},
|
||||
body: "",
|
||||
};
|
||||
|
||||
const mailServer: EmailServer = {
|
||||
id: config.id!,
|
||||
host: config.hostname!,
|
||||
port: config.port!,
|
||||
username: config.username!,
|
||||
password: config.password!,
|
||||
fromEmail: config.fromEmail!,
|
||||
fromName: config.fromName!,
|
||||
secure: Boolean(config.secure),
|
||||
};
|
||||
const mailServer: EmailServer = {
|
||||
id: config.id!,
|
||||
host: config.hostname!,
|
||||
port: config.port!,
|
||||
username: config.username!,
|
||||
password: config.password!,
|
||||
fromEmail: config.fromEmail!,
|
||||
fromName: config.fromName!,
|
||||
secure: Boolean(config.secure),
|
||||
};
|
||||
|
||||
try {
|
||||
await MailService.send(mail, {
|
||||
emailServer: mailServer,
|
||||
projectId: config.projectId!,
|
||||
timeout: 4000,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.",
|
||||
),
|
||||
);
|
||||
}
|
||||
try {
|
||||
await MailService.send(mail, {
|
||||
emailServer: mailServer,
|
||||
projectId: config.projectId!,
|
||||
timeout: 4000,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
throw new BadDataException(
|
||||
"Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.",
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
});
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
491
App/FeatureSet/Notification/API/WhatsApp.ts
Normal file
491
App/FeatureSet/Notification/API/WhatsApp.ts
Normal file
@@ -0,0 +1,491 @@
|
||||
import WhatsAppService from "../Services/WhatsAppService";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import GlobalConfig from "Common/Models/DatabaseModels/GlobalConfig";
|
||||
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Phone from "Common/Types/Phone";
|
||||
import WhatsAppMessage from "Common/Types/WhatsApp/WhatsAppMessage";
|
||||
import {
|
||||
WhatsAppTemplateId,
|
||||
WhatsAppTemplateIds,
|
||||
WhatsAppTemplateLanguage,
|
||||
} from "Common/Types/WhatsApp/WhatsAppTemplates";
|
||||
import WhatsAppStatus from "Common/Types/WhatsAppStatus";
|
||||
import ClusterKeyAuthorization from "Common/Server/Middleware/ClusterKeyAuthorization";
|
||||
import WhatsAppLogService from "Common/Server/Services/WhatsAppLogService";
|
||||
import GlobalConfigService from "Common/Server/Services/GlobalConfigService";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const MAX_STATUS_MESSAGE_LENGTH: number = 500;
|
||||
|
||||
export const mapWebhookStatusToWhatsAppStatus: (
|
||||
status?: string,
|
||||
) => WhatsAppStatus = (status?: string): WhatsAppStatus => {
|
||||
switch ((status || "").toLowerCase()) {
|
||||
case "sent":
|
||||
return WhatsAppStatus.Sent;
|
||||
case "delivered":
|
||||
return WhatsAppStatus.Delivered;
|
||||
case "read":
|
||||
return WhatsAppStatus.Read;
|
||||
case "failed":
|
||||
return WhatsAppStatus.Failed;
|
||||
case "deleted":
|
||||
case "removed":
|
||||
return WhatsAppStatus.Deleted;
|
||||
case "warning":
|
||||
return WhatsAppStatus.Warning;
|
||||
case "queued":
|
||||
case "pending":
|
||||
return WhatsAppStatus.Queued;
|
||||
case "error":
|
||||
return WhatsAppStatus.Error;
|
||||
case "success":
|
||||
return WhatsAppStatus.Success;
|
||||
default:
|
||||
return WhatsAppStatus.Unknown;
|
||||
}
|
||||
};
|
||||
|
||||
export const buildStatusMessage: (payload: JSONObject) => string | undefined = (
|
||||
payload: JSONObject,
|
||||
): string | undefined => {
|
||||
const messageParts: Array<string> = [];
|
||||
const rawStatus: string | undefined = payload["status"]
|
||||
? String(payload["status"])
|
||||
: undefined;
|
||||
|
||||
if (rawStatus) {
|
||||
messageParts.push(`Status: ${rawStatus}`);
|
||||
}
|
||||
|
||||
const timestamp: string | undefined = payload["timestamp"]
|
||||
? String(payload["timestamp"])
|
||||
: undefined;
|
||||
|
||||
if (timestamp) {
|
||||
const numericTimestamp: number = Number(timestamp);
|
||||
if (!isNaN(numericTimestamp)) {
|
||||
messageParts.push(
|
||||
`Timestamp: ${new Date(numericTimestamp * 1000).toISOString()}`,
|
||||
);
|
||||
} else {
|
||||
messageParts.push(`Timestamp: ${timestamp}`);
|
||||
}
|
||||
}
|
||||
|
||||
const conversation: JSONObject | undefined =
|
||||
(payload["conversation"] as JSONObject | undefined) || undefined;
|
||||
|
||||
if (conversation) {
|
||||
if (conversation["id"]) {
|
||||
messageParts.push(`Conversation: ${conversation["id"]}`);
|
||||
}
|
||||
|
||||
const origin: JSONObject | undefined =
|
||||
(conversation["origin"] as JSONObject | undefined) || undefined;
|
||||
|
||||
if (origin?.["type"]) {
|
||||
messageParts.push(`Origin: ${origin["type"]}`);
|
||||
}
|
||||
|
||||
if (conversation["expiration_timestamp"]) {
|
||||
const expirationTimestamp: number = Number(
|
||||
conversation["expiration_timestamp"],
|
||||
);
|
||||
|
||||
if (!isNaN(expirationTimestamp)) {
|
||||
messageParts.push(
|
||||
`Conversation expires: ${new Date(expirationTimestamp * 1000).toISOString()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pricing: JSONObject | undefined =
|
||||
(payload["pricing"] as JSONObject | undefined) || undefined;
|
||||
|
||||
if (pricing) {
|
||||
const pricingParts: Array<string> = [];
|
||||
|
||||
if (pricing["billable"] !== undefined) {
|
||||
pricingParts.push(`billable=${pricing["billable"]}`);
|
||||
}
|
||||
|
||||
if (pricing["category"]) {
|
||||
pricingParts.push(`category=${pricing["category"]}`);
|
||||
}
|
||||
|
||||
if (pricing["pricing_model"]) {
|
||||
pricingParts.push(`model=${pricing["pricing_model"]}`);
|
||||
}
|
||||
|
||||
if (pricingParts.length > 0) {
|
||||
messageParts.push(`Pricing: ${pricingParts.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
const errors: JSONArray | undefined =
|
||||
(payload["errors"] as JSONArray | undefined) || undefined;
|
||||
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
const firstError: JSONObject = errors[0] as JSONObject;
|
||||
const errorParts: Array<string> = [];
|
||||
|
||||
if (firstError["title"]) {
|
||||
errorParts.push(String(firstError["title"]));
|
||||
}
|
||||
|
||||
if (firstError["code"]) {
|
||||
errorParts.push(`code=${firstError["code"]}`);
|
||||
}
|
||||
|
||||
if (firstError["detail"]) {
|
||||
errorParts.push(String(firstError["detail"]));
|
||||
}
|
||||
|
||||
if (errorParts.length > 0) {
|
||||
messageParts.push(`Error: ${errorParts.join(" - ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (messageParts.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const statusMessage: string = messageParts.join(" | ");
|
||||
|
||||
if (statusMessage.length <= MAX_STATUS_MESSAGE_LENGTH) {
|
||||
return statusMessage;
|
||||
}
|
||||
|
||||
return `${statusMessage.substring(0, MAX_STATUS_MESSAGE_LENGTH - 3)}...`;
|
||||
};
|
||||
|
||||
const updateWhatsAppLogStatus: (
|
||||
statusPayload: JSONObject,
|
||||
) => Promise<void> = async (statusPayload: JSONObject): Promise<void> => {
|
||||
const messageId: string | undefined = statusPayload["id"]
|
||||
? String(statusPayload["id"])
|
||||
: undefined;
|
||||
|
||||
if (!messageId) {
|
||||
logger.warn(
|
||||
`[Meta WhatsApp Webhook] Received status payload without message id. Payload: ${JSON.stringify(statusPayload)}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const rawStatus: string | undefined = statusPayload["status"]
|
||||
? String(statusPayload["status"])
|
||||
: undefined;
|
||||
|
||||
const derivedStatus: WhatsAppStatus =
|
||||
mapWebhookStatusToWhatsAppStatus(rawStatus);
|
||||
|
||||
const statusMessage: string | undefined = buildStatusMessage(statusPayload);
|
||||
|
||||
const updateResult: number = await WhatsAppLogService.updateOneBy({
|
||||
query: {
|
||||
whatsAppMessageId: messageId,
|
||||
},
|
||||
data: {
|
||||
status: derivedStatus,
|
||||
...(statusMessage ? { statusMessage } : {}),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (updateResult === 0) {
|
||||
logger.warn(
|
||||
`[Meta WhatsApp Webhook] No WhatsApp log found for message id ${messageId}. Payload: ${JSON.stringify(statusPayload)}`,
|
||||
);
|
||||
} else {
|
||||
logger.debug(
|
||||
`[Meta WhatsApp Webhook] Updated WhatsApp log for message id ${messageId} with status ${derivedStatus}.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const toTemplateVariables: (
|
||||
rawVariables: JSONObject | undefined,
|
||||
) => Record<string, string> | undefined = (
|
||||
rawVariables: JSONObject | undefined,
|
||||
): Record<string, string> | undefined => {
|
||||
if (!rawVariables) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
for (const key of Object.keys(rawVariables)) {
|
||||
const value: unknown = rawVariables[key];
|
||||
if (value !== null && value !== undefined) {
|
||||
result[key] = String(value);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
};
|
||||
|
||||
router.post(
|
||||
"/send",
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
const body: JSONObject = req.body as JSONObject;
|
||||
|
||||
if (!body["to"]) {
|
||||
throw new BadDataException("`to` phone number is required");
|
||||
}
|
||||
|
||||
const toPhone: Phone = new Phone(body["to"] as string);
|
||||
|
||||
const message: WhatsAppMessage = {
|
||||
to: toPhone,
|
||||
body: (body["body"] as string) || "",
|
||||
templateKey: (body["templateKey"] as string) || undefined,
|
||||
templateVariables: toTemplateVariables(
|
||||
body["templateVariables"] as JSONObject | undefined,
|
||||
),
|
||||
templateLanguageCode:
|
||||
(body["templateLanguageCode"] as string) || undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
await WhatsAppService.sendWhatsApp(message, {
|
||||
projectId: body["projectId"]
|
||||
? new ObjectID(body["projectId"] as string)
|
||||
: undefined,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId: body["userOnCallLogTimelineId"]
|
||||
? new ObjectID(body["userOnCallLogTimelineId"] as string)
|
||||
: undefined,
|
||||
incidentId: body["incidentId"]
|
||||
? new ObjectID(body["incidentId"] as string)
|
||||
: undefined,
|
||||
alertId: body["alertId"]
|
||||
? new ObjectID(body["alertId"] as string)
|
||||
: undefined,
|
||||
scheduledMaintenanceId: body["scheduledMaintenanceId"]
|
||||
? new ObjectID(body["scheduledMaintenanceId"] as string)
|
||||
: undefined,
|
||||
statusPageId: body["statusPageId"]
|
||||
? new ObjectID(body["statusPageId"] as string)
|
||||
: undefined,
|
||||
statusPageAnnouncementId: body["statusPageAnnouncementId"]
|
||||
? new ObjectID(body["statusPageAnnouncementId"] as string)
|
||||
: undefined,
|
||||
userId: body["userId"]
|
||||
? new ObjectID(body["userId"] as string)
|
||||
: undefined,
|
||||
onCallPolicyId: body["onCallPolicyId"]
|
||||
? new ObjectID(body["onCallPolicyId"] as string)
|
||||
: undefined,
|
||||
onCallPolicyEscalationRuleId: body["onCallPolicyEscalationRuleId"]
|
||||
? new ObjectID(body["onCallPolicyEscalationRuleId"] as string)
|
||||
: undefined,
|
||||
onCallDutyPolicyExecutionLogTimelineId: body[
|
||||
"onCallDutyPolicyExecutionLogTimelineId"
|
||||
]
|
||||
? new ObjectID(
|
||||
body["onCallDutyPolicyExecutionLogTimelineId"] as string,
|
||||
)
|
||||
: undefined,
|
||||
onCallScheduleId: body["onCallScheduleId"]
|
||||
? new ObjectID(body["onCallScheduleId"] as string)
|
||||
: undefined,
|
||||
teamId: body["teamId"]
|
||||
? new ObjectID(body["teamId"] as string)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.get("/webhook", async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const mode: string | undefined = req.query["hub.mode"]
|
||||
? String(req.query["hub.mode"])
|
||||
: undefined;
|
||||
const verifyToken: string | undefined = req.query["hub.verify_token"]
|
||||
? String(req.query["hub.verify_token"])
|
||||
: undefined;
|
||||
const challenge: string | undefined = req.query["hub.challenge"]
|
||||
? String(req.query["hub.challenge"])
|
||||
: undefined;
|
||||
|
||||
if (mode === "subscribe" && challenge) {
|
||||
const globalConfigTokenResponse: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
metaWhatsAppWebhookVerifyToken: true,
|
||||
},
|
||||
});
|
||||
|
||||
const configuredVerifyToken: string | undefined =
|
||||
globalConfigTokenResponse?.metaWhatsAppWebhookVerifyToken?.trim() ||
|
||||
undefined;
|
||||
|
||||
if (!configuredVerifyToken) {
|
||||
logger.error(
|
||||
"Meta WhatsApp webhook verify token is not configured. Rejecting verification request.",
|
||||
);
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if (verifyToken === configuredVerifyToken) {
|
||||
res.status(200).send(challenge);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
"Meta WhatsApp webhook verification failed due to token mismatch.",
|
||||
);
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
res.sendStatus(400);
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/webhook",
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body as JSONObject;
|
||||
|
||||
if (
|
||||
(body["object"] as string | undefined) !== "whatsapp_business_account"
|
||||
) {
|
||||
logger.debug(
|
||||
`[Meta WhatsApp Webhook] Received event for unsupported object: ${JSON.stringify(body)}`,
|
||||
);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
|
||||
const entries: JSONArray | undefined = body["entry"] as
|
||||
| JSONArray
|
||||
| undefined;
|
||||
|
||||
if (!Array.isArray(entries)) {
|
||||
logger.warn(
|
||||
`[Meta WhatsApp Webhook] Payload did not include entries array. Payload: ${JSON.stringify(body)}`,
|
||||
);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
|
||||
const statusUpdatePromises: Array<Promise<void>> = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryObject: JSONObject = entry as JSONObject;
|
||||
const changes: JSONArray | undefined =
|
||||
(entryObject["changes"] as JSONArray | undefined) || undefined;
|
||||
|
||||
if (!Array.isArray(changes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const change of changes) {
|
||||
const changeObject: JSONObject = change as JSONObject;
|
||||
const value: JSONObject | undefined =
|
||||
(changeObject["value"] as JSONObject | undefined) || undefined;
|
||||
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const statuses: JSONArray | undefined =
|
||||
(value["statuses"] as JSONArray | undefined) || undefined;
|
||||
|
||||
if (Array.isArray(statuses)) {
|
||||
for (const statusItem of statuses) {
|
||||
statusUpdatePromises.push(
|
||||
updateWhatsAppLogStatus(statusItem as JSONObject),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (statusUpdatePromises.length > 0) {
|
||||
await Promise.allSettled(statusUpdatePromises);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/test",
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body as JSONObject;
|
||||
|
||||
if (!body["toPhone"]) {
|
||||
throw new BadDataException("toPhone is required");
|
||||
}
|
||||
|
||||
const toPhone: Phone = new Phone(body["toPhone"] as string);
|
||||
|
||||
const templateKey: WhatsAppTemplateId =
|
||||
WhatsAppTemplateIds.TestNotification;
|
||||
|
||||
const templateLanguageCode: string =
|
||||
WhatsAppTemplateLanguage[templateKey] || "en";
|
||||
|
||||
const message: WhatsAppMessage = {
|
||||
to: toPhone,
|
||||
body: "",
|
||||
templateKey,
|
||||
templateVariables: undefined,
|
||||
templateLanguageCode,
|
||||
};
|
||||
|
||||
try {
|
||||
await WhatsAppService.sendWhatsApp(message, {
|
||||
projectId: body["projectId"]
|
||||
? new ObjectID(body["projectId"] as string)
|
||||
: undefined,
|
||||
isSensitive: false,
|
||||
});
|
||||
} catch (err) {
|
||||
const errorMsg: string =
|
||||
err instanceof Error && err.message
|
||||
? err.message
|
||||
: "Failed to send test WhatsApp message.";
|
||||
|
||||
throw new BadDataException(errorMsg);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -12,6 +12,8 @@ import Phone from "Common/Types/Phone";
|
||||
|
||||
type GetGlobalSMTPConfig = () => Promise<EmailServer | null>;
|
||||
|
||||
export const DEFAULT_META_WHATSAPP_API_VERSION: string = "v23.0";
|
||||
|
||||
export const getGlobalSMTPConfig: GetGlobalSMTPConfig =
|
||||
async (): Promise<EmailServer | null> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
@@ -222,6 +224,83 @@ export const SMSHighRiskCostInCents: number = process.env[
|
||||
? parseInt(process.env["SMS_HIGH_RISK_COST_IN_CENTS"])
|
||||
: 0;
|
||||
|
||||
export interface MetaWhatsAppConfig {
|
||||
accessToken: string;
|
||||
phoneNumberId: string;
|
||||
businessAccountId?: string | undefined;
|
||||
appId?: string | undefined;
|
||||
appSecret?: string | undefined;
|
||||
apiVersion?: string | undefined;
|
||||
}
|
||||
|
||||
type GetMetaWhatsAppConfigFunction = () => Promise<MetaWhatsAppConfig>;
|
||||
|
||||
export const getMetaWhatsAppConfig: GetMetaWhatsAppConfigFunction =
|
||||
async (): Promise<MetaWhatsAppConfig> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
metaWhatsAppAccessToken: true,
|
||||
metaWhatsAppPhoneNumberId: true,
|
||||
metaWhatsAppBusinessAccountId: true,
|
||||
metaWhatsAppAppId: true,
|
||||
metaWhatsAppAppSecret: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!globalConfig) {
|
||||
throw new BadDataException("Global Config not found");
|
||||
}
|
||||
|
||||
const accessToken: string | undefined =
|
||||
globalConfig.metaWhatsAppAccessToken?.trim();
|
||||
const phoneNumberId: string | undefined =
|
||||
globalConfig.metaWhatsAppPhoneNumberId?.trim();
|
||||
|
||||
if (!accessToken) {
|
||||
throw new BadDataException(
|
||||
"Meta WhatsApp access token not configured. Please set it in the Admin Dashboard: " +
|
||||
AdminDashboardClientURL.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
if (!phoneNumberId) {
|
||||
throw new BadDataException(
|
||||
"Meta WhatsApp phone number ID not configured. Please set it in the Admin Dashboard: " +
|
||||
AdminDashboardClientURL.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
const businessAccountId: string | undefined =
|
||||
globalConfig.metaWhatsAppBusinessAccountId?.trim() || undefined;
|
||||
const appId: string | undefined =
|
||||
globalConfig.metaWhatsAppAppId?.trim() || undefined;
|
||||
const appSecret: string | undefined =
|
||||
globalConfig.metaWhatsAppAppSecret?.trim() || undefined;
|
||||
const apiVersion: string = DEFAULT_META_WHATSAPP_API_VERSION;
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
phoneNumberId,
|
||||
businessAccountId,
|
||||
appId,
|
||||
appSecret,
|
||||
apiVersion,
|
||||
};
|
||||
};
|
||||
|
||||
export const WhatsAppTextDefaultCostInCents: number = process.env[
|
||||
"WHATSAPP_TEXT_DEFAULT_COST_IN_CENTS"
|
||||
]
|
||||
? parseInt(process.env["WHATSAPP_TEXT_DEFAULT_COST_IN_CENTS"])
|
||||
: 0;
|
||||
|
||||
export const CallHighRiskCostInCentsPerMinute: number = process.env[
|
||||
"CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE"
|
||||
]
|
||||
|
||||
@@ -2,6 +2,7 @@ import CallAPI from "./API/Call";
|
||||
// API
|
||||
import MailAPI from "./API/Mail";
|
||||
import SmsAPI from "./API/SMS";
|
||||
import WhatsAppAPI from "./API/WhatsApp";
|
||||
import PushNotificationAPI from "./API/PushNotification";
|
||||
import SMTPConfigAPI from "./API/SMTPConfig";
|
||||
import "./Utils/Handlebars";
|
||||
@@ -16,6 +17,7 @@ const NotificationFeatureSet: FeatureSet = {
|
||||
|
||||
app.use([`/${APP_NAME}/email`, "/email"], MailAPI);
|
||||
app.use([`/${APP_NAME}/sms`, "/sms"], SmsAPI);
|
||||
app.use([`/${APP_NAME}/whatsapp`, "/whatsapp"], WhatsAppAPI);
|
||||
app.use([`/${APP_NAME}/push`, "/push"], PushNotificationAPI);
|
||||
app.use([`/${APP_NAME}/call`, "/call"], CallAPI);
|
||||
app.use([`/${APP_NAME}/smtp-config`, "/smtp-config"], SMTPConfigAPI);
|
||||
|
||||
@@ -47,9 +47,11 @@ function extractSayMessagesFromCallRequest(callRequest: CallRequest): string {
|
||||
if ((item as GatherInput).introMessage) {
|
||||
sayMessages.push((item as GatherInput).introMessage);
|
||||
}
|
||||
// NOTE: Excluding noInputMessage and onInputCallRequest messages from summary
|
||||
// as they contain system responses like "Good bye", "Invalid input", "You have acknowledged"
|
||||
// which should not be included in the call summary according to user requirements
|
||||
/*
|
||||
* NOTE: Excluding noInputMessage and onInputCallRequest messages from summary
|
||||
* as they contain system responses like "Good bye", "Invalid input", "You have acknowledged"
|
||||
* which should not be included in the call summary according to user requirements
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,11 +37,49 @@ class TransporterPool {
|
||||
private static semaphore: Map<string, number> = new Map();
|
||||
private static readonly MAX_CONCURRENT_CONNECTIONS = 100;
|
||||
|
||||
private static resolveConnectionSettings(emailServer: EmailServer): {
|
||||
portNumber: number;
|
||||
wantsSecureConnection: boolean;
|
||||
secureConnection: boolean;
|
||||
requireTLS: boolean;
|
||||
mode: "implicit-tls" | "starttls" | "plain";
|
||||
} {
|
||||
const portNumber: number = emailServer.port.toNumber();
|
||||
const wantsSecureConnection: boolean = emailServer.secure;
|
||||
const isImplicitTLSPort: boolean = portNumber === 465;
|
||||
|
||||
const secureConnection: boolean = isImplicitTLSPort;
|
||||
const requireTLS: boolean = wantsSecureConnection && !isImplicitTLSPort;
|
||||
|
||||
let mode: "implicit-tls" | "starttls" | "plain" = "plain";
|
||||
|
||||
if (secureConnection) {
|
||||
mode = "implicit-tls";
|
||||
} else if (requireTLS) {
|
||||
mode = "starttls";
|
||||
}
|
||||
|
||||
return {
|
||||
portNumber,
|
||||
wantsSecureConnection,
|
||||
secureConnection,
|
||||
requireTLS,
|
||||
mode,
|
||||
};
|
||||
}
|
||||
|
||||
private static getPoolKey(emailServer: EmailServer): string {
|
||||
const { portNumber, mode } = this.resolveConnectionSettings(emailServer);
|
||||
const username: string = emailServer.username || "noauth";
|
||||
|
||||
return `${emailServer.host.toString()}:${portNumber}:${username}:${mode}`;
|
||||
}
|
||||
|
||||
public static getTransporter(
|
||||
emailServer: EmailServer,
|
||||
options: { timeout?: number | undefined },
|
||||
): Transporter {
|
||||
const key: string = `${emailServer.host.toString()}:${emailServer.port.toNumber()}:${emailServer.username || "noauth"}`;
|
||||
const key: string = this.getPoolKey(emailServer);
|
||||
|
||||
if (!this.pools.has(key)) {
|
||||
const transporter: Transporter = this.createTransporter(
|
||||
@@ -59,9 +97,12 @@ class TransporterPool {
|
||||
emailServer: EmailServer,
|
||||
options: { timeout?: number | undefined },
|
||||
): Transporter {
|
||||
const { portNumber, wantsSecureConnection, secureConnection, requireTLS } =
|
||||
this.resolveConnectionSettings(emailServer);
|
||||
|
||||
let tlsOptions: tls.ConnectionOptions | undefined = undefined;
|
||||
|
||||
if (!emailServer.secure) {
|
||||
if (!wantsSecureConnection) {
|
||||
tlsOptions = {
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
@@ -69,8 +110,9 @@ class TransporterPool {
|
||||
|
||||
return nodemailer.createTransport({
|
||||
host: emailServer.host.toString(),
|
||||
port: emailServer.port.toNumber(),
|
||||
secure: emailServer.secure,
|
||||
port: portNumber,
|
||||
secure: secureConnection,
|
||||
requireTLS,
|
||||
tls: tlsOptions,
|
||||
auth:
|
||||
emailServer.username && emailServer.password
|
||||
@@ -88,7 +130,7 @@ class TransporterPool {
|
||||
public static async acquireConnection(
|
||||
emailServer: EmailServer,
|
||||
): Promise<void> {
|
||||
const key: string = `${emailServer.host.toString()}:${emailServer.port.toNumber()}:${emailServer.username || "noauth"}`;
|
||||
const key: string = this.getPoolKey(emailServer);
|
||||
|
||||
while ((this.semaphore.get(key) || 0) >= this.MAX_CONCURRENT_CONNECTIONS) {
|
||||
await new Promise<void>((resolve: () => void) => {
|
||||
@@ -100,7 +142,7 @@ class TransporterPool {
|
||||
}
|
||||
|
||||
public static releaseConnection(emailServer: EmailServer): void {
|
||||
const key: string = `${emailServer.host.toString()}:${emailServer.port.toNumber()}:${emailServer.username || "noauth"}`;
|
||||
const key: string = this.getPoolKey(emailServer);
|
||||
const current: number = this.semaphore.get(key) || 0;
|
||||
this.semaphore.set(key, Math.max(0, current - 1));
|
||||
}
|
||||
|
||||
497
App/FeatureSet/Notification/Services/WhatsAppService.ts
Normal file
497
App/FeatureSet/Notification/Services/WhatsAppService.ts
Normal file
@@ -0,0 +1,497 @@
|
||||
import {
|
||||
WhatsAppTextDefaultCostInCents,
|
||||
getMetaWhatsAppConfig,
|
||||
MetaWhatsAppConfig,
|
||||
DEFAULT_META_WHATSAPP_API_VERSION,
|
||||
} from "../Config";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import UserNotificationStatus from "Common/Types/UserNotification/UserNotificationStatus";
|
||||
import WhatsAppMessage from "Common/Types/WhatsApp/WhatsAppMessage";
|
||||
import WhatsAppStatus from "Common/Types/WhatsAppStatus";
|
||||
import {
|
||||
AuthenticationTemplates,
|
||||
WhatsAppTemplateId,
|
||||
} from "Common/Types/WhatsApp/WhatsAppTemplates";
|
||||
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import NotificationService from "Common/Server/Services/NotificationService";
|
||||
import ProjectService from "Common/Server/Services/ProjectService";
|
||||
import UserOnCallLogTimelineService from "Common/Server/Services/UserOnCallLogTimelineService";
|
||||
import WhatsAppLogService from "Common/Server/Services/WhatsAppLogService";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Project from "Common/Models/DatabaseModels/Project";
|
||||
import WhatsAppLog from "Common/Models/DatabaseModels/WhatsAppLog";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import Protocol from "Common/Types/API/Protocol";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
|
||||
const SENSITIVE_MESSAGE_PLACEHOLDER: string =
|
||||
"This message is sensitive and is not logged";
|
||||
|
||||
export default class WhatsAppService {
|
||||
public static async sendWhatsApp(
|
||||
message: WhatsAppMessage,
|
||||
options: {
|
||||
projectId?: ObjectID | undefined;
|
||||
isSensitive?: boolean | undefined;
|
||||
userOnCallLogTimelineId?: ObjectID | undefined;
|
||||
incidentId?: ObjectID | undefined;
|
||||
alertId?: ObjectID | undefined;
|
||||
scheduledMaintenanceId?: ObjectID | undefined;
|
||||
statusPageId?: ObjectID | undefined;
|
||||
statusPageAnnouncementId?: ObjectID | undefined;
|
||||
userId?: ObjectID | undefined;
|
||||
onCallPolicyId?: ObjectID | undefined;
|
||||
onCallPolicyEscalationRuleId?: ObjectID | undefined;
|
||||
onCallDutyPolicyExecutionLogTimelineId?: ObjectID | undefined;
|
||||
onCallScheduleId?: ObjectID | undefined;
|
||||
teamId?: ObjectID | undefined;
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
let sendError: Error | null = null;
|
||||
const whatsAppLog: WhatsAppLog = new WhatsAppLog();
|
||||
|
||||
try {
|
||||
if (!message.to) {
|
||||
throw new BadDataException(
|
||||
"WhatsApp recipient phone number is required",
|
||||
);
|
||||
}
|
||||
|
||||
if (!message.body && !message.templateKey) {
|
||||
throw new BadDataException(
|
||||
"Either WhatsApp message body or template key must be provided",
|
||||
);
|
||||
}
|
||||
|
||||
const isSensitiveMessage: boolean = Boolean(options.isSensitive);
|
||||
const messageSummary: string = isSensitiveMessage
|
||||
? SENSITIVE_MESSAGE_PLACEHOLDER
|
||||
: message.body ||
|
||||
(message.templateKey
|
||||
? `Template: ${message.templateKey}${
|
||||
message.templateVariables
|
||||
? " Variables: " + JSON.stringify(message.templateVariables)
|
||||
: ""
|
||||
}`
|
||||
: "");
|
||||
|
||||
whatsAppLog.toNumber = message.to;
|
||||
whatsAppLog.messageText = messageSummary;
|
||||
whatsAppLog.whatsAppCostInUSDCents = 0;
|
||||
|
||||
if (options.projectId) {
|
||||
whatsAppLog.projectId = options.projectId;
|
||||
}
|
||||
|
||||
if (options.incidentId) {
|
||||
whatsAppLog.incidentId = options.incidentId;
|
||||
}
|
||||
|
||||
if (options.alertId) {
|
||||
whatsAppLog.alertId = options.alertId;
|
||||
}
|
||||
|
||||
if (options.scheduledMaintenanceId) {
|
||||
whatsAppLog.scheduledMaintenanceId = options.scheduledMaintenanceId;
|
||||
}
|
||||
|
||||
if (options.statusPageId) {
|
||||
whatsAppLog.statusPageId = options.statusPageId;
|
||||
}
|
||||
|
||||
if (options.statusPageAnnouncementId) {
|
||||
whatsAppLog.statusPageAnnouncementId = options.statusPageAnnouncementId;
|
||||
}
|
||||
|
||||
if (options.userId) {
|
||||
whatsAppLog.userId = options.userId;
|
||||
}
|
||||
|
||||
if (options.teamId) {
|
||||
whatsAppLog.teamId = options.teamId;
|
||||
}
|
||||
|
||||
if (options.onCallPolicyId) {
|
||||
whatsAppLog.onCallDutyPolicyId = options.onCallPolicyId;
|
||||
}
|
||||
|
||||
if (options.onCallPolicyEscalationRuleId) {
|
||||
whatsAppLog.onCallDutyPolicyEscalationRuleId =
|
||||
options.onCallPolicyEscalationRuleId;
|
||||
}
|
||||
|
||||
if (options.onCallScheduleId) {
|
||||
whatsAppLog.onCallDutyPolicyScheduleId = options.onCallScheduleId;
|
||||
}
|
||||
|
||||
const config: MetaWhatsAppConfig = await getMetaWhatsAppConfig();
|
||||
|
||||
let messageCost: number = 0;
|
||||
const shouldChargeForMessage: boolean = IsBillingEnabled;
|
||||
|
||||
if (shouldChargeForMessage) {
|
||||
messageCost = WhatsAppTextDefaultCostInCents / 100;
|
||||
}
|
||||
|
||||
let project: Project | null = null;
|
||||
|
||||
if (options.projectId) {
|
||||
project = await ProjectService.findOneById({
|
||||
id: options.projectId,
|
||||
select: {
|
||||
smsOrCallCurrentBalanceInUSDCents: true,
|
||||
lowCallAndSMSBalanceNotificationSentToOwners: true,
|
||||
name: true,
|
||||
notEnabledSmsOrCallNotificationSentToOwners: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
whatsAppLog.status = WhatsAppStatus.Error;
|
||||
whatsAppLog.statusMessage = `Project ${options.projectId.toString()} not found.`;
|
||||
logger.error(whatsAppLog.statusMessage);
|
||||
await WhatsAppLogService.create({
|
||||
data: whatsAppLog,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldChargeForMessage) {
|
||||
let updatedBalance: number =
|
||||
project.smsOrCallCurrentBalanceInUSDCents || 0;
|
||||
|
||||
try {
|
||||
updatedBalance = await NotificationService.rechargeIfBalanceIsLow(
|
||||
project.id!,
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
}
|
||||
|
||||
project.smsOrCallCurrentBalanceInUSDCents = updatedBalance;
|
||||
|
||||
if (!project.smsOrCallCurrentBalanceInUSDCents) {
|
||||
whatsAppLog.status = WhatsAppStatus.LowBalance;
|
||||
whatsAppLog.statusMessage = `Project ${options.projectId.toString()} does not have enough balance for WhatsApp messages.`;
|
||||
logger.error(whatsAppLog.statusMessage);
|
||||
|
||||
await WhatsAppLogService.create({
|
||||
data: whatsAppLog,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project.lowCallAndSMSBalanceNotificationSentToOwners) {
|
||||
await ProjectService.updateOneById({
|
||||
id: project.id!,
|
||||
data: {
|
||||
lowCallAndSMSBalanceNotificationSentToOwners: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
await ProjectService.sendEmailToProjectOwners(
|
||||
project.id!,
|
||||
`Low WhatsApp message balance for ${project.name || ""}`,
|
||||
`We tried to send a WhatsApp message to ${message.to.toString()} with message:<br/><br/>${messageSummary}<br/><br/>The message was not sent because your project does not have enough balance for WhatsApp messages. Current balance is ${
|
||||
(project.smsOrCallCurrentBalanceInUSDCents || 0) / 100
|
||||
} USD. Required balance for this message is ${messageCost} USD. Please enable auto recharge or recharge manually.`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (project.smsOrCallCurrentBalanceInUSDCents < messageCost * 100) {
|
||||
whatsAppLog.status = WhatsAppStatus.LowBalance;
|
||||
whatsAppLog.statusMessage = `Project does not have enough balance to send WhatsApp message. Current balance is ${
|
||||
project.smsOrCallCurrentBalanceInUSDCents / 100
|
||||
} USD. Required balance is ${messageCost} USD.`;
|
||||
logger.error(whatsAppLog.statusMessage);
|
||||
|
||||
await WhatsAppLogService.create({
|
||||
data: whatsAppLog,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project.lowCallAndSMSBalanceNotificationSentToOwners) {
|
||||
await ProjectService.updateOneById({
|
||||
id: project.id!,
|
||||
data: {
|
||||
lowCallAndSMSBalanceNotificationSentToOwners: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
await ProjectService.sendEmailToProjectOwners(
|
||||
project.id!,
|
||||
`Low WhatsApp message balance for ${project.name || ""}`,
|
||||
`We tried to send a WhatsApp message to ${message.to.toString()} with message:<br/><br/>${messageSummary}<br/><br/>The message was not sent because your project does not have enough balance for WhatsApp messages. Current balance is ${
|
||||
project.smsOrCallCurrentBalanceInUSDCents / 100
|
||||
} USD. Required balance is ${messageCost} USD. Please enable auto recharge or recharge manually.`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const payload: JSONObject = {
|
||||
messaging_product: "whatsapp",
|
||||
recipient_type: "individual",
|
||||
to: message.to.toString(),
|
||||
} as JSONObject;
|
||||
|
||||
if (!message.templateKey) {
|
||||
throw new BadDataException("WhatsApp message template key is required");
|
||||
}
|
||||
|
||||
if (message.templateKey) {
|
||||
const template: JSONObject = {
|
||||
name: message.templateKey,
|
||||
language: {
|
||||
code: message.templateLanguageCode || "en",
|
||||
},
|
||||
} as JSONObject;
|
||||
|
||||
const components: JSONArray = [];
|
||||
|
||||
if (
|
||||
message.templateVariables &&
|
||||
Object.keys(message.templateVariables).length > 0
|
||||
) {
|
||||
const parameters: JSONArray = [];
|
||||
|
||||
for (const [key, value] of Object.entries(
|
||||
message.templateVariables,
|
||||
)) {
|
||||
parameters.push({
|
||||
type: "text",
|
||||
parameter_name: key,
|
||||
text: value,
|
||||
} as JSONObject);
|
||||
}
|
||||
|
||||
if (parameters.length > 0) {
|
||||
components.push({
|
||||
type: "body",
|
||||
parameters,
|
||||
} as JSONObject);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if this is an authentication template
|
||||
* Authentication templates may have special requirements including button components
|
||||
*/
|
||||
const isAuthTemplate: boolean = AuthenticationTemplates.has(
|
||||
message.templateKey as WhatsAppTemplateId,
|
||||
);
|
||||
|
||||
if (isAuthTemplate) {
|
||||
logger.info(
|
||||
`Sending authentication template: ${message.templateKey}`,
|
||||
);
|
||||
|
||||
/*
|
||||
* Authentication templates in WhatsApp may have a button component for "Copy Code"
|
||||
* If the template was created with a button, we need to provide button parameters
|
||||
*/
|
||||
if (message.templateVariables) {
|
||||
const otpCode: string | undefined =
|
||||
message.templateVariables["1"] ||
|
||||
message.templateVariables["otp"] ||
|
||||
message.templateVariables["code"];
|
||||
|
||||
if (otpCode) {
|
||||
/*
|
||||
* Add button component - the index should match the button position in the template
|
||||
* Usually authentication templates have the button as the first (and only) button
|
||||
*/
|
||||
components.push({
|
||||
type: "button",
|
||||
sub_type: "url",
|
||||
index: 0,
|
||||
parameters: [
|
||||
{
|
||||
type: "text",
|
||||
text: otpCode,
|
||||
},
|
||||
],
|
||||
} as JSONObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (components.length > 0) {
|
||||
template["components"] = components;
|
||||
}
|
||||
|
||||
payload["type"] = "template";
|
||||
payload["template"] = template;
|
||||
} else {
|
||||
payload["type"] = "text";
|
||||
payload["text"] = {
|
||||
body: message.body || "",
|
||||
} as JSONObject;
|
||||
}
|
||||
|
||||
const apiVersion: string =
|
||||
config.apiVersion?.trim() || DEFAULT_META_WHATSAPP_API_VERSION;
|
||||
|
||||
const url: URL = new URL(
|
||||
Protocol.HTTPS,
|
||||
"graph.facebook.com",
|
||||
new Route(`${apiVersion}/${config.phoneNumberId}/messages`),
|
||||
);
|
||||
|
||||
logger.debug(`WhatsApp API request: ${JSON.stringify(payload)}`);
|
||||
|
||||
const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
||||
await API.post<JSONObject>({
|
||||
url,
|
||||
data: payload,
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
logger.error("Failed to send WhatsApp message.");
|
||||
logger.error(response);
|
||||
const responseDataAsJSONObject: JSONObject = response.data;
|
||||
const responseJsonAsJSONObject: JSONObject | undefined =
|
||||
(response.jsonData as JSONObject | undefined) || undefined;
|
||||
|
||||
// Log full error details for debugging
|
||||
const errorObject: JSONObject | undefined =
|
||||
(responseDataAsJSONObject["error"] as JSONObject | undefined) ||
|
||||
(responseJsonAsJSONObject?.["error"] as JSONObject | undefined);
|
||||
|
||||
if (errorObject) {
|
||||
logger.error("WhatsApp API Error Details:");
|
||||
logger.error(JSON.stringify(errorObject, null, 2));
|
||||
}
|
||||
|
||||
const detailedErrorMessage: string | undefined =
|
||||
((responseDataAsJSONObject["error"] as JSONObject | undefined)?.[
|
||||
"message"
|
||||
] as string | undefined) ||
|
||||
((responseJsonAsJSONObject?.["error"] as JSONObject | undefined)?.[
|
||||
"message"
|
||||
] as string | undefined);
|
||||
|
||||
throw new BadDataException(
|
||||
detailedErrorMessage || "Failed to send WhatsApp message.",
|
||||
);
|
||||
}
|
||||
|
||||
const responseData: JSONObject = (response.jsonData || {}) as JSONObject;
|
||||
|
||||
let messageId: string | undefined = undefined;
|
||||
const messagesArray: JSONArray | undefined =
|
||||
(responseData["messages"] as JSONArray) || undefined;
|
||||
|
||||
if (Array.isArray(messagesArray) && messagesArray.length > 0) {
|
||||
const firstMessage: JSONObject = messagesArray[0] as JSONObject;
|
||||
if (firstMessage["id"]) {
|
||||
messageId = firstMessage["id"] as string;
|
||||
}
|
||||
}
|
||||
|
||||
if (messageId) {
|
||||
whatsAppLog.whatsAppMessageId = messageId;
|
||||
}
|
||||
|
||||
whatsAppLog.status = WhatsAppStatus.Sent;
|
||||
whatsAppLog.statusMessage = messageId
|
||||
? `Message ID: ${messageId}`
|
||||
: "WhatsApp message sent successfully";
|
||||
|
||||
if (shouldChargeForMessage && project) {
|
||||
const deduction: number = Math.floor(messageCost * 100);
|
||||
whatsAppLog.whatsAppCostInUSDCents = deduction;
|
||||
|
||||
project.smsOrCallCurrentBalanceInUSDCents = Math.max(
|
||||
0,
|
||||
Math.floor(
|
||||
(project.smsOrCallCurrentBalanceInUSDCents || 0) - deduction,
|
||||
),
|
||||
);
|
||||
|
||||
await ProjectService.updateOneById({
|
||||
id: project.id!,
|
||||
data: {
|
||||
smsOrCallCurrentBalanceInUSDCents:
|
||||
project.smsOrCallCurrentBalanceInUSDCents,
|
||||
notEnabledSmsOrCallNotificationSentToOwners: false,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error("Failed to send WhatsApp message.");
|
||||
logger.error(error);
|
||||
whatsAppLog.whatsAppCostInUSDCents = 0;
|
||||
whatsAppLog.status = WhatsAppStatus.Error;
|
||||
const errorMessage: string =
|
||||
error && error.message ? error.message.toString() : `${error}`;
|
||||
whatsAppLog.statusMessage = errorMessage;
|
||||
|
||||
sendError = error;
|
||||
}
|
||||
|
||||
if (options.projectId) {
|
||||
await WhatsAppLogService.create({
|
||||
data: whatsAppLog,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.userOnCallLogTimelineId) {
|
||||
await UserOnCallLogTimelineService.updateOneById({
|
||||
id: options.userOnCallLogTimelineId,
|
||||
data: {
|
||||
status: [
|
||||
WhatsAppStatus.Success,
|
||||
WhatsAppStatus.Sent,
|
||||
WhatsAppStatus.Delivered,
|
||||
WhatsAppStatus.Read,
|
||||
].includes(whatsAppLog.status || WhatsAppStatus.Error)
|
||||
? UserNotificationStatus.Sent
|
||||
: UserNotificationStatus.Error,
|
||||
statusMessage: whatsAppLog.statusMessage,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (sendError) {
|
||||
throw sendError;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,10 @@ loadPartials().catch((err: Error) => {
|
||||
|
||||
Handlebars.registerHelper("ifCond", function (v1, v2, options) {
|
||||
if (v1 === v2) {
|
||||
//@ts-ignore
|
||||
//@ts-expect-error - Handlebars uses dynamic this context for template helpers
|
||||
return options.fn(this);
|
||||
}
|
||||
//@ts-ignore
|
||||
//@ts-expect-error - Handlebars uses dynamic this context for template helpers
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
@@ -56,9 +56,9 @@ Handlebars.registerHelper("concat", (v1: any, v2: any) => {
|
||||
|
||||
Handlebars.registerHelper("ifNotCond", function (v1, v2, options) {
|
||||
if (v1 !== v2) {
|
||||
//@ts-ignore
|
||||
//@ts-expect-error - Handlebars uses dynamic this context for template helpers
|
||||
return options.fn(this);
|
||||
}
|
||||
//@ts-ignore
|
||||
//@ts-expect-error - Handlebars uses dynamic this context for template helpers
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"watch": ["./","../Common/Server", "../Common/Types", "../Common/Utils", "../Common/Models"],
|
||||
"ext": "ts,json,tsx,env,js,jsx,hbs",
|
||||
"ext": "ts,tsx",
|
||||
"ignore": [
|
||||
"./node_modules/**",
|
||||
"./public/**",
|
||||
"./bin/**",
|
||||
"./build/**",
|
||||
"greenlock.d/*"
|
||||
],
|
||||
"watchOptions": {
|
||||
@@ -12,5 +16,5 @@
|
||||
"TS_NODE_TRANSPILE_ONLY": "1",
|
||||
"TS_NODE_FILES": "false"
|
||||
},
|
||||
"exec": "node --inspect=0.0.0.0:9229 -r ts-node/register/transpile-only Index.ts"
|
||||
"exec": "node -r ts-node/register/transpile-only Index.ts"
|
||||
}
|
||||
531
App/package-lock.json
generated
531
App/package-lock.json
generated
@@ -41,7 +41,7 @@
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.52.1",
|
||||
"@opentelemetry/api-logs": "^0.206.0",
|
||||
"@opentelemetry/context-zone": "^1.25.1",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.52.1",
|
||||
@@ -59,7 +59,9 @@
|
||||
"@opentelemetry/sdk-trace-web": "^1.25.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.26.0",
|
||||
"@remixicon/react": "^4.2.0",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
@@ -68,14 +70,17 @@
|
||||
"@types/web-push": "^3.6.4",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"axios": "^1.7.2",
|
||||
"bullmq": "^5.3.3",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^1.12.0",
|
||||
"botbuilder": "^4.23.3",
|
||||
"bullmq": "^5.61.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
@@ -89,11 +94,11 @@
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^6.9.10",
|
||||
"nodemailer": "^7.0.7",
|
||||
"otpauth": "^9.3.1",
|
||||
"pg": "^8.7.3",
|
||||
"playwright": "^1.50.0",
|
||||
"posthog-js": "^1.139.6",
|
||||
"playwright": "^1.56.0",
|
||||
"posthog-js": "^1.275.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
@@ -108,7 +113,7 @@
|
||||
"react-router-dom": "^6.24.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-syntax-highlighter": "^16.0.0",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"recharts": "^2.12.7",
|
||||
@@ -123,13 +128,13 @@
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm": "^0.3.26",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"universal-cookie": "^7.2.1",
|
||||
"use-async-effect": "^2.2.6",
|
||||
"uuid": "^8.3.2",
|
||||
"web-push": "^3.6.7",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
@@ -252,89 +257,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
|
||||
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.23.4",
|
||||
"chalk": "^2.4.2"
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
|
||||
@@ -510,19 +446,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
||||
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -537,109 +475,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz",
|
||||
"integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==",
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
|
||||
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.6",
|
||||
"@babel/types": "^7.23.6"
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
|
||||
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
|
||||
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
|
||||
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.5"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -810,14 +667,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/parser": "^7.27.2",
|
||||
"@babel/types": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -845,14 +703,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
|
||||
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
|
||||
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.23.4",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1744,15 +1602,17 @@
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz",
|
||||
"integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -1862,21 +1722,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -1947,6 +1809,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -2107,6 +1982,7 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@@ -2135,10 +2011,11 @@
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@@ -2200,6 +2077,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@@ -2230,6 +2108,20 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
@@ -2239,9 +2131,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
@@ -2285,6 +2178,51 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
@@ -2388,9 +2326,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -2407,10 +2346,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -2432,15 +2372,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
@@ -2451,12 +2392,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2510,14 +2454,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
|
||||
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -2532,6 +2486,19 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -2586,11 +2553,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -2641,10 +2609,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -2652,10 +2621,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -2664,9 +2637,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
|
||||
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
@@ -2825,6 +2799,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@@ -3522,7 +3497,8 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
@@ -3779,6 +3755,15 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@@ -3786,12 +3771,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.2",
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3802,6 +3788,7 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -3810,6 +3797,7 @@
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
@@ -3874,9 +3862,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "6.9.7",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz",
|
||||
"integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==",
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz",
|
||||
"integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@@ -4119,10 +4108,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@@ -4626,20 +4616,12 @@
|
||||
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
@@ -4909,9 +4891,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/xml-crypto": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.0.tgz",
|
||||
"integrity": "sha512-qVurBUOQrmvlgmZqIVBqmb06TD2a/PpEUfFPgD7BuBfjmoH4zgkqaWSIJrnymlCvM2GGt9x+XtJFA+ttoAufqg==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.1.tgz",
|
||||
"integrity": "sha512-0GUNbPtQt+PLMsC5HoZRONX+K6NBJEqpXe/lsvrFj0EqfpGPpVfJKGE7a5jCg8s2+Wkrf/2U1G41kIH+zC9eyQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.8.8",
|
||||
"xpath": "0.0.32"
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "^8.1.0",
|
||||
"Common": "file:../Common",
|
||||
|
||||
|
||||
"ejs": "^3.1.9",
|
||||
"handlebars": "^4.7.8",
|
||||
"nodemailer": "^6.9.7",
|
||||
|
||||
@@ -3,6 +3,8 @@ import Route from "../../../Types/API/Route";
|
||||
import AnalyticsTableEngine from "../../../Types/AnalyticsDatabase/AnalyticsTableEngine";
|
||||
import AnalyticsTableColumn from "../../../Types/AnalyticsDatabase/TableColumn";
|
||||
import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
|
||||
import Projection from "../../../Types/AnalyticsDatabase/Projection";
|
||||
import MaterializedView from "../../../Types/AnalyticsDatabase/MaterializedView";
|
||||
import {
|
||||
ColumnAccessControl,
|
||||
TableAccessControl,
|
||||
@@ -40,6 +42,8 @@ export default class AnalyticsBaseModel extends CommonModel {
|
||||
enableWorkflowOn?: EnableWorkflowOn | undefined;
|
||||
enableRealtimeEventsOn?: EnableRealtimeEventsOn | undefined;
|
||||
partitionKey: string;
|
||||
projections?: Array<Projection> | undefined;
|
||||
materializedViews?: Array<MaterializedView> | undefined;
|
||||
}) {
|
||||
super({
|
||||
tableColumns: data.tableColumns,
|
||||
@@ -140,6 +144,8 @@ export default class AnalyticsBaseModel extends CommonModel {
|
||||
this.crudApiPath = data.crudApiPath;
|
||||
this.enableRealtimeEventsOn = data.enableRealtimeEventsOn;
|
||||
this.partitionKey = data.partitionKey;
|
||||
this.projections = data.projections || [];
|
||||
this.materializedViews = data.materializedViews || [];
|
||||
}
|
||||
|
||||
private _enableWorkflowOn: EnableWorkflowOn | undefined;
|
||||
@@ -250,6 +256,22 @@ export default class AnalyticsBaseModel extends CommonModel {
|
||||
this._crudApiPath = v;
|
||||
}
|
||||
|
||||
private _projections: Array<Projection> = [];
|
||||
public get projections(): Array<Projection> {
|
||||
return this._projections;
|
||||
}
|
||||
public set projections(v: Array<Projection>) {
|
||||
this._projections = v;
|
||||
}
|
||||
|
||||
private _materializedViews: Array<MaterializedView> = [];
|
||||
public get materializedViews(): Array<MaterializedView> {
|
||||
return this._materializedViews;
|
||||
}
|
||||
public set materializedViews(v: Array<MaterializedView>) {
|
||||
this._materializedViews = v;
|
||||
}
|
||||
|
||||
public getTenantColumn(): AnalyticsTableColumn | null {
|
||||
const column: AnalyticsTableColumn | undefined = this.tableColumns.find(
|
||||
(column: AnalyticsTableColumn) => {
|
||||
|
||||
@@ -9,6 +9,332 @@ import { SpanStatus } from "./Span";
|
||||
|
||||
export default class ExceptionInstance extends AnalyticsBaseModel {
|
||||
public constructor() {
|
||||
const projectIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const serviceIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "serviceId",
|
||||
title: "Service ID",
|
||||
description: "ID of the Service which created the log",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const timeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "time",
|
||||
title: "Time",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const timeUnixNanoColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "timeUnixNano",
|
||||
title: "Time (in Unix Nano)",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const exceptionTypeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "exceptionType",
|
||||
title: "Exception Type",
|
||||
description: "Exception Type", // Examples: java.net.ConnectException; OSError; etc.
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const stackTraceColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "stackTrace",
|
||||
title: "Stack Trace",
|
||||
description: "Exception Stack Trace", // Examples: Division by zero; Can't convert 'int' object to str implicitly
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const messageColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "message",
|
||||
title: "Exception Message",
|
||||
description: "Exception Message", // Examples: Division by zero; Can't convert 'int' object to str implicitly
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const spanStatusCodeColumn: AnalyticsTableColumn = new AnalyticsTableColumn(
|
||||
{
|
||||
key: "spanStatusCode",
|
||||
title: "Span Status Code",
|
||||
description: "Span Status Code",
|
||||
required: false,
|
||||
type: TableColumnType.Number,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const escapedColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "escaped",
|
||||
title: "Exception Escaped",
|
||||
description: "Exception Escaped", // SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span.
|
||||
required: false,
|
||||
type: TableColumnType.Boolean,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const traceIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "traceId",
|
||||
title: "Trace ID",
|
||||
description: "ID of the trace",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const spanIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "spanId",
|
||||
title: "Span ID",
|
||||
description: "ID of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const fingerprintColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "fingerprint",
|
||||
title: "Fingerprint",
|
||||
description: "Fingerprint of the exception",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const spanNameColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "spanName",
|
||||
title: "Span Name",
|
||||
description: "Name of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const attributesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "attributes",
|
||||
title: "Attributes",
|
||||
description: "Attributes",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
super({
|
||||
tableName: "ExceptionItem",
|
||||
tableEngine: AnalyticsTableEngine.MergeTree,
|
||||
@@ -45,330 +371,22 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
|
||||
},
|
||||
crudApiPath: new Route("/exceptions"),
|
||||
tableColumns: [
|
||||
new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "serviceId",
|
||||
title: "Service ID",
|
||||
description: "ID of the Service which created the log",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "time",
|
||||
title: "Time",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "timeUnixNano",
|
||||
title: "Time (in Unix Nano)",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "exceptionType",
|
||||
title: "Exception Type",
|
||||
description: "Exception Type", // Examples: java.net.ConnectException; OSError; etc.
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "stackTrace",
|
||||
title: "Stack Trace",
|
||||
description: "Exception Stack Trace", // Examples: Division by zero; Can't convert 'int' object to str implicitly
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "message",
|
||||
title: "Exception Message",
|
||||
description: "Exception Message", // Examples: Division by zero; Can't convert 'int' object to str implicitly
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "spanStatusCode",
|
||||
title: "Span Status Code",
|
||||
description: "Span Status Code",
|
||||
required: false,
|
||||
type: TableColumnType.Number,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "escaped",
|
||||
title: "Exception Escaped",
|
||||
description: "Exception Escaped", // SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span.
|
||||
required: false,
|
||||
type: TableColumnType.Boolean,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "traceId",
|
||||
title: "Trace ID",
|
||||
description: "ID of the trace",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "spanId",
|
||||
title: "Span ID",
|
||||
description: "ID of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "fingerprint",
|
||||
title: "Fingerprint",
|
||||
description: "Fingerprint of the exception",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "spanName",
|
||||
title: "Span Name",
|
||||
description: "Name of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "attributes",
|
||||
title: "Attributes",
|
||||
description: "Attributes",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryException,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryException,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
projectIdColumn,
|
||||
serviceIdColumn,
|
||||
timeColumn,
|
||||
timeUnixNanoColumn,
|
||||
exceptionTypeColumn,
|
||||
stackTraceColumn,
|
||||
messageColumn,
|
||||
spanStatusCodeColumn,
|
||||
escapedColumn,
|
||||
traceIdColumn,
|
||||
spanIdColumn,
|
||||
fingerprintColumn,
|
||||
spanNameColumn,
|
||||
attributesColumn,
|
||||
],
|
||||
projections: [],
|
||||
sortKeys: ["projectId", "time", "serviceId", "fingerprint"],
|
||||
primaryKeys: ["projectId", "time", "serviceId", "fingerprint"],
|
||||
partitionKey: "sipHash64(projectId) % 16",
|
||||
|
||||
@@ -2,7 +2,6 @@ import AnalyticsBaseModel from "./AnalyticsBaseModel/AnalyticsBaseModel";
|
||||
import Log from "./Log";
|
||||
import Metric from "./Metric";
|
||||
import Span from "./Span";
|
||||
import TelemetryAttribute from "./TelemetryAttribute";
|
||||
import ExceptionInstance from "./ExceptionInstance";
|
||||
import MonitorLog from "./MonitorLog";
|
||||
|
||||
@@ -10,7 +9,6 @@ const AnalyticsModels: Array<{ new (): AnalyticsBaseModel }> = [
|
||||
Log,
|
||||
Span,
|
||||
Metric,
|
||||
TelemetryAttribute,
|
||||
ExceptionInstance,
|
||||
MonitorLog,
|
||||
];
|
||||
|
||||
@@ -10,6 +10,264 @@ import LogSeverity from "../../Types/Log/LogSeverity";
|
||||
|
||||
export default class Log extends AnalyticsBaseModel {
|
||||
public constructor() {
|
||||
const projectIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const serviceIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "serviceId",
|
||||
title: "Service ID",
|
||||
description: "ID of the Service which created the log",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const timeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "time",
|
||||
title: "Time",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const timeUnixNanoColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "timeUnixNano",
|
||||
title: "Time (in Unix Nano)",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const severityTextColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "severityText",
|
||||
title: "Severity Text",
|
||||
description: "Log Severity Text",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const severityNumberColumn: AnalyticsTableColumn = new AnalyticsTableColumn(
|
||||
{
|
||||
key: "severityNumber",
|
||||
title: "Severity Number",
|
||||
description: "Log Severity Number",
|
||||
required: true,
|
||||
type: TableColumnType.Number,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const attributesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "attributes",
|
||||
title: "Attributes",
|
||||
description: "Attributes",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const attributeKeysColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "attributeKeys",
|
||||
title: "Attribute Keys",
|
||||
description: "Attribute keys extracted from attributes",
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
type: TableColumnType.ArrayText,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const traceIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "traceId",
|
||||
title: "Trace ID",
|
||||
description: "ID of the trace",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const spanIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "spanId",
|
||||
title: "Span ID",
|
||||
description: "ID of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const bodyColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "body",
|
||||
title: "Log Body",
|
||||
description: "Body of the Log",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
super({
|
||||
tableName: "LogItem",
|
||||
tableEngine: AnalyticsTableEngine.MergeTree,
|
||||
@@ -43,238 +301,19 @@ export default class Log extends AnalyticsBaseModel {
|
||||
pluralName: "Logs",
|
||||
crudApiPath: new Route("/logs"),
|
||||
tableColumns: [
|
||||
new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "serviceId",
|
||||
title: "Service ID",
|
||||
description: "ID of the Service which created the log",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "time",
|
||||
title: "Time",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "timeUnixNano",
|
||||
title: "Time (in Unix Nano)",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "severityText",
|
||||
title: "Severity Text",
|
||||
description: "Log Severity Text",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "severityNumber",
|
||||
title: "Severity Number",
|
||||
description: "Log Severity Number",
|
||||
required: true,
|
||||
type: TableColumnType.Number,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "attributes",
|
||||
title: "Attributes",
|
||||
description: "Attributes",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "traceId",
|
||||
title: "Trace ID",
|
||||
description: "ID of the trace",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "spanId",
|
||||
title: "Span ID",
|
||||
description: "ID of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "body",
|
||||
title: "Log Body",
|
||||
description: "Body of the Log",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
projectIdColumn,
|
||||
serviceIdColumn,
|
||||
timeColumn,
|
||||
timeUnixNanoColumn,
|
||||
severityTextColumn,
|
||||
severityNumberColumn,
|
||||
attributesColumn,
|
||||
attributeKeysColumn,
|
||||
traceIdColumn,
|
||||
spanIdColumn,
|
||||
bodyColumn,
|
||||
],
|
||||
projections: [],
|
||||
sortKeys: ["projectId", "time", "serviceId"],
|
||||
primaryKeys: ["projectId", "time", "serviceId"],
|
||||
partitionKey: "sipHash64(projectId) % 16",
|
||||
@@ -345,6 +384,14 @@ export default class Log extends AnalyticsBaseModel {
|
||||
this.setColumnValue("attributes", v);
|
||||
}
|
||||
|
||||
public get attributeKeys(): Array<string> | undefined {
|
||||
return this.getColumnValue("attributeKeys") as Array<string> | undefined;
|
||||
}
|
||||
|
||||
public set attributeKeys(v: Array<string> | undefined) {
|
||||
this.setColumnValue("attributeKeys", v);
|
||||
}
|
||||
|
||||
public get traceId(): string | undefined {
|
||||
return this.getColumnValue("traceId") as string | undefined;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,481 @@ export enum ServiceType {
|
||||
|
||||
export default class Metric extends AnalyticsBaseModel {
|
||||
public constructor() {
|
||||
const projectIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
// this can also be the monitor id or the telemetry service id.
|
||||
const serviceIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "serviceId",
|
||||
title: "Service ID",
|
||||
description: "ID of the Service which created the Metric",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
// this can also be the monitor id or the telemetry service id.
|
||||
const serviceTypeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "serviceType",
|
||||
title: "Service Type",
|
||||
description: "Type of the service that this telemetry belongs to",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
// add name and description
|
||||
const nameColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "name",
|
||||
title: "Name",
|
||||
description: "Name of the Metric",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const aggregationTemporalityColumn: AnalyticsTableColumn =
|
||||
new AnalyticsTableColumn({
|
||||
key: "aggregationTemporality",
|
||||
title: "Aggregation Temporality",
|
||||
description: "Aggregation Temporality of this Metric",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const metricPointTypeColumn: AnalyticsTableColumn =
|
||||
new AnalyticsTableColumn({
|
||||
key: "metricPointType",
|
||||
title: "Metric Point Type",
|
||||
description: "Metric Point Type of this Metric",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
// this is end time.
|
||||
const timeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "time",
|
||||
title: "Time",
|
||||
description: "When did the Metric happen?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const startTimeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "startTime",
|
||||
title: "Start Time",
|
||||
description: "When did the Metric happen?",
|
||||
required: false,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
// end time.
|
||||
const timeUnixNanoColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "timeUnixNano",
|
||||
title: "Time (in Unix Nano)",
|
||||
description: "When did the Metric happen?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const startTimeUnixNanoColumn: AnalyticsTableColumn =
|
||||
new AnalyticsTableColumn({
|
||||
key: "startTimeUnixNano",
|
||||
title: "Start Time (in Unix Nano)",
|
||||
description: "When did the Metric happen?",
|
||||
required: false,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const attributesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "attributes",
|
||||
title: "Attributes",
|
||||
description: "Attributes",
|
||||
required: true,
|
||||
type: TableColumnType.JSON,
|
||||
defaultValue: {},
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const attributeKeysColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "attributeKeys",
|
||||
title: "Attribute Keys",
|
||||
description: "Attribute keys extracted from attributes",
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
type: TableColumnType.ArrayText,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const isMonotonicColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "isMonotonic",
|
||||
title: "Is Monotonic",
|
||||
description: "Is Monotonic",
|
||||
required: false,
|
||||
type: TableColumnType.Boolean,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const countColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "count",
|
||||
title: "Count",
|
||||
description: "Count",
|
||||
required: false,
|
||||
type: TableColumnType.Number,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const sumColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "sum",
|
||||
title: "Sum",
|
||||
description: "Sum",
|
||||
required: false,
|
||||
type: TableColumnType.Decimal,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const valueColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "value",
|
||||
title: "Value",
|
||||
description: "Value",
|
||||
required: false,
|
||||
type: TableColumnType.Decimal,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const minColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "min",
|
||||
title: "Min",
|
||||
description: "Min",
|
||||
required: false,
|
||||
type: TableColumnType.Decimal,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const maxColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "max",
|
||||
title: "Max",
|
||||
description: "Max",
|
||||
required: false,
|
||||
type: TableColumnType.Decimal,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const bucketCountsColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "bucketCounts",
|
||||
title: "Bucket Counts",
|
||||
description: "Bucket Counts",
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
type: TableColumnType.ArrayNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const explicitBoundsColumn: AnalyticsTableColumn = new AnalyticsTableColumn(
|
||||
{
|
||||
key: "explicitBounds",
|
||||
title: "Explicit Bonds",
|
||||
description: "Explicit Bonds",
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
type: TableColumnType.ArrayNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
super({
|
||||
tableName: "MetricItem",
|
||||
tableEngine: AnalyticsTableEngine.MergeTree,
|
||||
@@ -61,453 +536,28 @@ export default class Metric extends AnalyticsBaseModel {
|
||||
],
|
||||
},
|
||||
tableColumns: [
|
||||
new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
// this can also be the monitor id or the telemetry service id.
|
||||
new AnalyticsTableColumn({
|
||||
key: "serviceId",
|
||||
title: "Service ID",
|
||||
description: "ID of the Service which created the Metric",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
// this can also be the monitor id or the telemetry service id.
|
||||
new AnalyticsTableColumn({
|
||||
key: "serviceType",
|
||||
title: "Service Type",
|
||||
description: "Type of the service that this telemetry belongs to",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
// add name and description
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "name",
|
||||
title: "Name",
|
||||
description: "Name of the Metric",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "aggregationTemporality",
|
||||
title: "Aggregation Temporality",
|
||||
description: "Aggregation Temporality of this Metric",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "metricPointType",
|
||||
title: "Metric Point Type",
|
||||
description: "Metric Point Type of this Metric",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
// this is end time.
|
||||
new AnalyticsTableColumn({
|
||||
key: "time",
|
||||
title: "Time",
|
||||
description: "When did the Metric happen?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "startTime",
|
||||
title: "Start Time",
|
||||
description: "When did the Metric happen?",
|
||||
required: false,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
// end time.
|
||||
new AnalyticsTableColumn({
|
||||
key: "timeUnixNano",
|
||||
title: "Time (in Unix Nano)",
|
||||
description: "When did the Metric happen?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "startTimeUnixNano",
|
||||
title: "Start Time (in Unix Nano)",
|
||||
description: "When did the Metric happen?",
|
||||
required: false,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "attributes",
|
||||
title: "Attributes",
|
||||
description: "Attributes",
|
||||
required: true,
|
||||
type: TableColumnType.JSON,
|
||||
defaultValue: {},
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "isMonotonic",
|
||||
title: "Is Monotonic",
|
||||
description: "Is Monotonic",
|
||||
required: false,
|
||||
type: TableColumnType.Boolean,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "count",
|
||||
title: "Count",
|
||||
description: "Count",
|
||||
required: false,
|
||||
type: TableColumnType.Number,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "sum",
|
||||
title: "Sum",
|
||||
description: "Sum",
|
||||
required: false,
|
||||
type: TableColumnType.Decimal,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "value",
|
||||
title: "Value",
|
||||
description: "Value",
|
||||
required: false,
|
||||
type: TableColumnType.Decimal,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "min",
|
||||
title: "Min",
|
||||
description: "Min",
|
||||
required: false,
|
||||
type: TableColumnType.Decimal,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "max",
|
||||
title: "Max",
|
||||
description: "Max",
|
||||
required: false,
|
||||
type: TableColumnType.Decimal,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "bucketCounts",
|
||||
title: "Bucket Counts",
|
||||
description: "Bucket Counts",
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
type: TableColumnType.ArrayNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "explicitBounds",
|
||||
title: "Explicit Bonds",
|
||||
description: "Explicit Bonds",
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
type: TableColumnType.ArrayNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
projectIdColumn,
|
||||
serviceIdColumn,
|
||||
serviceTypeColumn,
|
||||
nameColumn,
|
||||
aggregationTemporalityColumn,
|
||||
metricPointTypeColumn,
|
||||
timeColumn,
|
||||
startTimeColumn,
|
||||
timeUnixNanoColumn,
|
||||
startTimeUnixNanoColumn,
|
||||
attributesColumn,
|
||||
attributeKeysColumn,
|
||||
isMonotonicColumn,
|
||||
countColumn,
|
||||
sumColumn,
|
||||
valueColumn,
|
||||
minColumn,
|
||||
maxColumn,
|
||||
bucketCountsColumn,
|
||||
explicitBoundsColumn,
|
||||
],
|
||||
projections: [],
|
||||
sortKeys: ["projectId", "time", "serviceId"],
|
||||
primaryKeys: ["projectId", "time", "serviceId"],
|
||||
partitionKey: "sipHash64(projectId) % 16",
|
||||
@@ -590,6 +640,14 @@ export default class Metric extends AnalyticsBaseModel {
|
||||
this.setColumnValue("attributes", v);
|
||||
}
|
||||
|
||||
public get attributeKeys(): Array<string> | undefined {
|
||||
return this.getColumnValue("attributeKeys") as Array<string> | undefined;
|
||||
}
|
||||
|
||||
public set attributeKeys(v: Array<string> | undefined) {
|
||||
this.setColumnValue("attributeKeys", v);
|
||||
}
|
||||
|
||||
public get startTime(): Date | undefined {
|
||||
return this.getColumnValue("startTime") as Date | undefined;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,100 @@ import Permission from "../../Types/Permission";
|
||||
|
||||
export default class MonitorLog extends AnalyticsBaseModel {
|
||||
public constructor() {
|
||||
const projectIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectMonitor,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectMonitor,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const monitorIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "monitorId",
|
||||
title: "Monitor ID",
|
||||
description: "ID of the monitor which this logs belongs to",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectMonitor,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectMonitor,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const timeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "time",
|
||||
title: "Time",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectMonitor,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectMonitor,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const logBodyColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "logBody",
|
||||
title: "Log Body",
|
||||
description: "The body of the log",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectMonitor,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectMonitor,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
super({
|
||||
tableName: "MonitorLog",
|
||||
tableEngine: AnalyticsTableEngine.MergeTree,
|
||||
@@ -42,100 +136,12 @@ export default class MonitorLog extends AnalyticsBaseModel {
|
||||
pluralName: "Monitor Logs",
|
||||
crudApiPath: new Route("/monitor-log"),
|
||||
tableColumns: [
|
||||
new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectMonitor,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectMonitor,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "monitorId",
|
||||
title: "Monitor ID",
|
||||
description: "ID of the monitor which this logs belongs to",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectMonitor,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectMonitor,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "time",
|
||||
title: "Time",
|
||||
description: "When was the log created?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectMonitor,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectMonitor,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "logBody",
|
||||
title: "Log Body",
|
||||
description: "The body of the log",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectMonitor,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectMonitor,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
projectIdColumn,
|
||||
monitorIdColumn,
|
||||
timeColumn,
|
||||
logBodyColumn,
|
||||
],
|
||||
projections: [],
|
||||
sortKeys: ["projectId", "time", "monitorId"],
|
||||
primaryKeys: ["projectId", "time", "monitorId"],
|
||||
partitionKey: "sipHash64(projectId) % 16",
|
||||
|
||||
@@ -41,6 +41,451 @@ export interface SpanLink {
|
||||
|
||||
export default class Span extends AnalyticsBaseModel {
|
||||
public constructor() {
|
||||
const projectIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const serviceIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "serviceId",
|
||||
title: "Service ID",
|
||||
description: "ID of the Service which created the log",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const startTimeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "startTime",
|
||||
title: "Start Time",
|
||||
description: "When did the span start?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const endTimeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "endTime",
|
||||
title: "End Time",
|
||||
description: "When did the span end?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const startTimeUnixNanoColumn: AnalyticsTableColumn =
|
||||
new AnalyticsTableColumn({
|
||||
key: "startTimeUnixNano",
|
||||
title: "Start Time in Unix Nano",
|
||||
description: "When did the span start?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const durationUnixNanoColumn: AnalyticsTableColumn =
|
||||
new AnalyticsTableColumn({
|
||||
key: "durationUnixNano",
|
||||
title: "Duration in Unix Nano",
|
||||
description: "How long did the span last?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const endTimeUnixNanoColumn: AnalyticsTableColumn =
|
||||
new AnalyticsTableColumn({
|
||||
key: "endTimeUnixNano",
|
||||
title: "End Time",
|
||||
description: "When did the span end?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const traceIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "traceId",
|
||||
title: "Trace ID",
|
||||
description: "ID of the trace",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const spanIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "spanId",
|
||||
title: "Span ID",
|
||||
description: "ID of the span",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const parentSpanIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "parentSpanId",
|
||||
title: "Parent Span ID",
|
||||
description: "ID of the parent span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const traceStateColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "traceState",
|
||||
title: "Trace State",
|
||||
description: "Trace State",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const attributesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "attributes",
|
||||
title: "Attributes",
|
||||
description: "Attributes",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const attributeKeysColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "attributeKeys",
|
||||
title: "Attribute Keys",
|
||||
description: "Attribute keys extracted from attributes",
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
type: TableColumnType.ArrayText,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const eventsColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "events",
|
||||
title: "Events",
|
||||
description: "Span Events",
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
type: TableColumnType.JSONArray,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const linksColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "links",
|
||||
title: "Links",
|
||||
description: "Span Links",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const statusCodeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "statusCode",
|
||||
title: "Status Code",
|
||||
description: "Status Code",
|
||||
required: false,
|
||||
type: TableColumnType.Number,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const statusMessageColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "statusMessage",
|
||||
title: "Status Message",
|
||||
description: "Status Message",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const nameColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "name",
|
||||
title: "Name",
|
||||
description: "Name of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
const kindColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
||||
key: "kind",
|
||||
title: "Kind",
|
||||
description: "Kind of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
});
|
||||
|
||||
super({
|
||||
tableName: "SpanItem",
|
||||
tableEngine: AnalyticsTableEngine.MergeTree,
|
||||
@@ -74,424 +519,27 @@ export default class Span extends AnalyticsBaseModel {
|
||||
],
|
||||
},
|
||||
tableColumns: [
|
||||
new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "serviceId",
|
||||
title: "Service ID",
|
||||
description: "ID of the Service which created the log",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "startTime",
|
||||
title: "Start Time",
|
||||
description: "When did the span start?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "endTime",
|
||||
title: "End Time",
|
||||
description: "When did the span end?",
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "startTimeUnixNano",
|
||||
title: "Start Time in Unix Nano",
|
||||
description: "When did the span start?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "durationUnixNano",
|
||||
title: "Duration in Unix Nano",
|
||||
description: "How long did the span last?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "endTimeUnixNano",
|
||||
title: "End Time",
|
||||
description: "When did the span end?",
|
||||
required: true,
|
||||
type: TableColumnType.LongNumber,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "traceId",
|
||||
title: "Trace ID",
|
||||
description: "ID of the trace",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "spanId",
|
||||
title: "Span ID",
|
||||
description: "ID of the span",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "parentSpanId",
|
||||
title: "Parent Span ID",
|
||||
description: "ID of the parent span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "traceState",
|
||||
title: "Trace State",
|
||||
description: "Trace State",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "attributes",
|
||||
title: "Attributes",
|
||||
description: "Attributes",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "events",
|
||||
title: "Events",
|
||||
description: "Span Events",
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
type: TableColumnType.JSONArray,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "links",
|
||||
title: "Links",
|
||||
description: "Span Links",
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
type: TableColumnType.JSON,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "statusCode",
|
||||
title: "Status Code",
|
||||
description: "Status Code",
|
||||
required: false,
|
||||
type: TableColumnType.Number,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "statusMessage",
|
||||
title: "Status Message",
|
||||
description: "Status Message",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "name",
|
||||
title: "Name",
|
||||
description: "Name of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "kind",
|
||||
title: "Kind",
|
||||
description: "Kind of the span",
|
||||
required: false,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
projectIdColumn,
|
||||
serviceIdColumn,
|
||||
startTimeColumn,
|
||||
endTimeColumn,
|
||||
startTimeUnixNanoColumn,
|
||||
durationUnixNanoColumn,
|
||||
endTimeUnixNanoColumn,
|
||||
traceIdColumn,
|
||||
spanIdColumn,
|
||||
parentSpanIdColumn,
|
||||
traceStateColumn,
|
||||
attributesColumn,
|
||||
attributeKeysColumn,
|
||||
eventsColumn,
|
||||
linksColumn,
|
||||
statusCodeColumn,
|
||||
statusMessageColumn,
|
||||
nameColumn,
|
||||
kindColumn,
|
||||
],
|
||||
projections: [],
|
||||
sortKeys: ["projectId", "startTime", "serviceId", "traceId"],
|
||||
primaryKeys: ["projectId", "startTime", "serviceId", "traceId"],
|
||||
partitionKey: "sipHash64(projectId) % 16",
|
||||
@@ -610,6 +658,14 @@ export default class Span extends AnalyticsBaseModel {
|
||||
this.setColumnValue("attributes", v);
|
||||
}
|
||||
|
||||
public get attributeKeys(): Array<string> | undefined {
|
||||
return this.getColumnValue("attributeKeys") as Array<string> | undefined;
|
||||
}
|
||||
|
||||
public set attributeKeys(v: Array<string> | undefined) {
|
||||
this.setColumnValue("attributeKeys", v);
|
||||
}
|
||||
|
||||
public get events(): Array<SpanEvent> | undefined {
|
||||
return this.getColumnValue("events") as Array<SpanEvent> | undefined;
|
||||
}
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
import AnalyticsBaseModel from "./AnalyticsBaseModel/AnalyticsBaseModel";
|
||||
import Route from "../../Types/API/Route";
|
||||
import AnalyticsTableEngine from "../../Types/AnalyticsDatabase/AnalyticsTableEngine";
|
||||
import AnalyticsTableColumn from "../../Types/AnalyticsDatabase/TableColumn";
|
||||
import TableColumnType from "../../Types/AnalyticsDatabase/TableColumnType";
|
||||
import TelemetryType from "../../Types/Telemetry/TelemetryType";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import Permission from "../../Types/Permission";
|
||||
|
||||
export default class TelemetryAttribute extends AnalyticsBaseModel {
|
||||
public constructor() {
|
||||
super({
|
||||
tableName: "TelemetryAttribute",
|
||||
tableEngine: AnalyticsTableEngine.MergeTree,
|
||||
singularName: "Telemetry Attribute",
|
||||
pluralName: "Telemetry Attributes",
|
||||
crudApiPath: new Route("/telemetry-attributes"),
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
Permission.ReadTelemetryServiceMetrics,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateTelemetryServiceTraces,
|
||||
Permission.CreateTelemetryServiceLog,
|
||||
Permission.CreateTelemetryServiceMetrics,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditTelemetryServiceTraces,
|
||||
Permission.EditTelemetryServiceLog,
|
||||
Permission.EditTelemetryServiceMetrics,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.DeleteTelemetryServiceTraces,
|
||||
Permission.DeleteTelemetryServiceLog,
|
||||
Permission.DeleteTelemetryServiceMetrics,
|
||||
],
|
||||
},
|
||||
tableColumns: [
|
||||
new AnalyticsTableColumn({
|
||||
key: "projectId",
|
||||
title: "Project ID",
|
||||
description: "ID of project",
|
||||
required: true,
|
||||
type: TableColumnType.ObjectID,
|
||||
isTenantId: true,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
Permission.ReadTelemetryServiceMetrics,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditTelemetryServiceTraces,
|
||||
Permission.EditTelemetryServiceLog,
|
||||
Permission.EditTelemetryServiceMetrics,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "telemetryType",
|
||||
title: "Telemetry Type",
|
||||
description: "Type of telemetry",
|
||||
required: true,
|
||||
type: TableColumnType.Text,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
Permission.ReadTelemetryServiceMetrics,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditTelemetryServiceTraces,
|
||||
Permission.EditTelemetryServiceLog,
|
||||
Permission.EditTelemetryServiceMetrics,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
|
||||
new AnalyticsTableColumn({
|
||||
key: "attributes",
|
||||
title: "Attributes",
|
||||
description: "Attributes",
|
||||
required: true,
|
||||
type: TableColumnType.JSONArray,
|
||||
accessControl: {
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadTelemetryServiceTraces,
|
||||
Permission.ReadTelemetryServiceLog,
|
||||
Permission.ReadTelemetryServiceMetrics,
|
||||
],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditTelemetryServiceTraces,
|
||||
Permission.EditTelemetryServiceLog,
|
||||
Permission.EditTelemetryServiceMetrics,
|
||||
],
|
||||
update: [],
|
||||
},
|
||||
}),
|
||||
],
|
||||
sortKeys: ["projectId", "telemetryType"],
|
||||
primaryKeys: ["projectId", "telemetryType"],
|
||||
partitionKey: "sipHash64(projectId) % 16",
|
||||
});
|
||||
}
|
||||
|
||||
public get projectId(): ObjectID | undefined {
|
||||
return this.getColumnValue("projectId") as ObjectID | undefined;
|
||||
}
|
||||
|
||||
public set projectId(v: ObjectID | undefined) {
|
||||
this.setColumnValue("projectId", v);
|
||||
}
|
||||
|
||||
public get telemetryType(): TelemetryType | undefined {
|
||||
return this.getColumnValue("telemetryType") as TelemetryType | undefined;
|
||||
}
|
||||
|
||||
public set telemetryType(v: TelemetryType | undefined) {
|
||||
this.setColumnValue("telemetryType", v);
|
||||
}
|
||||
|
||||
public get attributes(): Array<string> | undefined {
|
||||
return this.getColumnValue("attributes") as Array<string> | undefined;
|
||||
}
|
||||
|
||||
public set attributes(v: Array<string> | undefined) {
|
||||
this.setColumnValue("attributes", v);
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@ import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccess
|
||||
import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
|
||||
import ColumnLength from "../../Types/Database/ColumnLength";
|
||||
import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
import TableMetadata from "../../Types/Database/TableMetadata";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import Route from "../../Types/API/Route";
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
|
||||
@TableAccessControl({
|
||||
@@ -24,6 +26,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
icon: IconProp.Lock,
|
||||
tableDescription: "HTTP Challege for Lets Encrypt Certificates",
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/acme-challenge"))
|
||||
@Entity({
|
||||
name: "AcmeChallenge",
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
import ColorField from "../../Types/Database/ColorField";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
import TableMetadata from "../../Types/Database/TableMetadata";
|
||||
@@ -418,6 +419,7 @@ export default class AlertFeed extends BaseModel {
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@ColorField()
|
||||
@TableColumn({
|
||||
type: TableColumnType.Color,
|
||||
required: true,
|
||||
|
||||
@@ -17,7 +17,16 @@ import TenantColumn from "../../Types/Database/TenantColumn";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import Permission from "../../Types/Permission";
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
import File from "./File";
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
} from "typeorm";
|
||||
|
||||
@EnableDocumentation()
|
||||
@CanAccessIfCanReadOn("alert")
|
||||
@@ -340,6 +349,54 @@ export default class AlertInternalNote extends BaseModel {
|
||||
})
|
||||
public note?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateAlertInternalNote,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditAlertInternalNote,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.EntityArray,
|
||||
modelType: File,
|
||||
title: "Attachments",
|
||||
description: "Files attached to this note.",
|
||||
})
|
||||
@ManyToMany(
|
||||
() => {
|
||||
return File;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
},
|
||||
)
|
||||
@JoinTable({
|
||||
name: "AlertInternalNoteFile",
|
||||
joinColumn: {
|
||||
name: "alertInternalNoteId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: "fileId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
})
|
||||
public attachments?: Array<File> = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
|
||||
@@ -12,6 +12,7 @@ import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
import ColorField from "../../Types/Database/ColorField";
|
||||
import SlugifyColumn from "../../Types/Database/SlugifyColumn";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
@@ -378,6 +379,7 @@ export default class AlertSeverity extends BaseModel {
|
||||
Permission.EditAlertSeverity,
|
||||
],
|
||||
})
|
||||
@ColorField()
|
||||
@TableColumn({
|
||||
title: "Color",
|
||||
required: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
import ColorField from "../../Types/Database/ColorField";
|
||||
import SlugifyColumn from "../../Types/Database/SlugifyColumn";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
@@ -356,6 +357,7 @@ export default class AlertState extends BaseModel {
|
||||
Permission.EditAlertState,
|
||||
],
|
||||
})
|
||||
@ColorField()
|
||||
@TableColumn({
|
||||
title: "Color",
|
||||
required: true,
|
||||
|
||||
@@ -15,6 +15,7 @@ import TableColumn, {
|
||||
getTableColumns,
|
||||
} from "../../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../../Types/Database/TableColumnType";
|
||||
import { getFirstColorFieldColumn } from "../../../Types/Database/ColorField";
|
||||
import Dictionary from "../../../Types/Dictionary";
|
||||
import Email from "../../../Types/Email";
|
||||
import BadDataException from "../../../Types/Exception/BadDataException";
|
||||
@@ -123,7 +124,6 @@ export default class DatabaseBaseModel extends BaseEntity {
|
||||
public isMasterAdminApiDocs!: boolean;
|
||||
|
||||
public currentUserCanAccessColumnBy!: string | null;
|
||||
public labelsColumn!: string | null;
|
||||
public slugifyColumn!: string | null;
|
||||
public saveSlugToColumn!: string | null;
|
||||
public singularName!: string | null;
|
||||
@@ -204,6 +204,10 @@ export default class DatabaseBaseModel extends BaseEntity {
|
||||
return new Columns(Object.keys(getTableColumns(this)));
|
||||
}
|
||||
|
||||
public getFirstColorColumn(): string | null {
|
||||
return getFirstColorFieldColumn(this);
|
||||
}
|
||||
|
||||
public canQueryMultiTenant(): boolean {
|
||||
return Boolean(this.isMultiTenantRequestAllowed);
|
||||
}
|
||||
@@ -345,10 +349,6 @@ export default class DatabaseBaseModel extends BaseEntity {
|
||||
return this.currentUserCanAccessColumnBy;
|
||||
}
|
||||
|
||||
public getLabelsColumn(): string | null {
|
||||
return this.labelsColumn;
|
||||
}
|
||||
|
||||
public get id(): ObjectID | null {
|
||||
return this._id ? new ObjectID(this._id) : null;
|
||||
}
|
||||
|
||||
@@ -301,7 +301,12 @@ export default class Domain extends BaseModel {
|
||||
public deletedByUserId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectDomain,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
|
||||
104
Common/Models/DatabaseModels/EnterpriseLicense.ts
Normal file
104
Common/Models/DatabaseModels/EnterpriseLicense.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
|
||||
import Route from "../../Types/API/Route";
|
||||
import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
|
||||
import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
|
||||
import ColumnLength from "../../Types/Database/ColumnLength";
|
||||
import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
import TableMetadata from "../../Types/Database/TableMetadata";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import { Column, Entity, Index } from "typeorm";
|
||||
|
||||
@TableAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
delete: [],
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/enterprise-license"))
|
||||
@TableMetadata({
|
||||
tableName: "EnterpriseLicense",
|
||||
singularName: "Enterprise License",
|
||||
pluralName: "Enterprise Licenses",
|
||||
icon: IconProp.Lock,
|
||||
tableDescription: "Enterprise license keys issued by OneUptime.",
|
||||
})
|
||||
@Entity({
|
||||
name: "EnterpriseLicense",
|
||||
})
|
||||
export default class EnterpriseLicense extends BaseModel {
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Company Name",
|
||||
description: "Company name associated with this license.",
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public companyName?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "License Key",
|
||||
description: "Enterprise license key.",
|
||||
unique: true,
|
||||
})
|
||||
@Index({ unique: true })
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
unique: true,
|
||||
})
|
||||
public licenseKey?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.Date,
|
||||
title: "Expires At",
|
||||
description: "Expiration date of this license.",
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.Date,
|
||||
})
|
||||
public expiresAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.Number,
|
||||
title: "Annual Contract Value",
|
||||
description: "Annual contract value (in USD) for this license.",
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.Number,
|
||||
})
|
||||
public annualContractValue?: number = undefined;
|
||||
}
|
||||
@@ -262,6 +262,115 @@ export default class GlobalConfig extends GlobalConfigModel {
|
||||
})
|
||||
public twilioSecondaryPhoneNumbers?: string = undefined; // phone numbers separated by comma
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.VeryLongText,
|
||||
title: "Meta WhatsApp Access Token",
|
||||
description:
|
||||
"Access token generated from Meta for sending WhatsApp messages.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.VeryLongText,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public metaWhatsAppAccessToken?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Meta WhatsApp Phone Number ID",
|
||||
description: "The WhatsApp Business phone number ID from Meta.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public metaWhatsAppPhoneNumberId?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Meta WhatsApp Business Account ID",
|
||||
description: "Business account ID associated with your WhatsApp setup.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public metaWhatsAppBusinessAccountId?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Meta WhatsApp App ID",
|
||||
description:
|
||||
"Facebook App ID used for the WhatsApp Business Platform integration.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public metaWhatsAppAppId?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.VeryLongText,
|
||||
title: "Meta WhatsApp App Secret",
|
||||
description: "Facebook App Secret for the WhatsApp Business Platform.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.VeryLongText,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public metaWhatsAppAppSecret?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Meta WhatsApp Webhook Verify Token",
|
||||
description:
|
||||
"Verify token configured in Meta to validate webhook subscriptions.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public metaWhatsAppWebhookVerifyToken?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
@@ -388,4 +497,75 @@ export default class GlobalConfig extends GlobalConfigModel {
|
||||
transformer: Email.getDatabaseTransformer(),
|
||||
})
|
||||
public adminNotificationEmail?: Email = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Enterprise Company Name",
|
||||
description:
|
||||
"Company name associated with the validated enterprise license.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public enterpriseCompanyName?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Enterprise License Key",
|
||||
description: "Enterprise license key stored after successful validation.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public enterpriseLicenseKey?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.Date,
|
||||
title: "Enterprise License Expires At",
|
||||
description: "Expiration date of the validated enterprise license.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Date,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public enterpriseLicenseExpiresAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.VeryLongText,
|
||||
title: "Enterprise License Token",
|
||||
description: "Signed JWT returned from license validation.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.VeryLongText,
|
||||
nullable: true,
|
||||
unique: true,
|
||||
})
|
||||
public enterpriseLicenseToken?: string = undefined;
|
||||
}
|
||||
|
||||
@@ -926,6 +926,39 @@ export default class Incident extends BaseModel {
|
||||
})
|
||||
public rootCause?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectIncident,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectIncident,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditProjectIncident,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.Markdown,
|
||||
required: false,
|
||||
isDefaultValueColumn: false,
|
||||
title: "Postmortem Note",
|
||||
description: "Document the postmortem summary for this incident.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Markdown,
|
||||
nullable: true,
|
||||
})
|
||||
public postmortemNote?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
|
||||
@@ -33,6 +33,7 @@ export enum IncidentFeedEventType {
|
||||
IncidentUpdated = "IncidentUpdated",
|
||||
RootCause = "RootCause",
|
||||
RemediationNotes = "RemediationNotes",
|
||||
PostmortemNote = "PostmortemNote",
|
||||
OwnerUserRemoved = "OwnerUserRemoved",
|
||||
OwnerTeamRemoved = "OwnerTeamRemoved",
|
||||
OnCallPolicy = "OnCallPolicy",
|
||||
|
||||
@@ -17,7 +17,16 @@ import TenantColumn from "../../Types/Database/TenantColumn";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import Permission from "../../Types/Permission";
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
import File from "./File";
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
} from "typeorm";
|
||||
|
||||
@EnableDocumentation()
|
||||
@CanAccessIfCanReadOn("incident")
|
||||
@@ -340,6 +349,54 @@ export default class IncidentInternalNote extends BaseModel {
|
||||
})
|
||||
public note?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentInternalNote,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentInternalNote,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditIncidentInternalNote,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.EntityArray,
|
||||
modelType: File,
|
||||
title: "Attachments",
|
||||
description: "Files attached to this note.",
|
||||
})
|
||||
@ManyToMany(
|
||||
() => {
|
||||
return File;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
},
|
||||
)
|
||||
@JoinTable({
|
||||
name: "IncidentInternalNoteFile",
|
||||
joinColumn: {
|
||||
name: "incidentInternalNoteId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: "fileId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
})
|
||||
public attachments?: Array<File> = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
|
||||
353
Common/Models/DatabaseModels/IncidentPostmortemTemplate.ts
Normal file
353
Common/Models/DatabaseModels/IncidentPostmortemTemplate.ts
Normal file
@@ -0,0 +1,353 @@
|
||||
import Project from "./Project";
|
||||
import User from "./User";
|
||||
import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
|
||||
import Route from "../../Types/API/Route";
|
||||
import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
|
||||
import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
|
||||
import ColumnLength from "../../Types/Database/ColumnLength";
|
||||
import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
import TableMetadata from "../../Types/Database/TableMetadata";
|
||||
import TenantColumn from "../../Types/Database/TenantColumn";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import Permission from "../../Types/Permission";
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
import TableBillingAccessControl from "../../Types/Database/AccessControl/TableBillingAccessControl";
|
||||
import { PlanType } from "../../Types/Billing/SubscriptionPlan";
|
||||
|
||||
@TableBillingAccessControl({
|
||||
create: PlanType.Growth,
|
||||
read: PlanType.Growth,
|
||||
update: PlanType.Growth,
|
||||
delete: PlanType.Growth,
|
||||
})
|
||||
@EnableDocumentation()
|
||||
@TenantColumn("projectId")
|
||||
@TableAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentNoteTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentNoteTemplate,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.DeleteIncidentNoteTemplate,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditIncidentNoteTemplate,
|
||||
],
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/incident-postmortem-template"))
|
||||
@Entity({
|
||||
name: "IncidentPostmortemTemplate",
|
||||
})
|
||||
@EnableWorkflow({
|
||||
create: true,
|
||||
delete: true,
|
||||
update: true,
|
||||
read: true,
|
||||
})
|
||||
@TableMetadata({
|
||||
tableName: "IncidentPostmortemTemplate",
|
||||
singularName: "Incident Postmortem Template",
|
||||
pluralName: "Incident Postmortem Templates",
|
||||
icon: IconProp.Book,
|
||||
tableDescription: "Manage postmortem templates for your incidents",
|
||||
})
|
||||
export default class IncidentPostmortemTemplate extends BaseModel {
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentNoteTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentNoteTemplate,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "projectId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: Project,
|
||||
title: "Project",
|
||||
description: "Relation to Project Resource in which this object belongs",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return Project;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "projectId" })
|
||||
public project?: Project = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentNoteTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentNoteTemplate,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Project ID",
|
||||
description: "ID of your OneUptime Project in which this object belongs",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public projectId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentNoteTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentNoteTemplate,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditIncidentNoteTemplate,
|
||||
],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.Markdown,
|
||||
title: "Postmortem Note",
|
||||
description:
|
||||
"Markdown template used when documenting an incident postmortem.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Markdown,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
})
|
||||
public postmortemNote?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentNoteTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentNoteTemplate,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditIncidentNoteTemplate,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Name",
|
||||
description: "Name of the Postmortem Template",
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public templateName?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentNoteTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentNoteTemplate,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditIncidentNoteTemplate,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.LongText,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Template Description",
|
||||
description: "Description of the Postmortem Template",
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
})
|
||||
public templateDescription?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentNoteTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentNoteTemplate,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "createdByUserId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: User,
|
||||
title: "Created by User",
|
||||
description:
|
||||
"Relation to User who created this object (if this object was created by a User)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return User;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "SET NULL",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "createdByUserId" })
|
||||
public createdByUser?: User = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentNoteTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentNoteTemplate,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
title: "Created by User ID",
|
||||
description:
|
||||
"User ID who created this object (if this object was created by a User)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public createdByUserId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "deletedByUserId",
|
||||
type: TableColumnType.Entity,
|
||||
title: "Deleted by User",
|
||||
modelType: User,
|
||||
description:
|
||||
"Relation to User who deleted this object (if this object was deleted by a User)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return User;
|
||||
},
|
||||
{
|
||||
cascade: false,
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "SET NULL",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "deletedByUserId" })
|
||||
public deletedByUser?: User = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
title: "Deleted by User ID",
|
||||
description:
|
||||
"User ID who deleted this object (if this object was deleted by a User)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public deletedByUserId?: ObjectID = undefined;
|
||||
}
|
||||
@@ -18,7 +18,16 @@ import IconProp from "../../Types/Icon/IconProp";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import Permission from "../../Types/Permission";
|
||||
import StatusPageSubscriberNotificationStatus from "../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
import File from "./File";
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
} from "typeorm";
|
||||
|
||||
@EnableDocumentation()
|
||||
@CanAccessIfCanReadOn("incident")
|
||||
@@ -341,6 +350,54 @@ export default class IncidentPublicNote extends BaseModel {
|
||||
})
|
||||
public note?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentPublicNote,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentPublicNote,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditIncidentPublicNote,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.EntityArray,
|
||||
modelType: File,
|
||||
title: "Attachments",
|
||||
description: "Files attached to this note.",
|
||||
})
|
||||
@ManyToMany(
|
||||
() => {
|
||||
return File;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
},
|
||||
)
|
||||
@JoinTable({
|
||||
name: "IncidentPublicNoteFile",
|
||||
joinColumn: {
|
||||
name: "incidentPublicNoteId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: "fileId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
})
|
||||
public attachments?: Array<File> = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
|
||||
@@ -12,6 +12,7 @@ import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
import ColorField from "../../Types/Database/ColorField";
|
||||
import SlugifyColumn from "../../Types/Database/SlugifyColumn";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
@@ -378,6 +379,7 @@ export default class IncidentSeverity extends BaseModel {
|
||||
Permission.EditIncidentSeverity,
|
||||
],
|
||||
})
|
||||
@ColorField()
|
||||
@TableColumn({
|
||||
title: "Color",
|
||||
required: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
import ColorField from "../../Types/Database/ColorField";
|
||||
import SlugifyColumn from "../../Types/Database/SlugifyColumn";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
@@ -380,6 +381,7 @@ export default class IncidentState extends BaseModel {
|
||||
Permission.EditIncidentState,
|
||||
],
|
||||
})
|
||||
@ColorField()
|
||||
@TableColumn({
|
||||
title: "Color",
|
||||
required: true,
|
||||
|
||||
@@ -716,6 +716,77 @@ export default class IncidentTemplate extends BaseModel {
|
||||
})
|
||||
public changeMonitorStatusToId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentTemplate,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "initialIncidentStateId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: IncidentState,
|
||||
title: "Initial Incident State",
|
||||
description:
|
||||
"Relation to Incident State Object. Incidents created from this template will start in this state.",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return IncidentState;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "initialIncidentStateId" })
|
||||
public initialIncidentState?: IncidentState = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateIncidentTemplate,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentTemplate,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.EditIncidentTemplate,
|
||||
],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: false,
|
||||
title: "Initial Incident State ID",
|
||||
description:
|
||||
"Relation to Incident State Object ID. Incidents created from this template will start in this state.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public initialIncidentStateId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
|
||||
@@ -24,6 +24,7 @@ import IncidentFeed from "./IncidentFeed";
|
||||
import IncidentCustomField from "./IncidentCustomField";
|
||||
import IncidentInternalNote from "./IncidentInternalNote";
|
||||
import IncidentNoteTemplate from "./IncidentNoteTemplate";
|
||||
import IncidentPostmortemTemplate from "./IncidentPostmortemTemplate";
|
||||
import IncidentOwnerTeam from "./IncidentOwnerTeam";
|
||||
import IncidentOwnerUser from "./IncidentOwnerUser";
|
||||
import IncidentPublicNote from "./IncidentPublicNote";
|
||||
@@ -80,6 +81,7 @@ import ProjectSmtpConfig from "./ProjectSmtpConfig";
|
||||
//SSO
|
||||
import ProjectSSO from "./ProjectSso";
|
||||
import PromoCode from "./PromoCode";
|
||||
import EnterpriseLicense from "./EnterpriseLicense";
|
||||
import Reseller from "./Reseller";
|
||||
import ResellerPlan from "./ResellerPlan";
|
||||
// ScheduledMaintenances
|
||||
@@ -100,6 +102,7 @@ import ServiceCopilotCodeRepository from "./ServiceCopilotCodeRepository";
|
||||
import ShortLink from "./ShortLink";
|
||||
// SMS
|
||||
import SmsLog from "./SmsLog";
|
||||
import WhatsAppLog from "./WhatsAppLog";
|
||||
import PushNotificationLog from "./PushNotificationLog";
|
||||
import WorkspaceNotificationLog from "./WorkspaceNotificationLog";
|
||||
// Status Page
|
||||
@@ -115,6 +118,7 @@ import StatusPageHistoryChartBarColorRule from "./StatusPageHistoryChartBarColor
|
||||
import StatusPageOwnerTeam from "./StatusPageOwnerTeam";
|
||||
import StatusPageOwnerUser from "./StatusPageOwnerUser";
|
||||
import StatusPagePrivateUser from "./StatusPagePrivateUser";
|
||||
import StatusPagePrivateUserSession from "./StatusPagePrivateUserSession";
|
||||
import StatusPageResource from "./StatusPageResource";
|
||||
import StatusPageSCIM from "./StatusPageSCIM";
|
||||
import StatusPageSSO from "./StatusPageSso";
|
||||
@@ -123,13 +127,16 @@ import StatusPageSubscriber from "./StatusPageSubscriber";
|
||||
import Team from "./Team";
|
||||
import TeamMember from "./TeamMember";
|
||||
import TeamPermission from "./TeamPermission";
|
||||
import TeamComplianceSetting from "./TeamComplianceSetting";
|
||||
import TelemetryService from "./TelemetryService";
|
||||
import UsageBilling from "./TelemetryUsageBilling";
|
||||
import User from "./User";
|
||||
import UserSession from "./UserSession";
|
||||
import UserCall from "./UserCall";
|
||||
// Notification Methods
|
||||
import UserEmail from "./UserEmail";
|
||||
import UserPush from "./UserPush";
|
||||
import UserWhatsApp from "./UserWhatsApp";
|
||||
// User Notification Rules
|
||||
import UserNotificationRule from "./UserNotificationRule";
|
||||
import UserNotificationSetting from "./UserNotificationSetting";
|
||||
@@ -145,7 +152,8 @@ import ServiceCatalogDependency from "./ServiceCatalogDependency";
|
||||
import ServiceCatalogMonitor from "./ServiceCatalogMonitor";
|
||||
import ServiceCatalogTelemetryService from "./ServiceCatalogTelemetryService";
|
||||
|
||||
import UserTwoFactorAuth from "./UserTwoFactorAuth";
|
||||
import UserTotpAuth from "./UserTotpAuth";
|
||||
import UserWebAuthn from "./UserWebAuthn";
|
||||
|
||||
import TelemetryIngestionKey from "./TelemetryIngestionKey";
|
||||
|
||||
@@ -196,6 +204,7 @@ const AllModelTypes: Array<{
|
||||
Team,
|
||||
TeamMember,
|
||||
TeamPermission,
|
||||
TeamComplianceSetting,
|
||||
ApiKey,
|
||||
Label,
|
||||
ApiKeyPermission,
|
||||
@@ -230,6 +239,7 @@ const AllModelTypes: Array<{
|
||||
IncidentOwnerUser,
|
||||
IncidentSeverity,
|
||||
IncidentNoteTemplate,
|
||||
IncidentPostmortemTemplate,
|
||||
|
||||
AlertState,
|
||||
Alert,
|
||||
@@ -258,6 +268,7 @@ const AllModelTypes: Array<{
|
||||
StatusPageFooterLink,
|
||||
StatusPageHeaderLink,
|
||||
StatusPagePrivateUser,
|
||||
StatusPagePrivateUserSession,
|
||||
StatusPageHistoryChartBarColorRule,
|
||||
|
||||
ScheduledMaintenanceState,
|
||||
@@ -294,6 +305,7 @@ const AllModelTypes: Array<{
|
||||
StatusPageOwnerUser,
|
||||
|
||||
SmsLog,
|
||||
WhatsAppLog,
|
||||
PushNotificationLog,
|
||||
WorkspaceNotificationLog,
|
||||
CallLog,
|
||||
@@ -303,6 +315,7 @@ const AllModelTypes: Array<{
|
||||
UserSms,
|
||||
UserCall,
|
||||
UserPush,
|
||||
UserWhatsApp,
|
||||
|
||||
UserNotificationRule,
|
||||
UserOnCallLog,
|
||||
@@ -322,6 +335,7 @@ const AllModelTypes: Array<{
|
||||
ResellerPlan,
|
||||
|
||||
PromoCode,
|
||||
EnterpriseLicense,
|
||||
|
||||
GlobalConfig,
|
||||
|
||||
@@ -364,7 +378,9 @@ const AllModelTypes: Array<{
|
||||
ProbeOwnerTeam,
|
||||
ProbeOwnerUser,
|
||||
|
||||
UserTwoFactorAuth,
|
||||
UserSession,
|
||||
UserTotpAuth,
|
||||
UserWebAuthn,
|
||||
|
||||
TelemetryIngestionKey,
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
import ColorField from "../../Types/Database/ColorField";
|
||||
import SlugifyColumn from "../../Types/Database/SlugifyColumn";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
@@ -365,6 +366,7 @@ export default class Label extends AccessControlModel {
|
||||
Permission.EditProjectLabel,
|
||||
],
|
||||
})
|
||||
@ColorField()
|
||||
@TableColumn({
|
||||
title: "Color",
|
||||
required: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
import ColorField from "../../Types/Database/ColorField";
|
||||
import SlugifyColumn from "../../Types/Database/SlugifyColumn";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
@@ -379,6 +380,7 @@ export default class MonitorStatus extends BaseModel {
|
||||
Permission.EditProjectMonitorStatus,
|
||||
],
|
||||
})
|
||||
@ColorField()
|
||||
@TableColumn({
|
||||
title: "Color",
|
||||
required: true,
|
||||
|
||||
@@ -29,10 +29,17 @@ import {
|
||||
ManyToOne,
|
||||
} from "typeorm";
|
||||
import NotificationRuleWorkspaceChannel from "../../Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel";
|
||||
import EnableWorkflow from "../../Types/Database/EnableWorkflow";
|
||||
|
||||
@EnableDocumentation()
|
||||
@AccessControlColumn("labels")
|
||||
@TenantColumn("projectId")
|
||||
@EnableWorkflow({
|
||||
create: true,
|
||||
delete: true,
|
||||
update: true,
|
||||
read: true,
|
||||
})
|
||||
@TableAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
|
||||
@@ -46,7 +46,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
Permission.EditProjectOnCallDutyPolicyEscalationRule,
|
||||
],
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule"))
|
||||
@CrudApiEndpoint(new Route("/on-call-duty-policy-escalation-rule"))
|
||||
@Entity({
|
||||
name: "OnCallDutyPolicyEscalationRule",
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
Permission.EditProjectOnCallDutyPolicyEscalationRuleSchedule,
|
||||
],
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule-schedule"))
|
||||
@CrudApiEndpoint(new Route("/on-call-duty-policy-escalation-rule-schedule"))
|
||||
@Entity({
|
||||
name: "OnCallDutyPolicyEscalationRuleSchedule",
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user