mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
1597 Commits
postmortem
...
tf-sets
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
935608d23d | ||
|
|
4a3000d3c3 | ||
|
|
b5df7042f7 | ||
|
|
a345390b9b | ||
|
|
d0a8c049ba | ||
|
|
56037adcf0 | ||
|
|
73e2fcf3c6 | ||
|
|
710bdea813 | ||
|
|
f07ba35310 | ||
|
|
9ef2163bc0 | ||
|
|
1f53a56b8f | ||
|
|
6998b63f59 | ||
|
|
dccddf3ebc | ||
|
|
2a7d076407 | ||
|
|
0e82b17f6b | ||
|
|
0aa7838fc5 | ||
|
|
b1d243896f | ||
|
|
ab70c2c041 | ||
|
|
22b6c5ace0 | ||
|
|
ad32579214 | ||
|
|
e525cc3708 | ||
|
|
db7eaacd14 | ||
|
|
a549daf9ab | ||
|
|
d9e65ce633 | ||
|
|
be2d33591d | ||
|
|
88897004a2 | ||
|
|
dceccf00fa | ||
|
|
f079a2b9e6 | ||
|
|
4044d705d6 | ||
|
|
a2cbe4e241 | ||
|
|
b509b57bb8 | ||
|
|
4a7f27a372 | ||
|
|
214ac678d3 | ||
|
|
9dabce1b7a | ||
|
|
9bc260847d | ||
|
|
229775935e | ||
|
|
668c35ba2e | ||
|
|
a808913049 | ||
|
|
54da185280 | ||
|
|
b1bc7bfde4 | ||
|
|
09d5ce0e1a | ||
|
|
3935446071 | ||
|
|
2463dcb2db | ||
|
|
0eb239a469 | ||
|
|
b865caae7a | ||
|
|
a3e25723af | ||
|
|
bae0338c36 | ||
|
|
f656f23836 | ||
|
|
a8a79162e4 | ||
|
|
04556835b0 | ||
|
|
84c5e50199 | ||
|
|
06fd44ecaf | ||
|
|
c535b68056 | ||
|
|
34c8e4fdec | ||
|
|
bf04796637 | ||
|
|
16e6c0c601 | ||
|
|
75a733e9b8 | ||
|
|
30217a64ec | ||
|
|
488295e303 | ||
|
|
973131b70a | ||
|
|
52cb00a1c4 | ||
|
|
ab0027a042 | ||
|
|
26a6d12809 | ||
|
|
0bdf74cab2 | ||
|
|
dd996539bc | ||
|
|
ad2ee2b0d6 | ||
|
|
ed6630c2d6 | ||
|
|
3af9121d6a | ||
|
|
e9d5a560ff | ||
|
|
d87b6da7c5 | ||
|
|
34d6c8edbe | ||
|
|
e9f63fb1e2 | ||
|
|
7a515f7ad8 | ||
|
|
4564baae70 | ||
|
|
4316fdbf81 | ||
|
|
be98736f4e | ||
|
|
ce7e10e3d9 | ||
|
|
21683de677 | ||
|
|
4dddec9966 | ||
|
|
b79a287791 | ||
|
|
6bd4b7257d | ||
|
|
438f8f4b6f | ||
|
|
75c1fedfba | ||
|
|
731a8e8b8f | ||
|
|
74768efea1 | ||
|
|
716d05b105 | ||
|
|
8ea7b26299 | ||
|
|
1538852bc4 | ||
|
|
a85607b996 | ||
|
|
a53db3b673 | ||
|
|
f6ec592cb6 | ||
|
|
d2e82fe50e | ||
|
|
30cb030470 | ||
|
|
3b84f5cece | ||
|
|
7f84f5c34d | ||
|
|
47b42d92c1 | ||
|
|
1928244a8e | ||
|
|
c3af14c3fe | ||
|
|
324666bafe | ||
|
|
8d4862f39f | ||
|
|
8c42627d46 | ||
|
|
590caa0563 | ||
|
|
15ac2bf749 | ||
|
|
d98103ca94 | ||
|
|
f8a6354fb4 | ||
|
|
c7cb3b3b20 | ||
|
|
978998b3f8 | ||
|
|
738e464c1e | ||
|
|
8882d62eac | ||
|
|
90cd819b0a | ||
|
|
a6873c687a | ||
|
|
4b54bd6b91 | ||
|
|
eaf2cbcb71 | ||
|
|
d9a1876ad4 | ||
|
|
c09f75faf0 | ||
|
|
674e32b95b | ||
|
|
84cab49386 | ||
|
|
479b83f6bf | ||
|
|
81ed9e0fc1 | ||
|
|
1f162461ad | ||
|
|
ad4f901e2f | ||
|
|
a885e2e8a8 | ||
|
|
8f11156011 | ||
|
|
1ef1101134 | ||
|
|
5664ad48dd | ||
|
|
e2608f56db | ||
|
|
78df267145 | ||
|
|
20e7e68e71 | ||
|
|
877d69f22e | ||
|
|
0791029f4a | ||
|
|
69c91a2c41 | ||
|
|
499d28c34c | ||
|
|
ec57a9ddbc | ||
|
|
e59db99b22 | ||
|
|
aaa3c4f602 | ||
|
|
29842c06e3 | ||
|
|
402ccf01d0 | ||
|
|
b7114304ee | ||
|
|
dca72856a2 | ||
|
|
1d7c758096 | ||
|
|
1e748365a5 | ||
|
|
a8022762a2 | ||
|
|
9c1ed659e9 | ||
|
|
42accb4204 | ||
|
|
529e5954d4 | ||
|
|
75ea34ef9e | ||
|
|
c4e1b8d97d | ||
|
|
b5626ef352 | ||
|
|
692d15159c | ||
|
|
91c163af9e | ||
|
|
fe71be64dd | ||
|
|
9e6587cb62 | ||
|
|
42b2f58d6a | ||
|
|
98afb63880 | ||
|
|
6d352c8579 | ||
|
|
21b761ddbf | ||
|
|
e51009e94d | ||
|
|
619136c9da | ||
|
|
d622334bd3 | ||
|
|
5508bf6302 | ||
|
|
4ba9151400 | ||
|
|
97fe212d15 | ||
|
|
af3738924e | ||
|
|
92f77c7ce2 | ||
|
|
ecb54381d8 | ||
|
|
0a9435ef1a | ||
|
|
2bc52c7b5d | ||
|
|
392b6dda9a | ||
|
|
0a6035ed65 | ||
|
|
70f9444aab | ||
|
|
088333c91c | ||
|
|
7fc7276207 | ||
|
|
631bf12c23 | ||
|
|
5ce158ebf3 | ||
|
|
4684f25f22 | ||
|
|
3d36d86bd6 | ||
|
|
93c017dbab | ||
|
|
c74204ed1f | ||
|
|
80125f500c | ||
|
|
2771efcd87 | ||
|
|
0e27802f1a | ||
|
|
a96c270a94 | ||
|
|
66d76676f5 | ||
|
|
cc0eb6a4b9 | ||
|
|
2d687a3275 | ||
|
|
47bca3fb9b | ||
|
|
79d3548492 | ||
|
|
3156302dbc | ||
|
|
84307250b7 | ||
|
|
c3986bd66a | ||
|
|
a4a56bf2c7 | ||
|
|
6644523c54 | ||
|
|
44aa046fec | ||
|
|
da5b9b4955 | ||
|
|
81dd803b62 | ||
|
|
efffa82cbf | ||
|
|
64b0c9f137 | ||
|
|
a3661e1626 | ||
|
|
4115deadc4 | ||
|
|
6fbd112964 | ||
|
|
a171c52c8d | ||
|
|
4f7d3ed2be | ||
|
|
9db97b3919 | ||
|
|
8bc545c90f | ||
|
|
19d1629e37 | ||
|
|
e7fd472c14 | ||
|
|
dd4a1416fc | ||
|
|
09b65b9a5b | ||
|
|
8fb1a1daf9 | ||
|
|
d3d0dedfee | ||
|
|
46635f4251 | ||
|
|
1068a5d96e | ||
|
|
d1e200a54f | ||
|
|
8220b0356c | ||
|
|
bc9b09aed5 | ||
|
|
6822718c46 | ||
|
|
9a935c4e90 | ||
|
|
6137199e63 | ||
|
|
6e6d989be4 | ||
|
|
5a7af27543 | ||
|
|
7c422b4384 | ||
|
|
b06de38f69 | ||
|
|
71723675d6 | ||
|
|
e699e323cb | ||
|
|
8e8bc54aed | ||
|
|
d3cf309aef | ||
|
|
a24f9d37a9 | ||
|
|
d5332ed494 | ||
|
|
23fdd3bfd7 | ||
|
|
714f8b4edf | ||
|
|
02f920a152 | ||
|
|
378663b03c | ||
|
|
78257ebda8 | ||
|
|
d29e876b96 | ||
|
|
88c1e23da9 | ||
|
|
cb50f89a12 | ||
|
|
869cc6d2b8 | ||
|
|
1566bd6a21 | ||
|
|
d175841b2a | ||
|
|
811fb49c2d | ||
|
|
290b59dfc9 | ||
|
|
acb57b6b32 | ||
|
|
ff1feb1a9f | ||
|
|
a99c09c05a | ||
|
|
b400965384 | ||
|
|
48a9523bb6 | ||
|
|
aa5ff55a9c | ||
|
|
08f8200d5b | ||
|
|
7030f27076 | ||
|
|
d7611b895b | ||
|
|
ec8afb2d0b | ||
|
|
81aeb373c4 | ||
|
|
cbc779ae0b | ||
|
|
9906115faf | ||
|
|
86d60f4688 | ||
|
|
d7f329fcff | ||
|
|
120d36f3dd | ||
|
|
13f22b1611 | ||
|
|
f9a89548e2 | ||
|
|
88c55f9e14 | ||
|
|
91965f3cc9 | ||
|
|
1383b1f3b0 | ||
|
|
cf45f089af | ||
|
|
ecfcbae86b | ||
|
|
b714ad168c | ||
|
|
59d6aeb2b4 | ||
|
|
be6d122879 | ||
|
|
b9fcfd5c61 | ||
|
|
9e902e5b76 | ||
|
|
4fa44b40c9 | ||
|
|
3baa081850 | ||
|
|
bcc7218091 | ||
|
|
91937ea9bc | ||
|
|
79835d411e | ||
|
|
8a965dcf1a | ||
|
|
549cbe7102 | ||
|
|
2f727b7707 | ||
|
|
5dd1f1a7f1 | ||
|
|
80defab7b2 | ||
|
|
5dea6fcbad | ||
|
|
5e6d5aebff | ||
|
|
d7e8dc3d92 | ||
|
|
0e21d2755b | ||
|
|
52ed66fafe | ||
|
|
37215aca51 | ||
|
|
441cd5c73d | ||
|
|
046e0c00cd | ||
|
|
e0a9ab8cfb | ||
|
|
ea47d2bd2c | ||
|
|
7b249f55b5 | ||
|
|
f261fe98f0 | ||
|
|
3070b549e3 | ||
|
|
4731a67ab4 | ||
|
|
b204b05bc3 | ||
|
|
9217d869dd | ||
|
|
02e512e6e8 | ||
|
|
475ad54a8f | ||
|
|
739cf632e5 | ||
|
|
e069e94971 | ||
|
|
391c9ea2e7 | ||
|
|
72a6b426ea | ||
|
|
86aca6a48e | ||
|
|
9b81a82eed | ||
|
|
c4ab245824 | ||
|
|
06cf878446 | ||
|
|
cd068f9219 | ||
|
|
53c0b1fb92 | ||
|
|
642a5a2982 | ||
|
|
c0c162cca5 | ||
|
|
c94a2db6fa | ||
|
|
3b4828eea1 | ||
|
|
5907bfe4d1 | ||
|
|
a2d9cda7d9 | ||
|
|
a8be03d3c9 | ||
|
|
272ae08048 | ||
|
|
5f2bda119a | ||
|
|
64cfeb5400 | ||
|
|
1e1d3e939e | ||
|
|
6894cae68c | ||
|
|
65b4a8217b | ||
|
|
5452342f2f | ||
|
|
d1f583fb47 | ||
|
|
41f3a4ce21 | ||
|
|
41f151b8eb | ||
|
|
60276876bd | ||
|
|
86fb8fbb30 | ||
|
|
5e9b5be0ad | ||
|
|
a9734dd18e | ||
|
|
511c65a01b | ||
|
|
a619f323e7 | ||
|
|
a021ad41ef | ||
|
|
1eddfff608 | ||
|
|
578774df08 | ||
|
|
82d0d68a7c | ||
|
|
8d9ba58964 | ||
|
|
52dbab88f6 | ||
|
|
387ebc9375 | ||
|
|
26f3e5bd5e | ||
|
|
7ed06d7391 | ||
|
|
3c2811000e | ||
|
|
5979e4f345 | ||
|
|
5c84699bae | ||
|
|
d153fc4cd4 | ||
|
|
34475f76f9 | ||
|
|
6565b7c803 | ||
|
|
c63923ed5b | ||
|
|
33d51932c5 | ||
|
|
557d14106c | ||
|
|
8d5395ae74 | ||
|
|
06e0100ede | ||
|
|
3db29ab264 | ||
|
|
7442e36b18 | ||
|
|
1fa446ec0c | ||
|
|
ef85d98362 | ||
|
|
46bccfb596 | ||
|
|
f7b2588647 | ||
|
|
b4106eb580 | ||
|
|
de05f727d7 | ||
|
|
5a3d6d9ccc | ||
|
|
7d5f813bac | ||
|
|
d4cb2587c9 | ||
|
|
75ca86d92d | ||
|
|
ddf3dcd8a8 | ||
|
|
bc1a30f877 | ||
|
|
194d87041c | ||
|
|
ba950928a4 | ||
|
|
449f780201 | ||
|
|
b95fe3ad4f | ||
|
|
0dc3e5fe8d | ||
|
|
36e0b18f13 | ||
|
|
c9e1a3b2b6 | ||
|
|
1a15e446ff | ||
|
|
3947b0bba1 | ||
|
|
3968428f0c | ||
|
|
79b36c5b27 | ||
|
|
3f24c910c0 | ||
|
|
8619ba379a | ||
|
|
34396c764e | ||
|
|
7ab6b8e135 | ||
|
|
54c7955c78 | ||
|
|
d5ae25545c | ||
|
|
f449191f84 | ||
|
|
3168b57e28 | ||
|
|
dbff165c34 | ||
|
|
25c3cf8aec | ||
|
|
0c0fb1be2d | ||
|
|
497394e5ee | ||
|
|
bdc9683c04 | ||
|
|
ad5372e354 | ||
|
|
72a31714a8 | ||
|
|
bccf8c116b | ||
|
|
6999849b7d | ||
|
|
44c71bff85 | ||
|
|
3870a9ed08 | ||
|
|
31fca8c50f | ||
|
|
7e8c6b42c3 | ||
|
|
5fb9357ad2 | ||
|
|
60c01b2180 | ||
|
|
760f1ea2d6 | ||
|
|
d2c2f66b66 | ||
|
|
3631a48d83 | ||
|
|
5d3d344110 | ||
|
|
ec2105d916 | ||
|
|
821cda573a | ||
|
|
537b257e1d | ||
|
|
319becae1b | ||
|
|
7806bb9f91 | ||
|
|
70ceccf3c1 | ||
|
|
68034b8eaf | ||
|
|
66ed8fd9aa | ||
|
|
38508f36b4 | ||
|
|
691227dfef | ||
|
|
7ed8372c4f | ||
|
|
e9d53a9e3b | ||
|
|
3801915850 | ||
|
|
d83e4684c4 | ||
|
|
f6897e776d | ||
|
|
e8a2c5eb7e | ||
|
|
91e8fffbe1 | ||
|
|
bd0ef197d9 | ||
|
|
05c15ddd09 | ||
|
|
7b99b0214d | ||
|
|
4b738ef85a | ||
|
|
6c9879003e | ||
|
|
c9e4ac526a | ||
|
|
a59eccdd9f | ||
|
|
b484b9b989 | ||
|
|
c63202150d | ||
|
|
66d4e0fe62 | ||
|
|
2a49ea987c | ||
|
|
5c334f6476 | ||
|
|
cca0d53f56 | ||
|
|
e477948b16 | ||
|
|
2adaa81af4 | ||
|
|
f22f18734f | ||
|
|
2d52126da4 | ||
|
|
707f35d12f | ||
|
|
3d45986b5a | ||
|
|
1b498ca5fa | ||
|
|
ce7ed97281 | ||
|
|
d5a6e0b7c7 | ||
|
|
1131b7eea0 | ||
|
|
b48b3f31fc | ||
|
|
7b35e7752c | ||
|
|
b9c6be38a1 | ||
|
|
e8ff2cd7af | ||
|
|
50868ac8ea | ||
|
|
f74c003065 | ||
|
|
0420d9042a | ||
|
|
4672a2d79e | ||
|
|
f59c215d1f | ||
|
|
3f3be46789 | ||
|
|
e046a5514d | ||
|
|
63e69f82f2 | ||
|
|
f7db7c537c | ||
|
|
a5e4717c2a | ||
|
|
43a0fc3694 | ||
|
|
4c669000fa | ||
|
|
07476f366c | ||
|
|
1a0fac5a3f | ||
|
|
4f68fd0542 | ||
|
|
c83600e446 | ||
|
|
02368685b5 | ||
|
|
113106a30d | ||
|
|
b98fa7af0e | ||
|
|
f027dda584 | ||
|
|
32f662a84c | ||
|
|
3e218e14f1 | ||
|
|
495b4e3a79 | ||
|
|
abd9741337 | ||
|
|
54b1a74dc2 | ||
|
|
b07fa604d0 | ||
|
|
b77f966cd1 | ||
|
|
b464ff1cf9 | ||
|
|
f34bd4ad36 | ||
|
|
9196a81693 | ||
|
|
82e4beb8d9 | ||
|
|
f494e0cdaa | ||
|
|
b2833d115d | ||
|
|
6115da2a2d | ||
|
|
b6f52df524 | ||
|
|
57c7f239e6 | ||
|
|
8b49f709e3 | ||
|
|
a25836db1b | ||
|
|
0426c3c064 | ||
|
|
f083abd741 | ||
|
|
57a58849ec | ||
|
|
c1efcf578f | ||
|
|
30c1585dd8 | ||
|
|
92cefa70fa | ||
|
|
57f36d75b3 | ||
|
|
165976608d | ||
|
|
f016c02dfb | ||
|
|
2dc2f3bf36 | ||
|
|
a9b5ea4702 | ||
|
|
81051064dd | ||
|
|
3dee4e9cc6 | ||
|
|
f272738ae9 | ||
|
|
99d198a33b | ||
|
|
e7089e9e85 | ||
|
|
827e4c8b90 | ||
|
|
6513938b00 | ||
|
|
1c6d243457 | ||
|
|
491e6341a9 | ||
|
|
89e1bdedaf | ||
|
|
b0ab3f299c | ||
|
|
94c8a25bfd | ||
|
|
2dfd33a86e | ||
|
|
21232465bc | ||
|
|
fc12833ae5 | ||
|
|
e2707581a2 | ||
|
|
d91272fd6c | ||
|
|
bf4e6cd6ab | ||
|
|
da4f5f6c03 | ||
|
|
ff90d94b6b | ||
|
|
1a7d2a76de | ||
|
|
8ed7381356 | ||
|
|
23dcca41fc | ||
|
|
dbad765586 | ||
|
|
c2be57e2f0 | ||
|
|
6134fa467c | ||
|
|
918fda707e | ||
|
|
c8bdbf619a | ||
|
|
27004092e3 | ||
|
|
dbd3742bbe | ||
|
|
c562af4d90 | ||
|
|
bc5457259f | ||
|
|
551faa170d | ||
|
|
73122b7d72 | ||
|
|
5eb83dec0a | ||
|
|
123e0275bc | ||
|
|
e29b9ce00d | ||
|
|
65fa365d06 | ||
|
|
c40e18b2ed | ||
|
|
f2520750fc | ||
|
|
ecfce30adb | ||
|
|
20b184d6bd | ||
|
|
bbfe8dcf7e | ||
|
|
f80497ead9 | ||
|
|
8826635920 | ||
|
|
a08700e9f5 | ||
|
|
1b642885ab | ||
|
|
5342317d57 | ||
|
|
6d919920f7 | ||
|
|
4fcfe2e44c | ||
|
|
e87d5de02f | ||
|
|
992af21d05 | ||
|
|
5c4c6dec93 | ||
|
|
9cae95d23b | ||
|
|
8db3c6d8c3 | ||
|
|
95ff3b510e | ||
|
|
921bbe3cc1 | ||
|
|
71d67ab48a | ||
|
|
8d1fe98c17 | ||
|
|
20c93e379b | ||
|
|
5e04a8c13c | ||
|
|
4eb513e852 | ||
|
|
d3583aa2c7 | ||
|
|
2d4f2d0d95 | ||
|
|
f351d90046 | ||
|
|
f9b284dd7c | ||
|
|
40b57f8e5f | ||
|
|
6f94e3dec9 | ||
|
|
b7bf950db6 | ||
|
|
f961e946be | ||
|
|
ca44b797a5 | ||
|
|
75be6baf28 | ||
|
|
a42a00edd5 | ||
|
|
c372419de6 | ||
|
|
37f35a5ea3 | ||
|
|
781455abb5 | ||
|
|
b539e474d0 | ||
|
|
21605b2a41 | ||
|
|
fd143903c9 | ||
|
|
ad8c825a62 | ||
|
|
cca88b9073 | ||
|
|
1950175f0b | ||
|
|
fdd9319162 | ||
|
|
fb26b120bd | ||
|
|
b8814f128b | ||
|
|
66c9ecdb8f | ||
|
|
462b6db8b8 | ||
|
|
7e12dde1ba | ||
|
|
f03daf66ee | ||
|
|
0312586770 | ||
|
|
d5e717f9b0 | ||
|
|
9351218df6 | ||
|
|
eaa6a7fe66 | ||
|
|
81a477cf49 | ||
|
|
95ed963071 | ||
|
|
247d732d1b | ||
|
|
7211bd3fac | ||
|
|
1f07ad7c0f | ||
|
|
d749be5cd0 | ||
|
|
be55e82075 | ||
|
|
886337ae30 | ||
|
|
0ef23eb161 | ||
|
|
737cdf4d3d | ||
|
|
6a50b0cdf4 | ||
|
|
dfe6f9be14 | ||
|
|
93a22c67fd | ||
|
|
c069355a83 | ||
|
|
197b7d5e22 | ||
|
|
4ef184d21e | ||
|
|
9d77b9ab09 | ||
|
|
d2df638df0 | ||
|
|
78ed019d50 | ||
|
|
89f9faacf6 | ||
|
|
7ff0da5ab5 | ||
|
|
e295525698 | ||
|
|
48b398b032 | ||
|
|
772e3684e5 | ||
|
|
20ee5fed6c | ||
|
|
e77fbdc439 | ||
|
|
7a207c9020 | ||
|
|
75b05c359e | ||
|
|
1c02a82a2c | ||
|
|
0e111b4bc1 | ||
|
|
2c0db9fe55 | ||
|
|
c42fcc480b | ||
|
|
bdfcf04d58 | ||
|
|
e392a61916 | ||
|
|
0349926d81 | ||
|
|
6c033dfe10 | ||
|
|
f62ad9527c | ||
|
|
dfec19b40e | ||
|
|
5a9d35ea8d | ||
|
|
c5bc5e30b0 | ||
|
|
346c1f6dcc | ||
|
|
d56a0b7c5c | ||
|
|
8984bb4ea6 | ||
|
|
07c624d34c | ||
|
|
9406f5af96 | ||
|
|
1e08c35bb8 | ||
|
|
12f8aaa10a | ||
|
|
1cfe9e7dad | ||
|
|
4f9c30dadf | ||
|
|
7f42566aeb | ||
|
|
3b148dce68 | ||
|
|
473b5886b2 | ||
|
|
07fbf3f47b | ||
|
|
9814edfc19 | ||
|
|
a4dda7f8e2 | ||
|
|
a7dd2283c0 | ||
|
|
38d0d73a3f | ||
|
|
4298a005ef | ||
|
|
583211b0e7 | ||
|
|
545860404c | ||
|
|
283785f64b | ||
|
|
1362ba96f9 | ||
|
|
fd519d7d9c | ||
|
|
ec097b6531 | ||
|
|
84a99c06b6 | ||
|
|
22b1ea282c | ||
|
|
32e1377a05 | ||
|
|
d7caf79b6d | ||
|
|
4a915c4779 | ||
|
|
ba6de67c45 | ||
|
|
b21fdba307 | ||
|
|
1f8d0b5ea6 | ||
|
|
6d41c8baa7 | ||
|
|
148167c911 | ||
|
|
9de24a5be3 | ||
|
|
cdc280fd15 | ||
|
|
70e2fcee45 | ||
|
|
3e82e3af78 | ||
|
|
ce5b7d9bc1 | ||
|
|
6ee68271e0 | ||
|
|
0a34ef3253 | ||
|
|
d719ce30e9 | ||
|
|
883653738a | ||
|
|
39caaff50e | ||
|
|
40979b9e2b | ||
|
|
1547dd4e6f | ||
|
|
52dfcb42b1 | ||
|
|
561cb73f83 | ||
|
|
e86b21e96c | ||
|
|
8867a17abe | ||
|
|
9aa4080fba | ||
|
|
1e9aa83f15 | ||
|
|
0f585e0ca3 | ||
|
|
4d51ee9afa | ||
|
|
29e5590493 | ||
|
|
1c1bb6ff32 | ||
|
|
82d0f004a8 | ||
|
|
d5f532821e | ||
|
|
b96ec9a292 | ||
|
|
67c71fa452 | ||
|
|
5ce45a3c8e | ||
|
|
8fc4294f4a | ||
|
|
054f0047fa | ||
|
|
ba22e4b745 | ||
|
|
3344d87f86 | ||
|
|
da1647ecb7 | ||
|
|
e3d13c74eb | ||
|
|
adda1bf450 | ||
|
|
52275ba3cc | ||
|
|
1587a96be3 | ||
|
|
a683681906 | ||
|
|
331b79ecc2 | ||
|
|
c8e2c755c1 | ||
|
|
dcdd578b87 | ||
|
|
9a86b01638 | ||
|
|
461fc5b6f5 | ||
|
|
2427b8d72c | ||
|
|
0b6ae78adf | ||
|
|
dd56240591 | ||
|
|
d374423786 | ||
|
|
bb606805ee | ||
|
|
5fa3cbf787 | ||
|
|
90d7200794 | ||
|
|
4d07d78b66 | ||
|
|
5e06fc554c | ||
|
|
81f7451350 | ||
|
|
1e6d65448c | ||
|
|
90a5fb2062 | ||
|
|
6fd4f93b7a | ||
|
|
b1ab1c4775 | ||
|
|
04602a245c | ||
|
|
f9b75e36fd | ||
|
|
c642315df1 | ||
|
|
5b200b9ab8 | ||
|
|
3fc8d650a8 | ||
|
|
e63a0ed45d | ||
|
|
d8fd1ec863 | ||
|
|
3f3a1a7d98 | ||
|
|
84c8b1e3d7 | ||
|
|
30b781c26f | ||
|
|
a3f0bf0428 | ||
|
|
dc321c365a | ||
|
|
0e8c204ca0 | ||
|
|
d1cc9c72c1 | ||
|
|
3634011c06 | ||
|
|
21b6ebe183 | ||
|
|
5e18d5c2cf | ||
|
|
4a95225671 | ||
|
|
cfbb473199 | ||
|
|
3971edfaaa | ||
|
|
69d568a0cb | ||
|
|
9dc47190ab | ||
|
|
283c2b8c89 | ||
|
|
25fd4cee7b | ||
|
|
6153e8b450 | ||
|
|
dd6ccbead8 | ||
|
|
2413340b3c | ||
|
|
fb37e67ac8 | ||
|
|
63e5c4abf8 | ||
|
|
c023f7236b | ||
|
|
4d453c9680 | ||
|
|
481eff1636 | ||
|
|
9544c77524 | ||
|
|
bbdd6ca24c | ||
|
|
5eeac0368b | ||
|
|
c88fd61753 | ||
|
|
4bae52be93 | ||
|
|
2114d33941 | ||
|
|
5678e36259 | ||
|
|
0628b8fb7e | ||
|
|
3a45c1e11a | ||
|
|
3aef8dd62a | ||
|
|
d520149d99 | ||
|
|
c4130c0a1f | ||
|
|
b826f9f19a | ||
|
|
13fc89e3a5 | ||
|
|
2df4446062 | ||
|
|
046bdc2eee | ||
|
|
3b9c0ab692 | ||
|
|
c11224a59d | ||
|
|
5b77798676 | ||
|
|
1c1f23da88 | ||
|
|
5879ae3a73 | ||
|
|
60b05191c0 | ||
|
|
c5b3ca88c6 | ||
|
|
79b0d2f63a | ||
|
|
d9e65e5657 | ||
|
|
ef724ab274 | ||
|
|
ca2d3abbc2 | ||
|
|
8bccd0419f | ||
|
|
f78c46e5e3 | ||
|
|
773af8f184 | ||
|
|
2cd750c7fd | ||
|
|
eae325e4f4 | ||
|
|
3769e0d1a5 | ||
|
|
26e241802d | ||
|
|
94e1812293 | ||
|
|
660d15bce5 | ||
|
|
8beddbf53e | ||
|
|
07a383314d | ||
|
|
0097d38811 | ||
|
|
4c26ccfe95 | ||
|
|
9673e54f9d | ||
|
|
89172c4f9d | ||
|
|
5e1a776777 | ||
|
|
05863e5824 | ||
|
|
a13949f340 | ||
|
|
bc49ad1409 | ||
|
|
78dc69bb5b | ||
|
|
8b6fdbd4a7 | ||
|
|
0779aaf24e | ||
|
|
58e2be3619 | ||
|
|
61a6c564ba | ||
|
|
9cca558d2d | ||
|
|
40c4cc2133 | ||
|
|
f5ad971f4b | ||
|
|
9e442958d3 | ||
|
|
115e0d6323 | ||
|
|
29e07e3b41 | ||
|
|
3ece51504f | ||
|
|
7456184dc5 | ||
|
|
4c8b720968 | ||
|
|
11050a7b3a | ||
|
|
e5dbe6ed05 | ||
|
|
288b7bc8c8 | ||
|
|
305d1133e0 | ||
|
|
a0a31e9e25 | ||
|
|
892305e13b | ||
|
|
6ee6aa59fd | ||
|
|
7fae47faee | ||
|
|
c8d3b091b1 | ||
|
|
623d41f694 | ||
|
|
47e5e0c706 | ||
|
|
14ff0b7956 | ||
|
|
1f793e8750 | ||
|
|
051641339a | ||
|
|
43947932f2 | ||
|
|
d838343eea | ||
|
|
0cf556efde | ||
|
|
c381c51957 | ||
|
|
8ab088ace0 | ||
|
|
f96c0404bc | ||
|
|
347e0cdd2f | ||
|
|
629890b118 | ||
|
|
0a20591bf9 | ||
|
|
464645dbc3 | ||
|
|
d9ffa6a108 | ||
|
|
38cfed6c24 | ||
|
|
e97dbc4579 | ||
|
|
f194b17b1b | ||
|
|
eb45aecda5 | ||
|
|
3633e376bc | ||
|
|
5c7b201305 | ||
|
|
6726dd9a6d | ||
|
|
0eece03dcd | ||
|
|
fec7853a2e | ||
|
|
81193dc441 | ||
|
|
7abf6f29e9 | ||
|
|
4b18181f2d | ||
|
|
b08174be97 | ||
|
|
660c800166 | ||
|
|
39de2ebb87 | ||
|
|
afe072f5e7 | ||
|
|
16fb097c55 | ||
|
|
122a4b7b0a | ||
|
|
ff38942416 | ||
|
|
e72a0d2b16 | ||
|
|
1c9425169a | ||
|
|
59b49519c9 | ||
|
|
9ff1a20858 | ||
|
|
d8806159e4 | ||
|
|
3f1bb2cb0b | ||
|
|
513b1d7e55 | ||
|
|
e49c005431 | ||
|
|
7892dbcac3 | ||
|
|
25d3d755dd | ||
|
|
92fed4016f | ||
|
|
d8d20aef8a | ||
|
|
41a1f80140 | ||
|
|
cdb4d04cc3 | ||
|
|
0f8e949190 | ||
|
|
0a98f33d72 | ||
|
|
64947413b0 | ||
|
|
8635726344 | ||
|
|
3fa6f9e7b1 | ||
|
|
6689aaa8b8 | ||
|
|
a47a3c79e0 | ||
|
|
8bd5916d14 | ||
|
|
1f28f0191d | ||
|
|
dcb9da7d91 | ||
|
|
3311e9d354 | ||
|
|
755b76cbce | ||
|
|
45a1748d50 | ||
|
|
c256b03be6 | ||
|
|
c2cfe123d6 | ||
|
|
e5c73daee6 | ||
|
|
9cb5b27e9b | ||
|
|
33a819853b | ||
|
|
3da9bbdf78 | ||
|
|
d7bb54332d | ||
|
|
bfb11c8366 | ||
|
|
f11c8fd60b | ||
|
|
cd7dfc4efb | ||
|
|
227a5ca52b | ||
|
|
9bbfe35880 | ||
|
|
94603b5615 | ||
|
|
f07b002744 | ||
|
|
1680c955f9 | ||
|
|
3394903323 | ||
|
|
877a97017d | ||
|
|
c389260b60 | ||
|
|
c8df86011c | ||
|
|
77a922b27e | ||
|
|
14909a8dce | ||
|
|
84c5cdab9d | ||
|
|
178e4a5e70 | ||
|
|
9183b6eb1f | ||
|
|
fa953a13cd | ||
|
|
bf426d026c | ||
|
|
69e073ac65 | ||
|
|
fdc4f08222 | ||
|
|
57669ba27d | ||
|
|
2161330598 | ||
|
|
9555c3827f | ||
|
|
bf44af92b7 | ||
|
|
02549cb33d | ||
|
|
97c437b5ce | ||
|
|
17ea3c6ae5 | ||
|
|
d5abbf420f | ||
|
|
2b2c821af5 | ||
|
|
07ed74d04e | ||
|
|
915712bf27 | ||
|
|
a02d181b86 | ||
|
|
922c4ec3d3 | ||
|
|
c912326ff2 | ||
|
|
53e1573c73 | ||
|
|
0c56943818 | ||
|
|
2a620f4cf3 | ||
|
|
e7fbda886b | ||
|
|
9116cb0397 | ||
|
|
0d3112a1f4 | ||
|
|
aa61be7e78 | ||
|
|
fc4ed33bd8 | ||
|
|
b88406524d | ||
|
|
a32a66ce12 | ||
|
|
6ef91bd9df | ||
|
|
1b06ec7138 | ||
|
|
a85bf13361 | ||
|
|
288d91c2c0 | ||
|
|
b3ffcc72ca | ||
|
|
ab6148b8b6 | ||
|
|
7aa1ce0929 | ||
|
|
cdf435b27a | ||
|
|
6ff22c2660 | ||
|
|
bb3d1007a6 | ||
|
|
05607d0487 | ||
|
|
29e7078670 | ||
|
|
864b8c7bac | ||
|
|
a77a752062 | ||
|
|
57a8084fd6 | ||
|
|
ec7eb958e8 | ||
|
|
7da6584c48 | ||
|
|
8f1cde9ec0 | ||
|
|
0492a1c679 | ||
|
|
fa7097539f | ||
|
|
8ee329c143 | ||
|
|
f2d83fc08e | ||
|
|
c711316097 | ||
|
|
1cd57bc35d | ||
|
|
efc0632b6c | ||
|
|
b674993b11 | ||
|
|
7a2a79ceba | ||
|
|
90ddd29e0e | ||
|
|
1e99da5c4c | ||
|
|
75304a4c67 | ||
|
|
ea19a70e14 | ||
|
|
fe3582b972 | ||
|
|
dbb13ce231 | ||
|
|
5726941e19 | ||
|
|
a9c5dceeaa | ||
|
|
c037bc3825 | ||
|
|
e1ba3127ee | ||
|
|
9fcf84db9c | ||
|
|
59d4c60d5c | ||
|
|
e445ada7d0 | ||
|
|
287c61b5bb | ||
|
|
3f695053a6 | ||
|
|
26964ad073 | ||
|
|
5687618c53 | ||
|
|
b5c6c93d7f | ||
|
|
ba08694e43 | ||
|
|
60df00686c | ||
|
|
9bfa13c9f5 | ||
|
|
f7bb851c80 | ||
|
|
79c3cf981f | ||
|
|
ea85d08c7a | ||
|
|
84e51a836d | ||
|
|
a3f8403a17 | ||
|
|
8aaf278982 | ||
|
|
ca93dc12d6 | ||
|
|
75d0803650 | ||
|
|
f519520966 | ||
|
|
958cb3c9fd | ||
|
|
f6ebc3db16 | ||
|
|
583d86bbc9 | ||
|
|
19f8dc8b19 | ||
|
|
1a2acbf12d | ||
|
|
51e9e2d95b | ||
|
|
818a638580 | ||
|
|
ca2f0cd644 | ||
|
|
4a8b265b41 | ||
|
|
66124f9de6 | ||
|
|
73bd9838c7 | ||
|
|
1da6ec7a16 | ||
|
|
558eb54783 | ||
|
|
cb46155224 | ||
|
|
fc78723d7b | ||
|
|
40562ea0d1 | ||
|
|
72369485a1 | ||
|
|
d09228a71d | ||
|
|
e5c48061e4 | ||
|
|
e1ffba9fbd | ||
|
|
cd44bf0360 | ||
|
|
04a553eec6 | ||
|
|
d2c33f4996 | ||
|
|
b2321dd966 | ||
|
|
47d9c118c0 | ||
|
|
649f317c3d | ||
|
|
1afcaab6fd | ||
|
|
22c832f14a | ||
|
|
272bb1b3ab | ||
|
|
cde10e34da | ||
|
|
1dd79e052f | ||
|
|
dce71c4eae | ||
|
|
75fa822489 | ||
|
|
0b0de6e32c | ||
|
|
797373d0dc | ||
|
|
4a41808afb | ||
|
|
0f36040cec | ||
|
|
e3ea99d182 | ||
|
|
4f06b241e4 | ||
|
|
d38bd9a1bd | ||
|
|
9c1fb7820c | ||
|
|
9e99ee14a4 | ||
|
|
0bbd99aa32 | ||
|
|
317f472013 | ||
|
|
1058f45160 | ||
|
|
a2ce44127c | ||
|
|
1457d248e0 | ||
|
|
78b4af6698 | ||
|
|
fdc30dc128 | ||
|
|
712e26f9a5 | ||
|
|
c91ade202a | ||
|
|
64c2fe6fd0 | ||
|
|
bf9fbe88c1 | ||
|
|
dd6554435d | ||
|
|
c65d199e5e | ||
|
|
9100b4c485 | ||
|
|
90f7f735e7 | ||
|
|
a386c75dbd | ||
|
|
a54257b5db | ||
|
|
9b0dfcffac | ||
|
|
c3ab746d24 | ||
|
|
78b51ffb43 | ||
|
|
485570cbff | ||
|
|
235fcd4af2 | ||
|
|
dfbc86deb0 | ||
|
|
d218f5e9cf | ||
|
|
3a4ca497ac | ||
|
|
2c0cd1b13d | ||
|
|
17edd054a9 | ||
|
|
72a1003c06 | ||
|
|
0e2e1360a6 | ||
|
|
9fcd93dc52 | ||
|
|
de8a917a92 | ||
|
|
166ac24565 | ||
|
|
7275212bb9 | ||
|
|
fd002ea2f2 | ||
|
|
afdff472e6 | ||
|
|
8bd9342183 | ||
|
|
86511b15f8 | ||
|
|
a187750581 | ||
|
|
e3f14c5424 | ||
|
|
2739dcc30c | ||
|
|
d963e81365 | ||
|
|
0599df28d7 | ||
|
|
d2d1d0dad3 | ||
|
|
00fcc51dc9 | ||
|
|
a3de5ceb7b | ||
|
|
ef817d0095 | ||
|
|
15d8816eed | ||
|
|
b98e90f7ec | ||
|
|
84e3fa02a4 | ||
|
|
801d75d5b5 | ||
|
|
c221890339 | ||
|
|
7083d17dbb | ||
|
|
955cd25fe9 | ||
|
|
434ae5954f | ||
|
|
a87c455c1a | ||
|
|
e7d13f9e7c | ||
|
|
6564fe2ae6 | ||
|
|
bd3e19f774 | ||
|
|
51ad4a5e9b | ||
|
|
921da83b2c | ||
|
|
3450ab87a9 | ||
|
|
2ea6c7328f | ||
|
|
3a36f148b7 | ||
|
|
82c7217cd5 | ||
|
|
cc0fc07e73 | ||
|
|
2d8a328a69 | ||
|
|
eea5d1e447 | ||
|
|
0fc7fa75a8 | ||
|
|
03e30276ec | ||
|
|
2bc585df20 | ||
|
|
7ef6c5fbdb | ||
|
|
cc9372edfb | ||
|
|
43aae580e2 | ||
|
|
9d147c9aa5 | ||
|
|
c417d87c00 | ||
|
|
c14708ac8d | ||
|
|
e92d7b3834 | ||
|
|
43583c3c30 | ||
|
|
51b08d34d0 | ||
|
|
b847ef6aca | ||
|
|
2cf3cd7e32 | ||
|
|
cc389542ad | ||
|
|
ab521336b6 | ||
|
|
e796c74d6f | ||
|
|
99eca5b8de | ||
|
|
1090f56684 | ||
|
|
083408f8a5 | ||
|
|
0f2b0a40b8 | ||
|
|
fd4661301e | ||
|
|
7906280c98 | ||
|
|
0353b57711 | ||
|
|
ba26e1a24b | ||
|
|
4283131030 | ||
|
|
154cdf6d25 | ||
|
|
a0f9c2892f | ||
|
|
3b8a91128a | ||
|
|
63debedf65 | ||
|
|
14dd1f16a0 | ||
|
|
df04467ae9 | ||
|
|
6ac4591c26 | ||
|
|
945d5df750 | ||
|
|
1a9b3d48c0 | ||
|
|
225480d99a | ||
|
|
c3f598f2f3 | ||
|
|
54982c5e88 | ||
|
|
3eb465a901 | ||
|
|
c8d091e5ef | ||
|
|
2986e94655 | ||
|
|
ddc21d6947 | ||
|
|
e7ca4dc1c2 | ||
|
|
5136dd5412 | ||
|
|
7642d2dd80 | ||
|
|
01d22b0ff1 | ||
|
|
2f1f446980 | ||
|
|
319eb98ffa | ||
|
|
f864e389ea | ||
|
|
bdc40bcd59 | ||
|
|
65bd7a90bd | ||
|
|
f61ee70c2c | ||
|
|
b2a0ca1a2f | ||
|
|
3908eb8701 | ||
|
|
dd793f7a90 | ||
|
|
36cf20ef21 | ||
|
|
aafab4b313 | ||
|
|
2e7ec3e5bf | ||
|
|
433482b87c | ||
|
|
904788fb34 | ||
|
|
7f6efa0b55 | ||
|
|
633c07323e | ||
|
|
3a575f8666 | ||
|
|
2eed0cbfb2 | ||
|
|
c4f21561ff | ||
|
|
ff3113cc30 | ||
|
|
1050cc729a | ||
|
|
50c43c6b7a | ||
|
|
de088b4012 | ||
|
|
0d5418e4a0 | ||
|
|
3ef008415e | ||
|
|
6560fea782 | ||
|
|
e26f3ea9d3 | ||
|
|
ce7925f947 | ||
|
|
1076987cc8 | ||
|
|
704456e256 | ||
|
|
73b38b9bcb | ||
|
|
f1ee95e1e2 | ||
|
|
21acba85f4 | ||
|
|
0f7f8aafe3 | ||
|
|
66dd76ab6a | ||
|
|
e86f033a8b | ||
|
|
994c614d5e | ||
|
|
c467d2ec30 | ||
|
|
4d1cde73b3 | ||
|
|
1b354cb040 | ||
|
|
c75e37b58c | ||
|
|
384fe01ddc | ||
|
|
8609fa638f | ||
|
|
992eb51eac | ||
|
|
e7f489da3b | ||
|
|
9481d61c2f | ||
|
|
89dd543677 | ||
|
|
b94c6f9fb7 | ||
|
|
c8409da40b | ||
|
|
eecf87bd0f | ||
|
|
2cf23c203e | ||
|
|
9dd2876664 | ||
|
|
51e6c1ce9c | ||
|
|
8e399accc1 | ||
|
|
53ec40a3cb | ||
|
|
73c126699d | ||
|
|
e93b9f7759 | ||
|
|
6cdc9f9a2b | ||
|
|
2fd7dd136d | ||
|
|
fd0c84d6b9 | ||
|
|
df78d71802 | ||
|
|
7ebbfb062a | ||
|
|
60b2ee0b45 | ||
|
|
e2cde12c2f | ||
|
|
849326d54e | ||
|
|
ab600ee29c | ||
|
|
cdd4ea1644 | ||
|
|
694f20f231 | ||
|
|
6a0db02101 | ||
|
|
14ebd5450b | ||
|
|
b27863ed37 | ||
|
|
8a6be6960b | ||
|
|
af155d8c43 | ||
|
|
449549e1f9 | ||
|
|
da0d3b2e34 | ||
|
|
39cc8bcb3f | ||
|
|
136c9bca26 | ||
|
|
8998faac57 | ||
|
|
9c3d21fec4 | ||
|
|
1a33d51190 | ||
|
|
37884050f8 | ||
|
|
737ba1b242 | ||
|
|
ed43f22815 | ||
|
|
9508c31a1e | ||
|
|
fd47a72d54 | ||
|
|
6acf9fe3cf | ||
|
|
3e8ad4c05c | ||
|
|
ac1d052f35 | ||
|
|
da21231d9b | ||
|
|
c14976bac8 | ||
|
|
b7c3070204 | ||
|
|
f5b18a0a3d | ||
|
|
6cfb7bf965 | ||
|
|
1526b59ff5 | ||
|
|
4966468d99 | ||
|
|
b4357d8e5b | ||
|
|
1895bffb95 | ||
|
|
8d79a38a1e | ||
|
|
bdd894f57e | ||
|
|
64a584dd76 | ||
|
|
4b967375aa | ||
|
|
35441d90a8 | ||
|
|
ea2b1192ff | ||
|
|
9dc2e8e04d | ||
|
|
56e2baeb44 | ||
|
|
907379ef23 | ||
|
|
afb3de360b | ||
|
|
a1587c33e7 | ||
|
|
f01227c997 | ||
|
|
ce50121696 | ||
|
|
e109b01ae5 | ||
|
|
9bd2dd0942 | ||
|
|
0fba2bb8bf | ||
|
|
3ca7d37c49 | ||
|
|
78c4a7cfc5 | ||
|
|
2e7fdd53a5 | ||
|
|
6335887d62 | ||
|
|
3797f258e8 | ||
|
|
8e628d0a4f | ||
|
|
858710cf1b | ||
|
|
5cbce238b1 | ||
|
|
276f79057d | ||
|
|
c50aa35064 | ||
|
|
87ab8b6c40 | ||
|
|
06f248717d | ||
|
|
9b714bbe29 | ||
|
|
2fb8239fe9 | ||
|
|
fbdedaacc3 | ||
|
|
4c577c7dfa | ||
|
|
f15f797d43 | ||
|
|
f92a109f3d | ||
|
|
5eca1a5d04 | ||
|
|
091a766c29 | ||
|
|
f2906f59a2 | ||
|
|
83107857bd | ||
|
|
73d2cab46e | ||
|
|
0563970eb6 | ||
|
|
2818146543 | ||
|
|
6371ac4e36 | ||
|
|
cc077aff99 | ||
|
|
5652298f38 | ||
|
|
931a5f9e63 | ||
|
|
bc05f75304 | ||
|
|
60955f0e1c | ||
|
|
863737c2a3 | ||
|
|
cac0ef7155 | ||
|
|
b361d854bb | ||
|
|
ee83583044 | ||
|
|
632849b334 | ||
|
|
17ba51a359 | ||
|
|
bb485070af | ||
|
|
393e01eb9e | ||
|
|
035edaf435 | ||
|
|
8fda0325d9 | ||
|
|
93d9c045e2 | ||
|
|
db895a0f11 | ||
|
|
ab1d357625 | ||
|
|
2515da12aa | ||
|
|
ac12a33405 | ||
|
|
4cfebb5e49 | ||
|
|
b9ae827c29 | ||
|
|
f69c81e815 | ||
|
|
012228d9d1 | ||
|
|
8efe2284f9 | ||
|
|
530a5b4f12 | ||
|
|
9fe3209db6 | ||
|
|
11087350de | ||
|
|
aebe606374 | ||
|
|
37b492beb2 | ||
|
|
95136cd3c0 | ||
|
|
1653e7626c | ||
|
|
33f7ce129d | ||
|
|
464a4ff46a | ||
|
|
8491c89a0b | ||
|
|
0f7ffb7a66 | ||
|
|
b53aae5516 | ||
|
|
2c7d3562f6 | ||
|
|
9d95232e69 | ||
|
|
62e5c944dd | ||
|
|
af21ea967a | ||
|
|
a9046071cf | ||
|
|
0b6a59ce1d | ||
|
|
f1c7f0a32e | ||
|
|
78c6bb82aa | ||
|
|
fc259cb0b2 | ||
|
|
b0f5db650c | ||
|
|
229987db94 | ||
|
|
e53af4d49e | ||
|
|
962398a947 | ||
|
|
8d6f301075 | ||
|
|
3a93181e89 | ||
|
|
518fe45c4b | ||
|
|
488a30a2c1 | ||
|
|
ffcfa93ed3 | ||
|
|
467d35889f | ||
|
|
6681640b5f | ||
|
|
f65197a0bf | ||
|
|
7c06b22e9d | ||
|
|
bb2bd2dde9 | ||
|
|
7d468be1e3 | ||
|
|
2b0b66a606 | ||
|
|
aa0105a8e2 | ||
|
|
ca5c31fc32 | ||
|
|
e928328bbb | ||
|
|
f502548dff | ||
|
|
7c05566167 | ||
|
|
9803872917 | ||
|
|
698d4c020a | ||
|
|
e69f32e244 | ||
|
|
04e07da274 | ||
|
|
a015869447 | ||
|
|
cab5630ab3 | ||
|
|
41f25269dc | ||
|
|
fdb33d9375 | ||
|
|
1aa5074ca2 | ||
|
|
92d316d557 | ||
|
|
e56fe553d3 | ||
|
|
e4736c4f45 | ||
|
|
145f164c21 | ||
|
|
c9382817e3 | ||
|
|
8709120d66 | ||
|
|
722a74f46b | ||
|
|
0e2629473a | ||
|
|
98031222bc | ||
|
|
db3871aab6 | ||
|
|
a892c38905 | ||
|
|
06d3614bc8 | ||
|
|
3f651d52a3 | ||
|
|
dcf00d313c | ||
|
|
e6f9f2fe59 | ||
|
|
fa29b32cf0 | ||
|
|
7dc229bf7e | ||
|
|
8db2f4a962 | ||
|
|
ddd372a9b0 | ||
|
|
7f41ebe697 | ||
|
|
e8d282ec06 | ||
|
|
0eeea3b76c | ||
|
|
b5eee5968d | ||
|
|
02b6ee5985 | ||
|
|
fea6bf2196 | ||
|
|
6a9a9bcba6 | ||
|
|
775d875174 | ||
|
|
165952255c | ||
|
|
5dbef764df | ||
|
|
60a2a76469 | ||
|
|
3387f3ddfc | ||
|
|
2a172bfcf7 | ||
|
|
431d49f547 | ||
|
|
4dd806eaf8 | ||
|
|
1f9805441e | ||
|
|
b72c9bf087 | ||
|
|
ef537ea791 | ||
|
|
37ee6eb74a | ||
|
|
e72af99a39 | ||
|
|
f1004d2d75 | ||
|
|
73225a06d6 | ||
|
|
355c351203 | ||
|
|
8f8704e2d0 | ||
|
|
c617372e37 | ||
|
|
27a2fdc794 | ||
|
|
94b107beb3 | ||
|
|
abc0446c3a | ||
|
|
6e5e0b4a0a | ||
|
|
b04e639864 | ||
|
|
d30e06c740 | ||
|
|
221b70a5cf | ||
|
|
60f292048d | ||
|
|
efc7a99982 | ||
|
|
ab4e3d9aa8 | ||
|
|
e21c26f2e0 | ||
|
|
4d1c687412 | ||
|
|
7b2636f46a | ||
|
|
aa60206beb | ||
|
|
524f0cc867 | ||
|
|
08c960ba89 | ||
|
|
4dbb24de77 | ||
|
|
7d0d3c31b0 | ||
|
|
32463b370e | ||
|
|
d7d382bcf6 | ||
|
|
8079f5b74d | ||
|
|
a08b551139 | ||
|
|
4f73e60d9b | ||
|
|
f163390970 | ||
|
|
eea9c2788b | ||
|
|
210eb82369 | ||
|
|
e9a02b5579 | ||
|
|
34cc0af99e | ||
|
|
c2a8431624 | ||
|
|
05b6a1b33b | ||
|
|
de6a58009a | ||
|
|
cc9e8f174a | ||
|
|
15026fdc0a | ||
|
|
c6c39d92ac | ||
|
|
e01d67a7d9 | ||
|
|
fd5d828a6d | ||
|
|
7a9b46cede | ||
|
|
f44435e44c | ||
|
|
10e1f5b411 | ||
|
|
28de37dc1a | ||
|
|
5e445f918b | ||
|
|
7dd2a7e61d | ||
|
|
c41b74070b | ||
|
|
d4a0b2689d | ||
|
|
e272215c9e | ||
|
|
2ec80061c8 | ||
|
|
f085caed43 | ||
|
|
c8acc720cf | ||
|
|
4fd5420ddc | ||
|
|
83a3ecb217 | ||
|
|
777f45dd96 | ||
|
|
896801dcb0 | ||
|
|
e1d94955c3 | ||
|
|
c30a4d0ed6 | ||
|
|
0cc97ca25c | ||
|
|
ec5f7b081d | ||
|
|
fda52266ee | ||
|
|
7d893bed2e | ||
|
|
5c96f160ea | ||
|
|
a0cc36715e | ||
|
|
fca7bac461 | ||
|
|
a0d15a4c75 | ||
|
|
41a2b5f3d6 | ||
|
|
455d6dc4a6 | ||
|
|
2119689c8b | ||
|
|
46e6a37d71 | ||
|
|
11ae53b6de | ||
|
|
a4a2d118e4 | ||
|
|
0ac884900d | ||
|
|
231cee0c9f | ||
|
|
b87f4d0893 | ||
|
|
b4a43cca0f | ||
|
|
c8995aa057 | ||
|
|
6062e7cd24 | ||
|
|
21ba2d0939 | ||
|
|
84c7e5fc3a | ||
|
|
607bdaecbc | ||
|
|
8d9d1b9182 | ||
|
|
a1999f12e7 | ||
|
|
e4d9263b9f | ||
|
|
4b15893f12 | ||
|
|
9e72288885 | ||
|
|
403f4a1c5b | ||
|
|
e328dca641 | ||
|
|
eb7db11cd6 | ||
|
|
42314e4e3f | ||
|
|
48ffea35c4 | ||
|
|
e2fe58f9cd | ||
|
|
3a858e81eb | ||
|
|
4d9fd5ee1d | ||
|
|
7ca91c848b | ||
|
|
424b57db65 | ||
|
|
ac09d45a61 | ||
|
|
3c7b9dc9ea | ||
|
|
31f73ec551 | ||
|
|
b2ca376520 | ||
|
|
5fede38237 | ||
|
|
521cd911ee | ||
|
|
c43b191b2e | ||
|
|
c8e9b7d6cf | ||
|
|
7ebc0ee4a5 | ||
|
|
6dceb063f0 | ||
|
|
7c291b58df | ||
|
|
5759328d6a | ||
|
|
f79b1cec03 | ||
|
|
cbe2777e7d | ||
|
|
308efbbddc | ||
|
|
e481cf3488 | ||
|
|
5172dd8e1d | ||
|
|
ec10cefbb2 | ||
|
|
0933f01082 | ||
|
|
bdd3c5fc40 | ||
|
|
1e953feeb8 | ||
|
|
48f86579be | ||
|
|
34e1ceec33 | ||
|
|
edba39d475 | ||
|
|
5385eb2076 | ||
|
|
40597b7647 | ||
|
|
2e5cc47522 | ||
|
|
e6c7eceb57 | ||
|
|
ef2bb2f7b6 | ||
|
|
049dc02a5f | ||
|
|
100f46ab3c | ||
|
|
e21d080e6f | ||
|
|
3740382e76 | ||
|
|
d3864e268b | ||
|
|
d3db3fd174 | ||
|
|
9f9e337350 | ||
|
|
1e84ece07e | ||
|
|
ee4981bd19 | ||
|
|
f8802eea24 | ||
|
|
5b45cab822 | ||
|
|
908f16d769 | ||
|
|
e4852e5799 | ||
|
|
06e672abdd | ||
|
|
efed184276 | ||
|
|
9bd14ec3f3 | ||
|
|
9950f1502e | ||
|
|
43432261e1 | ||
|
|
bd2b8ba1fb | ||
|
|
03742ab6f4 | ||
|
|
7324bff68b | ||
|
|
8dbfa524e5 | ||
|
|
b2ef34f45f | ||
|
|
1ec9c885f3 | ||
|
|
007973aa86 | ||
|
|
500101350f | ||
|
|
524fcae430 | ||
|
|
7ec8fc5b1c | ||
|
|
6af3daa98e | ||
|
|
1d35614cd3 | ||
|
|
91219c9a96 | ||
|
|
65ca7623d5 | ||
|
|
c569977b45 | ||
|
|
2263916a9f | ||
|
|
2cca728dfc | ||
|
|
ed687a1639 | ||
|
|
270199806c | ||
|
|
30a3c5e1b2 | ||
|
|
0c5bd31023 | ||
|
|
84a75b7af6 | ||
|
|
e25be96040 | ||
|
|
7777f7d9aa | ||
|
|
8e37df3fc0 | ||
|
|
88c9e0beb5 | ||
|
|
d751537473 | ||
|
|
60be6c00e9 | ||
|
|
91bf55dc20 | ||
|
|
d20a125742 | ||
|
|
d10bcd2edd | ||
|
|
0b32408bf2 | ||
|
|
269fbd3f24 | ||
|
|
2640ea8c10 | ||
|
|
f3180d3a83 | ||
|
|
7727fe835f | ||
|
|
00fbfbc08e | ||
|
|
44d1183066 | ||
|
|
0ccef797ab | ||
|
|
9914fb905f | ||
|
|
35ecc19ceb | ||
|
|
fa0362f739 | ||
|
|
8ea9084d9e | ||
|
|
eeb31a2250 | ||
|
|
b58c91dbab | ||
|
|
868bf4d3e1 | ||
|
|
a3fc20b393 | ||
|
|
c8dad04b5c | ||
|
|
ee7db393f8 | ||
|
|
e52da9fef2 | ||
|
|
9332df5648 | ||
|
|
120fc2ad71 | ||
|
|
1a7672748f | ||
|
|
fb761438ab |
@@ -33,6 +33,15 @@ stop
|
||||
|
||||
nohup.out*
|
||||
|
||||
# Large directories not needed for Docker builds
|
||||
E2E/playwright-report
|
||||
E2E/test-results
|
||||
Terraform
|
||||
HelmChart
|
||||
Scripts
|
||||
.git
|
||||
GoSDK
|
||||
|
||||
encrypted-credentials.tar
|
||||
encrypted-credentials/
|
||||
|
||||
|
||||
50
.github/workflows/build.yml
vendored
50
.github/workflows/build.yml
vendored
@@ -220,29 +220,6 @@ jobs:
|
||||
command: sudo docker build --no-cache -f ./App/Dockerfile .
|
||||
|
||||
|
||||
docker-build-copilot:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
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
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Copilot/Dockerfile .
|
||||
|
||||
docker-build-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -456,10 +433,10 @@ jobs:
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
@@ -473,3 +450,26 @@ jobs:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./TestServer/Dockerfile .
|
||||
|
||||
docker-build-ai-agent:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
|
||||
# build image for ai agent service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./AIAgent/Dockerfile .
|
||||
|
||||
36
.github/workflows/compile.yml
vendored
36
.github/workflows/compile.yml
vendored
@@ -162,23 +162,6 @@ jobs:
|
||||
max_attempts: 3
|
||||
command: cd Docs && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-copilot:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Copilot
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Copilot && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-nginx:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -404,4 +387,21 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd MCP && npm update @oneuptime/common && 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
|
||||
|
||||
compile-ai-agent:
|
||||
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 AIAgent
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd AIAgent && npm install && npm run compile && npm run dep-check
|
||||
1652
.github/workflows/release.yml
vendored
1652
.github/workflows/release.yml
vendored
File diff suppressed because it is too large
Load Diff
32
.github/workflows/reliability-copilot.yml
vendored
32
.github/workflows/reliability-copilot.yml
vendored
@@ -1,32 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
name: "OneUptime Reliability Copilot"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
# Run every day at midnight UTC
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze Code
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
# Run Reliability Copilot in Docker Container
|
||||
- name: Run Copilot
|
||||
run: |
|
||||
docker run --rm \
|
||||
-e ONEUPTIME_URL="https://test.oneuptime.com" \
|
||||
-e ONEUPTIME_REPOSITORY_SECRET_KEY="${{ secrets.COPILOT_ONEUPTIME_REPOSITORY_SECRET_KEY }}" \
|
||||
-e CODE_REPOSITORY_PASSWORD="${{ github.token }}" \
|
||||
-e CODE_REPOSITORY_USERNAME="simlarsen" \
|
||||
-e OPENAI_API_KEY="${{ secrets.OPENAI_API_KEY }}" \
|
||||
--net=host oneuptime/copilot:test
|
||||
139
.github/workflows/terraform-provider-e2e.yml
vendored
Normal file
139
.github/workflows/terraform-provider-e2e.yml
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
name: Terraform Provider E2E Tests
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
terraform-e2e-tests:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{ github.run_number }}
|
||||
APP_TAG: latest
|
||||
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
tool-cache: true
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
|
||||
- name: Additional Disk Cleanup
|
||||
run: |
|
||||
echo "=== Initial disk space ==="
|
||||
df -h
|
||||
|
||||
echo "=== Removing unnecessary tools and libraries ==="
|
||||
# Remove Android SDK (if not already removed)
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
|
||||
# Remove .NET SDK and runtime
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /etc/skel/.dotnet || true
|
||||
|
||||
# Remove Haskell/GHC
|
||||
sudo rm -rf /opt/ghc || true
|
||||
sudo rm -rf /usr/local/.ghcup || true
|
||||
|
||||
# Remove CodeQL
|
||||
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
|
||||
|
||||
# Remove Boost
|
||||
sudo rm -rf /usr/local/share/boost || true
|
||||
|
||||
# Remove Swift
|
||||
sudo rm -rf /usr/share/swift || true
|
||||
|
||||
# Remove Julia
|
||||
sudo rm -rf /usr/local/julia* || true
|
||||
|
||||
# Remove Rust (cargo/rustup)
|
||||
sudo rm -rf /usr/share/rust || true
|
||||
sudo rm -rf /home/runner/.rustup || true
|
||||
sudo rm -rf /home/runner/.cargo || true
|
||||
|
||||
# Remove unnecessary hostedtoolcache items
|
||||
sudo rm -rf /opt/hostedtoolcache/Python || true
|
||||
sudo rm -rf /opt/hostedtoolcache/PyPy || true
|
||||
sudo rm -rf /opt/hostedtoolcache/Ruby || true
|
||||
sudo rm -rf /opt/hostedtoolcache/Java* || true
|
||||
|
||||
# Remove additional large directories
|
||||
sudo rm -rf /usr/share/miniconda || true
|
||||
sudo rm -rf /usr/local/graalvm || true
|
||||
sudo rm -rf /usr/local/share/chromium || true
|
||||
sudo rm -rf /usr/local/share/powershell || true
|
||||
sudo rm -rf /usr/share/az_* || true
|
||||
|
||||
# Remove documentation
|
||||
sudo rm -rf /usr/share/doc || true
|
||||
sudo rm -rf /usr/share/man || true
|
||||
|
||||
# Remove unnecessary locales
|
||||
sudo rm -rf /usr/share/locale || true
|
||||
|
||||
# Clean apt cache
|
||||
sudo apt-get clean || true
|
||||
sudo rm -rf /var/lib/apt/lists/* || true
|
||||
sudo rm -rf /var/cache/apt/archives/* || true
|
||||
|
||||
# Clean tmp
|
||||
sudo rm -rf /tmp/* || true
|
||||
|
||||
echo "=== Moving Docker data to /mnt for more space ==="
|
||||
# Stop docker
|
||||
sudo systemctl stop docker || true
|
||||
|
||||
# Move docker data directory to /mnt (which has ~70GB)
|
||||
sudo mv /var/lib/docker /mnt/docker || true
|
||||
sudo mkdir -p /var/lib/docker || true
|
||||
sudo mount --bind /mnt/docker /var/lib/docker || true
|
||||
|
||||
# Restart docker
|
||||
sudo systemctl start docker || true
|
||||
|
||||
echo "=== Final disk space ==="
|
||||
df -h
|
||||
|
||||
echo "=== Docker info ==="
|
||||
docker info | grep -E "Docker Root Dir|Storage Driver" || true
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
cache: 'npm'
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
cache: true
|
||||
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@v3
|
||||
with:
|
||||
terraform_version: "1.6.0"
|
||||
terraform_wrapper: false
|
||||
|
||||
- name: Run E2E Tests
|
||||
run: |
|
||||
chmod +x ./E2E/Terraform/e2e-tests/scripts/*.sh
|
||||
./E2E/Terraform/e2e-tests/scripts/index.sh
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
go-version: 'stable'
|
||||
cache: true
|
||||
|
||||
- name: Install Common dependencies
|
||||
|
||||
1622
.github/workflows/test-release.yaml
vendored
1622
.github/workflows/test-release.yaml
vendored
File diff suppressed because it is too large
Load Diff
23
.github/workflows/test.ai-agent.yaml
vendored
Normal file
23
.github/workflows/test.ai-agent.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: AIAgent Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'hotfix-*'
|
||||
- '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 Common && npm install
|
||||
- run: cd AIAgent && npm install && npm run test
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -116,8 +116,8 @@ InfrastructureAgent/oneuptime-infrastructure-agent
|
||||
# Terraform generated files
|
||||
openapi.json
|
||||
|
||||
Terraform/**
|
||||
|
||||
Terraform/terraform-provider-oneuptime/**
|
||||
Terraform/openapi.json
|
||||
TerraformTest/**
|
||||
|
||||
terraform-provider-example/**
|
||||
@@ -128,3 +128,13 @@ MCP/.env
|
||||
MCP/node_modules
|
||||
Dashboard/public/sw.js
|
||||
.claude/settings.local.json
|
||||
Common/.claude/settings.local.json
|
||||
E2E/Terraform/e2e-tests/test-env.sh
|
||||
|
||||
# Terraform state and plan files
|
||||
*.tfplan
|
||||
tfplan
|
||||
terraform.tfstate
|
||||
terraform.tfstate.backup
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
## OneUptime Copilot
|
||||
|
||||
This folder contains the configuration files for the OneUptime Copilot. The Copilot is a tool that automatically improves your code. It can fix issues, improve code quality, and help you ship faster.
|
||||
|
||||
This folder has the following structure:
|
||||
|
||||
- `config.js`: The configuration file for the Copilot. You can customize the Copilot's behavior by changing this file.
|
||||
- `scripts`: A folder containing scripts that the Copilot runs. These are hooks that run at different stages of the Copilot's process.
|
||||
- `on-after-clone.sh`: A script that runs after the Copilot clones your repository.
|
||||
- `on-before-code-change.sh`: A script that runs before the Copilot makes changes to your code.
|
||||
- `on-after-code-change.sh`: A script that runs after the Copilot makes changes to your code.
|
||||
- `on-before-commit.sh`: A script that runs before the Copilot commits changes to your repository.
|
||||
- `on-after-commit.sh`: A script that runs after the Copilot commits changes to your repository.
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// This is the configuration file for the oneuptime copilot.
|
||||
|
||||
const getCopilotConfig = () => {
|
||||
return {
|
||||
// The version of the schema for this configuration file.
|
||||
schemaVersion: '1.0',
|
||||
}
|
||||
}
|
||||
|
||||
export default getCopilotConfig;
|
||||
@@ -1,16 +0,0 @@
|
||||
# Description: Copilot clones your repository and to improve your code.
|
||||
# This scirpt runs after the clone process is completed.
|
||||
# Some of the common tasks you can do here are:
|
||||
# 1. Install dependencies
|
||||
# 2. Run linting
|
||||
# 3. Run tests
|
||||
# 4. Run build
|
||||
# 5. Run any other command that you want to run after the clone process is completed.
|
||||
# If this script fails, copilot will not proceed with the next steps to improve your code.
|
||||
# This step is to ensure that the code is in a good state before we start improving it.
|
||||
# If you want to skip this script, you can keep this file empty.
|
||||
# It's highly recommended to run linting and tests in this script to ensure the code is in a good state.
|
||||
# This scirpt will run on ubuntu machine. So, make sure the commands you run are compatible with ubuntu.
|
||||
|
||||
npm install
|
||||
npm run lint
|
||||
@@ -1,13 +0,0 @@
|
||||
# Description: Copilot will run this script after we make improvements to your code and write it to disk.
|
||||
# Some of the common tasks you can do here are:
|
||||
# 1. Run linting
|
||||
# 2. Run tests
|
||||
# 3. Run build
|
||||
# 4. Run any other command that you want to run after the code is changed.
|
||||
# If this script fails, copilot will not commit the changes to your repository.
|
||||
# This step is to ensure that the code is in a good state before we commit the changes.
|
||||
# If you want to skip this script, you can keep this file empty.
|
||||
# It's highly recommended to run linting and tests in this script to ensure the code is in a good state.
|
||||
# This scirpt will run on ubuntu machine. So, make sure the commands you run are compatible with ubuntu.
|
||||
|
||||
npm run fix
|
||||
@@ -1 +0,0 @@
|
||||
# Description: Copilot will run this script after the commit process is completed.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Description: Copilot will run this script before we make changes to your code.
|
||||
# Some of the common tasks you can do here are:
|
||||
# 1. Install dependencies
|
||||
# 2. Run any other command that you want to run before the code is changed.
|
||||
# If this script fails, copilot will not make any changes to the code.
|
||||
# This step is to ensure that the code is in a good state before we start making changes.
|
||||
# If you want to skip this script, you can keep this file empty.
|
||||
# It's highly recommended to run things like installing dependencies in this script.
|
||||
# This scirpt will run on ubuntu machine. So, make sure the commands you run are compatible with ubuntu.
|
||||
@@ -1 +0,0 @@
|
||||
# Description: Copilot will run this script before we commit the changes to your repository.
|
||||
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@@ -19,20 +19,6 @@
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/TestServer",
|
||||
"name": "Copilot: Debug with Docker",
|
||||
"port": 9985,
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"restart": true,
|
||||
"autoAttachChildProcesses": true
|
||||
},
|
||||
{
|
||||
"name": "Debug Infrastructure Agent",
|
||||
"type": "go",
|
||||
|
||||
95
AIAgent/API/Metrics.ts
Normal file
95
AIAgent/API/Metrics.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPMethod from "Common/Types/API/HTTPMethod";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import API from "Common/Utils/API";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import AIAgentAPIRequest from "../Utils/AIAgentAPIRequest";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
/*
|
||||
* Metrics endpoint for Keda autoscaling
|
||||
* Returns the number of pending AI agent tasks
|
||||
*/
|
||||
router.get(
|
||||
"/queue-size",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
/*
|
||||
* Get the pending task count from OneUptime API
|
||||
* This is the correct metric - the number of tasks waiting to be processed
|
||||
*/
|
||||
const pendingTaskCountUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/api/ai-agent-task/get-pending-task-count");
|
||||
|
||||
logger.debug(
|
||||
"Fetching pending task count from OneUptime API for KEDA scaling",
|
||||
);
|
||||
|
||||
// Use AI Agent authentication (AI Agent key and AI Agent ID)
|
||||
const requestBody: JSONObject = AIAgentAPIRequest.getDefaultRequestBody();
|
||||
|
||||
const result: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
||||
await API.fetch<JSONObject>({
|
||||
method: HTTPMethod.POST,
|
||||
url: pendingTaskCountUrl,
|
||||
data: requestBody,
|
||||
headers: {},
|
||||
});
|
||||
|
||||
if (result instanceof HTTPErrorResponse) {
|
||||
logger.error("Error fetching pending task count from OneUptime API");
|
||||
logger.error(result);
|
||||
throw result;
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
"Pending task count fetched successfully from OneUptime API",
|
||||
);
|
||||
logger.debug(result.data);
|
||||
|
||||
// Extract count from the response - this is the number of tasks pending to be processed
|
||||
let queueSize: number = (result.data["count"] as number) || 0;
|
||||
|
||||
// if string then convert to number
|
||||
if (typeof queueSize === "string") {
|
||||
const parsedQueueSize: number = parseInt(queueSize, 10);
|
||||
if (!isNaN(parsedQueueSize)) {
|
||||
queueSize = parsedQueueSize;
|
||||
} else {
|
||||
logger.warn(
|
||||
"Pending task count is not a valid number, defaulting to 0",
|
||||
);
|
||||
queueSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Pending task count for KEDA: ${queueSize}`);
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
queueSize: queueSize,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error("Error in metrics queue-size endpoint");
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
103
AIAgent/CodeAgents/CodeAgentFactory.ts
Normal file
103
AIAgent/CodeAgents/CodeAgentFactory.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
CodeAgent,
|
||||
CodeAgentType,
|
||||
getCodeAgentDisplayName,
|
||||
} from "./CodeAgentInterface";
|
||||
import OpenCodeAgent from "./OpenCodeAgent";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
// Factory class to create code agents
|
||||
export default class CodeAgentFactory {
|
||||
// Default agent type to use
|
||||
private static defaultAgentType: CodeAgentType = CodeAgentType.OpenCode;
|
||||
|
||||
// Create an agent of the specified type
|
||||
public static createAgent(type: CodeAgentType): CodeAgent {
|
||||
logger.debug(`Creating code agent: ${getCodeAgentDisplayName(type)}`);
|
||||
|
||||
switch (type) {
|
||||
case CodeAgentType.OpenCode:
|
||||
return new OpenCodeAgent();
|
||||
|
||||
/*
|
||||
* Future agents can be added here:
|
||||
* case CodeAgentType.Goose:
|
||||
* return new GooseAgent();
|
||||
* case CodeAgentType.ClaudeCode:
|
||||
* return new ClaudeCodeAgent();
|
||||
*/
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown code agent type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the default agent
|
||||
public static createDefaultAgent(): CodeAgent {
|
||||
return this.createAgent(this.defaultAgentType);
|
||||
}
|
||||
|
||||
// Set the default agent type
|
||||
public static setDefaultAgentType(type: CodeAgentType): void {
|
||||
this.defaultAgentType = type;
|
||||
}
|
||||
|
||||
// Get the default agent type
|
||||
public static getDefaultAgentType(): CodeAgentType {
|
||||
return this.defaultAgentType;
|
||||
}
|
||||
|
||||
// Get all available agent types
|
||||
public static getAvailableAgentTypes(): Array<CodeAgentType> {
|
||||
return Object.values(CodeAgentType);
|
||||
}
|
||||
|
||||
// Check if an agent type is available on the system
|
||||
public static async isAgentAvailable(type: CodeAgentType): Promise<boolean> {
|
||||
try {
|
||||
const agent: CodeAgent = this.createAgent(type);
|
||||
return await agent.isAvailable();
|
||||
} catch (error) {
|
||||
logger.error(`Error checking agent availability for ${type}:`);
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the first available agent
|
||||
public static async getFirstAvailableAgent(): Promise<CodeAgent | null> {
|
||||
for (const type of this.getAvailableAgentTypes()) {
|
||||
if (await this.isAgentAvailable(type)) {
|
||||
return this.createAgent(type);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create agent with fallback
|
||||
* Tries to create the specified type, falls back to first available
|
||||
*/
|
||||
public static async createAgentWithFallback(
|
||||
preferredType?: CodeAgentType,
|
||||
): Promise<CodeAgent> {
|
||||
// If preferred type is specified and available, use it
|
||||
if (preferredType && (await this.isAgentAvailable(preferredType))) {
|
||||
return this.createAgent(preferredType);
|
||||
}
|
||||
|
||||
// Try the default type
|
||||
if (await this.isAgentAvailable(this.defaultAgentType)) {
|
||||
return this.createAgent(this.defaultAgentType);
|
||||
}
|
||||
|
||||
// Fall back to first available
|
||||
const agent: CodeAgent | null = await this.getFirstAvailableAgent();
|
||||
|
||||
if (!agent) {
|
||||
throw new Error("No code agents are available on this system");
|
||||
}
|
||||
|
||||
return agent;
|
||||
}
|
||||
}
|
||||
94
AIAgent/CodeAgents/CodeAgentInterface.ts
Normal file
94
AIAgent/CodeAgents/CodeAgentInterface.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import LlmType from "Common/Types/LLM/LlmType";
|
||||
import TaskLogger from "../Utils/TaskLogger";
|
||||
|
||||
// Configuration for the LLM provider
|
||||
export interface CodeAgentLLMConfig {
|
||||
llmType: LlmType;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
modelName?: string;
|
||||
}
|
||||
|
||||
// The task to be executed by the code agent
|
||||
export interface CodeAgentTask {
|
||||
workingDirectory: string;
|
||||
prompt: string;
|
||||
context?: string;
|
||||
timeoutMs?: number;
|
||||
servicePath?: string; // Path within the repo where the service code lives
|
||||
}
|
||||
|
||||
// Result from the code agent execution
|
||||
export interface CodeAgentResult {
|
||||
success: boolean;
|
||||
filesModified: Array<string>;
|
||||
summary: string;
|
||||
logs: Array<string>;
|
||||
error?: string;
|
||||
exitCode?: number;
|
||||
}
|
||||
|
||||
// Progress event from the code agent
|
||||
export interface CodeAgentProgressEvent {
|
||||
type: "stdout" | "stderr" | "status";
|
||||
message: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
// Callback type for progress events
|
||||
export type CodeAgentProgressCallback = (
|
||||
event: CodeAgentProgressEvent,
|
||||
) => void | Promise<void>;
|
||||
|
||||
/*
|
||||
* Abstract interface for code agents
|
||||
* This allows us to support multiple agents (OpenCode, Goose, Claude Code, etc.)
|
||||
*/
|
||||
export interface CodeAgent {
|
||||
// Name of the agent (e.g., "OpenCode", "Goose", "ClaudeCode")
|
||||
readonly name: string;
|
||||
|
||||
// Initialize the agent with LLM configuration
|
||||
initialize(config: CodeAgentLLMConfig, logger?: TaskLogger): Promise<void>;
|
||||
|
||||
// Execute a task and return the result
|
||||
executeTask(task: CodeAgentTask): Promise<CodeAgentResult>;
|
||||
|
||||
// Set a callback for progress events (streaming output)
|
||||
onProgress(callback: CodeAgentProgressCallback): void;
|
||||
|
||||
// Check if the agent is available on the system
|
||||
isAvailable(): Promise<boolean>;
|
||||
|
||||
// Abort the current task execution
|
||||
abort(): Promise<void>;
|
||||
|
||||
// Clean up any resources used by the agent
|
||||
cleanup(): Promise<void>;
|
||||
}
|
||||
|
||||
// Enum for supported code agent types
|
||||
export enum CodeAgentType {
|
||||
OpenCode = "OpenCode",
|
||||
/*
|
||||
* Future agents:
|
||||
* Goose = "Goose",
|
||||
* ClaudeCode = "ClaudeCode",
|
||||
* Aider = "Aider",
|
||||
*/
|
||||
}
|
||||
|
||||
// Helper function to get display name for agent type
|
||||
export function getCodeAgentDisplayName(type: CodeAgentType): string {
|
||||
switch (type) {
|
||||
case CodeAgentType.OpenCode:
|
||||
return "OpenCode AI";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if an agent type is valid
|
||||
export function isValidCodeAgentType(type: string): type is CodeAgentType {
|
||||
return Object.values(CodeAgentType).includes(type as CodeAgentType);
|
||||
}
|
||||
15
AIAgent/CodeAgents/Index.ts
Normal file
15
AIAgent/CodeAgents/Index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// Export all code agent related types and classes
|
||||
export {
|
||||
CodeAgent,
|
||||
CodeAgentLLMConfig,
|
||||
CodeAgentTask,
|
||||
CodeAgentResult,
|
||||
CodeAgentProgressEvent,
|
||||
CodeAgentProgressCallback,
|
||||
CodeAgentType,
|
||||
getCodeAgentDisplayName,
|
||||
isValidCodeAgentType,
|
||||
} from "./CodeAgentInterface";
|
||||
|
||||
export { default as CodeAgentFactory } from "./CodeAgentFactory";
|
||||
export { default as OpenCodeAgent } from "./OpenCodeAgent";
|
||||
562
AIAgent/CodeAgents/OpenCodeAgent.ts
Normal file
562
AIAgent/CodeAgents/OpenCodeAgent.ts
Normal file
@@ -0,0 +1,562 @@
|
||||
import {
|
||||
CodeAgent,
|
||||
CodeAgentLLMConfig,
|
||||
CodeAgentTask,
|
||||
CodeAgentResult,
|
||||
CodeAgentProgressCallback,
|
||||
CodeAgentProgressEvent,
|
||||
} from "./CodeAgentInterface";
|
||||
import TaskLogger from "../Utils/TaskLogger";
|
||||
import Execute from "Common/Server/Utils/Execute";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import LlmType from "Common/Types/LLM/LlmType";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import path from "path";
|
||||
import { ChildProcess, spawn } from "child_process";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
|
||||
// OpenCode configuration file structure
|
||||
interface OpenCodeConfig {
|
||||
provider?: Record<string, unknown>;
|
||||
model?: string;
|
||||
small_model?: string;
|
||||
disabled_providers?: Array<string>;
|
||||
enabled_providers?: Array<string>;
|
||||
}
|
||||
|
||||
export default class OpenCodeAgent implements CodeAgent {
|
||||
public readonly name: string = "OpenCode";
|
||||
|
||||
private config: CodeAgentLLMConfig | null = null;
|
||||
private taskLogger: TaskLogger | null = null;
|
||||
private progressCallback: CodeAgentProgressCallback | null = null;
|
||||
private currentProcess: ChildProcess | null = null;
|
||||
private aborted: boolean = false;
|
||||
|
||||
// Track original opencode.json content for restoration
|
||||
private originalOpenCodeConfig: string | null = null;
|
||||
private openCodeConfigPath: string | null = null;
|
||||
|
||||
// Default timeout: 30 minutes
|
||||
private static readonly DEFAULT_TIMEOUT_MS: number = 30 * 60 * 1000;
|
||||
|
||||
public async initialize(
|
||||
config: CodeAgentLLMConfig,
|
||||
taskLogger?: TaskLogger,
|
||||
): Promise<void> {
|
||||
this.config = config;
|
||||
|
||||
if (taskLogger) {
|
||||
this.taskLogger = taskLogger;
|
||||
}
|
||||
|
||||
await this.log(`Initializing ${this.name} with ${config.llmType} provider`);
|
||||
}
|
||||
|
||||
public async executeTask(task: CodeAgentTask): Promise<CodeAgentResult> {
|
||||
if (!this.config) {
|
||||
return this.createErrorResult(
|
||||
"Agent not initialized. Call initialize() first.",
|
||||
);
|
||||
}
|
||||
|
||||
this.aborted = false;
|
||||
const logs: Array<string> = [];
|
||||
const timeoutMs: number =
|
||||
task.timeoutMs || OpenCodeAgent.DEFAULT_TIMEOUT_MS;
|
||||
|
||||
try {
|
||||
await this.log(`Executing task in directory: ${task.workingDirectory}`);
|
||||
|
||||
// Create OpenCode config file in the working directory
|
||||
await this.createOpenCodeConfig(task.workingDirectory);
|
||||
|
||||
// Build the prompt
|
||||
const fullPrompt: string = this.buildFullPrompt(task);
|
||||
|
||||
await this.log("Starting OpenCode execution...");
|
||||
logs.push(`Prompt: ${fullPrompt.substring(0, 500)}...`);
|
||||
|
||||
// Execute OpenCode
|
||||
const output: string = await this.runOpenCode(
|
||||
task.workingDirectory,
|
||||
fullPrompt,
|
||||
timeoutMs,
|
||||
(event: CodeAgentProgressEvent) => {
|
||||
logs.push(`[${event.type}] ${event.message}`);
|
||||
if (this.progressCallback) {
|
||||
this.progressCallback(event);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
logs.push(
|
||||
`Output: ${output.substring(0, 1000)}${output.length > 1000 ? "..." : ""}`,
|
||||
);
|
||||
|
||||
if (this.aborted) {
|
||||
return this.createErrorResult("Task was aborted", logs);
|
||||
}
|
||||
|
||||
// Check for modified files
|
||||
const modifiedFiles: Array<string> = await this.getModifiedFiles(
|
||||
task.workingDirectory,
|
||||
);
|
||||
|
||||
// Restore or delete opencode.json before returning
|
||||
await this.restoreOpenCodeConfig();
|
||||
|
||||
await this.log(
|
||||
`OpenCode completed. ${modifiedFiles.length} files modified.`,
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
filesModified: modifiedFiles,
|
||||
summary: this.extractSummary(output),
|
||||
logs,
|
||||
exitCode: 0,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage: string =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
// Restore or delete opencode.json on error
|
||||
await this.restoreOpenCodeConfig();
|
||||
|
||||
await this.log(`OpenCode execution failed: ${errorMessage}`);
|
||||
logs.push(`Error: ${errorMessage}`);
|
||||
|
||||
return this.createErrorResult(errorMessage, logs);
|
||||
}
|
||||
}
|
||||
|
||||
public onProgress(callback: CodeAgentProgressCallback): void {
|
||||
this.progressCallback = callback;
|
||||
}
|
||||
|
||||
public async isAvailable(): Promise<boolean> {
|
||||
try {
|
||||
const result: string = await Execute.executeCommandFile({
|
||||
command: "opencode",
|
||||
args: ["--version"],
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
logger.debug(`OpenCode version check: ${result}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.debug("OpenCode is not available:");
|
||||
logger.debug(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async abort(): Promise<void> {
|
||||
this.aborted = true;
|
||||
|
||||
if (this.currentProcess) {
|
||||
this.currentProcess.kill("SIGTERM");
|
||||
this.currentProcess = null;
|
||||
}
|
||||
|
||||
await this.log("OpenCode execution aborted");
|
||||
}
|
||||
|
||||
public async cleanup(): Promise<void> {
|
||||
if (this.currentProcess) {
|
||||
this.currentProcess.kill("SIGTERM");
|
||||
this.currentProcess = null;
|
||||
}
|
||||
|
||||
this.config = null;
|
||||
this.progressCallback = null;
|
||||
}
|
||||
|
||||
// Create OpenCode configuration file in the workspace
|
||||
private async createOpenCodeConfig(workingDirectory: string): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw new Error("Config not initialized");
|
||||
}
|
||||
|
||||
const configPath: string = path.join(workingDirectory, "opencode.json");
|
||||
this.openCodeConfigPath = configPath;
|
||||
|
||||
// Check if opencode.json already exists and backup its content
|
||||
try {
|
||||
const existingContent: string = await LocalFile.read(configPath);
|
||||
this.originalOpenCodeConfig = existingContent;
|
||||
await this.log("Backed up existing opencode.json from repository");
|
||||
} catch {
|
||||
// File doesn't exist, which is the normal case
|
||||
this.originalOpenCodeConfig = null;
|
||||
}
|
||||
|
||||
const openCodeConfig: OpenCodeConfig = {
|
||||
model: this.getModelString(),
|
||||
small_model: this.getSmallModelString(),
|
||||
};
|
||||
|
||||
// Set enabled providers based on LLM type
|
||||
if (this.config.llmType === LlmType.Anthropic) {
|
||||
openCodeConfig.enabled_providers = ["anthropic"];
|
||||
} else if (this.config.llmType === LlmType.OpenAI) {
|
||||
openCodeConfig.enabled_providers = ["openai"];
|
||||
}
|
||||
|
||||
await LocalFile.write(configPath, JSON.stringify(openCodeConfig, null, 2));
|
||||
|
||||
await this.log(`Created OpenCode config at ${configPath}`);
|
||||
}
|
||||
|
||||
// Restore or delete opencode.json after execution
|
||||
private async restoreOpenCodeConfig(): Promise<void> {
|
||||
if (!this.openCodeConfigPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.originalOpenCodeConfig !== null) {
|
||||
// Restore the original file content
|
||||
await LocalFile.write(
|
||||
this.openCodeConfigPath,
|
||||
this.originalOpenCodeConfig,
|
||||
);
|
||||
await this.log("Restored original opencode.json from repository");
|
||||
} else {
|
||||
// Delete the file we created
|
||||
await LocalFile.deleteFile(this.openCodeConfigPath);
|
||||
await this.log("Deleted generated opencode.json config file");
|
||||
}
|
||||
} catch (error) {
|
||||
// Log but don't throw - cleanup failure shouldn't fail the task
|
||||
logger.warn(`Failed to restore/delete opencode.json: ${error}`);
|
||||
}
|
||||
|
||||
// Reset the tracking variables
|
||||
this.openCodeConfigPath = null;
|
||||
this.originalOpenCodeConfig = null;
|
||||
}
|
||||
|
||||
// Get the model string in OpenCode format (provider/model)
|
||||
private getModelString(): string {
|
||||
if (!this.config) {
|
||||
throw new Error("Config not initialized");
|
||||
}
|
||||
|
||||
const provider: string = this.getProviderName();
|
||||
const model: string = this.config.modelName || this.getDefaultModel();
|
||||
|
||||
return `${provider}/${model}`;
|
||||
}
|
||||
|
||||
// Get the small model string for quick operations
|
||||
private getSmallModelString(): string {
|
||||
if (!this.config) {
|
||||
throw new Error("Config not initialized");
|
||||
}
|
||||
|
||||
const provider: string = this.getProviderName();
|
||||
const smallModel: string = this.getDefaultSmallModel();
|
||||
|
||||
return `${provider}/${smallModel}`;
|
||||
}
|
||||
|
||||
// Get provider name for OpenCode config
|
||||
private getProviderName(): string {
|
||||
if (!this.config) {
|
||||
return "anthropic";
|
||||
}
|
||||
|
||||
switch (this.config.llmType) {
|
||||
case LlmType.Anthropic:
|
||||
return "anthropic";
|
||||
case LlmType.OpenAI:
|
||||
return "openai";
|
||||
case LlmType.Ollama:
|
||||
return "ollama";
|
||||
default:
|
||||
throw new BadDataException("Unsupported LLM type for OpenCode agent");
|
||||
}
|
||||
}
|
||||
|
||||
// Get default model based on provider
|
||||
private getDefaultModel(): string {
|
||||
if (!this.config) {
|
||||
return "claude-sonnet-4-20250514";
|
||||
}
|
||||
|
||||
switch (this.config.llmType) {
|
||||
case LlmType.Anthropic:
|
||||
return "claude-sonnet-4-20250514";
|
||||
case LlmType.OpenAI:
|
||||
return "gpt-4o";
|
||||
case LlmType.Ollama:
|
||||
return "llama2";
|
||||
default:
|
||||
throw new BadDataException("Unsupported LLM type for OpenCode agent");
|
||||
}
|
||||
}
|
||||
|
||||
// Get default small model for quick operations
|
||||
private getDefaultSmallModel(): string {
|
||||
if (!this.config) {
|
||||
return "claude-haiku-4-20250514";
|
||||
}
|
||||
|
||||
switch (this.config.llmType) {
|
||||
case LlmType.Anthropic:
|
||||
return "claude-haiku-4-20250514";
|
||||
case LlmType.OpenAI:
|
||||
return "gpt-4o-mini";
|
||||
case LlmType.Ollama:
|
||||
return "llama2";
|
||||
default:
|
||||
throw new BadDataException("Unsupported LLM type for OpenCode agent");
|
||||
}
|
||||
}
|
||||
|
||||
// Build the full prompt including context
|
||||
private buildFullPrompt(task: CodeAgentTask): string {
|
||||
let prompt: string = task.prompt;
|
||||
|
||||
if (task.context) {
|
||||
prompt = `${task.context}\n\n${prompt}`;
|
||||
}
|
||||
|
||||
if (task.servicePath) {
|
||||
prompt = `The service code is located at: ${task.servicePath}\n\n${prompt}`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
// Run OpenCode in non-interactive mode
|
||||
private async runOpenCode(
|
||||
workingDirectory: string,
|
||||
prompt: string,
|
||||
timeoutMs: number,
|
||||
onOutput: (event: CodeAgentProgressEvent) => void,
|
||||
): Promise<string> {
|
||||
return new Promise(
|
||||
(resolve: (value: string) => void, reject: (reason: Error) => void) => {
|
||||
if (!this.config) {
|
||||
reject(new Error("Config not initialized"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set environment variables for API key
|
||||
const env: NodeJS.ProcessEnv = { ...process.env };
|
||||
|
||||
if (this.config.apiKey) {
|
||||
switch (this.config.llmType) {
|
||||
case LlmType.Anthropic:
|
||||
env["ANTHROPIC_API_KEY"] = this.config.apiKey;
|
||||
break;
|
||||
case LlmType.OpenAI:
|
||||
env["OPENAI_API_KEY"] = this.config.apiKey;
|
||||
break;
|
||||
case LlmType.Ollama:
|
||||
if (this.config.baseUrl) {
|
||||
env["OLLAMA_HOST"] = this.config.baseUrl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Use CLI mode flags to ensure output goes to stdout/stderr instead of TUI
|
||||
* Pass prompt via stdin using "-" to avoid command line argument issues with long prompts
|
||||
*/
|
||||
const args: Array<string> = [
|
||||
"run",
|
||||
"--print-logs",
|
||||
"--log-level",
|
||||
"DEBUG",
|
||||
"--format",
|
||||
"default",
|
||||
"-", // Read prompt from stdin
|
||||
];
|
||||
|
||||
logger.debug(
|
||||
`Running: opencode ${args.join(" ")} (prompt via stdin, ${prompt.length} chars)`,
|
||||
);
|
||||
|
||||
const child: ChildProcess = spawn("opencode", args, {
|
||||
cwd: workingDirectory,
|
||||
env,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
this.currentProcess = child;
|
||||
|
||||
// Write prompt to stdin and close it
|
||||
if (child.stdin) {
|
||||
child.stdin.write(prompt);
|
||||
child.stdin.end();
|
||||
}
|
||||
|
||||
let stdout: string = "";
|
||||
let stderr: string = "";
|
||||
|
||||
// Set timeout
|
||||
const timeout: ReturnType<typeof setTimeout> = setTimeout(() => {
|
||||
if (child.pid) {
|
||||
child.kill("SIGTERM");
|
||||
reject(
|
||||
new Error(
|
||||
`OpenCode execution timed out after ${timeoutMs / 1000} seconds`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}, timeoutMs);
|
||||
|
||||
child.stdout?.on("data", (data: Buffer) => {
|
||||
const text: string = data.toString();
|
||||
stdout += text;
|
||||
|
||||
// Stream to console immediately
|
||||
const trimmedText: string = text.trim();
|
||||
if (trimmedText) {
|
||||
logger.info(`[OpenCode stdout] ${trimmedText}`);
|
||||
|
||||
// Stream to task logger for server-side logging
|
||||
if (this.taskLogger) {
|
||||
this.taskLogger
|
||||
.info(`[OpenCode] ${trimmedText}`)
|
||||
.catch((err: Error) => {
|
||||
logger.error(`Failed to log OpenCode output: ${err.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onOutput({
|
||||
type: "stdout",
|
||||
message: trimmedText,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
});
|
||||
|
||||
child.stderr?.on("data", (data: Buffer) => {
|
||||
const text: string = data.toString();
|
||||
stderr += text;
|
||||
|
||||
// Stream to console immediately
|
||||
const trimmedText: string = text.trim();
|
||||
if (trimmedText) {
|
||||
logger.warn(`[OpenCode stderr] ${trimmedText}`);
|
||||
|
||||
// Stream to task logger for server-side logging
|
||||
if (this.taskLogger) {
|
||||
this.taskLogger
|
||||
.warning(`[OpenCode stderr] ${trimmedText}`)
|
||||
.catch((err: Error) => {
|
||||
logger.error(`Failed to log OpenCode stderr: ${err.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onOutput({
|
||||
type: "stderr",
|
||||
message: trimmedText,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
});
|
||||
|
||||
child.on("close", (code: number | null) => {
|
||||
clearTimeout(timeout);
|
||||
this.currentProcess = null;
|
||||
|
||||
if (this.aborted) {
|
||||
reject(new Error("Execution aborted"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (code === 0 || code === null) {
|
||||
resolve(stdout);
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`OpenCode exited with code ${code}. stderr: ${stderr.substring(0, 500)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
child.on("error", (error: Error) => {
|
||||
clearTimeout(timeout);
|
||||
this.currentProcess = null;
|
||||
reject(error);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Get list of modified files using git
|
||||
private async getModifiedFiles(
|
||||
workingDirectory: string,
|
||||
): Promise<Array<string>> {
|
||||
try {
|
||||
const result: string = await Execute.executeCommandFile({
|
||||
command: "git",
|
||||
args: ["status", "--porcelain"],
|
||||
cwd: workingDirectory,
|
||||
});
|
||||
|
||||
if (!result.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return result
|
||||
.split("\n")
|
||||
.filter((line: string) => {
|
||||
return line.trim().length > 0;
|
||||
})
|
||||
.map((line: string) => {
|
||||
// Git status format: "XY filename"
|
||||
return line.substring(3).trim();
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error getting modified files:");
|
||||
logger.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Extract summary from OpenCode output
|
||||
private extractSummary(output: string): string {
|
||||
// Try to extract a meaningful summary from the output
|
||||
const lines: Array<string> = output.split("\n").filter((line: string) => {
|
||||
return line.trim().length > 0;
|
||||
});
|
||||
|
||||
// Return last few meaningful lines as summary
|
||||
const summaryLines: Array<string> = lines.slice(-5);
|
||||
|
||||
return summaryLines.join("\n") || "No summary available";
|
||||
}
|
||||
|
||||
// Create error result helper
|
||||
private createErrorResult(
|
||||
errorMessage: string,
|
||||
logs: Array<string> = [],
|
||||
): CodeAgentResult {
|
||||
return {
|
||||
success: false,
|
||||
filesModified: [],
|
||||
summary: "",
|
||||
logs,
|
||||
error: errorMessage,
|
||||
exitCode: 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Logging helper
|
||||
private async log(message: string): Promise<void> {
|
||||
if (this.taskLogger) {
|
||||
await this.taskLogger.info(`[${this.name}] ${message}`);
|
||||
} else {
|
||||
logger.debug(`[${this.name}] ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
AIAgent/Config.ts
Normal file
36
AIAgent/Config.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import URL from "Common/Types/API/URL";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Port from "Common/Types/Port";
|
||||
|
||||
if (!process.env["ONEUPTIME_URL"]) {
|
||||
logger.error("ONEUPTIME_URL is not set");
|
||||
process.exit();
|
||||
}
|
||||
|
||||
export const ONEUPTIME_URL: URL = URL.fromString(
|
||||
process.env["ONEUPTIME_URL"] || "https://oneuptime.com",
|
||||
);
|
||||
|
||||
export const AI_AGENT_ID: ObjectID | null = process.env["AI_AGENT_ID"]
|
||||
? new ObjectID(process.env["AI_AGENT_ID"])
|
||||
: null;
|
||||
|
||||
if (!process.env["AI_AGENT_KEY"]) {
|
||||
logger.error("AI_AGENT_KEY is not set");
|
||||
process.exit();
|
||||
}
|
||||
|
||||
export const AI_AGENT_KEY: string = process.env["AI_AGENT_KEY"];
|
||||
|
||||
export const AI_AGENT_NAME: string | null =
|
||||
process.env["AI_AGENT_NAME"] || null;
|
||||
|
||||
export const AI_AGENT_DESCRIPTION: string | null =
|
||||
process.env["AI_AGENT_DESCRIPTION"] || null;
|
||||
|
||||
export const HOSTNAME: string = process.env["HOSTNAME"] || "localhost";
|
||||
|
||||
export const PORT: Port = new Port(
|
||||
process.env["PORT"] ? parseInt(process.env["PORT"]) : 3875,
|
||||
);
|
||||
@@ -1,14 +1,14 @@
|
||||
#
|
||||
# OneUptime-copilot Dockerfile
|
||||
# OneUptime-AIAgent Dockerfile
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:22.3.0
|
||||
FROM public.ecr.aws/docker/library/node:24.9
|
||||
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
|
||||
RUN npm config set fetch-retry-mintimeout 100000
|
||||
RUN npm config set fetch-retry-maxtimeout 600000
|
||||
RUN npm config set fetch-retry-mintimeout 20000
|
||||
RUN npm config set fetch-retry-maxtimeout 60000
|
||||
|
||||
|
||||
ARG GIT_SHA
|
||||
@@ -18,7 +18,11 @@ 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
|
||||
ENV NODE_OPTIONS="--use-openssl-ca"
|
||||
|
||||
## Add Intermediate Certs
|
||||
COPY ./SslCertificates /usr/local/share/ca-certificates
|
||||
RUN update-ca-certificates
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
@@ -27,16 +31,18 @@ RUN if [ -z "$APP_VERSION" ]; then export APP_VERSION=1.0.0; fi
|
||||
|
||||
RUN apt-get update
|
||||
|
||||
# Install bash.
|
||||
# Install bash.
|
||||
RUN apt-get install bash -y && apt-get install curl -y
|
||||
|
||||
# Install python
|
||||
RUN apt-get update && apt-get install -y .gyp python3 make g++
|
||||
# Install OpenCode AI coding assistant
|
||||
RUN curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
# Add OpenCode to PATH (installed to $HOME/.opencode/bin by default)
|
||||
ENV PATH="/root/.opencode/bin:${PATH}"
|
||||
|
||||
#Use bash shell by default
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
|
||||
RUN mkdir -p /usr/src
|
||||
|
||||
WORKDIR /usr/src/Common
|
||||
@@ -47,29 +53,28 @@ RUN npm install
|
||||
COPY ./Common /usr/src/Common
|
||||
|
||||
|
||||
|
||||
ENV PRODUCTION=true
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY ./Copilot/package*.json /usr/src/app/
|
||||
COPY ./AIAgent/package*.json /usr/src/app/
|
||||
# Set version in ./AIAgent/package.json to the APP_VERSION
|
||||
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/app/package.json
|
||||
RUN npm install
|
||||
|
||||
# Create /repository/ directory where the app will store the repository
|
||||
RUN mkdir -p /repository
|
||||
|
||||
# Set the stack trace limit to 30 to show longer stack traces
|
||||
ENV NODE_OPTIONS="--stack-trace-limit=30"
|
||||
# Expose ports.
|
||||
# - 3875: OneUptime-AIAgent
|
||||
EXPOSE 3875
|
||||
|
||||
{{ if eq .Env.ENVIRONMENT "development" }}
|
||||
#Run the app
|
||||
CMD [ "npm", "run", "dev" ]
|
||||
{{ else }}
|
||||
# Copy app source
|
||||
COPY ./Copilot /usr/src/app
|
||||
COPY ./AIAgent /usr/src/app
|
||||
# Bundle app source
|
||||
RUN npm run build
|
||||
RUN npm run compile
|
||||
# Set permission to write logs and cache in case container run as non root
|
||||
RUN chown -R 1000:1000 "/tmp/npm" && chmod -R 2777 "/tmp/npm"
|
||||
#Run the app
|
||||
84
AIAgent/Index.ts
Normal file
84
AIAgent/Index.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { PORT } from "./Config";
|
||||
import AliveJob from "./Jobs/Alive";
|
||||
import startTaskProcessingLoop from "./Jobs/ProcessScheduledTasks";
|
||||
import Register from "./Services/Register";
|
||||
import MetricsAPI from "./API/Metrics";
|
||||
import {
|
||||
getTaskHandlerRegistry,
|
||||
FixExceptionTaskHandler,
|
||||
} from "./TaskHandlers/Index";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import App from "Common/Server/Utils/StartServer";
|
||||
import Telemetry from "Common/Server/Utils/Telemetry";
|
||||
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
|
||||
import "ejs";
|
||||
|
||||
const APP_NAME: string = "ai-agent";
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// Initialize telemetry
|
||||
Telemetry.init({
|
||||
serviceName: APP_NAME,
|
||||
});
|
||||
|
||||
logger.info("AI Agent Service - Starting...");
|
||||
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: PORT,
|
||||
isFrontendApp: false,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
|
||||
// Add metrics API routes for KEDA autoscaling
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
app.use("/metrics", MetricsAPI);
|
||||
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
|
||||
try {
|
||||
// Register this AI Agent.
|
||||
await Register.registerAIAgent();
|
||||
|
||||
logger.debug("AI Agent registered");
|
||||
|
||||
AliveJob();
|
||||
|
||||
// Register task handlers
|
||||
logger.debug("Registering task handlers...");
|
||||
const taskHandlerRegistry: ReturnType<typeof getTaskHandlerRegistry> =
|
||||
getTaskHandlerRegistry();
|
||||
taskHandlerRegistry.register(new FixExceptionTaskHandler());
|
||||
logger.debug(
|
||||
`Registered ${taskHandlerRegistry.getHandlerCount()} task handler(s): ${taskHandlerRegistry.getRegisteredTaskTypes().join(", ")}`,
|
||||
);
|
||||
|
||||
// Start task processing loop (runs in background)
|
||||
startTaskProcessingLoop().catch((err: Error) => {
|
||||
logger.error("Task processing loop failed:");
|
||||
logger.error(err);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error("Register AI Agent failed");
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("App Init Failed:");
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
init().catch((err: Error) => {
|
||||
logger.error(err);
|
||||
logger.error("Exiting node process");
|
||||
process.exit(1);
|
||||
});
|
||||
56
AIAgent/Jobs/Alive.ts
Normal file
56
AIAgent/Jobs/Alive.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import Register from "../Services/Register";
|
||||
import AIAgentAPIRequest from "../Utils/AIAgentAPIRequest";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import BasicCron from "Common/Server/Utils/BasicCron";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
const InitJob: VoidFunction = (): void => {
|
||||
BasicCron({
|
||||
jobName: "AIAgent:Alive",
|
||||
options: {
|
||||
schedule: EVERY_MINUTE,
|
||||
runOnStartup: false,
|
||||
},
|
||||
runFunction: async () => {
|
||||
logger.debug("Checking if AI Agent is alive...");
|
||||
|
||||
const aiAgentId: string | undefined = LocalCache.getString(
|
||||
"AI_AGENT",
|
||||
"AI_AGENT_ID",
|
||||
);
|
||||
|
||||
if (!aiAgentId) {
|
||||
logger.warn(
|
||||
"AI Agent is not registered yet. Skipping alive check. Trying to register AI Agent again...",
|
||||
);
|
||||
await Register.registerAIAgent();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("AI Agent ID: " + aiAgentId.toString());
|
||||
|
||||
const aliveUrl: URL = URL.fromString(ONEUPTIME_URL.toString()).addRoute(
|
||||
"/api/ai-agent/alive",
|
||||
);
|
||||
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: aliveUrl,
|
||||
data: AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
});
|
||||
|
||||
if (result.isSuccess()) {
|
||||
logger.debug("AI Agent update sent to server successfully.");
|
||||
} else {
|
||||
logger.error("Failed to send AI Agent update to server.");
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default InitJob;
|
||||
257
AIAgent/Jobs/ProcessScheduledTasks.ts
Normal file
257
AIAgent/Jobs/ProcessScheduledTasks.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import AIAgentAPIRequest from "../Utils/AIAgentAPIRequest";
|
||||
import AIAgentTaskLog from "../Utils/AIAgentTaskLog";
|
||||
import TaskLogger from "../Utils/TaskLogger";
|
||||
import BackendAPI from "../Utils/BackendAPI";
|
||||
import {
|
||||
getTaskHandlerRegistry,
|
||||
TaskContext,
|
||||
TaskMetadata,
|
||||
TaskHandler,
|
||||
TaskResult,
|
||||
} from "../TaskHandlers/Index";
|
||||
import TaskHandlerRegistry from "../TaskHandlers/TaskHandlerRegistry";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import AIAgentTaskStatus from "Common/Types/AI/AIAgentTaskStatus";
|
||||
import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Sleep from "Common/Types/Sleep";
|
||||
|
||||
// Type for task data from the API
|
||||
interface AIAgentTaskData {
|
||||
_id: string;
|
||||
projectId: string;
|
||||
taskType: AIAgentTaskType;
|
||||
metadata: TaskMetadata;
|
||||
createdAt: string;
|
||||
status?: AIAgentTaskStatus;
|
||||
}
|
||||
|
||||
// Type for API response containing task
|
||||
interface GetPendingTaskResponse {
|
||||
task: AIAgentTaskData | null;
|
||||
}
|
||||
|
||||
const SLEEP_WHEN_NO_TASKS_MS: number = 60 * 1000; // 1 minute
|
||||
|
||||
type ExecuteTaskFunction = (task: AIAgentTaskData) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Execute an AI Agent task using the registered task handler
|
||||
*/
|
||||
const executeTask: ExecuteTaskFunction = async (
|
||||
task: AIAgentTaskData,
|
||||
): Promise<void> => {
|
||||
const taskIdString: string = task._id;
|
||||
const projectIdString: string = task.projectId;
|
||||
const taskId: ObjectID = new ObjectID(taskIdString);
|
||||
const projectId: ObjectID = new ObjectID(projectIdString);
|
||||
const taskType: AIAgentTaskType = task.taskType;
|
||||
const metadata: TaskMetadata = task.metadata || {};
|
||||
const createdAt: Date = new Date(task.createdAt);
|
||||
|
||||
// Get the task handler from the registry
|
||||
const registry: TaskHandlerRegistry = getTaskHandlerRegistry();
|
||||
const handler: TaskHandler | undefined = registry.getHandler(taskType);
|
||||
|
||||
if (!handler) {
|
||||
throw new Error(`No handler registered for task type: ${taskType}`);
|
||||
}
|
||||
|
||||
// Create task logger
|
||||
const taskLogger: TaskLogger = new TaskLogger({
|
||||
taskId: taskIdString,
|
||||
context: `${handler.name}`,
|
||||
});
|
||||
|
||||
// Create backend API client
|
||||
const backendAPI: BackendAPI = new BackendAPI();
|
||||
|
||||
// Build task context
|
||||
const context: TaskContext = {
|
||||
taskId,
|
||||
projectId,
|
||||
taskType,
|
||||
metadata,
|
||||
logger: taskLogger,
|
||||
backendAPI,
|
||||
createdAt,
|
||||
startedAt: new Date(),
|
||||
};
|
||||
|
||||
try {
|
||||
// Log handler starting
|
||||
await taskLogger.info(
|
||||
`Starting ${handler.name} for task type: ${taskType}`,
|
||||
);
|
||||
|
||||
// Validate metadata if the handler supports it
|
||||
if (handler.validateMetadata && !handler.validateMetadata(metadata)) {
|
||||
throw new Error(`Invalid metadata for task type: ${taskType}`);
|
||||
}
|
||||
|
||||
// Execute the task handler
|
||||
const result: TaskResult = await handler.execute(context);
|
||||
|
||||
// Log result
|
||||
if (result.success) {
|
||||
await taskLogger.info(`Task completed: ${result.message}`);
|
||||
|
||||
if (result.pullRequestsCreated && result.pullRequestsCreated > 0) {
|
||||
await taskLogger.info(
|
||||
`Created ${result.pullRequestsCreated} pull request(s): ${result.pullRequestUrls?.join(", ") || ""}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await taskLogger.warning(`Task did not succeed: ${result.message}`);
|
||||
}
|
||||
|
||||
// Flush all pending logs
|
||||
await taskLogger.flush();
|
||||
|
||||
/*
|
||||
* If the task was not successful and we want to report it as an error
|
||||
* Note: Based on user requirements, "no fix found" should be Completed, not Error
|
||||
* Only throw if there was an actual error (not just "no action taken")
|
||||
*/
|
||||
if (!result.success && result.data?.["isError"]) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ensure logs are flushed even on error
|
||||
await taskLogger.flush();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const startTaskProcessingLoop: () => Promise<void> =
|
||||
async (): Promise<void> => {
|
||||
logger.info("Starting AI Agent task processing loop...");
|
||||
|
||||
const getPendingTaskUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/api/ai-agent-task/get-pending-task");
|
||||
|
||||
const updateTaskStatusUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/api/ai-agent-task/update-task-status");
|
||||
|
||||
/* Continuous loop to process tasks */
|
||||
while (true) {
|
||||
try {
|
||||
/* Fetch one scheduled task */
|
||||
const getPendingTaskResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: getPendingTaskUrl,
|
||||
data: AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
});
|
||||
|
||||
if (!getPendingTaskResult.isSuccess()) {
|
||||
logger.error("Failed to fetch pending task from server");
|
||||
logger.debug(
|
||||
`Sleeping for ${SLEEP_WHEN_NO_TASKS_MS / 1000} seconds before retrying...`,
|
||||
);
|
||||
await Sleep.sleep(SLEEP_WHEN_NO_TASKS_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
const responseData: GetPendingTaskResponse =
|
||||
getPendingTaskResult.data as unknown as GetPendingTaskResponse;
|
||||
const task: AIAgentTaskData | null = responseData.task;
|
||||
|
||||
if (!task || !task._id) {
|
||||
logger.debug("No pending tasks available");
|
||||
logger.debug(
|
||||
`Sleeping for ${SLEEP_WHEN_NO_TASKS_MS / 1000} seconds before checking again...`,
|
||||
);
|
||||
await Sleep.sleep(SLEEP_WHEN_NO_TASKS_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
const taskId: string = task._id;
|
||||
const taskType: string = task.taskType || "Unknown";
|
||||
logger.info(`Processing task: ${taskId} (type: ${taskType})`);
|
||||
|
||||
try {
|
||||
/* Mark task as InProgress */
|
||||
const inProgressResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: updateTaskStatusUrl,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: taskId,
|
||||
status: AIAgentTaskStatus.InProgress,
|
||||
},
|
||||
});
|
||||
|
||||
if (!inProgressResult.isSuccess()) {
|
||||
logger.error(
|
||||
`Failed to mark task ${taskId} as InProgress. Skipping.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Send task started log */
|
||||
await AIAgentTaskLog.sendTaskStartedLog(taskId);
|
||||
|
||||
/* Execute the task using the handler system */
|
||||
await executeTask(task);
|
||||
|
||||
/* Mark task as Completed */
|
||||
const completedResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: updateTaskStatusUrl,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: taskId,
|
||||
status: AIAgentTaskStatus.Completed,
|
||||
},
|
||||
});
|
||||
|
||||
if (!completedResult.isSuccess()) {
|
||||
logger.error(`Failed to mark task ${taskId} as Completed`);
|
||||
} else {
|
||||
/* Send task completed log */
|
||||
await AIAgentTaskLog.sendTaskCompletedLog(taskId);
|
||||
logger.info(`Task completed successfully: ${taskId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
/* Mark task as Error with error message */
|
||||
const errorMessage: string =
|
||||
error instanceof Error ? error.message : "Unknown error occurred";
|
||||
|
||||
const errorResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: updateTaskStatusUrl,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: taskId,
|
||||
status: AIAgentTaskStatus.Error,
|
||||
statusMessage: errorMessage,
|
||||
},
|
||||
});
|
||||
|
||||
if (!errorResult.isSuccess()) {
|
||||
logger.error(
|
||||
`Failed to mark task ${taskId} as Error: ${errorMessage}`,
|
||||
);
|
||||
}
|
||||
|
||||
/* Send task error log */
|
||||
await AIAgentTaskLog.sendTaskErrorLog(taskId, errorMessage);
|
||||
|
||||
logger.error(`Task failed: ${taskId} - ${errorMessage}`);
|
||||
logger.error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error in task processing loop:");
|
||||
logger.error(error);
|
||||
logger.debug(
|
||||
`Sleeping for ${SLEEP_WHEN_NO_TASKS_MS / 1000} seconds before retrying...`,
|
||||
);
|
||||
await Sleep.sleep(SLEEP_WHEN_NO_TASKS_MS);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default startTaskProcessingLoop;
|
||||
127
AIAgent/Services/Register.ts
Normal file
127
AIAgent/Services/Register.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
ONEUPTIME_URL,
|
||||
AI_AGENT_ID,
|
||||
AI_AGENT_KEY,
|
||||
AI_AGENT_NAME,
|
||||
AI_AGENT_DESCRIPTION,
|
||||
} from "../Config";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import Sleep from "Common/Types/Sleep";
|
||||
import API from "Common/Utils/API";
|
||||
import { HasClusterKey } from "Common/Server/EnvironmentConfig";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import ClusterKeyAuthorization from "Common/Server/Middleware/ClusterKeyAuthorization";
|
||||
|
||||
export default class Register {
|
||||
public static async registerAIAgent(): Promise<void> {
|
||||
// register AI agent with 10 retries and 30 second interval between each retry.
|
||||
|
||||
let currentRetry: number = 0;
|
||||
|
||||
const maxRetry: number = 10;
|
||||
|
||||
const retryIntervalInSeconds: number = 30;
|
||||
|
||||
while (currentRetry < maxRetry) {
|
||||
try {
|
||||
logger.debug(`Registering AI Agent. Attempt: ${currentRetry + 1}`);
|
||||
await Register._registerAIAgent();
|
||||
logger.debug(`AI Agent registered successfully.`);
|
||||
break;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to register AI Agent. Retrying after ${retryIntervalInSeconds} seconds...`,
|
||||
);
|
||||
logger.error(error);
|
||||
currentRetry++;
|
||||
await Sleep.sleep(retryIntervalInSeconds * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async _registerAIAgent(): Promise<void> {
|
||||
if (HasClusterKey) {
|
||||
// Clustered mode: Auto-register and get ID from server
|
||||
const aiAgentRegistrationUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/api/ai-agent/register");
|
||||
|
||||
logger.debug("Registering AI Agent...");
|
||||
logger.debug("Sending request to: " + aiAgentRegistrationUrl.toString());
|
||||
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: aiAgentRegistrationUrl,
|
||||
data: {
|
||||
aiAgentKey: AI_AGENT_KEY,
|
||||
aiAgentName: AI_AGENT_NAME,
|
||||
aiAgentDescription: AI_AGENT_DESCRIPTION,
|
||||
clusterKey: ClusterKeyAuthorization.getClusterKey(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
logger.error(
|
||||
`Failed to register AI Agent. Status: ${result.statusCode}`,
|
||||
);
|
||||
logger.error(result.data);
|
||||
throw new Error(
|
||||
"Failed to register AI Agent: HTTP " + result.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug("AI Agent Registered");
|
||||
logger.debug(result.data);
|
||||
|
||||
const aiAgentId: string | undefined = result.data["_id"] as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (!aiAgentId) {
|
||||
logger.error("AI Agent ID not found in response");
|
||||
logger.error(result.data);
|
||||
throw new Error("AI Agent ID not found in registration response");
|
||||
}
|
||||
|
||||
LocalCache.setString("AI_AGENT", "AI_AGENT_ID", aiAgentId);
|
||||
} else {
|
||||
// Non-clustered mode: Validate AI agent by sending alive request
|
||||
if (!AI_AGENT_ID) {
|
||||
logger.error("AI_AGENT_ID or ONEUPTIME_SECRET should be set");
|
||||
return process.exit();
|
||||
}
|
||||
|
||||
const aliveUrl: URL = URL.fromString(ONEUPTIME_URL.toString()).addRoute(
|
||||
"/api/ai-agent/alive",
|
||||
);
|
||||
|
||||
logger.debug("Registering AI Agent...");
|
||||
logger.debug("Sending request to: " + aliveUrl.toString());
|
||||
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: aliveUrl,
|
||||
data: {
|
||||
aiAgentKey: AI_AGENT_KEY.toString(),
|
||||
aiAgentId: AI_AGENT_ID.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
if (result.isSuccess()) {
|
||||
LocalCache.setString(
|
||||
"AI_AGENT",
|
||||
"AI_AGENT_ID",
|
||||
AI_AGENT_ID.toString() as string,
|
||||
);
|
||||
logger.debug("AI Agent registered successfully");
|
||||
} else {
|
||||
throw new Error("Failed to register AI Agent: " + result.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`AI Agent ID: ${LocalCache.getString("AI_AGENT", "AI_AGENT_ID") || "Unknown"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
454
AIAgent/TaskHandlers/FixExceptionTaskHandler.ts
Normal file
454
AIAgent/TaskHandlers/FixExceptionTaskHandler.ts
Normal file
@@ -0,0 +1,454 @@
|
||||
import {
|
||||
BaseTaskHandler,
|
||||
TaskContext,
|
||||
TaskResult,
|
||||
TaskMetadata,
|
||||
TaskResultData,
|
||||
} from "./TaskHandlerInterface";
|
||||
import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType";
|
||||
import {
|
||||
LLMConfig,
|
||||
ExceptionDetails,
|
||||
CodeRepositoryInfo,
|
||||
RepositoryToken,
|
||||
} from "../Utils/BackendAPI";
|
||||
import RepositoryManager, {
|
||||
RepositoryConfig,
|
||||
CloneResult,
|
||||
} from "../Utils/RepositoryManager";
|
||||
import PullRequestCreator, {
|
||||
PullRequestResult,
|
||||
} from "../Utils/PullRequestCreator";
|
||||
import WorkspaceManager, { WorkspaceInfo } from "../Utils/WorkspaceManager";
|
||||
import {
|
||||
CodeAgentFactory,
|
||||
CodeAgent,
|
||||
CodeAgentType,
|
||||
CodeAgentTask,
|
||||
CodeAgentResult,
|
||||
CodeAgentProgressEvent,
|
||||
CodeAgentLLMConfig,
|
||||
} from "../CodeAgents/Index";
|
||||
|
||||
// Metadata structure for Fix Exception tasks
|
||||
export interface FixExceptionMetadata extends TaskMetadata {
|
||||
exceptionId: string;
|
||||
serviceId?: string;
|
||||
stackTrace?: string;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export default class FixExceptionTaskHandler extends BaseTaskHandler<FixExceptionMetadata> {
|
||||
public readonly taskType: AIAgentTaskType = AIAgentTaskType.FixException;
|
||||
public readonly name: string = "Fix Exception Handler";
|
||||
|
||||
// Default timeout for code agent execution (30 minutes)
|
||||
private static readonly CODE_AGENT_TIMEOUT_MS: number = 30 * 60 * 1000;
|
||||
|
||||
public async execute(
|
||||
context: TaskContext<FixExceptionMetadata>,
|
||||
): Promise<TaskResult> {
|
||||
const metadata: FixExceptionMetadata = context.metadata;
|
||||
|
||||
await this.log(
|
||||
context,
|
||||
`Starting Fix Exception task for exception: ${metadata.exceptionId} (taskId: ${context.taskId.toString()})`,
|
||||
);
|
||||
|
||||
let workspace: WorkspaceInfo | null = null;
|
||||
|
||||
try {
|
||||
// Step 1: Get LLM configuration for the project
|
||||
await this.log(context, "Fetching LLM provider configuration...");
|
||||
const llmConfig: LLMConfig = await context.backendAPI.getLLMConfig(
|
||||
context.projectId.toString(),
|
||||
);
|
||||
await this.log(
|
||||
context,
|
||||
`Using LLM provider: ${llmConfig.llmType}${llmConfig.modelName ? ` (${llmConfig.modelName})` : ""}`,
|
||||
);
|
||||
|
||||
// Step 2: Get exception details
|
||||
await this.log(context, "Fetching exception details...");
|
||||
const exceptionDetails: ExceptionDetails =
|
||||
await context.backendAPI.getExceptionDetails(metadata.exceptionId);
|
||||
|
||||
if (!exceptionDetails.service) {
|
||||
await this.log(context, "No service linked to this exception", "error");
|
||||
return this.createFailureResult("No service linked to this exception", {
|
||||
isError: true,
|
||||
});
|
||||
}
|
||||
|
||||
await this.log(
|
||||
context,
|
||||
`Exception: ${exceptionDetails.exception.message.substring(0, 100)}...`,
|
||||
);
|
||||
await this.log(context, `Service: ${exceptionDetails.service.name}`);
|
||||
|
||||
// Step 3: Get linked code repositories
|
||||
await this.log(context, "Finding linked code repositories...");
|
||||
const repositories: Array<CodeRepositoryInfo> =
|
||||
await context.backendAPI.getCodeRepositories(
|
||||
exceptionDetails.service.id,
|
||||
);
|
||||
|
||||
if (repositories.length === 0) {
|
||||
await this.log(
|
||||
context,
|
||||
"No code repositories linked to this service",
|
||||
"error",
|
||||
);
|
||||
return this.createFailureResult(
|
||||
"No code repositories linked to this service via Service Catalog",
|
||||
{ isError: true },
|
||||
);
|
||||
}
|
||||
|
||||
await this.log(
|
||||
context,
|
||||
`Found ${repositories.length} linked code repository(ies)`,
|
||||
);
|
||||
|
||||
// Step 4: Create workspace for the task
|
||||
workspace = await WorkspaceManager.createWorkspace(
|
||||
context.taskId.toString(),
|
||||
);
|
||||
await this.log(context, `Created workspace: ${workspace.workspacePath}`);
|
||||
|
||||
// Step 5: Process each repository
|
||||
const pullRequestUrls: Array<string> = [];
|
||||
const errors: Array<string> = [];
|
||||
|
||||
for (const repo of repositories) {
|
||||
try {
|
||||
await this.log(
|
||||
context,
|
||||
`Processing repository: ${repo.organizationName}/${repo.repositoryName}`,
|
||||
);
|
||||
|
||||
const prUrl: string | null = await this.processRepository(
|
||||
context,
|
||||
repo,
|
||||
exceptionDetails,
|
||||
llmConfig,
|
||||
workspace,
|
||||
);
|
||||
|
||||
if (prUrl) {
|
||||
pullRequestUrls.push(prUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage: string =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
errors.push(
|
||||
`${repo.organizationName}/${repo.repositoryName}: ${errorMessage}`,
|
||||
);
|
||||
await this.log(
|
||||
context,
|
||||
`Failed to process repository ${repo.organizationName}/${repo.repositoryName}: ${errorMessage}`,
|
||||
"error",
|
||||
);
|
||||
// Continue with next repository
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Return result
|
||||
if (pullRequestUrls.length > 0) {
|
||||
await this.log(
|
||||
context,
|
||||
`Successfully created ${pullRequestUrls.length} pull request(s)`,
|
||||
);
|
||||
|
||||
const resultData: TaskResultData = {
|
||||
pullRequests: pullRequestUrls,
|
||||
};
|
||||
|
||||
if (errors.length > 0) {
|
||||
resultData.errors = errors;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Created ${pullRequestUrls.length} pull request(s)`,
|
||||
pullRequestsCreated: pullRequestUrls.length,
|
||||
pullRequestUrls,
|
||||
data: resultData,
|
||||
};
|
||||
}
|
||||
|
||||
// No PRs created - mark as error
|
||||
await this.log(
|
||||
context,
|
||||
"No fixes could be applied to any repository",
|
||||
"error",
|
||||
);
|
||||
return this.createFailureResult(
|
||||
errors.length > 0
|
||||
? `No fixes could be applied. Errors: ${errors.join("; ")}`
|
||||
: "No fixes could be applied to any repository",
|
||||
{ isError: true },
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage: string =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
await this.log(context, `Task failed: ${errorMessage}`, "error");
|
||||
// Mark as an actual error (not just "no action taken") so task gets Error status
|
||||
return this.createFailureResult(errorMessage, { isError: true });
|
||||
} finally {
|
||||
// Cleanup workspace
|
||||
if (workspace) {
|
||||
await this.log(context, "Cleaning up workspace...");
|
||||
await WorkspaceManager.deleteWorkspace(workspace.workspacePath);
|
||||
}
|
||||
|
||||
// Flush logs
|
||||
await context.logger.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Process a single repository
|
||||
private async processRepository(
|
||||
context: TaskContext<FixExceptionMetadata>,
|
||||
repo: CodeRepositoryInfo,
|
||||
exceptionDetails: ExceptionDetails,
|
||||
llmConfig: LLMConfig,
|
||||
workspace: WorkspaceInfo,
|
||||
): Promise<string | null> {
|
||||
// Get access token for the repository
|
||||
await this.log(
|
||||
context,
|
||||
`Getting access token for ${repo.organizationName}/${repo.repositoryName}...`,
|
||||
);
|
||||
|
||||
const tokenData: RepositoryToken =
|
||||
await context.backendAPI.getRepositoryToken(repo.id);
|
||||
|
||||
// Clone the repository
|
||||
await this.log(
|
||||
context,
|
||||
`Cloning repository ${repo.organizationName}/${repo.repositoryName}...`,
|
||||
);
|
||||
|
||||
const repoConfig: RepositoryConfig = {
|
||||
organizationName: tokenData.organizationName,
|
||||
repositoryName: tokenData.repositoryName,
|
||||
token: tokenData.token,
|
||||
repositoryUrl: tokenData.repositoryUrl,
|
||||
};
|
||||
|
||||
const repoManager: RepositoryManager = new RepositoryManager(
|
||||
context.logger,
|
||||
);
|
||||
const cloneResult: CloneResult = await repoManager.cloneRepository(
|
||||
repoConfig,
|
||||
workspace.workspacePath,
|
||||
);
|
||||
|
||||
// Create a fix branch
|
||||
const branchName: string = `oneuptime-fix-exception-${context.taskId.toString().substring(0, 8)}`;
|
||||
await this.log(context, `Creating branch: ${branchName}`);
|
||||
await repoManager.createBranch(cloneResult.repositoryPath, branchName);
|
||||
|
||||
// Build the prompt for the code agent
|
||||
const prompt: string = this.buildFixPrompt(
|
||||
exceptionDetails,
|
||||
repo.servicePathInRepository,
|
||||
);
|
||||
|
||||
// Initialize code agent
|
||||
await this.log(context, "Initializing code agent...");
|
||||
const agent: CodeAgent = CodeAgentFactory.createAgent(
|
||||
CodeAgentType.OpenCode,
|
||||
);
|
||||
const agentConfig: CodeAgentLLMConfig = {
|
||||
llmType: llmConfig.llmType,
|
||||
};
|
||||
|
||||
if (llmConfig.apiKey) {
|
||||
agentConfig.apiKey = llmConfig.apiKey;
|
||||
}
|
||||
|
||||
if (llmConfig.baseUrl) {
|
||||
agentConfig.baseUrl = llmConfig.baseUrl;
|
||||
}
|
||||
|
||||
if (llmConfig.modelName) {
|
||||
agentConfig.modelName = llmConfig.modelName;
|
||||
}
|
||||
|
||||
await agent.initialize(agentConfig, context.logger);
|
||||
|
||||
// Set up progress callback to log agent output
|
||||
agent.onProgress((event: CodeAgentProgressEvent) => {
|
||||
context.logger.logProcessOutput("CodeAgent", event.message);
|
||||
});
|
||||
|
||||
// Execute the code agent
|
||||
await this.log(context, "Running code agent to fix exception...");
|
||||
const codeAgentTask: CodeAgentTask = {
|
||||
workingDirectory: cloneResult.repositoryPath,
|
||||
prompt,
|
||||
timeoutMs: FixExceptionTaskHandler.CODE_AGENT_TIMEOUT_MS,
|
||||
};
|
||||
|
||||
if (repo.servicePathInRepository) {
|
||||
codeAgentTask.servicePath = repo.servicePathInRepository;
|
||||
}
|
||||
|
||||
const agentResult: CodeAgentResult = await agent.executeTask(codeAgentTask);
|
||||
|
||||
// Check if any changes were made
|
||||
if (!agentResult.success || agentResult.filesModified.length === 0) {
|
||||
await this.log(
|
||||
context,
|
||||
`Code agent did not make any changes: ${agentResult.error || agentResult.summary}`,
|
||||
"warning",
|
||||
);
|
||||
await agent.cleanup();
|
||||
return null;
|
||||
}
|
||||
|
||||
await this.log(
|
||||
context,
|
||||
`Code agent modified ${agentResult.filesModified.length} file(s)`,
|
||||
);
|
||||
|
||||
// Add all changes and commit
|
||||
await this.log(context, "Committing changes...");
|
||||
await repoManager.addAllChanges(cloneResult.repositoryPath);
|
||||
|
||||
const commitMessage: string = this.buildCommitMessage(exceptionDetails);
|
||||
await repoManager.commitChanges(cloneResult.repositoryPath, commitMessage);
|
||||
|
||||
// Push the branch
|
||||
await this.log(context, `Pushing branch ${branchName}...`);
|
||||
await repoManager.pushBranch(
|
||||
cloneResult.repositoryPath,
|
||||
branchName,
|
||||
repoConfig,
|
||||
);
|
||||
|
||||
// Create pull request
|
||||
await this.log(context, "Creating pull request...");
|
||||
const prCreator: PullRequestCreator = new PullRequestCreator(
|
||||
context.logger,
|
||||
);
|
||||
|
||||
const prTitle: string = PullRequestCreator.generatePRTitle(
|
||||
exceptionDetails.exception.message,
|
||||
);
|
||||
|
||||
const prBody: string = PullRequestCreator.generatePRBody({
|
||||
exceptionMessage: exceptionDetails.exception.message,
|
||||
exceptionType: exceptionDetails.exception.exceptionType,
|
||||
stackTrace: exceptionDetails.exception.stackTrace,
|
||||
serviceName: exceptionDetails.service?.name || "Unknown Service",
|
||||
summary: agentResult.summary,
|
||||
});
|
||||
|
||||
const prResult: PullRequestResult = await prCreator.createPullRequest({
|
||||
token: tokenData.token,
|
||||
organizationName: tokenData.organizationName,
|
||||
repositoryName: tokenData.repositoryName,
|
||||
baseBranch: repo.mainBranchName || "main",
|
||||
headBranch: branchName,
|
||||
title: prTitle,
|
||||
body: prBody,
|
||||
});
|
||||
|
||||
await this.log(context, `Pull request created: ${prResult.htmlUrl}`);
|
||||
|
||||
// Record the PR in the backend
|
||||
await context.backendAPI.recordPullRequest({
|
||||
taskId: context.taskId.toString(),
|
||||
codeRepositoryId: repo.id,
|
||||
pullRequestUrl: prResult.htmlUrl,
|
||||
pullRequestNumber: prResult.number,
|
||||
pullRequestId: prResult.id,
|
||||
title: prResult.title,
|
||||
description: prBody.substring(0, 1000),
|
||||
headRefName: branchName,
|
||||
baseRefName: repo.mainBranchName || "main",
|
||||
});
|
||||
|
||||
// Cleanup agent
|
||||
await agent.cleanup();
|
||||
|
||||
return prResult.htmlUrl;
|
||||
}
|
||||
|
||||
// Build the prompt for the code agent
|
||||
private buildFixPrompt(
|
||||
exceptionDetails: ExceptionDetails,
|
||||
servicePathInRepository: string | null,
|
||||
): string {
|
||||
let prompt: string = `You are a software engineer fixing a bug in a codebase.
|
||||
|
||||
## Exception Information
|
||||
|
||||
**Exception Type:** ${exceptionDetails.exception.exceptionType}
|
||||
|
||||
**Error Message:**
|
||||
${exceptionDetails.exception.message}
|
||||
|
||||
**Stack Trace:**
|
||||
\`\`\`
|
||||
${exceptionDetails.exception.stackTrace}
|
||||
\`\`\`
|
||||
|
||||
## Task
|
||||
|
||||
Please analyze the stack trace and fix the exception. Here are the steps to follow:
|
||||
|
||||
1. Identify the root cause of the exception from the stack trace
|
||||
2. Find the relevant source files in the codebase
|
||||
3. Implement a fix for the issue
|
||||
4. Make sure your fix handles edge cases appropriately
|
||||
5. The fix should be minimal and focused - only change what's necessary
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Do NOT add excessive error handling or logging unless necessary
|
||||
- Do NOT refactor unrelated code
|
||||
- Keep the fix simple and targeted
|
||||
- Preserve existing code style and patterns
|
||||
- If you cannot determine how to fix the issue, explain why
|
||||
|
||||
Please proceed with analyzing and fixing this exception.`;
|
||||
|
||||
if (servicePathInRepository) {
|
||||
prompt = `The service code is located in the \`${servicePathInRepository}\` directory.\n\n${prompt}`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
// Build commit message for the fix
|
||||
private buildCommitMessage(exceptionDetails: ExceptionDetails): string {
|
||||
const shortMessage: string = exceptionDetails.exception.message
|
||||
.replace(/\n/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.substring(0, 50);
|
||||
|
||||
return `fix: ${shortMessage}
|
||||
|
||||
This commit fixes an exception detected by OneUptime.
|
||||
|
||||
Exception Type: ${exceptionDetails.exception.exceptionType}
|
||||
Exception ID: ${exceptionDetails.exception.id}
|
||||
|
||||
Automatically generated by OneUptime AI Agent.`;
|
||||
}
|
||||
|
||||
// Validate metadata
|
||||
public validateMetadata(metadata: FixExceptionMetadata): boolean {
|
||||
return Boolean(metadata.exceptionId);
|
||||
}
|
||||
|
||||
// Get handler description
|
||||
public getDescription(): string {
|
||||
return "Analyzes exceptions detected by OneUptime and attempts to fix them by modifying the source code and creating a pull request.";
|
||||
}
|
||||
}
|
||||
16
AIAgent/TaskHandlers/Index.ts
Normal file
16
AIAgent/TaskHandlers/Index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// Export all task handler related types and classes
|
||||
export {
|
||||
TaskHandler,
|
||||
TaskContext,
|
||||
TaskResult,
|
||||
TaskMetadata,
|
||||
TaskResultData,
|
||||
BaseTaskHandler,
|
||||
} from "./TaskHandlerInterface";
|
||||
|
||||
export {
|
||||
default as TaskHandlerRegistry,
|
||||
getTaskHandlerRegistry,
|
||||
} from "./TaskHandlerRegistry";
|
||||
|
||||
export { default as FixExceptionTaskHandler } from "./FixExceptionTaskHandler";
|
||||
161
AIAgent/TaskHandlers/TaskHandlerInterface.ts
Normal file
161
AIAgent/TaskHandlers/TaskHandlerInterface.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import TaskLogger from "../Utils/TaskLogger";
|
||||
import BackendAPI from "../Utils/BackendAPI";
|
||||
|
||||
// Base interface for task metadata - handlers should define their own specific metadata types
|
||||
export interface TaskMetadata {
|
||||
// All metadata must have at least these optional fields for extensibility
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// Base interface for task result data
|
||||
export interface TaskResultData {
|
||||
// Pull requests created (for Fix Exception tasks)
|
||||
pullRequests?: Array<string>;
|
||||
// Errors encountered during processing
|
||||
errors?: Array<string>;
|
||||
// Flag to indicate if this is an error result (not just "no action taken")
|
||||
isError?: boolean;
|
||||
// Additional data fields
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// Context provided to task handlers
|
||||
export interface TaskContext<TMetadata extends TaskMetadata = TaskMetadata> {
|
||||
// Task identification
|
||||
taskId: ObjectID;
|
||||
projectId: ObjectID;
|
||||
taskType: AIAgentTaskType;
|
||||
|
||||
// Task metadata (varies by task type)
|
||||
metadata: TMetadata;
|
||||
|
||||
// Utilities
|
||||
logger: TaskLogger;
|
||||
backendAPI: BackendAPI;
|
||||
|
||||
// Task timestamps
|
||||
createdAt: Date;
|
||||
startedAt: Date;
|
||||
}
|
||||
|
||||
// Result returned by task handlers
|
||||
export interface TaskResult {
|
||||
// Whether the task completed successfully
|
||||
success: boolean;
|
||||
|
||||
// Human-readable message describing the result
|
||||
message: string;
|
||||
|
||||
// Additional data about the result (optional)
|
||||
data?: TaskResultData;
|
||||
|
||||
// Number of PRs created (for Fix Exception tasks)
|
||||
pullRequestsCreated?: number;
|
||||
|
||||
// List of PR URLs created
|
||||
pullRequestUrls?: Array<string>;
|
||||
}
|
||||
|
||||
// Interface that all task handlers must implement
|
||||
export interface TaskHandler<TMetadata extends TaskMetadata = TaskMetadata> {
|
||||
// The type of task this handler processes
|
||||
readonly taskType: AIAgentTaskType;
|
||||
|
||||
// Human-readable name for the handler
|
||||
readonly name: string;
|
||||
|
||||
// Execute the task and return a result
|
||||
execute(context: TaskContext<TMetadata>): Promise<TaskResult>;
|
||||
|
||||
// Check if this handler can process a given task
|
||||
canHandle(taskType: AIAgentTaskType): boolean;
|
||||
|
||||
// Optional: Validate task metadata before execution
|
||||
validateMetadata?(metadata: TMetadata): boolean;
|
||||
|
||||
// Optional: Get a description of what this handler does
|
||||
getDescription?(): string;
|
||||
}
|
||||
|
||||
// Abstract base class that provides common functionality for task handlers
|
||||
export abstract class BaseTaskHandler<
|
||||
TMetadata extends TaskMetadata = TaskMetadata,
|
||||
> implements TaskHandler<TMetadata>
|
||||
{
|
||||
public abstract readonly taskType: AIAgentTaskType;
|
||||
public abstract readonly name: string;
|
||||
|
||||
public abstract execute(context: TaskContext<TMetadata>): Promise<TaskResult>;
|
||||
|
||||
public canHandle(taskType: AIAgentTaskType): boolean {
|
||||
return taskType === this.taskType;
|
||||
}
|
||||
|
||||
// Create a success result
|
||||
protected createSuccessResult(
|
||||
message: string,
|
||||
data?: TaskResultData,
|
||||
): TaskResult {
|
||||
const result: TaskResult = {
|
||||
success: true,
|
||||
message,
|
||||
};
|
||||
|
||||
if (data) {
|
||||
result.data = data;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create a failure result
|
||||
protected createFailureResult(
|
||||
message: string,
|
||||
data?: TaskResultData,
|
||||
): TaskResult {
|
||||
const result: TaskResult = {
|
||||
success: false,
|
||||
message,
|
||||
};
|
||||
|
||||
if (data) {
|
||||
result.data = data;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create a result for when no action was taken
|
||||
protected createNoActionResult(message: string): TaskResult {
|
||||
return {
|
||||
success: true,
|
||||
message,
|
||||
pullRequestsCreated: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Log to the task logger
|
||||
protected async log(
|
||||
context: TaskContext<TMetadata>,
|
||||
message: string,
|
||||
level: "info" | "debug" | "warning" | "error" = "info",
|
||||
): Promise<void> {
|
||||
switch (level) {
|
||||
case "debug":
|
||||
await context.logger.debug(message);
|
||||
break;
|
||||
case "warning":
|
||||
await context.logger.warning(message);
|
||||
break;
|
||||
case "error":
|
||||
await context.logger.error(message);
|
||||
break;
|
||||
case "info":
|
||||
default:
|
||||
await context.logger.info(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
AIAgent/TaskHandlers/TaskHandlerRegistry.ts
Normal file
93
AIAgent/TaskHandlers/TaskHandlerRegistry.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { TaskHandler } from "./TaskHandlerInterface";
|
||||
import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
/*
|
||||
* Registry for task handlers
|
||||
* Allows dynamic registration and lookup of handlers by task type
|
||||
*/
|
||||
export default class TaskHandlerRegistry {
|
||||
private static instance: TaskHandlerRegistry | null = null;
|
||||
private handlers: Map<AIAgentTaskType, TaskHandler> = new Map();
|
||||
|
||||
// Private constructor for singleton pattern
|
||||
private constructor() {}
|
||||
|
||||
// Get the singleton instance
|
||||
public static getInstance(): TaskHandlerRegistry {
|
||||
if (!TaskHandlerRegistry.instance) {
|
||||
TaskHandlerRegistry.instance = new TaskHandlerRegistry();
|
||||
}
|
||||
return TaskHandlerRegistry.instance;
|
||||
}
|
||||
|
||||
// Reset the singleton (useful for testing)
|
||||
public static resetInstance(): void {
|
||||
TaskHandlerRegistry.instance = null;
|
||||
}
|
||||
|
||||
// Register a task handler
|
||||
public register(handler: TaskHandler): void {
|
||||
if (this.handlers.has(handler.taskType)) {
|
||||
logger.warn(
|
||||
`Overwriting existing handler for task type: ${handler.taskType}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.handlers.set(handler.taskType, handler);
|
||||
logger.debug(
|
||||
`Registered handler "${handler.name}" for task type: ${handler.taskType}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Register multiple handlers at once
|
||||
public registerAll(handlers: Array<TaskHandler>): void {
|
||||
for (const handler of handlers) {
|
||||
this.register(handler);
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister a handler
|
||||
public unregister(taskType: AIAgentTaskType): void {
|
||||
if (this.handlers.has(taskType)) {
|
||||
this.handlers.delete(taskType);
|
||||
logger.debug(`Unregistered handler for task type: ${taskType}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a handler for a specific task type
|
||||
public getHandler(taskType: AIAgentTaskType): TaskHandler | undefined {
|
||||
return this.handlers.get(taskType);
|
||||
}
|
||||
|
||||
// Check if a handler exists for a task type
|
||||
public hasHandler(taskType: AIAgentTaskType): boolean {
|
||||
return this.handlers.has(taskType);
|
||||
}
|
||||
|
||||
// Get all registered task types
|
||||
public getRegisteredTaskTypes(): Array<AIAgentTaskType> {
|
||||
return Array.from(this.handlers.keys());
|
||||
}
|
||||
|
||||
// Get all registered handlers
|
||||
public getAllHandlers(): Array<TaskHandler> {
|
||||
return Array.from(this.handlers.values());
|
||||
}
|
||||
|
||||
// Get the number of registered handlers
|
||||
public getHandlerCount(): number {
|
||||
return this.handlers.size;
|
||||
}
|
||||
|
||||
// Clear all handlers
|
||||
public clear(): void {
|
||||
this.handlers.clear();
|
||||
logger.debug("Cleared all task handlers");
|
||||
}
|
||||
}
|
||||
|
||||
// Export a convenience function to get the registry instance
|
||||
export function getTaskHandlerRegistry(): TaskHandlerRegistry {
|
||||
return TaskHandlerRegistry.getInstance();
|
||||
}
|
||||
17
AIAgent/Utils/AIAgent.ts
Normal file
17
AIAgent/Utils/AIAgent.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
|
||||
export default class AIAgentUtil {
|
||||
public static getAIAgentId(): ObjectID {
|
||||
const id: string | undefined =
|
||||
LocalCache.getString("AI_AGENT", "AI_AGENT_ID") ||
|
||||
process.env["AI_AGENT_ID"];
|
||||
|
||||
if (!id) {
|
||||
throw new BadDataException("AI Agent ID not found");
|
||||
}
|
||||
|
||||
return new ObjectID(id);
|
||||
}
|
||||
}
|
||||
12
AIAgent/Utils/AIAgentAPIRequest.ts
Normal file
12
AIAgent/Utils/AIAgentAPIRequest.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { AI_AGENT_KEY } from "../Config";
|
||||
import AIAgentUtil from "./AIAgent";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
export default class AIAgentAPIRequest {
|
||||
public static getDefaultRequestBody(): JSONObject {
|
||||
return {
|
||||
aiAgentKey: AI_AGENT_KEY,
|
||||
aiAgentId: AIAgentUtil.getAIAgentId().toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
79
AIAgent/Utils/AIAgentTaskLog.ts
Normal file
79
AIAgent/Utils/AIAgentTaskLog.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import AIAgentAPIRequest from "./AIAgentAPIRequest";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import LogSeverity from "Common/Types/Log/LogSeverity";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
export interface SendLogOptions {
|
||||
taskId: string;
|
||||
severity: LogSeverity;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export default class AIAgentTaskLog {
|
||||
private static createLogUrl: URL | null = null;
|
||||
|
||||
private static getCreateLogUrl(): URL {
|
||||
if (!this.createLogUrl) {
|
||||
this.createLogUrl = URL.fromString(ONEUPTIME_URL.toString()).addRoute(
|
||||
"/api/ai-agent-task-log/create-log",
|
||||
);
|
||||
}
|
||||
return this.createLogUrl;
|
||||
}
|
||||
|
||||
public static async sendLog(options: SendLogOptions): Promise<boolean> {
|
||||
try {
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: this.getCreateLogUrl(),
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: options.taskId,
|
||||
severity: options.severity,
|
||||
message: options.message,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
logger.error(`Failed to send log for task ${options.taskId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`Error sending log for task ${options.taskId}:`);
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static async sendTaskStartedLog(taskId: string): Promise<boolean> {
|
||||
return this.sendLog({
|
||||
taskId,
|
||||
severity: LogSeverity.Information,
|
||||
message: "Task execution started",
|
||||
});
|
||||
}
|
||||
|
||||
public static async sendTaskCompletedLog(taskId: string): Promise<boolean> {
|
||||
return this.sendLog({
|
||||
taskId,
|
||||
severity: LogSeverity.Information,
|
||||
message: "Task execution completed successfully",
|
||||
});
|
||||
}
|
||||
|
||||
public static async sendTaskErrorLog(
|
||||
taskId: string,
|
||||
errorMessage: string,
|
||||
): Promise<boolean> {
|
||||
return this.sendLog({
|
||||
taskId,
|
||||
severity: LogSeverity.Error,
|
||||
message: `Task execution failed: ${errorMessage}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
394
AIAgent/Utils/BackendAPI.ts
Normal file
394
AIAgent/Utils/BackendAPI.ts
Normal file
@@ -0,0 +1,394 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import AIAgentAPIRequest from "./AIAgentAPIRequest";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import LlmType from "Common/Types/LLM/LlmType";
|
||||
import AIAgentTaskStatus from "Common/Types/AI/AIAgentTaskStatus";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
// API Response types
|
||||
interface LLMConfigResponse {
|
||||
llmType: LlmType;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
modelName?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface ExceptionResponse {
|
||||
id: string;
|
||||
message: string;
|
||||
stackTrace: string;
|
||||
exceptionType: string;
|
||||
fingerprint: string;
|
||||
}
|
||||
|
||||
interface ServiceResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface ExceptionDetailsResponse {
|
||||
exception: ExceptionResponse;
|
||||
service: ServiceResponse | null;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface CodeRepositoryResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
repositoryHostedAt: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
mainBranchName: string;
|
||||
servicePathInRepository: string | null;
|
||||
gitHubAppInstallationId: string | null;
|
||||
}
|
||||
|
||||
interface CodeRepositoriesResponse {
|
||||
repositories: Array<CodeRepositoryResponse>;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface RepositoryTokenResponse {
|
||||
token: string;
|
||||
expiresAt: string;
|
||||
repositoryUrl: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface RecordPullRequestResponse {
|
||||
success: boolean;
|
||||
pullRequestId: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface UpdateTaskStatusResponse {
|
||||
success?: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// Exported types
|
||||
export interface LLMConfig {
|
||||
llmType: LlmType;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
modelName?: string;
|
||||
}
|
||||
|
||||
export interface ExceptionDetails {
|
||||
exception: {
|
||||
id: string;
|
||||
message: string;
|
||||
stackTrace: string;
|
||||
exceptionType: string;
|
||||
fingerprint: string;
|
||||
};
|
||||
service: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface CodeRepositoryInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
repositoryHostedAt: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
mainBranchName: string;
|
||||
servicePathInRepository: string | null;
|
||||
gitHubAppInstallationId: string | null;
|
||||
}
|
||||
|
||||
export interface RepositoryToken {
|
||||
token: string;
|
||||
expiresAt: Date;
|
||||
repositoryUrl: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
}
|
||||
|
||||
export interface RecordPullRequestOptions {
|
||||
taskId: string;
|
||||
codeRepositoryId: string;
|
||||
pullRequestUrl: string;
|
||||
pullRequestNumber?: number;
|
||||
pullRequestId?: number;
|
||||
title: string;
|
||||
description?: string;
|
||||
headRefName?: string;
|
||||
baseRefName?: string;
|
||||
}
|
||||
|
||||
export interface RecordPullRequestResult {
|
||||
success: boolean;
|
||||
pullRequestId: string;
|
||||
}
|
||||
|
||||
export default class BackendAPI {
|
||||
private baseUrl: URL;
|
||||
|
||||
public constructor() {
|
||||
this.baseUrl = URL.fromString(ONEUPTIME_URL.toString());
|
||||
}
|
||||
|
||||
// Get LLM configuration for a project
|
||||
public async getLLMConfig(projectId: string): Promise<LLMConfig> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/get-llm-config",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
projectId: projectId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: LLMConfigResponse =
|
||||
response.data as unknown as LLMConfigResponse;
|
||||
const errorMessage: string = data?.message || "Failed to get LLM config";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: LLMConfigResponse =
|
||||
response.data as unknown as LLMConfigResponse;
|
||||
|
||||
logger.debug(`Got LLM config for project ${projectId}: ${data.llmType}`);
|
||||
|
||||
const llmConfig: LLMConfig = {
|
||||
llmType: data.llmType,
|
||||
};
|
||||
|
||||
if (data.apiKey) {
|
||||
llmConfig.apiKey = data.apiKey;
|
||||
}
|
||||
|
||||
if (data.baseUrl) {
|
||||
llmConfig.baseUrl = data.baseUrl;
|
||||
}
|
||||
|
||||
if (data.modelName) {
|
||||
llmConfig.modelName = data.modelName;
|
||||
}
|
||||
|
||||
return llmConfig;
|
||||
}
|
||||
|
||||
// Get exception details with telemetry service info
|
||||
public async getExceptionDetails(
|
||||
exceptionId: string,
|
||||
): Promise<ExceptionDetails> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/get-exception-details",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
exceptionId: exceptionId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: ExceptionDetailsResponse =
|
||||
response.data as unknown as ExceptionDetailsResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to get exception details";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: ExceptionDetailsResponse =
|
||||
response.data as unknown as ExceptionDetailsResponse;
|
||||
|
||||
logger.debug(
|
||||
`Got exception details for ${exceptionId}: ${data.exception.message.substring(0, 100)}`,
|
||||
);
|
||||
|
||||
return {
|
||||
exception: {
|
||||
id: data.exception.id,
|
||||
message: data.exception.message,
|
||||
stackTrace: data.exception.stackTrace,
|
||||
exceptionType: data.exception.exceptionType,
|
||||
fingerprint: data.exception.fingerprint,
|
||||
},
|
||||
service: data.service
|
||||
? {
|
||||
id: data.service.id,
|
||||
name: data.service.name,
|
||||
description: data.service.description,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Get code repositories linked to a service
|
||||
public async getCodeRepositories(
|
||||
serviceId: string,
|
||||
): Promise<Array<CodeRepositoryInfo>> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/get-code-repositories",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
serviceId: serviceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: CodeRepositoriesResponse =
|
||||
response.data as unknown as CodeRepositoriesResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to get code repositories";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: CodeRepositoriesResponse =
|
||||
response.data as unknown as CodeRepositoriesResponse;
|
||||
|
||||
logger.debug(
|
||||
`Got ${data.repositories.length} code repositories for service ${serviceId}`,
|
||||
);
|
||||
|
||||
return data.repositories.map((repo: CodeRepositoryResponse) => {
|
||||
return {
|
||||
id: repo.id,
|
||||
name: repo.name,
|
||||
repositoryHostedAt: repo.repositoryHostedAt,
|
||||
organizationName: repo.organizationName,
|
||||
repositoryName: repo.repositoryName,
|
||||
mainBranchName: repo.mainBranchName,
|
||||
servicePathInRepository: repo.servicePathInRepository,
|
||||
gitHubAppInstallationId: repo.gitHubAppInstallationId,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Get access token for a code repository
|
||||
public async getRepositoryToken(
|
||||
codeRepositoryId: string,
|
||||
): Promise<RepositoryToken> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/get-repository-token",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
codeRepositoryId: codeRepositoryId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: RepositoryTokenResponse =
|
||||
response.data as unknown as RepositoryTokenResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to get repository token";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: RepositoryTokenResponse =
|
||||
response.data as unknown as RepositoryTokenResponse;
|
||||
|
||||
logger.debug(
|
||||
`Got access token for repository ${data.organizationName}/${data.repositoryName}`,
|
||||
);
|
||||
|
||||
return {
|
||||
token: data.token,
|
||||
expiresAt: new Date(data.expiresAt),
|
||||
repositoryUrl: data.repositoryUrl,
|
||||
organizationName: data.organizationName,
|
||||
repositoryName: data.repositoryName,
|
||||
};
|
||||
}
|
||||
|
||||
// Record a pull request created by the AI Agent
|
||||
public async recordPullRequest(
|
||||
options: RecordPullRequestOptions,
|
||||
): Promise<RecordPullRequestResult> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/record-pull-request",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: options.taskId,
|
||||
codeRepositoryId: options.codeRepositoryId,
|
||||
pullRequestUrl: options.pullRequestUrl,
|
||||
pullRequestNumber: options.pullRequestNumber,
|
||||
pullRequestId: options.pullRequestId,
|
||||
title: options.title,
|
||||
description: options.description,
|
||||
headRefName: options.headRefName,
|
||||
baseRefName: options.baseRefName,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: RecordPullRequestResponse =
|
||||
response.data as unknown as RecordPullRequestResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to record pull request";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: RecordPullRequestResponse =
|
||||
response.data as unknown as RecordPullRequestResponse;
|
||||
|
||||
logger.debug(`Recorded pull request: ${options.pullRequestUrl}`);
|
||||
|
||||
return {
|
||||
success: data.success,
|
||||
pullRequestId: data.pullRequestId,
|
||||
};
|
||||
}
|
||||
|
||||
// Update task status (wrapper around existing endpoint)
|
||||
public async updateTaskStatus(
|
||||
taskId: string,
|
||||
status: AIAgentTaskStatus,
|
||||
statusMessage?: string,
|
||||
): Promise<void> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-task/update-task-status",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: taskId,
|
||||
status: status,
|
||||
statusMessage: statusMessage,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: UpdateTaskStatusResponse =
|
||||
response.data as unknown as UpdateTaskStatusResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to update task status";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
logger.debug(`Updated task ${taskId} status to ${status}`);
|
||||
}
|
||||
}
|
||||
369
AIAgent/Utils/PullRequestCreator.ts
Normal file
369
AIAgent/Utils/PullRequestCreator.ts
Normal file
@@ -0,0 +1,369 @@
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject, JSONArray } from "Common/Types/JSON";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Headers from "Common/Types/API/Headers";
|
||||
import TaskLogger from "./TaskLogger";
|
||||
|
||||
export interface PullRequestOptions {
|
||||
token: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
baseBranch: string;
|
||||
headBranch: string;
|
||||
title: string;
|
||||
body: string;
|
||||
draft?: boolean;
|
||||
}
|
||||
|
||||
export interface PullRequestResult {
|
||||
id: number;
|
||||
number: number;
|
||||
url: string;
|
||||
htmlUrl: string;
|
||||
state: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default class PullRequestCreator {
|
||||
private static readonly GITHUB_API_BASE: string = "https://api.github.com";
|
||||
private static readonly GITHUB_API_VERSION: string = "2022-11-28";
|
||||
|
||||
private logger: TaskLogger | null = null;
|
||||
|
||||
public constructor(taskLogger?: TaskLogger) {
|
||||
if (taskLogger) {
|
||||
this.logger = taskLogger;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a pull request on GitHub
|
||||
public async createPullRequest(
|
||||
options: PullRequestOptions,
|
||||
): Promise<PullRequestResult> {
|
||||
await this.log(
|
||||
`Creating pull request: ${options.title} (${options.headBranch} -> ${options.baseBranch})`,
|
||||
);
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${options.organizationName}/${options.repositoryName}/pulls`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(options.token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
title: options.title,
|
||||
body: options.body,
|
||||
head: options.headBranch,
|
||||
base: options.baseBranch,
|
||||
draft: options.draft || false,
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const errorData: JSONObject = response.data as JSONObject;
|
||||
const errorMessage: string =
|
||||
(errorData["message"] as string) || "Failed to create pull request";
|
||||
logger.error(`GitHub API error: ${errorMessage}`);
|
||||
throw new Error(`Failed to create pull request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const data: JSONObject = response.data as JSONObject;
|
||||
|
||||
const result: PullRequestResult = {
|
||||
id: data["id"] as number,
|
||||
number: data["number"] as number,
|
||||
url: data["url"] as string,
|
||||
htmlUrl: data["html_url"] as string,
|
||||
state: data["state"] as string,
|
||||
title: data["title"] as string,
|
||||
};
|
||||
|
||||
await this.log(`Pull request created: ${result.htmlUrl}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get an existing pull request by number
|
||||
public async getPullRequest(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
pullNumber: number,
|
||||
): Promise<PullRequestResult | null> {
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/pulls/${pullNumber}`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.get({
|
||||
url,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data: JSONObject = response.data as JSONObject;
|
||||
|
||||
return {
|
||||
id: data["id"] as number,
|
||||
number: data["number"] as number,
|
||||
url: data["url"] as string,
|
||||
htmlUrl: data["html_url"] as string,
|
||||
state: data["state"] as string,
|
||||
title: data["title"] as string,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if a pull request already exists for a branch
|
||||
public async findExistingPullRequest(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
headBranch: string,
|
||||
baseBranch: string,
|
||||
): Promise<PullRequestResult | null> {
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/pulls`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONArray> | HTTPErrorResponse = await API.get(
|
||||
{
|
||||
url,
|
||||
headers,
|
||||
params: {
|
||||
head: `${organizationName}:${headBranch}`,
|
||||
base: baseBranch,
|
||||
state: "open",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pulls: JSONArray = response.data as JSONArray;
|
||||
|
||||
if (pulls.length > 0) {
|
||||
const data: JSONObject = pulls[0] as JSONObject;
|
||||
return {
|
||||
id: data["id"] as number,
|
||||
number: data["number"] as number,
|
||||
url: data["url"] as string,
|
||||
htmlUrl: data["html_url"] as string,
|
||||
state: data["state"] as string,
|
||||
title: data["title"] as string,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update an existing pull request
|
||||
public async updatePullRequest(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
pullNumber: number,
|
||||
updates: { title?: string; body?: string; state?: "open" | "closed" },
|
||||
): Promise<PullRequestResult> {
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/pulls/${pullNumber}`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.patch({
|
||||
url,
|
||||
data: updates,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const errorData: JSONObject = response.data as JSONObject;
|
||||
const errorMessage: string =
|
||||
(errorData["message"] as string) || "Failed to update pull request";
|
||||
throw new Error(`Failed to update pull request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const data: JSONObject = response.data as JSONObject;
|
||||
|
||||
return {
|
||||
id: data["id"] as number,
|
||||
number: data["number"] as number,
|
||||
url: data["url"] as string,
|
||||
htmlUrl: data["html_url"] as string,
|
||||
state: data["state"] as string,
|
||||
title: data["title"] as string,
|
||||
};
|
||||
}
|
||||
|
||||
// Add labels to a pull request
|
||||
public async addLabels(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
issueNumber: number,
|
||||
labels: Array<string>,
|
||||
): Promise<void> {
|
||||
await this.log(`Adding labels to PR #${issueNumber}: ${labels.join(", ")}`);
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/issues/${issueNumber}/labels`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: { labels },
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
logger.warn(`Failed to add labels to PR #${issueNumber}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add reviewers to a pull request
|
||||
public async requestReviewers(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
pullNumber: number,
|
||||
reviewers: Array<string>,
|
||||
teamReviewers?: Array<string>,
|
||||
): Promise<void> {
|
||||
await this.log(`Requesting reviewers for PR #${pullNumber}`);
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/pulls/${pullNumber}/requested_reviewers`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const data: JSONObject = {
|
||||
reviewers,
|
||||
};
|
||||
|
||||
if (teamReviewers && teamReviewers.length > 0) {
|
||||
data["team_reviewers"] = teamReviewers;
|
||||
}
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
logger.warn(`Failed to request reviewers for PR #${pullNumber}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a comment to a pull request
|
||||
public async addComment(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
issueNumber: number,
|
||||
comment: string,
|
||||
): Promise<void> {
|
||||
await this.log(`Adding comment to PR #${issueNumber}`);
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/issues/${issueNumber}/comments`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: { body: comment },
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
logger.warn(`Failed to add comment to PR #${issueNumber}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate PR body from exception details
|
||||
public static generatePRBody(data: {
|
||||
exceptionMessage: string;
|
||||
exceptionType: string;
|
||||
stackTrace: string;
|
||||
serviceName: string;
|
||||
summary: string;
|
||||
}): string {
|
||||
return `## Exception Fix
|
||||
|
||||
This pull request was automatically generated by OneUptime AI Agent to fix an exception.
|
||||
|
||||
### Exception Details
|
||||
|
||||
**Service:** ${data.serviceName}
|
||||
**Type:** ${data.exceptionType}
|
||||
**Message:** ${data.exceptionMessage}
|
||||
|
||||
### Stack Trace
|
||||
|
||||
\`\`\`
|
||||
${data.stackTrace.substring(0, 2000)}${data.stackTrace.length > 2000 ? "\n...(truncated)" : ""}
|
||||
\`\`\`
|
||||
|
||||
### Summary of Changes
|
||||
|
||||
${data.summary}
|
||||
|
||||
---
|
||||
|
||||
*This PR was automatically generated by [OneUptime AI Agent](https://oneuptime.com)*`;
|
||||
}
|
||||
|
||||
// Generate PR title from exception
|
||||
public static generatePRTitle(exceptionMessage: string): string {
|
||||
// Truncate and clean the message for use as a title
|
||||
const cleanMessage: string = exceptionMessage
|
||||
.replace(/\n/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
const maxLength: number = 70;
|
||||
if (cleanMessage.length <= maxLength) {
|
||||
return `fix: ${cleanMessage}`;
|
||||
}
|
||||
|
||||
return `fix: ${cleanMessage.substring(0, maxLength - 3)}...`;
|
||||
}
|
||||
|
||||
// Helper method to get GitHub API headers
|
||||
private getHeaders(token: string): Headers {
|
||||
return {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": PullRequestCreator.GITHUB_API_VERSION,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method for logging
|
||||
private async log(message: string): Promise<void> {
|
||||
if (this.logger) {
|
||||
await this.logger.info(message);
|
||||
} else {
|
||||
logger.debug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
313
AIAgent/Utils/RepositoryManager.ts
Normal file
313
AIAgent/Utils/RepositoryManager.ts
Normal file
@@ -0,0 +1,313 @@
|
||||
import Execute from "Common/Server/Utils/Execute";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import path from "path";
|
||||
import TaskLogger from "./TaskLogger";
|
||||
|
||||
export interface CloneResult {
|
||||
workingDirectory: string;
|
||||
repositoryPath: string;
|
||||
}
|
||||
|
||||
export interface RepositoryConfig {
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
token: string;
|
||||
repositoryUrl?: string;
|
||||
}
|
||||
|
||||
export default class RepositoryManager {
|
||||
private logger: TaskLogger | null = null;
|
||||
|
||||
public constructor(taskLogger?: TaskLogger) {
|
||||
if (taskLogger) {
|
||||
this.logger = taskLogger;
|
||||
}
|
||||
}
|
||||
|
||||
// Clone a repository with token-based authentication
|
||||
public async cloneRepository(
|
||||
config: RepositoryConfig,
|
||||
workDir: string,
|
||||
): Promise<CloneResult> {
|
||||
await this.log(
|
||||
`Cloning repository ${config.organizationName}/${config.repositoryName}...`,
|
||||
);
|
||||
|
||||
// Build the authenticated URL
|
||||
const authUrl: string = this.buildAuthenticatedUrl(config);
|
||||
|
||||
// Ensure the working directory exists
|
||||
await LocalFile.makeDirectory(workDir);
|
||||
|
||||
// Clone the repository
|
||||
await this.runGitCommand(workDir, ["clone", authUrl]);
|
||||
|
||||
const repositoryPath: string = path.join(workDir, config.repositoryName);
|
||||
|
||||
await this.log(`Repository cloned to ${repositoryPath}`);
|
||||
|
||||
// Set git config for the repository
|
||||
await this.setGitConfig(repositoryPath);
|
||||
|
||||
return {
|
||||
workingDirectory: workDir,
|
||||
repositoryPath: repositoryPath,
|
||||
};
|
||||
}
|
||||
|
||||
// Build URL with embedded token for authentication
|
||||
private buildAuthenticatedUrl(config: RepositoryConfig): string {
|
||||
if (config.repositoryUrl) {
|
||||
// If URL is provided, inject token
|
||||
const url: URL = new URL(config.repositoryUrl);
|
||||
url.username = "x-access-token";
|
||||
url.password = config.token;
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
// Default to GitHub
|
||||
return `https://x-access-token:${config.token}@github.com/${config.organizationName}/${config.repositoryName}.git`;
|
||||
}
|
||||
|
||||
// Set git user config for commits
|
||||
private async setGitConfig(repoPath: string): Promise<void> {
|
||||
await this.runGitCommand(repoPath, [
|
||||
"config",
|
||||
"user.name",
|
||||
"OneUptime AI Agent",
|
||||
]);
|
||||
|
||||
await this.runGitCommand(repoPath, [
|
||||
"config",
|
||||
"user.email",
|
||||
"ai-agent@oneuptime.com",
|
||||
]);
|
||||
}
|
||||
|
||||
// Create a new branch
|
||||
public async createBranch(
|
||||
repoPath: string,
|
||||
branchName: string,
|
||||
): Promise<void> {
|
||||
await this.log(`Creating branch: ${branchName}`);
|
||||
|
||||
await this.runGitCommand(repoPath, ["checkout", "-b", branchName]);
|
||||
|
||||
await this.log(`Branch ${branchName} created and checked out`);
|
||||
}
|
||||
|
||||
// Checkout existing branch
|
||||
public async checkoutBranch(
|
||||
repoPath: string,
|
||||
branchName: string,
|
||||
): Promise<void> {
|
||||
await this.log(`Checking out branch: ${branchName}`);
|
||||
|
||||
await this.runGitCommand(repoPath, ["checkout", branchName]);
|
||||
}
|
||||
|
||||
// Create branch if doesn't exist, or checkout if it does
|
||||
public async createOrCheckoutBranch(
|
||||
repoPath: string,
|
||||
branchName: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Check if branch exists locally
|
||||
await this.runGitCommand(repoPath, ["rev-parse", "--verify", branchName]);
|
||||
await this.checkoutBranch(repoPath, branchName);
|
||||
} catch {
|
||||
// Branch doesn't exist, create it
|
||||
await this.createBranch(repoPath, branchName);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all changes to staging
|
||||
public async addAllChanges(repoPath: string): Promise<void> {
|
||||
await this.log("Adding all changes to git staging...");
|
||||
|
||||
await this.runGitCommand(repoPath, ["add", "-A"]);
|
||||
}
|
||||
|
||||
// Check if there are any changes to commit
|
||||
public async hasChanges(repoPath: string): Promise<boolean> {
|
||||
try {
|
||||
const status: string = await this.runGitCommand(repoPath, [
|
||||
"status",
|
||||
"--porcelain",
|
||||
]);
|
||||
return status.trim().length > 0;
|
||||
} catch (error) {
|
||||
logger.error("Error checking for changes:");
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get list of changed files
|
||||
public async getChangedFiles(repoPath: string): Promise<Array<string>> {
|
||||
const status: string = await this.runGitCommand(repoPath, [
|
||||
"status",
|
||||
"--porcelain",
|
||||
]);
|
||||
|
||||
if (!status.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return status
|
||||
.split("\n")
|
||||
.filter((line: string) => {
|
||||
return line.trim().length > 0;
|
||||
})
|
||||
.map((line: string) => {
|
||||
// Status output format is "XY filename" where XY is the status
|
||||
return line.substring(3).trim();
|
||||
});
|
||||
}
|
||||
|
||||
// Commit changes
|
||||
public async commitChanges(repoPath: string, message: string): Promise<void> {
|
||||
await this.log(`Committing changes: ${message.substring(0, 50)}...`);
|
||||
|
||||
await Execute.executeCommandFile({
|
||||
command: "git",
|
||||
args: ["commit", "-m", message],
|
||||
cwd: repoPath,
|
||||
});
|
||||
|
||||
await this.log("Changes committed successfully");
|
||||
}
|
||||
|
||||
// Push branch to remote
|
||||
public async pushBranch(
|
||||
repoPath: string,
|
||||
branchName: string,
|
||||
config: RepositoryConfig,
|
||||
): Promise<void> {
|
||||
await this.log(`Pushing branch ${branchName} to remote...`);
|
||||
|
||||
// Set the remote URL with authentication
|
||||
const authUrl: string = this.buildAuthenticatedUrl(config);
|
||||
|
||||
// Update the remote URL
|
||||
await this.runGitCommand(repoPath, [
|
||||
"remote",
|
||||
"set-url",
|
||||
"origin",
|
||||
authUrl,
|
||||
]);
|
||||
|
||||
// Push with tracking
|
||||
await this.runGitCommand(repoPath, ["push", "-u", "origin", branchName]);
|
||||
|
||||
await this.log(`Branch ${branchName} pushed to remote`);
|
||||
}
|
||||
|
||||
// Get the current branch name
|
||||
public async getCurrentBranch(repoPath: string): Promise<string> {
|
||||
const branch: string = await this.runGitCommand(repoPath, [
|
||||
"rev-parse",
|
||||
"--abbrev-ref",
|
||||
"HEAD",
|
||||
]);
|
||||
return branch.trim();
|
||||
}
|
||||
|
||||
// Get the current commit hash
|
||||
public async getCurrentCommitHash(repoPath: string): Promise<string> {
|
||||
const hash: string = await this.runGitCommand(repoPath, [
|
||||
"rev-parse",
|
||||
"HEAD",
|
||||
]);
|
||||
return hash.trim();
|
||||
}
|
||||
|
||||
// Pull latest changes from remote
|
||||
public async pullChanges(repoPath: string): Promise<void> {
|
||||
await this.log("Pulling latest changes from remote...");
|
||||
|
||||
await this.runGitCommand(repoPath, ["pull"]);
|
||||
}
|
||||
|
||||
// Discard all local changes
|
||||
public async discardChanges(repoPath: string): Promise<void> {
|
||||
await this.log("Discarding all local changes...");
|
||||
|
||||
await this.runGitCommand(repoPath, ["checkout", "."]);
|
||||
await this.runGitCommand(repoPath, ["clean", "-fd"]);
|
||||
}
|
||||
|
||||
// Clean up the repository directory
|
||||
public async cleanup(workDir: string): Promise<void> {
|
||||
await this.log(`Cleaning up workspace: ${workDir}`);
|
||||
|
||||
try {
|
||||
await LocalFile.deleteDirectory(workDir);
|
||||
await this.log("Workspace cleaned up successfully");
|
||||
} catch (error) {
|
||||
logger.error(`Error cleaning up workspace ${workDir}:`);
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get diff of current changes
|
||||
public async getDiff(repoPath: string): Promise<string> {
|
||||
try {
|
||||
const diff: string = await this.runGitCommand(repoPath, ["diff"]);
|
||||
return diff;
|
||||
} catch (error) {
|
||||
logger.error("Error getting diff:");
|
||||
logger.error(error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Get staged diff
|
||||
public async getStagedDiff(repoPath: string): Promise<string> {
|
||||
try {
|
||||
const diff: string = await this.runGitCommand(repoPath, [
|
||||
"diff",
|
||||
"--staged",
|
||||
]);
|
||||
return diff;
|
||||
} catch (error) {
|
||||
logger.error("Error getting staged diff:");
|
||||
logger.error(error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to run git commands
|
||||
private async runGitCommand(
|
||||
repoPath: string,
|
||||
args: Array<string>,
|
||||
): Promise<string> {
|
||||
const cwd: string = path.resolve(repoPath);
|
||||
|
||||
const logArgs: Array<string> = args.map((arg: string) => {
|
||||
// Mask tokens in URLs
|
||||
if (arg.includes("x-access-token:")) {
|
||||
return arg.replace(/x-access-token:[^@]+@/, "x-access-token:***@");
|
||||
}
|
||||
return arg.includes(" ") ? `"${arg}"` : arg;
|
||||
});
|
||||
|
||||
logger.debug(`Executing git command in ${cwd}: git ${logArgs.join(" ")}`);
|
||||
|
||||
return Execute.executeCommandFile({
|
||||
command: "git",
|
||||
args,
|
||||
cwd,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper method for logging
|
||||
private async log(message: string): Promise<void> {
|
||||
if (this.logger) {
|
||||
await this.logger.info(message);
|
||||
} else {
|
||||
logger.debug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
229
AIAgent/Utils/TaskLogger.ts
Normal file
229
AIAgent/Utils/TaskLogger.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import AIAgentAPIRequest from "./AIAgentAPIRequest";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import LogSeverity from "Common/Types/Log/LogSeverity";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
|
||||
export interface TaskLoggerOptions {
|
||||
taskId: string;
|
||||
context?: string;
|
||||
batchSize?: number;
|
||||
flushIntervalMs?: number;
|
||||
}
|
||||
|
||||
interface LogEntry {
|
||||
severity: LogSeverity;
|
||||
message: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export default class TaskLogger {
|
||||
private taskId: string;
|
||||
private context: string | undefined;
|
||||
private logBuffer: Array<LogEntry> = [];
|
||||
private batchSize: number;
|
||||
private flushIntervalMs: number;
|
||||
private flushTimer: ReturnType<typeof setInterval> | null = null;
|
||||
private createLogUrl: URL | null = null;
|
||||
|
||||
public constructor(options: TaskLoggerOptions) {
|
||||
this.taskId = options.taskId;
|
||||
this.context = options.context;
|
||||
this.batchSize = options.batchSize || 10;
|
||||
this.flushIntervalMs = options.flushIntervalMs || 5000; // 5 seconds default
|
||||
|
||||
// Start periodic flush timer
|
||||
this.startFlushTimer();
|
||||
}
|
||||
|
||||
private getCreateLogUrl(): URL {
|
||||
if (!this.createLogUrl) {
|
||||
this.createLogUrl = URL.fromString(ONEUPTIME_URL.toString()).addRoute(
|
||||
"/api/ai-agent-task-log/create-log",
|
||||
);
|
||||
}
|
||||
return this.createLogUrl;
|
||||
}
|
||||
|
||||
private startFlushTimer(): void {
|
||||
this.flushTimer = setInterval(() => {
|
||||
this.flush().catch((err: Error) => {
|
||||
logger.error(`Error flushing logs: ${err.message}`);
|
||||
});
|
||||
}, this.flushIntervalMs);
|
||||
}
|
||||
|
||||
private stopFlushTimer(): void {
|
||||
if (this.flushTimer) {
|
||||
clearInterval(this.flushTimer);
|
||||
this.flushTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private formatMessage(
|
||||
severity: LogSeverity,
|
||||
message: string,
|
||||
timestamp: Date,
|
||||
): string {
|
||||
const timestampStr: string = OneUptimeDate.toDateTimeLocalString(timestamp);
|
||||
const severityStr: string = severity.toUpperCase().padEnd(7);
|
||||
const contextStr: string = this.context ? `[${this.context}] ` : "";
|
||||
|
||||
return `[${timestampStr}] [${severityStr}] ${contextStr}${message}`;
|
||||
}
|
||||
|
||||
private addToBuffer(severity: LogSeverity, message: string): void {
|
||||
const entry: LogEntry = {
|
||||
severity,
|
||||
message,
|
||||
timestamp: OneUptimeDate.getCurrentDate(),
|
||||
};
|
||||
|
||||
this.logBuffer.push(entry);
|
||||
|
||||
// Also log locally for debugging
|
||||
logger.debug(
|
||||
`[Task ${this.taskId}] ${this.formatMessage(entry.severity, entry.message, entry.timestamp)}`,
|
||||
);
|
||||
|
||||
// Auto-flush if buffer is full
|
||||
if (this.logBuffer.length >= this.batchSize) {
|
||||
this.flush().catch((err: Error) => {
|
||||
logger.error(`Error auto-flushing logs: ${err.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async sendLogToServer(
|
||||
severity: LogSeverity,
|
||||
message: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: this.getCreateLogUrl(),
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: this.taskId,
|
||||
severity: severity,
|
||||
message: message,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
logger.error(`Failed to send log for task ${this.taskId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`Error sending log for task ${this.taskId}:`);
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Public logging methods
|
||||
public async debug(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Debug, message);
|
||||
}
|
||||
|
||||
public async info(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Information, message);
|
||||
}
|
||||
|
||||
public async warning(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Warning, message);
|
||||
}
|
||||
|
||||
public async error(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Error, message);
|
||||
// Immediately flush on errors
|
||||
await this.flush();
|
||||
}
|
||||
|
||||
public async trace(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Trace, message);
|
||||
}
|
||||
|
||||
// Log output from external processes like OpenCode
|
||||
public async logProcessOutput(
|
||||
processName: string,
|
||||
output: string,
|
||||
): Promise<void> {
|
||||
const lines: Array<string> = output.split("\n").filter((line: string) => {
|
||||
return line.trim().length > 0;
|
||||
});
|
||||
|
||||
for (const line of lines) {
|
||||
this.addToBuffer(LogSeverity.Information, `[${processName}] ${line}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a code block (useful for stack traces, code snippets, etc.)
|
||||
public async logCodeBlock(
|
||||
title: string,
|
||||
code: string,
|
||||
severity: LogSeverity = LogSeverity.Information,
|
||||
): Promise<void> {
|
||||
const formattedCode: string = `${title}:\n\`\`\`\n${code}\n\`\`\``;
|
||||
this.addToBuffer(severity, formattedCode);
|
||||
}
|
||||
|
||||
// Flush all buffered logs to the server
|
||||
public async flush(): Promise<void> {
|
||||
if (this.logBuffer.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all entries and clear buffer
|
||||
const entries: Array<LogEntry> = [...this.logBuffer];
|
||||
this.logBuffer = [];
|
||||
|
||||
// Send each log entry separately to preserve individual log lines
|
||||
for (const entry of entries) {
|
||||
const formattedMessage: string = this.formatMessage(
|
||||
entry.severity,
|
||||
entry.message,
|
||||
entry.timestamp,
|
||||
);
|
||||
await this.sendLogToServer(entry.severity, formattedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup method - call when task is done
|
||||
public async dispose(): Promise<void> {
|
||||
this.stopFlushTimer();
|
||||
await this.flush();
|
||||
}
|
||||
|
||||
// Helper methods for common log patterns
|
||||
public async logStepStart(stepName: string): Promise<void> {
|
||||
await this.info(`Starting: ${stepName}`);
|
||||
}
|
||||
|
||||
public async logStepComplete(stepName: string): Promise<void> {
|
||||
await this.info(`Completed: ${stepName}`);
|
||||
}
|
||||
|
||||
public async logStepFailed(stepName: string, error: string): Promise<void> {
|
||||
await this.error(`Failed: ${stepName} - ${error}`);
|
||||
}
|
||||
|
||||
// Create a child logger with additional context
|
||||
public createChildLogger(childContext: string): TaskLogger {
|
||||
const fullContext: string = this.context
|
||||
? `${this.context}:${childContext}`
|
||||
: childContext;
|
||||
|
||||
return new TaskLogger({
|
||||
taskId: this.taskId,
|
||||
context: fullContext,
|
||||
batchSize: this.batchSize,
|
||||
flushIntervalMs: this.flushIntervalMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
221
AIAgent/Utils/WorkspaceManager.ts
Normal file
221
AIAgent/Utils/WorkspaceManager.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
export interface WorkspaceInfo {
|
||||
workspacePath: string;
|
||||
taskId: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export default class WorkspaceManager {
|
||||
private static readonly BASE_TEMP_DIR: string = path.join(
|
||||
os.tmpdir(),
|
||||
"oneuptime-ai-agent",
|
||||
);
|
||||
|
||||
// Create a new workspace for a task
|
||||
public static async createWorkspace(taskId: string): Promise<WorkspaceInfo> {
|
||||
const timestamp: number = Date.now();
|
||||
const uniqueId: string = ObjectID.generate().toString().substring(0, 8);
|
||||
const workspaceName: string = `task-${taskId}-${timestamp}-${uniqueId}`;
|
||||
const workspacePath: string = path.join(this.BASE_TEMP_DIR, workspaceName);
|
||||
|
||||
logger.debug(`Creating workspace: ${workspacePath}`);
|
||||
|
||||
// Create the workspace directory
|
||||
await LocalFile.makeDirectory(workspacePath);
|
||||
|
||||
return {
|
||||
workspacePath,
|
||||
taskId,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
// Create a subdirectory within a workspace
|
||||
public static async createSubdirectory(
|
||||
workspacePath: string,
|
||||
subdirectoryName: string,
|
||||
): Promise<string> {
|
||||
const subdirectoryPath: string = path.join(workspacePath, subdirectoryName);
|
||||
await LocalFile.makeDirectory(subdirectoryPath);
|
||||
return subdirectoryPath;
|
||||
}
|
||||
|
||||
// Check if workspace exists
|
||||
public static async workspaceExists(workspacePath: string): Promise<boolean> {
|
||||
try {
|
||||
await LocalFile.readDirectory(workspacePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a workspace and all its contents
|
||||
public static async deleteWorkspace(workspacePath: string): Promise<void> {
|
||||
logger.debug(`Deleting workspace: ${workspacePath}`);
|
||||
|
||||
try {
|
||||
// Verify the path is within our temp directory to prevent accidental deletion
|
||||
const normalizedPath: string = path.normalize(workspacePath);
|
||||
const normalizedBase: string = path.normalize(this.BASE_TEMP_DIR);
|
||||
|
||||
if (!normalizedPath.startsWith(normalizedBase)) {
|
||||
throw new Error(
|
||||
`Security error: Cannot delete path outside workspace base: ${workspacePath}`,
|
||||
);
|
||||
}
|
||||
|
||||
await LocalFile.deleteDirectory(workspacePath);
|
||||
logger.debug(`Workspace deleted: ${workspacePath}`);
|
||||
} catch (error) {
|
||||
logger.error(`Error deleting workspace ${workspacePath}:`);
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Write a file to workspace
|
||||
public static async writeFile(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
content: string,
|
||||
): Promise<string> {
|
||||
const filePath: string = path.join(workspacePath, relativePath);
|
||||
|
||||
// Ensure parent directory exists
|
||||
const parentDir: string = path.dirname(filePath);
|
||||
await LocalFile.makeDirectory(parentDir);
|
||||
|
||||
await LocalFile.write(filePath, content);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// Read a file from workspace
|
||||
public static async readFile(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
): Promise<string> {
|
||||
const filePath: string = path.join(workspacePath, relativePath);
|
||||
return LocalFile.read(filePath);
|
||||
}
|
||||
|
||||
// Check if a file exists in workspace
|
||||
public static async fileExists(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const filePath: string = path.join(workspacePath, relativePath);
|
||||
await LocalFile.read(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a file from workspace
|
||||
public static async deleteFile(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
): Promise<void> {
|
||||
const filePath: string = path.join(workspacePath, relativePath);
|
||||
await LocalFile.deleteFile(filePath);
|
||||
}
|
||||
|
||||
// List files in workspace directory
|
||||
public static async listFiles(workspacePath: string): Promise<Array<string>> {
|
||||
const entries: Array<{ name: string; isDirectory(): boolean }> =
|
||||
await LocalFile.readDirectory(workspacePath);
|
||||
return entries.map((entry: { name: string }) => {
|
||||
return entry.name;
|
||||
});
|
||||
}
|
||||
|
||||
// Get the full path for a relative path in workspace
|
||||
public static getFullPath(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
): string {
|
||||
return path.join(workspacePath, relativePath);
|
||||
}
|
||||
|
||||
// Clean up old workspaces (older than specified hours)
|
||||
public static async cleanupOldWorkspaces(
|
||||
maxAgeHours: number = 24,
|
||||
): Promise<number> {
|
||||
logger.debug(`Cleaning up workspaces older than ${maxAgeHours} hours`);
|
||||
|
||||
let cleanedCount: number = 0;
|
||||
|
||||
try {
|
||||
// Ensure base directory exists
|
||||
try {
|
||||
await LocalFile.readDirectory(this.BASE_TEMP_DIR);
|
||||
} catch {
|
||||
// Base directory doesn't exist, nothing to clean
|
||||
return 0;
|
||||
}
|
||||
|
||||
const entries: Array<{ name: string; isDirectory(): boolean }> =
|
||||
await LocalFile.readDirectory(this.BASE_TEMP_DIR);
|
||||
|
||||
const maxAge: number = maxAgeHours * 60 * 60 * 1000; // Convert to milliseconds
|
||||
const now: number = Date.now();
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const workspacePath: string = path.join(this.BASE_TEMP_DIR, entry.name);
|
||||
|
||||
/*
|
||||
* Try to extract timestamp from directory name
|
||||
* Format: task-{taskId}-{timestamp}-{uniqueId}
|
||||
*/
|
||||
const match: RegExpMatchArray | null = entry.name.match(
|
||||
/task-[^-]+-(\d+)-[^-]+/,
|
||||
);
|
||||
|
||||
if (match) {
|
||||
const timestamp: number = parseInt(match[1] || "0", 10);
|
||||
|
||||
if (now - timestamp > maxAge) {
|
||||
await this.deleteWorkspace(workspacePath);
|
||||
cleanedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error during workspace cleanup:");
|
||||
logger.error(error);
|
||||
}
|
||||
|
||||
logger.debug(`Cleaned up ${cleanedCount} old workspaces`);
|
||||
|
||||
return cleanedCount;
|
||||
}
|
||||
|
||||
// Initialize workspace manager (create base directory if needed)
|
||||
public static async initialize(): Promise<void> {
|
||||
try {
|
||||
await LocalFile.makeDirectory(this.BASE_TEMP_DIR);
|
||||
logger.debug(
|
||||
`Workspace base directory initialized: ${this.BASE_TEMP_DIR}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Error initializing workspace manager:");
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the base temp directory path
|
||||
public static getBaseTempDir(): string {
|
||||
return this.BASE_TEMP_DIR;
|
||||
}
|
||||
}
|
||||
11
AIAgent/nodemon.json
Normal file
11
AIAgent/nodemon.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"watch": [
|
||||
"./",
|
||||
"../Common"
|
||||
],
|
||||
"ext": "ts,tsx",
|
||||
"ignore": ["./node_modules/**", "./public/**", "./bin/**", "./build/**"],
|
||||
"watchOptions": {"useFsEvents": false, "interval": 500},
|
||||
"env": {"TS_NODE_TRANSPILE_ONLY": "1", "TS_NODE_FILES": "false"},
|
||||
"exec": "node -r ts-node/register/transpile-only Index.ts"
|
||||
}
|
||||
4753
AIAgent/package-lock.json
generated
Normal file
4753
AIAgent/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
AIAgent/package.json
Normal file
34
AIAgent/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@oneuptime/ai-agent",
|
||||
"version": "1.0.0",
|
||||
"description": "OneUptime AI Agent",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime"
|
||||
},
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
|
||||
"compile": "tsc",
|
||||
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
|
||||
"dev": "npx nodemon",
|
||||
"audit": "npm audit --audit-level=low",
|
||||
"dep-check": "npm install -g depcheck && depcheck ./ --skip-missing=true",
|
||||
"test": "jest --detectOpenHandles --passWithNoTests",
|
||||
"coverage": "jest --detectOpenHandles --coverage",
|
||||
"debug:test": "node --inspect node_modules/.bin/jest --runInBand ./Tests --detectOpenHandles"
|
||||
},
|
||||
"author": "OneUptime <hello@oneuptime.com> (https://oneuptime.com/)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
"ejs": "^3.1.10",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^17.0.31",
|
||||
"jest": "^28.1.0",
|
||||
"nodemon": "^2.0.20"
|
||||
}
|
||||
}
|
||||
45
AIAgent/tsconfig.json
Normal file
45
AIAgent/tsconfig.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"rootDir": "",
|
||||
"moduleResolution": "node",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"types": ["node", "jest"],
|
||||
"sourceMap": true,
|
||||
"outDir": "build/dist",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -7,8 +7,8 @@ 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
|
||||
RUN npm config set fetch-retry-mintimeout 100000
|
||||
RUN npm config set fetch-retry-maxtimeout 600000
|
||||
RUN npm config set fetch-retry-mintimeout 20000
|
||||
RUN npm config set fetch-retry-maxtimeout 60000
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import CodeExampleGenerator, {
|
||||
CodeExamples,
|
||||
} from "../Utils/CodeExampleGenerator";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import PageNotFoundServiceHandler from "./PageNotFound";
|
||||
import { AppApiRoute } from "Common/ServiceRoute";
|
||||
@@ -8,6 +11,7 @@ import {
|
||||
TableColumnMetadata,
|
||||
} from "Common/Types/Database/TableColumn";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { JSONObject, JSONValue } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Permission, {
|
||||
PermissionHelper,
|
||||
@@ -18,6 +22,296 @@ import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
|
||||
interface ExampleObjects {
|
||||
simpleSelectExample: JSONObject;
|
||||
simpleQueryExample: JSONObject;
|
||||
simpleSortExample: JSONObject;
|
||||
simpleCreateExample: JSONObject;
|
||||
simpleUpdateExample: JSONObject;
|
||||
simpleResponseExample: JSONObject;
|
||||
simpleListResponseExample: Array<JSONObject>;
|
||||
}
|
||||
|
||||
interface ApiCodeExamples {
|
||||
list: CodeExamples;
|
||||
getItem: CodeExamples;
|
||||
count: CodeExamples;
|
||||
create: CodeExamples;
|
||||
update: CodeExamples;
|
||||
delete: CodeExamples;
|
||||
}
|
||||
|
||||
// Helper function to get a default example value based on column type
|
||||
function getDefaultExampleForType(
|
||||
columnType: string | undefined,
|
||||
columnTitle: string | undefined,
|
||||
): JSONValue {
|
||||
const typeStr: string = (columnType || "").toLowerCase();
|
||||
const title: string = (columnTitle || "").toLowerCase();
|
||||
|
||||
if (typeStr.includes("objectid") || typeStr.includes("id")) {
|
||||
return "550e8400-e29b-41d4-a716-446655440000";
|
||||
}
|
||||
if (typeStr.includes("boolean") || typeStr.includes("bool")) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
typeStr.includes("number") ||
|
||||
typeStr.includes("int") ||
|
||||
typeStr.includes("decimal")
|
||||
) {
|
||||
return 100;
|
||||
}
|
||||
if (typeStr.includes("date") || typeStr.includes("time")) {
|
||||
return "2024-01-15T10:30:00.000Z";
|
||||
}
|
||||
if (typeStr.includes("email")) {
|
||||
return "user@example.com";
|
||||
}
|
||||
if (typeStr.includes("phone")) {
|
||||
return "+1-555-123-4567";
|
||||
}
|
||||
if (typeStr.includes("url") || typeStr.includes("link")) {
|
||||
return "https://example.com";
|
||||
}
|
||||
if (typeStr.includes("color")) {
|
||||
return "#3498db";
|
||||
}
|
||||
if (
|
||||
typeStr.includes("markdown") ||
|
||||
typeStr.includes("longtext") ||
|
||||
typeStr.includes("description")
|
||||
) {
|
||||
return `Example ${title || "text"} content`;
|
||||
}
|
||||
if (typeStr.includes("json") || typeStr.includes("object")) {
|
||||
return { key: "value" };
|
||||
}
|
||||
if (typeStr.includes("array")) {
|
||||
return [];
|
||||
}
|
||||
// Default for text fields
|
||||
return `Example ${title || "value"}`;
|
||||
}
|
||||
|
||||
// Helper function to generate example objects from column metadata
|
||||
function generateExampleObjects(
|
||||
tableColumns: Dictionary<TableColumnMetadata>,
|
||||
exampleObjectID: string,
|
||||
): ExampleObjects {
|
||||
const simpleSelectExample: JSONObject = {};
|
||||
const simpleQueryExample: JSONObject = {};
|
||||
const simpleSortExample: JSONObject = {};
|
||||
const simpleCreateExample: JSONObject = {};
|
||||
const simpleUpdateExample: JSONObject = {};
|
||||
const simpleResponseExample: JSONObject = {
|
||||
_id: exampleObjectID,
|
||||
};
|
||||
|
||||
// Sort columns: prioritize fields with examples, then required, then alphabetically
|
||||
const sortedColumnKeys: Array<string> = Object.keys(tableColumns).sort(
|
||||
(a: string, b: string) => {
|
||||
const aHasExample: boolean = tableColumns[a]?.example !== undefined;
|
||||
const bHasExample: boolean = tableColumns[b]?.example !== undefined;
|
||||
const aRequired: boolean = tableColumns[a]?.required || false;
|
||||
const bRequired: boolean = tableColumns[b]?.required || false;
|
||||
|
||||
// Prioritize fields with examples
|
||||
if (aHasExample && !bHasExample) {
|
||||
return -1;
|
||||
}
|
||||
if (!aHasExample && bHasExample) {
|
||||
return 1;
|
||||
}
|
||||
// Then prioritize required fields
|
||||
if (aRequired && !bRequired) {
|
||||
return -1;
|
||||
}
|
||||
if (!aRequired && bRequired) {
|
||||
return 1;
|
||||
}
|
||||
return a.localeCompare(b);
|
||||
},
|
||||
);
|
||||
|
||||
let selectCount: number = 0;
|
||||
let createCount: number = 0;
|
||||
let updateCount: number = 0;
|
||||
|
||||
for (const key of sortedColumnKeys) {
|
||||
const column: TableColumnMetadata | undefined = tableColumns[key];
|
||||
if (!column) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const accessControl: ColumnAccessControl | undefined = (
|
||||
column as unknown as JSONObject
|
||||
)["permissions"] as ColumnAccessControl | undefined;
|
||||
|
||||
// Get the example value - use defined example or generate a default
|
||||
const exampleValue: JSONValue =
|
||||
column.example !== undefined
|
||||
? (column.example as JSONValue)
|
||||
: getDefaultExampleForType(column.type?.toString(), column.title);
|
||||
|
||||
/*
|
||||
* Add to select example (limit to 5 fields for readability)
|
||||
* Also add the field to response example so response matches select
|
||||
*/
|
||||
if (
|
||||
selectCount < 5 &&
|
||||
accessControl?.read &&
|
||||
accessControl.read.length > 0
|
||||
) {
|
||||
simpleSelectExample[key] = true;
|
||||
simpleResponseExample[key] = exampleValue;
|
||||
selectCount++;
|
||||
}
|
||||
|
||||
// Add to create example (only fields with create permission)
|
||||
if (
|
||||
createCount < 5 &&
|
||||
accessControl?.create &&
|
||||
accessControl.create.length > 0 &&
|
||||
!column.computed
|
||||
) {
|
||||
simpleCreateExample[key] = exampleValue;
|
||||
createCount++;
|
||||
}
|
||||
|
||||
// Add to update example (only fields with update permission)
|
||||
if (
|
||||
updateCount < 3 &&
|
||||
accessControl?.update &&
|
||||
accessControl.update.length > 0 &&
|
||||
!column.computed
|
||||
) {
|
||||
simpleUpdateExample[key] = exampleValue;
|
||||
updateCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a query example using the first string/text field with an example
|
||||
for (const key of sortedColumnKeys) {
|
||||
const column: TableColumnMetadata | undefined = tableColumns[key];
|
||||
if (column) {
|
||||
const exampleValue: JSONValue =
|
||||
column.example !== undefined
|
||||
? (column.example as JSONValue)
|
||||
: getDefaultExampleForType(column.type?.toString(), column.title);
|
||||
|
||||
if (
|
||||
typeof exampleValue === "string" &&
|
||||
column.type?.toString().toLowerCase().includes("text")
|
||||
) {
|
||||
simpleQueryExample[key] = exampleValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add sort example - sort by createdAt descending if available
|
||||
simpleSortExample["createdAt"] = -1;
|
||||
|
||||
// Generate list response with 3 sample items
|
||||
const simpleListResponseExample: Array<JSONObject> = [
|
||||
{ ...simpleResponseExample, _id: exampleObjectID },
|
||||
{
|
||||
...simpleResponseExample,
|
||||
_id: ObjectID.generate().toString(),
|
||||
},
|
||||
{
|
||||
...simpleResponseExample,
|
||||
_id: ObjectID.generate().toString(),
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
simpleSelectExample,
|
||||
simpleQueryExample,
|
||||
simpleSortExample,
|
||||
simpleCreateExample,
|
||||
simpleUpdateExample,
|
||||
simpleResponseExample,
|
||||
simpleListResponseExample,
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to generate code examples for all API operations
|
||||
function generateApiCodeExamples(
|
||||
apiPath: string,
|
||||
exampleObjects: ExampleObjects,
|
||||
exampleObjectID: string,
|
||||
): ApiCodeExamples {
|
||||
// List endpoint
|
||||
const listExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "POST",
|
||||
endpoint: `${apiPath}/get-list?skip=0&limit=10`,
|
||||
body: {
|
||||
select: exampleObjects.simpleSelectExample,
|
||||
query: exampleObjects.simpleQueryExample,
|
||||
sort: exampleObjects.simpleSortExample,
|
||||
},
|
||||
description: "List items with pagination",
|
||||
});
|
||||
|
||||
// Get item endpoint
|
||||
const getItemExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "POST",
|
||||
endpoint: `${apiPath}/${exampleObjectID}/get-item`,
|
||||
body: {
|
||||
select: exampleObjects.simpleSelectExample,
|
||||
},
|
||||
description: "Get a single item by ID",
|
||||
});
|
||||
|
||||
// Count endpoint
|
||||
const countExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "POST",
|
||||
endpoint: `${apiPath}/count`,
|
||||
body: {
|
||||
query: exampleObjects.simpleQueryExample,
|
||||
},
|
||||
description: "Count items matching a query",
|
||||
});
|
||||
|
||||
// Create endpoint
|
||||
const createExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "POST",
|
||||
endpoint: apiPath,
|
||||
body: {
|
||||
data: exampleObjects.simpleCreateExample,
|
||||
},
|
||||
description: "Create a new item",
|
||||
});
|
||||
|
||||
// Update endpoint
|
||||
const updateExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "PUT",
|
||||
endpoint: `${apiPath}/${exampleObjectID}`,
|
||||
body: {
|
||||
data: exampleObjects.simpleUpdateExample,
|
||||
},
|
||||
description: "Update an existing item",
|
||||
});
|
||||
|
||||
// Delete endpoint
|
||||
const deleteExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "DELETE",
|
||||
endpoint: `${apiPath}/${exampleObjectID}`,
|
||||
description: "Delete an item by ID",
|
||||
});
|
||||
|
||||
return {
|
||||
list: listExamples,
|
||||
getItem: getItemExamples,
|
||||
count: countExamples,
|
||||
create: createExamples,
|
||||
update: updateExamples,
|
||||
delete: deleteExamples,
|
||||
};
|
||||
}
|
||||
|
||||
// Get all resources and resource dictionary
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
@@ -261,11 +555,28 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Generate a unique ID for the example object
|
||||
pageData["exampleObjectID"] = ObjectID.generate();
|
||||
const exampleObjectID: string = ObjectID.generate().toString();
|
||||
pageData["exampleObjectID"] = exampleObjectID;
|
||||
|
||||
// Generate dynamic example objects from column metadata
|
||||
const exampleObjects: ExampleObjects = generateExampleObjects(
|
||||
tableColumns,
|
||||
exampleObjectID,
|
||||
);
|
||||
pageData["exampleObjects"] = exampleObjects;
|
||||
|
||||
// Construct the API path for the current resource
|
||||
pageData["apiPath"] =
|
||||
const apiPath: string =
|
||||
AppApiRoute.toString() + currentResource.model.crudApiPath?.toString();
|
||||
pageData["apiPath"] = apiPath;
|
||||
|
||||
// Generate code examples for all languages
|
||||
const codeExamples: ApiCodeExamples = generateApiCodeExamples(
|
||||
apiPath,
|
||||
exampleObjects,
|
||||
exampleObjectID,
|
||||
);
|
||||
pageData["codeExamples"] = codeExamples;
|
||||
|
||||
// Check if the current resource is a master admin API
|
||||
pageData["isMasterAdminApiDocs"] =
|
||||
|
||||
771
APIReference/Utils/CodeExampleGenerator.ts
Normal file
771
APIReference/Utils/CodeExampleGenerator.ts
Normal file
@@ -0,0 +1,771 @@
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
export interface RequestPreview {
|
||||
headers: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface CodeExamples {
|
||||
requestPreview: RequestPreview;
|
||||
curl: string;
|
||||
javascript: string;
|
||||
typescript: string;
|
||||
python: string;
|
||||
go: string;
|
||||
java: string;
|
||||
csharp: string;
|
||||
php: string;
|
||||
ruby: string;
|
||||
rust: string;
|
||||
powershell: string;
|
||||
}
|
||||
|
||||
export interface ApiRequestParams {
|
||||
method: "GET" | "POST" | "PUT" | "DELETE";
|
||||
endpoint: string;
|
||||
body?: JSONObject;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export default class CodeExampleGenerator {
|
||||
private static readonly API_KEY_PLACEHOLDER: string = "YOUR_API_KEY";
|
||||
private static readonly BASE_URL: string = "https://oneuptime.com";
|
||||
|
||||
public static generate(params: ApiRequestParams): CodeExamples {
|
||||
return {
|
||||
requestPreview: this.generateRequestPreview(params),
|
||||
curl: this.generateCurl(params),
|
||||
javascript: this.generateJavaScript(params),
|
||||
typescript: this.generateTypeScript(params),
|
||||
python: this.generatePython(params),
|
||||
go: this.generateGo(params),
|
||||
java: this.generateJava(params),
|
||||
csharp: this.generateCSharp(params),
|
||||
php: this.generatePHP(params),
|
||||
ruby: this.generateRuby(params),
|
||||
rust: this.generateRust(params),
|
||||
powershell: this.generatePowerShell(params),
|
||||
};
|
||||
}
|
||||
|
||||
private static generateRequestPreview(
|
||||
params: ApiRequestParams,
|
||||
): RequestPreview {
|
||||
const { body } = params;
|
||||
|
||||
const headers: string = `Content-Type: application/json
|
||||
ApiKey: ${this.API_KEY_PLACEHOLDER}`;
|
||||
|
||||
const bodyStr: string =
|
||||
body && Object.keys(body).length > 0 ? JSON.stringify(body, null, 2) : "";
|
||||
|
||||
return {
|
||||
headers,
|
||||
body: bodyStr,
|
||||
};
|
||||
}
|
||||
|
||||
private static generateCurl(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let curlCmd: string = `curl -X ${method} "${url}"`;
|
||||
curlCmd += ` \\\n -H "Content-Type: application/json"`;
|
||||
curlCmd += ` \\\n -H "ApiKey: ${this.API_KEY_PLACEHOLDER}"`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 2)
|
||||
.split("\n")
|
||||
.map((line: string, index: number) => {
|
||||
return index === 0 ? line : ` ${line}`;
|
||||
})
|
||||
.join("\n");
|
||||
curlCmd += ` \\\n -d '${jsonBody}'`;
|
||||
}
|
||||
|
||||
return curlCmd;
|
||||
}
|
||||
|
||||
private static generateJavaScript(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `const axios = require('axios');
|
||||
|
||||
const response = await axios({
|
||||
method: '${method.toLowerCase()}',
|
||||
url: '${url}',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'ApiKey': '${this.API_KEY_PLACEHOLDER}'
|
||||
}`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 2)
|
||||
.split("\n")
|
||||
.map((line: string, index: number) => {
|
||||
return index === 0 ? line : ` ${line}`;
|
||||
})
|
||||
.join("\n");
|
||||
code += `,\n data: ${jsonBody}`;
|
||||
}
|
||||
|
||||
code += `
|
||||
});
|
||||
|
||||
console.log(response.data);`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateTypeScript(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `import axios, { AxiosResponse } from 'axios';
|
||||
|
||||
interface ApiResponse {
|
||||
// Define your response type here
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const response: AxiosResponse<ApiResponse> = await axios({
|
||||
method: '${method.toLowerCase()}',
|
||||
url: '${url}',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'ApiKey': '${this.API_KEY_PLACEHOLDER}'
|
||||
}`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 2)
|
||||
.split("\n")
|
||||
.map((line: string, index: number) => {
|
||||
return index === 0 ? line : ` ${line}`;
|
||||
})
|
||||
.join("\n");
|
||||
code += `,\n data: ${jsonBody}`;
|
||||
}
|
||||
|
||||
code += `
|
||||
});
|
||||
|
||||
console.log(response.data);`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generatePython(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `import requests
|
||||
|
||||
url = "${url}"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"ApiKey": "${this.API_KEY_PLACEHOLDER}"
|
||||
}`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const pythonBody: string = this.jsonToPython(body);
|
||||
code += `
|
||||
|
||||
payload = ${pythonBody}
|
||||
|
||||
response = requests.${method.toLowerCase()}(url, json=payload, headers=headers)`;
|
||||
} else {
|
||||
code += `
|
||||
|
||||
response = requests.${method.toLowerCase()}(url, headers=headers)`;
|
||||
}
|
||||
|
||||
code += `
|
||||
print(response.json())`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateGo(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
code += `
|
||||
payload := map[string]interface{}{`;
|
||||
const entries: Array<string> = Object.entries(body).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return ` "${key}": ${this.goValue(value)}`;
|
||||
},
|
||||
);
|
||||
code += `\n${entries.join(",\n")},
|
||||
}
|
||||
jsonData, _ := json.Marshal(payload)
|
||||
|
||||
req, _ := http.NewRequest("${method}", "${url}", bytes.NewBuffer(jsonData))`;
|
||||
} else {
|
||||
code += `
|
||||
req, _ := http.NewRequest("${method}", "${url}", nil)`;
|
||||
}
|
||||
|
||||
code += `
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("ApiKey", "${this.API_KEY_PLACEHOLDER}")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, _ := client.Do(req)
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
fmt.Println(string(body))
|
||||
}`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateJava(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class ApiRequest {
|
||||
public static void main(String[] args) throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 12).replace(
|
||||
/"/g,
|
||||
'\\"',
|
||||
);
|
||||
code += `
|
||||
String jsonBody = "${jsonBody.replace(/\n/g, "\\n")}";
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("${url}"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("ApiKey", "${this.API_KEY_PLACEHOLDER}")
|
||||
.method("${method}", HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.build();`;
|
||||
} else {
|
||||
code += `
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("${url}"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("ApiKey", "${this.API_KEY_PLACEHOLDER}")
|
||||
.method("${method}", HttpRequest.BodyPublishers.noBody())
|
||||
.build();`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
HttpResponse<String> response = client.send(request,
|
||||
HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
System.out.println(response.body());
|
||||
}
|
||||
}`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateCSharp(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
class Program
|
||||
{
|
||||
static async Task Main()
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
|
||||
client.DefaultRequestHeaders.Add("ApiKey", "${this.API_KEY_PLACEHOLDER}");
|
||||
`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 8);
|
||||
code += `
|
||||
var json = @"${jsonBody.replace(/"/g, '""')}";
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await client.${this.csharpMethod(method)}Async(
|
||||
"${url}"${method !== "GET" && method !== "DELETE" ? ", content" : ""});`;
|
||||
} else {
|
||||
code += `
|
||||
var response = await client.${this.csharpMethod(method)}Async("${url}");`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
}`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generatePHP(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `<?php
|
||||
|
||||
$url = "${url}";
|
||||
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"ApiKey: ${this.API_KEY_PLACEHOLDER}"
|
||||
];
|
||||
`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
code += `
|
||||
$data = ${this.jsonToPhp(body)};
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "${method}");
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));`;
|
||||
} else {
|
||||
code += `
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "${method}");`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
print_r($result);
|
||||
?>`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateRuby(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `require 'net/http'
|
||||
require 'uri'
|
||||
require 'json'
|
||||
|
||||
uri = URI.parse("${url}")
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = true
|
||||
|
||||
request = Net::HTTP::${this.rubyMethodClass(method)}.new(uri.request_uri)
|
||||
request["Content-Type"] = "application/json"
|
||||
request["ApiKey"] = "${this.API_KEY_PLACEHOLDER}"`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const rubyBody: string = this.jsonToRuby(body);
|
||||
code += `
|
||||
|
||||
request.body = ${rubyBody}.to_json`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
response = http.request(request)
|
||||
puts JSON.parse(response.body)`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateRust(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||
headers.insert("ApiKey", HeaderValue::from_static("${this.API_KEY_PLACEHOLDER}"));`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const rustBody: string = this.jsonToRust(body);
|
||||
code += `
|
||||
|
||||
let body = ${rustBody};
|
||||
|
||||
let response = client
|
||||
.${method.toLowerCase()}("${url}")
|
||||
.headers(headers)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?;`;
|
||||
} else {
|
||||
code += `
|
||||
|
||||
let response = client
|
||||
.${method.toLowerCase()}("${url}")
|
||||
.headers(headers)
|
||||
.send()
|
||||
.await?;`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
let result: serde_json::Value = response.json().await?;
|
||||
println!("{:#?}", result);
|
||||
|
||||
Ok(())
|
||||
}`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generatePowerShell(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `$headers = @{
|
||||
"Content-Type" = "application/json"
|
||||
"ApiKey" = "${this.API_KEY_PLACEHOLDER}"
|
||||
}`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const psBody: string = this.jsonToPowerShell(body);
|
||||
code += `
|
||||
|
||||
$body = ${psBody} | ConvertTo-Json -Depth 10
|
||||
|
||||
$response = Invoke-RestMethod -Uri "${url}" -Method ${method} -Headers $headers -Body $body`;
|
||||
} else {
|
||||
code += `
|
||||
|
||||
$response = Invoke-RestMethod -Uri "${url}" -Method ${method} -Headers $headers`;
|
||||
}
|
||||
|
||||
code += `
|
||||
$response | ConvertTo-Json -Depth 10`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
// Helper methods for language-specific formatting
|
||||
|
||||
private static jsonToPython(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "[]";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.jsonToPython(item as JSONObject, indent + 1);
|
||||
});
|
||||
return `[\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}]`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${key}": ${this.pythonValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `{\n${entries.join(",\n")}\n${spaces}}`;
|
||||
}
|
||||
|
||||
return this.pythonValue(obj, indent);
|
||||
}
|
||||
|
||||
private static pythonValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "None";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "True" : "False";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return this.jsonToPython(value as JSONObject, indent);
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static goValue(value: unknown): string {
|
||||
if (value === null) {
|
||||
return "nil";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return `[]interface{}{${value
|
||||
.map((v: unknown) => {
|
||||
return this.goValue(v);
|
||||
})
|
||||
.join(", ")}}`;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const entries: Array<string> = Object.entries(
|
||||
value as Record<string, unknown>,
|
||||
).map(([k, v]: [string, unknown]) => {
|
||||
return `"${k}": ${this.goValue(v)}`;
|
||||
});
|
||||
return `map[string]interface{}{${entries.join(", ")}}`;
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static jsonToRuby(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "[]";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.jsonToRuby(item as JSONObject, indent + 1);
|
||||
});
|
||||
return `[\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}]`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${key}" => ${this.rubyValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `{\n${entries.join(",\n")}\n${spaces}}`;
|
||||
}
|
||||
|
||||
return this.rubyValue(obj, indent);
|
||||
}
|
||||
|
||||
private static rubyValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "nil";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return this.jsonToRuby(value as JSONObject, indent);
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static rubyMethodClass(method: string): string {
|
||||
const methodMap: Record<string, string> = {
|
||||
GET: "Get",
|
||||
POST: "Post",
|
||||
PUT: "Put",
|
||||
DELETE: "Delete",
|
||||
};
|
||||
return methodMap[method] || "Get";
|
||||
}
|
||||
|
||||
private static csharpMethod(method: string): string {
|
||||
const methodMap: Record<string, string> = {
|
||||
GET: "Get",
|
||||
POST: "Post",
|
||||
PUT: "Put",
|
||||
DELETE: "Delete",
|
||||
};
|
||||
return methodMap[method] || "Get";
|
||||
}
|
||||
|
||||
private static jsonToRust(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "json!([])";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.rustInnerValue(item, indent + 1);
|
||||
});
|
||||
return `json!([\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}])`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${key}": ${this.rustInnerValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `json!({\n${entries.join(",\n")}\n${spaces}})`;
|
||||
}
|
||||
|
||||
return `json!(${this.rustInnerValue(obj, indent)})`;
|
||||
}
|
||||
|
||||
private static rustInnerValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "null";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
const items: Array<string> = value.map((v: unknown) => {
|
||||
return this.rustInnerValue(v, indent + 1);
|
||||
});
|
||||
return `[\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}]`;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
const entries: Array<string> = Object.entries(
|
||||
value as Record<string, unknown>,
|
||||
).map(([k, v]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${k}": ${this.rustInnerValue(v, indent + 1)}`;
|
||||
});
|
||||
return `{\n${entries.join(",\n")}\n${spaces}}`;
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static jsonToPowerShell(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "@()";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.jsonToPowerShell(item as JSONObject, indent + 1);
|
||||
});
|
||||
return `@(\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces})`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}${key} = ${this.psValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `@{\n${entries.join("\n")}\n${spaces}}`;
|
||||
}
|
||||
|
||||
return this.psValue(obj, indent);
|
||||
}
|
||||
|
||||
private static psValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "$null";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "$true" : "$false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return this.jsonToPowerShell(value as JSONObject, indent);
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static jsonToPhp(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "[]";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.jsonToPhp(item as JSONObject, indent + 1);
|
||||
});
|
||||
return `[\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}]`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${key}" => ${this.phpValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `[\n${entries.join(",\n")}\n${spaces}]`;
|
||||
}
|
||||
|
||||
return this.phpValue(obj, indent);
|
||||
}
|
||||
|
||||
private static phpValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "null";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return this.jsonToPhp(value as JSONObject, indent);
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
14
APIReference/package-lock.json
generated
14
APIReference/package-lock.json
generated
@@ -53,6 +53,7 @@
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
@@ -81,8 +82,10 @@
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"marked": "^12.0.2",
|
||||
"mermaid": "^11.12.2",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^7.0.7",
|
||||
"otpauth": "^9.3.1",
|
||||
@@ -99,7 +102,7 @@
|
||||
"react-dropzone": "^14.2.2",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-markdown": "^9.0.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
@@ -109,7 +112,7 @@
|
||||
"recharts": "^2.12.7",
|
||||
"redis-semaphore": "^5.5.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"slackify-markdown": "^4.4.0",
|
||||
"slugify": "^1.6.5",
|
||||
"socket.io": "^4.7.4",
|
||||
@@ -1919,9 +1922,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
"name": "@oneuptime/api-reference",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime"
|
||||
},
|
||||
"main": "Index.ts",
|
||||
"scripts": {
|
||||
"start": "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<main class="py-16">
|
||||
<article class="prose">
|
||||
<div>
|
||||
<div>
|
||||
|
||||
<h1 class="next-error-h1" style="display:inline-block;margin:0;margin-right:20px;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1>
|
||||
<div style="display:inline-block;text-align:left;line-height:49px;height:49px;vertical-align:middle">
|
||||
<h2 style="font-size:14px;font-weight:normal;line-height:49px;margin:0;padding:0">
|
||||
This page could not be found<!-- -->.
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<div class="text-center py-20">
|
||||
<div class="flex items-center justify-center w-16 h-16 rounded-2xl bg-indigo-100 mx-auto mb-6">
|
||||
<svg class="w-8 h-8 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-7xl font-bold text-slate-900 tracking-tight mb-4">404</h1>
|
||||
<p class="text-xl text-slate-600 mb-8">This page could not be found.</p>
|
||||
<a href="/reference" class="inline-flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 transition-colors">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
</svg>
|
||||
Back to API Reference
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
@@ -1,39 +1,51 @@
|
||||
<main class="py-16">
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<h1>Authentication</h1>
|
||||
<p class="lead">You'll need to authenticate your requests to access any of the endpoints in the OneUptime API. In
|
||||
this guide, we'll look at how authentication works. OneUptime offers one way to authenticate your API requests
|
||||
- by using an API Key.</p>
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Authentication</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">You'll need to authenticate your requests to access any of the endpoints in the OneUptime API. In this guide, we'll look at how authentication works. OneUptime offers one way to authenticate your API requests - by using an API Key.</p>
|
||||
</div>
|
||||
|
||||
|
||||
<h2 id="basic-authentication" class="mb-5 scroll-mt-24 mt-24 font-bold text-lg">
|
||||
<h2 id="generate-api-key" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12">
|
||||
Generate an API Key
|
||||
</h2>
|
||||
<p> Please head over to <b>Project Settings</b> > <b>API Keys</b>. Create a new API Key. Please note: New API Keys
|
||||
have no permissions assigned to them, so you will have to assign a permission before you can use it.</p>
|
||||
<a class="mt-5 inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/permissions">
|
||||
<p class="text-slate-600 leading-relaxed">Please head over to <strong class="text-slate-800">Project Settings</strong> > <strong class="text-slate-800">API Keys</strong>. Create a new API Key. Please note: New API Keys have no permissions assigned to them, so you will have to assign a permission before you can use it.</p>
|
||||
<a class="mt-4 inline-flex gap-1 items-center font-medium text-indigo-600 hover:text-indigo-700 transition-colors" href="/reference/permissions">
|
||||
Read more about permissions
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-5 w-5">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
|
||||
<h2 id="basic-authentication" class="mb-5 scroll-mt-24 mt-24 font-bold text-lg">
|
||||
<h2 id="project-id" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12">
|
||||
Project ID
|
||||
</h2>
|
||||
<p> Please head over to <b>Project Settings</b> > <b>Project</b>. You should see your Project ID there.</p>
|
||||
<p class="text-slate-600 leading-relaxed">Please head over to <strong class="text-slate-800">Project Settings</strong> > <strong class="text-slate-800">Project</strong>. You should see your Project ID there.</p>
|
||||
|
||||
<h2 id="basic-authentication">
|
||||
<h2 id="auth-with-api-key" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12">
|
||||
Authentication with API Key
|
||||
</h2>
|
||||
<p>You can use OneUptime API Key on Request Header when you're making a request. You can use <code
|
||||
class="rounded p-0.5 px-1 text-sm text-gray-500 bg-gray-100 border-2 border-gray-200">Authorization</code>
|
||||
header with your API Key when you make a request.</p>
|
||||
<p class="text-slate-600 leading-relaxed mb-6">You can use OneUptime API Key on Request Header when you're making a request. You can use <code class="inline-code">ApiKey</code> header with your API Key when you make a request.</p>
|
||||
|
||||
<%- include('../partials/code', {title: "Example request with API Key" , requestUrl: "" , requestType: "" , code: "curl --header \"ApiKey: {secret-api-key}\" https://oneuptime.com/api/\<path\>" }) -%>
|
||||
<p class="text-sm">Please don't commit your OneUptime API Key to GitHub, or on any other source control
|
||||
project. Please regenerate a new API Key, if your API Key is committed by mistake.</p>
|
||||
|
||||
<div class="mt-6 rounded-xl border border-amber-200 bg-amber-50 p-4">
|
||||
<div class="flex gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-amber-500" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-sm text-amber-800">Please don't commit your OneUptime API Key to GitHub, or on any other source control project. Please regenerate a new API Key if your API Key is committed by mistake.</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
</main>
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<h1>Data Types</h1>
|
||||
<p class="lead">In this guide, we will look at how to work with OneUptime Data Types when querying the OneUptime
|
||||
API. </p>
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Data Types</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">In this guide, we will look at how to work with OneUptime Data Types when querying the OneUptime API.</p>
|
||||
</div>
|
||||
|
||||
<a href="#select" class="cursor-default">
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Select
|
||||
</h2>
|
||||
</a>
|
||||
<h2 id="select" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12">
|
||||
Select
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Select can be used to select fields from an Object in List or Get Item API. By default only ID's
|
||||
@@ -16,13 +24,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Select</dt>
|
||||
<dd><code class="inline-code">select</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Select</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Select</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Dictionary of fieldName - boolean as JSON Object. Here's an example. If the
|
||||
@@ -46,11 +54,9 @@
|
||||
|
||||
|
||||
|
||||
<a href="#sort" class="cursor-default">
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Sort
|
||||
</h2>
|
||||
</a>
|
||||
<h2 id="sort" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 pt-8 border-t border-slate-200">
|
||||
Sort
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Sort can be used to sort list by fields in List API. By default objects are sorted by their createdAt
|
||||
@@ -59,13 +65,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Sort</dt>
|
||||
<dd><code class="inline-code">sort</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Sort</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Sort</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Dictionary of fieldName - 'DESC' / 'ASC' as JSON Object. Here's an example. If
|
||||
@@ -87,11 +93,9 @@
|
||||
</div>
|
||||
|
||||
|
||||
<a href="#queries" class="cursor-default">
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Query
|
||||
</h2>
|
||||
</a>
|
||||
<h2 id="queries" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 pt-8 border-t border-slate-200">
|
||||
Query
|
||||
</h2>
|
||||
|
||||
|
||||
<h3 id="example-using-cursors" class="scroll-mt-24">
|
||||
@@ -106,13 +110,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of an Equal To Query</p>
|
||||
@@ -144,13 +148,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a Not Equal To Query</p>
|
||||
@@ -182,13 +186,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of an is null query</p>
|
||||
@@ -220,13 +224,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of an is not null query</p>
|
||||
@@ -257,13 +261,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a greater than query</p>
|
||||
@@ -295,13 +299,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a greater than or equal query</p>
|
||||
@@ -333,13 +337,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a less than query</p>
|
||||
@@ -371,13 +375,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a less than or equal query</p>
|
||||
@@ -410,13 +414,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a less than or equal query</p>
|
||||
|
||||
@@ -1,90 +1,77 @@
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<h1 class="font-bold text-xl mb-5">Errors</h1>
|
||||
<p class="lead">In this guide, we will talk about what happens when something goes wrong while you work with the API. Mistakes happen, and mostly they will be yours, not ours. Let's look at some status codes and error types you might encounter.</p>
|
||||
<p>You can tell if your request was successful by checking the status code when receiving an API response. If a response comes back unsuccessful, you can use the status code and error message to figure out what has gone wrong and do some rudimentary debugging (before contacting support).</p>
|
||||
<div class="my-6 flex gap-2.5 rounded-2xl border border-emerald-500/20 bg-emerald-50/50 p-4 leading-6 text-emerald-900 ">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" class="mt-1 h-4 w-4 flex-none fill-emerald-500 stroke-white ">
|
||||
<circle cx="8" cy="8" r="8" stroke-width="0"></circle>
|
||||
<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.75 7.75h1.5v3.5"></path>
|
||||
<circle cx="8" cy="4" r=".5" fill="none"></circle>
|
||||
</svg>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Before reaching out to support with an error, please be aware that 99% of all
|
||||
reported errors are, in fact, user errors. Therefore, please carefully check
|
||||
your code before contacting OneUptime support.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="status-codes" class="mb-5 scroll-mt-24 mt-24 font-bold text-lg">
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Errors</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">In this guide, we will talk about what happens when something goes wrong while you work with the API. Mistakes happen, and mostly they will be yours, not ours. Let's look at some status codes and error types you might encounter.</p>
|
||||
</div>
|
||||
|
||||
<p class="text-slate-600 leading-relaxed">You can tell if your request was successful by checking the status code when receiving an API response. If a response comes back unsuccessful, you can use the status code and error message to figure out what has gone wrong and do some rudimentary debugging (before contacting support).</p>
|
||||
|
||||
<div class="my-6 flex gap-3 rounded-xl border border-indigo-500/20 bg-indigo-50/50 p-4">
|
||||
<div class="flex-shrink-0">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" class="h-5 w-5 fill-indigo-500 stroke-white">
|
||||
<circle cx="8" cy="8" r="8" stroke-width="0"></circle>
|
||||
<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.75 7.75h1.5v3.5"></path>
|
||||
<circle cx="8" cy="4" r=".5" fill="none"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-sm text-indigo-900">Before reaching out to support with an error, please be aware that 99% of all reported errors are, in fact, user errors. Therefore, please carefully check your code before contacting OneUptime support.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="status-codes" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 pt-8 border-t border-slate-200">
|
||||
Status codes
|
||||
</h2>
|
||||
<p>Here is a list of the different categories of status codes returned by the OneUptime API. Use these to understand if a request was successful.</p>
|
||||
<div class="my-6">
|
||||
<ul role="list" class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">2xx</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>A 2xx status code indicates a successful response.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">4xx</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>A 4xx status code indicates a client error — this means it's a <em>you</em>
|
||||
problem.
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
</h2>
|
||||
<p class="text-slate-600 leading-relaxed mb-6">Here is a list of the different categories of status codes returned by the OneUptime API. Use these to understand if a request was successful.</p>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 list-none divide-y divide-slate-100 p-0">
|
||||
<li class="m-0 px-5 py-4">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">429</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p> Request limit exceeded. Request limits are 100 operations per second per project (this includes all the API keys in the project).
|
||||
</p>
|
||||
</dd>
|
||||
<dd><code class="inline-flex items-center rounded-md bg-emerald-50 px-2 py-1 text-xs font-medium text-emerald-700 ring-1 ring-inset ring-emerald-600/20">2xx</code></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1">A 2xx status code indicates a successful response.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">5xx</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>A 5xx status code indicates a server error — you won't be seeing a lot of these.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="error-types" class="mb-5 scroll-mt-24 mt-24 font-bold text-lg">Error Messages</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Whenever a request is unsuccessful, the OneUptime API will return an error response with an error message. You can use this information to understand better what has gone wrong and how to fix it. Most of the error messages are pretty helpful and actionable.</p>
|
||||
<p>Here is an example of an error message: </p>
|
||||
|
||||
</div>
|
||||
<%- include('../partials/code', {title: "Example error response", requestUrl: "", requestType: "", code: "{ \"message\": \"Name is required\" }" }) -%>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
<li class="m-0 px-5 py-4">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-flex items-center rounded-md bg-amber-50 px-2 py-1 text-xs font-medium text-amber-700 ring-1 ring-inset ring-amber-600/20">4xx</code></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1">A 4xx status code indicates a client error - this means it's a <em>you</em> problem.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-5 py-4">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-flex items-center rounded-md bg-orange-50 px-2 py-1 text-xs font-medium text-orange-700 ring-1 ring-inset ring-orange-600/20">429</code></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1">Request limit exceeded. Request limits are 100 operations per second per project (this includes all the API keys in the project).</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-5 py-4">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/20">5xx</code></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1">A 5xx status code indicates a server error - you won't be seeing a lot of these.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="error-types" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 pt-8 border-t border-slate-200">
|
||||
Error Messages
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p class="text-slate-600 leading-relaxed">Whenever a request is unsuccessful, the OneUptime API will return an error response with an error message. You can use this information to understand better what has gone wrong and how to fix it. Most of the error messages are pretty helpful and actionable.</p>
|
||||
<p class="text-slate-600 leading-relaxed">Here is an example of an error message:</p>
|
||||
</div>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0 xl:sticky xl:top-24">
|
||||
<%- include('../partials/code', {title: "Example error response", requestUrl: "", requestType: "", code: "{ \"message\": \"Name is required\" }" }) -%>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
@@ -1,198 +1,87 @@
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<div class="absolute inset-0 -z-10 mx-0 max-w-none overflow-hidden">
|
||||
<div
|
||||
class="absolute left-1/2 top-0 ml-[-38rem] h-[25rem] w-[81.25rem]">
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-r from-[#36b49f] to-[#DBFF75] opacity-40 [mask-image:radial-gradient(farthest-side_at_top,white,transparent)]">
|
||||
<svg aria-hidden="true"
|
||||
class="absolute inset-x-0 inset-y-[-50%] h-[200%] w-full skew-y-[-18deg] fill-black/40 stroke-black/50 mix-blend-overlay /5">
|
||||
<defs>
|
||||
<pattern id=":r6s:" width="72" height="56" patternUnits="userSpaceOnUse" x="-12"
|
||||
y="4">
|
||||
<path d="M.5 56V.5H72" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" stroke-width="0" fill="url(#:r6s:)"></rect>
|
||||
<svg x="-12" y="4" class="overflow-visible">
|
||||
<rect stroke-width="0" width="73" height="57" x="288" y="168"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="144" y="56"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="504" y="168"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="720" y="336"></rect>
|
||||
</svg>
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="relative mb-12">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<svg viewBox="0 0 1113 440" aria-hidden="true"
|
||||
class="absolute top-0 left-1/2 ml-[-19rem] w-[69.5625rem] fill-white blur-[26px] ">
|
||||
<path d="M.016 439.5s-9.5-300 434-300S882.516 20 882.516 20V0h230.004v439.5H.016Z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">REST API</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-4">API Reference</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">
|
||||
Use the OneUptime API to access any resource in your projects, create automated workflows,
|
||||
and seamlessly integrate with the tools and services your organization uses.
|
||||
</p>
|
||||
</div>
|
||||
<h1 class="font-bold text-xl">API Reference</h1>
|
||||
<p class="lead">Use the OneUptime API to access any resource in your projects, create automated
|
||||
workflows, and more
|
||||
and
|
||||
seamlessly integrate your project into the other tools and services you use in your
|
||||
organization.</p>
|
||||
|
||||
|
||||
<div class="my-16 xl:max-w-none">
|
||||
<h2 id="guides" class="scroll-mt-24 font-bold">
|
||||
<span class="group text-inherit no-underline hover:text-inherit">
|
||||
<div
|
||||
class="absolute mt-1 ml-[calc(-1*var(--width))] hidden w-[var(--width)] opacity-0 transition [--width:calc(2.625rem+0.5px+50%-min(50%,calc(theme(maxWidth.lg)+theme(spacing.8))))] group-hover:opacity-100 group-focus:opacity-100 md:block lg:z-50 2xl:[--width:theme(spacing.10)]">
|
||||
<div
|
||||
class="group/anchor block h-5 w-5 rounded-lg bg-zinc-50 ring-1 ring-inset ring-zinc-300 transition hover:ring-zinc-500 ">
|
||||
<svg viewBox="0 0 20 20" fill="none" stroke-linecap="round" aria-hidden="true"
|
||||
class="h-5 w-5 stroke-zinc-500 transition ">
|
||||
<path
|
||||
d="m6.5 11.5-.964-.964a3.535 3.535 0 1 1 5-5l.964.964m2 2 .964.964a3.536 3.536 0 0 1-5 5L8.5 13.5m0-5 3 3">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
Guides
|
||||
</span>
|
||||
<!-- Guides Section -->
|
||||
<div class="my-12 xl:max-w-none">
|
||||
<h2 id="guides" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-6">
|
||||
Getting Started
|
||||
</h2>
|
||||
<div
|
||||
class="not-prose mt-4 grid grid-cols-1 gap-8 border-t border-zinc-900/5 pt-10 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-zinc-900 ">Authentication</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 ">Learn how to authenticate your
|
||||
API
|
||||
requests.</p>
|
||||
<p class="mt-4">
|
||||
<a class="inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/authentication">
|
||||
Read more
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true"
|
||||
class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-zinc-900 ">Pagination</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 ">Understand how to work with
|
||||
paginated responses.</p>
|
||||
<p class="mt-4">
|
||||
<a class="inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/pagination">
|
||||
Read more
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true"
|
||||
class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-zinc-900 ">Errors</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 ">Read about the different types
|
||||
of
|
||||
errors returned by the API.</p>
|
||||
<p class="mt-4">
|
||||
<a class="inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/errors">
|
||||
Read more
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true"
|
||||
class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-zinc-900 ">Permissions</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 ">Learn how API Key Permissions
|
||||
work.</p>
|
||||
<p class="mt-4">
|
||||
<a class="inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/permissions">
|
||||
Read more
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true"
|
||||
class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="not-prose grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<a href="/reference/authentication" class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-50 text-indigo-600 mb-4 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors">Authentication</h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed">Learn how to authenticate your API requests.</p>
|
||||
</a>
|
||||
<a href="/reference/pagination" class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-50 text-indigo-600 mb-4 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors">Pagination</h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed">Understand how to work with paginated responses.</p>
|
||||
</a>
|
||||
<a href="/reference/errors" class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-50 text-indigo-600 mb-4 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors">Errors</h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed">Read about the different types of errors returned.</p>
|
||||
</a>
|
||||
<a href="/reference/permissions" class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-50 text-indigo-600 mb-4 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors">Permissions</h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed">Learn how API Key Permissions work.</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-16 xl:max-w-none">
|
||||
<h2 id="resources" class="scroll-mt-24 font-bold"><span
|
||||
class="group text-inherit no-underline hover:text-inherit">Featured Resources</span>
|
||||
</h2>
|
||||
<div
|
||||
class="not-prose mt-4 grid grid-cols-1 gap-8 border-t border-zinc-900/5 pt-10 sm:grid-cols-2 xl:grid-cols-4">
|
||||
|
||||
<% for(var i=0; i<pageData.featuredResources.length; i++) {%>
|
||||
<div
|
||||
class="cursor-pointer group relative flex rounded-2xl bg-zinc-50 transition-shadow hover:shadow-md hover:shadow-zinc-900/5 ">
|
||||
<div class="pointer-events-none">
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl transition duration-300 [mask-image:linear-gradient(white,transparent)] group-hover:opacity-50">
|
||||
<svg aria-hidden="true"
|
||||
class="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/[0.02] stroke-black/5 /2.5">
|
||||
<defs>
|
||||
<pattern id=":r6t:" width="72" height="56" patternUnits="userSpaceOnUse"
|
||||
x="50%" y="16">
|
||||
<path d="M.5 56V.5H72" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" stroke-width="0" fill="url(#:r6t:)"></rect>
|
||||
<svg x="50%" y="16" class="overflow-visible">
|
||||
<rect stroke-width="0" width="73" height="57" x="0" y="56"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="72" y="168"></rect>
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[#D7EDEA] to-[#F4FBDF] opacity-0 transition duration-300 group-hover:opacity-100"
|
||||
data-projection-id="35"
|
||||
style="-webkit-mask-image: radial-gradient(180px at 0px 0px, white, transparent);">
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl opacity-0 mix-blend-overlay transition duration-300 group-hover:opacity-100"
|
||||
data-projection-id="36"
|
||||
style="-webkit-mask-image: radial-gradient(180px at 0px 0px, white, transparent);">
|
||||
<svg aria-hidden="true"
|
||||
class="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/50 stroke-black/70 /10">
|
||||
<defs>
|
||||
<pattern id=":r6u:" width="72" height="56" patternUnits="userSpaceOnUse"
|
||||
x="50%" y="16">
|
||||
<path d="M.5 56V.5H72" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" stroke-width="0" fill="url(#:r6u:)"></rect>
|
||||
<svg x="50%" y="16" class="overflow-visible">
|
||||
<rect stroke-width="0" width="73" height="57" x="0" y="56"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="72" y="168"></rect>
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl ring-1 ring-inset ring-zinc-900/7.5 group-hover:ring-zinc-900/10 ">
|
||||
</div>
|
||||
<div class="relative rounded-2xl px-4 pt-16 pb-4">
|
||||
|
||||
<h3 class="mt-4 text-sm font-semibold leading-7 text-zinc-900 "><a
|
||||
href="/reference/<%= pageData.featuredResources[i].path -%>"><span
|
||||
class="absolute inset-0 rounded-2xl"></span> <%= pageData.featuredResources[i].name -%></a>
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 "><%= pageData.featuredResources[i].description -%></p>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- Featured Resources Section -->
|
||||
<div class="my-12 xl:max-w-none">
|
||||
<h2 id="resources" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-6">
|
||||
Featured Resources
|
||||
</h2>
|
||||
<div class="not-prose grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<% for(var i=0; i<pageData.featuredResources.length; i++) {%>
|
||||
<a href="/reference/<%= pageData.featuredResources[i].path -%>"
|
||||
class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-slate-100 text-slate-600 mb-4 group-hover:bg-indigo-100 group-hover:text-indigo-600 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors"><%= pageData.featuredResources[i].name -%></h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed"><%= pageData.featuredResources[i].description -%></p>
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
</main>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,113 +1,83 @@
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<h1>Pagination</h1>
|
||||
<p class="lead">In this guide, we will look at how to work with paginated responses when querying the OneUptime
|
||||
API. By default, all responses limit results to ten. </p>
|
||||
<p>When an API response returns a list of objects, no matter the amount, pagination is supported. In paginated
|
||||
responses, objects are nested in a <code class="inline-code">data</code> attribute. The API response also has
|
||||
<code class="inline-code">count</code> attribute that indicates total count in the list with that query. You
|
||||
can use the <code class="inline-code">limit</code> and <code class="inline-code">skip</code> query parameters
|
||||
to query pages.</p>
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Pagination</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">In this guide, we will look at how to work with paginated responses when querying the OneUptime API. By default, all responses limit results to ten.</p>
|
||||
</div>
|
||||
|
||||
<p class="text-slate-600 leading-relaxed">When an API response returns a list of objects, no matter the amount, pagination is supported. In paginated responses, objects are nested in a <code class="inline-code">data</code> attribute. The API response also has <code class="inline-code">count</code> attribute that indicates total count in the list with that query. You can use the <code class="inline-code">limit</code> and <code class="inline-code">skip</code> query parameters to query pages.</p>
|
||||
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-6 mt-12">
|
||||
Pagination Example
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>In this example, we request the list of monitors. As a result, we get a list of three monitors and can
|
||||
tell by the <code class="inline-code">count</code> attribute that we have reached the end of the
|
||||
result set</p>
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Query Parameters
|
||||
</h2>
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<p class="text-slate-600 leading-relaxed">In this example, we request the list of monitors. As a result, we get a list of three monitors and can tell by the <code class="inline-code">count</code> attribute that we have reached the end of the result set</p>
|
||||
|
||||
<h3 class="text-base font-semibold text-slate-800 mt-8 mb-3">Query Parameters</h3>
|
||||
<div class="rounded-lg border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 list-none divide-y divide-slate-100 p-0">
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">limit</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Number of items you need to fetch. More items will lead to slower responses. Max limit is
|
||||
100.</p>
|
||||
</dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Number of items you need to fetch. More items will lead to slower responses. Max limit is 100.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">skip</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Number of items to skip. This can be useful when you are paginating items.</p>
|
||||
</dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Number of items to skip. This can be useful when you are paginating items.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3 class="text-base font-semibold text-slate-800 mt-8 mb-3">Response Body</h3>
|
||||
<div class="rounded-lg border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 list-none divide-y divide-slate-100 p-0">
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code">data</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">JSON Array</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">List of items fetched.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code">count</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Total number of items in the database</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code">limit</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Number of items you need to fetch. More items will lead to slower responses. Max limit is 100.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code">skip</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Number of items to skip. This can be useful when you are paginating items.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Response Body
|
||||
</h2>
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">data</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">JSON Array</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>List of items fetched.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">count</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Total number of items in the database</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">limit</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Number of items you need to fetch. More items will lead to slower responses. Max limit is
|
||||
100.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">skip</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Number of items to skip. This can be useful when you are paginating items.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0 xl:sticky xl:top-24">
|
||||
<%- 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>
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<h1>Permissions</h1>
|
||||
<p class="lead"> Your API Token needs permissions to create, update, read or delete any resource. If you do not have permissions to make a request a <code class="inline-code">4xx</code> status will be sent as response. You can manage permissions for your API Key in Project Settings > API Keys. </p>
|
||||
|
||||
|
||||
<h2 id="consuming-webhooks" >
|
||||
Permissions List
|
||||
</h2>
|
||||
<p>Here is a list of all the permissions:</p>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<div class="my-6">
|
||||
<ul role="list" class="m-0 w-full">
|
||||
<% for(var i=0; i<pageData.permissions.length; i++) {%>
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code"> <%= pageData.permissions[i].permission -%></code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "><%= pageData.permissions[i].title -%></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p><%= pageData.permissions[i].description -%></p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<% } %>
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Permissions</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">Your API Token needs permissions to create, update, read or delete any resource. If you do not have permissions to make a request a <code class="inline-code">4xx</code> status will be sent as response. You can manage permissions for your API Key in Project Settings > API Keys.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
<h2 id="consuming-webhooks" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-6 mt-12">
|
||||
Permissions List
|
||||
</h2>
|
||||
<p class="text-slate-600 leading-relaxed mb-6">Here is a list of all the permissions:</p>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 divide-y divide-slate-100 p-0">
|
||||
<% for(var i=0; i<pageData.permissions.length; i++) {%>
|
||||
<li class="m-0 px-5 py-4 hover:bg-slate-50/50 transition-colors">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code"><%= pageData.permissions[i].permission -%></code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500"><%= pageData.permissions[i].title -%></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1"><%= pageData.permissions[i].description -%></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
@@ -1,16 +1,14 @@
|
||||
<html lang="en" class="js-focus-visible ctshmsrlsm idc0_345">
|
||||
<html lang="en" class="h-full antialiased scroll-smooth" style="color-scheme: light;">
|
||||
<%- include('../partials/head', {
|
||||
enableGoogleTagManager: typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false,
|
||||
}) -%>
|
||||
|
||||
<body onload="applyStyles()" class="bg-white antialiased " data-new-gr-c-s-check-loaded="14.1095.0"
|
||||
data-gr-ext-installed="">
|
||||
<div id="__next">
|
||||
<body onload="applyStyles()" class="flex min-h-full bg-white">
|
||||
<div id="__next" class="w-full">
|
||||
<div class="lg:ml-72 xl:ml-80">
|
||||
<%- include('../partials/nav') -%>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative px-4 pt-14 sm:px-6 lg:px-8 max-w-5xl ">
|
||||
<div>
|
||||
<div class="relative px-4 pt-14 sm:px-6 lg:px-8 max-w-5xl w-full">
|
||||
<div class="flex justify-center">
|
||||
<%- include('../main/'+page) -%>
|
||||
</div>
|
||||
@@ -18,7 +16,6 @@
|
||||
<%- include('../partials/footer') -%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
276
APIReference/views/partials/code-tabs.ejs
Normal file
276
APIReference/views/partials/code-tabs.ejs
Normal file
@@ -0,0 +1,276 @@
|
||||
<%
|
||||
const uniqueId = 'code-tabs-' + Math.random().toString(36).substr(2, 9);
|
||||
const tabs = [
|
||||
{ id: 'preview', name: 'Request', icon: 'preview', isPreview: true },
|
||||
{ id: 'curl', name: 'cURL', icon: 'terminal' },
|
||||
{ id: 'javascript', name: 'JavaScript', icon: 'js' },
|
||||
{ id: 'typescript', name: 'TypeScript', icon: 'ts' },
|
||||
{ id: 'python', name: 'Python', icon: 'python' },
|
||||
{ id: 'go', name: 'Go', icon: 'go' },
|
||||
{ id: 'java', name: 'Java', icon: 'java' },
|
||||
{ id: 'csharp', name: 'C#', icon: 'csharp' },
|
||||
{ id: 'php', name: 'PHP', icon: 'php' },
|
||||
{ id: 'ruby', name: 'Ruby', icon: 'ruby' },
|
||||
{ id: 'rust', name: 'Rust', icon: 'rust' },
|
||||
{ id: 'powershell', name: 'PowerShell', icon: 'powershell' }
|
||||
];
|
||||
%>
|
||||
|
||||
<div class="code-tabs-container my-6 w-full max-w-full overflow-hidden" id="<%= uniqueId %>">
|
||||
<!-- Header with title and endpoint -->
|
||||
<div class="rounded-t-xl bg-slate-900 border border-slate-700/50 border-b-0">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 px-4 py-3 border-b border-slate-700/50">
|
||||
<h4 class="text-sm font-semibold text-slate-200 tracking-wide"><%= title %></h4>
|
||||
<div class="flex items-center gap-2">
|
||||
<% if(requestType === "GET"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-emerald-500/15 px-2.5 py-1 text-xs font-bold text-emerald-400 ring-1 ring-inset ring-emerald-500/30 uppercase tracking-wide">GET</span>
|
||||
<% } else if(requestType === "POST"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-500/15 px-2.5 py-1 text-xs font-bold text-indigo-400 ring-1 ring-inset ring-indigo-500/30 uppercase tracking-wide">POST</span>
|
||||
<% } else if(requestType === "DELETE"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-red-500/15 px-2.5 py-1 text-xs font-bold text-red-400 ring-1 ring-inset ring-red-500/30 uppercase tracking-wide">DELETE</span>
|
||||
<% } else if(requestType === "PUT"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-amber-500/15 px-2.5 py-1 text-xs font-bold text-amber-400 ring-1 ring-inset ring-amber-500/30 uppercase tracking-wide">PUT</span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if(requestUrl){ %>
|
||||
<div class="px-4 py-2.5 bg-slate-800/40 border-b border-slate-700/30">
|
||||
<code class="font-mono text-sm text-slate-300 break-all"><%= requestUrl %></code>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- Language Tabs -->
|
||||
<div class="relative">
|
||||
<div class="flex overflow-x-auto scrollbar-hide border-b border-slate-700/50" role="tablist">
|
||||
<% tabs.forEach((tab, index) => { %>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected="<%= index === 0 ? 'true' : 'false' %>"
|
||||
aria-controls="<%= uniqueId %>-panel-<%= tab.id %>"
|
||||
id="<%= uniqueId %>-tab-<%= tab.id %>"
|
||||
data-tab-id="<%= tab.id %>"
|
||||
onclick="switchCodeTab('<%= uniqueId %>', '<%= tab.id %>')"
|
||||
class="code-tab relative flex items-center gap-2 px-4 py-3 text-sm font-medium whitespace-nowrap transition-all duration-200 <%= index === 0 ? 'text-indigo-400 bg-slate-800/50' : 'text-slate-400 hover:text-slate-300 hover:bg-slate-800/30' %>"
|
||||
>
|
||||
<span class="lang-icon w-4 h-4 flex items-center justify-center">
|
||||
<% if(tab.icon === 'preview'){ %>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg>
|
||||
<% } else if(tab.icon === 'terminal'){ %>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
||||
<% } else if(tab.icon === 'js'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.405-.585-.585-.765-.63-.63-1.47-.93-2.835-.885l-.705.09c-.676.165-1.32.525-1.71 1.005-1.14 1.29-.81 3.54.57 4.47 1.365 1.035 3.369 1.26 3.629 2.235.225 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.63-1.755-1.455l-1.83 1.05c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.665 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z"/></svg>
|
||||
<% } else if(tab.icon === 'ts'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>
|
||||
<% } else if(tab.icon === 'python'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/></svg>
|
||||
<% } else if(tab.icon === 'go'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M1.811 10.231c-.047 0-.058-.023-.035-.059l.246-.315c.023-.035.081-.058.128-.058h4.172c.046 0 .058.035.035.07l-.199.303c-.023.036-.082.07-.117.07zM.047 11.306c-.047 0-.059-.023-.035-.058l.245-.316c.023-.035.082-.058.129-.058h5.328c.047 0 .07.035.058.07l-.093.28c-.012.047-.058.07-.105.07zm2.828 1.075c-.047 0-.059-.035-.035-.07l.163-.292c.023-.035.07-.07.117-.07h2.337c.047 0 .07.035.07.082l-.023.28c0 .047-.047.082-.082.082zm12.129-2.36c-.736.187-1.239.327-1.963.514c-.176.046-.187.058-.34-.117c-.174-.199-.303-.327-.548-.444c-.737-.362-1.45-.257-2.115.175c-.795.514-1.204 1.274-1.192 2.22c.011.935.654 1.706 1.577 1.835c.795.105 1.46-.175 1.987-.77c.105-.13.198-.27.315-.434H10.47c-.245 0-.304-.152-.222-.35c.152-.362.432-.97.596-1.274a.32.32 0 0 1 .292-.187h4.253c-.023.316-.023.631-.07.947a5 5 0 0 1-.958 2.29c-.841 1.11-1.94 1.8-3.33 1.986c-1.145.152-2.209-.07-3.143-.77c-.865-.655-1.356-1.52-1.484-2.595c-.152-1.274.222-2.419.993-3.424c.83-1.086 1.928-1.776 3.272-2.02c1.098-.2 2.15-.07 3.096.571c.62.41 1.063.97 1.356 1.648c.07.105.023.164-.117.2m3.868 6.461c-1.064-.024-2.034-.328-2.852-1.029a3.67 3.67 0 0 1-1.262-2.255c-.21-1.32.152-2.489.947-3.529c.853-1.122 1.881-1.706 3.272-1.95c1.192-.21 2.314-.095 3.33.595c.923.63 1.496 1.484 1.648 2.605c.198 1.578-.257 2.863-1.344 3.962c-.771.783-1.718 1.273-2.805 1.495c-.315.06-.63.07-.934.106m2.78-4.72c-.011-.153-.011-.27-.034-.387c-.21-1.157-1.274-1.81-2.384-1.554c-1.087.245-1.788.935-2.045 2.033c-.21.912.234 1.835 1.075 2.21c.643.28 1.285.244 1.905-.07c.923-.48 1.425-1.228 1.484-2.233z"/></svg>
|
||||
<% } else if(tab.icon === 'java'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M8.851 18.56s-.917.534.653.714c1.902.218 2.874.187 4.969-.211 0 0 .552.346 1.321.646-4.699 2.013-10.633-.118-6.943-1.149M8.276 15.933s-1.028.761.542.924c2.032.209 3.636.227 6.413-.308 0 0 .384.389.987.602-5.679 1.661-12.007.13-7.942-1.218M13.116 11.475c1.158 1.333-.304 2.533-.304 2.533s2.939-1.518 1.589-3.418c-1.261-1.772-2.228-2.652 3.007-5.688 0-.001-8.216 2.051-4.292 6.573M19.33 20.504s.679.559-.747.991c-2.712.822-11.288 1.069-13.669.033-.856-.373.75-.89 1.254-.998.527-.114.828-.093.828-.093-.953-.671-6.156 1.317-2.643 1.887 9.58 1.553 17.462-.7 14.977-1.82M9.292 13.21s-4.362 1.036-1.544 1.412c1.189.159 3.561.123 5.77-.062 1.806-.152 3.618-.477 3.618-.477s-.637.272-1.098.587c-4.429 1.165-12.986.623-10.522-.568 2.082-1.006 3.776-.892 3.776-.892M17.116 17.584c4.503-2.34 2.421-4.589.968-4.285-.355.074-.515.138-.515.138s.132-.207.385-.297c2.875-1.011 5.086 2.981-.928 4.562 0-.001.07-.062.09-.118M14.401 0s2.494 2.494-2.365 6.33c-3.896 3.077-.888 4.832-.001 6.836-2.274-2.053-3.943-3.858-2.824-5.539 1.644-2.469 6.197-3.665 5.19-7.627M9.734 23.924c4.322.277 10.959-.153 11.116-2.198 0 0-.302.775-3.572 1.391-3.688.694-8.239.613-10.937.168 0-.001.553.457 3.393.639"/></svg>
|
||||
<% } else if(tab.icon === 'csharp'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M11.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C2.56 15.77 2 14.16 2 12.21c.05-2.31.72-4.08 2-5.32C5.32 5.64 6.96 5 8.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32zm5.5-5.48h3.5v1.33H17v3.45l2 .1v1.27l-3.5-.1v-4.72H14v-1.33h3zm-1 0v1.33h-2v-1.33h2zm0 2.66v1.34h-2v-1.34h2zm0 2.67V17h-2v-1.18h2z"/></svg>
|
||||
<% } else if(tab.icon === 'php'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M7.01 10.207h-.944l-.515 2.648h.838c.556 0 .97-.105 1.242-.314.272-.21.455-.559.55-1.049.092-.47.05-.802-.124-.995-.175-.193-.523-.29-1.047-.29zM12 5.688C5.373 5.688 0 8.514 0 12s5.373 6.313 12 6.313S24 15.486 24 12c0-3.486-5.373-6.312-12-6.312zm-3.26 7.451c-.261.25-.575.438-.917.551-.336.108-.765.164-1.285.164H5.357l-.327 1.681H3.652l1.23-6.326h2.65c.797 0 1.378.209 1.744.628.366.418.476 1.002.33 1.752a2.836 2.836 0 01-.305.847c-.143.255-.33.49-.561.703zm4.024.715l.543-2.799c.063-.318.039-.536-.068-.651-.107-.116-.336-.174-.687-.174H11.46l-.704 3.625H9.388l1.23-6.327h1.367l-.327 1.682h1.218c.767 0 1.295.134 1.586.401s.378.7.263 1.299l-.572 2.944h-1.389zm7.597-2.465a2.782 2.782 0 01-.305.847c-.143.255-.33.49-.561.703a2.44 2.44 0 01-.917.551c-.336.108-.765.164-1.286.164h-1.18l-.327 1.682h-1.378l1.23-6.326h2.649c.797 0 1.378.209 1.744.628.366.417.477 1.001.331 1.751zm-2.595-1.382h-.943l-.516 2.648h.838c.557 0 .971-.105 1.242-.314.272-.21.455-.559.551-1.049.092-.47.049-.802-.125-.995s-.524-.29-1.047-.29z"/></svg>
|
||||
<% } else if(tab.icon === 'ruby'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M20.156.083c3.033.525 3.893 2.598 3.829 4.77L24 4.822 22.635 22.71 4.89 23.926h.016C3.433 23.864.15 23.729 0 19.139l1.645-3 2.819 6.586.503 1.172 2.805-9.144-.03.007.016-.03 9.255 2.956-1.396-5.431-.99-3.9 8.82-.569-.615-.51L16.5 2.114 20.159.073l-.003.01zM0 19.089v.026-.029.003zM5.13 5.073c3.561-3.533 8.157-5.621 9.922-3.84 1.762 1.777-.105 6.105-3.673 9.636-3.563 3.532-8.103 5.734-9.864 3.957-1.766-1.777.045-6.217 3.612-9.75l.003-.003z"/></svg>
|
||||
<% } else if(tab.icon === 'rust'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M23.687 11.709l-.995-.616a13.559 13.559 0 00-.028-.29l.855-.797a.344.344 0 00-.114-.571l-1.093-.409a8.392 8.392 0 00-.086-.282l.682-.947a.344.344 0 00-.199-.54l-1.135-.228a8.344 8.344 0 00-.14-.261l.48-1.066a.344.344 0 00-.276-.477l-1.149-.044a7.373 7.373 0 00-.192-.227l.258-1.151a.344.344 0 00-.348-.389l-1.136.14a6.4 6.4 0 00-.238-.18l.024-1.186a.344.344 0 00-.41-.281l-1.097.32a5.7 5.7 0 00-.275-.12l-.212-1.178a.344.344 0 00-.46-.157l-1.028.494a4.87 4.87 0 00-.3-.054l-.441-1.129a.344.344 0 00-.494-.023l-.935.657a3.787 3.787 0 00-.315.015l-.66-1.038a.344.344 0 00-.512.114l-.817.802a3.453 3.453 0 00-.318.084l-.86-.911a.344.344 0 00-.512.234l-.68.932a3.29 3.29 0 00-.304.152l-1.033-.744a.344.344 0 00-.493.342l-.524 1.04a3.29 3.29 0 00-.275.216l-1.168-.54a.344.344 0 00-.457.43l-.353 1.124a3.453 3.453 0 00-.233.275l-1.262-.3a.344.344 0 00-.404.498l-.168 1.178a3.787 3.787 0 00-.178.322l-1.315-.032a.344.344 0 00-.333.547l.025 1.2a4.87 4.87 0 00-.113.354l-1.325.241a.344.344 0 00-.248.576l.227 1.19a5.7 5.7 0 00-.04.373l-1.29.513a.344.344 0 00-.151.583l.422 1.147a6.4 6.4 0 00.038.38l-1.212.77a.344.344 0 00-.046.57l.605 1.072a7.373 7.373 0 00.116.371l-1.094 1.008a.344.344 0 00.06.538l.771.967a8.344 8.344 0 00.19.348l-.94 1.218a.344.344 0 00.163.486l.915.836a8.392 8.392 0 00.259.31l-.756 1.393a.344.344 0 00.258.415l1.03.68c.1.09.202.178.307.264l-.544 1.53a.344.344 0 00.342.327l1.115.504a13.559 13.559 0 00.173.12l-.312 1.63a.344.344 0 00.413.223l1.17.307a14.195 14.195 0 00.22.085l-.067 1.688a.344.344 0 00.469.106l1.193.094a14.718 14.718 0 00.253.044l.184 1.7a.344.344 0 00.508-.016l1.184-.128a14.94 14.94 0 00.273-.003l.432 1.667a.344.344 0 00.528-.136l1.142-.347a14.862 14.862 0 00.282-.053l.67 1.588a.344.344 0 00.53-.246l1.072-.56a14.49 14.49 0 00.278-.105l.893 1.465a.344.344 0 00.512-.343l.972-.76a13.844 13.844 0 00.262-.156l1.095 1.3a.344.344 0 00.475-.425l.85-.943c.08-.063.157-.127.234-.192l1.27 1.094a.344.344 0 00.422-.49l.706-1.105a12.656 12.656 0 00.197-.225l1.412.853a.344.344 0 00.354-.538l.544-1.24c.058-.084.115-.17.17-.255l1.521.583a.344.344 0 00.27-.567l.367-1.346c.047-.092.092-.185.136-.279l1.59.293a.344.344 0 00.176-.575l.18-1.418a9.903 9.903 0 00.094-.3l1.62-.008a.344.344 0 00.076-.56l-.013-1.455a9.245 9.245 0 00.045-.315l1.607-.307a.344.344 0 00-.028-.522l-.206-1.457c.005-.109.007-.218.007-.328l1.553-.595a.344.344 0 00-.13-.461zM12 16.874a4.874 4.874 0 110-9.748 4.874 4.874 0 010 9.748z"/></svg>
|
||||
<% } else if(tab.icon === 'powershell'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M23.181 2.974c.568 0 .923.463.792 1.035l-3.659 15.982c-.13.572-.697 1.035-1.265 1.035H.819c-.568 0-.923-.463-.792-1.035L3.686 4.009c.13-.572.697-1.035 1.265-1.035h18.23zm-8.375 12.15c0-.292-.123-.559-.322-.744l.004.004-5.444-4.678c-.162-.144-.373-.232-.605-.232-.506 0-.916.41-.916.916 0 .272.119.516.307.684l-.002-.002 4.453 3.832-4.453 3.832c-.188.166-.307.41-.307.684 0 .506.41.916.916.916.232 0 .443-.088.605-.232l5.444-4.678c.2-.186.323-.453.323-.746v-.556h-.003zm.62 2.406c0-.355.288-.644.644-.644h4.023c.355 0 .644.288.644.644 0 .355-.288.644-.644.644h-4.023a.644.644 0 01-.644-.644z"/></svg>
|
||||
<% } %>
|
||||
</span>
|
||||
<span><%= tab.name %></span>
|
||||
</button>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Code Panels -->
|
||||
<div class="rounded-b-xl bg-slate-900 border border-slate-700/50 border-t-0 overflow-hidden min-w-0">
|
||||
<% tabs.forEach((tab, index) => { %>
|
||||
<div
|
||||
role="tabpanel"
|
||||
id="<%= uniqueId %>-panel-<%= tab.id %>"
|
||||
aria-labelledby="<%= uniqueId %>-tab-<%= tab.id %>"
|
||||
class="code-panel relative min-w-0 w-full <%= index === 0 ? '' : 'hidden' %>"
|
||||
data-panel-id="<%= tab.id %>"
|
||||
>
|
||||
<% if(tab.isPreview) { %>
|
||||
<!-- Request Preview Panel -->
|
||||
<button
|
||||
class="copy-btn-tabs"
|
||||
onclick="copyPreviewFromPanel(this)"
|
||||
aria-label="Copy request"
|
||||
>
|
||||
<svg class="copy-icon w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<svg class="check-icon w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="copy-text">Copy</span>
|
||||
</button>
|
||||
<div class="code-content p-4 w-full max-w-full overflow-hidden" style="min-height: 200px;">
|
||||
<!-- Headers Section -->
|
||||
<div class="mb-4">
|
||||
<div class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Headers</div>
|
||||
<div class="bg-slate-800/50 rounded-lg p-3 border border-slate-700/50 overflow-hidden">
|
||||
<pre class="text-sm text-slate-300 font-mono whitespace-pre-wrap m-0 preview-headers overflow-x-auto"><%= codeExamples.requestPreview.headers %></pre>
|
||||
</div>
|
||||
</div>
|
||||
<% if(codeExamples.requestPreview.body) { %>
|
||||
<!-- Body Section -->
|
||||
<div>
|
||||
<div class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Body</div>
|
||||
<div class="bg-slate-800/50 rounded-lg p-3 border border-slate-700/50 overflow-hidden">
|
||||
<pre class="text-sm leading-relaxed m-0 overflow-x-auto"><code class="language-json preview-body"><%= codeExamples.requestPreview.body %></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<!-- Code Panel -->
|
||||
<button
|
||||
class="copy-btn-tabs"
|
||||
onclick="copyCodeFromPanel(this)"
|
||||
aria-label="Copy code"
|
||||
>
|
||||
<svg class="copy-icon w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<svg class="check-icon w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="copy-text">Copy</span>
|
||||
</button>
|
||||
<div class="code-content w-full max-w-full overflow-hidden" style="min-height: 200px;">
|
||||
<pre class="overflow-x-auto p-4 text-sm leading-relaxed m-0 w-full max-w-full"><code class="language-<%= tab.id === 'javascript' || tab.id === 'typescript' ? 'javascript' : (tab.id === 'python' ? 'python' : (tab.id === 'go' ? 'go' : (tab.id === 'ruby' ? 'ruby' : (tab.id === 'rust' ? 'rust' : (tab.id === 'powershell' ? 'powershell' : (tab.id === 'java' ? 'java' : (tab.id === 'csharp' ? 'csharp' : (tab.id === 'php' ? 'php' : 'bash')))))))) %>"><%= codeExamples[tab.id] %></code></pre>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function switchCodeTab(containerId, tabId) {
|
||||
// Update all code-tabs-containers on the page
|
||||
document.querySelectorAll('.code-tabs-container').forEach(container => {
|
||||
// Update tabs
|
||||
container.querySelectorAll('.code-tab').forEach(tab => {
|
||||
const isActive = tab.dataset.tabId === tabId;
|
||||
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
||||
|
||||
// Update tab styling
|
||||
if (isActive) {
|
||||
tab.classList.remove('text-slate-400', 'hover:text-slate-300', 'hover:bg-slate-800/30');
|
||||
tab.classList.add('text-indigo-400', 'bg-slate-800/50');
|
||||
} else {
|
||||
tab.classList.add('text-slate-400', 'hover:text-slate-300', 'hover:bg-slate-800/30');
|
||||
tab.classList.remove('text-indigo-400', 'bg-slate-800/50');
|
||||
}
|
||||
});
|
||||
|
||||
// Update panels
|
||||
container.querySelectorAll('.code-panel').forEach(panel => {
|
||||
if (panel.dataset.panelId === tabId) {
|
||||
panel.classList.remove('hidden');
|
||||
// Apply syntax highlighting to code blocks
|
||||
panel.querySelectorAll('pre code').forEach((block) => {
|
||||
if (window.hljs && !block.dataset.highlighted) {
|
||||
hljs.highlightElement(block);
|
||||
block.dataset.highlighted = 'true';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
panel.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Store preference in localStorage (only for non-preview tabs)
|
||||
if (tabId !== 'preview') {
|
||||
try {
|
||||
localStorage.setItem('preferred-code-lang', tabId);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
function copyCodeFromPanel(btn) {
|
||||
const panel = btn.closest('.code-panel');
|
||||
const code = panel.querySelector('code');
|
||||
if (code) {
|
||||
navigator.clipboard.writeText(code.textContent).then(() => {
|
||||
showCopySuccess(btn);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function copyPreviewFromPanel(btn) {
|
||||
const panel = btn.closest('.code-panel');
|
||||
const headers = panel.querySelector('.preview-headers');
|
||||
const body = panel.querySelector('.preview-body');
|
||||
|
||||
let textToCopy = '';
|
||||
if (headers) {
|
||||
textToCopy += headers.textContent;
|
||||
}
|
||||
if (body) {
|
||||
textToCopy += '\n\n' + body.textContent;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||
showCopySuccess(btn);
|
||||
});
|
||||
}
|
||||
|
||||
function showCopySuccess(btn) {
|
||||
const copyIcon = btn.querySelector('.copy-icon');
|
||||
const checkIcon = btn.querySelector('.check-icon');
|
||||
const copyText = btn.querySelector('.copy-text');
|
||||
|
||||
copyIcon.classList.add('hidden');
|
||||
checkIcon.classList.remove('hidden');
|
||||
copyText.textContent = 'Copied!';
|
||||
btn.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
copyIcon.classList.remove('hidden');
|
||||
checkIcon.classList.add('hidden');
|
||||
copyText.textContent = 'Copy';
|
||||
btn.classList.remove('copied');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Apply syntax highlighting and stored language preference on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Wait a bit for all scripts to load, then highlight all code blocks
|
||||
setTimeout(function() {
|
||||
if (window.hljs) {
|
||||
// Highlight ALL code blocks (including hidden ones)
|
||||
document.querySelectorAll('pre code').forEach((block) => {
|
||||
if (!block.classList.contains('hljs')) {
|
||||
hljs.highlightElement(block);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Apply stored preference if exists
|
||||
try {
|
||||
const preferred = localStorage.getItem('preferred-code-lang');
|
||||
if (preferred && preferred !== 'preview') {
|
||||
// Switch all containers to the preferred language
|
||||
document.querySelectorAll('.code-tabs-container').forEach(container => {
|
||||
const tab = container.querySelector(`[data-tab-id="${preferred}"]`);
|
||||
if (tab) {
|
||||
switchCodeTab(container.id, preferred);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
@@ -1,33 +1,63 @@
|
||||
<div class="not-prose my-6 overflow-hidden rounded-2xl bg-zinc-900 shadow-md ">
|
||||
<div class="flex min-h-[calc(theme(spacing.12)+1px)] flex-wrap items-start gap-x-4 border-b border-zinc-700 bg-zinc-800 px-4 ">
|
||||
<h4 class="mr-auto text-xs font-semibold text-white mt-5"><%= title -%></h4>
|
||||
<div class="not-prose my-6 overflow-hidden rounded-xl bg-slate-900 shadow-lg ring-1 ring-slate-800/50 code-block-wrapper">
|
||||
<div class="flex min-h-[calc(theme(spacing.11)+1px)] flex-wrap items-center gap-x-4 border-b border-slate-700/50 bg-slate-800/50 px-3 sm:px-4">
|
||||
<h4 class="mr-auto text-xs font-semibold text-slate-300 tracking-wide"><%= title -%></h4>
|
||||
<button class="copy-btn-response" onclick="copyCodeBlock(this)" aria-label="Copy code">
|
||||
<svg class="copy-icon w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<svg class="check-icon w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="copy-text">Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="group ">
|
||||
|
||||
|
||||
<div class="group">
|
||||
<% if(requestType && requestUrl){ %>
|
||||
|
||||
<div class="flex h-9 items-center gap-2 border-b-white/7.5 bg-zinc-900 bg-white/2.5 px-4 ">
|
||||
<div class="flex flex-wrap sm:flex-nowrap items-center gap-2 sm:gap-3 py-2 sm:py-0 sm:h-10 border-b border-slate-700/30 bg-slate-800/30 px-3 sm:px-4">
|
||||
<% if( requestType === "GET"){ %>
|
||||
<div class="dark flex"><span class="font-mono text-sm font-semibold leading-6 text-emerald-500 ">GET</span></div>
|
||||
<% } %>
|
||||
<span class="inline-flex items-center rounded-md bg-emerald-500/10 px-2 py-1 text-xs font-semibold text-emerald-400 ring-1 ring-inset ring-emerald-500/20 flex-shrink-0">GET</span>
|
||||
<% } %>
|
||||
<% if( requestType === "POST"){ %>
|
||||
<div class="dark flex"><span class="font-mono text-sm font-semibold leading-6 text-sky-500 ">POST</span></div>
|
||||
<% } %>
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-500/10 px-2 py-1 text-xs font-semibold text-indigo-400 ring-1 ring-inset ring-indigo-500/20 flex-shrink-0">POST</span>
|
||||
<% } %>
|
||||
<% if( requestType === "DELETE"){ %>
|
||||
<div class="dark flex"><span class="font-mono text-sm font-semibold leading-6 text-red-500 ">DELETE</span></div>
|
||||
<% } %>
|
||||
<span class="inline-flex items-center rounded-md bg-red-500/10 px-2 py-1 text-xs font-semibold text-red-400 ring-1 ring-inset ring-red-500/20 flex-shrink-0">DELETE</span>
|
||||
<% } %>
|
||||
<% if( requestType === "PUT"){ %>
|
||||
<div class="dark flex"><span class="font-mono text-sm font-semibold leading-6 text-amber-500 ">PUT</span></div>
|
||||
<% } %>
|
||||
<span class="h-0.5 w-0.5 rounded-full bg-zinc-500"></span><span class="font-mono text-sm text-zinc-300"><%= requestUrl -%></span>
|
||||
<span class="inline-flex items-center rounded-md bg-amber-500/10 px-2 py-1 text-xs font-semibold text-amber-400 ring-1 ring-inset ring-amber-500/20 flex-shrink-0">PUT</span>
|
||||
<% } %>
|
||||
<span class="font-mono text-xs sm:text-sm text-slate-400 break-all"><%= requestUrl -%></span>
|
||||
</div>
|
||||
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% if(code){ %>
|
||||
<div class="relative">
|
||||
<pre class="overflow-x-auto p-4 text-xs text-white"><code class="text-sm"><%= code -%></code></pre>
|
||||
<pre class="overflow-x-auto p-4 text-sm text-slate-300 leading-relaxed"><code class="language-json text-sm"><%= code -%></code></pre>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function copyCodeBlock(btn) {
|
||||
const codeBlock = btn.closest('.code-block-wrapper');
|
||||
const code = codeBlock.querySelector('code');
|
||||
if (code) {
|
||||
navigator.clipboard.writeText(code.textContent).then(() => {
|
||||
const copyIcon = btn.querySelector('.copy-icon');
|
||||
const checkIcon = btn.querySelector('.check-icon');
|
||||
const copyText = btn.querySelector('.copy-text');
|
||||
|
||||
copyIcon.classList.add('hidden');
|
||||
checkIcon.classList.remove('hidden');
|
||||
copyText.textContent = 'Copied!';
|
||||
btn.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
copyIcon.classList.remove('hidden');
|
||||
checkIcon.classList.add('hidden');
|
||||
copyText.textContent = 'Copy';
|
||||
btn.classList.remove('copied');
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,31 +1,41 @@
|
||||
<footer class="mx-auto max-w-2xl space-y-10 pb-16 lg:max-w-5xl">
|
||||
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center justify-between gap-5 border-t border-zinc-900/5 pt-8 sm:flex-row">
|
||||
<p class="text-xs text-zinc-600 ">
|
||||
OneUptime documentation is under MIT license. Please feel free to contribute and improve it on GitHub.
|
||||
</p>
|
||||
<div class="flex gap-4">
|
||||
<a class="group" href="https://twitter.com/OneUptimeHQ" target="_blank">
|
||||
<span class="sr-only">Follow us on Twitter</span>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"
|
||||
class="h-5 w-5 fill-zinc-700 transition group-hover:fill-zinc-900 :fill-zinc-500">
|
||||
<path
|
||||
d="M16.712 6.652c.01.146.01.29.01.436 0 4.449-3.267 9.579-9.242 9.579v-.003a8.963 8.963 0 0 1-4.98-1.509 6.379 6.379 0 0 0 4.807-1.396c-1.39-.027-2.608-.966-3.035-2.337.487.097.99.077 1.467-.059-1.514-.316-2.606-1.696-2.606-3.3v-.041c.45.26.956.404 1.475.42C3.18 7.454 2.74 5.486 3.602 3.947c1.65 2.104 4.083 3.382 6.695 3.517a3.446 3.446 0 0 1 .94-3.217 3.172 3.172 0 0 1 4.596.148 6.38 6.38 0 0 0 2.063-.817 3.357 3.357 0 0 1-1.428 1.861 6.283 6.283 0 0 0 1.865-.53 6.735 6.735 0 0 1-1.62 1.744Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
<a class="group" href="https://github.com/oneuptime/oneuptime" target="_blank">
|
||||
<span class="sr-only">Follow us on GitHub</span>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"
|
||||
class="h-5 w-5 fill-zinc-700 transition group-hover:fill-zinc-900 :fill-zinc-500">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<footer class="mx-auto w-full space-y-10 pb-16">
|
||||
<div class="mt-12 sm:mt-16 pt-8 border-t border-slate-200">
|
||||
<div class="rounded-xl bg-gradient-to-r from-slate-50 to-slate-100/50 p-4 sm:p-6 ring-1 ring-slate-200/50">
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex-shrink-0 rounded-lg bg-indigo-100 p-2.5">
|
||||
<svg class="h-5 w-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 mb-1">Open Source</h3>
|
||||
<p class="text-sm text-slate-600 leading-relaxed">
|
||||
OneUptime API documentation is under Apache 2.0 license. Contribute on GitHub.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<a class="group flex items-center justify-center w-9 h-9 rounded-lg bg-white ring-1 ring-slate-200 hover:ring-indigo-300 hover:bg-indigo-50 transition-all" href="https://twitter.com/OneUptimeHQ" target="_blank">
|
||||
<span class="sr-only">Follow us on Twitter</span>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"
|
||||
class="h-4 w-4 fill-slate-500 transition group-hover:fill-indigo-600">
|
||||
<path
|
||||
d="M16.712 6.652c.01.146.01.29.01.436 0 4.449-3.267 9.579-9.242 9.579v-.003a8.963 8.963 0 0 1-4.98-1.509 6.379 6.379 0 0 0 4.807-1.396c-1.39-.027-2.608-.966-3.035-2.337.487.097.99.077 1.467-.059-1.514-.316-2.606-1.696-2.606-3.3v-.041c.45.26.956.404 1.475.42C3.18 7.454 2.74 5.486 3.602 3.947c1.65 2.104 4.083 3.382 6.695 3.517a3.446 3.446 0 0 1 .94-3.217 3.172 3.172 0 0 1 4.596.148 6.38 6.38 0 0 0 2.063-.817 3.357 3.357 0 0 1-1.428 1.861 6.283 6.283 0 0 0 1.865-.53 6.735 6.735 0 0 1-1.62 1.744Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
<a class="group flex items-center justify-center w-9 h-9 rounded-lg bg-white ring-1 ring-slate-200 hover:ring-indigo-300 hover:bg-indigo-50 transition-all" href="https://github.com/oneuptime/oneuptime" target="_blank">
|
||||
<span class="sr-only">Follow us on GitHub</span>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"
|
||||
class="h-4 w-4 fill-slate-500 transition group-hover:fill-indigo-600">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</footer>
|
||||
@@ -4,90 +4,377 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
||||
rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<!-- Load additional language support -->
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/java.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/csharp.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/php.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/ruby.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/rust.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/powershell.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/json.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/typescript.min.js"></script>
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<script>
|
||||
function applyStyles() {
|
||||
applyStylesTo("h1", "font-bold text-2xl mb-5")
|
||||
applyStylesTo("h2", "mb-5 scroll-mt-24 mt-24 font-bold text-xl")
|
||||
applyStylesTo("h3", "mb-5 scroll-mt-24 mt-10 font-bold text-base")
|
||||
applyStylesTo("p", "mb-5")
|
||||
applyStylesTo("link", "text-emerald-500 hover:text-emerald-600")
|
||||
applyStylesTo("model-inline-code", "rounded p-0.5 px-1 text-sm text-gray-50 bg-gray-600 border-2 border-gray-600 shadow")
|
||||
applyStylesTo("inline-code", "rounded p-0.5 px-1 text-sm text-gray-500 bg-gray-100 border-2 border-gray-200")
|
||||
applyStylesTo("h1", "font-bold text-2xl mb-6 text-slate-900 tracking-tight")
|
||||
applyStylesTo("h2", "mb-5 scroll-mt-24 mt-16 font-semibold text-xl text-slate-900 tracking-tight")
|
||||
applyStylesTo("h3", "mb-4 scroll-mt-24 mt-10 font-semibold text-base text-slate-800")
|
||||
applyStylesTo("p", "mb-5 text-slate-600 leading-7")
|
||||
applyStylesTo("link", "text-indigo-600 hover:text-indigo-700 font-medium transition-colors")
|
||||
applyStylesTo("model-inline-code", "rounded-md py-0.5 px-1.5 text-sm font-medium text-slate-700 bg-slate-100 border border-slate-200")
|
||||
applyStylesTo("inline-code", "rounded-md py-0.5 px-1.5 text-sm font-medium text-slate-700 bg-slate-100 border border-slate-200")
|
||||
}
|
||||
|
||||
function applyStylesTo(tagOrClassName, classList) {
|
||||
|
||||
|
||||
let elements = document.getElementsByClassName(tagOrClassName);
|
||||
|
||||
if(elements.length === 0){
|
||||
elements = document.getElementsByTagName(tagOrClassName);
|
||||
}
|
||||
|
||||
for (var i = 0, all = elements.length; i < all; i++) {
|
||||
classList.split(" ").map((classItem)=> {
|
||||
elements[i].classList.add(classItem);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.hljs {
|
||||
color: unset;
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
.hljs-string {
|
||||
color: #6ee7b7
|
||||
}
|
||||
|
||||
.hljs-number {
|
||||
color: #7dd3fc
|
||||
}
|
||||
|
||||
.hljs-punctuation {
|
||||
color: #e5e7eb
|
||||
}
|
||||
|
||||
.hljs-comment {
|
||||
color: #9ca3af
|
||||
}
|
||||
|
||||
.hljs-keyword{
|
||||
color: #7dd3fc;
|
||||
font-weight: unset;
|
||||
}
|
||||
|
||||
.hljs-attr{
|
||||
color: #fda4af;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: Inter;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
|
||||
/* Better prose styling */
|
||||
.prose p {
|
||||
color: #475569;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.prose .lead {
|
||||
font-size: 1.125rem;
|
||||
color: #64748b;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.prose hr {
|
||||
border-color: #e2e8f0;
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.prose ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Syntax highlighting - comprehensive token styles */
|
||||
.hljs {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Keywords: import, from, def, class, if, else, for, while, return, etc. */
|
||||
.hljs-keyword,
|
||||
.hljs-built_in,
|
||||
.hljs-type,
|
||||
.hljs-literal,
|
||||
.hljs-symbol {
|
||||
color: #c792ea !important;
|
||||
}
|
||||
|
||||
/* Strings */
|
||||
.hljs-string,
|
||||
.hljs-doctag,
|
||||
.hljs-regexp {
|
||||
color: #c3e88d !important;
|
||||
}
|
||||
|
||||
/* Numbers */
|
||||
.hljs-number {
|
||||
color: #f78c6c !important;
|
||||
}
|
||||
|
||||
/* Comments */
|
||||
.hljs-comment {
|
||||
color: #676e95 !important;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Function/method names */
|
||||
.hljs-title,
|
||||
.hljs-title.function_,
|
||||
.hljs-title.class_ {
|
||||
color: #82aaff !important;
|
||||
}
|
||||
|
||||
/* Variables and parameters */
|
||||
.hljs-variable,
|
||||
.hljs-params,
|
||||
.hljs-attr {
|
||||
color: #f07178 !important;
|
||||
}
|
||||
|
||||
/* Properties and attributes */
|
||||
.hljs-property,
|
||||
.hljs-attribute {
|
||||
color: #ffcb6b !important;
|
||||
}
|
||||
|
||||
/* Punctuation */
|
||||
.hljs-punctuation {
|
||||
color: #89ddff !important;
|
||||
}
|
||||
|
||||
/* Operators */
|
||||
.hljs-operator {
|
||||
color: #89ddff !important;
|
||||
}
|
||||
|
||||
/* Meta/preprocessor */
|
||||
.hljs-meta {
|
||||
color: #ffcb6b !important;
|
||||
}
|
||||
|
||||
/* Section headers */
|
||||
.hljs-section {
|
||||
color: #82aaff !important;
|
||||
}
|
||||
|
||||
/* Names (function calls, etc) */
|
||||
.hljs-name {
|
||||
color: #f07178 !important;
|
||||
}
|
||||
|
||||
/* Selector tags in CSS */
|
||||
.hljs-selector-tag,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id {
|
||||
color: #c792ea !important;
|
||||
}
|
||||
|
||||
/* Template variables */
|
||||
.hljs-template-variable {
|
||||
color: #f07178 !important;
|
||||
}
|
||||
|
||||
/* Deletion/addition in diffs */
|
||||
.hljs-deletion {
|
||||
color: #f07178 !important;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #c3e88d !important;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
bottom: 0;
|
||||
color: transparent;
|
||||
cursor: pointer;
|
||||
height: auto;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #e2e8f0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #cbd5e1;
|
||||
}
|
||||
|
||||
/* Code block copy button */
|
||||
.code-block-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Response block copy button */
|
||||
.copy-btn-response {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #94a3b8;
|
||||
background: rgba(30, 41, 59, 0.6);
|
||||
border: 1px solid #334155;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.copy-btn-response:hover {
|
||||
color: #f1f5f9;
|
||||
background: #334155;
|
||||
border-color: #475569;
|
||||
}
|
||||
.copy-btn-response.copied {
|
||||
color: #22c55e;
|
||||
border-color: #22c55e;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
/* Code tabs styling */
|
||||
.code-tabs-container {
|
||||
--tab-hover-bg: rgba(30, 41, 59, 0.5);
|
||||
}
|
||||
|
||||
.code-tab {
|
||||
position: relative;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.code-tab[aria-selected="true"] {
|
||||
border-bottom-color: #6366f1;
|
||||
}
|
||||
|
||||
.code-panel {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-btn-tabs {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #94a3b8;
|
||||
background: rgba(30, 41, 59, 0.9);
|
||||
border: 1px solid #334155;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: all 0.15s ease;
|
||||
z-index: 10;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.code-panel:hover .copy-btn-tabs {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.copy-btn-tabs:hover {
|
||||
color: #f1f5f9;
|
||||
background: #334155;
|
||||
border-color: #475569;
|
||||
}
|
||||
|
||||
.copy-btn-tabs.copied {
|
||||
color: #22c55e;
|
||||
border-color: #22c55e;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
/* Code content min-height for consistent sizing */
|
||||
.code-content {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
/* Fix code tabs width - prevent content from expanding container */
|
||||
.code-tabs-container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-tabs-container pre {
|
||||
width: 0;
|
||||
min-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-panel {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-content {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-content pre code {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for tabs but keep functionality */
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tab indicator animation */
|
||||
.tab-indicator {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* Code panel base styles */
|
||||
.code-panel pre {
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.code-panel code {
|
||||
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
/* Better focus states */
|
||||
a:focus-visible, button:focus-visible {
|
||||
outline: 2px solid #6366f1;
|
||||
outline-offset: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
a, button {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
/* Property list styling */
|
||||
.property-item {
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
.property-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Endpoint card styling */
|
||||
.endpoint-card {
|
||||
background: linear-gradient(to right, #fafafa, white);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Calendar picker */
|
||||
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
|
||||
input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
background: transparent;
|
||||
bottom: 0;
|
||||
@@ -101,7 +388,6 @@
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %>
|
||||
|
||||
@@ -144,7 +430,7 @@
|
||||
<meta property="og:image" content="https://oneuptime.com/img/hou-wb.svg">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="theme-color" content="#6366f1">
|
||||
<meta name="twitter:image" content="/img/ou-wb.svg">
|
||||
<meta name="twitter:site" content="@oneuptimeinc">
|
||||
<meta name="twitter:title" content="OneUptime - One Complete Observability platform.">
|
||||
|
||||
@@ -1,9 +1,92 @@
|
||||
<header class="contents lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex">
|
||||
<div
|
||||
class="contents lg:pointer-events-auto lg:block lg:w-72 lg:overflow-y-auto lg:border-r lg:border-zinc-900/10 lg:px-6 lg:pt-4 lg:pb-8 xl:w-80">
|
||||
<div class="hidden lg:flex">
|
||||
class="contents lg:pointer-events-auto lg:block lg:w-72 lg:overflow-y-auto lg:border-r lg:border-slate-200/80 lg:px-6 lg:pt-6 lg:pb-8 xl:w-80 lg:bg-gradient-to-b lg:from-slate-50 lg:to-white">
|
||||
<div class="hidden lg:flex lg:items-center lg:mb-8">
|
||||
<a aria-label="Home" href="/">
|
||||
<svg class="h-6 -ml-48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
<svg class="h-7" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 75" preserveAspectRatio="xMidYMid meet">
|
||||
<defs>
|
||||
<g></g>
|
||||
<clipPath id="1d83d73318">
|
||||
<path d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
<clipPath id="32c1ad7ad2">
|
||||
<path d="M 366.480469 51.03125 L 352.816406 51.03125 C 352.253906 51.03125 351.792969 50.574219 351.792969 50.007812 L 351.792969 36.347656 C 351.792969 35.785156 352.253906 35.324219 352.816406 35.324219 L 366.480469 35.324219 C 367.042969 35.324219 367.5 35.785156 367.5 36.347656 L 367.5 50.007812 C 367.5 50.574219 367.042969 51.03125 366.480469 51.03125 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#1d83d73318)">
|
||||
<g clip-path="url(#32c1ad7ad2)">
|
||||
<path fill="#7ed957" d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " fill-opacity="1" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(11.173064, 55.95717)">
|
||||
<g>
|
||||
<path d="M 1.5625 -21.828125 C 1.5625 -25.191406 2.054688 -28.320312 3.046875 -31.21875 C 4.046875 -34.164062 5.5 -36.71875 7.40625 -38.875 C 9.3125 -41.070312 11.632812 -42.773438 14.375 -43.984375 C 15.738281 -44.597656 17.195312 -45.0625 18.75 -45.375 C 20.3125 -45.6875 21.941406 -45.84375 23.640625 -45.84375 C 25.335938 -45.84375 26.960938 -45.6875 28.515625 -45.375 C 30.078125 -45.0625 31.539062 -44.578125 32.90625 -43.921875 C 34.269531 -43.296875 35.53125 -42.546875 36.6875 -41.671875 C 37.851562 -40.804688 38.914062 -39.8125 39.875 -38.6875 C 41.78125 -36.53125 43.25 -34.003906 44.28125 -31.109375 C 45.28125 -28.203125 45.78125 -25.109375 45.78125 -21.828125 C 45.78125 -18.554688 45.28125 -15.46875 44.28125 -12.5625 C 43.957031 -11.613281 43.578125 -10.679688 43.140625 -9.765625 C 42.703125 -8.847656 42.210938 -7.984375 41.671875 -7.171875 C 41.140625 -6.367188 40.5625 -5.597656 39.9375 -4.859375 C 38.976562 -3.734375 37.90625 -2.734375 36.71875 -1.859375 C 35.539062 -0.992188 34.289062 -0.25 32.96875 0.375 C 30.226562 1.65625 27.113281 2.296875 23.625 2.296875 C 20.144531 2.296875 17.039062 1.65625 14.3125 0.375 C 13.644531 0.0390625 13 -0.3125 12.375 -0.6875 C 11.757812 -1.0625 11.160156 -1.460938 10.578125 -1.890625 C 9.992188 -2.328125 9.429688 -2.796875 8.890625 -3.296875 C 8.359375 -3.796875 7.863281 -4.316406 7.40625 -4.859375 C 5.5 -7.054688 4.046875 -9.625 3.046875 -12.5625 C 2.054688 -15.46875 1.5625 -18.554688 1.5625 -21.828125 Z M 10.703125 -21.828125 C 10.703125 -20.796875 10.773438 -19.769531 10.921875 -18.75 C 11.066406 -17.738281 11.28125 -16.734375 11.5625 -15.734375 C 11.894531 -14.785156 12.269531 -13.882812 12.6875 -13.03125 C 13.101562 -12.175781 13.601562 -11.378906 14.1875 -10.640625 C 14.351562 -10.390625 14.535156 -10.160156 14.734375 -9.953125 C 14.941406 -9.742188 15.148438 -9.535156 15.359375 -9.328125 C 15.566406 -9.117188 15.785156 -8.910156 16.015625 -8.703125 C 16.242188 -8.492188 16.484375 -8.304688 16.734375 -8.140625 C 16.984375 -7.984375 17.234375 -7.820312 17.484375 -7.65625 C 17.734375 -7.488281 17.976562 -7.320312 18.21875 -7.15625 C 19.800781 -6.320312 21.609375 -5.90625 23.640625 -5.90625 C 24.378906 -5.90625 25.082031 -5.945312 25.75 -6.03125 C 26.414062 -6.113281 27.035156 -6.25 27.609375 -6.4375 C 28.191406 -6.625 28.734375 -6.84375 29.234375 -7.09375 C 30.847656 -7.875 32.195312 -8.953125 33.28125 -10.328125 C 33.65625 -10.785156 33.984375 -11.269531 34.265625 -11.78125 C 34.554688 -12.300781 34.828125 -12.863281 35.078125 -13.46875 C 35.328125 -14.070312 35.554688 -14.679688 35.765625 -15.296875 C 36.347656 -17.328125 36.640625 -19.503906 36.640625 -21.828125 C 36.640625 -23.898438 36.328125 -25.894531 35.703125 -27.8125 C 35.117188 -29.71875 34.25 -31.414062 33.09375 -32.90625 C 31.96875 -34.351562 30.617188 -35.515625 29.046875 -36.390625 C 27.472656 -37.222656 25.671875 -37.640625 23.640625 -37.640625 C 23.140625 -37.640625 22.640625 -37.617188 22.140625 -37.578125 C 21.640625 -37.535156 21.160156 -37.460938 20.703125 -37.359375 C 20.253906 -37.253906 19.820312 -37.128906 19.40625 -36.984375 C 18.988281 -36.835938 18.59375 -36.660156 18.21875 -36.453125 C 17.394531 -36.078125 16.648438 -35.617188 15.984375 -35.078125 C 15.316406 -34.546875 14.71875 -33.925781 14.1875 -33.21875 C 13.0625 -31.8125 12.1875 -30.148438 11.5625 -28.234375 C 10.988281 -26.242188 10.703125 -24.109375 10.703125 -21.828125 Z M 10.703125 -21.828125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(58.505968, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.046875 C 12.269531 -31.367188 12.601562 -31.65625 12.9375 -31.90625 C 13.269531 -32.15625 13.625 -32.382812 14 -32.59375 C 16.070312 -33.957031 18.5 -34.640625 21.28125 -34.640625 C 23.3125 -34.640625 25.113281 -34.316406 26.6875 -33.671875 C 28.257812 -33.035156 29.585938 -32.078125 30.671875 -30.796875 C 32.703125 -28.304688 33.71875 -25.070312 33.71875 -21.09375 L 33.71875 1.5625 L 25 1.5625 L 25 -20.46875 C 25 -21.164062 24.945312 -21.804688 24.84375 -22.390625 C 24.738281 -22.972656 24.59375 -23.488281 24.40625 -23.9375 C 24.226562 -24.394531 23.992188 -24.789062 23.703125 -25.125 C 23.453125 -25.457031 23.171875 -25.738281 22.859375 -25.96875 C 22.546875 -26.195312 22.179688 -26.363281 21.765625 -26.46875 C 21.359375 -26.570312 20.882812 -26.625 20.34375 -26.625 C 19.59375 -26.625 18.84375 -26.476562 18.09375 -26.1875 C 17.3125 -25.894531 16.546875 -25.476562 15.796875 -24.9375 C 15.054688 -24.363281 14.351562 -23.722656 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.046875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(95.327364, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(129.163241, 55.95717)">
|
||||
<g>
|
||||
<path d="M 31.28125 -45.09375 L 38.5625 -45.09375 L 38.5625 -16.796875 C 38.5625 -13.890625 38.148438 -11.253906 37.328125 -8.890625 C 36.492188 -6.523438 35.289062 -4.492188 33.71875 -2.796875 C 33.382812 -2.460938 33.039062 -2.148438 32.6875 -1.859375 C 32.332031 -1.578125 31.976562 -1.289062 31.625 -1 C 31.28125 -0.707031 30.910156 -0.445312 30.515625 -0.21875 C 30.117188 0.0078125 29.710938 0.226562 29.296875 0.4375 C 28.878906 0.644531 28.441406 0.832031 27.984375 1 C 25.828125 1.863281 23.425781 2.296875 20.78125 2.296875 C 15.257812 2.296875 10.925781 0.660156 7.78125 -2.609375 C 4.664062 -5.890625 3.109375 -10.535156 3.109375 -16.546875 L 3.109375 -45.09375 L 12.125 -45.09375 L 12.125 -17.484375 C 12.125 -13.617188 12.875 -10.691406 14.375 -8.703125 C 15.03125 -7.796875 15.875 -7.113281 16.90625 -6.65625 C 17.945312 -6.195312 19.238281 -5.96875 20.78125 -5.96875 C 23.800781 -5.96875 26.019531 -6.878906 27.4375 -8.703125 C 28.96875 -10.648438 29.734375 -13.578125 29.734375 -17.484375 L 29.734375 -45.09375 Z M 31.28125 -45.09375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(170.836098, 55.95717)">
|
||||
<g>
|
||||
<path d="M 3.671875 -33.90625 C 4.492188 -33.90625 5.257812 -33.84375 5.96875 -33.71875 C 6.675781 -33.59375 7.347656 -33.394531 7.984375 -33.125 C 8.628906 -32.851562 9.222656 -32.507812 9.765625 -32.09375 C 10.304688 -31.675781 10.765625 -31.179688 11.140625 -30.609375 C 11.835938 -31.304688 12.644531 -31.925781 13.5625 -32.46875 C 13.8125 -32.632812 14.0625 -32.789062 14.3125 -32.9375 C 14.5625 -33.082031 14.828125 -33.21875 15.109375 -33.34375 C 15.398438 -33.46875 15.691406 -33.582031 15.984375 -33.6875 C 16.273438 -33.789062 16.566406 -33.90625 16.859375 -34.03125 C 17.523438 -34.238281 18.207031 -34.390625 18.90625 -34.484375 C 19.613281 -34.585938 20.34375 -34.640625 21.09375 -34.640625 C 23.164062 -34.640625 25.164062 -34.222656 27.09375 -33.390625 C 29.019531 -32.566406 30.707031 -31.367188 32.15625 -29.796875 C 33.613281 -28.222656 34.753906 -26.316406 35.578125 -24.078125 C 35.867188 -23.328125 36.109375 -22.535156 36.296875 -21.703125 C 36.484375 -20.878906 36.628906 -20.03125 36.734375 -19.15625 C 36.835938 -18.289062 36.890625 -17.398438 36.890625 -16.484375 C 36.890625 -13.742188 36.472656 -11.210938 35.640625 -8.890625 C 35.222656 -7.773438 34.734375 -6.71875 34.171875 -5.71875 C 33.617188 -4.726562 32.96875 -3.816406 32.21875 -2.984375 C 31.96875 -2.648438 31.6875 -2.335938 31.375 -2.046875 C 31.070312 -1.765625 30.753906 -1.476562 30.421875 -1.1875 C 30.085938 -0.894531 29.753906 -0.625 29.421875 -0.375 C 29.085938 -0.125 28.734375 0.101562 28.359375 0.3125 C 27.992188 0.519531 27.625 0.707031 27.25 0.875 C 26.289062 1.375 25.300781 1.734375 24.28125 1.953125 C 23.269531 2.179688 22.25 2.296875 21.21875 2.296875 C 19.019531 2.296875 17.003906 1.863281 15.171875 1 C 14.515625 0.707031 13.894531 0.375 13.3125 0 L 13.3125 15.234375 L 4.609375 15.234375 L 4.609375 -24.078125 C 4.609375 -25.023438 4.441406 -25.644531 4.109375 -25.9375 C 3.734375 -26.269531 3.191406 -26.4375 2.484375 -26.4375 L 0.4375 -26.4375 L 1 -28.421875 L 2.171875 -32.78125 L 2.484375 -33.90625 Z M 28.046875 -16.296875 C 28.046875 -17.171875 27.984375 -17.976562 27.859375 -18.71875 C 27.742188 -19.46875 27.570312 -20.160156 27.34375 -20.796875 C 27.113281 -21.441406 26.851562 -22.035156 26.5625 -22.578125 C 26.269531 -23.117188 25.9375 -23.617188 25.5625 -24.078125 C 24.820312 -24.941406 23.960938 -25.59375 22.984375 -26.03125 C 22.003906 -26.46875 20.957031 -26.6875 19.84375 -26.6875 C 19.09375 -26.6875 18.382812 -26.59375 17.71875 -26.40625 C 17.0625 -26.21875 16.441406 -25.9375 15.859375 -25.5625 C 14.785156 -24.894531 13.9375 -24.191406 13.3125 -23.453125 L 13.3125 -8.703125 C 14.1875 -7.921875 15.140625 -7.257812 16.171875 -6.71875 C 17.335938 -6.09375 18.644531 -5.78125 20.09375 -5.78125 C 21.25 -5.78125 22.304688 -6.007812 23.265625 -6.46875 C 24.210938 -6.96875 25.039062 -7.648438 25.75 -8.515625 C 26.125 -8.972656 26.445312 -9.472656 26.71875 -10.015625 C 26.988281 -10.554688 27.226562 -11.15625 27.4375 -11.8125 C 27.644531 -12.476562 27.796875 -13.179688 27.890625 -13.921875 C 27.992188 -14.671875 28.046875 -15.460938 28.046875 -16.296875 Z M 28.046875 -16.296875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(208.963656, 55.95717)">
|
||||
<g>
|
||||
<path d="M 7.53125 -42.921875 L 13.375 -42.921875 L 13.375 -32.71875 L 22.328125 -32.71875 L 22.328125 -24.75 L 13.375 -24.75 L 13.375 -10.703125 C 13.375 -9.660156 13.476562 -8.8125 13.6875 -8.15625 C 13.894531 -7.53125 14.144531 -7.050781 14.4375 -6.71875 C 14.71875 -6.382812 15.066406 -6.15625 15.484375 -6.03125 C 15.734375 -5.945312 15.984375 -5.882812 16.234375 -5.84375 C 16.484375 -5.800781 16.734375 -5.78125 16.984375 -5.78125 C 17.847656 -5.78125 18.632812 -5.90625 19.34375 -6.15625 C 20.21875 -6.488281 21.023438 -6.863281 21.765625 -7.28125 L 23.390625 -8.15625 L 24.015625 -6.40625 L 25.5625 -1.921875 L 25.9375 -0.75 L 24.875 -0.125 C 23.96875 0.457031 22.75 1 21.21875 1.5 C 20.425781 1.78125 19.613281 1.984375 18.78125 2.109375 C 17.957031 2.234375 17.128906 2.296875 16.296875 2.296875 C 12.734375 2.296875 9.894531 1.113281 7.78125 -1.25 C 6.695312 -2.445312 5.90625 -3.875 5.40625 -5.53125 C 4.914062 -7.195312 4.671875 -9.066406 4.671875 -11.140625 L 4.671875 -24.75 L -0.9375 -24.75 L -0.9375 -32.71875 L 4.96875 -32.71875 L 5.96875 -41.546875 L 6.15625 -42.921875 Z M 7.53125 -42.921875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(233.656391, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.578125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 12.125 -33.90625 L 12.125 1.5625 Z M 7.78125 -36.328125 C 6.28125 -36.328125 4.988281 -36.863281 3.90625 -37.9375 C 2.832031 -39.019531 2.296875 -40.304688 2.296875 -41.796875 C 2.296875 -43.410156 2.800781 -44.726562 3.8125 -45.75 C 4.832031 -46.769531 6.15625 -47.28125 7.78125 -47.28125 C 8.519531 -47.28125 9.222656 -47.144531 9.890625 -46.875 C 10.554688 -46.601562 11.144531 -46.207031 11.65625 -45.6875 C 12.175781 -45.164062 12.570312 -44.570312 12.84375 -43.90625 C 13.113281 -43.25 13.25 -42.546875 13.25 -41.796875 C 13.25 -40.179688 12.738281 -38.863281 11.71875 -37.84375 C 10.707031 -36.832031 9.394531 -36.328125 7.78125 -36.328125 Z M 7.78125 -36.328125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(249.205965, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.109375 C 12.269531 -31.390625 12.59375 -31.65625 12.90625 -31.90625 C 13.21875 -32.15625 13.5625 -32.382812 13.9375 -32.59375 C 14.4375 -32.925781 14.953125 -33.226562 15.484375 -33.5 C 16.023438 -33.769531 16.59375 -33.984375 17.1875 -34.140625 C 17.789062 -34.304688 18.394531 -34.429688 19 -34.515625 C 19.601562 -34.597656 20.238281 -34.640625 20.90625 -34.640625 C 23.800781 -34.640625 26.203125 -33.914062 28.109375 -32.46875 C 29.273438 -31.59375 30.234375 -30.515625 30.984375 -29.234375 C 32.015625 -30.398438 33.113281 -31.4375 34.28125 -32.34375 C 34.6875 -32.632812 35.117188 -32.90625 35.578125 -33.15625 C 36.035156 -33.40625 36.507812 -33.625 37 -33.8125 C 37.5 -34 38.007812 -34.148438 38.53125 -34.265625 C 39.050781 -34.390625 39.578125 -34.484375 40.109375 -34.546875 C 40.648438 -34.609375 41.210938 -34.640625 41.796875 -34.640625 C 42.046875 -34.640625 42.285156 -34.640625 42.515625 -34.640625 C 42.742188 -34.640625 42.972656 -34.628906 43.203125 -34.609375 C 43.429688 -34.585938 43.65625 -34.554688 43.875 -34.515625 C 44.101562 -34.472656 44.320312 -34.429688 44.53125 -34.390625 C 44.738281 -34.359375 44.945312 -34.320312 45.15625 -34.28125 C 45.363281 -34.238281 45.570312 -34.1875 45.78125 -34.125 C 45.988281 -34.0625 46.195312 -33.988281 46.40625 -33.90625 C 46.613281 -33.820312 46.820312 -33.738281 47.03125 -33.65625 C 47.394531 -33.488281 47.753906 -33.300781 48.109375 -33.09375 C 48.460938 -32.882812 48.796875 -32.65625 49.109375 -32.40625 C 49.421875 -32.15625 49.722656 -31.894531 50.015625 -31.625 C 50.304688 -31.351562 50.570312 -31.054688 50.8125 -30.734375 C 51.851562 -29.523438 52.601562 -28.09375 53.0625 -26.4375 C 53.5625 -24.820312 53.8125 -23.039062 53.8125 -21.09375 L 53.8125 1.5625 L 45.09375 1.5625 L 45.09375 -20.65625 C 45.09375 -22.5625 44.679688 -24.070312 43.859375 -25.1875 C 43.109375 -26.144531 42.09375 -26.625 40.8125 -26.625 C 40.0625 -26.625 39.351562 -26.476562 38.6875 -26.1875 C 37.945312 -25.851562 37.242188 -25.414062 36.578125 -24.875 C 35.867188 -24.300781 35.179688 -23.640625 34.515625 -22.890625 C 34.273438 -22.554688 34.007812 -22.203125 33.71875 -21.828125 C 33.425781 -21.460938 33.175781 -21.113281 32.96875 -20.78125 L 32.96875 1.5625 L 24.265625 1.5625 L 24.265625 -20.65625 C 24.265625 -22.5625 23.847656 -24.070312 23.015625 -25.1875 C 22.265625 -26.144531 21.25 -26.625 19.96875 -26.625 C 19.257812 -26.625 18.554688 -26.476562 17.859375 -26.1875 C 17.523438 -26.0625 17.171875 -25.894531 16.796875 -25.6875 C 16.421875 -25.476562 16.066406 -25.226562 15.734375 -24.9375 C 15.609375 -24.851562 15.484375 -24.757812 15.359375 -24.65625 C 15.234375 -24.5625 15.117188 -24.460938 15.015625 -24.359375 C 14.910156 -24.253906 14.796875 -24.148438 14.671875 -24.046875 C 14.554688 -23.941406 14.445312 -23.835938 14.34375 -23.734375 C 14.238281 -23.628906 14.132812 -23.515625 14.03125 -23.390625 C 13.925781 -23.265625 13.8125 -23.140625 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.109375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(306.117405, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden">
|
||||
<a aria-label="Home" href="/">
|
||||
<svg class="h-6" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="500" zoomAndPan="magnify" viewBox="0 0 375 74.999997" height="100"
|
||||
preserveAspectRatio="xMidYMid meet" version="1.0">
|
||||
<defs>
|
||||
@@ -101,9 +184,9 @@
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="fixed inset-x-0 top-0 z-50 flex h-14 border-b-2 items-center justify-between gap-12 px-4 transition sm:px-6 lg:left-72 lg:z-30 lg:px-8 xl:left-80 backdrop-blur-sm lg:left-72 xl:left-80 bg-white/[var(--bg-opacity-light)] /[var(--bg-opacity-dark)]"
|
||||
<div class="fixed inset-x-0 top-0 z-50 flex h-14 border-b border-slate-200 items-center justify-between gap-12 px-4 transition sm:px-6 lg:left-72 lg:z-30 lg:px-8 xl:left-80 backdrop-blur-sm lg:left-72 xl:left-80 bg-white/95 supports-[backdrop-filter]:bg-white/80"
|
||||
style="--bg-opacity-light:0.5; ">
|
||||
<div class="absolute inset-x-0 top-full h-px transition bg-zinc-900/7.5 "></div>
|
||||
<div class="absolute inset-x-0 top-full h-px transition bg-slate-900/5"></div>
|
||||
<div class="hidden lg:block lg:max-w-md lg:flex-auto">
|
||||
<!-- <button type="button"
|
||||
class="hidden h-8 w-full items-center gap-2 rounded-full bg-white pl-2 pr-3 text-sm text-zinc-500 ring-1 ring-zinc-900/10 transition hover:ring-zinc-900/20 lg:flex focus:[&:not(:focus-visible)]:outline-none">
|
||||
@@ -116,44 +199,117 @@
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="flex items-center gap-5 lg:hidden">
|
||||
<button type="button"
|
||||
class="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 "
|
||||
<button type="button" id="mobile-menu-toggle"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-lg transition hover:bg-slate-100"
|
||||
aria-label="Toggle navigation">
|
||||
<svg viewBox="0 0 10 9" fill="none" stroke-linecap="round" aria-hidden="true"
|
||||
class="w-2.5 stroke-zinc-900 ">
|
||||
<path d="M.5 1h9M.5 8h9M.5 4.5h9"></path>
|
||||
<svg id="menu-open-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"
|
||||
class="w-5 h-5 stroke-slate-700">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
<svg id="menu-close-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"
|
||||
class="w-5 h-5 stroke-slate-700 hidden">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<a aria-label="Home" href="/">
|
||||
<svg viewBox="0 0 99 24" aria-hidden="true" class="h-6">
|
||||
<path class="fill-emerald-400"
|
||||
d="M16 8a5 5 0 0 0-5-5H5a5 5 0 0 0-5 5v13.927a1 1 0 0 0 1.623.782l3.684-2.93a4 4 0 0 1 2.49-.87H11a5 5 0 0 0 5-5V8Z">
|
||||
</path>
|
||||
<path class="fill-zinc-900 "
|
||||
d="M26.538 18h2.654v-3.999h2.576c2.672 0 4.456-1.723 4.456-4.333V9.65c0-2.61-1.784-4.333-4.456-4.333h-5.23V18Zm4.58-10.582c1.52 0 2.416.8 2.416 2.241v.018c0 1.441-.896 2.25-2.417 2.25h-1.925V7.418h1.925ZM38.051 18h2.566v-5.414c0-1.371.923-2.206 2.382-2.206.396 0 .791.061 1.178.15V8.287a3.843 3.843 0 0 0-.958-.123c-1.257 0-2.136.615-2.443 1.661h-.159V8.323h-2.566V18Zm11.55.202c2.979 0 4.772-1.88 4.772-5.036v-.018c0-3.128-1.82-5.036-4.773-5.036-2.953 0-4.772 1.916-4.772 5.036v.018c0 3.146 1.793 5.036 4.772 5.036Zm0-2.013c-1.372 0-2.145-1.116-2.145-3.023v-.018c0-1.89.782-3.023 2.144-3.023 1.354 0 2.145 1.134 2.145 3.023v.018c0 1.907-.782 3.023-2.145 3.023Zm10.52 1.846c.492 0 .967-.053 1.283-.114v-1.907a6.057 6.057 0 0 1-.755.044c-.87 0-1.24-.387-1.24-1.257v-4.544h1.995V8.323H59.41V6.012h-2.592v2.311h-1.495v1.934h1.495v5.133c0 1.88.949 2.645 3.304 2.645Zm7.287.167c2.98 0 4.772-1.88 4.772-5.036v-.018c0-3.128-1.82-5.036-4.772-5.036-2.954 0-4.773 1.916-4.773 5.036v.018c0 3.146 1.793 5.036 4.773 5.036Zm0-2.013c-1.372 0-2.145-1.116-2.145-3.023v-.018c0-1.89.782-3.023 2.145-3.023 1.353 0 2.144 1.134 2.144 3.023v.018c0 1.907-.782 3.023-2.144 3.023Zm10.767 2.013c2.522 0 4.034-1.353 4.297-3.463l.01-.053h-2.374l-.017.036c-.229.966-.853 1.467-1.908 1.467-1.37 0-2.135-1.08-2.135-3.04v-.018c0-1.934.755-3.006 2.135-3.006 1.099 0 1.74.615 1.908 1.556l.008.017h2.391v-.026c-.228-2.162-1.749-3.56-4.315-3.56-3.033 0-4.738 1.837-4.738 5.019v.017c0 3.217 1.714 5.054 4.738 5.054Zm10.257 0c2.98 0 4.772-1.88 4.772-5.036v-.018c0-3.128-1.82-5.036-4.772-5.036-2.953 0-4.773 1.916-4.773 5.036v.018c0 3.146 1.793 5.036 4.773 5.036Zm0-2.013c-1.371 0-2.145-1.116-2.145-3.023v-.018c0-1.89.782-3.023 2.145-3.023 1.353 0 2.144 1.134 2.144 3.023v.018c0 1.907-.782 3.023-2.144 3.023ZM95.025 18h2.566V4.623h-2.566V18Z">
|
||||
</path>
|
||||
<svg class="h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 75" preserveAspectRatio="xMidYMid meet">
|
||||
<defs>
|
||||
<g></g>
|
||||
<clipPath id="mobile-header-1d83d73318">
|
||||
<path d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
<clipPath id="mobile-header-32c1ad7ad2">
|
||||
<path d="M 366.480469 51.03125 L 352.816406 51.03125 C 352.253906 51.03125 351.792969 50.574219 351.792969 50.007812 L 351.792969 36.347656 C 351.792969 35.785156 352.253906 35.324219 352.816406 35.324219 L 366.480469 35.324219 C 367.042969 35.324219 367.5 35.785156 367.5 36.347656 L 367.5 50.007812 C 367.5 50.574219 367.042969 51.03125 366.480469 51.03125 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#mobile-header-1d83d73318)">
|
||||
<g clip-path="url(#mobile-header-32c1ad7ad2)">
|
||||
<path fill="#7ed957" d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " fill-opacity="1" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(11.173064, 55.95717)">
|
||||
<g>
|
||||
<path d="M 1.5625 -21.828125 C 1.5625 -25.191406 2.054688 -28.320312 3.046875 -31.21875 C 4.046875 -34.164062 5.5 -36.71875 7.40625 -38.875 C 9.3125 -41.070312 11.632812 -42.773438 14.375 -43.984375 C 15.738281 -44.597656 17.195312 -45.0625 18.75 -45.375 C 20.3125 -45.6875 21.941406 -45.84375 23.640625 -45.84375 C 25.335938 -45.84375 26.960938 -45.6875 28.515625 -45.375 C 30.078125 -45.0625 31.539062 -44.578125 32.90625 -43.921875 C 34.269531 -43.296875 35.53125 -42.546875 36.6875 -41.671875 C 37.851562 -40.804688 38.914062 -39.8125 39.875 -38.6875 C 41.78125 -36.53125 43.25 -34.003906 44.28125 -31.109375 C 45.28125 -28.203125 45.78125 -25.109375 45.78125 -21.828125 C 45.78125 -18.554688 45.28125 -15.46875 44.28125 -12.5625 C 43.957031 -11.613281 43.578125 -10.679688 43.140625 -9.765625 C 42.703125 -8.847656 42.210938 -7.984375 41.671875 -7.171875 C 41.140625 -6.367188 40.5625 -5.597656 39.9375 -4.859375 C 38.976562 -3.734375 37.90625 -2.734375 36.71875 -1.859375 C 35.539062 -0.992188 34.289062 -0.25 32.96875 0.375 C 30.226562 1.65625 27.113281 2.296875 23.625 2.296875 C 20.144531 2.296875 17.039062 1.65625 14.3125 0.375 C 13.644531 0.0390625 13 -0.3125 12.375 -0.6875 C 11.757812 -1.0625 11.160156 -1.460938 10.578125 -1.890625 C 9.992188 -2.328125 9.429688 -2.796875 8.890625 -3.296875 C 8.359375 -3.796875 7.863281 -4.316406 7.40625 -4.859375 C 5.5 -7.054688 4.046875 -9.625 3.046875 -12.5625 C 2.054688 -15.46875 1.5625 -18.554688 1.5625 -21.828125 Z M 10.703125 -21.828125 C 10.703125 -20.796875 10.773438 -19.769531 10.921875 -18.75 C 11.066406 -17.738281 11.28125 -16.734375 11.5625 -15.734375 C 11.894531 -14.785156 12.269531 -13.882812 12.6875 -13.03125 C 13.101562 -12.175781 13.601562 -11.378906 14.1875 -10.640625 C 14.351562 -10.390625 14.535156 -10.160156 14.734375 -9.953125 C 14.941406 -9.742188 15.148438 -9.535156 15.359375 -9.328125 C 15.566406 -9.117188 15.785156 -8.910156 16.015625 -8.703125 C 16.242188 -8.492188 16.484375 -8.304688 16.734375 -8.140625 C 16.984375 -7.984375 17.234375 -7.820312 17.484375 -7.65625 C 17.734375 -7.488281 17.976562 -7.320312 18.21875 -7.15625 C 19.800781 -6.320312 21.609375 -5.90625 23.640625 -5.90625 C 24.378906 -5.90625 25.082031 -5.945312 25.75 -6.03125 C 26.414062 -6.113281 27.035156 -6.25 27.609375 -6.4375 C 28.191406 -6.625 28.734375 -6.84375 29.234375 -7.09375 C 30.847656 -7.875 32.195312 -8.953125 33.28125 -10.328125 C 33.65625 -10.785156 33.984375 -11.269531 34.265625 -11.78125 C 34.554688 -12.300781 34.828125 -12.863281 35.078125 -13.46875 C 35.328125 -14.070312 35.554688 -14.679688 35.765625 -15.296875 C 36.347656 -17.328125 36.640625 -19.503906 36.640625 -21.828125 C 36.640625 -23.898438 36.328125 -25.894531 35.703125 -27.8125 C 35.117188 -29.71875 34.25 -31.414062 33.09375 -32.90625 C 31.96875 -34.351562 30.617188 -35.515625 29.046875 -36.390625 C 27.472656 -37.222656 25.671875 -37.640625 23.640625 -37.640625 C 23.140625 -37.640625 22.640625 -37.617188 22.140625 -37.578125 C 21.640625 -37.535156 21.160156 -37.460938 20.703125 -37.359375 C 20.253906 -37.253906 19.820312 -37.128906 19.40625 -36.984375 C 18.988281 -36.835938 18.59375 -36.660156 18.21875 -36.453125 C 17.394531 -36.078125 16.648438 -35.617188 15.984375 -35.078125 C 15.316406 -34.546875 14.71875 -33.925781 14.1875 -33.21875 C 13.0625 -31.8125 12.1875 -30.148438 11.5625 -28.234375 C 10.988281 -26.242188 10.703125 -24.109375 10.703125 -21.828125 Z M 10.703125 -21.828125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(58.505968, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.046875 C 12.269531 -31.367188 12.601562 -31.65625 12.9375 -31.90625 C 13.269531 -32.15625 13.625 -32.382812 14 -32.59375 C 16.070312 -33.957031 18.5 -34.640625 21.28125 -34.640625 C 23.3125 -34.640625 25.113281 -34.316406 26.6875 -33.671875 C 28.257812 -33.035156 29.585938 -32.078125 30.671875 -30.796875 C 32.703125 -28.304688 33.71875 -25.070312 33.71875 -21.09375 L 33.71875 1.5625 L 25 1.5625 L 25 -20.46875 C 25 -21.164062 24.945312 -21.804688 24.84375 -22.390625 C 24.738281 -22.972656 24.59375 -23.488281 24.40625 -23.9375 C 24.226562 -24.394531 23.992188 -24.789062 23.703125 -25.125 C 23.453125 -25.457031 23.171875 -25.738281 22.859375 -25.96875 C 22.546875 -26.195312 22.179688 -26.363281 21.765625 -26.46875 C 21.359375 -26.570312 20.882812 -26.625 20.34375 -26.625 C 19.59375 -26.625 18.84375 -26.476562 18.09375 -26.1875 C 17.3125 -25.894531 16.546875 -25.476562 15.796875 -24.9375 C 15.054688 -24.363281 14.351562 -23.722656 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.046875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(95.327364, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(129.163241, 55.95717)">
|
||||
<g>
|
||||
<path d="M 31.28125 -45.09375 L 38.5625 -45.09375 L 38.5625 -16.796875 C 38.5625 -13.890625 38.148438 -11.253906 37.328125 -8.890625 C 36.492188 -6.523438 35.289062 -4.492188 33.71875 -2.796875 C 33.382812 -2.460938 33.039062 -2.148438 32.6875 -1.859375 C 32.332031 -1.578125 31.976562 -1.289062 31.625 -1 C 31.28125 -0.707031 30.910156 -0.445312 30.515625 -0.21875 C 30.117188 0.0078125 29.710938 0.226562 29.296875 0.4375 C 28.878906 0.644531 28.441406 0.832031 27.984375 1 C 25.828125 1.863281 23.425781 2.296875 20.78125 2.296875 C 15.257812 2.296875 10.925781 0.660156 7.78125 -2.609375 C 4.664062 -5.890625 3.109375 -10.535156 3.109375 -16.546875 L 3.109375 -45.09375 L 12.125 -45.09375 L 12.125 -17.484375 C 12.125 -13.617188 12.875 -10.691406 14.375 -8.703125 C 15.03125 -7.796875 15.875 -7.113281 16.90625 -6.65625 C 17.945312 -6.195312 19.238281 -5.96875 20.78125 -5.96875 C 23.800781 -5.96875 26.019531 -6.878906 27.4375 -8.703125 C 28.96875 -10.648438 29.734375 -13.578125 29.734375 -17.484375 L 29.734375 -45.09375 Z M 31.28125 -45.09375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(170.836098, 55.95717)">
|
||||
<g>
|
||||
<path d="M 3.671875 -33.90625 C 4.492188 -33.90625 5.257812 -33.84375 5.96875 -33.71875 C 6.675781 -33.59375 7.347656 -33.394531 7.984375 -33.125 C 8.628906 -32.851562 9.222656 -32.507812 9.765625 -32.09375 C 10.304688 -31.675781 10.765625 -31.179688 11.140625 -30.609375 C 11.835938 -31.304688 12.644531 -31.925781 13.5625 -32.46875 C 13.8125 -32.632812 14.0625 -32.789062 14.3125 -32.9375 C 14.5625 -33.082031 14.828125 -33.21875 15.109375 -33.34375 C 15.398438 -33.46875 15.691406 -33.582031 15.984375 -33.6875 C 16.273438 -33.789062 16.566406 -33.90625 16.859375 -34.03125 C 17.523438 -34.238281 18.207031 -34.390625 18.90625 -34.484375 C 19.613281 -34.585938 20.34375 -34.640625 21.09375 -34.640625 C 23.164062 -34.640625 25.164062 -34.222656 27.09375 -33.390625 C 29.019531 -32.566406 30.707031 -31.367188 32.15625 -29.796875 C 33.613281 -28.222656 34.753906 -26.316406 35.578125 -24.078125 C 35.867188 -23.328125 36.109375 -22.535156 36.296875 -21.703125 C 36.484375 -20.878906 36.628906 -20.03125 36.734375 -19.15625 C 36.835938 -18.289062 36.890625 -17.398438 36.890625 -16.484375 C 36.890625 -13.742188 36.472656 -11.210938 35.640625 -8.890625 C 35.222656 -7.773438 34.734375 -6.71875 34.171875 -5.71875 C 33.617188 -4.726562 32.96875 -3.816406 32.21875 -2.984375 C 31.96875 -2.648438 31.6875 -2.335938 31.375 -2.046875 C 31.070312 -1.765625 30.753906 -1.476562 30.421875 -1.1875 C 30.085938 -0.894531 29.753906 -0.625 29.421875 -0.375 C 29.085938 -0.125 28.734375 0.101562 28.359375 0.3125 C 27.992188 0.519531 27.625 0.707031 27.25 0.875 C 26.289062 1.375 25.300781 1.734375 24.28125 1.953125 C 23.269531 2.179688 22.25 2.296875 21.21875 2.296875 C 19.019531 2.296875 17.003906 1.863281 15.171875 1 C 14.515625 0.707031 13.894531 0.375 13.3125 0 L 13.3125 15.234375 L 4.609375 15.234375 L 4.609375 -24.078125 C 4.609375 -25.023438 4.441406 -25.644531 4.109375 -25.9375 C 3.734375 -26.269531 3.191406 -26.4375 2.484375 -26.4375 L 0.4375 -26.4375 L 1 -28.421875 L 2.171875 -32.78125 L 2.484375 -33.90625 Z M 28.046875 -16.296875 C 28.046875 -17.171875 27.984375 -17.976562 27.859375 -18.71875 C 27.742188 -19.46875 27.570312 -20.160156 27.34375 -20.796875 C 27.113281 -21.441406 26.851562 -22.035156 26.5625 -22.578125 C 26.269531 -23.117188 25.9375 -23.617188 25.5625 -24.078125 C 24.820312 -24.941406 23.960938 -25.59375 22.984375 -26.03125 C 22.003906 -26.46875 20.957031 -26.6875 19.84375 -26.6875 C 19.09375 -26.6875 18.382812 -26.59375 17.71875 -26.40625 C 17.0625 -26.21875 16.441406 -25.9375 15.859375 -25.5625 C 14.785156 -24.894531 13.9375 -24.191406 13.3125 -23.453125 L 13.3125 -8.703125 C 14.1875 -7.921875 15.140625 -7.257812 16.171875 -6.71875 C 17.335938 -6.09375 18.644531 -5.78125 20.09375 -5.78125 C 21.25 -5.78125 22.304688 -6.007812 23.265625 -6.46875 C 24.210938 -6.96875 25.039062 -7.648438 25.75 -8.515625 C 26.125 -8.972656 26.445312 -9.472656 26.71875 -10.015625 C 26.988281 -10.554688 27.226562 -11.15625 27.4375 -11.8125 C 27.644531 -12.476562 27.796875 -13.179688 27.890625 -13.921875 C 27.992188 -14.671875 28.046875 -15.460938 28.046875 -16.296875 Z M 28.046875 -16.296875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(208.963656, 55.95717)">
|
||||
<g>
|
||||
<path d="M 7.53125 -42.921875 L 13.375 -42.921875 L 13.375 -32.71875 L 22.328125 -32.71875 L 22.328125 -24.75 L 13.375 -24.75 L 13.375 -10.703125 C 13.375 -9.660156 13.476562 -8.8125 13.6875 -8.15625 C 13.894531 -7.53125 14.144531 -7.050781 14.4375 -6.71875 C 14.71875 -6.382812 15.066406 -6.15625 15.484375 -6.03125 C 15.734375 -5.945312 15.984375 -5.882812 16.234375 -5.84375 C 16.484375 -5.800781 16.734375 -5.78125 16.984375 -5.78125 C 17.847656 -5.78125 18.632812 -5.90625 19.34375 -6.15625 C 20.21875 -6.488281 21.023438 -6.863281 21.765625 -7.28125 L 23.390625 -8.15625 L 24.015625 -6.40625 L 25.5625 -1.921875 L 25.9375 -0.75 L 24.875 -0.125 C 23.96875 0.457031 22.75 1 21.21875 1.5 C 20.425781 1.78125 19.613281 1.984375 18.78125 2.109375 C 17.957031 2.234375 17.128906 2.296875 16.296875 2.296875 C 12.734375 2.296875 9.894531 1.113281 7.78125 -1.25 C 6.695312 -2.445312 5.90625 -3.875 5.40625 -5.53125 C 4.914062 -7.195312 4.671875 -9.066406 4.671875 -11.140625 L 4.671875 -24.75 L -0.9375 -24.75 L -0.9375 -32.71875 L 4.96875 -32.71875 L 5.96875 -41.546875 L 6.15625 -42.921875 Z M 7.53125 -42.921875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(233.656391, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.578125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 12.125 -33.90625 L 12.125 1.5625 Z M 7.78125 -36.328125 C 6.28125 -36.328125 4.988281 -36.863281 3.90625 -37.9375 C 2.832031 -39.019531 2.296875 -40.304688 2.296875 -41.796875 C 2.296875 -43.410156 2.800781 -44.726562 3.8125 -45.75 C 4.832031 -46.769531 6.15625 -47.28125 7.78125 -47.28125 C 8.519531 -47.28125 9.222656 -47.144531 9.890625 -46.875 C 10.554688 -46.601562 11.144531 -46.207031 11.65625 -45.6875 C 12.175781 -45.164062 12.570312 -44.570312 12.84375 -43.90625 C 13.113281 -43.25 13.25 -42.546875 13.25 -41.796875 C 13.25 -40.179688 12.738281 -38.863281 11.71875 -37.84375 C 10.707031 -36.832031 9.394531 -36.328125 7.78125 -36.328125 Z M 7.78125 -36.328125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(249.205965, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.109375 C 12.269531 -31.390625 12.59375 -31.65625 12.90625 -31.90625 C 13.21875 -32.15625 13.5625 -32.382812 13.9375 -32.59375 C 14.4375 -32.925781 14.953125 -33.226562 15.484375 -33.5 C 16.023438 -33.769531 16.59375 -33.984375 17.1875 -34.140625 C 17.789062 -34.304688 18.394531 -34.429688 19 -34.515625 C 19.601562 -34.597656 20.238281 -34.640625 20.90625 -34.640625 C 23.800781 -34.640625 26.203125 -33.914062 28.109375 -32.46875 C 29.273438 -31.59375 30.234375 -30.515625 30.984375 -29.234375 C 32.015625 -30.398438 33.113281 -31.4375 34.28125 -32.34375 C 34.6875 -32.632812 35.117188 -32.90625 35.578125 -33.15625 C 36.035156 -33.40625 36.507812 -33.625 37 -33.8125 C 37.5 -34 38.007812 -34.148438 38.53125 -34.265625 C 39.050781 -34.390625 39.578125 -34.484375 40.109375 -34.546875 C 40.648438 -34.609375 41.210938 -34.640625 41.796875 -34.640625 C 42.046875 -34.640625 42.285156 -34.640625 42.515625 -34.640625 C 42.742188 -34.640625 42.972656 -34.628906 43.203125 -34.609375 C 43.429688 -34.585938 43.65625 -34.554688 43.875 -34.515625 C 44.101562 -34.472656 44.320312 -34.429688 44.53125 -34.390625 C 44.738281 -34.359375 44.945312 -34.320312 45.15625 -34.28125 C 45.363281 -34.238281 45.570312 -34.1875 45.78125 -34.125 C 45.988281 -34.0625 46.195312 -33.988281 46.40625 -33.90625 C 46.613281 -33.820312 46.820312 -33.738281 47.03125 -33.65625 C 47.394531 -33.488281 47.753906 -33.300781 48.109375 -33.09375 C 48.460938 -32.882812 48.796875 -32.65625 49.109375 -32.40625 C 49.421875 -32.15625 49.722656 -31.894531 50.015625 -31.625 C 50.304688 -31.351562 50.570312 -31.054688 50.8125 -30.734375 C 51.851562 -29.523438 52.601562 -28.09375 53.0625 -26.4375 C 53.5625 -24.820312 53.8125 -23.039062 53.8125 -21.09375 L 53.8125 1.5625 L 45.09375 1.5625 L 45.09375 -20.65625 C 45.09375 -22.5625 44.679688 -24.070312 43.859375 -25.1875 C 43.109375 -26.144531 42.09375 -26.625 40.8125 -26.625 C 40.0625 -26.625 39.351562 -26.476562 38.6875 -26.1875 C 37.945312 -25.851562 37.242188 -25.414062 36.578125 -24.875 C 35.867188 -24.300781 35.179688 -23.640625 34.515625 -22.890625 C 34.273438 -22.554688 34.007812 -22.203125 33.71875 -21.828125 C 33.425781 -21.460938 33.175781 -21.113281 32.96875 -20.78125 L 32.96875 1.5625 L 24.265625 1.5625 L 24.265625 -20.65625 C 24.265625 -22.5625 23.847656 -24.070312 23.015625 -25.1875 C 22.265625 -26.144531 21.25 -26.625 19.96875 -26.625 C 19.257812 -26.625 18.554688 -26.476562 17.859375 -26.1875 C 17.523438 -26.0625 17.171875 -25.894531 16.796875 -25.6875 C 16.421875 -25.476562 16.066406 -25.226562 15.734375 -24.9375 C 15.609375 -24.851562 15.484375 -24.757812 15.359375 -24.65625 C 15.234375 -24.5625 15.117188 -24.460938 15.015625 -24.359375 C 14.910156 -24.253906 14.796875 -24.148438 14.671875 -24.046875 C 14.554688 -23.941406 14.445312 -23.835938 14.34375 -23.734375 C 14.238281 -23.628906 14.132812 -23.515625 14.03125 -23.390625 C 13.925781 -23.265625 13.8125 -23.140625 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.109375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(306.117405, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-5">
|
||||
<nav class="hidden md:block">
|
||||
<ul role="list" class="flex items-center gap-8">
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
<ul role="list" class="flex items-center gap-6">
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
href="/">Home</a></li>
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
target="_blank"
|
||||
href="https://join.slack.com/t/oneuptimesupport/shared_invite/zt-2pz5p1uhe-Fpmc7bv5ZE5xRMe7qJnwmA">Chat
|
||||
with us on Slack</a></li>
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="https://join.slack.com/t/oneuptimesupport/shared_invite/zt-2pz5p1uhe-Fpmc7bv5ZE5xRMe7qJnwmA">Slack</a></li>
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
href="/support">Support</a></li>
|
||||
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="/reference/openapi" type="_blank">OpenAPI Spec</a></li>
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
href="/reference/openapi" type="_blank">OpenAPI</a></li>
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
href="https://github.com/oneuptime/oneuptime">GitHub</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="hidden md:block md:h-5 md:w-px md:bg-zinc-900/10 md:"></div>
|
||||
<div class="hidden md:block md:h-5 md:w-px md:bg-slate-200"></div>
|
||||
<div class="flex gap-4">
|
||||
<div class="contents lg:hidden">
|
||||
<button type="button"
|
||||
@@ -184,87 +340,287 @@
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="hidden min-[416px]:contents"><a
|
||||
class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 "
|
||||
class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-indigo-600 py-1.5 px-4 text-white hover:bg-indigo-700 shadow-sm"
|
||||
href="/dashboard">Sign in</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="hidden lg:mt-10 lg:block">
|
||||
<ul role="list">
|
||||
<nav class="hidden lg:block">
|
||||
<div class="mb-6">
|
||||
<span class="inline-flex items-center rounded-full bg-indigo-50 px-3 py-1 text-xs font-semibold text-indigo-700 ring-1 ring-inset ring-indigo-600/20">
|
||||
API Reference
|
||||
</span>
|
||||
</div>
|
||||
<ul role="list" class="space-y-6">
|
||||
<li class="md:hidden"><a
|
||||
class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 "
|
||||
class="block py-1 text-sm text-slate-600 transition hover:text-slate-900"
|
||||
href="/">API</a></li>
|
||||
<li class="md:hidden"><a
|
||||
class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 "
|
||||
class="block py-1 text-sm text-slate-600 transition hover:text-slate-900"
|
||||
href="/#">Documentation</a></li>
|
||||
<li class="md:hidden"><a
|
||||
class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 "
|
||||
class="block py-1 text-sm text-slate-600 transition hover:text-slate-900"
|
||||
href="/#">Support</a></li>
|
||||
<li class="relative mt-6 md:mt-0">
|
||||
<h6 class="text-sm font-semibold text-zinc-900 ">Guides</h6>
|
||||
<li class="relative md:mt-0">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide">Guides</h6>
|
||||
<div class="relative mt-3 pl-2">
|
||||
<div class="absolute inset-x-0 top-0 bg-zinc-800/2.5 will-change-transform "
|
||||
data-projection-id="32"
|
||||
style="height: 64px; top: 0px; border-radius: 8px; opacity: 1; transform: none; transform-origin: 50% 50% 0px;">
|
||||
</div>
|
||||
<div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 "
|
||||
style="transform: none; transform-origin: 50% 50% 0px;"></div>
|
||||
<div class="absolute left-2 h-6 w-px bg-emerald-500" data-projection-id="33"
|
||||
style="top: 4px; opacity: 1;"></div>
|
||||
<ul role="list" class="border-l border-transparent">
|
||||
<div class="absolute inset-y-0 left-2 w-px bg-slate-200"></div>
|
||||
<ul role="list" class="border-l border-transparent space-y-1">
|
||||
<li class="relative">
|
||||
<a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-900 "
|
||||
href="/reference/introduction" aria-current="page"><span
|
||||
<a class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/introduction"><span
|
||||
class="truncate">Introduction</span></a>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/authentication"><span class="truncate">Authentication</span></a></li>
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/pagination"><span class="truncate">Pagination</span></a></li>
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/permissions"><span class="truncate">Permissions</span></a></li>
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/data-types"><span class="truncate">Data Types</span></a></li>
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/errors"><span class="truncate">Errors</span></a></li>
|
||||
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/openapi"><span class="truncate">OpenAPI Spec</span></a></li>
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="relative mt-6">
|
||||
<h6 class="text-sm font-semibold text-zinc-900 "
|
||||
style="transform: none; transform-origin: 50% 50% 0px;">Resources</h6>
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide">Resources</h6>
|
||||
<div class="relative mt-3 pl-2">
|
||||
<div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 "
|
||||
style="transform: none; transform-origin: 50% 50% 0px;"></div>
|
||||
<ul role="list" class="border-l border-transparent">
|
||||
<div class="absolute inset-y-0 left-2 w-px bg-slate-200"></div>
|
||||
<ul role="list" class="border-l border-transparent space-y-1">
|
||||
<% for(var i=0; i<resources.length; i++) {%>
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/<%= resources[i].path -%>"><span class="truncate">
|
||||
<%= resources[i].name -%>
|
||||
</span></a></li>
|
||||
<% } %>
|
||||
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sticky bottom-0 z-10 mt-6 min-[416px]:hidden"><a
|
||||
class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 :bg-emerald-400 w-full"
|
||||
class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-indigo-600 py-1.5 px-4 text-white hover:bg-indigo-700 shadow-sm w-full"
|
||||
href="/#">Sign in</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
<!-- Mobile Navigation Overlay -->
|
||||
<div id="mobile-menu-overlay" class="fixed inset-0 z-50 bg-slate-900/50 backdrop-blur-sm hidden lg:hidden" aria-hidden="true"></div>
|
||||
|
||||
<!-- Mobile Navigation Panel -->
|
||||
<div id="mobile-menu-panel" class="fixed inset-y-0 left-0 z-50 w-80 max-w-[calc(100%-3rem)] bg-white shadow-2xl transform -translate-x-full transition-transform duration-300 ease-in-out lg:hidden overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<a aria-label="Home" href="/">
|
||||
<svg class="h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 75" preserveAspectRatio="xMidYMid meet">
|
||||
<defs>
|
||||
<g></g>
|
||||
<clipPath id="mobile-menu-1d83d73318">
|
||||
<path d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
<clipPath id="mobile-menu-32c1ad7ad2">
|
||||
<path d="M 366.480469 51.03125 L 352.816406 51.03125 C 352.253906 51.03125 351.792969 50.574219 351.792969 50.007812 L 351.792969 36.347656 C 351.792969 35.785156 352.253906 35.324219 352.816406 35.324219 L 366.480469 35.324219 C 367.042969 35.324219 367.5 35.785156 367.5 36.347656 L 367.5 50.007812 C 367.5 50.574219 367.042969 51.03125 366.480469 51.03125 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#mobile-menu-1d83d73318)">
|
||||
<g clip-path="url(#mobile-menu-32c1ad7ad2)">
|
||||
<path fill="#7ed957" d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " fill-opacity="1" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(11.173064, 55.95717)">
|
||||
<g>
|
||||
<path d="M 1.5625 -21.828125 C 1.5625 -25.191406 2.054688 -28.320312 3.046875 -31.21875 C 4.046875 -34.164062 5.5 -36.71875 7.40625 -38.875 C 9.3125 -41.070312 11.632812 -42.773438 14.375 -43.984375 C 15.738281 -44.597656 17.195312 -45.0625 18.75 -45.375 C 20.3125 -45.6875 21.941406 -45.84375 23.640625 -45.84375 C 25.335938 -45.84375 26.960938 -45.6875 28.515625 -45.375 C 30.078125 -45.0625 31.539062 -44.578125 32.90625 -43.921875 C 34.269531 -43.296875 35.53125 -42.546875 36.6875 -41.671875 C 37.851562 -40.804688 38.914062 -39.8125 39.875 -38.6875 C 41.78125 -36.53125 43.25 -34.003906 44.28125 -31.109375 C 45.28125 -28.203125 45.78125 -25.109375 45.78125 -21.828125 C 45.78125 -18.554688 45.28125 -15.46875 44.28125 -12.5625 C 43.957031 -11.613281 43.578125 -10.679688 43.140625 -9.765625 C 42.703125 -8.847656 42.210938 -7.984375 41.671875 -7.171875 C 41.140625 -6.367188 40.5625 -5.597656 39.9375 -4.859375 C 38.976562 -3.734375 37.90625 -2.734375 36.71875 -1.859375 C 35.539062 -0.992188 34.289062 -0.25 32.96875 0.375 C 30.226562 1.65625 27.113281 2.296875 23.625 2.296875 C 20.144531 2.296875 17.039062 1.65625 14.3125 0.375 C 13.644531 0.0390625 13 -0.3125 12.375 -0.6875 C 11.757812 -1.0625 11.160156 -1.460938 10.578125 -1.890625 C 9.992188 -2.328125 9.429688 -2.796875 8.890625 -3.296875 C 8.359375 -3.796875 7.863281 -4.316406 7.40625 -4.859375 C 5.5 -7.054688 4.046875 -9.625 3.046875 -12.5625 C 2.054688 -15.46875 1.5625 -18.554688 1.5625 -21.828125 Z M 10.703125 -21.828125 C 10.703125 -20.796875 10.773438 -19.769531 10.921875 -18.75 C 11.066406 -17.738281 11.28125 -16.734375 11.5625 -15.734375 C 11.894531 -14.785156 12.269531 -13.882812 12.6875 -13.03125 C 13.101562 -12.175781 13.601562 -11.378906 14.1875 -10.640625 C 14.351562 -10.390625 14.535156 -10.160156 14.734375 -9.953125 C 14.941406 -9.742188 15.148438 -9.535156 15.359375 -9.328125 C 15.566406 -9.117188 15.785156 -8.910156 16.015625 -8.703125 C 16.242188 -8.492188 16.484375 -8.304688 16.734375 -8.140625 C 16.984375 -7.984375 17.234375 -7.820312 17.484375 -7.65625 C 17.734375 -7.488281 17.976562 -7.320312 18.21875 -7.15625 C 19.800781 -6.320312 21.609375 -5.90625 23.640625 -5.90625 C 24.378906 -5.90625 25.082031 -5.945312 25.75 -6.03125 C 26.414062 -6.113281 27.035156 -6.25 27.609375 -6.4375 C 28.191406 -6.625 28.734375 -6.84375 29.234375 -7.09375 C 30.847656 -7.875 32.195312 -8.953125 33.28125 -10.328125 C 33.65625 -10.785156 33.984375 -11.269531 34.265625 -11.78125 C 34.554688 -12.300781 34.828125 -12.863281 35.078125 -13.46875 C 35.328125 -14.070312 35.554688 -14.679688 35.765625 -15.296875 C 36.347656 -17.328125 36.640625 -19.503906 36.640625 -21.828125 C 36.640625 -23.898438 36.328125 -25.894531 35.703125 -27.8125 C 35.117188 -29.71875 34.25 -31.414062 33.09375 -32.90625 C 31.96875 -34.351562 30.617188 -35.515625 29.046875 -36.390625 C 27.472656 -37.222656 25.671875 -37.640625 23.640625 -37.640625 C 23.140625 -37.640625 22.640625 -37.617188 22.140625 -37.578125 C 21.640625 -37.535156 21.160156 -37.460938 20.703125 -37.359375 C 20.253906 -37.253906 19.820312 -37.128906 19.40625 -36.984375 C 18.988281 -36.835938 18.59375 -36.660156 18.21875 -36.453125 C 17.394531 -36.078125 16.648438 -35.617188 15.984375 -35.078125 C 15.316406 -34.546875 14.71875 -33.925781 14.1875 -33.21875 C 13.0625 -31.8125 12.1875 -30.148438 11.5625 -28.234375 C 10.988281 -26.242188 10.703125 -24.109375 10.703125 -21.828125 Z M 10.703125 -21.828125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(58.505968, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.046875 C 12.269531 -31.367188 12.601562 -31.65625 12.9375 -31.90625 C 13.269531 -32.15625 13.625 -32.382812 14 -32.59375 C 16.070312 -33.957031 18.5 -34.640625 21.28125 -34.640625 C 23.3125 -34.640625 25.113281 -34.316406 26.6875 -33.671875 C 28.257812 -33.035156 29.585938 -32.078125 30.671875 -30.796875 C 32.703125 -28.304688 33.71875 -25.070312 33.71875 -21.09375 L 33.71875 1.5625 L 25 1.5625 L 25 -20.46875 C 25 -21.164062 24.945312 -21.804688 24.84375 -22.390625 C 24.738281 -22.972656 24.59375 -23.488281 24.40625 -23.9375 C 24.226562 -24.394531 23.992188 -24.789062 23.703125 -25.125 C 23.453125 -25.457031 23.171875 -25.738281 22.859375 -25.96875 C 22.546875 -26.195312 22.179688 -26.363281 21.765625 -26.46875 C 21.359375 -26.570312 20.882812 -26.625 20.34375 -26.625 C 19.59375 -26.625 18.84375 -26.476562 18.09375 -26.1875 C 17.3125 -25.894531 16.546875 -25.476562 15.796875 -24.9375 C 15.054688 -24.363281 14.351562 -23.722656 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.046875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(95.327364, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(129.163241, 55.95717)">
|
||||
<g>
|
||||
<path d="M 31.28125 -45.09375 L 38.5625 -45.09375 L 38.5625 -16.796875 C 38.5625 -13.890625 38.148438 -11.253906 37.328125 -8.890625 C 36.492188 -6.523438 35.289062 -4.492188 33.71875 -2.796875 C 33.382812 -2.460938 33.039062 -2.148438 32.6875 -1.859375 C 32.332031 -1.578125 31.976562 -1.289062 31.625 -1 C 31.28125 -0.707031 30.910156 -0.445312 30.515625 -0.21875 C 30.117188 0.0078125 29.710938 0.226562 29.296875 0.4375 C 28.878906 0.644531 28.441406 0.832031 27.984375 1 C 25.828125 1.863281 23.425781 2.296875 20.78125 2.296875 C 15.257812 2.296875 10.925781 0.660156 7.78125 -2.609375 C 4.664062 -5.890625 3.109375 -10.535156 3.109375 -16.546875 L 3.109375 -45.09375 L 12.125 -45.09375 L 12.125 -17.484375 C 12.125 -13.617188 12.875 -10.691406 14.375 -8.703125 C 15.03125 -7.796875 15.875 -7.113281 16.90625 -6.65625 C 17.945312 -6.195312 19.238281 -5.96875 20.78125 -5.96875 C 23.800781 -5.96875 26.019531 -6.878906 27.4375 -8.703125 C 28.96875 -10.648438 29.734375 -13.578125 29.734375 -17.484375 L 29.734375 -45.09375 Z M 31.28125 -45.09375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(170.836098, 55.95717)">
|
||||
<g>
|
||||
<path d="M 3.671875 -33.90625 C 4.492188 -33.90625 5.257812 -33.84375 5.96875 -33.71875 C 6.675781 -33.59375 7.347656 -33.394531 7.984375 -33.125 C 8.628906 -32.851562 9.222656 -32.507812 9.765625 -32.09375 C 10.304688 -31.675781 10.765625 -31.179688 11.140625 -30.609375 C 11.835938 -31.304688 12.644531 -31.925781 13.5625 -32.46875 C 13.8125 -32.632812 14.0625 -32.789062 14.3125 -32.9375 C 14.5625 -33.082031 14.828125 -33.21875 15.109375 -33.34375 C 15.398438 -33.46875 15.691406 -33.582031 15.984375 -33.6875 C 16.273438 -33.789062 16.566406 -33.90625 16.859375 -34.03125 C 17.523438 -34.238281 18.207031 -34.390625 18.90625 -34.484375 C 19.613281 -34.585938 20.34375 -34.640625 21.09375 -34.640625 C 23.164062 -34.640625 25.164062 -34.222656 27.09375 -33.390625 C 29.019531 -32.566406 30.707031 -31.367188 32.15625 -29.796875 C 33.613281 -28.222656 34.753906 -26.316406 35.578125 -24.078125 C 35.867188 -23.328125 36.109375 -22.535156 36.296875 -21.703125 C 36.484375 -20.878906 36.628906 -20.03125 36.734375 -19.15625 C 36.835938 -18.289062 36.890625 -17.398438 36.890625 -16.484375 C 36.890625 -13.742188 36.472656 -11.210938 35.640625 -8.890625 C 35.222656 -7.773438 34.734375 -6.71875 34.171875 -5.71875 C 33.617188 -4.726562 32.96875 -3.816406 32.21875 -2.984375 C 31.96875 -2.648438 31.6875 -2.335938 31.375 -2.046875 C 31.070312 -1.765625 30.753906 -1.476562 30.421875 -1.1875 C 30.085938 -0.894531 29.753906 -0.625 29.421875 -0.375 C 29.085938 -0.125 28.734375 0.101562 28.359375 0.3125 C 27.992188 0.519531 27.625 0.707031 27.25 0.875 C 26.289062 1.375 25.300781 1.734375 24.28125 1.953125 C 23.269531 2.179688 22.25 2.296875 21.21875 2.296875 C 19.019531 2.296875 17.003906 1.863281 15.171875 1 C 14.515625 0.707031 13.894531 0.375 13.3125 0 L 13.3125 15.234375 L 4.609375 15.234375 L 4.609375 -24.078125 C 4.609375 -25.023438 4.441406 -25.644531 4.109375 -25.9375 C 3.734375 -26.269531 3.191406 -26.4375 2.484375 -26.4375 L 0.4375 -26.4375 L 1 -28.421875 L 2.171875 -32.78125 L 2.484375 -33.90625 Z M 28.046875 -16.296875 C 28.046875 -17.171875 27.984375 -17.976562 27.859375 -18.71875 C 27.742188 -19.46875 27.570312 -20.160156 27.34375 -20.796875 C 27.113281 -21.441406 26.851562 -22.035156 26.5625 -22.578125 C 26.269531 -23.117188 25.9375 -23.617188 25.5625 -24.078125 C 24.820312 -24.941406 23.960938 -25.59375 22.984375 -26.03125 C 22.003906 -26.46875 20.957031 -26.6875 19.84375 -26.6875 C 19.09375 -26.6875 18.382812 -26.59375 17.71875 -26.40625 C 17.0625 -26.21875 16.441406 -25.9375 15.859375 -25.5625 C 14.785156 -24.894531 13.9375 -24.191406 13.3125 -23.453125 L 13.3125 -8.703125 C 14.1875 -7.921875 15.140625 -7.257812 16.171875 -6.71875 C 17.335938 -6.09375 18.644531 -5.78125 20.09375 -5.78125 C 21.25 -5.78125 22.304688 -6.007812 23.265625 -6.46875 C 24.210938 -6.96875 25.039062 -7.648438 25.75 -8.515625 C 26.125 -8.972656 26.445312 -9.472656 26.71875 -10.015625 C 26.988281 -10.554688 27.226562 -11.15625 27.4375 -11.8125 C 27.644531 -12.476562 27.796875 -13.179688 27.890625 -13.921875 C 27.992188 -14.671875 28.046875 -15.460938 28.046875 -16.296875 Z M 28.046875 -16.296875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(208.963656, 55.95717)">
|
||||
<g>
|
||||
<path d="M 7.53125 -42.921875 L 13.375 -42.921875 L 13.375 -32.71875 L 22.328125 -32.71875 L 22.328125 -24.75 L 13.375 -24.75 L 13.375 -10.703125 C 13.375 -9.660156 13.476562 -8.8125 13.6875 -8.15625 C 13.894531 -7.53125 14.144531 -7.050781 14.4375 -6.71875 C 14.71875 -6.382812 15.066406 -6.15625 15.484375 -6.03125 C 15.734375 -5.945312 15.984375 -5.882812 16.234375 -5.84375 C 16.484375 -5.800781 16.734375 -5.78125 16.984375 -5.78125 C 17.847656 -5.78125 18.632812 -5.90625 19.34375 -6.15625 C 20.21875 -6.488281 21.023438 -6.863281 21.765625 -7.28125 L 23.390625 -8.15625 L 24.015625 -6.40625 L 25.5625 -1.921875 L 25.9375 -0.75 L 24.875 -0.125 C 23.96875 0.457031 22.75 1 21.21875 1.5 C 20.425781 1.78125 19.613281 1.984375 18.78125 2.109375 C 17.957031 2.234375 17.128906 2.296875 16.296875 2.296875 C 12.734375 2.296875 9.894531 1.113281 7.78125 -1.25 C 6.695312 -2.445312 5.90625 -3.875 5.40625 -5.53125 C 4.914062 -7.195312 4.671875 -9.066406 4.671875 -11.140625 L 4.671875 -24.75 L -0.9375 -24.75 L -0.9375 -32.71875 L 4.96875 -32.71875 L 5.96875 -41.546875 L 6.15625 -42.921875 Z M 7.53125 -42.921875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(233.656391, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.578125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 12.125 -33.90625 L 12.125 1.5625 Z M 7.78125 -36.328125 C 6.28125 -36.328125 4.988281 -36.863281 3.90625 -37.9375 C 2.832031 -39.019531 2.296875 -40.304688 2.296875 -41.796875 C 2.296875 -43.410156 2.800781 -44.726562 3.8125 -45.75 C 4.832031 -46.769531 6.15625 -47.28125 7.78125 -47.28125 C 8.519531 -47.28125 9.222656 -47.144531 9.890625 -46.875 C 10.554688 -46.601562 11.144531 -46.207031 11.65625 -45.6875 C 12.175781 -45.164062 12.570312 -44.570312 12.84375 -43.90625 C 13.113281 -43.25 13.25 -42.546875 13.25 -41.796875 C 13.25 -40.179688 12.738281 -38.863281 11.71875 -37.84375 C 10.707031 -36.832031 9.394531 -36.328125 7.78125 -36.328125 Z M 7.78125 -36.328125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(249.205965, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.109375 C 12.269531 -31.390625 12.59375 -31.65625 12.90625 -31.90625 C 13.21875 -32.15625 13.5625 -32.382812 13.9375 -32.59375 C 14.4375 -32.925781 14.953125 -33.226562 15.484375 -33.5 C 16.023438 -33.769531 16.59375 -33.984375 17.1875 -34.140625 C 17.789062 -34.304688 18.394531 -34.429688 19 -34.515625 C 19.601562 -34.597656 20.238281 -34.640625 20.90625 -34.640625 C 23.800781 -34.640625 26.203125 -33.914062 28.109375 -32.46875 C 29.273438 -31.59375 30.234375 -30.515625 30.984375 -29.234375 C 32.015625 -30.398438 33.113281 -31.4375 34.28125 -32.34375 C 34.6875 -32.632812 35.117188 -32.90625 35.578125 -33.15625 C 36.035156 -33.40625 36.507812 -33.625 37 -33.8125 C 37.5 -34 38.007812 -34.148438 38.53125 -34.265625 C 39.050781 -34.390625 39.578125 -34.484375 40.109375 -34.546875 C 40.648438 -34.609375 41.210938 -34.640625 41.796875 -34.640625 C 42.046875 -34.640625 42.285156 -34.640625 42.515625 -34.640625 C 42.742188 -34.640625 42.972656 -34.628906 43.203125 -34.609375 C 43.429688 -34.585938 43.65625 -34.554688 43.875 -34.515625 C 44.101562 -34.472656 44.320312 -34.429688 44.53125 -34.390625 C 44.738281 -34.359375 44.945312 -34.320312 45.15625 -34.28125 C 45.363281 -34.238281 45.570312 -34.1875 45.78125 -34.125 C 45.988281 -34.0625 46.195312 -33.988281 46.40625 -33.90625 C 46.613281 -33.820312 46.820312 -33.738281 47.03125 -33.65625 C 47.394531 -33.488281 47.753906 -33.300781 48.109375 -33.09375 C 48.460938 -32.882812 48.796875 -32.65625 49.109375 -32.40625 C 49.421875 -32.15625 49.722656 -31.894531 50.015625 -31.625 C 50.304688 -31.351562 50.570312 -31.054688 50.8125 -30.734375 C 51.851562 -29.523438 52.601562 -28.09375 53.0625 -26.4375 C 53.5625 -24.820312 53.8125 -23.039062 53.8125 -21.09375 L 53.8125 1.5625 L 45.09375 1.5625 L 45.09375 -20.65625 C 45.09375 -22.5625 44.679688 -24.070312 43.859375 -25.1875 C 43.109375 -26.144531 42.09375 -26.625 40.8125 -26.625 C 40.0625 -26.625 39.351562 -26.476562 38.6875 -26.1875 C 37.945312 -25.851562 37.242188 -25.414062 36.578125 -24.875 C 35.867188 -24.300781 35.179688 -23.640625 34.515625 -22.890625 C 34.273438 -22.554688 34.007812 -22.203125 33.71875 -21.828125 C 33.425781 -21.460938 33.175781 -21.113281 32.96875 -20.78125 L 32.96875 1.5625 L 24.265625 1.5625 L 24.265625 -20.65625 C 24.265625 -22.5625 23.847656 -24.070312 23.015625 -25.1875 C 22.265625 -26.144531 21.25 -26.625 19.96875 -26.625 C 19.257812 -26.625 18.554688 -26.476562 17.859375 -26.1875 C 17.523438 -26.0625 17.171875 -25.894531 16.796875 -25.6875 C 16.421875 -25.476562 16.066406 -25.226562 15.734375 -24.9375 C 15.609375 -24.851562 15.484375 -24.757812 15.359375 -24.65625 C 15.234375 -24.5625 15.117188 -24.460938 15.015625 -24.359375 C 14.910156 -24.253906 14.796875 -24.148438 14.671875 -24.046875 C 14.554688 -23.941406 14.445312 -23.835938 14.34375 -23.734375 C 14.238281 -23.628906 14.132812 -23.515625 14.03125 -23.390625 C 13.925781 -23.265625 13.8125 -23.140625 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.109375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(306.117405, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
<button type="button" id="mobile-menu-close" class="flex h-8 w-8 items-center justify-center rounded-lg hover:bg-slate-100 transition">
|
||||
<svg class="w-5 h-5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<span class="inline-flex items-center rounded-full bg-indigo-50 px-3 py-1 text-xs font-semibold text-indigo-700 ring-1 ring-inset ring-indigo-600/20">
|
||||
API Reference
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<ul role="list" class="space-y-6">
|
||||
<li class="relative">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide mb-3">Guides</h6>
|
||||
<ul role="list" class="space-y-1 border-l border-slate-200 ml-2">
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/introduction">Introduction</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/authentication">Authentication</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/pagination">Pagination</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/permissions">Permissions</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/data-types">Data Types</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/errors">Errors</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/openapi">OpenAPI Spec</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide mb-3">Resources</h6>
|
||||
<ul role="list" class="space-y-1 border-l border-slate-200 ml-2">
|
||||
<% for(var i=0; i<resources.length; i++) {%>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition truncate" href="/reference/<%= resources[i].path -%>"><%= resources[i].name -%></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-slate-200">
|
||||
<a href="/dashboard" class="flex items-center justify-center w-full rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-indigo-700 transition shadow-sm">
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Highlight active navigation link
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const currentPath = window.location.pathname;
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
const mobileNavLinks = document.querySelectorAll('.mobile-nav-link');
|
||||
|
||||
navLinks.forEach(function(link) {
|
||||
const href = link.getAttribute('href');
|
||||
if (href === currentPath) {
|
||||
link.classList.remove('text-slate-600', 'border-transparent', 'hover:border-slate-300');
|
||||
link.classList.add('text-slate-900', 'font-medium', 'border-indigo-500');
|
||||
link.setAttribute('aria-current', 'page');
|
||||
}
|
||||
});
|
||||
|
||||
mobileNavLinks.forEach(function(link) {
|
||||
const href = link.getAttribute('href');
|
||||
if (href === currentPath) {
|
||||
link.classList.remove('text-slate-600', 'border-transparent', 'hover:border-slate-300');
|
||||
link.classList.add('text-slate-900', 'font-medium', 'border-indigo-500');
|
||||
link.setAttribute('aria-current', 'page');
|
||||
}
|
||||
});
|
||||
|
||||
// Mobile menu toggle functionality
|
||||
const menuToggle = document.getElementById('mobile-menu-toggle');
|
||||
const menuClose = document.getElementById('mobile-menu-close');
|
||||
const menuOverlay = document.getElementById('mobile-menu-overlay');
|
||||
const menuPanel = document.getElementById('mobile-menu-panel');
|
||||
const menuOpenIcon = document.getElementById('menu-open-icon');
|
||||
const menuCloseIcon = document.getElementById('menu-close-icon');
|
||||
|
||||
function openMenu() {
|
||||
menuOverlay.classList.remove('hidden');
|
||||
menuPanel.classList.remove('-translate-x-full');
|
||||
menuOpenIcon.classList.add('hidden');
|
||||
menuCloseIcon.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
menuOverlay.classList.add('hidden');
|
||||
menuPanel.classList.add('-translate-x-full');
|
||||
menuOpenIcon.classList.remove('hidden');
|
||||
menuCloseIcon.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (menuToggle) {
|
||||
menuToggle.addEventListener('click', function() {
|
||||
const isOpen = !menuPanel.classList.contains('-translate-x-full');
|
||||
if (isOpen) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (menuClose) {
|
||||
menuClose.addEventListener('click', closeMenu);
|
||||
}
|
||||
|
||||
if (menuOverlay) {
|
||||
menuOverlay.addEventListener('click', closeMenu);
|
||||
}
|
||||
|
||||
// Close menu on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && !menuPanel.classList.contains('-translate-x-full')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -7,8 +7,8 @@ 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
|
||||
RUN npm config set fetch-retry-mintimeout 100000
|
||||
RUN npm config set fetch-retry-maxtimeout 600000
|
||||
RUN npm config set fetch-retry-mintimeout 20000
|
||||
RUN npm config set fetch-retry-maxtimeout 60000
|
||||
|
||||
|
||||
|
||||
|
||||
40
Accounts/package-lock.json
generated
40
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.30.1",
|
||||
"react-router-dom": "^6.30.2",
|
||||
"use-async-effect": "^2.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -57,6 +57,7 @@
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
@@ -85,8 +86,10 @@
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"marked": "^12.0.2",
|
||||
"mermaid": "^11.12.2",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^7.0.7",
|
||||
"otpauth": "^9.3.1",
|
||||
@@ -103,7 +106,7 @@
|
||||
"react-dropzone": "^14.2.2",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-markdown": "^9.0.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
@@ -113,7 +116,7 @@
|
||||
"recharts": "^2.12.7",
|
||||
"redis-semaphore": "^5.5.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"slackify-markdown": "^4.4.0",
|
||||
"slugify": "^1.6.5",
|
||||
"socket.io": "^4.7.4",
|
||||
@@ -348,9 +351,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
||||
"version": "1.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
|
||||
"integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -656,10 +659,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
@@ -966,12 +970,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.30.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
||||
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
|
||||
"version": "6.30.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
|
||||
"integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.23.0"
|
||||
"@remix-run/router": "1.23.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -981,13 +985,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"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==",
|
||||
"version": "6.30.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
|
||||
"integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.23.0",
|
||||
"react-router": "6.30.1"
|
||||
"@remix-run/router": "1.23.2",
|
||||
"react-router": "6.30.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
"name": "@oneuptime/accounts",
|
||||
"version": "0.1.0",
|
||||
"private": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime"
|
||||
},
|
||||
"scripts": {
|
||||
"dev-build": "NODE_ENV=development node esbuild.config.js",
|
||||
"dev": "npx nodemon",
|
||||
@@ -33,7 +37,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-router-dom": "^6.30.2",
|
||||
"use-async-effect": "^2.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
Disallow: /api/*
|
||||
|
||||
@@ -2,12 +2,13 @@ import React from "react";
|
||||
|
||||
const ForbiddenPage: () => JSX.Element = () => {
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto text-center">
|
||||
<div className="text-6xl sm:text-7xl mb-4">🚫</div>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Forbidden
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
You do not have permission to access this page.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -14,35 +14,35 @@ const ForgotPassword: () => JSX.Element = () => {
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Forgot your password
|
||||
</h2>
|
||||
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
Please enter your email and the password reset link will be sent to
|
||||
you.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
We have emailed you the password reset link. Please do not forget to
|
||||
check spam.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
name="Forgot Password"
|
||||
@@ -81,8 +81,8 @@ const ForgotPassword: () => JSX.Element = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
<div className="mt-4 sm:mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500 text-sm sm:text-base">
|
||||
Remember your password?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
|
||||
@@ -274,10 +274,10 @@ const LoginPage: () => JSX.Element = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
@@ -286,10 +286,10 @@ const LoginPage: () => JSX.Element = () => {
|
||||
</div>
|
||||
{!showTwoFactorAuth && (
|
||||
<>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
Join thousands of business that use OneUptime to help them stay
|
||||
online all the time.
|
||||
</p>
|
||||
@@ -298,10 +298,10 @@ const LoginPage: () => JSX.Element = () => {
|
||||
|
||||
{showTwoFactorAuth && (
|
||||
<>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Two Factor Authentication
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
Select two factor authentication method. You will be asked to
|
||||
enter a code from the selected method.
|
||||
</p>
|
||||
@@ -309,8 +309,8 @@ const LoginPage: () => JSX.Element = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto">
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow sm:rounded-lg sm:py-8 sm:px-10 rounded-lg">
|
||||
{!showTwoFactorAuth && (
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
@@ -505,9 +505,9 @@ const LoginPage: () => JSX.Element = () => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-10 text-center">
|
||||
<div className="mt-6 sm:mt-10 text-center">
|
||||
{!selectedTotpAuth && !selectedWebAuthn && (
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
<div className="text-muted mb-0 text-gray-500 text-sm sm:text-base">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/register")}
|
||||
|
||||
@@ -126,18 +126,18 @@ const LoginPage: () => JSX.Element = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<div className="w-full max-w-md mx-auto px-4 sm:px-0">
|
||||
<div className="flex min-h-full flex-col justify-center py-8 sm:py-12">
|
||||
<div className="w-full">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-10 text-center text-xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-6 sm:mt-10 text-center text-lg sm:text-xl tracking-tight text-gray-900">
|
||||
Select Project
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
Select the project you want to login to.
|
||||
</p>
|
||||
</div>
|
||||
@@ -164,23 +164,23 @@ const LoginPage: () => JSX.Element = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Login with SSO
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
Login with your SSO provider to access your account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto">
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
<BasicForm
|
||||
modelType={User}
|
||||
id="login-form"
|
||||
@@ -217,8 +217,8 @@ const LoginPage: () => JSX.Element = () => {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
<div className="mt-6 sm:mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500 text-sm sm:text-base">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/register")}
|
||||
|
||||
@@ -2,12 +2,13 @@ import React from "react";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto text-center">
|
||||
<div className="text-6xl sm:text-7xl mb-4">🔍</div>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Page not found
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
Page you are looking for does not exist.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -248,17 +248,17 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="flex min-h-full flex-col justify-center py-6 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto lg:max-w-2xl">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Create your OneUptime account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
Join thousands of business that use OneUptime to help them stay online
|
||||
all the time.
|
||||
</p>
|
||||
@@ -267,8 +267,8 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 lg:mx-auto lg:w-full lg:max-w-2xl">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto lg:max-w-2xl">
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
@@ -339,8 +339,8 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5 text-center text-gray-500">
|
||||
<p className="text-muted mb-0">
|
||||
<div className="mt-4 sm:mt-5 text-center text-gray-500">
|
||||
<p className="text-muted mb-0 text-sm sm:text-base">
|
||||
Already have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
|
||||
@@ -14,33 +14,33 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Reset your password
|
||||
</h2>
|
||||
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
Please enter your new password and we will have it updated.{" "}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
Your password has been updated. Please log in.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
@@ -97,8 +97,8 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
<div className="mt-4 sm:mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500 text-sm sm:text-base">
|
||||
Know your password?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
|
||||
@@ -58,60 +58,53 @@ const VerifyEmail: () => JSX.Element = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="container-fluid p-0">
|
||||
<div className="row g-0">
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<div className="text-center mb-8 sm:mb-10">
|
||||
<img
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-6">
|
||||
<div className="auth-full-page-content d-flex p-sm-5 p-4">
|
||||
<div className="w-100">
|
||||
<div className="d-flex flex-column h-100">
|
||||
<div className="auth-content my-auto">
|
||||
<div
|
||||
className="mt-4 text-center flex justify-center"
|
||||
style={{ marginBottom: "40px" }}
|
||||
>
|
||||
<img
|
||||
style={{ height: "50px" }}
|
||||
src={`${OneUptimeLogo}`}
|
||||
/>
|
||||
</div>
|
||||
{!error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">Your email is verified.</h5>
|
||||
<p className="text-muted mt-2 mb-0">
|
||||
Thank you for verifying your email. You can now log in
|
||||
to OneUptime.{" "}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">Sorry, something went wrong!</h5>
|
||||
<p className="text-muted mt-2 mb-0">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0">
|
||||
Return to sign in?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="hover:underline text-primary fw-semibold"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
{!error && (
|
||||
<div className="text-center">
|
||||
<div className="text-5xl sm:text-6xl mb-4">✅</div>
|
||||
<h2 className="text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Your email is verified.
|
||||
</h2>
|
||||
<p className="text-gray-600 mt-3 text-sm sm:text-base px-2 sm:px-0">
|
||||
Thank you for verifying your email. You can now log in to
|
||||
OneUptime.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
{error && (
|
||||
<div className="text-center">
|
||||
<div className="text-5xl sm:text-6xl mb-4">❌</div>
|
||||
<h2 className="text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Sorry, something went wrong!
|
||||
</h2>
|
||||
<p className="text-gray-600 mt-3 text-sm sm:text-base px-2 sm:px-0">
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 sm:mt-8 text-center">
|
||||
<p className="text-gray-500 text-sm sm:text-base">
|
||||
Return to sign in?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="slack-app-id" content="ACVBMTPJQ">
|
||||
<meta name="description" content="OneUptime — the complete open-source observability platform.">
|
||||
<meta name="description" content="OneUptime - the complete open-source observability platform.">
|
||||
|
||||
<% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %>
|
||||
<!-- Google Tag Manager -->
|
||||
|
||||
@@ -7,8 +7,8 @@ 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
|
||||
RUN npm config set fetch-retry-mintimeout 100000
|
||||
RUN npm config set fetch-retry-maxtimeout 600000
|
||||
RUN npm config set fetch-retry-mintimeout 20000
|
||||
RUN npm config set fetch-retry-maxtimeout 60000
|
||||
|
||||
|
||||
|
||||
|
||||
40
AdminDashboard/package-lock.json
generated
40
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.30.1"
|
||||
"react-router-dom": "^6.30.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.35",
|
||||
@@ -56,6 +56,7 @@
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
@@ -84,8 +85,10 @@
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"marked": "^12.0.2",
|
||||
"mermaid": "^11.12.2",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^7.0.7",
|
||||
"otpauth": "^9.3.1",
|
||||
@@ -102,7 +105,7 @@
|
||||
"react-dropzone": "^14.2.2",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-markdown": "^9.0.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
@@ -112,7 +115,7 @@
|
||||
"recharts": "^2.12.7",
|
||||
"redis-semaphore": "^5.5.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"slackify-markdown": "^4.4.0",
|
||||
"slugify": "^1.6.5",
|
||||
"socket.io": "^4.7.4",
|
||||
@@ -347,9 +350,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
||||
"version": "1.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
|
||||
"integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -575,10 +578,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
@@ -946,12 +950,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.30.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
||||
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
|
||||
"version": "6.30.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
|
||||
"integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.23.0"
|
||||
"@remix-run/router": "1.23.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -961,13 +965,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"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==",
|
||||
"version": "6.30.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
|
||||
"integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.23.0",
|
||||
"react-router": "6.30.1"
|
||||
"@remix-run/router": "1.23.2",
|
||||
"react-router": "6.30.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
"name": "@oneuptime/admin-dashboard",
|
||||
"version": "0.1.0",
|
||||
"private": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime"
|
||||
},
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
|
||||
"ejs": "^3.1.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.30.1"
|
||||
"react-router-dom": "^6.30.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev-build": "NODE_ENV=development node esbuild.config.js",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
Disallow: /api/*
|
||||
|
||||
@@ -4,11 +4,14 @@ import Logout from "./Pages/Logout/Logout";
|
||||
import Projects from "./Pages/Projects/Index";
|
||||
import SettingsAPIKey from "./Pages/Settings/APIKey/Index";
|
||||
import SettingsAuthentication from "./Pages/Settings/Authentication/Index";
|
||||
import SettingsDataRetention from "./Pages/Settings/DataRetention/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";
|
||||
import SettingsAIAgents from "./Pages/Settings/AIAgents/Index";
|
||||
import SettingsLlmProviders from "./Pages/Settings/LlmProviders/Index";
|
||||
import Users from "./Pages/Users/Index";
|
||||
import PageMap from "./Utils/PageMap";
|
||||
import RouteMap from "./Utils/RouteMap";
|
||||
@@ -122,6 +125,16 @@ const App: () => JSX.Element = () => {
|
||||
element={<SettingsProbes />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_AI_AGENTS]?.toString() || ""}
|
||||
element={<SettingsAIAgents />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_LLM_PROVIDERS]?.toString() || ""}
|
||||
element={<SettingsLlmProviders />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_AUTHENTICATION]?.toString() || ""}
|
||||
element={<SettingsAuthentication />}
|
||||
@@ -131,6 +144,11 @@ const App: () => JSX.Element = () => {
|
||||
path={RouteMap[PageMap.SETTINGS_API_KEY]?.toString() || ""}
|
||||
element={<SettingsAPIKey />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_DATA_RETENTION]?.toString() || ""}
|
||||
element={<SettingsDataRetention />}
|
||||
/>
|
||||
</Routes>
|
||||
</MasterPage>
|
||||
);
|
||||
|
||||
@@ -68,7 +68,7 @@ const Projects: FunctionComponent = (): ReactElement => {
|
||||
_id: true,
|
||||
},
|
||||
title: "Project ID",
|
||||
fieldType: FieldType.Text,
|
||||
fieldType: FieldType.ObjectID,
|
||||
placeholder: "-",
|
||||
},
|
||||
{
|
||||
|
||||
248
AdminDashboard/src/Pages/Settings/AIAgents/Index.tsx
Normal file
248
AdminDashboard/src/Pages/Settings/AIAgents/Index.tsx
Normal file
@@ -0,0 +1,248 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IsNull from "Common/Types/BaseDatabase/IsNull";
|
||||
import { Green, Red } from "Common/Types/BrandColors";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Banner from "Common/UI/Components/Banner/Banner";
|
||||
import { ButtonStyleType } from "Common/UI/Components/Button/Button";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import Page from "Common/UI/Components/Page/Page";
|
||||
import AIAgentElement from "Common/UI/Components/AIAgent/AIAgent";
|
||||
import Statusbubble from "Common/UI/Components/StatusBubble/StatusBubble";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import AIAgent from "Common/Models/DatabaseModels/AIAgent";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
const [showKeyModal, setShowKeyModal] = useState<boolean>(false);
|
||||
|
||||
const [currentAIAgent, setCurrentAIAgent] = useState<AIAgent | null>(null);
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Global AI Agents",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AI_AGENTS] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
|
||||
<Banner
|
||||
openInNewTab={true}
|
||||
title="Need help with setting up Global AI Agents?"
|
||||
description="Here is a guide which will help you get set up"
|
||||
link={Route.fromString("/docs/ai/ai-agent")}
|
||||
hideOnMobile={true}
|
||||
/>
|
||||
|
||||
<ModelTable<AIAgent>
|
||||
userPreferencesKey={"admin-ai-agents-table"}
|
||||
modelType={AIAgent}
|
||||
id="ai-agents-table"
|
||||
name="Settings > Global AI Agents"
|
||||
isDeleteable={true}
|
||||
isEditable={true}
|
||||
isCreateable={true}
|
||||
cardProps={{
|
||||
title: "Global AI Agents",
|
||||
description:
|
||||
"Global AI Agents help you automate incident management with AI-powered responses from different locations around the world.",
|
||||
}}
|
||||
query={{
|
||||
projectId: new IsNull(),
|
||||
isGlobalAIAgent: true,
|
||||
}}
|
||||
modelAPI={AdminModelAPI}
|
||||
noItemsMessage={"No AI agents found."}
|
||||
showRefreshButton={true}
|
||||
onBeforeCreate={(item: AIAgent) => {
|
||||
item.isGlobalAIAgent = true;
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "my-ai-agent",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
fieldType: FormFieldSchemaType.LongText,
|
||||
required: false,
|
||||
placeholder: "This AI agent handles automated incident management.",
|
||||
},
|
||||
|
||||
{
|
||||
field: {
|
||||
iconFile: true,
|
||||
},
|
||||
title: "AI Agent Logo",
|
||||
fieldType: FormFieldSchemaType.ImageFile,
|
||||
required: false,
|
||||
placeholder: "Upload logo",
|
||||
},
|
||||
]}
|
||||
selectMoreFields={{
|
||||
key: true,
|
||||
iconFileId: true,
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
title: "Show ID and Key",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
onClick: async (
|
||||
item: AIAgent,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction,
|
||||
) => {
|
||||
try {
|
||||
setCurrentAIAgent(item);
|
||||
setShowKeyModal(true);
|
||||
|
||||
onCompleteAction();
|
||||
} catch (err) {
|
||||
onCompleteAction();
|
||||
onError(err as Error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
type: FieldType.LongText,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
|
||||
getElement: (item: AIAgent): ReactElement => {
|
||||
return <AIAgentElement aiAgent={item} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
noValueMessage: "-",
|
||||
title: "Description",
|
||||
type: FieldType.LongText,
|
||||
hideOnMobile: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
lastAlive: true,
|
||||
},
|
||||
title: "Status",
|
||||
type: FieldType.Text,
|
||||
|
||||
getElement: (item: AIAgent): ReactElement => {
|
||||
if (
|
||||
item &&
|
||||
item["lastAlive"] &&
|
||||
OneUptimeDate.getNumberOfMinutesBetweenDates(
|
||||
OneUptimeDate.fromString(item["lastAlive"]),
|
||||
OneUptimeDate.getCurrentDate(),
|
||||
) < 5
|
||||
) {
|
||||
return (
|
||||
<Statusbubble
|
||||
text={"Connected"}
|
||||
color={Green}
|
||||
shouldAnimate={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Statusbubble
|
||||
text={"Disconnected"}
|
||||
color={Red}
|
||||
shouldAnimate={false}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showKeyModal && currentAIAgent ? (
|
||||
<ConfirmModal
|
||||
title={`AI Agent Key`}
|
||||
description={
|
||||
<div>
|
||||
<span>Here is your AI agent key. Please keep this a secret.</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>AI Agent ID: </b> {currentAIAgent["_id"]?.toString()}
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>AI Agent Key: </b> {currentAIAgent["key"]?.toString()}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
submitButtonText={"Close"}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
onSubmit={async () => {
|
||||
setShowKeyModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
85
AdminDashboard/src/Pages/Settings/DataRetention/Index.tsx
Normal file
85
AdminDashboard/src/Pages/Settings/DataRetention/Index.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
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 } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Data Retention",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_DATA_RETENTION] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
<CardModelDetail
|
||||
name="Monitor Log Retention Settings"
|
||||
cardProps={{
|
||||
title: "Monitor Log Retention",
|
||||
description:
|
||||
"Configure how long monitor logs are retained before being automatically deleted.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
monitorLogRetentionInDays: true,
|
||||
},
|
||||
title: "Monitor Log Retention (Days)",
|
||||
fieldType: FormFieldSchemaType.PositiveNumber,
|
||||
required: false,
|
||||
description:
|
||||
"Number of days to retain monitor logs. Monitor logs older than this will be automatically deleted. Default is 1 day if not set. Minimum: 1 day, Maximum: 365 days.",
|
||||
validation: {
|
||||
minValue: 1,
|
||||
maxValue: 365,
|
||||
},
|
||||
placeholder: "1",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config-data-retention",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
monitorLogRetentionInDays: true,
|
||||
},
|
||||
fieldType: FieldType.Number,
|
||||
title: "Monitor Log Retention (Days)",
|
||||
placeholder: "1 (default)",
|
||||
description:
|
||||
"Number of days to retain monitor logs. Monitor logs older than this will be automatically deleted.",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
252
AdminDashboard/src/Pages/Settings/LlmProviders/Index.tsx
Normal file
252
AdminDashboard/src/Pages/Settings/LlmProviders/Index.tsx
Normal file
@@ -0,0 +1,252 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IsNull from "Common/Types/BaseDatabase/IsNull";
|
||||
import Banner from "Common/UI/Components/Banner/Banner";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import Page from "Common/UI/Components/Page/Page";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import LlmProvider from "Common/Models/DatabaseModels/LlmProvider";
|
||||
import LlmType from "Common/Types/LLM/LlmType";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import DropdownUtil from "Common/UI/Utils/Dropdown";
|
||||
import { BILLING_ENABLED } from "Common/UI/Config";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Global LLM Providers",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_LLM_PROVIDERS] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* LLM Provider Settings View */}
|
||||
|
||||
<Banner
|
||||
openInNewTab={true}
|
||||
title="Need help with setting up LLM Providers?"
|
||||
description="LLM Providers enable AI features. You can configure global LLM Providers that are available to all projects."
|
||||
link={Route.fromString("/docs/ai/llm-provider")}
|
||||
hideOnMobile={true}
|
||||
/>
|
||||
|
||||
<ModelTable<LlmProvider>
|
||||
userPreferencesKey={"admin-llms-table"}
|
||||
modelType={LlmProvider}
|
||||
id="llms-table"
|
||||
name="Settings > Global LLM Providers"
|
||||
isDeleteable={true}
|
||||
isEditable={true}
|
||||
isCreateable={true}
|
||||
cardProps={{
|
||||
title: "Global LLM Providers",
|
||||
description:
|
||||
"Global LLM Providers are available to all projects for AI features. Configure OpenAI, Anthropic, Ollama, or other LLM providers.",
|
||||
}}
|
||||
query={{
|
||||
projectId: new IsNull(),
|
||||
isGlobalLlm: true,
|
||||
}}
|
||||
modelAPI={AdminModelAPI}
|
||||
noItemsMessage={
|
||||
"No LLM Providers configured. Add an LLM Provider to enable AI features."
|
||||
}
|
||||
showRefreshButton={true}
|
||||
onBeforeCreate={(item: LlmProvider) => {
|
||||
item.isGlobalLlm = true;
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formSteps={[
|
||||
{
|
||||
title: "Basic Info",
|
||||
id: "basic-info",
|
||||
},
|
||||
{
|
||||
title: "Provider Settings",
|
||||
id: "provider-settings",
|
||||
},
|
||||
...(BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
title: "Cost Settings",
|
||||
id: "cost-settings",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
stepId: "basic-info",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "My OpenAI GPT-4",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
stepId: "basic-info",
|
||||
fieldType: FormFieldSchemaType.LongText,
|
||||
required: false,
|
||||
placeholder: "GPT-4 for general AI features.",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
llmType: true,
|
||||
},
|
||||
title: "LLM Provider",
|
||||
stepId: "provider-settings",
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
required: true,
|
||||
placeholder: "Select LLM Provider",
|
||||
dropdownOptions: DropdownUtil.getDropdownOptionsFromEnum(LlmType),
|
||||
},
|
||||
{
|
||||
field: {
|
||||
apiKey: true,
|
||||
},
|
||||
title: "API Key",
|
||||
stepId: "provider-settings",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
placeholder: "sk-...",
|
||||
description:
|
||||
"Required for OpenAI and Anthropic. Not required for Ollama if self-hosted.",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
modelName: true,
|
||||
},
|
||||
title: "Model Name",
|
||||
stepId: "provider-settings",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
placeholder: "gpt-4, claude-3-opus, llama2",
|
||||
description:
|
||||
"The specific model to use (e.g., gpt-4, claude-3-opus, llama2).",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
baseUrl: true,
|
||||
},
|
||||
title: "Base URL",
|
||||
stepId: "provider-settings",
|
||||
fieldType: FormFieldSchemaType.URL,
|
||||
required: false,
|
||||
placeholder: "http://localhost:11434",
|
||||
description:
|
||||
"Required for Ollama. Optional for others to use custom endpoints.",
|
||||
},
|
||||
...(BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
field: {
|
||||
costPerMillionTokensInUSDCents: true,
|
||||
},
|
||||
title: "Cost Per Million Tokens (USD Cents)",
|
||||
stepId: "cost-settings",
|
||||
fieldType: FormFieldSchemaType.Number,
|
||||
required: false,
|
||||
placeholder: "0",
|
||||
description:
|
||||
"Cost per million tokens in USD cents. For example, if the cost is $0.01 per 1M tokens, enter 1.",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
selectMoreFields={{
|
||||
apiKey: true,
|
||||
}}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
type: FieldType.LongText,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
llmType: true,
|
||||
},
|
||||
title: "Provider",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
llmType: true,
|
||||
},
|
||||
title: "Provider",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
modelName: true,
|
||||
},
|
||||
title: "Model",
|
||||
type: FieldType.Text,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
...(BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
field: {
|
||||
costPerMillionTokensInUSDCents: true,
|
||||
},
|
||||
title: "Cost (cents/1M)",
|
||||
type: FieldType.Number,
|
||||
noValueMessage: "0",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
@@ -72,6 +72,37 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
|
||||
icon={IconProp.Signal}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuSection title="Data Retention">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Data Retention",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_DATA_RETENTION] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Database}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuSection title="AI">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Global AI Agents",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AI_AGENTS] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Automation}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Global LLM Providers",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_LLM_PROVIDERS] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Brain}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuSection title="API and Integrations">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
|
||||
@@ -176,7 +176,7 @@ const buildWhatsAppSetupMarkdown: BuildWhatsAppSetupMarkdown = (): string => {
|
||||
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.",
|
||||
"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.",
|
||||
@@ -195,7 +195,7 @@ const buildWhatsAppSetupMarkdown: BuildWhatsAppSetupMarkdown = (): string => {
|
||||
"### 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.",
|
||||
"> 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)
|
||||
@@ -342,7 +342,7 @@ const SettingsWhatsApp: FunctionComponent = (): ReactElement => {
|
||||
metaWhatsAppPhoneNumberId: true,
|
||||
},
|
||||
title: "Phone Number ID",
|
||||
fieldType: FieldType.Text,
|
||||
fieldType: FieldType.ObjectID,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
@@ -350,7 +350,7 @@ const SettingsWhatsApp: FunctionComponent = (): ReactElement => {
|
||||
metaWhatsAppBusinessAccountId: true,
|
||||
},
|
||||
title: "Business Account ID",
|
||||
fieldType: FieldType.Text,
|
||||
fieldType: FieldType.ObjectID,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
@@ -366,7 +366,7 @@ const SettingsWhatsApp: FunctionComponent = (): ReactElement => {
|
||||
metaWhatsAppAppId: true,
|
||||
},
|
||||
title: "App ID",
|
||||
fieldType: FieldType.Text,
|
||||
fieldType: FieldType.ObjectID,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -90,7 +90,7 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
_id: true,
|
||||
},
|
||||
title: "User ID",
|
||||
fieldType: FieldType.Text,
|
||||
fieldType: FieldType.ObjectID,
|
||||
placeholder: "-",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,8 +18,11 @@ enum PageMap {
|
||||
SETTINGS_CALL_AND_SMS = "SETTINGS_CALL_AND_SMS",
|
||||
SETTINGS_WHATSAPP = "SETTINGS_WHATSAPP",
|
||||
SETTINGS_PROBES = "SETTINGS_PROBES",
|
||||
SETTINGS_AI_AGENTS = "SETTINGS_AI_AGENTS",
|
||||
SETTINGS_LLM_PROVIDERS = "SETTINGS_LLM_PROVIDERS",
|
||||
SETTINGS_AUTHENTICATION = "SETTINGS_AUTHENTICATION",
|
||||
SETTINGS_API_KEY = "SETTINGS_API_KEY",
|
||||
SETTINGS_DATA_RETENTION = "SETTINGS_DATA_RETENTION",
|
||||
}
|
||||
|
||||
export default PageMap;
|
||||
|
||||
@@ -30,10 +30,15 @@ const RouteMap: Dictionary<Route> = {
|
||||
[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_AI_AGENTS]: new Route(`/admin/settings/ai-agents`),
|
||||
[PageMap.SETTINGS_LLM_PROVIDERS]: new Route(`/admin/settings/llm-providers`),
|
||||
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
|
||||
`/admin/settings/authentication`,
|
||||
),
|
||||
[PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`),
|
||||
[PageMap.SETTINGS_DATA_RETENTION]: new Route(
|
||||
`/admin/settings/data-retention`,
|
||||
),
|
||||
};
|
||||
|
||||
export class RouteUtil {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="slack-app-id" content="ACVBMTPJQ">
|
||||
<meta name="description" content="OneUptime — the complete open-source observability platform.">
|
||||
<meta name="description" content="OneUptime - the complete open-source observability platform.">
|
||||
|
||||
<% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %>
|
||||
<!-- Google Tag Manager -->
|
||||
|
||||
@@ -7,8 +7,8 @@ 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
|
||||
RUN npm config set fetch-retry-mintimeout 100000
|
||||
RUN npm config set fetch-retry-maxtimeout 600000
|
||||
RUN npm config set fetch-retry-mintimeout 20000
|
||||
RUN npm config set fetch-retry-maxtimeout 60000
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,15 +3,19 @@ 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";
|
||||
import CopilotActionAPI from "Common/Server/API/CopilotActionAPI";
|
||||
import CopilotPullRequestAPI from "Common/Server/API/CopilotPullRequestAPI";
|
||||
import FileAPI from "Common/Server/API/FileAPI";
|
||||
import GlobalConfigAPI from "Common/Server/API/GlobalConfigAPI";
|
||||
import MonitorGroupAPI from "Common/Server/API/MonitorGroupAPI";
|
||||
import NotificationAPI from "Common/Server/API/NotificationAPI";
|
||||
import AIBillingAPI from "Common/Server/API/AIBillingAPI";
|
||||
import TelemetryAPI from "Common/Server/API/TelemetryAPI";
|
||||
import ProbeAPI from "Common/Server/API/ProbeAPI";
|
||||
import AIAgentAPI from "Common/Server/API/AIAgentAPI";
|
||||
import AIAgentTaskAPI from "Common/Server/API/AIAgentTaskAPI";
|
||||
import AIAgentTaskLogAPI from "Common/Server/API/AIAgentTaskLogAPI";
|
||||
import AIAgentTaskPullRequestAPI from "Common/Server/API/AIAgentTaskPullRequestAPI";
|
||||
import AIAgentDataAPI from "Common/Server/API/AIAgentDataAPI";
|
||||
import LlmProviderAPI from "Common/Server/API/LlmProviderAPI";
|
||||
import ProjectAPI from "Common/Server/API/ProjectAPI";
|
||||
import ProjectSsoAPI from "Common/Server/API/ProjectSSO";
|
||||
import WhatsAppLogAPI from "./WhatsAppLogAPI";
|
||||
@@ -34,10 +38,13 @@ import IncidentPublicNoteAPI from "Common/Server/API/IncidentPublicNoteAPI";
|
||||
import ScheduledMaintenanceInternalNoteAPI from "Common/Server/API/ScheduledMaintenanceInternalNoteAPI";
|
||||
import ScheduledMaintenancePublicNoteAPI from "Common/Server/API/ScheduledMaintenancePublicNoteAPI";
|
||||
import IncidentAPI from "Common/Server/API/IncidentAPI";
|
||||
import ScheduledMaintenanceAPI from "Common/Server/API/ScheduledMaintenanceAPI";
|
||||
import AlertAPI from "Common/Server/API/AlertAPI";
|
||||
// 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 UserIncomingCallNumberAPI from "Common/Server/API/UserIncomingCallNumberAPI";
|
||||
import UserWhatsAppAPI from "Common/Server/API/UserWhatsAppAPI";
|
||||
import UserPushAPI from "Common/Server/API/UserPushAPI";
|
||||
import UserAPI from "Common/Server/API/UserAPI";
|
||||
@@ -56,6 +63,12 @@ import DomainService, {
|
||||
import EmailLogService, {
|
||||
Service as EmailLogServiceType,
|
||||
} from "Common/Server/Services/EmailLogService";
|
||||
import ProjectSCIMLogService, {
|
||||
Service as ProjectSCIMLogServiceType,
|
||||
} from "Common/Server/Services/ProjectSCIMLogService";
|
||||
import StatusPageSCIMLogService, {
|
||||
Service as StatusPageSCIMLogServiceType,
|
||||
} from "Common/Server/Services/StatusPageSCIMLogService";
|
||||
import TelemetryIngestionKeyService, {
|
||||
Service as TelemetryIngestionKeyServiceType,
|
||||
} from "Common/Server/Services/TelemetryIngestionKeyService";
|
||||
@@ -67,6 +80,7 @@ import AlertCustomFieldService, {
|
||||
Service as AlertCustomFieldServiceType,
|
||||
} from "Common/Server/Services/AlertCustomFieldService";
|
||||
import AlertInternalNoteAPI from "Common/Server/API/AlertInternalNoteAPI";
|
||||
import TelemetryExceptionAPI from "Common/Server/API/TelemetryExceptionAPI";
|
||||
import AlertNoteTemplateService, {
|
||||
Service as AlertNoteTemplateServiceType,
|
||||
} from "Common/Server/Services/AlertNoteTemplateService";
|
||||
@@ -81,9 +95,7 @@ import DashboardService, {
|
||||
import AlertOwnerUserService, {
|
||||
Service as AlertOwnerUserServiceType,
|
||||
} from "Common/Server/Services/AlertOwnerUserService";
|
||||
import AlertService, {
|
||||
Service as AlertServiceType,
|
||||
} from "Common/Server/Services/AlertService";
|
||||
|
||||
import AlertSeverityService, {
|
||||
Service as AlertSeverityServiceType,
|
||||
} from "Common/Server/Services/AlertSeverityService";
|
||||
@@ -94,6 +106,32 @@ import AlertStateTimelineService, {
|
||||
Service as AlertStateTimelineServiceType,
|
||||
} from "Common/Server/Services/AlertStateTimelineService";
|
||||
|
||||
// AlertEpisode Services
|
||||
import AlertEpisodeService, {
|
||||
Service as AlertEpisodeServiceType,
|
||||
} from "Common/Server/Services/AlertEpisodeService";
|
||||
import AlertEpisodeFeedService, {
|
||||
Service as AlertEpisodeFeedServiceType,
|
||||
} from "Common/Server/Services/AlertEpisodeFeedService";
|
||||
import AlertEpisodeInternalNoteService, {
|
||||
Service as AlertEpisodeInternalNoteServiceType,
|
||||
} from "Common/Server/Services/AlertEpisodeInternalNoteService";
|
||||
import AlertEpisodeMemberService, {
|
||||
Service as AlertEpisodeMemberServiceType,
|
||||
} from "Common/Server/Services/AlertEpisodeMemberService";
|
||||
import AlertEpisodeOwnerTeamService, {
|
||||
Service as AlertEpisodeOwnerTeamServiceType,
|
||||
} from "Common/Server/Services/AlertEpisodeOwnerTeamService";
|
||||
import AlertEpisodeOwnerUserService, {
|
||||
Service as AlertEpisodeOwnerUserServiceType,
|
||||
} from "Common/Server/Services/AlertEpisodeOwnerUserService";
|
||||
import AlertEpisodeStateTimelineService, {
|
||||
Service as AlertEpisodeStateTimelineServiceType,
|
||||
} from "Common/Server/Services/AlertEpisodeStateTimelineService";
|
||||
import AlertGroupingRuleService, {
|
||||
Service as AlertGroupingRuleServiceType,
|
||||
} from "Common/Server/Services/AlertGroupingRuleService";
|
||||
|
||||
import IncidentCustomFieldService, {
|
||||
Service as IncidentCustomFieldServiceType,
|
||||
} from "Common/Server/Services/IncidentCustomFieldService";
|
||||
@@ -140,10 +178,6 @@ import LogService, {
|
||||
LogService as LogServiceType,
|
||||
} from "Common/Server/Services/LogService";
|
||||
|
||||
import CopilotActionTypePriorityService, {
|
||||
Service as CopilotActionTypePriorityServiceType,
|
||||
} from "Common/Server/Services/CopilotActionTypePriorityService";
|
||||
|
||||
import MetricService, {
|
||||
MetricService as MetricServiceType,
|
||||
} from "Common/Server/Services/MetricService";
|
||||
@@ -188,6 +222,20 @@ import OnCallDutyPolicyCustomFieldService, {
|
||||
import OnCallDutyPolicyEscalationRuleScheduleService, {
|
||||
Service as OnCallDutyPolicyEscalationRuleScheduleServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService";
|
||||
|
||||
// Incoming Call Policy
|
||||
import IncomingCallPolicyService, {
|
||||
Service as IncomingCallPolicyServiceType,
|
||||
} from "Common/Server/Services/IncomingCallPolicyService";
|
||||
import IncomingCallPolicyEscalationRuleService, {
|
||||
Service as IncomingCallPolicyEscalationRuleServiceType,
|
||||
} from "Common/Server/Services/IncomingCallPolicyEscalationRuleService";
|
||||
import IncomingCallLogService, {
|
||||
Service as IncomingCallLogServiceType,
|
||||
} from "Common/Server/Services/IncomingCallLogService";
|
||||
import IncomingCallLogItemService, {
|
||||
Service as IncomingCallLogItemServiceType,
|
||||
} from "Common/Server/Services/IncomingCallLogItemService";
|
||||
import OnCallDutyPolicyEscalationRuleService, {
|
||||
Service as OnCallDutyPolicyEscalationRuleServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyEscalationRuleService";
|
||||
@@ -221,6 +269,9 @@ import ProjectSmtpConfigService, {
|
||||
import PromoCodeService, {
|
||||
Service as PromoCodeServiceType,
|
||||
} from "Common/Server/Services/PromoCodeService";
|
||||
import CodeRepositoryService, {
|
||||
Service as CodeRepositoryServiceType,
|
||||
} from "Common/Server/Services/CodeRepositoryService";
|
||||
import ResellerService, {
|
||||
Service as ResellerServiceType,
|
||||
} from "Common/Server/Services/ResellerService";
|
||||
@@ -236,39 +287,34 @@ import ScheduledMaintenanceOwnerTeamService, {
|
||||
import ScheduledMaintenanceOwnerUserService, {
|
||||
Service as ScheduledMaintenanceOwnerUserServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenanceOwnerUserService";
|
||||
import ScheduledMaintenanceService, {
|
||||
Service as ScheduledMaintenanceServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenanceService";
|
||||
|
||||
import ScheduledMaintenanceStateService, {
|
||||
Service as ScheduledMaintenanceStateServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenanceStateService";
|
||||
import ScheduledMaintenanceStateTimelineService, {
|
||||
Service as ScheduledMaintenanceStateTimelineServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenanceStateTimelineService";
|
||||
import ServiceCatalogOwnerTeamService, {
|
||||
Service as ServiceCatalogOwnerTeamServiceType,
|
||||
} from "Common/Server/Services/ServiceCatalogOwnerTeamService";
|
||||
import ServiceCatalogOwnerUserService, {
|
||||
Service as ServiceCatalogOwnerUserServiceType,
|
||||
} from "Common/Server/Services/ServiceCatalogOwnerUserService";
|
||||
import ServiceCatalogService, {
|
||||
Service as ServiceCatalogServiceType,
|
||||
} from "Common/Server/Services/ServiceCatalogService";
|
||||
import ServiceCopilotCodeRepositoryService, {
|
||||
Service as ServiceCopilotCodeRepositoryType,
|
||||
} from "Common/Server/Services/ServiceCopilotCodeRepositoryService";
|
||||
import ServiceCatalogDependencyService, {
|
||||
Service as ServiceCatalogDependencyServiceType,
|
||||
} from "Common/Server/Services/ServiceCatalogDependencyService";
|
||||
import ServiceCatalogMonitor from "Common/Models/DatabaseModels/ServiceCatalogMonitor";
|
||||
import ServiceCatalogMonitorService, {
|
||||
Service as ServiceCatalogMonitorServiceType,
|
||||
} from "Common/Server/Services/ServiceCatalogMonitorService";
|
||||
import ServiceOwnerTeamService, {
|
||||
Service as ServiceOwnerTeamServiceType,
|
||||
} from "Common/Server/Services/ServiceOwnerTeamService";
|
||||
import ServiceOwnerUserService, {
|
||||
Service as ServiceOwnerUserServiceType,
|
||||
} from "Common/Server/Services/ServiceOwnerUserService";
|
||||
import ServiceService, {
|
||||
Service as ServiceServiceType,
|
||||
} from "Common/Server/Services/ServiceService";
|
||||
import ServiceDependencyService, {
|
||||
Service as ServiceDependencyServiceType,
|
||||
} from "Common/Server/Services/ServiceDependencyService";
|
||||
import ServiceMonitor from "Common/Models/DatabaseModels/ServiceMonitor";
|
||||
import ServiceMonitorService, {
|
||||
Service as ServiceMonitorServiceType,
|
||||
} from "Common/Server/Services/ServiceMonitorService";
|
||||
|
||||
import ServiceCatalogTelemetryService from "Common/Models/DatabaseModels/ServiceCatalogTelemetryService";
|
||||
import ServiceCatalogTelemetryServiceService, {
|
||||
Service as ServiceCatalogTelemetryServiceServiceType,
|
||||
} from "Common/Server/Services/ServiceCatalogTelemetryServiceService";
|
||||
import ServiceCodeRepository from "Common/Models/DatabaseModels/ServiceCodeRepository";
|
||||
import ServiceCodeRepositoryService, {
|
||||
Service as ServiceCodeRepositoryServiceType,
|
||||
} from "Common/Server/Services/ServiceCodeRepositoryService";
|
||||
|
||||
import ShortLinkService, {
|
||||
Service as ShortLinkServiceType,
|
||||
@@ -325,9 +371,6 @@ import TeamComplianceSettingService, {
|
||||
import TeamService, {
|
||||
Service as TeamServiceType,
|
||||
} from "Common/Server/Services/TeamService";
|
||||
import TelemetryServiceService, {
|
||||
Service as TelemetryServiceServiceType,
|
||||
} from "Common/Server/Services/TelemetryServiceService";
|
||||
import TelemetryUsageBillingService, {
|
||||
Service as TelemetryUsageBillingServiceType,
|
||||
} from "Common/Server/Services/TelemetryUsageBillingService";
|
||||
@@ -358,9 +401,22 @@ import ProbeOwnerUserService, {
|
||||
Service as ProbeOwnerUserServiceType,
|
||||
} from "Common/Server/Services/ProbeOwnerUserService";
|
||||
|
||||
import TelemetryExceptionService, {
|
||||
Service as TelemetryExceptionServiceType,
|
||||
} from "Common/Server/Services/TelemetryExceptionService";
|
||||
import AIAgentOwnerTeamService, {
|
||||
Service as AIAgentOwnerTeamServiceType,
|
||||
} from "Common/Server/Services/AIAgentOwnerTeamService";
|
||||
|
||||
import AIAgentOwnerUserService, {
|
||||
Service as AIAgentOwnerUserServiceType,
|
||||
} from "Common/Server/Services/AIAgentOwnerUserService";
|
||||
|
||||
import AIAgentTaskTelemetryException from "Common/Models/DatabaseModels/AIAgentTaskTelemetryException";
|
||||
import AIAgentTaskTelemetryExceptionService, {
|
||||
Service as AIAgentTaskTelemetryExceptionServiceType,
|
||||
} from "Common/Server/Services/AIAgentTaskTelemetryExceptionService";
|
||||
|
||||
import LlmLogService, {
|
||||
Service as LlmLogServiceType,
|
||||
} from "Common/Server/Services/LlmLogService";
|
||||
|
||||
import ExceptionInstanceService, {
|
||||
ExceptionInstanceService as ExceptionInstanceServiceType,
|
||||
@@ -379,10 +435,11 @@ import PushNotificationLog from "Common/Models/DatabaseModels/PushNotificationLo
|
||||
import WorkspaceNotificationLog from "Common/Models/DatabaseModels/WorkspaceNotificationLog";
|
||||
import Domain from "Common/Models/DatabaseModels/Domain";
|
||||
import EmailLog from "Common/Models/DatabaseModels/EmailLog";
|
||||
import ProjectSCIMLog from "Common/Models/DatabaseModels/ProjectSCIMLog";
|
||||
import StatusPageSCIMLog from "Common/Models/DatabaseModels/StatusPageSCIMLog";
|
||||
import EmailVerificationToken from "Common/Models/DatabaseModels/EmailVerificationToken";
|
||||
import Dashboard from "Common/Models/DatabaseModels/Dashboard";
|
||||
|
||||
import Alert from "Common/Models/DatabaseModels/Alert";
|
||||
import AlertCustomField from "Common/Models/DatabaseModels/AlertCustomField";
|
||||
import AlertNoteTemplate from "Common/Models/DatabaseModels/AlertNoteTemplate";
|
||||
import AlertOwnerTeam from "Common/Models/DatabaseModels/AlertOwnerTeam";
|
||||
@@ -391,6 +448,16 @@ import AlertSeverity from "Common/Models/DatabaseModels/AlertSeverity";
|
||||
import AlertState from "Common/Models/DatabaseModels/AlertState";
|
||||
import AlertStateTimeline from "Common/Models/DatabaseModels/AlertStateTimeline";
|
||||
|
||||
// AlertEpisode Models
|
||||
import AlertEpisode from "Common/Models/DatabaseModels/AlertEpisode";
|
||||
import AlertEpisodeFeed from "Common/Models/DatabaseModels/AlertEpisodeFeed";
|
||||
import AlertEpisodeInternalNote from "Common/Models/DatabaseModels/AlertEpisodeInternalNote";
|
||||
import AlertEpisodeMember from "Common/Models/DatabaseModels/AlertEpisodeMember";
|
||||
import AlertEpisodeOwnerTeam from "Common/Models/DatabaseModels/AlertEpisodeOwnerTeam";
|
||||
import AlertEpisodeOwnerUser from "Common/Models/DatabaseModels/AlertEpisodeOwnerUser";
|
||||
import AlertEpisodeStateTimeline from "Common/Models/DatabaseModels/AlertEpisodeStateTimeline";
|
||||
import AlertGroupingRule from "Common/Models/DatabaseModels/AlertGroupingRule";
|
||||
|
||||
import IncidentCustomField from "Common/Models/DatabaseModels/IncidentCustomField";
|
||||
import IncidentNoteTemplate from "Common/Models/DatabaseModels/IncidentNoteTemplate";
|
||||
import IncidentPostmortemTemplate from "Common/Models/DatabaseModels/IncidentPostmortemTemplate";
|
||||
@@ -416,6 +483,12 @@ import MonitorStatus from "Common/Models/DatabaseModels/MonitorStatus";
|
||||
import MonitorTimelineStatus from "Common/Models/DatabaseModels/MonitorStatusTimeline";
|
||||
import OnCallDutyPolicyCustomField from "Common/Models/DatabaseModels/OnCallDutyPolicyCustomField";
|
||||
import OnCallDutyPolicyEscalationRule from "Common/Models/DatabaseModels/OnCallDutyPolicyEscalationRule";
|
||||
|
||||
// Incoming Call Policy Models
|
||||
import IncomingCallPolicy from "Common/Models/DatabaseModels/IncomingCallPolicy";
|
||||
import IncomingCallPolicyEscalationRule from "Common/Models/DatabaseModels/IncomingCallPolicyEscalationRule";
|
||||
import IncomingCallLog from "Common/Models/DatabaseModels/IncomingCallLog";
|
||||
import IncomingCallLogItem from "Common/Models/DatabaseModels/IncomingCallLogItem";
|
||||
import OnCallDutyPolicyEscalationRuleSchedule from "Common/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule";
|
||||
import OnCallDutyPolicyEscalationRuleTeam from "Common/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam";
|
||||
import OnCallDutyPolicyEscalationRuleUser from "Common/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser";
|
||||
@@ -427,18 +500,17 @@ import OnCallDutyPolicyScheduleLayerUser from "Common/Models/DatabaseModels/OnCa
|
||||
import ProjectCallSMSConfig from "Common/Models/DatabaseModels/ProjectCallSMSConfig";
|
||||
import ProjectSmtpConfig from "Common/Models/DatabaseModels/ProjectSmtpConfig";
|
||||
import PromoCode from "Common/Models/DatabaseModels/PromoCode";
|
||||
import CodeRepository from "Common/Models/DatabaseModels/CodeRepository";
|
||||
import Reseller from "Common/Models/DatabaseModels/Reseller";
|
||||
import ScheduledMaintenance from "Common/Models/DatabaseModels/ScheduledMaintenance";
|
||||
import ScheduledMaintenanceCustomField from "Common/Models/DatabaseModels/ScheduledMaintenanceCustomField";
|
||||
import ScheduledMaintenanceNoteTemplate from "Common/Models/DatabaseModels/ScheduledMaintenanceNoteTemplate";
|
||||
import ScheduledMaintenanceOwnerTeam from "Common/Models/DatabaseModels/ScheduledMaintenanceOwnerTeam";
|
||||
import ScheduledMaintenanceOwnerUser from "Common/Models/DatabaseModels/ScheduledMaintenanceOwnerUser";
|
||||
import ScheduledMaintenanceState from "Common/Models/DatabaseModels/ScheduledMaintenanceState";
|
||||
import ScheduledMaintenanceStateTimeline from "Common/Models/DatabaseModels/ScheduledMaintenanceStateTimeline";
|
||||
import ServiceCatalog from "Common/Models/DatabaseModels/ServiceCatalog";
|
||||
import ServiceCatalogOwnerTeam from "Common/Models/DatabaseModels/ServiceCatalogOwnerTeam";
|
||||
import ServiceCatalogOwnerUser from "Common/Models/DatabaseModels/ServiceCatalogOwnerUser";
|
||||
import ServiceCopilotCodeRepository from "Common/Models/DatabaseModels/ServiceCopilotCodeRepository";
|
||||
import Service from "Common/Models/DatabaseModels/Service";
|
||||
import ServiceOwnerTeam from "Common/Models/DatabaseModels/ServiceOwnerTeam";
|
||||
import ServiceOwnerUser from "Common/Models/DatabaseModels/ServiceOwnerUser";
|
||||
import ShortLink from "Common/Models/DatabaseModels/ShortLink";
|
||||
import SmsLog from "Common/Models/DatabaseModels/SmsLog";
|
||||
// Custom Fields API
|
||||
@@ -457,7 +529,6 @@ 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 UserNotificationRule from "Common/Models/DatabaseModels/UserNotificationRule";
|
||||
import UserNotificationSetting from "Common/Models/DatabaseModels/UserNotificationSetting";
|
||||
@@ -467,10 +538,11 @@ import WorkflowLog from "Common/Models/DatabaseModels/WorkflowLog";
|
||||
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 AIAgentOwnerTeam from "Common/Models/DatabaseModels/AIAgentOwnerTeam";
|
||||
import AIAgentOwnerUser from "Common/Models/DatabaseModels/AIAgentOwnerUser";
|
||||
import LlmLog from "Common/Models/DatabaseModels/LlmLog";
|
||||
import ServiceDependency from "Common/Models/DatabaseModels/ServiceDependency";
|
||||
import ExceptionInstance from "Common/Models/AnalyticsModels/ExceptionInstance";
|
||||
import TelemetyException from "Common/Models/DatabaseModels/TelemetryException";
|
||||
import CopilotActionTypePriority from "Common/Models/DatabaseModels/CopilotActionTypePriority";
|
||||
import WorkspaceNotificationLogService, {
|
||||
Service as WorkspaceNotificationLogServiceType,
|
||||
} from "Common/Server/Services/WorkspaceNotificationLogService";
|
||||
@@ -508,6 +580,7 @@ import ScheduledMaintenanceFeedService, {
|
||||
|
||||
import SlackAPI from "Common/Server/API/SlackAPI";
|
||||
import MicrosoftTeamsAPI from "Common/Server/API/MicrosoftTeamsAPI";
|
||||
import GitHubAPI from "Common/Server/API/GitHubAPI";
|
||||
|
||||
import WorkspaceProjectAuthToken from "Common/Models/DatabaseModels/WorkspaceProjectAuthToken";
|
||||
import WorkspaceProjectAuthTokenService, {
|
||||
@@ -570,6 +643,18 @@ import StatusPageAnnouncementTemplateService, {
|
||||
Service as StatusPageAnnouncementTemplateServiceType,
|
||||
} from "Common/Server/Services/StatusPageAnnouncementTemplateService";
|
||||
|
||||
// status page subscriber notification templates
|
||||
import StatusPageSubscriberNotificationTemplate from "Common/Models/DatabaseModels/StatusPageSubscriberNotificationTemplate";
|
||||
import StatusPageSubscriberNotificationTemplateService, {
|
||||
Service as StatusPageSubscriberNotificationTemplateServiceType,
|
||||
} from "Common/Server/Services/StatusPageSubscriberNotificationTemplateService";
|
||||
|
||||
// status page subscriber notification template status page (linking table)
|
||||
import StatusPageSubscriberNotificationTemplateStatusPage from "Common/Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage";
|
||||
import StatusPageSubscriberNotificationTemplateStatusPageService, {
|
||||
Service as StatusPageSubscriberNotificationTemplateStatusPageServiceType,
|
||||
} from "Common/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService";
|
||||
|
||||
// ProjectSCIM
|
||||
import ProjectSCIM from "Common/Models/DatabaseModels/ProjectSCIM";
|
||||
import ProjectSCIMService, {
|
||||
@@ -644,6 +729,30 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// status page subscriber notification templates
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
StatusPageSubscriberNotificationTemplate,
|
||||
StatusPageSubscriberNotificationTemplateServiceType
|
||||
>(
|
||||
StatusPageSubscriberNotificationTemplate,
|
||||
StatusPageSubscriberNotificationTemplateService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// status page subscriber notification template status page (linking table)
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
StatusPageSubscriberNotificationTemplateStatusPage,
|
||||
StatusPageSubscriberNotificationTemplateStatusPageServiceType
|
||||
>(
|
||||
StatusPageSubscriberNotificationTemplateStatusPage,
|
||||
StatusPageSubscriberNotificationTemplateStatusPageService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// OnCallDutyPolicyTimeLogService
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
@@ -785,10 +894,7 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<Alert, AlertServiceType>(Alert, AlertService).getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new AlertAPI().getRouter());
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
@@ -835,6 +941,74 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// AlertEpisode Routes
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AlertEpisode, AlertEpisodeServiceType>(
|
||||
AlertEpisode,
|
||||
AlertEpisodeService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AlertEpisodeFeed, AlertEpisodeFeedServiceType>(
|
||||
AlertEpisodeFeed,
|
||||
AlertEpisodeFeedService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
AlertEpisodeInternalNote,
|
||||
AlertEpisodeInternalNoteServiceType
|
||||
>(AlertEpisodeInternalNote, AlertEpisodeInternalNoteService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AlertEpisodeMember, AlertEpisodeMemberServiceType>(
|
||||
AlertEpisodeMember,
|
||||
AlertEpisodeMemberService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AlertEpisodeOwnerTeam, AlertEpisodeOwnerTeamServiceType>(
|
||||
AlertEpisodeOwnerTeam,
|
||||
AlertEpisodeOwnerTeamService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AlertEpisodeOwnerUser, AlertEpisodeOwnerUserServiceType>(
|
||||
AlertEpisodeOwnerUser,
|
||||
AlertEpisodeOwnerUserService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
AlertEpisodeStateTimeline,
|
||||
AlertEpisodeStateTimelineServiceType
|
||||
>(
|
||||
AlertEpisodeStateTimeline,
|
||||
AlertEpisodeStateTimelineService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AlertGroupingRule, AlertGroupingRuleServiceType>(
|
||||
AlertGroupingRule,
|
||||
AlertGroupingRuleService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAnalyticsAPI<ExceptionInstance, ExceptionInstanceServiceType>(
|
||||
@@ -845,10 +1019,7 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<TelemetyException, TelemetryExceptionServiceType>(
|
||||
TelemetyException,
|
||||
TelemetryExceptionService,
|
||||
).getRouter(),
|
||||
new TelemetryExceptionAPI().getRouter(),
|
||||
);
|
||||
|
||||
// scheduled maintenance template
|
||||
@@ -890,17 +1061,6 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new BaseAnalyticsAPI<Log, LogServiceType>(Log, LogService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
CopilotActionTypePriority,
|
||||
CopilotActionTypePriorityServiceType
|
||||
>(
|
||||
CopilotActionTypePriority,
|
||||
CopilotActionTypePriorityService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<Dashboard, DashboardServiceType>(
|
||||
@@ -951,10 +1111,10 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
ServiceCatalogDependency,
|
||||
ServiceCatalogDependencyServiceType
|
||||
>(ServiceCatalogDependency, ServiceCatalogDependencyService).getRouter(),
|
||||
new BaseAPI<ServiceDependency, ServiceDependencyServiceType>(
|
||||
ServiceDependency,
|
||||
ServiceDependencyService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
@@ -970,20 +1130,17 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ServiceCatalogMonitor, ServiceCatalogMonitorServiceType>(
|
||||
ServiceCatalogMonitor,
|
||||
ServiceCatalogMonitorService,
|
||||
new BaseAPI<ServiceMonitor, ServiceMonitorServiceType>(
|
||||
ServiceMonitor,
|
||||
ServiceMonitorService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
ServiceCatalogTelemetryService,
|
||||
ServiceCatalogTelemetryServiceServiceType
|
||||
>(
|
||||
ServiceCatalogTelemetryService,
|
||||
ServiceCatalogTelemetryServiceService,
|
||||
new BaseAPI<ServiceCodeRepository, ServiceCodeRepositoryServiceType>(
|
||||
ServiceCodeRepository,
|
||||
ServiceCodeRepositoryService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
@@ -1029,17 +1186,6 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new BaseAPI<Team, TeamServiceType>(Team, TeamService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
ServiceCopilotCodeRepository,
|
||||
ServiceCopilotCodeRepositoryType
|
||||
>(
|
||||
ServiceCopilotCodeRepository,
|
||||
ServiceCopilotCodeRepositoryService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<MonitorGroupOwnerUser, MonitorGroupOwnerUserServiceType>(
|
||||
@@ -1050,25 +1196,25 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ServiceCatalog, ServiceCatalogServiceType>(
|
||||
ServiceCatalog,
|
||||
ServiceCatalogService,
|
||||
new BaseAPI<Service, ServiceServiceType>(
|
||||
Service,
|
||||
ServiceService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ServiceCatalogOwnerTeam, ServiceCatalogOwnerTeamServiceType>(
|
||||
ServiceCatalogOwnerTeam,
|
||||
ServiceCatalogOwnerTeamService,
|
||||
new BaseAPI<ServiceOwnerTeam, ServiceOwnerTeamServiceType>(
|
||||
ServiceOwnerTeam,
|
||||
ServiceOwnerTeamService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ServiceCatalogOwnerUser, ServiceCatalogOwnerUserServiceType>(
|
||||
ServiceCatalogOwnerUser,
|
||||
ServiceCatalogOwnerUserService,
|
||||
new BaseAPI<ServiceOwnerUser, ServiceOwnerUserServiceType>(
|
||||
ServiceOwnerUser,
|
||||
ServiceOwnerUserService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
@@ -1201,14 +1347,6 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<TelemetryService, TelemetryServiceServiceType>(
|
||||
TelemetryService,
|
||||
TelemetryServiceService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<WorkflowVariable, WorkflowVariableServiceType>(
|
||||
@@ -1269,10 +1407,7 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ScheduledMaintenance, ScheduledMaintenanceServiceType>(
|
||||
ScheduledMaintenance,
|
||||
ScheduledMaintenanceService,
|
||||
).getRouter(),
|
||||
new ScheduledMaintenanceAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
@@ -1543,6 +1678,22 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ProjectSCIMLog, ProjectSCIMLogServiceType>(
|
||||
ProjectSCIMLog,
|
||||
ProjectSCIMLogService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<StatusPageSCIMLog, StatusPageSCIMLogServiceType>(
|
||||
StatusPageSCIMLog,
|
||||
StatusPageSCIMLogService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<Reseller, ResellerServiceType>(
|
||||
@@ -1587,6 +1738,45 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new OnCallDutyPolicyAPI().getRouter(),
|
||||
);
|
||||
|
||||
// IncomingCallPolicy
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncomingCallPolicy, IncomingCallPolicyServiceType>(
|
||||
IncomingCallPolicy,
|
||||
IncomingCallPolicyService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// IncomingCallPolicyEscalationRule
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncomingCallPolicyEscalationRule,
|
||||
IncomingCallPolicyEscalationRuleServiceType
|
||||
>(
|
||||
IncomingCallPolicyEscalationRule,
|
||||
IncomingCallPolicyEscalationRuleService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// IncomingCallLog
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncomingCallLog, IncomingCallLogServiceType>(
|
||||
IncomingCallLog,
|
||||
IncomingCallLogService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// IncomingCallLogItem
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncomingCallLogItem, IncomingCallLogItemServiceType>(
|
||||
IncomingCallLogItem,
|
||||
IncomingCallLogItemService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// TeamComplianceAPI
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
@@ -1625,26 +1815,12 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new MicrosoftTeamsAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new GitHubAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new GlobalConfigAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new CopilotCodeRepositoryAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new CopilotActionAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new CopilotPullRequestAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new UserNotificationLogTimelineAPI().getRouter(),
|
||||
@@ -1660,12 +1836,79 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserEmailAPI().getRouter());
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserSMSAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new UserIncomingCallNumberAPI().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());
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new AIAgentAPI().getRouter());
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AIAgentOwnerUser, AIAgentOwnerUserServiceType>(
|
||||
AIAgentOwnerUser,
|
||||
AIAgentOwnerUserService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AIAgentOwnerTeam, AIAgentOwnerTeamServiceType>(
|
||||
AIAgentOwnerTeam,
|
||||
AIAgentOwnerTeamService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// AI Agent Task
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new AIAgentTaskAPI().getRouter(),
|
||||
);
|
||||
|
||||
// AI Agent Task Log
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new AIAgentTaskLogAPI().getRouter(),
|
||||
);
|
||||
|
||||
// AI Agent Task Pull Request
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new AIAgentTaskPullRequestAPI().getRouter(),
|
||||
);
|
||||
|
||||
// AI Agent Data API (for AI Agent to fetch data)
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new AIAgentDataAPI().getRouter(),
|
||||
);
|
||||
|
||||
// AI Agent Task Telemetry Exception (linking table)
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
AIAgentTaskTelemetryException,
|
||||
AIAgentTaskTelemetryExceptionServiceType
|
||||
>(
|
||||
AIAgentTaskTelemetryException,
|
||||
AIAgentTaskTelemetryExceptionService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new LlmProviderAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<LlmLog, LlmLogServiceType>(LlmLog, LlmLogService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
@@ -1769,6 +2012,15 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// Code Repository
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<CodeRepository, CodeRepositoryServiceType>(
|
||||
CodeRepository,
|
||||
CodeRepositoryService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
@@ -1817,6 +2069,8 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, NotificationAPI);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, AIBillingAPI);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, TelemetryAPI);
|
||||
|
||||
//attach api's
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ import ObjectID from "Common/Types/ObjectID";
|
||||
import DatabaseConfig from "Common/Server/DatabaseConfig";
|
||||
import { EncryptionSecret } from "Common/Server/EnvironmentConfig";
|
||||
import MailService from "Common/Server/Services/MailService";
|
||||
import ProjectSMTPConfigService from "Common/Server/Services/ProjectSmtpConfigService";
|
||||
import StatusPagePrivateUserService from "Common/Server/Services/StatusPagePrivateUserService";
|
||||
import StatusPageService from "Common/Server/Services/StatusPageService";
|
||||
import StatusPagePrivateUserSessionService, {
|
||||
@@ -468,6 +469,16 @@ router.post(
|
||||
logoFileId: true,
|
||||
requireSsoForLogin: true,
|
||||
projectId: true,
|
||||
smtpConfig: {
|
||||
_id: true,
|
||||
hostname: true,
|
||||
port: true,
|
||||
username: true,
|
||||
password: true,
|
||||
fromEmail: true,
|
||||
fromName: true,
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -547,6 +558,9 @@ router.post(
|
||||
},
|
||||
{
|
||||
projectId: statusPage.projectId!,
|
||||
mailServer: ProjectSMTPConfigService.toEmailServer(
|
||||
statusPage.smtpConfig,
|
||||
),
|
||||
statusPageId: statusPage.id!,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
@@ -632,6 +646,16 @@ router.post(
|
||||
logoFileId: true,
|
||||
requireSsoForLogin: true,
|
||||
projectId: true,
|
||||
smtpConfig: {
|
||||
_id: true,
|
||||
hostname: true,
|
||||
port: true,
|
||||
username: true,
|
||||
password: true,
|
||||
fromEmail: true,
|
||||
fromName: true,
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -689,6 +713,9 @@ router.post(
|
||||
},
|
||||
{
|
||||
projectId: statusPage.projectId!,
|
||||
mailServer: ProjectSMTPConfigService.toEmailServer(
|
||||
statusPage.smtpConfig,
|
||||
),
|
||||
statusPageId: statusPage.id!,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
262
App/FeatureSet/Identity/Utils/SCIMLogger.ts
Normal file
262
App/FeatureSet/Identity/Utils/SCIMLogger.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import ProjectSCIMLog from "Common/Models/DatabaseModels/ProjectSCIMLog";
|
||||
import StatusPageSCIMLog from "Common/Models/DatabaseModels/StatusPageSCIMLog";
|
||||
import ProjectSCIMLogService from "Common/Server/Services/ProjectSCIMLogService";
|
||||
import StatusPageSCIMLogService from "Common/Server/Services/StatusPageSCIMLogService";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import SCIMLogStatus from "Common/Types/SCIM/SCIMLogStatus";
|
||||
import { JSONObject, JSONValue, JSONArray } from "Common/Types/JSON";
|
||||
|
||||
export interface ProjectSCIMLogData {
|
||||
projectId: ObjectID;
|
||||
projectScimId: ObjectID;
|
||||
operationType: string;
|
||||
status: SCIMLogStatus;
|
||||
statusMessage?: string | undefined;
|
||||
httpMethod?: string | undefined;
|
||||
requestPath?: string | undefined;
|
||||
httpStatusCode?: number | undefined;
|
||||
affectedUserEmail?: string | undefined;
|
||||
affectedGroupName?: string | undefined;
|
||||
requestBody?: JSONObject | undefined;
|
||||
responseBody?: JSONObject | undefined;
|
||||
queryParams?: JSONObject | undefined;
|
||||
steps?: string[] | undefined;
|
||||
userInfo?: JSONObject | undefined;
|
||||
groupInfo?: JSONObject | undefined;
|
||||
additionalContext?: JSONObject | undefined;
|
||||
}
|
||||
|
||||
export interface StatusPageSCIMLogData {
|
||||
projectId: ObjectID;
|
||||
statusPageId: ObjectID;
|
||||
statusPageScimId: ObjectID;
|
||||
operationType: string;
|
||||
status: SCIMLogStatus;
|
||||
statusMessage?: string | undefined;
|
||||
httpMethod?: string | undefined;
|
||||
requestPath?: string | undefined;
|
||||
httpStatusCode?: number | undefined;
|
||||
affectedUserEmail?: string | undefined;
|
||||
requestBody?: JSONObject | undefined;
|
||||
responseBody?: JSONObject | undefined;
|
||||
queryParams?: JSONObject | undefined;
|
||||
steps?: string[] | undefined;
|
||||
userInfo?: JSONObject | undefined;
|
||||
additionalContext?: JSONObject | undefined;
|
||||
}
|
||||
|
||||
const sanitizeSensitiveData: (
|
||||
data: JSONObject | undefined,
|
||||
) => JSONObject | undefined = (
|
||||
data: JSONObject | undefined,
|
||||
): JSONObject | undefined => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sanitized: JSONObject = { ...data };
|
||||
const sensitiveKeys: string[] = [
|
||||
"password",
|
||||
"bearerToken",
|
||||
"bearer_token",
|
||||
"authorization",
|
||||
"Authorization",
|
||||
"token",
|
||||
"secret",
|
||||
"apiKey",
|
||||
"api_key",
|
||||
];
|
||||
|
||||
const sanitizeRecursive: (obj: JSONObject) => JSONObject = (
|
||||
obj: JSONObject,
|
||||
): JSONObject => {
|
||||
const result: JSONObject = {};
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const value: JSONValue = obj[key];
|
||||
if (
|
||||
sensitiveKeys.some((k: string) => {
|
||||
return key.toLowerCase().includes(k.toLowerCase());
|
||||
})
|
||||
) {
|
||||
result[key] = "[REDACTED]";
|
||||
} else if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!Array.isArray(value)
|
||||
) {
|
||||
result[key] = sanitizeRecursive(value as JSONObject);
|
||||
} else if (Array.isArray(value)) {
|
||||
result[key] = (value as JSONArray).map((item: JSONValue) => {
|
||||
if (
|
||||
typeof item === "object" &&
|
||||
item !== null &&
|
||||
!Array.isArray(item)
|
||||
) {
|
||||
return sanitizeRecursive(item as JSONObject);
|
||||
}
|
||||
return item;
|
||||
}) as JSONArray;
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return sanitizeRecursive(sanitized);
|
||||
};
|
||||
|
||||
export interface LogBodyDetails {
|
||||
requestBody?: JSONObject | undefined;
|
||||
responseBody?: JSONObject | undefined;
|
||||
timestamp: Date;
|
||||
queryParams?: JSONObject | undefined;
|
||||
steps?: string[] | undefined;
|
||||
userInfo?: JSONObject | undefined;
|
||||
groupInfo?: JSONObject | undefined;
|
||||
additionalContext?: JSONObject | undefined;
|
||||
}
|
||||
|
||||
const buildLogBody: (data: LogBodyDetails) => string = (
|
||||
data: LogBodyDetails,
|
||||
): string => {
|
||||
const logBody: JSONObject = {
|
||||
timestamp: data.timestamp.toISOString(),
|
||||
executedAt: data.timestamp.toISOString(),
|
||||
};
|
||||
|
||||
if (data.queryParams && Object.keys(data.queryParams).length > 0) {
|
||||
logBody["queryParameters"] = data.queryParams;
|
||||
}
|
||||
|
||||
if (data.requestBody) {
|
||||
logBody["request"] = sanitizeSensitiveData(data.requestBody);
|
||||
}
|
||||
|
||||
if (data.responseBody) {
|
||||
logBody["response"] = sanitizeSensitiveData(data.responseBody);
|
||||
}
|
||||
|
||||
if (data.steps && data.steps.length > 0) {
|
||||
logBody["executionSteps"] = data.steps;
|
||||
}
|
||||
|
||||
if (data.userInfo) {
|
||||
logBody["userDetails"] = sanitizeSensitiveData(data.userInfo);
|
||||
}
|
||||
|
||||
if (data.groupInfo) {
|
||||
logBody["groupDetails"] = sanitizeSensitiveData(data.groupInfo);
|
||||
}
|
||||
|
||||
if (data.additionalContext) {
|
||||
logBody["additionalContext"] = sanitizeSensitiveData(
|
||||
data.additionalContext,
|
||||
);
|
||||
}
|
||||
|
||||
return JSON.stringify(logBody, null, 2);
|
||||
};
|
||||
|
||||
export const createProjectSCIMLog: (
|
||||
data: ProjectSCIMLogData,
|
||||
) => Promise<void> = async (data: ProjectSCIMLogData): Promise<void> => {
|
||||
try {
|
||||
const log: ProjectSCIMLog = new ProjectSCIMLog();
|
||||
log.projectId = data.projectId;
|
||||
log.projectScimId = data.projectScimId;
|
||||
log.operationType = data.operationType;
|
||||
log.status = data.status;
|
||||
if (data.statusMessage !== undefined) {
|
||||
log.statusMessage = data.statusMessage;
|
||||
}
|
||||
if (data.httpMethod !== undefined) {
|
||||
log.httpMethod = data.httpMethod;
|
||||
}
|
||||
if (data.requestPath !== undefined) {
|
||||
log.requestPath = data.requestPath;
|
||||
}
|
||||
if (data.httpStatusCode !== undefined) {
|
||||
log.httpStatusCode = data.httpStatusCode;
|
||||
}
|
||||
if (data.affectedUserEmail !== undefined) {
|
||||
log.affectedUserEmail = data.affectedUserEmail;
|
||||
}
|
||||
if (data.affectedGroupName !== undefined) {
|
||||
log.affectedGroupName = data.affectedGroupName;
|
||||
}
|
||||
log.logBody = buildLogBody({
|
||||
requestBody: data.requestBody,
|
||||
responseBody: data.responseBody,
|
||||
timestamp: new Date(),
|
||||
queryParams: data.queryParams,
|
||||
steps: data.steps,
|
||||
userInfo: data.userInfo,
|
||||
groupInfo: data.groupInfo,
|
||||
additionalContext: data.additionalContext,
|
||||
});
|
||||
|
||||
await ProjectSCIMLogService.create({
|
||||
data: log,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
} catch (err) {
|
||||
// Log errors silently to not affect SCIM operations
|
||||
logger.error("Failed to create Project SCIM log entry:");
|
||||
logger.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const createStatusPageSCIMLog: (
|
||||
data: StatusPageSCIMLogData,
|
||||
) => Promise<void> = async (data: StatusPageSCIMLogData): Promise<void> => {
|
||||
try {
|
||||
const log: StatusPageSCIMLog = new StatusPageSCIMLog();
|
||||
log.projectId = data.projectId;
|
||||
log.statusPageId = data.statusPageId;
|
||||
log.statusPageScimId = data.statusPageScimId;
|
||||
log.operationType = data.operationType;
|
||||
log.status = data.status;
|
||||
if (data.statusMessage !== undefined) {
|
||||
log.statusMessage = data.statusMessage;
|
||||
}
|
||||
if (data.httpMethod !== undefined) {
|
||||
log.httpMethod = data.httpMethod;
|
||||
}
|
||||
if (data.requestPath !== undefined) {
|
||||
log.requestPath = data.requestPath;
|
||||
}
|
||||
if (data.httpStatusCode !== undefined) {
|
||||
log.httpStatusCode = data.httpStatusCode;
|
||||
}
|
||||
if (data.affectedUserEmail !== undefined) {
|
||||
log.affectedUserEmail = data.affectedUserEmail;
|
||||
}
|
||||
log.logBody = buildLogBody({
|
||||
requestBody: data.requestBody,
|
||||
responseBody: data.responseBody,
|
||||
timestamp: new Date(),
|
||||
queryParams: data.queryParams,
|
||||
steps: data.steps,
|
||||
userInfo: data.userInfo,
|
||||
additionalContext: data.additionalContext,
|
||||
});
|
||||
|
||||
await StatusPageSCIMLogService.create({
|
||||
data: log,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
} catch (err) {
|
||||
// Log errors silently to not affect SCIM operations
|
||||
logger.error("Failed to create Status Page SCIM log entry:");
|
||||
logger.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
createProjectSCIMLog,
|
||||
createStatusPageSCIMLog,
|
||||
};
|
||||
@@ -4,6 +4,23 @@ import { JSONObject } from "Common/Types/JSON";
|
||||
import Email from "Common/Types/Email";
|
||||
import Name from "Common/Types/Name";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Exception from "Common/Types/Exception/Exception";
|
||||
|
||||
/**
|
||||
* SCIM Error types as defined in RFC 7644
|
||||
*/
|
||||
export enum SCIMErrorType {
|
||||
InvalidFilter = "invalidFilter",
|
||||
TooMany = "tooMany",
|
||||
Uniqueness = "uniqueness",
|
||||
Mutability = "mutability",
|
||||
InvalidSyntax = "invalidSyntax",
|
||||
InvalidPath = "invalidPath",
|
||||
NoTarget = "noTarget",
|
||||
InvalidValue = "invalidValue",
|
||||
InvalidVers = "invalidVers",
|
||||
Sensitive = "sensitive",
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared SCIM utility functions for both Project SCIM and Status Page SCIM
|
||||
@@ -174,7 +191,7 @@ export const generateServiceProviderConfig: (
|
||||
bulk: {
|
||||
supported: true,
|
||||
maxOperations: 1000,
|
||||
maxPayloadSize: 1048576,
|
||||
maxPayloadSize: 1048576, // 1MB
|
||||
},
|
||||
filter: {
|
||||
supported: true,
|
||||
@@ -263,3 +280,512 @@ export const parseSCIMQueryParams: (req: ExpressRequest) => {
|
||||
|
||||
return { startIndex, count };
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate SCIM-compliant error response as per RFC 7644
|
||||
*/
|
||||
export const generateSCIMErrorResponse: (
|
||||
status: number,
|
||||
detail: string,
|
||||
scimType?: SCIMErrorType,
|
||||
) => JSONObject = (
|
||||
status: number,
|
||||
detail: string,
|
||||
scimType?: SCIMErrorType,
|
||||
): JSONObject => {
|
||||
const errorResponse: JSONObject = {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
status: status.toString(),
|
||||
detail: detail,
|
||||
};
|
||||
|
||||
if (scimType) {
|
||||
errorResponse["scimType"] = scimType;
|
||||
}
|
||||
|
||||
return errorResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate SCIM Schemas endpoint response
|
||||
*/
|
||||
export const generateSchemasResponse: (
|
||||
req: ExpressRequest,
|
||||
scimId: string,
|
||||
scimType: "project" | "status-page",
|
||||
) => JSONObject = (
|
||||
req: ExpressRequest,
|
||||
scimId: string,
|
||||
scimType: "project" | "status-page",
|
||||
): JSONObject => {
|
||||
const baseUrl: string = `${req.protocol}://${req.get("host")}`;
|
||||
const endpointPath: string =
|
||||
scimType === "project"
|
||||
? `/scim/v2/${scimId}`
|
||||
: `/status-page-scim/v2/${scimId}`;
|
||||
|
||||
const schemas: JSONObject[] = [
|
||||
{
|
||||
id: "urn:ietf:params:scim:schemas:core:2.0:User",
|
||||
name: "User",
|
||||
description: "User Schema",
|
||||
attributes: [
|
||||
{
|
||||
name: "userName",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description:
|
||||
"Unique identifier for the User, typically email address",
|
||||
required: true,
|
||||
caseExact: false,
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
uniqueness: "server",
|
||||
},
|
||||
{
|
||||
name: "name",
|
||||
type: "complex",
|
||||
multiValued: false,
|
||||
description: "The components of the user's name",
|
||||
required: false,
|
||||
subAttributes: [
|
||||
{
|
||||
name: "formatted",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description: "The full name",
|
||||
required: false,
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
{
|
||||
name: "familyName",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description: "The family name or last name",
|
||||
required: false,
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
{
|
||||
name: "givenName",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description: "The given name or first name",
|
||||
required: false,
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
],
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
{
|
||||
name: "displayName",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description: "The name of the User suitable for display",
|
||||
required: false,
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
{
|
||||
name: "emails",
|
||||
type: "complex",
|
||||
multiValued: true,
|
||||
description: "Email addresses for the user",
|
||||
required: false,
|
||||
subAttributes: [
|
||||
{
|
||||
name: "value",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description: "Email address value",
|
||||
required: false,
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description: "Type of email (work, home, other)",
|
||||
required: false,
|
||||
canonicalValues: ["work", "home", "other"],
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
{
|
||||
name: "primary",
|
||||
type: "boolean",
|
||||
multiValued: false,
|
||||
description: "Indicates if this is the primary email",
|
||||
required: false,
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
],
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
{
|
||||
name: "active",
|
||||
type: "boolean",
|
||||
multiValued: false,
|
||||
description: "Indicates whether the user is active",
|
||||
required: false,
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
resourceType: "Schema",
|
||||
location: `${baseUrl}${endpointPath}/Schemas/urn:ietf:params:scim:schemas:core:2.0:User`,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Add Group schema only for project SCIM
|
||||
if (scimType === "project") {
|
||||
schemas.push({
|
||||
id: "urn:ietf:params:scim:schemas:core:2.0:Group",
|
||||
name: "Group",
|
||||
description: "Group Schema (Teams in OneUptime)",
|
||||
attributes: [
|
||||
{
|
||||
name: "displayName",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description: "Human-readable name for the Group/Team",
|
||||
required: true,
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
uniqueness: "server",
|
||||
},
|
||||
{
|
||||
name: "members",
|
||||
type: "complex",
|
||||
multiValued: true,
|
||||
description: "A list of members of the Group",
|
||||
required: false,
|
||||
subAttributes: [
|
||||
{
|
||||
name: "value",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description: "Identifier of the member",
|
||||
required: false,
|
||||
mutability: "immutable",
|
||||
returned: "default",
|
||||
},
|
||||
{
|
||||
name: "$ref",
|
||||
type: "reference",
|
||||
referenceTypes: ["User"],
|
||||
multiValued: false,
|
||||
description: "URI of the member resource",
|
||||
required: false,
|
||||
mutability: "immutable",
|
||||
returned: "default",
|
||||
},
|
||||
{
|
||||
name: "display",
|
||||
type: "string",
|
||||
multiValued: false,
|
||||
description: "Display name of the member",
|
||||
required: false,
|
||||
mutability: "immutable",
|
||||
returned: "default",
|
||||
},
|
||||
],
|
||||
mutability: "readWrite",
|
||||
returned: "default",
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
resourceType: "Schema",
|
||||
location: `${baseUrl}${endpointPath}/Schemas/urn:ietf:params:scim:schemas:core:2.0:Group`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: schemas.length,
|
||||
itemsPerPage: schemas.length,
|
||||
startIndex: 1,
|
||||
Resources: schemas,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate SCIM ResourceTypes endpoint response
|
||||
*/
|
||||
export const generateResourceTypesResponse: (
|
||||
req: ExpressRequest,
|
||||
scimId: string,
|
||||
scimType: "project" | "status-page",
|
||||
) => JSONObject = (
|
||||
req: ExpressRequest,
|
||||
scimId: string,
|
||||
scimType: "project" | "status-page",
|
||||
): JSONObject => {
|
||||
const baseUrl: string = `${req.protocol}://${req.get("host")}`;
|
||||
const endpointPath: string =
|
||||
scimType === "project"
|
||||
? `/scim/v2/${scimId}`
|
||||
: `/status-page-scim/v2/${scimId}`;
|
||||
|
||||
const resourceTypes: JSONObject[] = [
|
||||
{
|
||||
schemas: ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"],
|
||||
id: "User",
|
||||
name: "User",
|
||||
endpoint: "/Users",
|
||||
description: "User Account",
|
||||
schema: "urn:ietf:params:scim:schemas:core:2.0:User",
|
||||
schemaExtensions: [],
|
||||
meta: {
|
||||
resourceType: "ResourceType",
|
||||
location: `${baseUrl}${endpointPath}/ResourceTypes/User`,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Add Group resource type only for project SCIM
|
||||
if (scimType === "project") {
|
||||
resourceTypes.push({
|
||||
schemas: ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"],
|
||||
id: "Group",
|
||||
name: "Group",
|
||||
endpoint: "/Groups",
|
||||
description: "Group (Team in OneUptime)",
|
||||
schema: "urn:ietf:params:scim:schemas:core:2.0:Group",
|
||||
schemaExtensions: [],
|
||||
meta: {
|
||||
resourceType: "ResourceType",
|
||||
location: `${baseUrl}${endpointPath}/ResourceTypes/Group`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: resourceTypes.length,
|
||||
itemsPerPage: resourceTypes.length,
|
||||
startIndex: 1,
|
||||
Resources: resourceTypes,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Map HTTP status codes to SCIM error types
|
||||
*/
|
||||
export const getScimErrorTypeFromException: (
|
||||
err: Exception,
|
||||
) => SCIMErrorType | undefined = (
|
||||
err: Exception,
|
||||
): SCIMErrorType | undefined => {
|
||||
const errorName: string = err.constructor.name;
|
||||
|
||||
switch (errorName) {
|
||||
case "BadRequestException":
|
||||
return SCIMErrorType.InvalidValue;
|
||||
case "NotFoundException":
|
||||
return SCIMErrorType.NoTarget;
|
||||
case "NotAuthorizedException":
|
||||
return undefined; // No specific SCIM type for auth errors
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get HTTP status code from exception
|
||||
*/
|
||||
export const getHttpStatusFromException: (err: Exception) => number = (
|
||||
err: Exception,
|
||||
): number => {
|
||||
const errorName: string = err.constructor.name;
|
||||
|
||||
switch (errorName) {
|
||||
case "BadRequestException":
|
||||
return 400;
|
||||
case "NotAuthorizedException":
|
||||
return 401;
|
||||
case "PaymentRequiredException":
|
||||
return 402;
|
||||
case "NotFoundException":
|
||||
return 404;
|
||||
case "NotImplementedException":
|
||||
return 501;
|
||||
default:
|
||||
return 500;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* SCIM Bulk Operation as per RFC 7644 Section 3.7
|
||||
*/
|
||||
export interface SCIMBulkOperation {
|
||||
method: "POST" | "PUT" | "PATCH" | "DELETE";
|
||||
path: string;
|
||||
bulkId?: string;
|
||||
version?: string;
|
||||
data?: JSONObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* SCIM Bulk Operation Response
|
||||
*/
|
||||
export interface SCIMBulkOperationResponse {
|
||||
method: string;
|
||||
bulkId?: string | undefined;
|
||||
version?: string | undefined;
|
||||
location?: string | undefined;
|
||||
status: string;
|
||||
response?: JSONObject | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* SCIM Bulk Request
|
||||
*/
|
||||
export interface SCIMBulkRequest {
|
||||
schemas: string[];
|
||||
Operations: SCIMBulkOperation[];
|
||||
failOnErrors?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SCIM Bulk Response
|
||||
*/
|
||||
export const generateBulkResponse: (
|
||||
operations: SCIMBulkOperationResponse[],
|
||||
) => JSONObject = (operations: SCIMBulkOperationResponse[]): JSONObject => {
|
||||
return {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:BulkResponse"],
|
||||
Operations: operations as unknown as JSONObject[],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse path to extract resource type and ID
|
||||
* Path format: /ResourceType or /ResourceType/{id}
|
||||
*/
|
||||
export const parseBulkOperationPath: (path: string) => {
|
||||
resourceType: string;
|
||||
resourceId: string | undefined;
|
||||
} = (
|
||||
path: string,
|
||||
): { resourceType: string; resourceId: string | undefined } => {
|
||||
// Remove leading slash if present
|
||||
const cleanPath: string = path.startsWith("/") ? path.substring(1) : path;
|
||||
const parts: string[] = cleanPath.split("/");
|
||||
|
||||
return {
|
||||
resourceType: parts[0] || "",
|
||||
resourceId: parts[1],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate SCIM Bulk Request
|
||||
*/
|
||||
export const validateBulkRequest: (
|
||||
body: JSONObject,
|
||||
maxOperations: number,
|
||||
) => { valid: boolean; error?: string } = (
|
||||
body: JSONObject,
|
||||
maxOperations: number = 1000,
|
||||
): { valid: boolean; error?: string } => {
|
||||
// Check for required schema
|
||||
const schemas: string[] = body["schemas"] as string[];
|
||||
if (
|
||||
!schemas ||
|
||||
!schemas.includes("urn:ietf:params:scim:api:messages:2.0:BulkRequest")
|
||||
) {
|
||||
return {
|
||||
valid: false,
|
||||
error:
|
||||
"Invalid or missing schema. Expected urn:ietf:params:scim:api:messages:2.0:BulkRequest",
|
||||
};
|
||||
}
|
||||
|
||||
// Check for operations array
|
||||
const operations: JSONObject[] = body["Operations"] as JSONObject[];
|
||||
if (!operations || !Array.isArray(operations)) {
|
||||
return { valid: false, error: "Operations array is required" };
|
||||
}
|
||||
|
||||
// Check operation count
|
||||
if (operations.length === 0) {
|
||||
return { valid: false, error: "At least one operation is required" };
|
||||
}
|
||||
|
||||
if (operations.length > maxOperations) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Too many operations. Maximum allowed is ${maxOperations}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Validate each operation
|
||||
for (let i: number = 0; i < operations.length; i++) {
|
||||
const op: JSONObject = operations[i]!;
|
||||
const method: string = op["method"] as string;
|
||||
const path: string = op["path"] as string;
|
||||
|
||||
if (!method) {
|
||||
return { valid: false, error: `Operation ${i + 1}: method is required` };
|
||||
}
|
||||
|
||||
const validMethods: string[] = ["POST", "PUT", "PATCH", "DELETE"];
|
||||
if (!validMethods.includes(method.toUpperCase())) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Operation ${i + 1}: invalid method '${method}'. Must be one of: ${validMethods.join(", ")}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
return { valid: false, error: `Operation ${i + 1}: path is required` };
|
||||
}
|
||||
|
||||
// POST requires data
|
||||
if (method.toUpperCase() === "POST" && !op["data"]) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Operation ${i + 1}: data is required for POST operations`,
|
||||
};
|
||||
}
|
||||
|
||||
// PUT requires data
|
||||
if (method.toUpperCase() === "PUT" && !op["data"]) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Operation ${i + 1}: data is required for PUT operations`,
|
||||
};
|
||||
}
|
||||
|
||||
// PATCH typically requires data (Operations array in SCIM PATCH)
|
||||
if (method.toUpperCase() === "PATCH" && !op["data"]) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Operation ${i + 1}: data is required for PATCH operations`,
|
||||
};
|
||||
}
|
||||
|
||||
// For PUT, PATCH, DELETE - path must include resource ID
|
||||
if (["PUT", "PATCH", "DELETE"].includes(method.toUpperCase())) {
|
||||
const { resourceId } = parseBulkOperationPath(path);
|
||||
if (!resourceId) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Operation ${i + 1}: resource ID is required in path for ${method} operations`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
};
|
||||
|
||||
743
App/FeatureSet/Notification/API/IncomingCall.ts
Normal file
743
App/FeatureSet/Notification/API/IncomingCall.ts
Normal file
@@ -0,0 +1,743 @@
|
||||
import CallProviderFactory from "../Providers/CallProviderFactory";
|
||||
import { getProjectTwilioConfig } from "../Utils/TwilioConfigHelper";
|
||||
import {
|
||||
DialStatusData,
|
||||
ICallProvider,
|
||||
IncomingCallData,
|
||||
WebhookRequest,
|
||||
} from "Common/Types/Call/CallProvider";
|
||||
import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig";
|
||||
import IncomingCallStatus from "Common/Types/IncomingCall/IncomingCallStatus";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import IncomingCallPolicyService from "Common/Server/Services/IncomingCallPolicyService";
|
||||
import IncomingCallPolicyEscalationRuleService from "Common/Server/Services/IncomingCallPolicyEscalationRuleService";
|
||||
import IncomingCallLogService from "Common/Server/Services/IncomingCallLogService";
|
||||
import IncomingCallLogItemService from "Common/Server/Services/IncomingCallLogItemService";
|
||||
import OnCallDutyPolicyScheduleService from "Common/Server/Services/OnCallDutyPolicyScheduleService";
|
||||
import UserService from "Common/Server/Services/UserService";
|
||||
import UserIncomingCallNumberService from "Common/Server/Services/UserIncomingCallNumberService";
|
||||
import UserIncomingCallNumber from "Common/Models/DatabaseModels/UserIncomingCallNumber";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import IncomingCallPolicy from "Common/Models/DatabaseModels/IncomingCallPolicy";
|
||||
import IncomingCallPolicyEscalationRule from "Common/Models/DatabaseModels/IncomingCallPolicyEscalationRule";
|
||||
import IncomingCallLog from "Common/Models/DatabaseModels/IncomingCallLog";
|
||||
import IncomingCallLogItem from "Common/Models/DatabaseModels/IncomingCallLogItem";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import Phone from "Common/Types/Phone";
|
||||
import { Host, HttpProtocol } from "Common/Server/EnvironmentConfig";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
// Handle incoming voice call - single endpoint for all phone numbers
|
||||
router.post(
|
||||
"/voice",
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
// Parse the called phone number from the request body (Twilio sends this)
|
||||
const calledPhoneNumber: string = req.body["To"] || req.body["Called"];
|
||||
|
||||
if (!calledPhoneNumber) {
|
||||
logger.error("No called phone number in request");
|
||||
res.status(400).send("Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the policy by the called phone number
|
||||
const policy: IncomingCallPolicy | null =
|
||||
await IncomingCallPolicyService.findOneBy({
|
||||
query: {
|
||||
routingPhoneNumber: new Phone(calledPhoneNumber),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
projectId: true,
|
||||
projectCallSMSConfigId: true,
|
||||
isEnabled: true,
|
||||
greetingMessage: true,
|
||||
noAnswerMessage: true,
|
||||
noOneAvailableMessage: true,
|
||||
repeatPolicyIfNoOneAnswers: true,
|
||||
repeatPolicyIfNoOneAnswersTimes: true,
|
||||
routingPhoneNumber: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!policy) {
|
||||
logger.error(
|
||||
`Incoming call policy not found for phone number: ${calledPhoneNumber}`,
|
||||
);
|
||||
res.status(404).send("Policy not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Require project-level Twilio config
|
||||
if (!policy.projectCallSMSConfigId) {
|
||||
logger.error(
|
||||
`Policy ${policy.id?.toString()} does not have a project Twilio config`,
|
||||
);
|
||||
res.status(400).send("Policy not configured correctly");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get project Twilio config
|
||||
const customTwilioConfig: TwilioConfig | null =
|
||||
await getProjectTwilioConfig(policy.projectCallSMSConfigId);
|
||||
|
||||
if (!customTwilioConfig) {
|
||||
logger.error(
|
||||
`Project Twilio config not found for policy ${policy.id?.toString()}`,
|
||||
);
|
||||
res.status(400).send("Twilio configuration not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get provider with project config
|
||||
const provider: ICallProvider =
|
||||
CallProviderFactory.getProviderWithConfig(customTwilioConfig);
|
||||
|
||||
// Validate webhook signature to ensure request is from the call provider
|
||||
const signature: string =
|
||||
(req.headers["x-twilio-signature"] as string) || "";
|
||||
|
||||
// Debug logging
|
||||
logger.debug("=== Incoming Call Webhook Debug ===");
|
||||
logger.debug(`Original URL: ${req.originalUrl}`);
|
||||
logger.debug(`Base URL: ${req.baseUrl}`);
|
||||
logger.debug(`Path: ${req.path}`);
|
||||
logger.debug(`Protocol: ${req.protocol}`);
|
||||
logger.debug(`Host header: ${req.get("host")}`);
|
||||
logger.debug(`X-Forwarded-Proto: ${req.get("x-forwarded-proto")}`);
|
||||
logger.debug(`X-Forwarded-Host: ${req.get("x-forwarded-host")}`);
|
||||
logger.debug(`Twilio Signature: ${signature}`);
|
||||
logger.debug(`Environment HOST: ${Host}`);
|
||||
logger.debug(`Environment HttpProtocol: ${HttpProtocol}`);
|
||||
logger.debug("=== End Debug ===");
|
||||
|
||||
if (
|
||||
!provider.validateWebhookSignature(
|
||||
req as unknown as WebhookRequest,
|
||||
signature,
|
||||
)
|
||||
) {
|
||||
logger.error("Invalid webhook signature for incoming call");
|
||||
res.status(403).send("Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse incoming call data
|
||||
const callData: IncomingCallData = provider.parseIncomingCallWebhook(
|
||||
req as unknown as WebhookRequest,
|
||||
);
|
||||
|
||||
const policyId: string = policy.id!.toString();
|
||||
|
||||
// Create call log early so we can track all outcomes
|
||||
const callLog: IncomingCallLog = new IncomingCallLog();
|
||||
if (policy.projectId) {
|
||||
callLog.projectId = policy.projectId;
|
||||
}
|
||||
callLog.incomingCallPolicyId = new ObjectID(policyId);
|
||||
callLog.callerPhoneNumber = new Phone(callData.callerPhoneNumber);
|
||||
if (policy.routingPhoneNumber) {
|
||||
callLog.routingPhoneNumber = policy.routingPhoneNumber;
|
||||
}
|
||||
callLog.callProviderCallId = callData.callId;
|
||||
callLog.status = IncomingCallStatus.Initiated;
|
||||
callLog.startedAt = new Date();
|
||||
callLog.currentEscalationRuleOrder = 1;
|
||||
callLog.repeatCount = 0;
|
||||
|
||||
// Check if policy is enabled
|
||||
if (!policy.isEnabled) {
|
||||
callLog.status = IncomingCallStatus.Failed;
|
||||
callLog.statusMessage = "Policy is disabled";
|
||||
callLog.endedAt = new Date();
|
||||
await IncomingCallLogService.create({
|
||||
data: callLog,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
const twiml: string = provider.generateHangupResponse(
|
||||
"Sorry, this service is currently disabled.",
|
||||
);
|
||||
res.type("text/xml");
|
||||
return res.send(twiml);
|
||||
}
|
||||
|
||||
// Save the call log now that initial checks passed
|
||||
const createdCallLog: IncomingCallLog =
|
||||
await IncomingCallLogService.create({
|
||||
data: callLog,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Get the first escalation rule
|
||||
const firstRule: IncomingCallPolicyEscalationRule | null =
|
||||
await IncomingCallPolicyEscalationRuleService.findOneBy({
|
||||
query: {
|
||||
incomingCallPolicyId: new ObjectID(policyId),
|
||||
order: 1,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
escalateAfterSeconds: true,
|
||||
onCallDutyPolicyScheduleId: true,
|
||||
userId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!firstRule) {
|
||||
await IncomingCallLogService.updateOneById({
|
||||
id: createdCallLog.id!,
|
||||
data: {
|
||||
status: IncomingCallStatus.Failed,
|
||||
statusMessage: "No escalation rules configured",
|
||||
endedAt: new Date(),
|
||||
},
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
const twiml: string = provider.generateHangupResponse(
|
||||
policy.noOneAvailableMessage ||
|
||||
"We're sorry, but no on-call engineer is currently available.",
|
||||
);
|
||||
res.type("text/xml");
|
||||
return res.send(twiml);
|
||||
}
|
||||
|
||||
// Get the user to call
|
||||
const userToCall: UserToCall | null = await getUserToCall(
|
||||
firstRule,
|
||||
policy.projectId!,
|
||||
);
|
||||
|
||||
if (!userToCall) {
|
||||
await IncomingCallLogService.updateOneById({
|
||||
id: createdCallLog.id!,
|
||||
data: {
|
||||
status: IncomingCallStatus.Failed,
|
||||
statusMessage:
|
||||
"No on-call user available or user has no phone number",
|
||||
endedAt: new Date(),
|
||||
},
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
const twiml: string = provider.generateHangupResponse(
|
||||
policy.noOneAvailableMessage ||
|
||||
"We're sorry, but no on-call engineer is currently available.",
|
||||
);
|
||||
res.type("text/xml");
|
||||
return res.send(twiml);
|
||||
}
|
||||
|
||||
// Create call log item
|
||||
const callLogItem: IncomingCallLogItem = new IncomingCallLogItem();
|
||||
if (policy.projectId) {
|
||||
callLogItem.projectId = policy.projectId;
|
||||
}
|
||||
callLogItem.incomingCallLogId = createdCallLog.id!;
|
||||
if (firstRule.id) {
|
||||
callLogItem.incomingCallPolicyEscalationRuleId = firstRule.id;
|
||||
}
|
||||
callLogItem.userId = userToCall.userId;
|
||||
callLogItem.userPhoneNumber = userToCall.phoneNumber;
|
||||
callLogItem.status = IncomingCallStatus.Ringing;
|
||||
callLogItem.startedAt = new Date();
|
||||
callLogItem.isAnswered = false;
|
||||
|
||||
const createdCallLogItem: IncomingCallLogItem =
|
||||
await IncomingCallLogItemService.create({
|
||||
data: callLogItem,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Generate TwiML response
|
||||
const greetingMessage: string =
|
||||
policy.greetingMessage ||
|
||||
"Please wait while we connect you to the on-call engineer.";
|
||||
|
||||
// Construct status callback URL
|
||||
const statusCallbackUrl: string = `${HttpProtocol}${Host}/notification/incoming-call/dial-status/${createdCallLog.id?.toString()}/${createdCallLogItem.id?.toString()}`;
|
||||
|
||||
// Generate greeting + dial TwiML
|
||||
const twiml: string = generateGreetingAndDialTwiml(
|
||||
provider,
|
||||
greetingMessage,
|
||||
userToCall.phoneNumber.toString(),
|
||||
policy.routingPhoneNumber?.toString() || callData.calledPhoneNumber,
|
||||
firstRule.escalateAfterSeconds || 30,
|
||||
statusCallbackUrl,
|
||||
);
|
||||
|
||||
res.type("text/xml");
|
||||
return res.send(twiml);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Handle dial status callback
|
||||
router.post(
|
||||
"/dial-status/:callLogId/:callLogItemId",
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const callLogId: string = req.params["callLogId"] as string;
|
||||
const callLogItemId: string = req.params["callLogItemId"] as string;
|
||||
|
||||
if (!callLogId || !callLogItemId) {
|
||||
throw new BadDataException("Invalid webhook URL");
|
||||
}
|
||||
|
||||
// Get the call log to find the policy and its Twilio config
|
||||
const callLog: IncomingCallLog | null =
|
||||
await IncomingCallLogService.findOneById({
|
||||
id: new ObjectID(callLogId),
|
||||
select: {
|
||||
_id: true,
|
||||
currentEscalationRuleOrder: true,
|
||||
repeatCount: true,
|
||||
incomingCallPolicyId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!callLog) {
|
||||
logger.error(`Call log not found: ${callLogId}`);
|
||||
res.status(404).send("Call log not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the policy with its Twilio config
|
||||
const policy: IncomingCallPolicy | null =
|
||||
await IncomingCallPolicyService.findOneById({
|
||||
id: callLog.incomingCallPolicyId!,
|
||||
select: {
|
||||
_id: true,
|
||||
projectId: true,
|
||||
projectCallSMSConfigId: true,
|
||||
noAnswerMessage: true,
|
||||
noOneAvailableMessage: true,
|
||||
repeatPolicyIfNoOneAnswers: true,
|
||||
repeatPolicyIfNoOneAnswersTimes: true,
|
||||
routingPhoneNumber: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!policy || !policy.projectCallSMSConfigId) {
|
||||
logger.error("Policy or Twilio config not found");
|
||||
res.status(400).send("Configuration error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get project Twilio config
|
||||
const customTwilioConfig: TwilioConfig | null =
|
||||
await getProjectTwilioConfig(policy.projectCallSMSConfigId);
|
||||
|
||||
if (!customTwilioConfig) {
|
||||
logger.error("Twilio config not found for policy");
|
||||
res.status(400).send("Configuration error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get provider with project config
|
||||
const provider: ICallProvider =
|
||||
CallProviderFactory.getProviderWithConfig(customTwilioConfig);
|
||||
|
||||
// Validate webhook signature to ensure request is from the call provider
|
||||
const signature: string =
|
||||
(req.headers["x-twilio-signature"] as string) || "";
|
||||
if (
|
||||
!provider.validateWebhookSignature(
|
||||
req as unknown as WebhookRequest,
|
||||
signature,
|
||||
)
|
||||
) {
|
||||
logger.error("Invalid webhook signature for dial status callback");
|
||||
res.status(403).send("Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse dial status
|
||||
const dialStatus: DialStatusData = provider.parseDialStatusWebhook(
|
||||
req as unknown as WebhookRequest,
|
||||
);
|
||||
|
||||
// Get the call log item
|
||||
const callLogItem: IncomingCallLogItem | null =
|
||||
await IncomingCallLogItemService.findOneById({
|
||||
id: new ObjectID(callLogItemId),
|
||||
select: {
|
||||
_id: true,
|
||||
incomingCallLogId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!callLogItem) {
|
||||
logger.error(`Call log item not found: ${callLogItemId}`);
|
||||
const twiml: string = provider.generateHangupResponse();
|
||||
res.type("text/xml");
|
||||
return res.send(twiml);
|
||||
}
|
||||
|
||||
// Update call log item
|
||||
const now: Date = new Date();
|
||||
await IncomingCallLogItemService.updateOneById({
|
||||
id: new ObjectID(callLogItemId),
|
||||
data: {
|
||||
status:
|
||||
dialStatus.dialStatus === "completed"
|
||||
? IncomingCallStatus.Connected
|
||||
: IncomingCallStatus.NoAnswer,
|
||||
dialDurationInSeconds: dialStatus.dialDurationSeconds || 0,
|
||||
endedAt: now,
|
||||
isAnswered: dialStatus.dialStatus === "completed",
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
// If call was answered, mark as completed
|
||||
if (dialStatus.dialStatus === "completed") {
|
||||
await IncomingCallLogService.updateOneById({
|
||||
id: new ObjectID(callLogId),
|
||||
data: {
|
||||
status: IncomingCallStatus.Completed,
|
||||
endedAt: now,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Hang up - the call is complete
|
||||
const twiml: string = provider.generateHangupResponse();
|
||||
res.type("text/xml");
|
||||
return res.send(twiml);
|
||||
}
|
||||
|
||||
// Call was not answered, try next escalation rule
|
||||
const nextOrder: number = (callLog.currentEscalationRuleOrder || 1) + 1;
|
||||
|
||||
// Get the next escalation rule
|
||||
const nextRule: IncomingCallPolicyEscalationRule | null =
|
||||
await IncomingCallPolicyEscalationRuleService.findOneBy({
|
||||
query: {
|
||||
incomingCallPolicyId: callLog.incomingCallPolicyId!,
|
||||
order: nextOrder,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
escalateAfterSeconds: true,
|
||||
onCallDutyPolicyScheduleId: true,
|
||||
userId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!nextRule) {
|
||||
// No more rules, check if we should repeat
|
||||
if (
|
||||
policy.repeatPolicyIfNoOneAnswers &&
|
||||
(callLog.repeatCount || 0) <
|
||||
(policy.repeatPolicyIfNoOneAnswersTimes || 1)
|
||||
) {
|
||||
// Restart from first rule
|
||||
await IncomingCallLogService.updateOneById({
|
||||
id: new ObjectID(callLogId),
|
||||
data: {
|
||||
currentEscalationRuleOrder: 1,
|
||||
repeatCount: (callLog.repeatCount || 0) + 1,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Get first rule again
|
||||
const firstRule: IncomingCallPolicyEscalationRule | null =
|
||||
await IncomingCallPolicyEscalationRuleService.findOneBy({
|
||||
query: {
|
||||
incomingCallPolicyId: callLog.incomingCallPolicyId!,
|
||||
order: 1,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
escalateAfterSeconds: true,
|
||||
onCallDutyPolicyScheduleId: true,
|
||||
userId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (firstRule && policy.projectId) {
|
||||
const userToCall: UserToCall | null = await getUserToCall(
|
||||
firstRule,
|
||||
policy.projectId,
|
||||
);
|
||||
if (userToCall) {
|
||||
// Continue with the call
|
||||
return await dialNextUser(
|
||||
res,
|
||||
provider,
|
||||
policy,
|
||||
callLog,
|
||||
firstRule,
|
||||
userToCall,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No more options, end the call
|
||||
await IncomingCallLogService.updateOneById({
|
||||
id: new ObjectID(callLogId),
|
||||
data: {
|
||||
status: IncomingCallStatus.NoAnswer,
|
||||
endedAt: now,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const twiml: string = provider.generateHangupResponse(
|
||||
policy.noAnswerMessage ||
|
||||
"No one is available. Please try again later.",
|
||||
);
|
||||
res.type("text/xml");
|
||||
return res.send(twiml);
|
||||
}
|
||||
|
||||
// Update call log with new escalation rule order
|
||||
await IncomingCallLogService.updateOneById({
|
||||
id: new ObjectID(callLogId),
|
||||
data: {
|
||||
currentEscalationRuleOrder: nextOrder,
|
||||
status: IncomingCallStatus.Escalated,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Get the user to call
|
||||
const userToCall: UserToCall | null = await getUserToCall(
|
||||
nextRule,
|
||||
policy.projectId!,
|
||||
);
|
||||
|
||||
if (!userToCall) {
|
||||
/*
|
||||
* Skip this rule and try the next one (recursive approach via TwiML redirect would be complex)
|
||||
* For simplicity, end the call if no user available
|
||||
*/
|
||||
await IncomingCallLogService.updateOneById({
|
||||
id: new ObjectID(callLogId),
|
||||
data: {
|
||||
status: IncomingCallStatus.Failed,
|
||||
statusMessage:
|
||||
"No on-call user available or user has no phone number",
|
||||
endedAt: new Date(),
|
||||
},
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
const twiml: string = provider.generateHangupResponse(
|
||||
policy.noOneAvailableMessage ||
|
||||
"We're sorry, but no on-call engineer is currently available.",
|
||||
);
|
||||
res.type("text/xml");
|
||||
return res.send(twiml);
|
||||
}
|
||||
|
||||
// Dial the next user
|
||||
return await dialNextUser(
|
||||
res,
|
||||
provider,
|
||||
policy,
|
||||
callLog,
|
||||
nextRule,
|
||||
userToCall,
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Interface for user with phone number to call
|
||||
interface UserToCall {
|
||||
userId: ObjectID;
|
||||
phoneNumber: Phone;
|
||||
name?: string | undefined;
|
||||
email?: string | undefined;
|
||||
}
|
||||
|
||||
// Helper function to get user to call from escalation rule
|
||||
async function getUserToCall(
|
||||
rule: IncomingCallPolicyEscalationRule,
|
||||
projectId: ObjectID,
|
||||
): Promise<UserToCall | null> {
|
||||
let userId: ObjectID | null = null;
|
||||
|
||||
// If rule has a direct user, use that
|
||||
if (rule.userId) {
|
||||
userId = rule.userId;
|
||||
} else if (rule.onCallDutyPolicyScheduleId) {
|
||||
// If rule has an on-call schedule, get the current on-call user
|
||||
userId = await OnCallDutyPolicyScheduleService.getCurrentUserIdInSchedule(
|
||||
rule.onCallDutyPolicyScheduleId,
|
||||
);
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the user has a verified incoming call number for this project
|
||||
const verifiedIncomingCallNumber: UserIncomingCallNumber | null =
|
||||
await UserIncomingCallNumberService.findOneBy({
|
||||
query: {
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
phone: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!verifiedIncomingCallNumber || !verifiedIncomingCallNumber.phone) {
|
||||
// No verified incoming call number for this user in this project
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get user details for logging
|
||||
const user: User | null = await UserService.findOneById({
|
||||
id: userId,
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
userId: userId,
|
||||
phoneNumber: verifiedIncomingCallNumber.phone,
|
||||
name: user?.name?.toString(),
|
||||
email: user?.email?.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to generate greeting and dial TwiML
|
||||
function generateGreetingAndDialTwiml(
|
||||
provider: ICallProvider,
|
||||
greetingMessage: string,
|
||||
toPhoneNumber: string,
|
||||
fromPhoneNumber: string,
|
||||
timeoutSeconds: number,
|
||||
statusCallbackUrl: string,
|
||||
): string {
|
||||
// Use the escalation response which says a message then dials
|
||||
return provider.generateEscalationResponse(greetingMessage, {
|
||||
toPhoneNumber,
|
||||
fromPhoneNumber,
|
||||
timeoutSeconds,
|
||||
statusCallbackUrl,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to dial the next user
|
||||
async function dialNextUser(
|
||||
res: ExpressResponse,
|
||||
provider: ICallProvider,
|
||||
policy: IncomingCallPolicy,
|
||||
callLog: IncomingCallLog,
|
||||
rule: IncomingCallPolicyEscalationRule,
|
||||
userToCall: UserToCall,
|
||||
): Promise<ExpressResponse> {
|
||||
// Create call log item
|
||||
const callLogItem: IncomingCallLogItem = new IncomingCallLogItem();
|
||||
if (policy.projectId) {
|
||||
callLogItem.projectId = policy.projectId;
|
||||
}
|
||||
callLogItem.incomingCallLogId = callLog.id!;
|
||||
if (rule.id) {
|
||||
callLogItem.incomingCallPolicyEscalationRuleId = rule.id;
|
||||
}
|
||||
callLogItem.userId = userToCall.userId;
|
||||
callLogItem.userPhoneNumber = userToCall.phoneNumber;
|
||||
callLogItem.status = IncomingCallStatus.Ringing;
|
||||
callLogItem.startedAt = new Date();
|
||||
callLogItem.isAnswered = false;
|
||||
|
||||
const createdCallLogItem: IncomingCallLogItem =
|
||||
await IncomingCallLogItemService.create({
|
||||
data: callLogItem,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Construct status callback URL
|
||||
const statusCallbackUrl: string = `${HttpProtocol}${Host}/notification/incoming-call/dial-status/${callLog.id?.toString()}/${createdCallLogItem.id?.toString()}`;
|
||||
|
||||
// Generate dial TwiML with escalation message
|
||||
const escalationMessage: string = `Connecting you to the next available engineer.`;
|
||||
|
||||
const twiml: string = provider.generateEscalationResponse(escalationMessage, {
|
||||
toPhoneNumber: userToCall.phoneNumber.toString(),
|
||||
fromPhoneNumber: policy.routingPhoneNumber?.toString() || "",
|
||||
timeoutSeconds: rule.escalateAfterSeconds || 30,
|
||||
statusCallbackUrl,
|
||||
});
|
||||
|
||||
res.type("text/xml");
|
||||
return res.send(twiml);
|
||||
}
|
||||
|
||||
export default router;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user