mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
560 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d62816dd49 | ||
|
|
7dd6129dad | ||
|
|
7ccea02340 | ||
|
|
0af41725b4 | ||
|
|
9f6bcddc1e | ||
|
|
97c461f7a3 | ||
|
|
736f8bb83c | ||
|
|
eb33daf64f | ||
|
|
c3c90eef03 | ||
|
|
e92e9f08d3 | ||
|
|
2b313a7702 | ||
|
|
3cf7c7d1ae | ||
|
|
76cfa7186e | ||
|
|
afaff717c0 | ||
|
|
fde0d5f2c6 | ||
|
|
d5c5387621 | ||
|
|
e0ef6e9a77 | ||
|
|
2dc0dc4c96 | ||
|
|
c9eb72ba2c | ||
|
|
92e247d168 | ||
|
|
14988c438a | ||
|
|
d81682d02f | ||
|
|
9d5faca3ec | ||
|
|
89ccde1bc4 | ||
|
|
3aab280dcd | ||
|
|
b8e44a1bcf | ||
|
|
4c3b4d23ff | ||
|
|
a4ff718d61 | ||
|
|
3433a815f3 | ||
|
|
2a20807126 | ||
|
|
991dc1c842 | ||
|
|
2026e7fd77 | ||
|
|
1d0016412e | ||
|
|
917f27fe11 | ||
|
|
c07c89e3dd | ||
|
|
32c4c1666d | ||
|
|
636a419cbd | ||
|
|
61699b9f4a | ||
|
|
b6ed3643c3 | ||
|
|
9e73ac45a1 | ||
|
|
7a3dbd0e8e | ||
|
|
1ec25c27ee | ||
|
|
5286527155 | ||
|
|
895af10755 | ||
|
|
77ccca7e2a | ||
|
|
66f46e9b84 | ||
|
|
91edae50b2 | ||
|
|
7ab3dfe043 | ||
|
|
fb661126d4 | ||
|
|
94c57f3189 | ||
|
|
4de6021905 | ||
|
|
c62a49d499 | ||
|
|
01fd5263ca | ||
|
|
d87eee68e8 | ||
|
|
3f4db5b7e0 | ||
|
|
462ad9d6ab | ||
|
|
6444d3d5cc | ||
|
|
415222561b | ||
|
|
8cf2661c63 | ||
|
|
a820f817ff | ||
|
|
576927c6c7 | ||
|
|
e866db9e18 | ||
|
|
8e91a786f9 | ||
|
|
02d16446f1 | ||
|
|
5d5517258b | ||
|
|
5df632c46c | ||
|
|
c1ee79b339 | ||
|
|
67265c0fc8 | ||
|
|
72e5384012 | ||
|
|
dc8e9d44b1 | ||
|
|
91102ee952 | ||
|
|
e46d1ae7da | ||
|
|
008005415a | ||
|
|
c7362f3ada | ||
|
|
1f634576fe | ||
|
|
d25a97fe17 | ||
|
|
b89ff11db8 | ||
|
|
5ac5ffede5 | ||
|
|
d9167b89ba | ||
|
|
66b995c64a | ||
|
|
f383bbba4d | ||
|
|
43f1a59042 | ||
|
|
7d49872edc | ||
|
|
6d2d5892b9 | ||
|
|
756217e19e | ||
|
|
ca3cf01be7 | ||
|
|
fd0a81a0b1 | ||
|
|
14016d188b | ||
|
|
3a2aff7f34 | ||
|
|
4a6edfee06 | ||
|
|
2dc1b8aa8c | ||
|
|
eb0f0e742d | ||
|
|
23c82c5239 | ||
|
|
2b61e4f4b7 | ||
|
|
9b21abf78d | ||
|
|
bd54b38a69 | ||
|
|
4dc799d238 | ||
|
|
b4d90e3bef | ||
|
|
6c8d4203da | ||
|
|
f7e9745624 | ||
|
|
f7d133adba | ||
|
|
b06c2cb1c3 | ||
|
|
b51c5d9677 | ||
|
|
9a1e265d1c | ||
|
|
e18d75fc8e | ||
|
|
5a68d2f726 | ||
|
|
dfa7c4875a | ||
|
|
8a568e0495 | ||
|
|
7152058ee2 | ||
|
|
b198dc0ec8 | ||
|
|
b0a3f8d60f | ||
|
|
83a13635cf | ||
|
|
a2c1744e8c | ||
|
|
9dfbc05618 | ||
|
|
4271ddbdcb | ||
|
|
6ffb081a02 | ||
|
|
16a9edbfcd | ||
|
|
e32d4395a3 | ||
|
|
b8cd3ce1c1 | ||
|
|
b86aee7f2a | ||
|
|
2cde167445 | ||
|
|
9bd6b011fe | ||
|
|
538eef5660 | ||
|
|
e1a343ae38 | ||
|
|
8b42af35c1 | ||
|
|
fc9026a8d8 | ||
|
|
86edee35c1 | ||
|
|
109c276bc5 | ||
|
|
8040dd0f56 | ||
|
|
00d4148b6b | ||
|
|
dec03bc3a8 | ||
|
|
46a9f95fc0 | ||
|
|
8b2f9bc778 | ||
|
|
fcc6223850 | ||
|
|
c9bc214e86 | ||
|
|
2897a937ba | ||
|
|
f3cd7be143 | ||
|
|
f324a4e864 | ||
|
|
f6a8cef649 | ||
|
|
770ef007a4 | ||
|
|
dafa0cc5d9 | ||
|
|
196e9cae10 | ||
|
|
d0d26d20b2 | ||
|
|
6a90ee97bf | ||
|
|
f2a2644b0e | ||
|
|
5cb2ac8c8b | ||
|
|
6751d59b2f | ||
|
|
aefc649743 | ||
|
|
cfba73665c | ||
|
|
049c5d003c | ||
|
|
fd8998952d | ||
|
|
5ca85e4915 | ||
|
|
aa401291b6 | ||
|
|
bf3d90871d | ||
|
|
4a4dff9264 | ||
|
|
fd3f75e4e2 | ||
|
|
43fc5acdda | ||
|
|
c7ca6138f3 | ||
|
|
87475b00c4 | ||
|
|
d5613cc4bd | ||
|
|
b1c9d9a645 | ||
|
|
01c6101ae9 | ||
|
|
ec56609bf4 | ||
|
|
e5f652a950 | ||
|
|
749bd2e41d | ||
|
|
cc23416ad8 | ||
|
|
86fda9ba16 | ||
|
|
969983043b | ||
|
|
2b64dd0b1d | ||
|
|
3a514969dc | ||
|
|
10d006890c | ||
|
|
2cb719d53a | ||
|
|
eafb543371 | ||
|
|
5800fe4f7a | ||
|
|
8d3712c36a | ||
|
|
f8e26246dd | ||
|
|
8560ecab41 | ||
|
|
5b2a6924d9 | ||
|
|
e047143974 | ||
|
|
d23dc791e2 | ||
|
|
a4b3b340c8 | ||
|
|
2173e4e611 | ||
|
|
dc19f87404 | ||
|
|
a3045c5f26 | ||
|
|
5d6907be97 | ||
|
|
e2ace9fc11 | ||
|
|
5a11bf228a | ||
|
|
cdd8d5523f | ||
|
|
f5029fada7 | ||
|
|
8131c9d42f | ||
|
|
946c7d4c48 | ||
|
|
9cbc7d9646 | ||
|
|
72d95871f7 | ||
|
|
3545a221bc | ||
|
|
9a5bcb9f31 | ||
|
|
91a4d3601c | ||
|
|
ea99dd4873 | ||
|
|
34863dbcb6 | ||
|
|
effeb3a0b6 | ||
|
|
86bdcb416a | ||
|
|
665f194f6d | ||
|
|
1378445dc5 | ||
|
|
236be5b60e | ||
|
|
256f4334eb | ||
|
|
28d5ad4292 | ||
|
|
5c169ccd5b | ||
|
|
e05f15d3f6 | ||
|
|
de0cbe1f42 | ||
|
|
fc48a0efdb | ||
|
|
e623c973ee | ||
|
|
8d56287892 | ||
|
|
0950d4288f | ||
|
|
56ea1c4690 | ||
|
|
08d2b6f5a2 | ||
|
|
2cabdde5bd | ||
|
|
3e48a706bd | ||
|
|
7c672e14a1 | ||
|
|
80a3bbac3d | ||
|
|
25f9b826cf | ||
|
|
c478e6af30 | ||
|
|
555a722732 | ||
|
|
6b5f981424 | ||
|
|
e0e614cf21 | ||
|
|
44cc072d98 | ||
|
|
c2c97dae0a | ||
|
|
3978374ccb | ||
|
|
6950daf10a | ||
|
|
7a07e669c9 | ||
|
|
67ece0fcca | ||
|
|
5413e24bd4 | ||
|
|
59b3fc0334 | ||
|
|
2bc72dbdb6 | ||
|
|
15ccf00503 | ||
|
|
b3d73a5523 | ||
|
|
43e6291608 | ||
|
|
09d82f64de | ||
|
|
51ed9fc2bb | ||
|
|
b23ccdcc57 | ||
|
|
147e687bac | ||
|
|
b84cebcb10 | ||
|
|
7374e3bf9a | ||
|
|
413ba90b02 | ||
|
|
2fd61385bd | ||
|
|
822bc9f8d5 | ||
|
|
e53a490606 | ||
|
|
cc53460e7a | ||
|
|
7d6e0488ba | ||
|
|
385a0fb9e5 | ||
|
|
584b79f48c | ||
|
|
92901b1647 | ||
|
|
bcbc4f6d99 | ||
|
|
04dd1260ac | ||
|
|
882f9f6ae4 | ||
|
|
549dc3546b | ||
|
|
25edcf7d9b | ||
|
|
46378fc3db | ||
|
|
f9f5bff4ce | ||
|
|
12b78249c5 | ||
|
|
f8cbc3a551 | ||
|
|
670b984cee | ||
|
|
e677e54ea9 | ||
|
|
928a2589c2 | ||
|
|
45f7a86888 | ||
|
|
0fa7848ab9 | ||
|
|
ae6e49da8f | ||
|
|
e80e22b1fa | ||
|
|
2adefd1cee | ||
|
|
36cbb3159a | ||
|
|
54909116b9 | ||
|
|
4582f6100a | ||
|
|
49a01eca8c | ||
|
|
349df0e181 | ||
|
|
c52116bec1 | ||
|
|
098a18005f | ||
|
|
6dbcd69ecd | ||
|
|
09a6827709 | ||
|
|
dbb1fa6c18 | ||
|
|
cd450bc3b6 | ||
|
|
047195116d | ||
|
|
564f21388b | ||
|
|
c69d7c949e | ||
|
|
dd47b9c3a9 | ||
|
|
ce731cb489 | ||
|
|
f725fdd2d9 | ||
|
|
1aec570c83 | ||
|
|
97b7e15ece | ||
|
|
7cdac5fe66 | ||
|
|
4add175070 | ||
|
|
711cfd2f6b | ||
|
|
6869ee670a | ||
|
|
c4d978cc3b | ||
|
|
1dffc2fbbe | ||
|
|
6b0756cd3a | ||
|
|
fbfa7747e0 | ||
|
|
a7c38dcbf2 | ||
|
|
6b8dd9e8b5 | ||
|
|
c5e7429b3d | ||
|
|
13ccee4e69 | ||
|
|
f9c9ac5ef0 | ||
|
|
10654a0a04 | ||
|
|
0d900dca78 | ||
|
|
bf5846e7f4 | ||
|
|
9fa48c7a25 | ||
|
|
9efb070334 | ||
|
|
85e7dd1150 | ||
|
|
072f162b6e | ||
|
|
9e01b0b75a | ||
|
|
3d7b98d1ee | ||
|
|
62f6900dd2 | ||
|
|
51cf4a88bf | ||
|
|
0dfd38d263 | ||
|
|
66424eee24 | ||
|
|
93adee4b16 | ||
|
|
d7efe2445c | ||
|
|
1bf4c52518 | ||
|
|
8348bf6897 | ||
|
|
7f2192206f | ||
|
|
ddf7636965 | ||
|
|
52514fbb7e | ||
|
|
2c3521561d | ||
|
|
8d6ef5a277 | ||
|
|
726ae7ef98 | ||
|
|
fde974d968 | ||
|
|
eae5e026fa | ||
|
|
5b01743e74 | ||
|
|
2cfc6a5e68 | ||
|
|
0976df1bee | ||
|
|
caa59aea7e | ||
|
|
ab5e0ec3c4 | ||
|
|
f4eda526c5 | ||
|
|
19f347a826 | ||
|
|
7eb84c2fb0 | ||
|
|
a27f3953ab | ||
|
|
4ec162208b | ||
|
|
13482b13d7 | ||
|
|
69c0253862 | ||
|
|
92d8b7b425 | ||
|
|
0ef053dc3d | ||
|
|
fa0bd99bc8 | ||
|
|
68b6ca9fd3 | ||
|
|
cd9b711ee4 | ||
|
|
b05d1652e1 | ||
|
|
d971573db0 | ||
|
|
5a11518c31 | ||
|
|
ce4f41367b | ||
|
|
f553726186 | ||
|
|
1bd746b285 | ||
|
|
82558fda59 | ||
|
|
3f1fe2bf1c | ||
|
|
d00fa80e47 | ||
|
|
dc4805c3b2 | ||
|
|
642fb95209 | ||
|
|
01baf60b2e | ||
|
|
225679f5d3 | ||
|
|
51f16e2213 | ||
|
|
0af23bbacb | ||
|
|
7b446a853c | ||
|
|
df480577ab | ||
|
|
c031cc2af3 | ||
|
|
ae17820d0d | ||
|
|
f2f3900506 | ||
|
|
4c8b92144c | ||
|
|
4c45e16f56 | ||
|
|
53e39724e7 | ||
|
|
849882d868 | ||
|
|
e3f8af83e5 | ||
|
|
18a5559116 | ||
|
|
3c3ecfc698 | ||
|
|
c22f7fec46 | ||
|
|
75d473f6d7 | ||
|
|
5d8f8e248e | ||
|
|
a7c3ea274f | ||
|
|
da324f49d9 | ||
|
|
6fd9223ee9 | ||
|
|
c062d651e8 | ||
|
|
1c264ac5a1 | ||
|
|
8923a4bff6 | ||
|
|
ab4e0cf85f | ||
|
|
8f63e93eba | ||
|
|
c06697f299 | ||
|
|
2590a8d671 | ||
|
|
e4eadc297b | ||
|
|
d0bf351dc1 | ||
|
|
3234cc7d09 | ||
|
|
0158271e6a | ||
|
|
9d50bf2535 | ||
|
|
c8ca2eacc5 | ||
|
|
66699901a7 | ||
|
|
68a33eee2f | ||
|
|
016793d77d | ||
|
|
7b040b659d | ||
|
|
c6db71f383 | ||
|
|
16923c750b | ||
|
|
f57173f43c | ||
|
|
9716d138ea | ||
|
|
8e1b6859f5 | ||
|
|
9751dd0d5f | ||
|
|
e84bd95f49 | ||
|
|
02b76539ab | ||
|
|
c40c33773b | ||
|
|
b0cebf9338 | ||
|
|
674e35dc70 | ||
|
|
d5f42141a0 | ||
|
|
8fb6da7d41 | ||
|
|
d14e77ee7f | ||
|
|
71e8a70717 | ||
|
|
370bdc6e21 | ||
|
|
d2a5d037c1 | ||
|
|
1ffa87d322 | ||
|
|
28ffde7983 | ||
|
|
4655e207a5 | ||
|
|
62a5b216a0 | ||
|
|
79fb9d18ca | ||
|
|
ba5e8fdaeb | ||
|
|
aa31bbab45 | ||
|
|
3c84365d61 | ||
|
|
ae59bd8300 | ||
|
|
b868206e82 | ||
|
|
ddbf971f1f | ||
|
|
483578ba4d | ||
|
|
614cb4413e | ||
|
|
ad43fc2df2 | ||
|
|
aa2a6deb9e | ||
|
|
dcb13bb401 | ||
|
|
5260364e91 | ||
|
|
1938e620bb | ||
|
|
c3fd71dcd4 | ||
|
|
aba191c533 | ||
|
|
847c019aea | ||
|
|
edf05944c1 | ||
|
|
5293876943 | ||
|
|
ca7a702c13 | ||
|
|
4020b4b647 | ||
|
|
650849f4ad | ||
|
|
9098261ac0 | ||
|
|
70c6abbb86 | ||
|
|
23bc5531f0 | ||
|
|
20404458e2 | ||
|
|
31d3ce949d | ||
|
|
61ed224ad0 | ||
|
|
71ea76ee62 | ||
|
|
7a4a0553ca | ||
|
|
cb57fa4a07 | ||
|
|
22bc222689 | ||
|
|
9d96170c42 | ||
|
|
d0f4d21177 | ||
|
|
e9a2167484 | ||
|
|
a8af991a80 | ||
|
|
8bb3a5b7ac | ||
|
|
d0b1efb660 | ||
|
|
4f3259c3b1 | ||
|
|
30b53a90a4 | ||
|
|
cf8377ceec | ||
|
|
ffc7dbc35f | ||
|
|
97fd817db4 | ||
|
|
aa7caaa193 | ||
|
|
a05853ea09 | ||
|
|
2ba96c093d | ||
|
|
ed3df77ca4 | ||
|
|
9ec363d222 | ||
|
|
a090ec2747 | ||
|
|
9fde4fece9 | ||
|
|
596798801a | ||
|
|
dcc87c46b2 | ||
|
|
33fdabaea3 | ||
|
|
35deea863b | ||
|
|
2b2bbbdd55 | ||
|
|
d8cd92c504 | ||
|
|
f6ef2fa97d | ||
|
|
8a86f6a94f | ||
|
|
4b30274915 | ||
|
|
01f7d7cc78 | ||
|
|
616e64110a | ||
|
|
903a72a4e1 | ||
|
|
699c1d4341 | ||
|
|
078a4e8180 | ||
|
|
29232e7052 | ||
|
|
a221f7247c | ||
|
|
c4b5aca463 | ||
|
|
9de4be6661 | ||
|
|
a532dcdd5f | ||
|
|
bde09d2326 | ||
|
|
b36ac68026 | ||
|
|
200a94692e | ||
|
|
3afc3a3302 | ||
|
|
fa52c30462 | ||
|
|
c229936d5c | ||
|
|
70b2fb8c16 | ||
|
|
264613c676 | ||
|
|
61209f967f | ||
|
|
f82de89f3f | ||
|
|
b94a095bef | ||
|
|
c08de3da35 | ||
|
|
748e18fd1b | ||
|
|
c0d7c34018 | ||
|
|
e249ee6e59 | ||
|
|
9eca0153ce | ||
|
|
c4deb0d0b4 | ||
|
|
7ecd86eca7 | ||
|
|
f3312a2417 | ||
|
|
e0558a4a0a | ||
|
|
e2a238e3e3 | ||
|
|
543c62df5a | ||
|
|
382c838d40 | ||
|
|
1a88832efc | ||
|
|
d1f97a3193 | ||
|
|
70a269b662 | ||
|
|
a4ae42fd08 | ||
|
|
6c0161543a | ||
|
|
4519292cc8 | ||
|
|
83993fc2a4 | ||
|
|
5340b04b26 | ||
|
|
2a6003e78f | ||
|
|
bfed03a10e | ||
|
|
d99b20327f | ||
|
|
3a317a8b55 | ||
|
|
0c64ba30b0 | ||
|
|
4400a7e5dd | ||
|
|
45aab853c4 | ||
|
|
b37c13d347 | ||
|
|
d13407494b | ||
|
|
724ab97874 | ||
|
|
15e2fdcf48 | ||
|
|
3a546f9b5a | ||
|
|
291a0f12f1 | ||
|
|
3d62b67bca | ||
|
|
4c31c2b651 | ||
|
|
acdcb2d5da | ||
|
|
43084263ab | ||
|
|
1bbc953462 | ||
|
|
7cef3956e8 | ||
|
|
4904a535d1 | ||
|
|
5db511036e | ||
|
|
8b9023d93d | ||
|
|
0546d1fb12 | ||
|
|
d523ae822d | ||
|
|
9fd781c083 | ||
|
|
8d743dbb59 | ||
|
|
5b3e97c10d | ||
|
|
252a81c9ae | ||
|
|
e6b414a94b | ||
|
|
f521091f8e | ||
|
|
19e112a8a8 | ||
|
|
84dd084dae | ||
|
|
439c1f8716 | ||
|
|
647b713375 | ||
|
|
a2e6b7a4fc | ||
|
|
b06bc71a2c | ||
|
|
367a80c413 | ||
|
|
f49e4bd5d0 | ||
|
|
a74a7e0a9a | ||
|
|
f4946449f3 | ||
|
|
9640732e29 | ||
|
|
6ef5e409da | ||
|
|
bcdfa034f6 | ||
|
|
959267a174 | ||
|
|
767db415d2 | ||
|
|
7ca81aa9f8 | ||
|
|
26bd4c7a90 | ||
|
|
9d29a1d00b |
36
.github/workflows/compile.yml
vendored
36
.github/workflows/compile.yml
vendored
@@ -389,6 +389,23 @@ jobs:
|
||||
max_attempts: 3
|
||||
command: cd MCP && npm update @oneuptime/common && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-mobile-app:
|
||||
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 && npm run compile
|
||||
- name: Compile MobileApp
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd MobileApp && npm install && npm run compile
|
||||
|
||||
compile-ai-agent:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -404,4 +421,21 @@ jobs:
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd AIAgent && npm install && npm run compile && npm run dep-check
|
||||
command: cd AIAgent && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-cli:
|
||||
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 CLI
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd CLI && npm install && npm run compile && npm run dep-check
|
||||
237
.github/workflows/release.yml
vendored
237
.github/workflows/release.yml
vendored
@@ -1913,12 +1913,51 @@ jobs:
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
# Aggressively free disk space before anything else
|
||||
- name: Aggressive Disk Cleanup
|
||||
run: |
|
||||
echo "=== Disk space BEFORE cleanup ==="
|
||||
df -h /
|
||||
# Remove pre-installed software not needed for this job
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
sudo rm -rf /opt/hostedtoolcache || true
|
||||
sudo rm -rf /usr/local/share/boost || true
|
||||
sudo rm -rf /usr/local/graalvm/ || true
|
||||
sudo rm -rf /usr/local/share/powershell || true
|
||||
sudo rm -rf /usr/local/share/chromium || true
|
||||
sudo rm -rf /usr/local/lib/node_modules || true
|
||||
sudo rm -rf /usr/share/swift || true
|
||||
sudo rm -rf /usr/share/miniconda || true
|
||||
sudo rm -rf /usr/lib/google-cloud-sdk || true
|
||||
sudo rm -rf /usr/lib/jvm || true
|
||||
sudo rm -rf /usr/lib/firefox || true
|
||||
sudo rm -rf /usr/lib/heroku || true
|
||||
sudo rm -rf /usr/local/julia* || true
|
||||
sudo rm -rf /opt/az || true
|
||||
sudo rm -rf /opt/microsoft || true
|
||||
sudo rm -rf /opt/pipx || true
|
||||
sudo rm -rf /opt/actionarchivecache || true
|
||||
sudo rm -rf /imagegeneration || true
|
||||
sudo rm -rf /usr/share/az_* || true
|
||||
sudo rm -rf /usr/share/sbt || true
|
||||
sudo rm -rf /usr/share/gradle* || true
|
||||
sudo rm -rf /usr/share/kotlinc || true
|
||||
sudo rm -rf /usr/share/ri || true
|
||||
sudo rm -rf /usr/local/.ghcup || true
|
||||
# Clean apt cache
|
||||
sudo apt-get clean || true
|
||||
sudo rm -rf /var/lib/apt/lists/* || true
|
||||
# Clean temp files
|
||||
sudo rm -rf /tmp/* || true
|
||||
# Docker cleanup
|
||||
docker system prune -af --volumes || true
|
||||
echo "=== Disk space AFTER aggressive cleanup ==="
|
||||
df -h /
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
android: true
|
||||
dotnet: true
|
||||
@@ -1926,6 +1965,10 @@ jobs:
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- name: Final Disk Space Check
|
||||
run: |
|
||||
echo "=== Disk space after all cleanup ==="
|
||||
df -h /
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -2001,12 +2044,51 @@ jobs:
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
# Aggressively free disk space before anything else
|
||||
- name: Aggressive Disk Cleanup
|
||||
run: |
|
||||
echo "=== Disk space BEFORE cleanup ==="
|
||||
df -h /
|
||||
# Remove pre-installed software not needed for this job
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
sudo rm -rf /opt/hostedtoolcache || true
|
||||
sudo rm -rf /usr/local/share/boost || true
|
||||
sudo rm -rf /usr/local/graalvm/ || true
|
||||
sudo rm -rf /usr/local/share/powershell || true
|
||||
sudo rm -rf /usr/local/share/chromium || true
|
||||
sudo rm -rf /usr/local/lib/node_modules || true
|
||||
sudo rm -rf /usr/share/swift || true
|
||||
sudo rm -rf /usr/share/miniconda || true
|
||||
sudo rm -rf /usr/lib/google-cloud-sdk || true
|
||||
sudo rm -rf /usr/lib/jvm || true
|
||||
sudo rm -rf /usr/lib/firefox || true
|
||||
sudo rm -rf /usr/lib/heroku || true
|
||||
sudo rm -rf /usr/local/julia* || true
|
||||
sudo rm -rf /opt/az || true
|
||||
sudo rm -rf /opt/microsoft || true
|
||||
sudo rm -rf /opt/pipx || true
|
||||
sudo rm -rf /opt/actionarchivecache || true
|
||||
sudo rm -rf /imagegeneration || true
|
||||
sudo rm -rf /usr/share/az_* || true
|
||||
sudo rm -rf /usr/share/sbt || true
|
||||
sudo rm -rf /usr/share/gradle* || true
|
||||
sudo rm -rf /usr/share/kotlinc || true
|
||||
sudo rm -rf /usr/share/ri || true
|
||||
sudo rm -rf /usr/local/.ghcup || true
|
||||
# Clean apt cache
|
||||
sudo apt-get clean || true
|
||||
sudo rm -rf /var/lib/apt/lists/* || true
|
||||
# Clean temp files
|
||||
sudo rm -rf /tmp/* || true
|
||||
# Docker cleanup
|
||||
docker system prune -af --volumes || true
|
||||
echo "=== Disk space AFTER aggressive cleanup ==="
|
||||
df -h /
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
android: true
|
||||
dotnet: true
|
||||
@@ -2014,6 +2096,10 @@ jobs:
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- name: Final Disk Space Check
|
||||
run: |
|
||||
echo "=== Disk space after all cleanup ==="
|
||||
df -h /
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -2200,6 +2286,145 @@ jobs:
|
||||
tag_name: ${{needs.read-version.outputs.major_minor}}
|
||||
|
||||
|
||||
# Build Android release APK and attach to GitHub Release.
|
||||
# Required secrets setup guide: MobileApp/docs/RELEASE_SIGNING.md
|
||||
mobile-app-android-deploy:
|
||||
needs: [draft-github-release, generate-build-number, read-version]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Setup Java 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd MobileApp && npm install
|
||||
|
||||
- name: Decode Android keystore
|
||||
run: |
|
||||
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > /tmp/release.keystore
|
||||
|
||||
- name: Build release APK
|
||||
env:
|
||||
ANDROID_KEYSTORE_FILE: /tmp/release.keystore
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
run: |
|
||||
cd MobileApp/android
|
||||
./gradlew assembleRelease \
|
||||
-PversionName=${{ needs.read-version.outputs.major_minor }} \
|
||||
-PversionCode=${{ needs.generate-build-number.outputs.build_number }}
|
||||
|
||||
- name: Upload APK to GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: MobileApp/android/app/build/outputs/apk/release/*.apk
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
tag_name: ${{ needs.read-version.outputs.major_minor }}
|
||||
|
||||
# Build iOS release IPA and attach to GitHub Release.
|
||||
# Required secrets setup guide: MobileApp/docs/RELEASE_SIGNING.md
|
||||
mobile-app-ios-deploy:
|
||||
needs: [draft-github-release, generate-build-number, read-version]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd MobileApp && npm install
|
||||
|
||||
- name: Install CocoaPods dependencies
|
||||
run: cd MobileApp/ios && pod install
|
||||
|
||||
- name: Import signing certificate
|
||||
env:
|
||||
IOS_DISTRIBUTION_CERTIFICATE_BASE64: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_BASE64 }}
|
||||
IOS_DISTRIBUTION_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_PASSWORD }}
|
||||
run: |
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/distribution.p12
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
|
||||
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
|
||||
|
||||
echo "$IOS_DISTRIBUTION_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
|
||||
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
security import "$CERTIFICATE_PATH" -P "$IOS_DISTRIBUTION_CERTIFICATE_PASSWORD" \
|
||||
-A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security list-keychain -d user -s "$KEYCHAIN_PATH"
|
||||
|
||||
- name: Install provisioning profile
|
||||
env:
|
||||
IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
|
||||
run: |
|
||||
PROFILE_PATH=$RUNNER_TEMP/profile.mobileprovision
|
||||
echo "$IOS_PROVISIONING_PROFILE_BASE64" | base64 --decode > "$PROFILE_PATH"
|
||||
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/
|
||||
|
||||
- name: Build archive
|
||||
run: |
|
||||
cd MobileApp
|
||||
xcodebuild -workspace ios/OneUptime.xcworkspace \
|
||||
-scheme OneUptime \
|
||||
-configuration Release \
|
||||
-sdk iphoneos \
|
||||
-archivePath $RUNNER_TEMP/OneUptime.xcarchive \
|
||||
archive \
|
||||
MARKETING_VERSION=${{ needs.read-version.outputs.major_minor }} \
|
||||
CURRENT_PROJECT_VERSION=${{ needs.generate-build-number.outputs.build_number }}
|
||||
|
||||
- name: Prepare ExportOptions.plist with team ID
|
||||
env:
|
||||
IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
|
||||
run: |
|
||||
/usr/libexec/PlistBuddy -c "Add :teamID string $IOS_TEAM_ID" MobileApp/ios/ExportOptions.plist || \
|
||||
/usr/libexec/PlistBuddy -c "Set :teamID $IOS_TEAM_ID" MobileApp/ios/ExportOptions.plist
|
||||
|
||||
- name: Export IPA
|
||||
run: |
|
||||
cd MobileApp
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath $RUNNER_TEMP/OneUptime.xcarchive \
|
||||
-exportOptionsPlist ios/ExportOptions.plist \
|
||||
-exportPath $RUNNER_TEMP/build
|
||||
|
||||
- name: Upload IPA to GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ${{ runner.temp }}/build/*.ipa
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
tag_name: ${{ needs.read-version.outputs.major_minor }}
|
||||
|
||||
- name: Cleanup keychain
|
||||
if: always()
|
||||
run: |
|
||||
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
|
||||
|
||||
finalize-github-release:
|
||||
name: Publish GitHub release
|
||||
needs: [infrastructure-agent-deploy, generate-build-number, read-version]
|
||||
|
||||
10
.github/workflows/terraform-provider-e2e.yml
vendored
10
.github/workflows/terraform-provider-e2e.yml
vendored
@@ -134,6 +134,10 @@ jobs:
|
||||
terraform_wrapper: false
|
||||
|
||||
- name: Run E2E Tests
|
||||
run: |
|
||||
chmod +x ./E2E/Terraform/e2e-tests/scripts/*.sh
|
||||
./E2E/Terraform/e2e-tests/scripts/index.sh
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 60
|
||||
max_attempts: 3
|
||||
command: |
|
||||
chmod +x ./E2E/Terraform/e2e-tests/scripts/*.sh
|
||||
./E2E/Terraform/e2e-tests/scripts/index.sh
|
||||
|
||||
21
.github/workflows/test.cli.yaml
vendored
Normal file
21
.github/workflows/test.cli.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: CLI Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'hotfix-*' # excludes hotfix branches
|
||||
- 'release'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd CLI && npm install && npm run test
|
||||
39
.github/workflows/test.mobile-app.yaml
vendored
Normal file
39
.github/workflows/test.mobile-app.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: MobileApp Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'hotfix-*' # excludes hotfix branches
|
||||
- 'release'
|
||||
|
||||
jobs:
|
||||
expo-doctor:
|
||||
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 MobileApp && npm install
|
||||
- name: Run Expo Doctor
|
||||
run: cd MobileApp && npx expo-doctor@latest
|
||||
|
||||
expo-web-export:
|
||||
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 MobileApp && npm install
|
||||
- name: Export Web Bundle
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd MobileApp && npx expo export --platform web
|
||||
1
AIAgent/package-lock.json
generated
1
AIAgent/package-lock.json
generated
@@ -73,6 +73,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"expo-server-sdk": "^3.15.0",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
"history": "^5.3.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import AuthenticationServiceHandler from "./Service/Authentication";
|
||||
import DataTypeServiceHandler from "./Service/DataType";
|
||||
import DataTypeDetailServiceHandler from "./Service/DataTypeDetail";
|
||||
import ErrorServiceHandler from "./Service/Errors";
|
||||
import OpenAPIServiceHandler from "./Service/OpenAPI";
|
||||
import IntroductionServiceHandler from "./Service/Introduction";
|
||||
@@ -10,6 +11,7 @@ import PermissionServiceHandler from "./Service/Permissions";
|
||||
import StatusServiceHandler from "./Service/Status";
|
||||
import { StaticPath } from "./Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "./Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "./Utils/DataTypes";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import FeatureSet from "Common/Server/Types/FeatureSet";
|
||||
import Express, {
|
||||
@@ -24,6 +26,9 @@ const APIReferenceFeatureSet: FeatureSet = {
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
|
||||
const DataTypeDictionary: Dictionary<DataTypeDocumentation> =
|
||||
DataTypeUtil.getDataTypeDictionaryByPath();
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
// Serve static files for the API reference with a cache max age of 30 days
|
||||
@@ -72,6 +77,8 @@ const APIReferenceFeatureSet: FeatureSet = {
|
||||
return StatusServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "data-types") {
|
||||
return DataTypeServiceHandler.executeResponse(req, res);
|
||||
} else if (DataTypeDictionary[page]) {
|
||||
return DataTypeDetailServiceHandler.executeResponse(req, res);
|
||||
} else if (currentResource) {
|
||||
return ModelServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Retrieve resources documentation
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
@@ -27,6 +29,7 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
@@ -153,6 +155,7 @@ export default class ServiceHandler {
|
||||
pageDescription:
|
||||
"Data Types that can be used to interact with OneUptime API",
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
|
||||
3351
APIReference/Service/DataTypeDetail.ts
Normal file
3351
APIReference/Service/DataTypeDetail.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,13 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Fetch a list of resources used in the application
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
// Handles the HTTP response for a given request
|
||||
@@ -28,6 +30,7 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Get all resources and featured resources from ResourceUtil
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
const FeaturedResources: Array<ModelDocumentation> =
|
||||
ResourceUtil.getFeaturedResources();
|
||||
|
||||
@@ -34,6 +36,7 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -3,8 +3,10 @@ import CodeExampleGenerator, {
|
||||
CodeExamples,
|
||||
} from "../Utils/CodeExampleGenerator";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import PageNotFoundServiceHandler from "./PageNotFound";
|
||||
import { AppApiRoute } from "Common/ServiceRoute";
|
||||
import BaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
||||
import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl";
|
||||
import {
|
||||
getTableColumns,
|
||||
@@ -314,9 +316,13 @@ function generateApiCodeExamples(
|
||||
|
||||
// Get all resources and resource dictionary
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
|
||||
// Dynamically built from DataTypes registry — no manual updates needed when new types are added
|
||||
const TypeToDocPath: Dictionary<string> = DataTypeUtil.getTypeToDocPathMap();
|
||||
|
||||
// Get all permission props
|
||||
const PermissionDictionary: Dictionary<PermissionProps> =
|
||||
PermissionHelper.getAllPermissionPropsAsDictionary();
|
||||
@@ -392,6 +398,32 @@ export default class ServiceHandler {
|
||||
delete tableColumns["deletedByUser"];
|
||||
delete tableColumns["version"];
|
||||
|
||||
// For columns with a modelType (Entity/EntityArray), resolve the related model's documentation path
|
||||
for (const key in tableColumns) {
|
||||
const column: TableColumnMetadata | undefined = tableColumns[key];
|
||||
if (column?.modelType) {
|
||||
try {
|
||||
const relatedModelInstance: BaseModel = new column.modelType();
|
||||
if (relatedModelInstance.enableDocumentation) {
|
||||
(column as any).modelDocumentationPath =
|
||||
relatedModelInstance.getAPIDocumentationPath();
|
||||
(column as any).modelName = relatedModelInstance.singularName;
|
||||
}
|
||||
} catch {
|
||||
// If model instantiation fails, skip linking
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve non-entity complex types to their documentation paths
|
||||
if (column?.type && !(column as any).modelDocumentationPath) {
|
||||
const typeStr: string = column.type.toString();
|
||||
const docPath: string | undefined = TypeToDocPath[typeStr];
|
||||
if (docPath) {
|
||||
(column as any).typeDocumentationPath = docPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set page data
|
||||
pageData["title"] = currentResource.model.singularName;
|
||||
pageData["description"] = currentResource.model.tableDescription;
|
||||
@@ -586,6 +618,7 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -5,12 +5,14 @@ import {
|
||||
} from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Fetch a list of resources used in the application
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
// Handles the HTTP response for a given request
|
||||
@@ -36,6 +38,7 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources(); // Get an array of model documentation resources
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
// This is a static method that handles the response
|
||||
@@ -21,6 +23,7 @@ export default class ServiceHandler {
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: "Page you're looking for is not found.", // The page description
|
||||
resources: Resources, // The array of model documentation resources
|
||||
dataTypes: DataTypes,
|
||||
pageData: {}, // An empty object to hold any additional page data
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources(); // Get all resources from ResourceUtil
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
@@ -49,6 +51,7 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page, // Pass the page parameter
|
||||
resources: Resources, // Pass all resources
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled, // Pass the page title
|
||||
pageDescription: pageDescription, // Pass the page description
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { PermissionHelper, PermissionProps } from "Common/Types/Permission";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import {
|
||||
PermissionGroup,
|
||||
PermissionHelper,
|
||||
PermissionProps,
|
||||
} from "Common/Types/Permission";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
@@ -25,16 +31,39 @@ export default class ServiceHandler {
|
||||
pageDescription = "Learn how permissions work with OneUptime";
|
||||
|
||||
// Filter permissions to only include those assignable to tenants
|
||||
pageData["permissions"] = PermissionHelper.getAllPermissionProps().filter(
|
||||
(i: PermissionProps) => {
|
||||
const tenantPermissions: Array<PermissionProps> =
|
||||
PermissionHelper.getAllPermissionProps().filter((i: PermissionProps) => {
|
||||
return i.isAssignableToTenant;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Group permissions by PermissionGroup
|
||||
const permissionGroups: Array<{
|
||||
group: string;
|
||||
permissions: Array<PermissionProps>;
|
||||
}> = [];
|
||||
|
||||
for (const group of Object.values(PermissionGroup)) {
|
||||
const groupPermissions: Array<PermissionProps> = tenantPermissions.filter(
|
||||
(p: PermissionProps) => {
|
||||
return p.group === group;
|
||||
},
|
||||
);
|
||||
|
||||
if (groupPermissions.length > 0) {
|
||||
permissionGroups.push({
|
||||
group: group,
|
||||
permissions: groupPermissions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pageData["permissionGroups"] = permissionGroups;
|
||||
|
||||
// Render the page
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
|
||||
// Retrieve resources from ResourceUtil
|
||||
const resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const dataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
@@ -21,6 +23,7 @@ export default class ServiceHandler {
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: "200 - Success",
|
||||
resources: resources, // Pass resources to the template
|
||||
dataTypes: dataTypes,
|
||||
pageData: {}, // Pass empty data to the template
|
||||
});
|
||||
}
|
||||
|
||||
422
APIReference/Utils/DataTypes.ts
Normal file
422
APIReference/Utils/DataTypes.ts
Normal file
@@ -0,0 +1,422 @@
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
export interface DataTypeDocumentation {
|
||||
name: string;
|
||||
path: string;
|
||||
description: string;
|
||||
/*
|
||||
* Additional column type display strings that should link to this data type page.
|
||||
* Used for cases where the TableColumnType enum value doesn't match the PascalCase name
|
||||
* (e.g., enum "Date" should link to the "DateTime" data type page).
|
||||
*/
|
||||
columnTypeAliases?: Array<string>;
|
||||
/*
|
||||
* Category for grouping in sidebar navigation.
|
||||
* Types with the same category are grouped under a collapsible heading.
|
||||
*/
|
||||
category?: string;
|
||||
}
|
||||
|
||||
export interface DataTypeCategory {
|
||||
name: string;
|
||||
types: Array<DataTypeDocumentation>;
|
||||
}
|
||||
|
||||
export default class DataTypeUtil {
|
||||
public static getDataTypes(): Array<DataTypeDocumentation> {
|
||||
return [
|
||||
{
|
||||
name: "ObjectID",
|
||||
path: "object-id",
|
||||
description:
|
||||
"A unique identifier for objects, typically a UUID string.",
|
||||
},
|
||||
{
|
||||
name: "Decimal",
|
||||
path: "decimal",
|
||||
description: "A decimal number type for precise numeric values.",
|
||||
},
|
||||
{
|
||||
name: "Name",
|
||||
path: "name",
|
||||
description: "A structured name type representing a text name value.",
|
||||
},
|
||||
{
|
||||
name: "EqualTo",
|
||||
path: "equal-to",
|
||||
description:
|
||||
"A query filter that matches objects where a field is equal to the specified value.",
|
||||
},
|
||||
{
|
||||
name: "EqualToOrNull",
|
||||
path: "equal-to-or-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is equal to the specified value or is null.",
|
||||
},
|
||||
{
|
||||
name: "MonitorSteps",
|
||||
path: "monitor-steps",
|
||||
description:
|
||||
"Complex nested object describing monitor check configuration including steps and default status.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStep",
|
||||
path: "monitor-step",
|
||||
description:
|
||||
"A single monitor step defining a check target, request configuration, and criteria for determining status.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "Recurring",
|
||||
path: "recurring",
|
||||
description:
|
||||
"Object describing a recurring interval schedule (e.g., every 5 minutes, daily).",
|
||||
},
|
||||
{
|
||||
name: "RestrictionTimes",
|
||||
path: "restriction-times",
|
||||
description:
|
||||
"Object describing on-call duty time restrictions (daily or weekly windows).",
|
||||
},
|
||||
{
|
||||
name: "MonitorCriteria",
|
||||
path: "monitor-criteria",
|
||||
description:
|
||||
"A collection of monitor criteria instances used to evaluate monitor check results.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "PositiveNumber",
|
||||
path: "positive-number",
|
||||
description: "A number type that must be greater than zero.",
|
||||
columnTypeAliases: ["Small Positive Number", "Big Positive Number"],
|
||||
},
|
||||
{
|
||||
name: "MonitorCriteriaInstance",
|
||||
path: "monitor-criteria-instance",
|
||||
description:
|
||||
"A single criteria rule defining conditions and the resulting monitor status when conditions are met.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "NotEqual",
|
||||
path: "not-equal",
|
||||
description:
|
||||
"A query filter that matches objects where a field is not equal to the specified value.",
|
||||
},
|
||||
{
|
||||
name: "Email",
|
||||
path: "email",
|
||||
description: "An email address type with built-in format validation.",
|
||||
},
|
||||
{
|
||||
name: "Phone",
|
||||
path: "phone",
|
||||
description: "A phone number type with built-in format validation.",
|
||||
},
|
||||
{
|
||||
name: "Color",
|
||||
path: "color",
|
||||
description:
|
||||
"A color value represented as a hex string (e.g., #3498db).",
|
||||
},
|
||||
{
|
||||
name: "Domain",
|
||||
path: "domain",
|
||||
description: "A domain name type (e.g., example.com).",
|
||||
},
|
||||
{
|
||||
name: "Version",
|
||||
path: "version",
|
||||
description: "A semantic version type (e.g., 1.0.0).",
|
||||
},
|
||||
{
|
||||
name: "IP",
|
||||
path: "ip",
|
||||
description:
|
||||
"An IP address type supporting both IPv4 and IPv6 formats.",
|
||||
},
|
||||
{
|
||||
name: "Route",
|
||||
path: "route",
|
||||
description: "A URL route/path segment type.",
|
||||
},
|
||||
{
|
||||
name: "URL",
|
||||
path: "url",
|
||||
description: "A full URL type with protocol, host, and path.",
|
||||
},
|
||||
{
|
||||
name: "Permission",
|
||||
path: "permission",
|
||||
description:
|
||||
"A string identifier representing an access control permission in OneUptime.",
|
||||
},
|
||||
{
|
||||
name: "Search",
|
||||
path: "search",
|
||||
description:
|
||||
"A query filter for text search that matches objects containing the specified string.",
|
||||
},
|
||||
{
|
||||
name: "GreaterThan",
|
||||
path: "greater-than",
|
||||
description:
|
||||
"A query filter that matches objects where a field is greater than the specified value.",
|
||||
},
|
||||
{
|
||||
name: "GreaterThanOrEqual",
|
||||
path: "greater-than-or-equal",
|
||||
description:
|
||||
"A query filter that matches objects where a field is greater than or equal to the specified value.",
|
||||
},
|
||||
{
|
||||
name: "GreaterThanOrNull",
|
||||
path: "greater-than-or-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is greater than the specified value or is null.",
|
||||
},
|
||||
{
|
||||
name: "LessThanOrNull",
|
||||
path: "less-than-or-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is less than the specified value or is null.",
|
||||
},
|
||||
{
|
||||
name: "LessThan",
|
||||
path: "less-than",
|
||||
description:
|
||||
"A query filter that matches objects where a field is less than the specified value.",
|
||||
},
|
||||
{
|
||||
name: "LessThanOrEqual",
|
||||
path: "less-than-or-equal",
|
||||
description:
|
||||
"A query filter that matches objects where a field is less than or equal to the specified value.",
|
||||
},
|
||||
{
|
||||
name: "Port",
|
||||
path: "port",
|
||||
description: "A network port number type (1-65535).",
|
||||
},
|
||||
{
|
||||
name: "Hostname",
|
||||
path: "hostname",
|
||||
description: "A hostname type (e.g., api.example.com).",
|
||||
},
|
||||
{
|
||||
name: "HashedString",
|
||||
path: "hashed-string",
|
||||
description:
|
||||
"A string that is stored in hashed form. Used for sensitive data like passwords and API keys.",
|
||||
},
|
||||
{
|
||||
name: "DateTime",
|
||||
path: "date-time",
|
||||
description:
|
||||
"An ISO 8601 date-time string (e.g., 2024-01-15T10:30:00.000Z).",
|
||||
columnTypeAliases: ["Date"],
|
||||
},
|
||||
{
|
||||
name: "Buffer",
|
||||
path: "buffer",
|
||||
description:
|
||||
"A binary data buffer, typically base64-encoded when serialized to JSON.",
|
||||
},
|
||||
{
|
||||
name: "InBetween",
|
||||
path: "in-between",
|
||||
description:
|
||||
"A query filter that matches objects where a field value is between two specified values (inclusive).",
|
||||
},
|
||||
{
|
||||
name: "NotNull",
|
||||
path: "not-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is not null.",
|
||||
},
|
||||
{
|
||||
name: "IsNull",
|
||||
path: "is-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is null.",
|
||||
},
|
||||
{
|
||||
name: "Includes",
|
||||
path: "includes",
|
||||
description:
|
||||
"A query filter that matches objects where a field value is included in the specified array of values.",
|
||||
},
|
||||
{
|
||||
name: "DashboardComponent",
|
||||
path: "dashboard-component",
|
||||
description:
|
||||
"A configuration object for a dashboard component including its type, layout, and settings.",
|
||||
},
|
||||
{
|
||||
name: "DashboardViewConfig",
|
||||
path: "dashboard-view-config",
|
||||
description:
|
||||
"A configuration object for a dashboard view including its components and layout.",
|
||||
},
|
||||
{
|
||||
name: "CriteriaFilter",
|
||||
path: "criteria-filter",
|
||||
description:
|
||||
"A single filter condition within a MonitorCriteriaInstance that defines what to check and how to compare it.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "CriteriaIncident",
|
||||
path: "criteria-incident",
|
||||
description:
|
||||
"Configuration for an incident that is automatically created when a MonitorCriteriaInstance's conditions are met.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "CriteriaAlert",
|
||||
path: "criteria-alert",
|
||||
description:
|
||||
"Configuration for an alert that is automatically created when a MonitorCriteriaInstance's conditions are met.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "CheckOn",
|
||||
path: "check-on",
|
||||
description:
|
||||
"Enum specifying what aspect of a monitor response to evaluate (e.g., response code, response time, body content).",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "FilterType",
|
||||
path: "filter-type",
|
||||
description:
|
||||
"Enum specifying the comparison operator used in a CriteriaFilter (e.g., Equal To, Greater Than, Contains).",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "FilterCondition",
|
||||
path: "filter-condition",
|
||||
description:
|
||||
"Enum specifying how multiple filters are combined: 'All' (AND) or 'Any' (OR).",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStepLogMonitor",
|
||||
path: "monitor-step-log-monitor",
|
||||
description:
|
||||
"Configuration for a Log monitor step, defining which logs to query and evaluate.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStepTraceMonitor",
|
||||
path: "monitor-step-trace-monitor",
|
||||
description:
|
||||
"Configuration for a Trace monitor step, defining which spans to query and evaluate.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStepMetricMonitor",
|
||||
path: "monitor-step-metric-monitor",
|
||||
description:
|
||||
"Configuration for a Metric monitor step, defining which metrics to query and evaluate.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStepSnmpMonitor",
|
||||
path: "monitor-step-snmp-monitor",
|
||||
description:
|
||||
"Configuration for an SNMP monitor step, defining the SNMP device connection and OIDs to query.",
|
||||
category: "Monitor",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public static getDataTypesByCategory(): Array<DataTypeCategory> {
|
||||
const allTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
const uncategorized: Array<DataTypeDocumentation> = [];
|
||||
const categoryMap: Dictionary<Array<DataTypeDocumentation>> = {};
|
||||
const categoryOrder: Array<string> = [];
|
||||
|
||||
for (const dt of allTypes) {
|
||||
if (dt.category) {
|
||||
if (!categoryMap[dt.category]) {
|
||||
categoryMap[dt.category] = [];
|
||||
categoryOrder.push(dt.category);
|
||||
}
|
||||
categoryMap[dt.category]!.push(dt);
|
||||
} else {
|
||||
uncategorized.push(dt);
|
||||
}
|
||||
}
|
||||
|
||||
const result: Array<DataTypeCategory> = [];
|
||||
|
||||
// Add uncategorized types first under "General"
|
||||
if (uncategorized.length > 0) {
|
||||
result.push({ name: "General", types: uncategorized });
|
||||
}
|
||||
|
||||
// Add categorized groups
|
||||
for (const cat of categoryOrder) {
|
||||
if (categoryMap[cat]) {
|
||||
result.push({ name: cat, types: categoryMap[cat]! });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static getDataTypeDictionaryByPath(): Dictionary<DataTypeDocumentation> {
|
||||
const dict: Dictionary<DataTypeDocumentation> = {};
|
||||
|
||||
for (const dataType of DataTypeUtil.getDataTypes()) {
|
||||
dict[dataType.path] = dataType;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert PascalCase name to space-separated display string.
|
||||
* e.g., "ObjectID" → "Object ID", "MonitorSteps" → "Monitor Steps",
|
||||
* "HashedString" → "Hashed String", "IP" → "IP"
|
||||
*/
|
||||
private static pascalCaseToDisplayString(name: string): string {
|
||||
return name
|
||||
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
||||
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
|
||||
}
|
||||
|
||||
/*
|
||||
* Build a mapping from column type display strings to data type page paths.
|
||||
* Automatically derives both PascalCase and display-string variants from each
|
||||
* data type's name, so adding a new entry to getDataTypes() is all that's needed.
|
||||
*/
|
||||
public static getTypeToDocPathMap(): Dictionary<string> {
|
||||
const map: Dictionary<string> = {};
|
||||
|
||||
for (const dt of DataTypeUtil.getDataTypes()) {
|
||||
// Map PascalCase name: "ObjectID" → "object-id"
|
||||
map[dt.name] = dt.path;
|
||||
|
||||
// Map display string: "Object ID" → "object-id"
|
||||
const displayName: string = DataTypeUtil.pascalCaseToDisplayString(
|
||||
dt.name,
|
||||
);
|
||||
if (displayName !== dt.name) {
|
||||
map[displayName] = dt.path;
|
||||
}
|
||||
|
||||
// Map any explicit aliases (for edge cases like enum "Date" → "date-time")
|
||||
if (dt.columnTypeAliases) {
|
||||
for (const alias of dt.columnTypeAliases) {
|
||||
map[alias] = dt.path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
7
APIReference/package-lock.json
generated
7
APIReference/package-lock.json
generated
@@ -73,6 +73,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"expo-server-sdk": "^3.15.0",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
"history": "^5.3.0",
|
||||
@@ -278,6 +279,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz",
|
||||
"integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.23.5",
|
||||
@@ -1390,7 +1392,8 @@
|
||||
"node_modules/@types/node": {
|
||||
"version": "17.0.45",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
|
||||
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="
|
||||
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/prettier": {
|
||||
"version": "2.7.3",
|
||||
@@ -1661,6 +1664,7 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001565",
|
||||
"electron-to-chromium": "^1.4.601",
|
||||
@@ -4008,6 +4012,7 @@
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
|
||||
179
APIReference/views/main/data-type.ejs
Normal file
179
APIReference/views/main/data-type.ejs
Normal file
@@ -0,0 +1,179 @@
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Type Hierarchy Breadcrumb -->
|
||||
<% if (pageData.typeHierarchy && pageData.typeHierarchy.length > 0) { %>
|
||||
<nav class="mb-6">
|
||||
<ol class="flex items-center flex-wrap gap-1 text-sm">
|
||||
<% for (var h = 0; h < pageData.typeHierarchy.length; h++) { %>
|
||||
<% if (h > 0) { %>
|
||||
<li class="text-slate-400">
|
||||
<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="M9 5l7 7-7 7"></path></svg>
|
||||
</li>
|
||||
<% } %>
|
||||
<% if (pageData.typeHierarchy[h].path && pageData.typeHierarchy[h].name !== pageData.title) { %>
|
||||
<li><a href="/reference/<%= pageData.typeHierarchy[h].path %>" class="text-indigo-600 hover:text-indigo-700 hover:underline font-medium"><%= pageData.typeHierarchy[h].name %></a></li>
|
||||
<% } else { %>
|
||||
<li class="text-slate-900 font-semibold"><%= pageData.typeHierarchy[h].name %></li>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</ol>
|
||||
</nav>
|
||||
<% } %>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-emerald-600 shadow-lg shadow-emerald-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="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-emerald-600 uppercase tracking-wider"><%= pageData.isEnum ? 'Enum' : 'Data Type' %></span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3"><%= pageData.title %></h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl"><%- pageData.description %></p>
|
||||
</div>
|
||||
|
||||
<!-- At a Glance Summary Box -->
|
||||
<div class="mb-10 rounded-xl border border-slate-200 bg-slate-50 p-5">
|
||||
<h4 class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3">At a Glance</h4>
|
||||
<div class="flex flex-wrap gap-x-8 gap-y-3 text-sm">
|
||||
<div>
|
||||
<span class="text-slate-500">Kind</span>
|
||||
<span class="ml-2 font-medium text-slate-900"><%= pageData.isEnum ? 'Enum' : 'Data Type' %></span>
|
||||
</div>
|
||||
<% if (pageData.isEnum && pageData.valueCount > 0) { %>
|
||||
<div>
|
||||
<span class="text-slate-500">Values</span>
|
||||
<span class="ml-2 font-medium text-slate-900"><%= pageData.valueCount %></span>
|
||||
</div>
|
||||
<% } else if (pageData.propertyCount > 0) { %>
|
||||
<div>
|
||||
<span class="text-slate-500">Properties</span>
|
||||
<span class="ml-2 font-medium text-slate-900"><%= pageData.propertyCount %></span>
|
||||
</div>
|
||||
<% } %>
|
||||
<div>
|
||||
<span class="text-slate-500">JSON Format</span>
|
||||
<code class="ml-2 text-xs font-mono bg-white px-2 py-0.5 rounded border border-slate-200 text-slate-700">{"_type": "<%= pageData.jsonWrapperType %>", "value": ...}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (pageData.isEnum) { %>
|
||||
<!-- Quick Navigation for Enum Values -->
|
||||
<% if (pageData.values.length >= 4) { %>
|
||||
<div class="mb-6">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<% for (var qv = 0; qv < pageData.values.length; qv++) { %>
|
||||
<a href="#value-<%= pageData.values[qv].value.toLowerCase().replace(/\s+/g, '-') %>" class="rounded-md bg-white px-2.5 py-1 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-200 hover:bg-slate-50 hover:text-slate-900 transition-colors no-underline"><%= pageData.values[qv].value %></a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<h3 id="values" class="text-base font-semibold text-slate-800 mb-4 scroll-mt-24">Possible Values</h3>
|
||||
<div class="my-6 rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 w-full list-none divide-y divide-slate-100 p-0">
|
||||
<% for (var v = 0; v < pageData.values.length; v++) { %>
|
||||
<li id="value-<%= pageData.values[v].value.toLowerCase().replace(/\s+/g, '-') %>" class="m-0 px-5 py-5 hover:bg-slate-50/50 transition-colors scroll-mt-24">
|
||||
<dl class="m-0 flex flex-wrap items-start gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Index</dt>
|
||||
<dd class="text-xs font-mono text-slate-300 w-6 pt-0.5 flex-shrink-0"><%= v + 1 %>.</dd>
|
||||
<dt class="sr-only">Value</dt>
|
||||
<dd><code class="model-inline-code"><%= pageData.values[v].value %></code></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none pl-9 text-sm text-slate-600"><%- pageData.values[v].description %></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
<% } else if (pageData.properties && pageData.properties.length > 0) { %>
|
||||
<!-- Quick Navigation for Properties -->
|
||||
<% if (pageData.properties.length >= 4) { %>
|
||||
<div class="mb-6">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<% for (var qp = 0; qp < pageData.properties.length; qp++) { %>
|
||||
<a href="#prop-<%= pageData.properties[qp].name %>" class="rounded-md bg-white px-2.5 py-1 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-200 hover:bg-slate-50 hover:text-slate-900 transition-colors no-underline"><%= pageData.properties[qp].name %></a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<h3 id="properties" class="text-base font-semibold text-slate-800 mb-4 scroll-mt-24">Properties</h3>
|
||||
<div class="my-6 rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 w-full list-none divide-y divide-slate-100 p-0">
|
||||
<% for (var p = 0; p < pageData.properties.length; p++) { %>
|
||||
<li id="prop-<%= pageData.properties[p].name %>" class="m-0 px-5 py-5 hover:bg-slate-50/50 transition-colors scroll-mt-24">
|
||||
<div class="mb-1.5">
|
||||
<code class="text-sm font-semibold text-slate-900"><%= pageData.properties[p].name %></code>
|
||||
<% if (pageData.properties[p].required) { %>
|
||||
<span class="ml-2 inline-flex items-center rounded-full bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-700 ring-1 ring-inset ring-amber-600/20">Required</span>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="inline-flex items-center rounded-md bg-slate-100 px-2 py-0.5 text-xs font-mono text-slate-600">
|
||||
<% if (pageData.properties[p].typeLinks && pageData.properties[p].typeLinks.length > 0) { %>
|
||||
<% var typeLinks = pageData.properties[p].typeLinks; %>
|
||||
<% for (var tl = 0; tl < typeLinks.length; tl++) { %>
|
||||
<% if (typeLinks[tl].path) { %>
|
||||
<a href="/reference/<%= typeLinks[tl].path %>" class="text-indigo-600 hover:text-indigo-700 hover:underline font-medium"><%= typeLinks[tl].label %></a>
|
||||
<% } else { %>
|
||||
<%= typeLinks[tl].label %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
<%= pageData.properties[p].type %>
|
||||
<% } %>
|
||||
</span>
|
||||
</div>
|
||||
<p class="m-0 text-sm text-slate-600 leading-relaxed"><%- pageData.properties[p].description %></p>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- JSON Example Section -->
|
||||
<div class="border-t border-slate-200 pt-8 mt-10">
|
||||
<h3 id="example" class="text-base font-semibold text-slate-800 mb-4 scroll-mt-24">JSON Example</h3>
|
||||
<%- include('../partials/code', { title: "JSON", code: pageData.jsonExample, requestType: "", requestUrl: "" }) %>
|
||||
</div>
|
||||
|
||||
<% if (pageData.title === "Permission") { %>
|
||||
<div class="mt-6 rounded-lg border border-indigo-100 bg-indigo-50 p-4">
|
||||
<p class="text-sm text-indigo-800 m-0">
|
||||
For a complete list of all available permissions and their descriptions, see the
|
||||
<a href="/reference/permissions" class="font-medium text-indigo-600 hover:text-indigo-700 hover:underline">Permissions guide</a>.
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- Related Types Section -->
|
||||
<% if (pageData.relatedTypes && pageData.relatedTypes.length > 0) { %>
|
||||
<div class="border-t border-slate-200 pt-8 mt-10">
|
||||
<h3 id="related-types" class="text-base font-semibold text-slate-800 mb-4 scroll-mt-24">Related Types</h3>
|
||||
<div class="my-6 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<% for (var r = 0; r < pageData.relatedTypes.length; r++) { %>
|
||||
<a href="/reference/<%= pageData.relatedTypes[r].path %>" class="group flex items-start gap-3 rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-sm transition-all no-underline">
|
||||
<div class="flex-shrink-0 mt-0.5">
|
||||
<div class="flex items-center justify-center w-8 h-8 rounded-lg bg-indigo-50 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-4 h-4 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors"><%= pageData.relatedTypes[r].name %></div>
|
||||
<div class="text-xs text-slate-500 mt-0.5"><%= pageData.relatedTypes[r].relationship %></div>
|
||||
<% if (pageData.relatedTypes[r].description) { %>
|
||||
<div class="text-xs text-slate-400 mt-1 line-clamp-2"><%= pageData.relatedTypes[r].description %></div>
|
||||
<% } %>
|
||||
</div>
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
@@ -47,7 +47,13 @@
|
||||
<dd><code class="model-inline-code"><%= Object.keys(pageData.columns)[i] -%></code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500">
|
||||
<%= pageData.columns[Object.keys(pageData.columns)[i]].type -%>
|
||||
<% if(pageData.columns[Object.keys(pageData.columns)[i]].modelDocumentationPath) { %>
|
||||
<a href="/reference/<%= pageData.columns[Object.keys(pageData.columns)[i]].modelDocumentationPath -%>" class="text-indigo-600 hover:text-indigo-700 hover:underline font-medium"><%= pageData.columns[Object.keys(pageData.columns)[i]].modelName -%></a>
|
||||
<% } else if(pageData.columns[Object.keys(pageData.columns)[i]].typeDocumentationPath) { %>
|
||||
<a href="/reference/<%= pageData.columns[Object.keys(pageData.columns)[i]].typeDocumentationPath -%>" class="text-indigo-600 hover:text-indigo-700 hover:underline font-medium"><%= pageData.columns[Object.keys(pageData.columns)[i]].type -%></a>
|
||||
<% } else { %>
|
||||
<%= pageData.columns[Object.keys(pageData.columns)[i]].type -%>
|
||||
<% } %>
|
||||
<% if(pageData.columns[Object.keys(pageData.columns)[i]].required){ %>
|
||||
<span class="ml-1.5 inline-flex items-center rounded-full bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-700 ring-1 ring-inset ring-amber-600/20">Required</span>
|
||||
<% } %>
|
||||
|
||||
@@ -14,23 +14,49 @@
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<!-- Quick Navigation -->
|
||||
<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>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-indigo-900 mb-2">Jump to a category</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<% for(var g=0; g<pageData.permissionGroups.length; g++) { %>
|
||||
<a href="#<%= pageData.permissionGroups[g].group.toLowerCase().replace(/ /g, '-') -%>"
|
||||
class="inline-flex items-center gap-1.5 rounded-md bg-white px-2.5 py-1 text-xs font-medium text-slate-700 ring-1 ring-inset ring-slate-200 hover:bg-indigo-50 hover:text-indigo-700 hover:ring-indigo-200 transition-colors">
|
||||
<%= pageData.permissionGroups[g].group %>
|
||||
<span class="text-slate-400">(<%= pageData.permissionGroups[g].permissions.length %>)</span>
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% for(var g=0; g<pageData.permissionGroups.length; g++) { %>
|
||||
<h2 id="<%= pageData.permissionGroups[g].group.toLowerCase().replace(/ /g, '-') -%>"
|
||||
class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 <%= g > 0 ? 'pt-8 border-t border-slate-200' : '' %>">
|
||||
<%= pageData.permissionGroups[g].group %>
|
||||
<span class="ml-2 align-middle inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-medium text-slate-600"><%= pageData.permissionGroups[g].permissions.length %></span>
|
||||
</h2>
|
||||
|
||||
<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">
|
||||
<% for(var i=0; i<pageData.permissionGroups[g].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.permissionGroups[g].permissions[i].permission -%></code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500"><%= pageData.permissionGroups[g].permissions[i].title -%></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1"><%= pageData.permissionGroups[g].permissions[i].description -%></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
<% } %>
|
||||
</article>
|
||||
</main>
|
||||
</main>
|
||||
|
||||
@@ -406,6 +406,52 @@
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<% if (typeof dataTypes !== 'undefined' && dataTypes && dataTypes.length > 0) { %>
|
||||
<%
|
||||
// Group data types by category
|
||||
var _dtGeneral = [];
|
||||
var _dtCategories = {};
|
||||
var _dtCatOrder = [];
|
||||
for (var _di = 0; _di < dataTypes.length; _di++) {
|
||||
var _dt = dataTypes[_di];
|
||||
if (_dt.category) {
|
||||
if (!_dtCategories[_dt.category]) {
|
||||
_dtCategories[_dt.category] = [];
|
||||
_dtCatOrder.push(_dt.category);
|
||||
}
|
||||
_dtCategories[_dt.category].push(_dt);
|
||||
} else {
|
||||
_dtGeneral.push(_dt);
|
||||
}
|
||||
}
|
||||
%>
|
||||
<li class="relative mt-6">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide">Data Types</h6>
|
||||
<div class="relative mt-3 pl-2">
|
||||
<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<_dtGeneral.length; i++) {%>
|
||||
<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/<%= _dtGeneral[i].path -%>"><span class="truncate"><%= _dtGeneral[i].name -%></span></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% for (var _ci = 0; _ci < _dtCatOrder.length; _ci++) { %>
|
||||
<% var _catName = _dtCatOrder[_ci]; var _catTypes = _dtCategories[_catName]; %>
|
||||
<div class="mt-3 mb-1 pl-4">
|
||||
<span class="text-[10px] font-semibold text-slate-400 uppercase tracking-wider"><%= _catName %></span>
|
||||
</div>
|
||||
<ul role="list" class="border-l border-transparent space-y-1">
|
||||
<% for(var _ti=0; _ti<_catTypes.length; _ti++) {%>
|
||||
<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/<%= _catTypes[_ti].path -%>"><span class="truncate"><%= _catTypes[_ti].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-indigo-600 py-1.5 px-4 text-white hover:bg-indigo-700 shadow-sm w-full"
|
||||
href="/#">Sign in</a></li>
|
||||
@@ -537,6 +583,45 @@
|
||||
<% } %>
|
||||
</ul>
|
||||
</li>
|
||||
<% if (typeof dataTypes !== 'undefined' && dataTypes && dataTypes.length > 0) { %>
|
||||
<%
|
||||
// Group data types by category for mobile nav
|
||||
var _mGeneral = [];
|
||||
var _mCategories = {};
|
||||
var _mCatOrder = [];
|
||||
for (var _mi = 0; _mi < dataTypes.length; _mi++) {
|
||||
var _mdt = dataTypes[_mi];
|
||||
if (_mdt.category) {
|
||||
if (!_mCategories[_mdt.category]) {
|
||||
_mCategories[_mdt.category] = [];
|
||||
_mCatOrder.push(_mdt.category);
|
||||
}
|
||||
_mCategories[_mdt.category].push(_mdt);
|
||||
} else {
|
||||
_mGeneral.push(_mdt);
|
||||
}
|
||||
}
|
||||
%>
|
||||
<li class="relative">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide mb-3">Data Types</h6>
|
||||
<ul role="list" class="space-y-1 border-l border-slate-200 ml-2">
|
||||
<% for(var i=0; i<_mGeneral.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/<%= _mGeneral[i].path -%>"><%= _mGeneral[i].name -%></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% for (var _mci = 0; _mci < _mCatOrder.length; _mci++) { %>
|
||||
<% var _mCatName = _mCatOrder[_mci]; var _mCatTypes = _mCategories[_mCatName]; %>
|
||||
<div class="mt-3 mb-1 ml-4">
|
||||
<span class="text-[10px] font-semibold text-slate-400 uppercase tracking-wider"><%= _mCatName %></span>
|
||||
</div>
|
||||
<ul role="list" class="space-y-1 border-l border-slate-200 ml-2">
|
||||
<% for(var _mti=0; _mti<_mCatTypes.length; _mti++) {%>
|
||||
<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/<%= _mCatTypes[_mti].path -%>"><%= _mCatTypes[_mti].name -%></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% } %>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
||||
26
Accounts/index.d.ts
vendored
26
Accounts/index.d.ts
vendored
@@ -2,3 +2,29 @@ declare module "*.png";
|
||||
declare module "*.svg";
|
||||
declare module "*.jpg";
|
||||
declare module "*.gif";
|
||||
|
||||
declare module "react-syntax-highlighter/dist/esm/prism-light";
|
||||
declare module "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/javascript";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/typescript";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/jsx";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/tsx";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/python";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/bash";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/json";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/yaml";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/sql";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/go";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/java";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/css";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/markup";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/markdown";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/docker";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/rust";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/c";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/cpp";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/csharp";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/ruby";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/php";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/graphql";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/http";
|
||||
|
||||
1
Accounts/package-lock.json
generated
1
Accounts/package-lock.json
generated
@@ -77,6 +77,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"expo-server-sdk": "^3.15.0",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
"history": "^5.3.0",
|
||||
|
||||
@@ -32,6 +32,7 @@ import Reseller from "Common/Models/DatabaseModels/Reseller";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import React, { useState } from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
|
||||
const RegisterPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = SIGNUP_API_URL;
|
||||
@@ -172,6 +173,36 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!BILLING_ENABLED) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
selfHostedCompanyName: true,
|
||||
},
|
||||
overrideFieldKey: "selfHostedCompanyName",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Acme, Inc.",
|
||||
required: false,
|
||||
title: "Company Name",
|
||||
dataTestId: "selfHostedCompanyName",
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
overrideField: {
|
||||
selfHostedPhoneNumber: true,
|
||||
},
|
||||
overrideFieldKey: "selfHostedPhoneNumber",
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: false,
|
||||
placeholder: "+11234567890",
|
||||
title: "Phone Number",
|
||||
dataTestId: "selfHostedPhoneNumber",
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
field: {
|
||||
@@ -206,6 +237,25 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
},
|
||||
]);
|
||||
|
||||
if (!IsBillingEnabled) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
notifySelfHosted: true,
|
||||
},
|
||||
overrideFieldKey: "notifySelfHosted",
|
||||
|
||||
fieldType: FormFieldSchemaType.Checkbox,
|
||||
required: false,
|
||||
defaultValue: true,
|
||||
title: "Notify me about security patches and new releases",
|
||||
dataTestId: "notifySelfHosted",
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
spanFullRow: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if (isCaptchaEnabled) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
@@ -330,6 +380,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture("accounts/register");
|
||||
UiAnalytics.capture("sign_up");
|
||||
}
|
||||
|
||||
LoginUtil.login({
|
||||
|
||||
26
AdminDashboard/index.d.ts
vendored
26
AdminDashboard/index.d.ts
vendored
@@ -2,3 +2,29 @@ declare module "*.png";
|
||||
declare module "*.svg";
|
||||
declare module "*.jpg";
|
||||
declare module "*.gif";
|
||||
|
||||
declare module "react-syntax-highlighter/dist/esm/prism-light";
|
||||
declare module "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/javascript";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/typescript";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/jsx";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/tsx";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/python";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/bash";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/json";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/yaml";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/sql";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/go";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/java";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/css";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/markup";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/markdown";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/docker";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/rust";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/c";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/cpp";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/csharp";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/ruby";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/php";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/graphql";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/http";
|
||||
|
||||
1
AdminDashboard/package-lock.json
generated
1
AdminDashboard/package-lock.json
generated
@@ -76,6 +76,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"expo-server-sdk": "^3.15.0",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
"history": "^5.3.0",
|
||||
|
||||
@@ -12,7 +12,6 @@ import Toggle from "Common/UI/Components/Toggle/Toggle";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import { BILLING_ENABLED, getAllEnvVars } from "Common/UI/Config";
|
||||
import { GetReactElementFunction } from "Common/UI/Types/FunctionTypes";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import Project from "Common/Models/DatabaseModels/Project";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import React, {
|
||||
@@ -21,6 +20,7 @@ import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
|
||||
const Projects: FunctionComponent = (): ReactElement => {
|
||||
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
@@ -17,6 +18,7 @@ const DeletePage: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="name"
|
||||
modelType={Project}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"Project"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -41,6 +43,7 @@ const DeletePage: FunctionComponent = (): ReactElement => {
|
||||
<ModelDelete
|
||||
modelType={Project}
|
||||
modelId={modelId}
|
||||
modelAPI={AdminModelAPI}
|
||||
onDeleteSuccess={() => {
|
||||
Navigation.navigate(RouteMap[PageMap.PROJECTS] as Route);
|
||||
}}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
@@ -19,6 +20,7 @@ const Projects: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="name"
|
||||
modelType={Project}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"Project"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -43,6 +45,7 @@ const Projects: FunctionComponent = (): ReactElement => {
|
||||
<div>
|
||||
<CardModelDetail<Project>
|
||||
name="Project"
|
||||
modelAPI={AdminModelAPI}
|
||||
cardProps={{
|
||||
title: "Project",
|
||||
description: "Project details",
|
||||
|
||||
@@ -73,6 +73,46 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
|
||||
<CardModelDetail
|
||||
name="Project Creation Settings"
|
||||
cardProps={{
|
||||
title: "Project Creation",
|
||||
description:
|
||||
"Control who can create new projects on this OneUptime Server.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
disableUserProjectCreation: true,
|
||||
},
|
||||
title: "Restrict Project Creation to Admins Only",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
description:
|
||||
"When enabled, only master admin users can create new projects.",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-project-creation",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
disableUserProjectCreation: true,
|
||||
},
|
||||
fieldType: FieldType.Boolean,
|
||||
title: "Restrict Project Creation to Admins Only",
|
||||
placeholder: "No",
|
||||
description:
|
||||
"When enabled, only master admin users can create new projects.",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
@@ -17,6 +18,7 @@ const DeletePage: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="email"
|
||||
modelType={User}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"User"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -39,6 +41,7 @@ const DeletePage: FunctionComponent = (): ReactElement => {
|
||||
<ModelDelete
|
||||
modelType={User}
|
||||
modelId={modelId}
|
||||
modelAPI={AdminModelAPI}
|
||||
onDeleteSuccess={() => {
|
||||
Navigation.navigate(RouteMap[PageMap.USERS] as Route);
|
||||
}}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
@@ -19,6 +20,7 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="email"
|
||||
modelType={User}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"User"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -41,6 +43,7 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
<div>
|
||||
<CardModelDetail<User>
|
||||
name="User"
|
||||
modelAPI={AdminModelAPI}
|
||||
cardProps={{
|
||||
title: "User",
|
||||
description: "User details",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
@@ -19,6 +20,7 @@ const UserSettings: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="email"
|
||||
modelType={User}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"User"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -52,6 +54,7 @@ const UserSettings: FunctionComponent = (): ReactElement => {
|
||||
>
|
||||
<CardModelDetail<User>
|
||||
name="user-master-admin-settings"
|
||||
modelAPI={AdminModelAPI}
|
||||
cardProps={{
|
||||
title: "Master Admin Access",
|
||||
description:
|
||||
|
||||
@@ -23,6 +23,7 @@ import WhatsAppLogAPI from "./WhatsAppLogAPI";
|
||||
// Import API
|
||||
import ResellerPlanAPI from "Common/Server/API/ResellerPlanAPI";
|
||||
import EnterpriseLicenseAPI from "Common/Server/API/EnterpriseLicenseAPI";
|
||||
import OpenSourceDeploymentAPI from "Common/Server/API/OpenSourceDeploymentAPI";
|
||||
import MonitorAPI from "Common/Server/API/MonitorAPI";
|
||||
import ShortLinkAPI from "Common/Server/API/ShortLinkAPI";
|
||||
import StatusPageAPI from "Common/Server/API/StatusPageAPI";
|
||||
@@ -35,9 +36,11 @@ import UserWebAuthnAPI from "Common/Server/API/UserWebAuthnAPI";
|
||||
import MonitorTest from "Common/Models/DatabaseModels/MonitorTest";
|
||||
import IncidentInternalNoteAPI from "Common/Server/API/IncidentInternalNoteAPI";
|
||||
import IncidentPublicNoteAPI from "Common/Server/API/IncidentPublicNoteAPI";
|
||||
import IncidentEpisodePublicNoteAPI from "Common/Server/API/IncidentEpisodePublicNoteAPI";
|
||||
import ScheduledMaintenanceInternalNoteAPI from "Common/Server/API/ScheduledMaintenanceInternalNoteAPI";
|
||||
import ScheduledMaintenancePublicNoteAPI from "Common/Server/API/ScheduledMaintenancePublicNoteAPI";
|
||||
import IncidentAPI from "Common/Server/API/IncidentAPI";
|
||||
import IncidentEpisodeAPI from "Common/Server/API/IncidentEpisodeAPI";
|
||||
import ScheduledMaintenanceAPI from "Common/Server/API/ScheduledMaintenanceAPI";
|
||||
import AlertAPI from "Common/Server/API/AlertAPI";
|
||||
// User Notification methods.
|
||||
@@ -128,10 +131,48 @@ import AlertEpisodeOwnerUserService, {
|
||||
import AlertEpisodeStateTimelineService, {
|
||||
Service as AlertEpisodeStateTimelineServiceType,
|
||||
} from "Common/Server/Services/AlertEpisodeStateTimelineService";
|
||||
|
||||
// IncidentEpisode Services
|
||||
|
||||
import IncidentEpisodeFeedService, {
|
||||
Service as IncidentEpisodeFeedServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeFeedService";
|
||||
import IncidentEpisodeInternalNoteService, {
|
||||
Service as IncidentEpisodeInternalNoteServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeInternalNoteService";
|
||||
import IncidentEpisodeMemberService, {
|
||||
Service as IncidentEpisodeMemberServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeMemberService";
|
||||
import IncidentEpisodeOwnerTeamService, {
|
||||
Service as IncidentEpisodeOwnerTeamServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeOwnerTeamService";
|
||||
import IncidentEpisodeOwnerUserService, {
|
||||
Service as IncidentEpisodeOwnerUserServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeOwnerUserService";
|
||||
import IncidentEpisodeStateTimelineService, {
|
||||
Service as IncidentEpisodeStateTimelineServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeStateTimelineService";
|
||||
|
||||
import IncidentEpisodeRoleMemberService, {
|
||||
Service as IncidentEpisodeRoleMemberServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeRoleMemberService";
|
||||
|
||||
import AlertGroupingRuleService, {
|
||||
Service as AlertGroupingRuleServiceType,
|
||||
} from "Common/Server/Services/AlertGroupingRuleService";
|
||||
|
||||
import IncidentGroupingRuleService, {
|
||||
Service as IncidentGroupingRuleServiceType,
|
||||
} from "Common/Server/Services/IncidentGroupingRuleService";
|
||||
|
||||
import IncidentSlaService, {
|
||||
Service as IncidentSlaServiceType,
|
||||
} from "Common/Server/Services/IncidentSlaService";
|
||||
|
||||
import IncidentSlaRuleService, {
|
||||
Service as IncidentSlaRuleServiceType,
|
||||
} from "Common/Server/Services/IncidentSlaRuleService";
|
||||
|
||||
import IncidentCustomFieldService, {
|
||||
Service as IncidentCustomFieldServiceType,
|
||||
} from "Common/Server/Services/IncidentCustomFieldService";
|
||||
@@ -153,6 +194,12 @@ import IncidentOwnerUserService, {
|
||||
import IncidentSeverityService, {
|
||||
Service as IncidentSeverityServiceType,
|
||||
} from "Common/Server/Services/IncidentSeverityService";
|
||||
import IncidentRoleService, {
|
||||
Service as IncidentRoleServiceType,
|
||||
} from "Common/Server/Services/IncidentRoleService";
|
||||
import IncidentMemberService, {
|
||||
Service as IncidentMemberServiceType,
|
||||
} from "Common/Server/Services/IncidentMemberService";
|
||||
import IncidentStateService, {
|
||||
Service as IncidentStateServiceType,
|
||||
} from "Common/Server/Services/IncidentStateService";
|
||||
@@ -263,6 +310,9 @@ import OnCallDutyPolicyScheduleService, {
|
||||
import ProjectCallSMSConfigService, {
|
||||
Service as ProjectCallSMSConfigServiceType,
|
||||
} from "Common/Server/Services/ProjectCallSMSConfigService";
|
||||
import ProjectUserProfileService, {
|
||||
Service as ProjectUserProfileServiceType,
|
||||
} from "Common/Server/Services/ProjectUserProfileService";
|
||||
import ProjectSmtpConfigService, {
|
||||
Service as ProjectSMTPConfigServiceType,
|
||||
} from "Common/Server/Services/ProjectSmtpConfigService";
|
||||
@@ -362,6 +412,9 @@ import StatusPageSSOService, {
|
||||
import TeamMemberService, {
|
||||
TeamMemberService as TeamMemberServiceType,
|
||||
} from "Common/Server/Services/TeamMemberService";
|
||||
import TeamMemberCustomFieldService, {
|
||||
Service as TeamMemberCustomFieldServiceType,
|
||||
} from "Common/Server/Services/TeamMemberCustomFieldService";
|
||||
import TeamPermissionService, {
|
||||
Service as TeamPermissionServiceType,
|
||||
} from "Common/Server/Services/TeamPermissionService";
|
||||
@@ -457,6 +510,18 @@ import AlertEpisodeOwnerTeam from "Common/Models/DatabaseModels/AlertEpisodeOwne
|
||||
import AlertEpisodeOwnerUser from "Common/Models/DatabaseModels/AlertEpisodeOwnerUser";
|
||||
import AlertEpisodeStateTimeline from "Common/Models/DatabaseModels/AlertEpisodeStateTimeline";
|
||||
import AlertGroupingRule from "Common/Models/DatabaseModels/AlertGroupingRule";
|
||||
import IncidentGroupingRule from "Common/Models/DatabaseModels/IncidentGroupingRule";
|
||||
import IncidentSla from "Common/Models/DatabaseModels/IncidentSla";
|
||||
import IncidentSlaRule from "Common/Models/DatabaseModels/IncidentSlaRule";
|
||||
|
||||
// IncidentEpisode Models
|
||||
import IncidentEpisodeFeed from "Common/Models/DatabaseModels/IncidentEpisodeFeed";
|
||||
import IncidentEpisodeInternalNote from "Common/Models/DatabaseModels/IncidentEpisodeInternalNote";
|
||||
import IncidentEpisodeMember from "Common/Models/DatabaseModels/IncidentEpisodeMember";
|
||||
import IncidentEpisodeOwnerTeam from "Common/Models/DatabaseModels/IncidentEpisodeOwnerTeam";
|
||||
import IncidentEpisodeOwnerUser from "Common/Models/DatabaseModels/IncidentEpisodeOwnerUser";
|
||||
import IncidentEpisodeStateTimeline from "Common/Models/DatabaseModels/IncidentEpisodeStateTimeline";
|
||||
import IncidentEpisodeRoleMember from "Common/Models/DatabaseModels/IncidentEpisodeRoleMember";
|
||||
|
||||
import IncidentCustomField from "Common/Models/DatabaseModels/IncidentCustomField";
|
||||
import IncidentNoteTemplate from "Common/Models/DatabaseModels/IncidentNoteTemplate";
|
||||
@@ -464,6 +529,8 @@ import IncidentPostmortemTemplate from "Common/Models/DatabaseModels/IncidentPos
|
||||
import IncidentOwnerTeam from "Common/Models/DatabaseModels/IncidentOwnerTeam";
|
||||
import IncidentOwnerUser from "Common/Models/DatabaseModels/IncidentOwnerUser";
|
||||
import IncidentSeverity from "Common/Models/DatabaseModels/IncidentSeverity";
|
||||
import IncidentRole from "Common/Models/DatabaseModels/IncidentRole";
|
||||
import IncidentMember from "Common/Models/DatabaseModels/IncidentMember";
|
||||
import IncidentState from "Common/Models/DatabaseModels/IncidentState";
|
||||
import IncidentStateTimeline from "Common/Models/DatabaseModels/IncidentStateTimeline";
|
||||
import IncidentTemplate from "Common/Models/DatabaseModels/IncidentTemplate";
|
||||
@@ -499,6 +566,7 @@ import OnCallDutyPolicyScheduleLayer from "Common/Models/DatabaseModels/OnCallDu
|
||||
import OnCallDutyPolicyScheduleLayerUser from "Common/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser";
|
||||
import ProjectCallSMSConfig from "Common/Models/DatabaseModels/ProjectCallSMSConfig";
|
||||
import ProjectSmtpConfig from "Common/Models/DatabaseModels/ProjectSmtpConfig";
|
||||
import ProjectUserProfile from "Common/Models/DatabaseModels/ProjectUserProfile";
|
||||
import PromoCode from "Common/Models/DatabaseModels/PromoCode";
|
||||
import CodeRepository from "Common/Models/DatabaseModels/CodeRepository";
|
||||
import Reseller from "Common/Models/DatabaseModels/Reseller";
|
||||
@@ -527,6 +595,7 @@ import StatusPageResource from "Common/Models/DatabaseModels/StatusPageResource"
|
||||
import StatusPageSSO from "Common/Models/DatabaseModels/StatusPageSso";
|
||||
import Team from "Common/Models/DatabaseModels/Team";
|
||||
import TeamMember from "Common/Models/DatabaseModels/TeamMember";
|
||||
import TeamMemberCustomField from "Common/Models/DatabaseModels/TeamMemberCustomField";
|
||||
import TeamPermission from "Common/Models/DatabaseModels/TeamPermission";
|
||||
import TeamComplianceSetting from "Common/Models/DatabaseModels/TeamComplianceSetting";
|
||||
import TelemetryUsageBilling from "Common/Models/DatabaseModels/TelemetryUsageBilling";
|
||||
@@ -1001,6 +1070,77 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// IncidentEpisode Routes
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new IncidentEpisodeAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentEpisodeFeed, IncidentEpisodeFeedServiceType>(
|
||||
IncidentEpisodeFeed,
|
||||
IncidentEpisodeFeedService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncidentEpisodeInternalNote,
|
||||
IncidentEpisodeInternalNoteServiceType
|
||||
>(
|
||||
IncidentEpisodeInternalNote,
|
||||
IncidentEpisodeInternalNoteService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentEpisodeMember, IncidentEpisodeMemberServiceType>(
|
||||
IncidentEpisodeMember,
|
||||
IncidentEpisodeMemberService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncidentEpisodeOwnerTeam,
|
||||
IncidentEpisodeOwnerTeamServiceType
|
||||
>(IncidentEpisodeOwnerTeam, IncidentEpisodeOwnerTeamService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncidentEpisodeOwnerUser,
|
||||
IncidentEpisodeOwnerUserServiceType
|
||||
>(IncidentEpisodeOwnerUser, IncidentEpisodeOwnerUserService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncidentEpisodeStateTimeline,
|
||||
IncidentEpisodeStateTimelineServiceType
|
||||
>(
|
||||
IncidentEpisodeStateTimeline,
|
||||
IncidentEpisodeStateTimelineService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncidentEpisodeRoleMember,
|
||||
IncidentEpisodeRoleMemberServiceType
|
||||
>(
|
||||
IncidentEpisodeRoleMember,
|
||||
IncidentEpisodeRoleMemberService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AlertGroupingRule, AlertGroupingRuleServiceType>(
|
||||
@@ -1009,6 +1149,32 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentGroupingRule, IncidentGroupingRuleServiceType>(
|
||||
IncidentGroupingRule,
|
||||
IncidentGroupingRuleService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// IncidentSla
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentSla, IncidentSlaServiceType>(
|
||||
IncidentSla,
|
||||
IncidentSlaService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// IncidentSlaRule
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentSlaRule, IncidentSlaRuleServiceType>(
|
||||
IncidentSlaRule,
|
||||
IncidentSlaRuleService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAnalyticsAPI<ExceptionInstance, ExceptionInstanceServiceType>(
|
||||
@@ -1264,6 +1430,14 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ProjectUserProfile, ProjectUserProfileServiceType>(
|
||||
ProjectUserProfile,
|
||||
ProjectUserProfileService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<MonitorGroupResource, MonitorGroupResourceServiceType>(
|
||||
@@ -1280,6 +1454,14 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<TeamMemberCustomField, TeamMemberCustomFieldServiceType>(
|
||||
TeamMemberCustomField,
|
||||
TeamMemberCustomFieldService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<TeamPermission, TeamPermissionServiceType>(
|
||||
@@ -1466,6 +1648,22 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentRole, IncidentRoleServiceType>(
|
||||
IncidentRole,
|
||||
IncidentRoleService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentMember, IncidentMemberServiceType>(
|
||||
IncidentMember,
|
||||
IncidentMemberService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentOwnerUser, IncidentOwnerUserServiceType>(
|
||||
@@ -1810,6 +2008,10 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new EnterpriseLicenseAPI().getRouter(),
|
||||
);
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new OpenSourceDeploymentAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new SlackAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
@@ -1944,6 +2146,11 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new IncidentPublicNoteAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new IncidentEpisodePublicNoteAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new IncidentInternalNoteAPI().getRouter(),
|
||||
|
||||
@@ -17,9 +17,11 @@ import ObjectID from "Common/Types/ObjectID";
|
||||
import PositiveNumber from "Common/Types/PositiveNumber";
|
||||
import DatabaseConfig from "Common/Server/DatabaseConfig";
|
||||
import {
|
||||
AppVersion,
|
||||
EncryptionSecret,
|
||||
IsBillingEnabled,
|
||||
} from "Common/Server/EnvironmentConfig";
|
||||
import API from "Common/Utils/API";
|
||||
import AccessTokenService from "Common/Server/Services/AccessTokenService";
|
||||
import EmailVerificationTokenService from "Common/Server/Services/EmailVerificationTokenService";
|
||||
import MailService from "Common/Server/Services/MailService";
|
||||
@@ -29,6 +31,7 @@ import UserSessionService, {
|
||||
SessionMetadata,
|
||||
} from "Common/Server/Services/UserSessionService";
|
||||
import CookieUtil from "Common/Server/Utils/Cookie";
|
||||
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
@@ -54,6 +57,11 @@ const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;
|
||||
|
||||
interface FinalizeUserLoginResult {
|
||||
sessionMetadata: SessionMetadata;
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
type FinalizeUserLoginInput = {
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
@@ -63,9 +71,9 @@ type FinalizeUserLoginInput = {
|
||||
|
||||
const finalizeUserLogin: (
|
||||
data: FinalizeUserLoginInput,
|
||||
) => Promise<SessionMetadata> = async (
|
||||
) => Promise<FinalizeUserLoginResult> = async (
|
||||
data: FinalizeUserLoginInput,
|
||||
): Promise<SessionMetadata> => {
|
||||
): Promise<FinalizeUserLoginResult> => {
|
||||
const { req, res, user, isGlobalLogin } = data;
|
||||
|
||||
const sessionMetadata: SessionMetadata =
|
||||
@@ -87,7 +95,21 @@ const finalizeUserLogin: (
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return sessionMetadata;
|
||||
// Generate access token for response body (used by mobile clients)
|
||||
const accessToken: string = JSONWebToken.signUserLoginToken({
|
||||
tokenData: {
|
||||
userId: user.id!,
|
||||
email: user.email!,
|
||||
name: user.name!,
|
||||
timezone: user.timezone || null,
|
||||
isMasterAdmin: user.isMasterAdmin!,
|
||||
isGlobalLogin: isGlobalLogin,
|
||||
sessionId: sessionMetadata.session.id!,
|
||||
},
|
||||
expiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return { sessionMetadata, accessToken };
|
||||
};
|
||||
|
||||
router.post(
|
||||
@@ -251,6 +273,28 @@ router.post(
|
||||
|
||||
logger.info("User signed up: " + savedUser.email?.toString());
|
||||
|
||||
if (!IsBillingEnabled && miscDataProps["notifySelfHosted"] === true) {
|
||||
const instanceUrl: string = new URL(httpProtocol, host).toString();
|
||||
|
||||
API.post({
|
||||
url: URL.fromString(
|
||||
"https://oneuptime.com/api/open-source-deployment/register",
|
||||
),
|
||||
data: {
|
||||
email: savedUser.email?.toString() || "",
|
||||
name: savedUser.name?.toString() || "",
|
||||
companyName:
|
||||
(miscDataProps["selfHostedCompanyName"] as string) || undefined,
|
||||
companyPhoneNumber:
|
||||
(miscDataProps["selfHostedPhoneNumber"] as string) || undefined,
|
||||
oneuptimeVersion: AppVersion,
|
||||
instanceUrl: instanceUrl,
|
||||
},
|
||||
}).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
return Response.sendEntityResponse(req, res, savedUser, User);
|
||||
}
|
||||
|
||||
@@ -552,8 +596,10 @@ router.post(
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
// Try cookie first, then fallback to request body (for mobile clients)
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req);
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req) ||
|
||||
(req.body.refreshToken as string | undefined);
|
||||
|
||||
if (!refreshToken) {
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
@@ -658,7 +704,26 @@ router.post(
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
// Generate access token for response body (used by mobile clients)
|
||||
const newAccessToken: string = JSONWebToken.signUserLoginToken({
|
||||
tokenData: {
|
||||
userId: user.id!,
|
||||
email: user.email!,
|
||||
name: user.name!,
|
||||
timezone: user.timezone || null,
|
||||
isMasterAdmin: user.isMasterAdmin!,
|
||||
isGlobalLogin: isGlobalLogin,
|
||||
sessionId: renewedSession.session.id!,
|
||||
},
|
||||
expiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
accessToken: newAccessToken,
|
||||
refreshToken: renewedSession.refreshToken,
|
||||
refreshTokenExpiresAt:
|
||||
renewedSession.refreshTokenExpiresAt.toISOString(),
|
||||
});
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -673,8 +738,10 @@ router.post(
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
// Try cookie first, then fallback to request body (for mobile clients)
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req);
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req) ||
|
||||
(req.body.refreshToken as string | undefined);
|
||||
|
||||
if (refreshToken) {
|
||||
await UserSessionService.revokeSessionByRefreshToken(refreshToken, {
|
||||
@@ -987,14 +1054,21 @@ const login: LoginFunction = async (options: {
|
||||
if (alreadySavedUser.password.toString() === user.password!.toString()) {
|
||||
logger.info("User logged in: " + alreadySavedUser.email?.toString());
|
||||
|
||||
await finalizeUserLogin({
|
||||
const loginResult: FinalizeUserLoginResult = await finalizeUserLogin({
|
||||
req,
|
||||
res,
|
||||
user: alreadySavedUser,
|
||||
isGlobalLogin: true,
|
||||
});
|
||||
|
||||
return Response.sendEntityResponse(req, res, alreadySavedUser, User);
|
||||
return Response.sendEntityResponse(req, res, alreadySavedUser, User, {
|
||||
miscData: {
|
||||
accessToken: loginResult.accessToken,
|
||||
refreshToken: loginResult.sessionMetadata.refreshToken,
|
||||
refreshTokenExpiresAt:
|
||||
loginResult.sessionMetadata.refreshTokenExpiresAt.toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return Response.sendErrorResponse(
|
||||
|
||||
105
App/FeatureSet/Notification/API/PushRelay.ts
Normal file
105
App/FeatureSet/Notification/API/PushRelay.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import PushNotificationService from "Common/Server/Services/PushNotificationService";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
// Simple in-memory rate limiter by IP
|
||||
const rateLimitMap: Map<string, { count: number; resetTime: number }> =
|
||||
new Map();
|
||||
const RATE_LIMIT_WINDOW_MS: number = 60 * 1000; // 1 minute
|
||||
const RATE_LIMIT_MAX_REQUESTS: number = 60; // 60 requests per minute per IP
|
||||
|
||||
function isRateLimited(ip: string): boolean {
|
||||
const now: number = Date.now();
|
||||
const entry: { count: number; resetTime: number } | undefined =
|
||||
rateLimitMap.get(ip);
|
||||
|
||||
if (!entry || now > entry.resetTime) {
|
||||
rateLimitMap.set(ip, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
|
||||
return false;
|
||||
}
|
||||
|
||||
entry.count++;
|
||||
|
||||
return entry.count > RATE_LIMIT_MAX_REQUESTS;
|
||||
}
|
||||
|
||||
// Clean up stale rate limit entries every 5 minutes
|
||||
setInterval(() => {
|
||||
const now: number = Date.now();
|
||||
for (const [ip, entry] of rateLimitMap.entries()) {
|
||||
if (now > entry.resetTime) {
|
||||
rateLimitMap.delete(ip);
|
||||
}
|
||||
}
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
router.post(
|
||||
"/send",
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const clientIp: string =
|
||||
(req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim() ||
|
||||
req.socket.remoteAddress ||
|
||||
"unknown";
|
||||
|
||||
if (isRateLimited(clientIp)) {
|
||||
res.status(429).json({
|
||||
message: "Rate limit exceeded. Please try again later.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PushNotificationService.hasExpoAccessToken()) {
|
||||
throw new BadDataException(
|
||||
"Push relay is not configured. EXPO_ACCESS_TOKEN is not set on this server.",
|
||||
);
|
||||
}
|
||||
|
||||
const body: JSONObject = req.body as JSONObject;
|
||||
|
||||
const to: string | undefined = body["to"] as string | undefined;
|
||||
|
||||
if (!to || !PushNotificationService.isValidExpoPushToken(to)) {
|
||||
throw new BadDataException(
|
||||
"Invalid or missing push token. Must be a valid Expo push token.",
|
||||
);
|
||||
}
|
||||
|
||||
const title: string | undefined = body["title"] as string | undefined;
|
||||
const messageBody: string | undefined = body["body"] as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (!title && !messageBody) {
|
||||
throw new BadDataException(
|
||||
"At least one of 'title' or 'body' must be provided.",
|
||||
);
|
||||
}
|
||||
|
||||
await PushNotificationService.sendRelayPushNotification({
|
||||
to: to,
|
||||
title: title,
|
||||
body: messageBody,
|
||||
data: (body["data"] as { [key: string]: string }) || {},
|
||||
sound: (body["sound"] as string) || "default",
|
||||
priority: (body["priority"] as string) || "high",
|
||||
channelId: (body["channelId"] as string) || "default",
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, { success: true });
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -4,6 +4,7 @@ import MailAPI from "./API/Mail";
|
||||
import SmsAPI from "./API/SMS";
|
||||
import WhatsAppAPI from "./API/WhatsApp";
|
||||
import PushNotificationAPI from "./API/PushNotification";
|
||||
import PushRelayAPI from "./API/PushRelay";
|
||||
import SMTPConfigAPI from "./API/SMTPConfig";
|
||||
import PhoneNumberAPI from "./API/PhoneNumber";
|
||||
import IncomingCallAPI from "./API/IncomingCall";
|
||||
@@ -21,6 +22,7 @@ const NotificationFeatureSet: FeatureSet = {
|
||||
app.use([`/${APP_NAME}/sms`, "/sms"], SmsAPI);
|
||||
app.use([`/${APP_NAME}/whatsapp`, "/whatsapp"], WhatsAppAPI);
|
||||
app.use([`/${APP_NAME}/push`, "/push"], PushNotificationAPI);
|
||||
app.use([`/${APP_NAME}/push-relay`, "/push-relay"], PushRelayAPI);
|
||||
app.use([`/${APP_NAME}/call`, "/call"], CallAPI);
|
||||
app.use([`/${APP_NAME}/smtp-config`, "/smtp-config"], SMTPConfigAPI);
|
||||
app.use([`/${APP_NAME}/phone-number`, "/phone-number"], PhoneNumberAPI);
|
||||
|
||||
@@ -70,6 +70,7 @@ export default class CallService {
|
||||
customTwilioConfig?: TwilioConfig | undefined;
|
||||
incidentId?: ObjectID | undefined;
|
||||
alertId?: ObjectID | undefined;
|
||||
monitorId?: ObjectID | undefined;
|
||||
scheduledMaintenanceId?: ObjectID | undefined;
|
||||
statusPageId?: ObjectID | undefined;
|
||||
statusPageAnnouncementId?: ObjectID | undefined;
|
||||
@@ -144,6 +145,10 @@ export default class CallService {
|
||||
callLog.alertId = options.alertId;
|
||||
}
|
||||
|
||||
if (options.monitorId) {
|
||||
callLog.monitorId = options.monitorId;
|
||||
}
|
||||
|
||||
if (options.scheduledMaintenanceId) {
|
||||
callLog.scheduledMaintenanceId = options.scheduledMaintenanceId;
|
||||
}
|
||||
|
||||
@@ -483,6 +483,7 @@ export default class MailService {
|
||||
timeout?: number | undefined;
|
||||
incidentId?: ObjectID | undefined;
|
||||
alertId?: ObjectID | undefined;
|
||||
monitorId?: ObjectID | undefined;
|
||||
scheduledMaintenanceId?: ObjectID | undefined;
|
||||
statusPageId?: ObjectID | undefined;
|
||||
statusPageAnnouncementId?: ObjectID | undefined;
|
||||
@@ -516,6 +517,10 @@ export default class MailService {
|
||||
emailLog.alertId = options.alertId;
|
||||
}
|
||||
|
||||
if (options.monitorId) {
|
||||
emailLog.monitorId = options.monitorId;
|
||||
}
|
||||
|
||||
if (options.scheduledMaintenanceId) {
|
||||
emailLog.scheduledMaintenanceId = options.scheduledMaintenanceId;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ export default class SmsService {
|
||||
userOnCallLogTimelineId?: ObjectID | undefined;
|
||||
incidentId?: ObjectID | undefined;
|
||||
alertId?: ObjectID | undefined;
|
||||
monitorId?: ObjectID | undefined;
|
||||
scheduledMaintenanceId?: ObjectID | undefined;
|
||||
statusPageId?: ObjectID | undefined;
|
||||
statusPageAnnouncementId?: ObjectID | undefined;
|
||||
@@ -91,6 +92,10 @@ export default class SmsService {
|
||||
smsLog.alertId = options.alertId;
|
||||
}
|
||||
|
||||
if (options.monitorId) {
|
||||
smsLog.monitorId = options.monitorId;
|
||||
}
|
||||
|
||||
if (options.scheduledMaintenanceId) {
|
||||
smsLog.scheduledMaintenanceId = options.scheduledMaintenanceId;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export default class WhatsAppService {
|
||||
userOnCallLogTimelineId?: ObjectID | undefined;
|
||||
incidentId?: ObjectID | undefined;
|
||||
alertId?: ObjectID | undefined;
|
||||
monitorId?: ObjectID | undefined;
|
||||
scheduledMaintenanceId?: ObjectID | undefined;
|
||||
statusPageId?: ObjectID | undefined;
|
||||
statusPageAnnouncementId?: ObjectID | undefined;
|
||||
@@ -96,6 +97,10 @@ export default class WhatsAppService {
|
||||
whatsAppLog.alertId = options.alertId;
|
||||
}
|
||||
|
||||
if (options.monitorId) {
|
||||
whatsAppLog.monitorId = options.monitorId;
|
||||
}
|
||||
|
||||
if (options.scheduledMaintenanceId) {
|
||||
whatsAppLog.scheduledMaintenanceId = options.scheduledMaintenanceId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " incidentEpisodeTitle) }}
|
||||
|
||||
{{> InfoBlock info=(concat "A new incident episode has been created in the project - " projectName)}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Incident Episode Title:" text=incidentEpisodeTitle }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Resources Affected: " text=resourcesAffected }}
|
||||
{{> DetailBoxField title="Severity: " text=incidentEpisodeSeverity }}
|
||||
{{> DetailBoxField title="Root Cause: " text=rootCause }}
|
||||
{{> DetailBoxField title="Description: " text=incidentEpisodeDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
{{#if incidentsList}}
|
||||
{{> TitleBlock title=(concat "Incidents in this Episode (" incidentsCount ")") }}
|
||||
<!-- Incidents List Container -->
|
||||
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
|
||||
width="600" style="min-width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="st-Spacer st-Spacer--gutter"
|
||||
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
|
||||
width="64">
|
||||
<div class="st-Spacer st-Spacer--filler"></div>
|
||||
</td>
|
||||
<td style="border: 0; margin: 0; padding: 0;">
|
||||
{{{incidentsList}}}
|
||||
</td>
|
||||
<td class="st-Spacer st-Spacer--gutter"
|
||||
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
|
||||
width="64">
|
||||
<div class="st-Spacer st-Spacer--filler"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="16"
|
||||
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
|
||||
<div class="st-Spacer st-Spacer--filler"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- /Incidents List Container -->
|
||||
{{/if}}
|
||||
|
||||
{{> InfoBlock info="ACTION REQUIRED: Please acknowledge this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=acknowledgeIncidentEpisodeLink buttonText="Acknowledge Incident Episode"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=acknowledgeIncidentEpisodeLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> TitleBlock title="Why am I receiving this email?"}}
|
||||
{{> InfoBlock info="You are receiving this email because you are a member of the team that is responsible for this incident episode or you are currently on-call."}}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,30 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info="You have been added as the owner of this incident episode."}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Episode Title:" text=episodeTitle }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Severity: " text=episodeSeverity }}
|
||||
{{> DetailBoxField title="Description: " text=episodeDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=episodeViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=episodeViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,37 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info="A new note has been posted on this incident episode."}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Episode Title:" text=episodeTitle }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Severity: " text=episodeSeverity }}
|
||||
{{#if isPrivateNote}}
|
||||
{{> DetailBoxField title="Private Note: " text=note }}
|
||||
{{else}}
|
||||
{{> DetailBoxField title="Public Note: " text=note }}
|
||||
{{/if}}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=episodeViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=episodeViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> OwnerInfo this }}
|
||||
{{> UnsubscribeOwnerEmail this }}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,35 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info=(concat "A new incident episode has been created in the project - " projectName)}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Episode Title:" text=episodeTitle }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Episode Created By: " text=declaredBy }}
|
||||
{{> DetailBoxField title="Episode Created At: " text=declaredAt }}
|
||||
{{> DetailBoxField title="Severity: " text=episodeSeverity }}
|
||||
{{> DetailBoxField title="Description: " text=episodeDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=episodeViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=episodeViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> OwnerInfo this }}
|
||||
{{> UnsubscribeOwnerEmail this }}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,37 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info="Incident episode state has changed"}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> StateTransition this}}
|
||||
{{#ifNotCond previousStateDurationText ""}}
|
||||
{{> DetailBoxField title="Duration in Previous State:" text=previousStateDurationText }}
|
||||
{{/ifNotCond}}
|
||||
{{> DetailBoxField title="Episode Title:" text=episodeTitle }}
|
||||
{{> DetailBoxField title="State changed at:" text=stateChangedAt }}
|
||||
{{> DetailBoxField title="Severity:" text=episodeSeverity }}
|
||||
{{> DetailBoxField title="Description:" text=episodeDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=episodeViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=episodeViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> OwnerInfo this }}
|
||||
{{> UnsubscribeOwnerEmail this }}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,32 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident " incidentNumber ": " incidentTitle) }}
|
||||
|
||||
{{> InfoBlock info=(concat "You have been assigned as " incidentRole " to this incident.")}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Incident Title:" text=incidentTitle }}
|
||||
{{> DetailBoxField title="Your Role: " text=incidentRole }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Resources Affected: " text=resourcesAffected }}
|
||||
{{> DetailBoxField title="Severity: " text=incidentSeverity }}
|
||||
{{> DetailBoxField title="Description: " text=incidentDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this incident by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=incidentViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=incidentViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident changes."}}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
37
App/FeatureSet/Notification/Templates/Invoice.hbs
Normal file
37
App/FeatureSet/Notification/Templates/Invoice.hbs
Normal file
@@ -0,0 +1,37 @@
|
||||
{{> Start this}}
|
||||
|
||||
{{> Logo this}}
|
||||
|
||||
{{> EmailTitle title="Invoice from OneUptime" }}
|
||||
|
||||
{{> InfoBlock info="A new invoice has been generated for your account. Here are the details:"}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Invoice Number:" text=invoiceNumber }}
|
||||
{{> DetailBoxField title="Invoice Date:" text=invoiceDate }}
|
||||
{{> DetailBoxField title="Amount:" text=amount }}
|
||||
{{#if description}}
|
||||
{{> DetailBoxField title="Description:" text=description }}
|
||||
{{/if}}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
{{#if invoicePdfUrl}}
|
||||
{{> InfoBlock info="You can view and download your invoice by clicking the button below:"}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=invoicePdfUrl buttonText="View Invoice PDF"}}
|
||||
|
||||
{{> InfoBlock info="Or copy and paste this link:"}}
|
||||
{{> InfoBlock info=invoicePdfUrl}}
|
||||
{{/if}}
|
||||
|
||||
{{#if dashboardLink}}
|
||||
{{> InfoBlock info="You can also view all your invoices in your dashboard:"}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=dashboardLink buttonText="View Billing Dashboard"}}
|
||||
{{/if}}
|
||||
|
||||
{{> InfoBlock info="You have received this email because you are subscribed to receive invoice notifications for this project."}}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,36 @@
|
||||
{{> Start this}}
|
||||
|
||||
{{> CustomLogo this}}
|
||||
|
||||
{{> EmailTitle title=(concat "New Incident: " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info="A new incident has been reported that may affect the services you're subscribed to."}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Incident" text=episodeTitle }}
|
||||
{{#if episodeSeverity}}
|
||||
{{> DetailBoxField title="Severity" text=episodeSeverity }}
|
||||
{{/if}}
|
||||
{{> DetailBoxField title="Affected Resources" text=resourcesAffected }}
|
||||
{{#if episodeDescription}}
|
||||
{{> DetailBoxField title="Description" text=episodeDescription }}
|
||||
{{/if}}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
{{#if detailsUrl}}
|
||||
{{> ButtonBlock buttonUrl=detailsUrl buttonText="View Incident Details"}}
|
||||
{{else}}
|
||||
{{> ButtonBlock buttonUrl=statusPageUrl buttonText="View Status Page"}}
|
||||
{{/if}}
|
||||
|
||||
{{> VerticalSpace this}}
|
||||
|
||||
{{#if subscriberEmailNotificationFooterText}}
|
||||
{{> InfoBlock info=subscriberEmailNotificationFooterText }}
|
||||
{{/if}}
|
||||
|
||||
{{> UnsubscribeBlock this}}
|
||||
|
||||
{{> Footer this}}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,30 @@
|
||||
{{> Start this}}
|
||||
|
||||
{{> CustomLogo this}}
|
||||
{{> EmailTitle title=(concat "Incident: " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info="A new note has been added to the incident. Here are the details:"}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Incident Title" text=episodeTitle }}
|
||||
{{> DetailBoxField title="Resources Affected" text=resourcesAffected }}
|
||||
{{#if episodeSeverity}}
|
||||
{{> DetailBoxField title="Severity" text=episodeSeverity }}
|
||||
{{/if}}
|
||||
{{> DetailBoxField title="Note" text=note }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info=(concat subscriberEmailNotificationFooterText "") }}
|
||||
|
||||
|
||||
{{#if detailsUrl}}
|
||||
{{> InfoBlock info=(concat "Find further information here: " detailsUrl)}}
|
||||
{{else}}
|
||||
{{> InfoBlock info=(concat "Find further information here: " statusPageUrl)}}
|
||||
{{/if}}
|
||||
|
||||
{{> UnsubscribeBlock this}}
|
||||
{{> VerticalSpace this}}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,34 @@
|
||||
{{> Start this}}
|
||||
|
||||
{{> CustomLogo this}}
|
||||
|
||||
{{> EmailTitle title=emailTitle }}
|
||||
|
||||
{{> InfoBlock info="The status of an incident affecting services you're subscribed to has been updated."}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Incident" text=episodeTitle }}
|
||||
{{> DetailBoxField title="Current State" text=episodeState }}
|
||||
{{#if episodeSeverity}}
|
||||
{{> DetailBoxField title="Severity" text=episodeSeverity }}
|
||||
{{/if}}
|
||||
{{> DetailBoxField title="Affected Resources" text=resourcesAffected }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
{{#if detailsUrl}}
|
||||
{{> ButtonBlock buttonUrl=detailsUrl buttonText="View Incident Details"}}
|
||||
{{else}}
|
||||
{{> ButtonBlock buttonUrl=statusPageUrl buttonText="View Status Page"}}
|
||||
{{/if}}
|
||||
|
||||
{{> VerticalSpace this}}
|
||||
|
||||
{{#if subscriberEmailNotificationFooterText}}
|
||||
{{> InfoBlock info=subscriberEmailNotificationFooterText }}
|
||||
{{/if}}
|
||||
|
||||
{{> UnsubscribeBlock this}}
|
||||
|
||||
{{> Footer this}}
|
||||
|
||||
{{> End this}}
|
||||
81
App/package-lock.json
generated
81
App/package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@sendgrid/mail": "^8.1.0",
|
||||
"Common": "file:../Common",
|
||||
"ejs": "^3.1.9",
|
||||
"expo-server-sdk": "^5.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"nodemailer": "^6.9.7",
|
||||
"ts-node": "^10.9.1",
|
||||
@@ -83,6 +84,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"expo-server-sdk": "^3.15.0",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
"history": "^5.3.0",
|
||||
@@ -1609,13 +1611,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz",
|
||||
"integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -2165,6 +2167,12 @@
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/err-code": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
|
||||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
@@ -2298,6 +2306,20 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-server-sdk": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-5.0.0.tgz",
|
||||
"integrity": "sha512-GEp1XYLU80iS/hdRo3c2n092E8TgTXcHSuw6Lw68dSoWaAgiLPI2R+e5hp5+hGF1TtJZOi2nxtJX63+XA3iz9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"promise-limit": "^2.7.0",
|
||||
"promise-retry": "^2.0.1",
|
||||
"undici": "^7.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
@@ -2388,9 +2410,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
@@ -4166,6 +4188,25 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/promise-limit": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz",
|
||||
"integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/promise-retry": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
|
||||
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"err-code": "^2.0.2",
|
||||
"retry": "^0.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/prompts": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||
@@ -4191,9 +4232,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
||||
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
|
||||
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
@@ -4289,6 +4330,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
@@ -4800,6 +4850,15 @@
|
||||
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.22.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
|
||||
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@sendgrid/mail": "^8.1.0",
|
||||
"Common": "file:../Common",
|
||||
"ejs": "^3.1.9",
|
||||
"expo-server-sdk": "^5.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"nodemailer": "^6.9.7",
|
||||
"ts-node": "^10.9.1",
|
||||
|
||||
140
CLI/Commands/ConfigCommands.ts
Normal file
140
CLI/Commands/ConfigCommands.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { Command } from "commander";
|
||||
import * as ConfigManager from "../Core/ConfigManager";
|
||||
import { CLIContext } from "../Types/CLITypes";
|
||||
import { printSuccess, printError, printInfo } from "../Core/OutputFormatter";
|
||||
import Table from "cli-table3";
|
||||
import chalk from "chalk";
|
||||
|
||||
export function registerConfigCommands(program: Command): void {
|
||||
// Login command
|
||||
const loginCmd: Command = program
|
||||
.command("login")
|
||||
.description("Authenticate with a OneUptime instance")
|
||||
.argument("<api-key>", "API key for authentication")
|
||||
.argument(
|
||||
"<instance-url>",
|
||||
"OneUptime instance URL (e.g. https://oneuptime.com)",
|
||||
)
|
||||
.option("--context-name <name>", "Name for this context", "default")
|
||||
.action(
|
||||
(
|
||||
apiKey: string,
|
||||
instanceUrl: string,
|
||||
options: { contextName: string },
|
||||
) => {
|
||||
try {
|
||||
const context: CLIContext = {
|
||||
name: options.contextName,
|
||||
apiUrl: instanceUrl.replace(/\/+$/, ""),
|
||||
apiKey: apiKey,
|
||||
};
|
||||
|
||||
ConfigManager.addContext(context);
|
||||
ConfigManager.setCurrentContext(context.name);
|
||||
|
||||
printSuccess(
|
||||
`Logged in successfully. Context "${context.name}" is now active.`,
|
||||
);
|
||||
} catch (error) {
|
||||
printError(
|
||||
`Login failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Suppress unused variable warning - loginCmd is used for registration
|
||||
void loginCmd;
|
||||
|
||||
// Context commands
|
||||
const contextCmd: Command = program
|
||||
.command("context")
|
||||
.description("Manage CLI contexts (environments/projects)");
|
||||
|
||||
contextCmd
|
||||
.command("list")
|
||||
.description("List all configured contexts")
|
||||
.action(() => {
|
||||
const contexts: Array<CLIContext & { isCurrent: boolean }> =
|
||||
ConfigManager.listContexts();
|
||||
|
||||
if (contexts.length === 0) {
|
||||
printInfo(
|
||||
"No contexts configured. Run `oneuptime login` to create one.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const noColor: boolean =
|
||||
process.env["NO_COLOR"] !== undefined ||
|
||||
process.argv.includes("--no-color");
|
||||
|
||||
const table: Table.Table = new Table({
|
||||
head: ["", "Name", "URL"].map((h: string) => {
|
||||
return noColor ? h : chalk.cyan(h);
|
||||
}),
|
||||
style: { head: [], border: [] },
|
||||
});
|
||||
|
||||
for (const ctx of contexts) {
|
||||
table.push([ctx.isCurrent ? "*" : "", ctx.name, ctx.apiUrl]);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(table.toString());
|
||||
});
|
||||
|
||||
contextCmd
|
||||
.command("use <name>")
|
||||
.description("Switch to a different context")
|
||||
.action((name: string) => {
|
||||
try {
|
||||
ConfigManager.setCurrentContext(name);
|
||||
printSuccess(`Switched to context "${name}".`);
|
||||
} catch (error) {
|
||||
printError(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
contextCmd
|
||||
.command("current")
|
||||
.description("Show the current active context")
|
||||
.action(() => {
|
||||
const ctx: CLIContext | null = ConfigManager.getCurrentContext();
|
||||
if (!ctx) {
|
||||
printInfo(
|
||||
"No current context set. Run `oneuptime login` to create one.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const maskedKey: string =
|
||||
ctx.apiKey.length > 8
|
||||
? ctx.apiKey.substring(0, 4) +
|
||||
"****" +
|
||||
ctx.apiKey.substring(ctx.apiKey.length - 4)
|
||||
: "****";
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Context: ${ctx.name}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`URL: ${ctx.apiUrl}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`API Key: ${maskedKey}`);
|
||||
});
|
||||
|
||||
contextCmd
|
||||
.command("delete <name>")
|
||||
.description("Delete a context")
|
||||
.action((name: string) => {
|
||||
try {
|
||||
ConfigManager.removeContext(name);
|
||||
printSuccess(`Context "${name}" deleted.`);
|
||||
} catch (error) {
|
||||
printError(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
356
CLI/Commands/ResourceCommands.ts
Normal file
356
CLI/Commands/ResourceCommands.ts
Normal file
@@ -0,0 +1,356 @@
|
||||
import { Command } from "commander";
|
||||
import DatabaseModels from "Common/Models/DatabaseModels/Index";
|
||||
import AnalyticsModels from "Common/Models/AnalyticsModels/Index";
|
||||
import BaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
||||
import AnalyticsBaseModel from "Common/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
|
||||
import { ResourceInfo, ResolvedCredentials } from "../Types/CLITypes";
|
||||
import { executeApiRequest, ApiOperation } from "../Core/ApiClient";
|
||||
import { CLIOptions, getResolvedCredentials } from "../Core/ConfigManager";
|
||||
import { formatOutput, printSuccess } from "../Core/OutputFormatter";
|
||||
import { handleError } from "../Core/ErrorHandler";
|
||||
import { generateAllFieldsSelect } from "../Utils/SelectFieldGenerator";
|
||||
import { JSONObject, JSONValue } from "Common/Types/JSON";
|
||||
import * as fs from "fs";
|
||||
|
||||
function toKebabCase(str: string): string {
|
||||
return str
|
||||
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
||||
.replace(/[\s_]+/g, "-")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function parseJsonArg(value: string): JSONObject {
|
||||
try {
|
||||
return JSON.parse(value) as JSONObject;
|
||||
} catch {
|
||||
throw new Error(`Invalid JSON: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function discoverResources(): ResourceInfo[] {
|
||||
const resources: ResourceInfo[] = [];
|
||||
|
||||
// Database models
|
||||
for (const ModelClass of DatabaseModels) {
|
||||
try {
|
||||
const model: BaseModel = new ModelClass();
|
||||
const tableName: string = model.tableName || ModelClass.name;
|
||||
const singularName: string = model.singularName || tableName;
|
||||
const pluralName: string = model.pluralName || `${singularName}s`;
|
||||
const apiPath: string | undefined = model.crudApiPath?.toString();
|
||||
|
||||
if (tableName && model.enableMCP && apiPath) {
|
||||
resources.push({
|
||||
name: toKebabCase(singularName),
|
||||
singularName,
|
||||
pluralName,
|
||||
apiPath,
|
||||
tableName,
|
||||
modelType: "database",
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Skip models that fail to instantiate
|
||||
}
|
||||
}
|
||||
|
||||
// Analytics models
|
||||
for (const ModelClass of AnalyticsModels) {
|
||||
try {
|
||||
const model: AnalyticsBaseModel = new ModelClass();
|
||||
const tableName: string = model.tableName || ModelClass.name;
|
||||
const singularName: string = model.singularName || tableName;
|
||||
const pluralName: string = model.pluralName || `${singularName}s`;
|
||||
const apiPath: string | undefined = model.crudApiPath?.toString();
|
||||
|
||||
if (tableName && model.enableMCP && apiPath) {
|
||||
resources.push({
|
||||
name: toKebabCase(singularName),
|
||||
singularName,
|
||||
pluralName,
|
||||
apiPath,
|
||||
tableName,
|
||||
modelType: "analytics",
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Skip models that fail to instantiate
|
||||
}
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
function getParentOptions(cmd: Command): CLIOptions {
|
||||
// Walk up to root program to get global options
|
||||
let current: Command | null = cmd;
|
||||
while (current?.parent) {
|
||||
current = current.parent;
|
||||
}
|
||||
const opts: Record<string, unknown> = current?.opts() || {};
|
||||
return {
|
||||
apiKey: opts["apiKey"] as string | undefined,
|
||||
url: opts["url"] as string | undefined,
|
||||
context: opts["context"] as string | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function registerListCommand(
|
||||
resourceCmd: Command,
|
||||
resource: ResourceInfo,
|
||||
): void {
|
||||
resourceCmd
|
||||
.command("list")
|
||||
.description(`List ${resource.pluralName}`)
|
||||
.option("--query <json>", "Filter query as JSON")
|
||||
.option("--limit <n>", "Max results to return", "10")
|
||||
.option("--skip <n>", "Number of results to skip", "0")
|
||||
.option("--sort <json>", "Sort order as JSON")
|
||||
.option("-o, --output <format>", "Output format: json, table, wide")
|
||||
.action(
|
||||
async (options: {
|
||||
query?: string;
|
||||
limit: string;
|
||||
skip: string;
|
||||
sort?: string;
|
||||
output?: string;
|
||||
}) => {
|
||||
try {
|
||||
const parentOpts: CLIOptions = getParentOptions(resourceCmd);
|
||||
const creds: ResolvedCredentials = getResolvedCredentials(parentOpts);
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
resource.tableName,
|
||||
resource.modelType,
|
||||
);
|
||||
|
||||
const result: JSONValue = await executeApiRequest({
|
||||
apiUrl: creds.apiUrl,
|
||||
apiKey: creds.apiKey,
|
||||
apiPath: resource.apiPath,
|
||||
operation: "list" as ApiOperation,
|
||||
query: options.query ? parseJsonArg(options.query) : undefined,
|
||||
select,
|
||||
skip: parseInt(options.skip, 10),
|
||||
limit: parseInt(options.limit, 10),
|
||||
sort: options.sort ? parseJsonArg(options.sort) : undefined,
|
||||
});
|
||||
|
||||
// Extract data array from response
|
||||
const responseData: JSONValue =
|
||||
result && typeof result === "object" && !Array.isArray(result)
|
||||
? ((result as JSONObject)["data"] as JSONValue) || result
|
||||
: result;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(formatOutput(responseData, options.output));
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function registerGetCommand(
|
||||
resourceCmd: Command,
|
||||
resource: ResourceInfo,
|
||||
): void {
|
||||
resourceCmd
|
||||
.command("get <id>")
|
||||
.description(`Get a single ${resource.singularName} by ID`)
|
||||
.option("-o, --output <format>", "Output format: json, table, wide")
|
||||
.action(async (id: string, options: { output?: string }) => {
|
||||
try {
|
||||
const parentOpts: CLIOptions = getParentOptions(resourceCmd);
|
||||
const creds: ResolvedCredentials = getResolvedCredentials(parentOpts);
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
resource.tableName,
|
||||
resource.modelType,
|
||||
);
|
||||
|
||||
const result: JSONValue = await executeApiRequest({
|
||||
apiUrl: creds.apiUrl,
|
||||
apiKey: creds.apiKey,
|
||||
apiPath: resource.apiPath,
|
||||
operation: "read" as ApiOperation,
|
||||
id,
|
||||
select,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(formatOutput(result, options.output));
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function registerCreateCommand(
|
||||
resourceCmd: Command,
|
||||
resource: ResourceInfo,
|
||||
): void {
|
||||
resourceCmd
|
||||
.command("create")
|
||||
.description(`Create a new ${resource.singularName}`)
|
||||
.option("--data <json>", "Resource data as JSON")
|
||||
.option("--file <path>", "Read resource data from a JSON file")
|
||||
.option("-o, --output <format>", "Output format: json, table, wide")
|
||||
.action(
|
||||
async (options: { data?: string; file?: string; output?: string }) => {
|
||||
try {
|
||||
let data: JSONObject;
|
||||
|
||||
if (options.file) {
|
||||
const fileContent: string = fs.readFileSync(options.file, "utf-8");
|
||||
data = JSON.parse(fileContent) as JSONObject;
|
||||
} else if (options.data) {
|
||||
data = parseJsonArg(options.data);
|
||||
} else {
|
||||
throw new Error("Either --data or --file is required for create.");
|
||||
}
|
||||
|
||||
const parentOpts: CLIOptions = getParentOptions(resourceCmd);
|
||||
const creds: ResolvedCredentials = getResolvedCredentials(parentOpts);
|
||||
|
||||
const result: JSONValue = await executeApiRequest({
|
||||
apiUrl: creds.apiUrl,
|
||||
apiKey: creds.apiKey,
|
||||
apiPath: resource.apiPath,
|
||||
operation: "create" as ApiOperation,
|
||||
data,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(formatOutput(result, options.output));
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function registerUpdateCommand(
|
||||
resourceCmd: Command,
|
||||
resource: ResourceInfo,
|
||||
): void {
|
||||
resourceCmd
|
||||
.command("update <id>")
|
||||
.description(`Update an existing ${resource.singularName}`)
|
||||
.requiredOption("--data <json>", "Fields to update as JSON")
|
||||
.option("-o, --output <format>", "Output format: json, table, wide")
|
||||
.action(async (id: string, options: { data: string; output?: string }) => {
|
||||
try {
|
||||
const data: JSONObject = parseJsonArg(options.data);
|
||||
const parentOpts: CLIOptions = getParentOptions(resourceCmd);
|
||||
const creds: ResolvedCredentials = getResolvedCredentials(parentOpts);
|
||||
|
||||
const result: JSONValue = await executeApiRequest({
|
||||
apiUrl: creds.apiUrl,
|
||||
apiKey: creds.apiKey,
|
||||
apiPath: resource.apiPath,
|
||||
operation: "update" as ApiOperation,
|
||||
id,
|
||||
data,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(formatOutput(result, options.output));
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function registerDeleteCommand(
|
||||
resourceCmd: Command,
|
||||
resource: ResourceInfo,
|
||||
): void {
|
||||
resourceCmd
|
||||
.command("delete <id>")
|
||||
.description(`Delete a ${resource.singularName}`)
|
||||
.option("--force", "Skip confirmation")
|
||||
.action(async (id: string, _options: { force?: boolean }) => {
|
||||
try {
|
||||
const parentOpts: CLIOptions = getParentOptions(resourceCmd);
|
||||
const creds: ResolvedCredentials = getResolvedCredentials(parentOpts);
|
||||
|
||||
await executeApiRequest({
|
||||
apiUrl: creds.apiUrl,
|
||||
apiKey: creds.apiKey,
|
||||
apiPath: resource.apiPath,
|
||||
operation: "delete" as ApiOperation,
|
||||
id,
|
||||
});
|
||||
|
||||
printSuccess(`${resource.singularName} ${id} deleted successfully.`);
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function registerCountCommand(
|
||||
resourceCmd: Command,
|
||||
resource: ResourceInfo,
|
||||
): void {
|
||||
resourceCmd
|
||||
.command("count")
|
||||
.description(`Count ${resource.pluralName}`)
|
||||
.option("--query <json>", "Filter query as JSON")
|
||||
.action(async (options: { query?: string }) => {
|
||||
try {
|
||||
const parentOpts: CLIOptions = getParentOptions(resourceCmd);
|
||||
const creds: ResolvedCredentials = getResolvedCredentials(parentOpts);
|
||||
|
||||
const result: JSONValue = await executeApiRequest({
|
||||
apiUrl: creds.apiUrl,
|
||||
apiKey: creds.apiKey,
|
||||
apiPath: resource.apiPath,
|
||||
operation: "count" as ApiOperation,
|
||||
query: options.query ? parseJsonArg(options.query) : undefined,
|
||||
});
|
||||
|
||||
// Count response is typically { count: number }
|
||||
if (
|
||||
result &&
|
||||
typeof result === "object" &&
|
||||
!Array.isArray(result) &&
|
||||
"count" in (result as JSONObject)
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log((result as JSONObject)["count"]);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(result);
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function registerResourceCommands(program: Command): void {
|
||||
const resources: ResourceInfo[] = discoverResources();
|
||||
|
||||
for (const resource of resources) {
|
||||
const resourceCmd: Command = program
|
||||
.command(resource.name)
|
||||
.description(`Manage ${resource.pluralName} (${resource.modelType})`);
|
||||
|
||||
// Database models get full CRUD
|
||||
if (resource.modelType === "database") {
|
||||
registerListCommand(resourceCmd, resource);
|
||||
registerGetCommand(resourceCmd, resource);
|
||||
registerCreateCommand(resourceCmd, resource);
|
||||
registerUpdateCommand(resourceCmd, resource);
|
||||
registerDeleteCommand(resourceCmd, resource);
|
||||
registerCountCommand(resourceCmd, resource);
|
||||
}
|
||||
|
||||
// Analytics models get create, list, count
|
||||
if (resource.modelType === "analytics") {
|
||||
registerListCommand(resourceCmd, resource);
|
||||
registerCreateCommand(resourceCmd, resource);
|
||||
registerCountCommand(resourceCmd, resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
CLI/Commands/UtilityCommands.ts
Normal file
129
CLI/Commands/UtilityCommands.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Command } from "commander";
|
||||
import {
|
||||
CLIContext,
|
||||
ResolvedCredentials,
|
||||
ResourceInfo,
|
||||
} from "../Types/CLITypes";
|
||||
import {
|
||||
getCurrentContext,
|
||||
CLIOptions,
|
||||
getResolvedCredentials,
|
||||
} from "../Core/ConfigManager";
|
||||
import { printInfo, printError } from "../Core/OutputFormatter";
|
||||
import { discoverResources } from "./ResourceCommands";
|
||||
import Table from "cli-table3";
|
||||
import chalk from "chalk";
|
||||
|
||||
export function registerUtilityCommands(program: Command): void {
|
||||
// Version command
|
||||
program
|
||||
.command("version")
|
||||
.description("Print CLI version")
|
||||
.action(() => {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const pkg: { version: string } = require("../package.json") as {
|
||||
version: string;
|
||||
};
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(pkg.version);
|
||||
} catch {
|
||||
// Fallback if package.json can't be loaded at runtime
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("1.0.0");
|
||||
}
|
||||
});
|
||||
|
||||
// Whoami command
|
||||
program
|
||||
.command("whoami")
|
||||
.description("Show current authentication info")
|
||||
.action(() => {
|
||||
try {
|
||||
const ctx: CLIContext | null = getCurrentContext();
|
||||
const opts: Record<string, unknown> = program.opts();
|
||||
const cliOpts: CLIOptions = {
|
||||
apiKey: opts["apiKey"] as string | undefined,
|
||||
url: opts["url"] as string | undefined,
|
||||
context: opts["context"] as string | undefined,
|
||||
};
|
||||
|
||||
let creds: ResolvedCredentials;
|
||||
try {
|
||||
creds = getResolvedCredentials(cliOpts);
|
||||
} catch {
|
||||
printInfo(
|
||||
"Not authenticated. Run `oneuptime login` to authenticate.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const maskedKey: string =
|
||||
creds.apiKey.length > 8
|
||||
? creds.apiKey.substring(0, 4) +
|
||||
"****" +
|
||||
creds.apiKey.substring(creds.apiKey.length - 4)
|
||||
: "****";
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`URL: ${creds.apiUrl}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`API Key: ${maskedKey}`);
|
||||
if (ctx) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Context: ${ctx.name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
printError(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Resources command
|
||||
program
|
||||
.command("resources")
|
||||
.description("List all available resource types")
|
||||
.option("--type <type>", "Filter by model type: database, analytics")
|
||||
.action((options: { type?: string }) => {
|
||||
const resources: ResourceInfo[] = discoverResources();
|
||||
|
||||
const filtered: ResourceInfo[] = options.type
|
||||
? resources.filter((r: ResourceInfo) => {
|
||||
return r.modelType === options.type;
|
||||
})
|
||||
: resources;
|
||||
|
||||
if (filtered.length === 0) {
|
||||
printInfo("No resources found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const noColor: boolean =
|
||||
process.env["NO_COLOR"] !== undefined ||
|
||||
process.argv.includes("--no-color");
|
||||
|
||||
const table: Table.Table = new Table({
|
||||
head: ["Command", "Singular", "Plural", "Type", "API Path"].map(
|
||||
(h: string) => {
|
||||
return noColor ? h : chalk.cyan(h);
|
||||
},
|
||||
),
|
||||
style: { head: [], border: [] },
|
||||
});
|
||||
|
||||
for (const r of filtered) {
|
||||
table.push([
|
||||
r.name,
|
||||
r.singularName,
|
||||
r.pluralName,
|
||||
r.modelType,
|
||||
r.apiPath,
|
||||
]);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(table.toString());
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`\nTotal: ${filtered.length} resources`);
|
||||
});
|
||||
}
|
||||
150
CLI/Core/ApiClient.ts
Normal file
150
CLI/Core/ApiClient.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import API from "Common/Utils/API";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import Headers from "Common/Types/API/Headers";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import Protocol from "Common/Types/API/Protocol";
|
||||
import Hostname from "Common/Types/API/Hostname";
|
||||
import { JSONObject, JSONValue } from "Common/Types/JSON";
|
||||
|
||||
export type ApiOperation =
|
||||
| "create"
|
||||
| "read"
|
||||
| "list"
|
||||
| "update"
|
||||
| "delete"
|
||||
| "count";
|
||||
|
||||
export interface ApiRequestOptions {
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
apiPath: string;
|
||||
operation: ApiOperation;
|
||||
id?: string | undefined;
|
||||
data?: JSONObject | undefined;
|
||||
query?: JSONObject | undefined;
|
||||
select?: JSONObject | undefined;
|
||||
skip?: number | undefined;
|
||||
limit?: number | undefined;
|
||||
sort?: JSONObject | undefined;
|
||||
}
|
||||
|
||||
function buildApiRoute(
|
||||
apiPath: string,
|
||||
operation: ApiOperation,
|
||||
id?: string,
|
||||
): Route {
|
||||
let fullPath: string = `/api${apiPath}`;
|
||||
|
||||
switch (operation) {
|
||||
case "read":
|
||||
if (id) {
|
||||
fullPath = `/api${apiPath}/${id}/get-item`;
|
||||
}
|
||||
break;
|
||||
case "update":
|
||||
case "delete":
|
||||
if (id) {
|
||||
fullPath = `/api${apiPath}/${id}/`;
|
||||
}
|
||||
break;
|
||||
case "count":
|
||||
fullPath = `/api${apiPath}/count`;
|
||||
break;
|
||||
case "list":
|
||||
fullPath = `/api${apiPath}/get-list`;
|
||||
break;
|
||||
case "create":
|
||||
default:
|
||||
fullPath = `/api${apiPath}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Route(fullPath);
|
||||
}
|
||||
|
||||
function buildHeaders(apiKey: string): Headers {
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
APIKey: apiKey,
|
||||
};
|
||||
}
|
||||
|
||||
function buildRequestData(options: ApiRequestOptions): JSONObject | undefined {
|
||||
switch (options.operation) {
|
||||
case "create":
|
||||
return { data: options.data || {} } as JSONObject;
|
||||
case "update":
|
||||
return { data: options.data || {} } as JSONObject;
|
||||
case "list":
|
||||
case "count":
|
||||
return {
|
||||
query: options.query || {},
|
||||
select: options.select || {},
|
||||
skip: options.skip || 0,
|
||||
limit: options.limit || 10,
|
||||
sort: options.sort || {},
|
||||
} as JSONObject;
|
||||
case "read":
|
||||
return {
|
||||
select: options.select || {},
|
||||
} as JSONObject;
|
||||
case "delete":
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function executeApiRequest(
|
||||
options: ApiRequestOptions,
|
||||
): Promise<JSONValue> {
|
||||
const url: URL = URL.fromString(options.apiUrl);
|
||||
const protocol: Protocol = url.protocol;
|
||||
const hostname: Hostname = url.hostname;
|
||||
|
||||
const api: API = new API(protocol, hostname, new Route("/"));
|
||||
const route: Route = buildApiRoute(
|
||||
options.apiPath,
|
||||
options.operation,
|
||||
options.id,
|
||||
);
|
||||
const headers: Headers = buildHeaders(options.apiKey);
|
||||
const data: JSONObject | undefined = buildRequestData(options);
|
||||
|
||||
const requestUrl: URL = new URL(api.protocol, api.hostname, route);
|
||||
const baseOptions: { url: URL; headers: Headers } = {
|
||||
url: requestUrl,
|
||||
headers,
|
||||
};
|
||||
|
||||
let response: HTTPResponse<JSONObject> | HTTPErrorResponse;
|
||||
|
||||
switch (options.operation) {
|
||||
case "create":
|
||||
case "count":
|
||||
case "list":
|
||||
case "read":
|
||||
response = await API.post(data ? { ...baseOptions, data } : baseOptions);
|
||||
break;
|
||||
case "update":
|
||||
response = await API.put(data ? { ...baseOptions, data } : baseOptions);
|
||||
break;
|
||||
case "delete":
|
||||
response = await API.delete(
|
||||
data ? { ...baseOptions, data } : baseOptions,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported operation: ${options.operation}`);
|
||||
}
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw new Error(
|
||||
`API error (${response.statusCode}): ${response.message || "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
141
CLI/Core/ConfigManager.ts
Normal file
141
CLI/Core/ConfigManager.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
import { CLIConfig, CLIContext, ResolvedCredentials } from "../Types/CLITypes";
|
||||
|
||||
const CONFIG_DIR: string = path.join(os.homedir(), ".oneuptime");
|
||||
const CONFIG_FILE: string = path.join(CONFIG_DIR, "config.json");
|
||||
|
||||
function getDefaultConfig(): CLIConfig {
|
||||
return {
|
||||
currentContext: "",
|
||||
contexts: {},
|
||||
defaults: {
|
||||
output: "table",
|
||||
limit: 10,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function load(): CLIConfig {
|
||||
try {
|
||||
if (!fs.existsSync(CONFIG_FILE)) {
|
||||
return getDefaultConfig();
|
||||
}
|
||||
const raw: string = fs.readFileSync(CONFIG_FILE, "utf-8");
|
||||
return JSON.parse(raw) as CLIConfig;
|
||||
} catch {
|
||||
return getDefaultConfig();
|
||||
}
|
||||
}
|
||||
|
||||
export function save(config: CLIConfig): void {
|
||||
if (!fs.existsSync(CONFIG_DIR)) {
|
||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), {
|
||||
mode: 0o600,
|
||||
});
|
||||
}
|
||||
|
||||
export function getCurrentContext(): CLIContext | null {
|
||||
const config: CLIConfig = load();
|
||||
if (!config.currentContext) {
|
||||
return null;
|
||||
}
|
||||
return config.contexts[config.currentContext] || null;
|
||||
}
|
||||
|
||||
export function setCurrentContext(name: string): void {
|
||||
const config: CLIConfig = load();
|
||||
if (!config.contexts[name]) {
|
||||
throw new Error(`Context "${name}" does not exist.`);
|
||||
}
|
||||
config.currentContext = name;
|
||||
save(config);
|
||||
}
|
||||
|
||||
export function addContext(context: CLIContext): void {
|
||||
const config: CLIConfig = load();
|
||||
config.contexts[context.name] = context;
|
||||
if (!config.currentContext) {
|
||||
config.currentContext = context.name;
|
||||
}
|
||||
save(config);
|
||||
}
|
||||
|
||||
export function removeContext(name: string): void {
|
||||
const config: CLIConfig = load();
|
||||
if (!config.contexts[name]) {
|
||||
throw new Error(`Context "${name}" does not exist.`);
|
||||
}
|
||||
delete config.contexts[name];
|
||||
if (config.currentContext === name) {
|
||||
const remaining: string[] = Object.keys(config.contexts);
|
||||
config.currentContext = remaining[0] || "";
|
||||
}
|
||||
save(config);
|
||||
}
|
||||
|
||||
export function listContexts(): Array<CLIContext & { isCurrent: boolean }> {
|
||||
const config: CLIConfig = load();
|
||||
return Object.values(config.contexts).map(
|
||||
(ctx: CLIContext): CLIContext & { isCurrent: boolean } => {
|
||||
return {
|
||||
...ctx,
|
||||
isCurrent: ctx.name === config.currentContext,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export interface CLIOptions {
|
||||
apiKey?: string | undefined;
|
||||
url?: string | undefined;
|
||||
context?: string | undefined;
|
||||
}
|
||||
|
||||
export function getResolvedCredentials(
|
||||
cliOptions: CLIOptions,
|
||||
): ResolvedCredentials {
|
||||
// Priority 1: CLI flags
|
||||
if (cliOptions.apiKey && cliOptions.url) {
|
||||
return { apiKey: cliOptions.apiKey, apiUrl: cliOptions.url };
|
||||
}
|
||||
|
||||
// Priority 2: Environment variables
|
||||
const envApiKey: string | undefined = process.env["ONEUPTIME_API_KEY"];
|
||||
const envUrl: string | undefined = process.env["ONEUPTIME_URL"];
|
||||
if (envApiKey && envUrl) {
|
||||
return { apiKey: envApiKey, apiUrl: envUrl };
|
||||
}
|
||||
|
||||
// Priority 3: Specific context if specified via --context flag
|
||||
if (cliOptions.context) {
|
||||
const config: CLIConfig = load();
|
||||
const ctx: CLIContext | undefined = config.contexts[cliOptions.context];
|
||||
if (ctx) {
|
||||
return { apiKey: ctx.apiKey, apiUrl: ctx.apiUrl };
|
||||
}
|
||||
throw new Error(`Context "${cliOptions.context}" does not exist.`);
|
||||
}
|
||||
|
||||
// Priority 4: Current context in config file
|
||||
const currentCtx: CLIContext | null = getCurrentContext();
|
||||
if (currentCtx) {
|
||||
return { apiKey: currentCtx.apiKey, apiUrl: currentCtx.apiUrl };
|
||||
}
|
||||
|
||||
// Partial env vars + partial context
|
||||
if (envApiKey || envUrl) {
|
||||
const ctx: CLIContext | null = getCurrentContext();
|
||||
return {
|
||||
apiKey: envApiKey || ctx?.apiKey || "",
|
||||
apiUrl: envUrl || ctx?.apiUrl || "",
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"No credentials found. Run `oneuptime login` or set ONEUPTIME_API_KEY and ONEUPTIME_URL environment variables.",
|
||||
);
|
||||
}
|
||||
43
CLI/Core/ErrorHandler.ts
Normal file
43
CLI/Core/ErrorHandler.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { printError } from "./OutputFormatter";
|
||||
|
||||
export enum ExitCode {
|
||||
Success = 0,
|
||||
GeneralError = 1,
|
||||
AuthError = 2,
|
||||
NotFound = 3,
|
||||
}
|
||||
|
||||
export function handleError(error: unknown): never {
|
||||
if (error instanceof Error) {
|
||||
const message: string = error.message;
|
||||
|
||||
// Check for auth-related errors
|
||||
if (
|
||||
message.includes("API key") ||
|
||||
message.includes("credentials") ||
|
||||
message.includes("Unauthorized") ||
|
||||
message.includes("401")
|
||||
) {
|
||||
printError(`Authentication error: ${message}`);
|
||||
process.exit(ExitCode.AuthError);
|
||||
}
|
||||
|
||||
// Check for not found errors
|
||||
if (message.includes("404") || message.includes("not found")) {
|
||||
printError(`Not found: ${message}`);
|
||||
process.exit(ExitCode.NotFound);
|
||||
}
|
||||
|
||||
// General API errors
|
||||
if (message.includes("API error")) {
|
||||
printError(message);
|
||||
process.exit(ExitCode.GeneralError);
|
||||
}
|
||||
|
||||
printError(`Error: ${message}`);
|
||||
} else {
|
||||
printError(`Error: ${String(error)}`);
|
||||
}
|
||||
|
||||
process.exit(ExitCode.GeneralError);
|
||||
}
|
||||
195
CLI/Core/OutputFormatter.ts
Normal file
195
CLI/Core/OutputFormatter.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { OutputFormat } from "../Types/CLITypes";
|
||||
import { JSONValue, JSONObject, JSONArray } from "Common/Types/JSON";
|
||||
import Table from "cli-table3";
|
||||
import chalk from "chalk";
|
||||
|
||||
function isColorDisabled(): boolean {
|
||||
return (
|
||||
process.env["NO_COLOR"] !== undefined || process.argv.includes("--no-color")
|
||||
);
|
||||
}
|
||||
|
||||
function detectOutputFormat(cliFormat?: string): OutputFormat {
|
||||
if (cliFormat) {
|
||||
if (cliFormat === "json") {
|
||||
return OutputFormat.JSON;
|
||||
}
|
||||
if (cliFormat === "wide") {
|
||||
return OutputFormat.Wide;
|
||||
}
|
||||
if (cliFormat === "table") {
|
||||
return OutputFormat.Table;
|
||||
}
|
||||
}
|
||||
|
||||
// If stdout is not a TTY (piped), default to JSON
|
||||
if (!process.stdout.isTTY) {
|
||||
return OutputFormat.JSON;
|
||||
}
|
||||
|
||||
return OutputFormat.Table;
|
||||
}
|
||||
|
||||
function formatJson(data: JSONValue): string {
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
function formatTable(data: JSONValue, wide: boolean): string {
|
||||
if (!data) {
|
||||
return "No data returned.";
|
||||
}
|
||||
|
||||
// Handle single object
|
||||
if (!Array.isArray(data)) {
|
||||
return formatSingleObject(data as JSONObject);
|
||||
}
|
||||
|
||||
const items: JSONArray = data as JSONArray;
|
||||
if (items.length === 0) {
|
||||
return "No results found.";
|
||||
}
|
||||
|
||||
// Get all keys from the first item
|
||||
const firstItem: JSONObject = items[0] as JSONObject;
|
||||
if (!firstItem || typeof firstItem !== "object") {
|
||||
return formatJson(data);
|
||||
}
|
||||
|
||||
let columns: string[] = Object.keys(firstItem);
|
||||
|
||||
// In non-wide mode, limit columns to keep the table readable
|
||||
if (!wide && columns.length > 6) {
|
||||
// Prioritize common fields
|
||||
const priority: string[] = [
|
||||
"_id",
|
||||
"name",
|
||||
"title",
|
||||
"status",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
];
|
||||
const prioritized: string[] = priority.filter((col: string) => {
|
||||
return columns.includes(col);
|
||||
});
|
||||
const remaining: string[] = columns.filter((col: string) => {
|
||||
return !priority.includes(col);
|
||||
});
|
||||
columns = [...prioritized, ...remaining].slice(0, 6);
|
||||
}
|
||||
|
||||
const useColor: boolean = !isColorDisabled();
|
||||
|
||||
const table: Table.Table = new Table({
|
||||
head: columns.map((col: string) => {
|
||||
return useColor ? chalk.cyan(col) : col;
|
||||
}),
|
||||
style: {
|
||||
head: [],
|
||||
border: [],
|
||||
},
|
||||
wordWrap: true,
|
||||
});
|
||||
|
||||
for (const item of items) {
|
||||
const row: string[] = columns.map((col: string) => {
|
||||
const val: JSONValue = (item as JSONObject)[col] as JSONValue;
|
||||
return truncateValue(val);
|
||||
});
|
||||
table.push(row);
|
||||
}
|
||||
|
||||
return table.toString();
|
||||
}
|
||||
|
||||
function formatSingleObject(obj: JSONObject): string {
|
||||
const useColor: boolean = !isColorDisabled();
|
||||
|
||||
const table: Table.Table = new Table({
|
||||
style: { head: [], border: [] },
|
||||
});
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const label: string = useColor ? chalk.cyan(key) : key;
|
||||
table.push({ [label]: truncateValue(value as JSONValue) });
|
||||
}
|
||||
|
||||
return table.toString();
|
||||
}
|
||||
|
||||
function truncateValue(val: JSONValue, maxLen: number = 60): string {
|
||||
if (val === null || val === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (typeof val === "object") {
|
||||
const str: string = JSON.stringify(val);
|
||||
if (str.length > maxLen) {
|
||||
return str.substring(0, maxLen - 3) + "...";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
const str: string = String(val);
|
||||
if (str.length > maxLen) {
|
||||
return str.substring(0, maxLen - 3) + "...";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function formatOutput(data: JSONValue, format?: string): string {
|
||||
const outputFormat: OutputFormat = detectOutputFormat(format);
|
||||
|
||||
switch (outputFormat) {
|
||||
case OutputFormat.JSON:
|
||||
return formatJson(data);
|
||||
case OutputFormat.Wide:
|
||||
return formatTable(data, true);
|
||||
case OutputFormat.Table:
|
||||
default:
|
||||
return formatTable(data, false);
|
||||
}
|
||||
}
|
||||
|
||||
export function printSuccess(message: string): void {
|
||||
const useColor: boolean = !isColorDisabled();
|
||||
if (useColor) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(chalk.green(message));
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function printError(message: string): void {
|
||||
const useColor: boolean = !isColorDisabled();
|
||||
if (useColor) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(chalk.red(message));
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function printWarning(message: string): void {
|
||||
const useColor: boolean = !isColorDisabled();
|
||||
if (useColor) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(chalk.yellow(message));
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function printInfo(message: string): void {
|
||||
const useColor: boolean = !isColorDisabled();
|
||||
if (useColor) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(chalk.blue(message));
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
27
CLI/Index.ts
Normal file
27
CLI/Index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env npx ts-node
|
||||
|
||||
import { Command } from "commander";
|
||||
import { registerConfigCommands } from "./Commands/ConfigCommands";
|
||||
import { registerResourceCommands } from "./Commands/ResourceCommands";
|
||||
import { registerUtilityCommands } from "./Commands/UtilityCommands";
|
||||
|
||||
const program: Command = new Command();
|
||||
|
||||
program
|
||||
.name("oneuptime")
|
||||
.description(
|
||||
"OneUptime CLI - Manage your OneUptime resources from the command line",
|
||||
)
|
||||
.version("1.0.0")
|
||||
.option("--api-key <key>", "API key (overrides config)")
|
||||
.option("--url <url>", "OneUptime instance URL (overrides config)")
|
||||
.option("--context <name>", "Use a specific context")
|
||||
.option("-o, --output <format>", "Output format: json, table, wide")
|
||||
.option("--no-color", "Disable colored output");
|
||||
|
||||
// Register command groups
|
||||
registerConfigCommands(program);
|
||||
registerUtilityCommands(program);
|
||||
registerResourceCommands(program);
|
||||
|
||||
program.parse(process.argv);
|
||||
220
CLI/README.md
Normal file
220
CLI/README.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# @oneuptime/cli
|
||||
|
||||
Command-line interface for managing OneUptime resources. Supports all MCP-enabled resources with full CRUD operations, named contexts for multiple environments, and flexible output formats.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install -g @oneuptime/cli
|
||||
```
|
||||
|
||||
Or run directly within the monorepo:
|
||||
|
||||
```bash
|
||||
cd CLI
|
||||
npm install
|
||||
npm start -- --help
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Authenticate with your OneUptime instance
|
||||
oneuptime login <api-key> <instance-url>
|
||||
oneuptime login sk-your-api-key https://oneuptime.com
|
||||
|
||||
# List incidents
|
||||
oneuptime incident list --limit 10
|
||||
|
||||
# Get a single resource by ID
|
||||
oneuptime monitor get 550e8400-e29b-41d4-a716-446655440000
|
||||
|
||||
# Create a resource
|
||||
oneuptime monitor create --data '{"name":"API Health","projectId":"..."}'
|
||||
|
||||
# See all available resources
|
||||
oneuptime resources
|
||||
```
|
||||
|
||||
## Authentication & Contexts
|
||||
|
||||
The CLI supports multiple authentication contexts, making it easy to switch between environments.
|
||||
|
||||
### Setting Up
|
||||
|
||||
```bash
|
||||
# Create a production context
|
||||
oneuptime login sk-prod-key https://oneuptime.com --context-name production
|
||||
|
||||
# Create a staging context
|
||||
oneuptime login sk-staging-key https://staging.oneuptime.com --context-name staging
|
||||
```
|
||||
|
||||
### Switching Contexts
|
||||
|
||||
```bash
|
||||
# List all contexts
|
||||
oneuptime context list
|
||||
|
||||
# Switch active context
|
||||
oneuptime context use staging
|
||||
|
||||
# Show current context
|
||||
oneuptime context current
|
||||
|
||||
# Delete a context
|
||||
oneuptime context delete old-context
|
||||
```
|
||||
|
||||
### Credential Resolution Order
|
||||
|
||||
1. CLI flags: `--api-key` and `--url`
|
||||
2. Environment variables: `ONEUPTIME_API_KEY` and `ONEUPTIME_URL`
|
||||
3. Current context from config file (`~/.oneuptime/config.json`)
|
||||
|
||||
## Command Reference
|
||||
|
||||
### Authentication
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `oneuptime login <api-key> <url>` | Authenticate and create a context |
|
||||
| `oneuptime context list` | List all contexts |
|
||||
| `oneuptime context use <name>` | Switch active context |
|
||||
| `oneuptime context current` | Show current context |
|
||||
| `oneuptime context delete <name>` | Remove a context |
|
||||
| `oneuptime whoami` | Show current auth info |
|
||||
|
||||
### Resource Operations
|
||||
|
||||
Every discovered resource supports these subcommands:
|
||||
|
||||
| Subcommand | Description |
|
||||
|---|---|
|
||||
| `<resource> list [options]` | List resources with filtering and pagination |
|
||||
| `<resource> get <id>` | Get a single resource by ID |
|
||||
| `<resource> create --data <json>` | Create a new resource |
|
||||
| `<resource> update <id> --data <json>` | Update an existing resource |
|
||||
| `<resource> delete <id>` | Delete a resource |
|
||||
| `<resource> count [--query <json>]` | Count resources |
|
||||
|
||||
### List Options
|
||||
|
||||
```
|
||||
--query <json> Filter criteria as JSON
|
||||
--limit <n> Maximum number of results (default: 10)
|
||||
--skip <n> Number of results to skip (default: 0)
|
||||
--sort <json> Sort order as JSON (e.g. '{"createdAt": -1}')
|
||||
-o, --output Output format: json, table, wide
|
||||
```
|
||||
|
||||
### Utility Commands
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `oneuptime version` | Print CLI version |
|
||||
| `oneuptime whoami` | Show current authentication info |
|
||||
| `oneuptime resources` | List all available resource types |
|
||||
|
||||
## Output Formats
|
||||
|
||||
| Format | Description |
|
||||
|---|---|
|
||||
| `table` | Formatted ASCII table (default for TTY) |
|
||||
| `json` | Raw JSON (default when piped) |
|
||||
| `wide` | Table with all columns shown |
|
||||
|
||||
```bash
|
||||
# Explicit format
|
||||
oneuptime incident list -o json
|
||||
oneuptime incident list -o table
|
||||
oneuptime incident list -o wide
|
||||
|
||||
# Pipe to jq (auto-detects JSON)
|
||||
oneuptime incident list | jq '.[].title'
|
||||
```
|
||||
|
||||
## Scripting Examples
|
||||
|
||||
```bash
|
||||
# List incidents as JSON for scripting
|
||||
oneuptime incident list -o json --limit 100
|
||||
|
||||
# Count resources with filter
|
||||
oneuptime incident count --query '{"currentIncidentStateId":"..."}'
|
||||
|
||||
# Create from a JSON file
|
||||
oneuptime monitor create --file monitor.json
|
||||
|
||||
# Use environment variables in CI/CD
|
||||
ONEUPTIME_API_KEY=sk-xxx ONEUPTIME_URL=https://oneuptime.com oneuptime incident list
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `ONEUPTIME_API_KEY` | API key for authentication |
|
||||
| `ONEUPTIME_URL` | OneUptime instance URL |
|
||||
| `NO_COLOR` | Disable colored output |
|
||||
|
||||
## Configuration File
|
||||
|
||||
The CLI stores configuration at `~/.oneuptime/config.json` with `0600` permissions. The file contains:
|
||||
|
||||
```json
|
||||
{
|
||||
"currentContext": "production",
|
||||
"contexts": {
|
||||
"production": {
|
||||
"name": "production",
|
||||
"apiUrl": "https://oneuptime.com",
|
||||
"apiKey": "sk-..."
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"output": "table",
|
||||
"limit": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Global Options
|
||||
|
||||
| Option | Description |
|
||||
|---|---|
|
||||
| `--api-key <key>` | Override API key for this command |
|
||||
| `--url <url>` | Override instance URL for this command |
|
||||
| `--context <name>` | Use a specific context for this command |
|
||||
| `-o, --output <format>` | Output format: json, table, wide |
|
||||
| `--no-color` | Disable colored output |
|
||||
|
||||
## Supported Resources
|
||||
|
||||
Run `oneuptime resources` to see all available resource types. Resources are auto-discovered from OneUptime models that have MCP enabled. Currently supported:
|
||||
|
||||
- **Incident** - Manage incidents
|
||||
- **Alert** - Manage alerts
|
||||
- **Monitor** - Manage monitors
|
||||
- **Monitor Status** - Manage monitor statuses
|
||||
- **Incident State** - Manage incident states
|
||||
- **Status Page** - Manage status pages
|
||||
- **On-Call Policy** - Manage on-call duty policies
|
||||
- **Team** - Manage teams
|
||||
- **Scheduled Maintenance Event** - Manage scheduled maintenance
|
||||
|
||||
As more models are MCP-enabled in OneUptime, they automatically become available in the CLI.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
cd CLI
|
||||
npm install
|
||||
npm start -- --help # Run via ts-node
|
||||
npm test # Run tests
|
||||
npm run compile # Type-check
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
386
CLI/Tests/ApiClient.test.ts
Normal file
386
CLI/Tests/ApiClient.test.ts
Normal file
@@ -0,0 +1,386 @@
|
||||
import { executeApiRequest, ApiRequestOptions } from "../Core/ApiClient";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import { JSONValue } from "Common/Types/JSON";
|
||||
|
||||
// Mock the Common/Utils/API module
|
||||
jest.mock("Common/Utils/API", () => {
|
||||
const mockPost: jest.Mock = jest.fn();
|
||||
const mockPut: jest.Mock = jest.fn();
|
||||
const mockDelete: jest.Mock = jest.fn();
|
||||
|
||||
function MockAPI(
|
||||
this: { protocol: string; hostname: string },
|
||||
protocol: string,
|
||||
hostname: string,
|
||||
_route: string,
|
||||
): void {
|
||||
this.protocol = protocol;
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
MockAPI.post = mockPost;
|
||||
MockAPI.put = mockPut;
|
||||
MockAPI.delete = mockDelete;
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
default: MockAPI,
|
||||
};
|
||||
});
|
||||
|
||||
function createSuccessResponse(
|
||||
data: Record<string, unknown> | Record<string, unknown>[],
|
||||
): {
|
||||
data: Record<string, unknown> | Record<string, unknown>[];
|
||||
statusCode: number;
|
||||
} {
|
||||
return { data, statusCode: 200 };
|
||||
}
|
||||
|
||||
function createErrorResponse(
|
||||
statusCode: number,
|
||||
message: string,
|
||||
): HTTPErrorResponse {
|
||||
/*
|
||||
* HTTPErrorResponse computes `message` from `.data` via a getter.
|
||||
* We create a proper prototype chain and set data to contain the message.
|
||||
*/
|
||||
const resp: HTTPErrorResponse = Object.create(HTTPErrorResponse.prototype);
|
||||
resp.statusCode = statusCode;
|
||||
/*
|
||||
* HTTPResponse stores data in _jsonData and exposes it via `data` getter
|
||||
* But since the prototype chain may not have full getters, we define them
|
||||
*/
|
||||
Object.defineProperty(resp, "data", {
|
||||
get: (): { message: string } => {
|
||||
return { message: message };
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
return resp;
|
||||
}
|
||||
|
||||
describe("ApiClient", () => {
|
||||
let mockPost: jest.Mock;
|
||||
let mockPut: jest.Mock;
|
||||
let mockDelete: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPost = API.post as jest.Mock;
|
||||
mockPut = API.put as jest.Mock;
|
||||
mockDelete = API.delete as jest.Mock;
|
||||
(mockPost as jest.Mock).mockReset();
|
||||
(mockPut as jest.Mock).mockReset();
|
||||
(mockDelete as jest.Mock).mockReset();
|
||||
});
|
||||
|
||||
const baseOptions: ApiRequestOptions = {
|
||||
apiUrl: "https://oneuptime.com",
|
||||
apiKey: "test-api-key",
|
||||
apiPath: "/incident",
|
||||
operation: "create",
|
||||
};
|
||||
|
||||
describe("create operation", () => {
|
||||
it("should make a POST request with data wrapped in { data: ... }", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(
|
||||
createSuccessResponse({ _id: "123" }),
|
||||
);
|
||||
|
||||
const result: JSONValue = await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "create",
|
||||
data: { name: "Test Incident" },
|
||||
});
|
||||
|
||||
expect(mockPost).toHaveBeenCalledTimes(1);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.data).toEqual({ data: { name: "Test Incident" } });
|
||||
expect(result).toEqual({ _id: "123" });
|
||||
});
|
||||
|
||||
it("should use empty object when no data provided for create", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(
|
||||
createSuccessResponse({ _id: "123" }),
|
||||
);
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "create",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.data).toEqual({ data: {} });
|
||||
});
|
||||
});
|
||||
|
||||
describe("read operation", () => {
|
||||
it("should make a POST request with select and id in route", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(
|
||||
createSuccessResponse({ _id: "abc", name: "Test" }),
|
||||
);
|
||||
|
||||
const result: JSONValue = await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "read",
|
||||
id: "abc-123",
|
||||
select: { _id: true, name: true },
|
||||
});
|
||||
|
||||
expect(mockPost).toHaveBeenCalledTimes(1);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.url.toString()).toContain("abc-123/get-item");
|
||||
expect(callArgs.data).toEqual({ select: { _id: true, name: true } });
|
||||
expect(result).toEqual({ _id: "abc", name: "Test" });
|
||||
});
|
||||
|
||||
it("should use empty select when none provided", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(createSuccessResponse({}));
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "read",
|
||||
id: "abc-123",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.data).toEqual({ select: {} });
|
||||
});
|
||||
|
||||
it("should build route without id when no id provided", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(createSuccessResponse({}));
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "read",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.url.toString()).toContain("/api/incident");
|
||||
expect(callArgs.url.toString()).not.toContain("/get-item");
|
||||
});
|
||||
});
|
||||
|
||||
describe("list operation", () => {
|
||||
it("should make a POST request with query, select, skip, limit, sort", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(
|
||||
createSuccessResponse({ data: [] }),
|
||||
);
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "list",
|
||||
query: { status: "active" },
|
||||
select: { _id: true },
|
||||
skip: 5,
|
||||
limit: 20,
|
||||
sort: { createdAt: -1 },
|
||||
});
|
||||
|
||||
expect(mockPost).toHaveBeenCalledTimes(1);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.url.toString()).toContain("/get-list");
|
||||
expect(callArgs.data).toEqual({
|
||||
query: { status: "active" },
|
||||
select: { _id: true },
|
||||
skip: 5,
|
||||
limit: 20,
|
||||
sort: { createdAt: -1 },
|
||||
});
|
||||
});
|
||||
|
||||
it("should use defaults when no query options provided", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(
|
||||
createSuccessResponse({ data: [] }),
|
||||
);
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "list",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.data).toEqual({
|
||||
query: {},
|
||||
select: {},
|
||||
skip: 0,
|
||||
limit: 10,
|
||||
sort: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("count operation", () => {
|
||||
it("should make a POST request to /count path", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(
|
||||
createSuccessResponse({ count: 42 }),
|
||||
);
|
||||
|
||||
const result: JSONValue = await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "count",
|
||||
query: { status: "active" },
|
||||
});
|
||||
|
||||
expect(mockPost).toHaveBeenCalledTimes(1);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.url.toString()).toContain("/count");
|
||||
expect(result).toEqual({ count: 42 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("update operation", () => {
|
||||
it("should make a PUT request with data", async () => {
|
||||
(mockPut as jest.Mock).mockResolvedValue(
|
||||
createSuccessResponse({ _id: "abc" }),
|
||||
);
|
||||
|
||||
const result: JSONValue = await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "update",
|
||||
id: "abc-123",
|
||||
data: { name: "Updated" },
|
||||
});
|
||||
|
||||
expect(mockPut).toHaveBeenCalledTimes(1);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPut as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.url.toString()).toContain("abc-123");
|
||||
expect(callArgs.data).toEqual({ data: { name: "Updated" } });
|
||||
expect(result).toEqual({ _id: "abc" });
|
||||
});
|
||||
|
||||
it("should use empty object when no data provided for update", async () => {
|
||||
(mockPut as jest.Mock).mockResolvedValue(createSuccessResponse({}));
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "update",
|
||||
id: "abc-123",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPut as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.data).toEqual({ data: {} });
|
||||
});
|
||||
|
||||
it("should build route without id when no id provided", async () => {
|
||||
(mockPut as jest.Mock).mockResolvedValue(createSuccessResponse({}));
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "update",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPut as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.url.toString()).toContain("/api/incident");
|
||||
});
|
||||
});
|
||||
|
||||
describe("delete operation", () => {
|
||||
it("should make a DELETE request", async () => {
|
||||
(mockDelete as jest.Mock).mockResolvedValue(createSuccessResponse({}));
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "delete",
|
||||
id: "abc-123",
|
||||
});
|
||||
|
||||
expect(mockDelete).toHaveBeenCalledTimes(1);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockDelete as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.url.toString()).toContain("abc-123");
|
||||
expect(callArgs.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should build route without id when no id provided", async () => {
|
||||
(mockDelete as jest.Mock).mockResolvedValue(createSuccessResponse({}));
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "delete",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockDelete as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.url.toString()).toContain("/api/incident");
|
||||
});
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("should throw on HTTPErrorResponse", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(
|
||||
createErrorResponse(500, "Server Error"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
executeApiRequest({ ...baseOptions, operation: "create", data: {} }),
|
||||
).rejects.toThrow("API error");
|
||||
});
|
||||
|
||||
it("should include status code in error message", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(
|
||||
createErrorResponse(403, "Forbidden"),
|
||||
);
|
||||
|
||||
await expect(
|
||||
executeApiRequest({ ...baseOptions, operation: "list" }),
|
||||
).rejects.toThrow("403");
|
||||
});
|
||||
|
||||
it("should handle error response with no message", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(createErrorResponse(500, ""));
|
||||
|
||||
await expect(
|
||||
executeApiRequest({ ...baseOptions, operation: "list" }),
|
||||
).rejects.toThrow("API error");
|
||||
});
|
||||
});
|
||||
|
||||
describe("headers", () => {
|
||||
it("should include APIKey, Content-Type, and Accept headers", async () => {
|
||||
(mockPost as jest.Mock).mockResolvedValue(createSuccessResponse({}));
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "create",
|
||||
data: { name: "Test" },
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.headers["APIKey"]).toBe("test-api-key");
|
||||
expect(callArgs.headers["Content-Type"]).toBe("application/json");
|
||||
expect(callArgs.headers["Accept"]).toBe("application/json");
|
||||
});
|
||||
});
|
||||
|
||||
describe("default/unknown operation", () => {
|
||||
it("should handle unknown operation in buildRequestData (falls to default)", async () => {
|
||||
// The "delete" case hits the default branch in buildRequestData returning undefined
|
||||
(mockDelete as jest.Mock).mockResolvedValue(createSuccessResponse({}));
|
||||
|
||||
await executeApiRequest({
|
||||
...baseOptions,
|
||||
operation: "delete",
|
||||
id: "123",
|
||||
});
|
||||
|
||||
// Should not send data for delete
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callArgs: any = (mockDelete as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
257
CLI/Tests/ConfigCommands.test.ts
Normal file
257
CLI/Tests/ConfigCommands.test.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import { Command } from "commander";
|
||||
import { registerConfigCommands } from "../Commands/ConfigCommands";
|
||||
import * as ConfigManager from "../Core/ConfigManager";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
|
||||
const CONFIG_DIR: string = path.join(os.homedir(), ".oneuptime");
|
||||
const CONFIG_FILE: string = path.join(CONFIG_DIR, "config.json");
|
||||
|
||||
describe("ConfigCommands", () => {
|
||||
let originalConfigContent: string | null = null;
|
||||
let consoleLogSpy: jest.SpyInstance;
|
||||
let exitSpy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
originalConfigContent = fs.readFileSync(CONFIG_FILE, "utf-8");
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (originalConfigContent) {
|
||||
fs.writeFileSync(CONFIG_FILE, originalConfigContent, { mode: 0o600 });
|
||||
} else if (fs.existsSync(CONFIG_FILE)) {
|
||||
fs.unlinkSync(CONFIG_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
fs.unlinkSync(CONFIG_FILE);
|
||||
}
|
||||
consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => {});
|
||||
jest.spyOn(console, "error").mockImplementation(() => {});
|
||||
exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {}) as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleLogSpy.mockRestore();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
function createProgram(): Command {
|
||||
const program: Command = new Command();
|
||||
program.exitOverride(); // Prevent commander from calling process.exit
|
||||
program.configureOutput({
|
||||
writeOut: () => {},
|
||||
writeErr: () => {},
|
||||
});
|
||||
registerConfigCommands(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
describe("login command", () => {
|
||||
it("should create a context and set it as current", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"login",
|
||||
"my-api-key",
|
||||
"https://example.com",
|
||||
]);
|
||||
|
||||
const ctx: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx!.name).toBe("default");
|
||||
expect(ctx!.apiUrl).toBe("https://example.com");
|
||||
expect(ctx!.apiKey).toBe("my-api-key");
|
||||
});
|
||||
|
||||
it("should use custom context name", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"login",
|
||||
"key123",
|
||||
"https://prod.com",
|
||||
"--context-name",
|
||||
"production",
|
||||
]);
|
||||
|
||||
const ctx: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(ctx!.name).toBe("production");
|
||||
});
|
||||
|
||||
it("should handle login errors gracefully", async () => {
|
||||
// Mock addContext to throw
|
||||
const addCtxSpy: jest.SpyInstance = jest
|
||||
.spyOn(ConfigManager, "addContext")
|
||||
.mockImplementation(() => {
|
||||
throw new Error("Permission denied");
|
||||
});
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"login",
|
||||
"key123",
|
||||
"https://example.com",
|
||||
]);
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
addCtxSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("should strip trailing slashes from URL", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"login",
|
||||
"key123",
|
||||
"https://example.com///",
|
||||
]);
|
||||
|
||||
const ctx: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(ctx!.apiUrl).toBe("https://example.com");
|
||||
});
|
||||
});
|
||||
|
||||
describe("context list command", () => {
|
||||
it("should show message when no contexts exist", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "context", "list"]);
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should list contexts with current marker", async () => {
|
||||
ConfigManager.addContext({
|
||||
name: "a",
|
||||
apiUrl: "https://a.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
ConfigManager.addContext({
|
||||
name: "b",
|
||||
apiUrl: "https://b.com",
|
||||
apiKey: "k2",
|
||||
});
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "context", "list"]);
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("context use command", () => {
|
||||
it("should switch to the specified context", async () => {
|
||||
ConfigManager.addContext({
|
||||
name: "a",
|
||||
apiUrl: "https://a.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
ConfigManager.addContext({
|
||||
name: "b",
|
||||
apiUrl: "https://b.com",
|
||||
apiKey: "k2",
|
||||
});
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "context", "use", "b"]);
|
||||
|
||||
const current: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(current!.name).toBe("b");
|
||||
});
|
||||
|
||||
it("should handle non-existent context", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "context", "use", "nope"]);
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("context current command", () => {
|
||||
it("should show current context info", async () => {
|
||||
ConfigManager.addContext({
|
||||
name: "myctx",
|
||||
apiUrl: "https://myctx.com",
|
||||
apiKey: "abcdefghijklm",
|
||||
});
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "context", "current"]);
|
||||
|
||||
// Check that masked key is shown
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("Context: myctx");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("URL: https://myctx.com");
|
||||
// Key should be masked: abcd****jklm
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("****"),
|
||||
);
|
||||
});
|
||||
|
||||
it("should show message when no current context", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "context", "current"]);
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should mask short API keys", async () => {
|
||||
ConfigManager.addContext({
|
||||
name: "short",
|
||||
apiUrl: "https://s.com",
|
||||
apiKey: "abc",
|
||||
});
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "context", "current"]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("API Key: ****");
|
||||
});
|
||||
});
|
||||
|
||||
describe("context delete command", () => {
|
||||
it("should delete a context", async () => {
|
||||
ConfigManager.addContext({
|
||||
name: "todelete",
|
||||
apiUrl: "https://del.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"context",
|
||||
"delete",
|
||||
"todelete",
|
||||
]);
|
||||
|
||||
const contexts: ReturnType<typeof ConfigManager.listContexts> =
|
||||
ConfigManager.listContexts();
|
||||
expect(contexts).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should handle deletion of non-existent context", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"context",
|
||||
"delete",
|
||||
"nonexistent",
|
||||
]);
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
446
CLI/Tests/ConfigManager.test.ts
Normal file
446
CLI/Tests/ConfigManager.test.ts
Normal file
@@ -0,0 +1,446 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
import * as ConfigManager from "../Core/ConfigManager";
|
||||
import { CLIConfig, ResolvedCredentials } from "../Types/CLITypes";
|
||||
|
||||
const CONFIG_DIR: string = path.join(os.homedir(), ".oneuptime");
|
||||
const CONFIG_FILE: string = path.join(CONFIG_DIR, "config.json");
|
||||
|
||||
describe("ConfigManager", () => {
|
||||
let originalConfigContent: string | null = null;
|
||||
|
||||
beforeAll(() => {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
originalConfigContent = fs.readFileSync(CONFIG_FILE, "utf-8");
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (originalConfigContent) {
|
||||
fs.writeFileSync(CONFIG_FILE, originalConfigContent, { mode: 0o600 });
|
||||
} else if (fs.existsSync(CONFIG_FILE)) {
|
||||
fs.unlinkSync(CONFIG_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
fs.unlinkSync(CONFIG_FILE);
|
||||
}
|
||||
delete process.env["ONEUPTIME_API_KEY"];
|
||||
delete process.env["ONEUPTIME_URL"];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env["ONEUPTIME_API_KEY"];
|
||||
delete process.env["ONEUPTIME_URL"];
|
||||
});
|
||||
|
||||
describe("load", () => {
|
||||
it("should return default config when no config file exists", () => {
|
||||
const config: CLIConfig = ConfigManager.load();
|
||||
expect(config.currentContext).toBe("");
|
||||
expect(config.contexts).toEqual({});
|
||||
expect(config.defaults.output).toBe("table");
|
||||
expect(config.defaults.limit).toBe(10);
|
||||
});
|
||||
|
||||
it("should load existing config from file", () => {
|
||||
const testConfig: CLIConfig = {
|
||||
currentContext: "test",
|
||||
contexts: {
|
||||
test: { name: "test", apiUrl: "https://test.com", apiKey: "key123" },
|
||||
},
|
||||
defaults: { output: "json", limit: 20 },
|
||||
};
|
||||
if (!fs.existsSync(CONFIG_DIR)) {
|
||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(testConfig), {
|
||||
mode: 0o600,
|
||||
});
|
||||
|
||||
const config: CLIConfig = ConfigManager.load();
|
||||
expect(config.currentContext).toBe("test");
|
||||
expect(config.contexts["test"]?.apiKey).toBe("key123");
|
||||
});
|
||||
|
||||
it("should return default config when file contains invalid JSON", () => {
|
||||
if (!fs.existsSync(CONFIG_DIR)) {
|
||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(CONFIG_FILE, "not valid json {{{", { mode: 0o600 });
|
||||
|
||||
const config: CLIConfig = ConfigManager.load();
|
||||
expect(config.currentContext).toBe("");
|
||||
expect(config.contexts).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("save", () => {
|
||||
it("should create config directory if it does not exist", () => {
|
||||
// Remove the dir if it exists (we'll restore after)
|
||||
const tmpDir: string = path.join(
|
||||
os.tmpdir(),
|
||||
".oneuptime-test-" + Date.now(),
|
||||
);
|
||||
/*
|
||||
* We can't easily test this with the real path, but we verify save works
|
||||
* when the dir already exists (which it does after beforeAll).
|
||||
*/
|
||||
const config: CLIConfig = {
|
||||
currentContext: "",
|
||||
contexts: {},
|
||||
defaults: { output: "table", limit: 10 },
|
||||
};
|
||||
ConfigManager.save(config);
|
||||
expect(fs.existsSync(CONFIG_FILE)).toBe(true);
|
||||
void tmpDir; // unused but shows intent
|
||||
});
|
||||
|
||||
it("should write config with correct permissions", () => {
|
||||
const config: CLIConfig = {
|
||||
currentContext: "x",
|
||||
contexts: {
|
||||
x: { name: "x", apiUrl: "https://x.com", apiKey: "k" },
|
||||
},
|
||||
defaults: { output: "table", limit: 10 },
|
||||
};
|
||||
ConfigManager.save(config);
|
||||
const content: string = fs.readFileSync(CONFIG_FILE, "utf-8");
|
||||
const parsed: CLIConfig = JSON.parse(content);
|
||||
expect(parsed.currentContext).toBe("x");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCurrentContext", () => {
|
||||
it("should return null when no current context is set", () => {
|
||||
expect(ConfigManager.getCurrentContext()).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null when currentContext name does not match any context", () => {
|
||||
// Manually write a config with a dangling currentContext reference
|
||||
const config: CLIConfig = {
|
||||
currentContext: "ghost",
|
||||
contexts: {},
|
||||
defaults: { output: "table", limit: 10 },
|
||||
};
|
||||
ConfigManager.save(config);
|
||||
expect(ConfigManager.getCurrentContext()).toBeNull();
|
||||
});
|
||||
|
||||
it("should return the current context when set", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "prod",
|
||||
apiUrl: "https://prod.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
const ctx: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(ctx).not.toBeNull();
|
||||
expect(ctx!.name).toBe("prod");
|
||||
});
|
||||
});
|
||||
|
||||
describe("addContext", () => {
|
||||
it("should add a context and set it as current if first context", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "prod",
|
||||
apiUrl: "https://prod.oneuptime.com",
|
||||
apiKey: "sk-prod-123",
|
||||
});
|
||||
|
||||
const current: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(current).not.toBeNull();
|
||||
expect(current!.name).toBe("prod");
|
||||
});
|
||||
|
||||
it("should not change current context when adding a second context", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "prod",
|
||||
apiUrl: "https://prod.com",
|
||||
apiKey: "key1",
|
||||
});
|
||||
ConfigManager.addContext({
|
||||
name: "staging",
|
||||
apiUrl: "https://staging.com",
|
||||
apiKey: "key2",
|
||||
});
|
||||
|
||||
const current: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(current!.name).toBe("prod"); // First one remains current
|
||||
});
|
||||
|
||||
it("should add multiple contexts", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "prod",
|
||||
apiUrl: "https://prod.com",
|
||||
apiKey: "key1",
|
||||
});
|
||||
ConfigManager.addContext({
|
||||
name: "staging",
|
||||
apiUrl: "https://staging.com",
|
||||
apiKey: "key2",
|
||||
});
|
||||
|
||||
const contexts: ReturnType<typeof ConfigManager.listContexts> =
|
||||
ConfigManager.listContexts();
|
||||
expect(contexts).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setCurrentContext", () => {
|
||||
it("should switch the active context", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "a",
|
||||
apiUrl: "https://a.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
ConfigManager.addContext({
|
||||
name: "b",
|
||||
apiUrl: "https://b.com",
|
||||
apiKey: "k2",
|
||||
});
|
||||
|
||||
ConfigManager.setCurrentContext("b");
|
||||
const current: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(current!.name).toBe("b");
|
||||
});
|
||||
|
||||
it("should throw for non-existent context", () => {
|
||||
expect(() => {
|
||||
return ConfigManager.setCurrentContext("nonexistent");
|
||||
}).toThrow('Context "nonexistent" does not exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeContext", () => {
|
||||
it("should remove a context", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "test",
|
||||
apiUrl: "https://test.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
ConfigManager.removeContext("test");
|
||||
|
||||
const contexts: ReturnType<typeof ConfigManager.listContexts> =
|
||||
ConfigManager.listContexts();
|
||||
expect(contexts).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should throw for non-existent context", () => {
|
||||
expect(() => {
|
||||
return ConfigManager.removeContext("nonexistent");
|
||||
}).toThrow('Context "nonexistent" does not exist');
|
||||
});
|
||||
|
||||
it("should update current context when removing the current one", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "a",
|
||||
apiUrl: "https://a.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
ConfigManager.addContext({
|
||||
name: "b",
|
||||
apiUrl: "https://b.com",
|
||||
apiKey: "k2",
|
||||
});
|
||||
ConfigManager.setCurrentContext("a");
|
||||
ConfigManager.removeContext("a");
|
||||
|
||||
const current: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(current).not.toBeNull();
|
||||
expect(current!.name).toBe("b");
|
||||
});
|
||||
|
||||
it("should set current context to empty when removing last context", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "only",
|
||||
apiUrl: "https://only.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
ConfigManager.removeContext("only");
|
||||
|
||||
expect(ConfigManager.getCurrentContext()).toBeNull();
|
||||
const config: CLIConfig = ConfigManager.load();
|
||||
expect(config.currentContext).toBe("");
|
||||
});
|
||||
|
||||
it("should not change current context when removing a non-current one", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "a",
|
||||
apiUrl: "https://a.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
ConfigManager.addContext({
|
||||
name: "b",
|
||||
apiUrl: "https://b.com",
|
||||
apiKey: "k2",
|
||||
});
|
||||
ConfigManager.setCurrentContext("a");
|
||||
ConfigManager.removeContext("b");
|
||||
|
||||
const current: ReturnType<typeof ConfigManager.getCurrentContext> =
|
||||
ConfigManager.getCurrentContext();
|
||||
expect(current!.name).toBe("a");
|
||||
});
|
||||
});
|
||||
|
||||
describe("listContexts", () => {
|
||||
it("should return empty array when no contexts exist", () => {
|
||||
expect(ConfigManager.listContexts()).toEqual([]);
|
||||
});
|
||||
|
||||
it("should mark current context correctly", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "a",
|
||||
apiUrl: "https://a.com",
|
||||
apiKey: "k1",
|
||||
});
|
||||
ConfigManager.addContext({
|
||||
name: "b",
|
||||
apiUrl: "https://b.com",
|
||||
apiKey: "k2",
|
||||
});
|
||||
ConfigManager.setCurrentContext("b");
|
||||
|
||||
const contexts: ReturnType<typeof ConfigManager.listContexts> =
|
||||
ConfigManager.listContexts();
|
||||
const a:
|
||||
| ReturnType<typeof ConfigManager.listContexts>[number]
|
||||
| undefined = contexts.find(
|
||||
(c: ReturnType<typeof ConfigManager.listContexts>[number]) => {
|
||||
return c.name === "a";
|
||||
},
|
||||
);
|
||||
const b:
|
||||
| ReturnType<typeof ConfigManager.listContexts>[number]
|
||||
| undefined = contexts.find(
|
||||
(c: ReturnType<typeof ConfigManager.listContexts>[number]) => {
|
||||
return c.name === "b";
|
||||
},
|
||||
);
|
||||
expect(a!.isCurrent).toBe(false);
|
||||
expect(b!.isCurrent).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResolvedCredentials", () => {
|
||||
it("should resolve from CLI options first", () => {
|
||||
const creds: ResolvedCredentials = ConfigManager.getResolvedCredentials({
|
||||
apiKey: "cli-key",
|
||||
url: "https://cli.com",
|
||||
});
|
||||
expect(creds.apiKey).toBe("cli-key");
|
||||
expect(creds.apiUrl).toBe("https://cli.com");
|
||||
});
|
||||
|
||||
it("should resolve from env vars when CLI options are missing", () => {
|
||||
process.env["ONEUPTIME_API_KEY"] = "env-key";
|
||||
process.env["ONEUPTIME_URL"] = "https://env.com";
|
||||
|
||||
const creds: ResolvedCredentials = ConfigManager.getResolvedCredentials(
|
||||
{},
|
||||
);
|
||||
expect(creds.apiKey).toBe("env-key");
|
||||
expect(creds.apiUrl).toBe("https://env.com");
|
||||
});
|
||||
|
||||
it("should resolve from --context flag", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "named",
|
||||
apiUrl: "https://named.com",
|
||||
apiKey: "named-key",
|
||||
});
|
||||
|
||||
const creds: ResolvedCredentials = ConfigManager.getResolvedCredentials({
|
||||
context: "named",
|
||||
});
|
||||
expect(creds.apiKey).toBe("named-key");
|
||||
expect(creds.apiUrl).toBe("https://named.com");
|
||||
});
|
||||
|
||||
it("should throw when --context flag references non-existent context", () => {
|
||||
expect(() => {
|
||||
return ConfigManager.getResolvedCredentials({ context: "nope" });
|
||||
}).toThrow('Context "nope" does not exist');
|
||||
});
|
||||
|
||||
it("should resolve from current context in config", () => {
|
||||
ConfigManager.addContext({
|
||||
name: "ctx",
|
||||
apiUrl: "https://ctx.com",
|
||||
apiKey: "ctx-key",
|
||||
});
|
||||
|
||||
const creds: ResolvedCredentials = ConfigManager.getResolvedCredentials(
|
||||
{},
|
||||
);
|
||||
expect(creds.apiKey).toBe("ctx-key");
|
||||
expect(creds.apiUrl).toBe("https://ctx.com");
|
||||
});
|
||||
|
||||
it("should resolve from partial env vars (only ONEUPTIME_API_KEY)", () => {
|
||||
process.env["ONEUPTIME_API_KEY"] = "partial-key";
|
||||
|
||||
const creds: ResolvedCredentials = ConfigManager.getResolvedCredentials(
|
||||
{},
|
||||
);
|
||||
expect(creds.apiKey).toBe("partial-key");
|
||||
expect(creds.apiUrl).toBe("");
|
||||
});
|
||||
|
||||
it("should resolve from partial env vars (only ONEUPTIME_URL)", () => {
|
||||
process.env["ONEUPTIME_URL"] = "https://partial.com";
|
||||
|
||||
const creds: ResolvedCredentials = ConfigManager.getResolvedCredentials(
|
||||
{},
|
||||
);
|
||||
expect(creds.apiKey).toBe("");
|
||||
expect(creds.apiUrl).toBe("https://partial.com");
|
||||
});
|
||||
|
||||
it("should combine partial env var with context", () => {
|
||||
process.env["ONEUPTIME_API_KEY"] = "env-key";
|
||||
ConfigManager.addContext({
|
||||
name: "ctx",
|
||||
apiUrl: "https://ctx.com",
|
||||
apiKey: "ctx-key",
|
||||
});
|
||||
|
||||
const creds: ResolvedCredentials = ConfigManager.getResolvedCredentials(
|
||||
{},
|
||||
);
|
||||
/*
|
||||
* env vars take priority: both are set so goes through priority 2
|
||||
* Actually, only ONEUPTIME_API_KEY is set, not ONEUPTIME_URL
|
||||
* So it falls through to priority 4 (current context)
|
||||
*/
|
||||
expect(creds.apiKey).toBe("ctx-key");
|
||||
expect(creds.apiUrl).toBe("https://ctx.com");
|
||||
});
|
||||
|
||||
it("should throw when no credentials available at all", () => {
|
||||
expect(() => {
|
||||
return ConfigManager.getResolvedCredentials({});
|
||||
}).toThrow("No credentials found");
|
||||
});
|
||||
|
||||
it("should prefer CLI flags over env vars", () => {
|
||||
process.env["ONEUPTIME_API_KEY"] = "env-key";
|
||||
process.env["ONEUPTIME_URL"] = "https://env.com";
|
||||
|
||||
const creds: ResolvedCredentials = ConfigManager.getResolvedCredentials({
|
||||
apiKey: "cli-key",
|
||||
url: "https://cli.com",
|
||||
});
|
||||
expect(creds.apiKey).toBe("cli-key");
|
||||
expect(creds.apiUrl).toBe("https://cli.com");
|
||||
});
|
||||
});
|
||||
});
|
||||
98
CLI/Tests/ErrorHandler.test.ts
Normal file
98
CLI/Tests/ErrorHandler.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { handleError, ExitCode } from "../Core/ErrorHandler";
|
||||
import * as OutputFormatter from "../Core/OutputFormatter";
|
||||
|
||||
describe("ErrorHandler", () => {
|
||||
let exitSpy: jest.SpyInstance;
|
||||
let printErrorSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {
|
||||
// no-op
|
||||
}) as any);
|
||||
printErrorSpy = jest
|
||||
.spyOn(OutputFormatter, "printError")
|
||||
.mockImplementation(() => {
|
||||
// no-op
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
exitSpy.mockRestore();
|
||||
printErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("should exit with AuthError for API key errors", () => {
|
||||
handleError(new Error("Invalid API key provided"));
|
||||
expect(printErrorSpy).toHaveBeenCalledWith(
|
||||
"Authentication error: Invalid API key provided",
|
||||
);
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.AuthError);
|
||||
});
|
||||
|
||||
it("should exit with AuthError for credentials errors", () => {
|
||||
handleError(new Error("No credentials found"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.AuthError);
|
||||
});
|
||||
|
||||
it("should exit with AuthError for Unauthorized errors", () => {
|
||||
handleError(new Error("Unauthorized access"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.AuthError);
|
||||
});
|
||||
|
||||
it("should exit with AuthError for 401 errors", () => {
|
||||
handleError(new Error("HTTP 401 response"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.AuthError);
|
||||
});
|
||||
|
||||
it("should exit with NotFound for 404 errors", () => {
|
||||
handleError(new Error("HTTP 404 response"));
|
||||
expect(printErrorSpy).toHaveBeenCalledWith("Not found: HTTP 404 response");
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.NotFound);
|
||||
});
|
||||
|
||||
it("should exit with NotFound for not found errors", () => {
|
||||
handleError(new Error("Resource not found"));
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.NotFound);
|
||||
});
|
||||
|
||||
it("should exit with GeneralError for API error messages", () => {
|
||||
handleError(new Error("API error (500): Internal Server Error"));
|
||||
expect(printErrorSpy).toHaveBeenCalledWith(
|
||||
"API error (500): Internal Server Error",
|
||||
);
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.GeneralError);
|
||||
});
|
||||
|
||||
it("should exit with GeneralError for generic Error objects", () => {
|
||||
handleError(new Error("Something went wrong"));
|
||||
expect(printErrorSpy).toHaveBeenCalledWith("Error: Something went wrong");
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.GeneralError);
|
||||
});
|
||||
|
||||
it("should handle non-Error objects", () => {
|
||||
handleError("string error");
|
||||
expect(printErrorSpy).toHaveBeenCalledWith("Error: string error");
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.GeneralError);
|
||||
});
|
||||
|
||||
it("should handle null error", () => {
|
||||
handleError(null);
|
||||
expect(printErrorSpy).toHaveBeenCalledWith("Error: null");
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.GeneralError);
|
||||
});
|
||||
|
||||
it("should handle number error", () => {
|
||||
handleError(42);
|
||||
expect(printErrorSpy).toHaveBeenCalledWith("Error: 42");
|
||||
expect(exitSpy).toHaveBeenCalledWith(ExitCode.GeneralError);
|
||||
});
|
||||
|
||||
describe("ExitCode enum", () => {
|
||||
it("should have correct values", () => {
|
||||
expect(ExitCode.Success).toBe(0);
|
||||
expect(ExitCode.GeneralError).toBe(1);
|
||||
expect(ExitCode.AuthError).toBe(2);
|
||||
expect(ExitCode.NotFound).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
66
CLI/Tests/Index.test.ts
Normal file
66
CLI/Tests/Index.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Command, Option } from "commander";
|
||||
import { registerConfigCommands } from "../Commands/ConfigCommands";
|
||||
import { registerResourceCommands } from "../Commands/ResourceCommands";
|
||||
import { registerUtilityCommands } from "../Commands/UtilityCommands";
|
||||
|
||||
describe("Index (CLI entry point)", () => {
|
||||
it("should create a program with all command groups registered", () => {
|
||||
const program: Command = new Command();
|
||||
program
|
||||
.name("oneuptime")
|
||||
.description(
|
||||
"OneUptime CLI - Manage your OneUptime resources from the command line",
|
||||
)
|
||||
.version("1.0.0")
|
||||
.option("--api-key <key>", "API key (overrides config)")
|
||||
.option("--url <url>", "OneUptime instance URL (overrides config)")
|
||||
.option("--context <name>", "Use a specific context")
|
||||
.option("-o, --output <format>", "Output format: json, table, wide")
|
||||
.option("--no-color", "Disable colored output");
|
||||
|
||||
registerConfigCommands(program);
|
||||
registerUtilityCommands(program);
|
||||
registerResourceCommands(program);
|
||||
|
||||
// Verify all expected commands are registered
|
||||
const commandNames: string[] = program.commands.map((c: Command) => {
|
||||
return c.name();
|
||||
});
|
||||
expect(commandNames).toContain("login");
|
||||
expect(commandNames).toContain("context");
|
||||
expect(commandNames).toContain("version");
|
||||
expect(commandNames).toContain("whoami");
|
||||
expect(commandNames).toContain("resources");
|
||||
expect(commandNames).toContain("incident");
|
||||
expect(commandNames).toContain("monitor");
|
||||
expect(commandNames).toContain("alert");
|
||||
});
|
||||
|
||||
it("should set correct program name and description", () => {
|
||||
const program: Command = new Command();
|
||||
program.name("oneuptime").description("OneUptime CLI");
|
||||
|
||||
expect(program.name()).toBe("oneuptime");
|
||||
});
|
||||
|
||||
it("should define global options", () => {
|
||||
const program: Command = new Command();
|
||||
program
|
||||
.option("--api-key <key>", "API key")
|
||||
.option("--url <url>", "URL")
|
||||
.option("--context <name>", "Context")
|
||||
.option("-o, --output <format>", "Output format")
|
||||
.option("--no-color", "Disable color");
|
||||
|
||||
// Parse with just the program name - verify options are registered
|
||||
const options: readonly Option[] = program.options;
|
||||
const optionNames: (string | undefined)[] = options.map((o: Option) => {
|
||||
return o.long || o.short;
|
||||
});
|
||||
expect(optionNames).toContain("--api-key");
|
||||
expect(optionNames).toContain("--url");
|
||||
expect(optionNames).toContain("--context");
|
||||
expect(optionNames).toContain("--output");
|
||||
expect(optionNames).toContain("--no-color");
|
||||
});
|
||||
});
|
||||
373
CLI/Tests/OutputFormatter.test.ts
Normal file
373
CLI/Tests/OutputFormatter.test.ts
Normal file
@@ -0,0 +1,373 @@
|
||||
import {
|
||||
formatOutput,
|
||||
printSuccess,
|
||||
printError,
|
||||
printWarning,
|
||||
printInfo,
|
||||
} from "../Core/OutputFormatter";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
describe("OutputFormatter", () => {
|
||||
let consoleLogSpy: jest.SpyInstance;
|
||||
let consoleErrorSpy: jest.SpyInstance;
|
||||
let originalNoColor: string | undefined;
|
||||
let originalArgv: string[];
|
||||
|
||||
beforeEach(() => {
|
||||
consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => {});
|
||||
consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
|
||||
originalNoColor = process.env["NO_COLOR"];
|
||||
originalArgv = [...process.argv];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleLogSpy.mockRestore();
|
||||
consoleErrorSpy.mockRestore();
|
||||
if (originalNoColor !== undefined) {
|
||||
process.env["NO_COLOR"] = originalNoColor;
|
||||
} else {
|
||||
delete process.env["NO_COLOR"];
|
||||
}
|
||||
process.argv = originalArgv;
|
||||
});
|
||||
|
||||
describe("formatOutput with JSON format", () => {
|
||||
it("should format single object as JSON", () => {
|
||||
const data: Record<string, string> = { id: "123", name: "Test" };
|
||||
const result: string = formatOutput(data, "json");
|
||||
expect(JSON.parse(result)).toEqual(data);
|
||||
});
|
||||
|
||||
it("should format array as JSON", () => {
|
||||
const data: Record<string, string>[] = [
|
||||
{ id: "1", name: "A" },
|
||||
{ id: "2", name: "B" },
|
||||
];
|
||||
const result: string = formatOutput(data, "json");
|
||||
expect(JSON.parse(result)).toEqual(data);
|
||||
});
|
||||
|
||||
it("should format null as JSON", () => {
|
||||
const result: string = formatOutput(null, "json");
|
||||
expect(result).toBe("null");
|
||||
});
|
||||
|
||||
it("should format number as JSON", () => {
|
||||
const result: string = formatOutput(42, "json");
|
||||
expect(result).toBe("42");
|
||||
});
|
||||
|
||||
it("should format string as JSON", () => {
|
||||
const result: string = formatOutput("hello", "json");
|
||||
expect(result).toBe('"hello"');
|
||||
});
|
||||
|
||||
it("should format boolean as JSON", () => {
|
||||
const result: string = formatOutput(true, "json");
|
||||
expect(result).toBe("true");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatOutput with table format", () => {
|
||||
it("should format array as table", () => {
|
||||
const data: Record<string, string>[] = [
|
||||
{ _id: "1", name: "A" },
|
||||
{ _id: "2", name: "B" },
|
||||
];
|
||||
const result: string = formatOutput(data, "table");
|
||||
expect(result).toContain("1");
|
||||
expect(result).toContain("A");
|
||||
expect(result).toContain("2");
|
||||
expect(result).toContain("B");
|
||||
});
|
||||
|
||||
it("should handle empty array", () => {
|
||||
const result: string = formatOutput([], "table");
|
||||
expect(result).toBe("No results found.");
|
||||
});
|
||||
|
||||
it("should handle single object as key-value table", () => {
|
||||
const data: Record<string, string> = { name: "Test", status: "Active" };
|
||||
const result: string = formatOutput(data, "table");
|
||||
expect(result).toContain("Test");
|
||||
expect(result).toContain("Active");
|
||||
});
|
||||
|
||||
it("should return 'No data returned.' for null in table mode", () => {
|
||||
const result: string = formatOutput(null, "table");
|
||||
expect(result).toBe("No data returned.");
|
||||
});
|
||||
|
||||
it("should return 'No data returned.' for undefined in table mode", () => {
|
||||
const result: string = formatOutput(undefined as any, "table");
|
||||
expect(result).toBe("No data returned.");
|
||||
});
|
||||
|
||||
it("should return 'No data returned.' for empty string in table mode", () => {
|
||||
const result: string = formatOutput("" as any, "table");
|
||||
expect(result).toBe("No data returned.");
|
||||
});
|
||||
|
||||
it("should fallback to JSON for array of non-objects", () => {
|
||||
const data: string[] = ["a", "b", "c"];
|
||||
const result: string = formatOutput(data, "table");
|
||||
// First item is not an object, so should fallback to JSON
|
||||
expect(result).toContain('"a"');
|
||||
});
|
||||
|
||||
it("should truncate long string values", () => {
|
||||
const longValue: string = "x".repeat(100);
|
||||
const data: Record<string, string>[] = [{ _id: "1", field: longValue }];
|
||||
const result: string = formatOutput(data, "table");
|
||||
expect(result).toContain("...");
|
||||
});
|
||||
|
||||
it("should truncate long object values", () => {
|
||||
const bigObj: Record<string, string> = { a: "x".repeat(80) };
|
||||
const data: JSONObject[] = [{ _id: "1", nested: bigObj }];
|
||||
const result: string = formatOutput(data, "table");
|
||||
expect(result).toContain("...");
|
||||
});
|
||||
|
||||
it("should show short object values without truncation", () => {
|
||||
const smallObj: Record<string, number> = { a: 1 };
|
||||
const data: JSONObject[] = [{ _id: "1", nested: smallObj }];
|
||||
const result: string = formatOutput(data, "table");
|
||||
expect(result).toContain('{"a":1}');
|
||||
});
|
||||
|
||||
it("should render null values as empty in table", () => {
|
||||
const data: JSONObject[] = [{ _id: "1", value: null }];
|
||||
const result: string = formatOutput(data, "table");
|
||||
expect(result).toContain("1");
|
||||
});
|
||||
|
||||
it("should render undefined values as empty in table", () => {
|
||||
const data: JSONObject[] = [{ _id: "1", value: undefined }];
|
||||
const result: string = formatOutput(data, "table");
|
||||
expect(result).toContain("1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatOutput with wide format", () => {
|
||||
it("should show all columns in wide mode", () => {
|
||||
const data: Record<string, string>[] = [
|
||||
{
|
||||
_id: "1",
|
||||
name: "A",
|
||||
col1: "x",
|
||||
col2: "y",
|
||||
col3: "z",
|
||||
col4: "w",
|
||||
col5: "v",
|
||||
col6: "u",
|
||||
col7: "t",
|
||||
},
|
||||
];
|
||||
const result: string = formatOutput(data, "wide");
|
||||
expect(result).toContain("col7");
|
||||
});
|
||||
|
||||
it("should limit columns in non-wide table mode", () => {
|
||||
const data: Record<string, string>[] = [
|
||||
{
|
||||
_id: "1",
|
||||
name: "A",
|
||||
col1: "x",
|
||||
col2: "y",
|
||||
col3: "z",
|
||||
col4: "w",
|
||||
col5: "v",
|
||||
col6: "u",
|
||||
col7: "t",
|
||||
},
|
||||
];
|
||||
const result: string = formatOutput(data, "table");
|
||||
// Table mode should limit to 6 columns, so col7 should not appear
|
||||
expect(result).not.toContain("col7");
|
||||
});
|
||||
|
||||
it("should prioritize common columns in non-wide mode", () => {
|
||||
const data: Record<string, string>[] = [
|
||||
{
|
||||
extra1: "a",
|
||||
extra2: "b",
|
||||
extra3: "c",
|
||||
extra4: "d",
|
||||
extra5: "e",
|
||||
extra6: "f",
|
||||
_id: "1",
|
||||
name: "Test",
|
||||
title: "Title",
|
||||
status: "Active",
|
||||
createdAt: "2024-01-01",
|
||||
updatedAt: "2024-01-02",
|
||||
},
|
||||
];
|
||||
const result: string = formatOutput(data, "table");
|
||||
// Priority columns should appear
|
||||
expect(result).toContain("_id");
|
||||
expect(result).toContain("name");
|
||||
});
|
||||
});
|
||||
|
||||
describe("format auto-detection", () => {
|
||||
it("should default to JSON when not a TTY", () => {
|
||||
const originalIsTTY: boolean | undefined = process.stdout.isTTY;
|
||||
Object.defineProperty(process.stdout, "isTTY", {
|
||||
value: false,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const data: Record<string, string> = { id: "1" };
|
||||
const result: string = formatOutput(data);
|
||||
expect(() => {
|
||||
return JSON.parse(result);
|
||||
}).not.toThrow();
|
||||
|
||||
Object.defineProperty(process.stdout, "isTTY", {
|
||||
value: originalIsTTY,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should default to table when TTY", () => {
|
||||
const originalIsTTY: boolean | undefined = process.stdout.isTTY;
|
||||
Object.defineProperty(process.stdout, "isTTY", {
|
||||
value: true,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const data: Record<string, string>[] = [{ _id: "1", name: "Test" }];
|
||||
const result: string = formatOutput(data);
|
||||
// Table format contains box-drawing characters
|
||||
expect(result).toContain("\u2500");
|
||||
|
||||
Object.defineProperty(process.stdout, "isTTY", {
|
||||
value: originalIsTTY,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle unknown format string and default to table via TTY check", () => {
|
||||
const data: Record<string, string>[] = [{ _id: "1" }];
|
||||
// "unknown" is not json/table/wide, so cliFormat falls through and TTY detection occurs
|
||||
const originalIsTTY: boolean | undefined = process.stdout.isTTY;
|
||||
Object.defineProperty(process.stdout, "isTTY", {
|
||||
value: true,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const result: string = formatOutput(data, "unknown");
|
||||
expect(result).toContain("\u2500");
|
||||
|
||||
Object.defineProperty(process.stdout, "isTTY", {
|
||||
value: originalIsTTY,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("color handling", () => {
|
||||
it("should respect NO_COLOR env variable in table rendering", () => {
|
||||
process.env["NO_COLOR"] = "1";
|
||||
const data: Record<string, string>[] = [{ _id: "1", name: "A" }];
|
||||
const result: string = formatOutput(data, "table");
|
||||
// Should not contain ANSI color codes
|
||||
// eslint-disable-next-line no-control-regex
|
||||
expect(result).not.toMatch(/\x1b\[/);
|
||||
});
|
||||
|
||||
it("should respect --no-color argv flag in table rendering", () => {
|
||||
process.argv.push("--no-color");
|
||||
const data: Record<string, string>[] = [{ _id: "1", name: "A" }];
|
||||
const result: string = formatOutput(data, "table");
|
||||
// eslint-disable-next-line no-control-regex
|
||||
expect(result).not.toMatch(/\x1b\[/);
|
||||
});
|
||||
|
||||
it("should render single object without color when NO_COLOR set", () => {
|
||||
process.env["NO_COLOR"] = "1";
|
||||
const data: Record<string, string> = { name: "Test" };
|
||||
const result: string = formatOutput(data, "table");
|
||||
// eslint-disable-next-line no-control-regex
|
||||
expect(result).not.toMatch(/\x1b\[/);
|
||||
expect(result).toContain("name");
|
||||
});
|
||||
});
|
||||
|
||||
describe("printSuccess", () => {
|
||||
it("should log success message with color", () => {
|
||||
delete process.env["NO_COLOR"];
|
||||
// Remove --no-color from argv if present
|
||||
process.argv = process.argv.filter((a: string) => {
|
||||
return a !== "--no-color";
|
||||
});
|
||||
printSuccess("OK");
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should log success message without color when NO_COLOR is set", () => {
|
||||
process.env["NO_COLOR"] = "1";
|
||||
printSuccess("OK");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("OK");
|
||||
});
|
||||
});
|
||||
|
||||
describe("printError", () => {
|
||||
it("should log error message with color", () => {
|
||||
delete process.env["NO_COLOR"];
|
||||
process.argv = process.argv.filter((a: string) => {
|
||||
return a !== "--no-color";
|
||||
});
|
||||
printError("fail");
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should log error message without color when NO_COLOR is set", () => {
|
||||
process.env["NO_COLOR"] = "1";
|
||||
printError("fail");
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith("fail");
|
||||
});
|
||||
});
|
||||
|
||||
describe("printWarning", () => {
|
||||
it("should log warning message with color", () => {
|
||||
delete process.env["NO_COLOR"];
|
||||
process.argv = process.argv.filter((a: string) => {
|
||||
return a !== "--no-color";
|
||||
});
|
||||
printWarning("warn");
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should log warning message without color when NO_COLOR is set", () => {
|
||||
process.env["NO_COLOR"] = "1";
|
||||
printWarning("warn");
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith("warn");
|
||||
});
|
||||
});
|
||||
|
||||
describe("printInfo", () => {
|
||||
it("should log info message with color", () => {
|
||||
delete process.env["NO_COLOR"];
|
||||
process.argv = process.argv.filter((a: string) => {
|
||||
return a !== "--no-color";
|
||||
});
|
||||
printInfo("info");
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should log info message without color when NO_COLOR is set", () => {
|
||||
process.env["NO_COLOR"] = "1";
|
||||
printInfo("info");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("info");
|
||||
});
|
||||
});
|
||||
});
|
||||
568
CLI/Tests/ResourceCommands.test.ts
Normal file
568
CLI/Tests/ResourceCommands.test.ts
Normal file
@@ -0,0 +1,568 @@
|
||||
import { Command } from "commander";
|
||||
import { ResourceInfo } from "../Types/CLITypes";
|
||||
import * as ConfigManager from "../Core/ConfigManager";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
|
||||
// Mock the ApiClient module before it's imported by ResourceCommands
|
||||
const mockExecuteApiRequest: jest.Mock = jest.fn();
|
||||
jest.mock("../Core/ApiClient", () => {
|
||||
return {
|
||||
...jest.requireActual("../Core/ApiClient"),
|
||||
executeApiRequest: (...args: unknown[]): unknown => {
|
||||
return mockExecuteApiRequest(...args);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Import after mock setup
|
||||
import {
|
||||
discoverResources,
|
||||
registerResourceCommands,
|
||||
} from "../Commands/ResourceCommands";
|
||||
|
||||
const CONFIG_DIR: string = path.join(os.homedir(), ".oneuptime");
|
||||
const CONFIG_FILE: string = path.join(CONFIG_DIR, "config.json");
|
||||
|
||||
describe("ResourceCommands", () => {
|
||||
let originalConfigContent: string | null = null;
|
||||
|
||||
beforeAll(() => {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
originalConfigContent = fs.readFileSync(CONFIG_FILE, "utf-8");
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (originalConfigContent) {
|
||||
fs.writeFileSync(CONFIG_FILE, originalConfigContent, { mode: 0o600 });
|
||||
} else if (fs.existsSync(CONFIG_FILE)) {
|
||||
fs.unlinkSync(CONFIG_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
fs.unlinkSync(CONFIG_FILE);
|
||||
}
|
||||
jest.spyOn(console, "log").mockImplementation(() => {});
|
||||
jest.spyOn(console, "error").mockImplementation(() => {});
|
||||
jest.spyOn(process, "exit").mockImplementation((() => {}) as any);
|
||||
mockExecuteApiRequest.mockReset();
|
||||
delete process.env["ONEUPTIME_API_KEY"];
|
||||
delete process.env["ONEUPTIME_URL"];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
delete process.env["ONEUPTIME_API_KEY"];
|
||||
delete process.env["ONEUPTIME_URL"];
|
||||
});
|
||||
|
||||
describe("discoverResources", () => {
|
||||
let resources: ResourceInfo[];
|
||||
|
||||
beforeAll(() => {
|
||||
resources = discoverResources();
|
||||
});
|
||||
|
||||
it("should discover at least one resource", () => {
|
||||
expect(resources.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should discover the Incident resource", () => {
|
||||
const incident: ResourceInfo | undefined = resources.find(
|
||||
(r: ResourceInfo) => {
|
||||
return r.singularName === "Incident";
|
||||
},
|
||||
);
|
||||
expect(incident).toBeDefined();
|
||||
expect(incident!.modelType).toBe("database");
|
||||
expect(incident!.apiPath).toBe("/incident");
|
||||
});
|
||||
|
||||
it("should discover the Monitor resource", () => {
|
||||
const monitor: ResourceInfo | undefined = resources.find(
|
||||
(r: ResourceInfo) => {
|
||||
return r.singularName === "Monitor";
|
||||
},
|
||||
);
|
||||
expect(monitor).toBeDefined();
|
||||
expect(monitor!.modelType).toBe("database");
|
||||
});
|
||||
|
||||
it("should discover the Alert resource", () => {
|
||||
const alert: ResourceInfo | undefined = resources.find(
|
||||
(r: ResourceInfo) => {
|
||||
return r.singularName === "Alert";
|
||||
},
|
||||
);
|
||||
expect(alert).toBeDefined();
|
||||
});
|
||||
|
||||
it("should have kebab-case names for all resources", () => {
|
||||
for (const r of resources) {
|
||||
expect(r.name).toMatch(/^[a-z][a-z0-9-]*$/);
|
||||
}
|
||||
});
|
||||
|
||||
it("should have apiPath for all resources", () => {
|
||||
for (const r of resources) {
|
||||
expect(r.apiPath).toBeTruthy();
|
||||
expect(r.apiPath.startsWith("/")).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("should have valid modelType for all resources", () => {
|
||||
for (const r of resources) {
|
||||
expect(["database", "analytics"]).toContain(r.modelType);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("registerResourceCommands", () => {
|
||||
it("should register commands for all discovered resources", () => {
|
||||
const program: Command = new Command();
|
||||
program.exitOverride();
|
||||
registerResourceCommands(program);
|
||||
|
||||
const resources: ResourceInfo[] = discoverResources();
|
||||
for (const resource of resources) {
|
||||
const cmd: Command | undefined = program.commands.find((c: Command) => {
|
||||
return c.name() === resource.name;
|
||||
});
|
||||
expect(cmd).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("should register list, get, create, update, delete, count subcommands for database resources", () => {
|
||||
const program: Command = new Command();
|
||||
program.exitOverride();
|
||||
registerResourceCommands(program);
|
||||
|
||||
const incidentCmd: Command | undefined = program.commands.find(
|
||||
(c: Command) => {
|
||||
return c.name() === "incident";
|
||||
},
|
||||
);
|
||||
expect(incidentCmd).toBeDefined();
|
||||
|
||||
const subcommands: string[] = incidentCmd!.commands.map((c: Command) => {
|
||||
return c.name();
|
||||
});
|
||||
expect(subcommands).toContain("list");
|
||||
expect(subcommands).toContain("get");
|
||||
expect(subcommands).toContain("create");
|
||||
expect(subcommands).toContain("update");
|
||||
expect(subcommands).toContain("delete");
|
||||
expect(subcommands).toContain("count");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resource command actions", () => {
|
||||
function createProgramWithResources(): Command {
|
||||
const program: Command = new Command();
|
||||
program.exitOverride();
|
||||
program.configureOutput({
|
||||
writeOut: () => {},
|
||||
writeErr: () => {},
|
||||
});
|
||||
program
|
||||
.option("--api-key <key>", "API key")
|
||||
.option("--url <url>", "URL")
|
||||
.option("--context <name>", "Context");
|
||||
registerResourceCommands(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
ConfigManager.addContext({
|
||||
name: "test",
|
||||
apiUrl: "https://test.oneuptime.com",
|
||||
apiKey: "test-key-12345",
|
||||
});
|
||||
mockExecuteApiRequest.mockResolvedValue({ data: [] });
|
||||
});
|
||||
|
||||
describe("list subcommand", () => {
|
||||
it("should call API with list operation", async () => {
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync(["node", "test", "incident", "list"]);
|
||||
|
||||
expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteApiRequest.mock.calls[0][0].operation).toBe("list");
|
||||
expect(mockExecuteApiRequest.mock.calls[0][0].apiPath).toBe(
|
||||
"/incident",
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass query, limit, skip, sort options", async () => {
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"list",
|
||||
"--query",
|
||||
'{"status":"active"}',
|
||||
"--limit",
|
||||
"20",
|
||||
"--skip",
|
||||
"5",
|
||||
"--sort",
|
||||
'{"createdAt":-1}',
|
||||
]);
|
||||
|
||||
expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1);
|
||||
const opts: Record<string, unknown> =
|
||||
mockExecuteApiRequest.mock.calls[0][0];
|
||||
expect(opts.query).toEqual({ status: "active" });
|
||||
expect(opts.limit).toBe(20);
|
||||
expect(opts.skip).toBe(5);
|
||||
expect(opts.sort).toEqual({ createdAt: -1 });
|
||||
});
|
||||
|
||||
it("should extract data array from response object", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue({
|
||||
data: [{ _id: "1", name: "Test" }],
|
||||
});
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"list",
|
||||
"-o",
|
||||
"json",
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle response that is already an array", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue([{ _id: "1" }]);
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"list",
|
||||
"-o",
|
||||
"json",
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle API errors", async () => {
|
||||
mockExecuteApiRequest.mockRejectedValue(
|
||||
new Error("API error (500): Server Error"),
|
||||
);
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync(["node", "test", "incident", "list"]);
|
||||
|
||||
expect(process.exit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("get subcommand", () => {
|
||||
it("should call API with read operation and id", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue({
|
||||
_id: "abc-123",
|
||||
name: "Test",
|
||||
});
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"get",
|
||||
"abc-123",
|
||||
]);
|
||||
|
||||
expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1);
|
||||
const opts: Record<string, unknown> =
|
||||
mockExecuteApiRequest.mock.calls[0][0];
|
||||
expect(opts.operation).toBe("read");
|
||||
expect(opts.id).toBe("abc-123");
|
||||
});
|
||||
|
||||
it("should support output format flag", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue({ _id: "abc-123" });
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"get",
|
||||
"abc-123",
|
||||
"-o",
|
||||
"json",
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle get errors", async () => {
|
||||
mockExecuteApiRequest.mockRejectedValue(new Error("not found 404"));
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"get",
|
||||
"abc-123",
|
||||
]);
|
||||
|
||||
expect(process.exit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("create subcommand", () => {
|
||||
it("should call API with create operation and data", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue({ _id: "new-123" });
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"create",
|
||||
"--data",
|
||||
'{"name":"New Incident"}',
|
||||
]);
|
||||
|
||||
expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1);
|
||||
const opts: Record<string, unknown> =
|
||||
mockExecuteApiRequest.mock.calls[0][0];
|
||||
expect(opts.operation).toBe("create");
|
||||
expect(opts.data).toEqual({ name: "New Incident" });
|
||||
});
|
||||
|
||||
it("should support reading data from a file", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue({ _id: "new-123" });
|
||||
|
||||
const tmpFile: string = path.join(
|
||||
os.tmpdir(),
|
||||
"cli-test-" + Date.now() + ".json",
|
||||
);
|
||||
fs.writeFileSync(tmpFile, '{"name":"From File"}');
|
||||
|
||||
try {
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"create",
|
||||
"--file",
|
||||
tmpFile,
|
||||
]);
|
||||
|
||||
expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteApiRequest.mock.calls[0][0].data).toEqual({
|
||||
name: "From File",
|
||||
});
|
||||
} finally {
|
||||
fs.unlinkSync(tmpFile);
|
||||
}
|
||||
});
|
||||
|
||||
it("should error when neither --data nor --file is provided", async () => {
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync(["node", "test", "incident", "create"]);
|
||||
|
||||
expect(process.exit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should error on invalid JSON in --data", async () => {
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"create",
|
||||
"--data",
|
||||
"not-json",
|
||||
]);
|
||||
|
||||
expect(process.exit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("update subcommand", () => {
|
||||
it("should call API with update operation, id, and data", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue({ _id: "abc-123" });
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"update",
|
||||
"abc-123",
|
||||
"--data",
|
||||
'{"name":"Updated"}',
|
||||
]);
|
||||
|
||||
expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1);
|
||||
const opts: Record<string, unknown> =
|
||||
mockExecuteApiRequest.mock.calls[0][0];
|
||||
expect(opts.operation).toBe("update");
|
||||
expect(opts.id).toBe("abc-123");
|
||||
expect(opts.data).toEqual({ name: "Updated" });
|
||||
});
|
||||
|
||||
it("should handle update errors", async () => {
|
||||
mockExecuteApiRequest.mockRejectedValue(new Error("API error"));
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"update",
|
||||
"abc-123",
|
||||
"--data",
|
||||
'{"name":"x"}',
|
||||
]);
|
||||
|
||||
expect(process.exit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("delete subcommand", () => {
|
||||
it("should call API with delete operation and id", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue({});
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"delete",
|
||||
"abc-123",
|
||||
]);
|
||||
|
||||
expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1);
|
||||
const opts: Record<string, unknown> =
|
||||
mockExecuteApiRequest.mock.calls[0][0];
|
||||
expect(opts.operation).toBe("delete");
|
||||
expect(opts.id).toBe("abc-123");
|
||||
});
|
||||
|
||||
it("should handle API errors", async () => {
|
||||
mockExecuteApiRequest.mockRejectedValue(new Error("not found 404"));
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"delete",
|
||||
"abc-123",
|
||||
]);
|
||||
|
||||
expect(process.exit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("count subcommand", () => {
|
||||
it("should call API with count operation", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue({ count: 42 });
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync(["node", "test", "incident", "count"]);
|
||||
|
||||
expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteApiRequest.mock.calls[0][0].operation).toBe("count");
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.log).toHaveBeenCalledWith(42);
|
||||
});
|
||||
|
||||
it("should pass query filter", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue({ count: 5 });
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"incident",
|
||||
"count",
|
||||
"--query",
|
||||
'{"status":"active"}',
|
||||
]);
|
||||
|
||||
expect(mockExecuteApiRequest.mock.calls[0][0].query).toEqual({
|
||||
status: "active",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle response without count field", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue(99);
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync(["node", "test", "incident", "count"]);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.log).toHaveBeenCalledWith(99);
|
||||
});
|
||||
|
||||
it("should handle non-object response in count", async () => {
|
||||
mockExecuteApiRequest.mockResolvedValue("some-string");
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync(["node", "test", "incident", "count"]);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.log).toHaveBeenCalledWith("some-string");
|
||||
});
|
||||
|
||||
it("should handle count errors", async () => {
|
||||
mockExecuteApiRequest.mockRejectedValue(new Error("API error"));
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync(["node", "test", "incident", "count"]);
|
||||
|
||||
expect(process.exit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("credential resolution in commands", () => {
|
||||
it("should use global --api-key and --url flags", async () => {
|
||||
ConfigManager.removeContext("test");
|
||||
mockExecuteApiRequest.mockResolvedValue({ data: [] });
|
||||
|
||||
const program: Command = createProgramWithResources();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"--api-key",
|
||||
"global-key",
|
||||
"--url",
|
||||
"https://global.com",
|
||||
"incident",
|
||||
"list",
|
||||
]);
|
||||
|
||||
expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteApiRequest.mock.calls[0][0].apiKey).toBe(
|
||||
"global-key",
|
||||
);
|
||||
expect(mockExecuteApiRequest.mock.calls[0][0].apiUrl).toBe(
|
||||
"https://global.com",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
208
CLI/Tests/SelectFieldGenerator.test.ts
Normal file
208
CLI/Tests/SelectFieldGenerator.test.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import { generateAllFieldsSelect } from "../Utils/SelectFieldGenerator";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
describe("SelectFieldGenerator", () => {
|
||||
describe("generateAllFieldsSelect", () => {
|
||||
describe("database models", () => {
|
||||
it("should return fields for a known database model (Incident)", () => {
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"Incident",
|
||||
"database",
|
||||
);
|
||||
expect(Object.keys(select).length).toBeGreaterThan(0);
|
||||
// Should have some common fields
|
||||
expect(select).toHaveProperty("_id");
|
||||
});
|
||||
|
||||
it("should return fields for Monitor model", () => {
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"Monitor",
|
||||
"database",
|
||||
);
|
||||
expect(Object.keys(select).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should return default select for unknown database model", () => {
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"NonExistentModel12345",
|
||||
"database",
|
||||
);
|
||||
expect(select).toEqual({
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should filter fields based on access control", () => {
|
||||
// Testing with a real model that has access control
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"Incident",
|
||||
"database",
|
||||
);
|
||||
// We just verify it returns something reasonable
|
||||
expect(typeof select).toBe("object");
|
||||
expect(Object.keys(select).length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("analytics models", () => {
|
||||
it("should return default select for known analytics model (LogItem)", () => {
|
||||
// The Log analytics model has tableName "LogItem"
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"LogItem",
|
||||
"analytics",
|
||||
);
|
||||
expect(select).toEqual({
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return default select for unknown analytics model", () => {
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"NonExistentAnalytics",
|
||||
"analytics",
|
||||
);
|
||||
expect(select).toEqual({
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases", () => {
|
||||
it("should return default select for unknown model type", () => {
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"Incident",
|
||||
"unknown" as any,
|
||||
);
|
||||
expect(select).toEqual({
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return default select for empty tableName", () => {
|
||||
const select: JSONObject = generateAllFieldsSelect("", "database");
|
||||
expect(select).toEqual({
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle outer exception and return default select", () => {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
|
||||
const DatabaseModels: Record<string, unknown> =
|
||||
require("Common/Models/DatabaseModels/Index").default;
|
||||
/* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
|
||||
const origFind: unknown = DatabaseModels.find;
|
||||
try {
|
||||
DatabaseModels.find = (): never => {
|
||||
throw new Error("Simulated error");
|
||||
};
|
||||
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"Incident",
|
||||
"database",
|
||||
);
|
||||
expect(select).toEqual({
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
} finally {
|
||||
DatabaseModels.find = origFind;
|
||||
}
|
||||
});
|
||||
|
||||
it("should return default when getTableColumns returns empty", () => {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
|
||||
const tableColumnModule: Record<
|
||||
string,
|
||||
unknown
|
||||
> = require("Common/Types/Database/TableColumn");
|
||||
/* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
|
||||
const origGetTableColumns: unknown = tableColumnModule.getTableColumns;
|
||||
try {
|
||||
tableColumnModule.getTableColumns = (): Record<string, unknown> => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"Incident",
|
||||
"database",
|
||||
);
|
||||
expect(select).toEqual({
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
} finally {
|
||||
tableColumnModule.getTableColumns = origGetTableColumns;
|
||||
}
|
||||
});
|
||||
|
||||
it("should return default when all columns are filtered out", () => {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
|
||||
const tableColumnModule: Record<
|
||||
string,
|
||||
unknown
|
||||
> = require("Common/Types/Database/TableColumn");
|
||||
const origGetTableColumns: unknown = tableColumnModule.getTableColumns;
|
||||
const DatabaseModels: Record<string, unknown> =
|
||||
require("Common/Models/DatabaseModels/Index").default;
|
||||
const origFind: unknown = DatabaseModels.find;
|
||||
const Permission: Record<string, unknown> =
|
||||
require("Common/Types/Permission").default;
|
||||
/* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
|
||||
|
||||
try {
|
||||
tableColumnModule.getTableColumns = (): Record<
|
||||
string,
|
||||
Record<string, unknown>
|
||||
> => {
|
||||
return { field1: {}, field2: {} };
|
||||
};
|
||||
|
||||
DatabaseModels.find = (fn: (model: unknown) => boolean): unknown => {
|
||||
function MockModel(this: Record<string, unknown>): void {
|
||||
this.tableName = "MockTable";
|
||||
this.getColumnAccessControlForAllColumns = (): Record<
|
||||
string,
|
||||
unknown
|
||||
> => {
|
||||
return {
|
||||
field1: { read: [Permission.CurrentUser] },
|
||||
field2: { read: [Permission.CurrentUser] },
|
||||
};
|
||||
};
|
||||
}
|
||||
const matches: boolean = fn(MockModel);
|
||||
if (matches) {
|
||||
return MockModel;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const select: JSONObject = generateAllFieldsSelect(
|
||||
"MockTable",
|
||||
"database",
|
||||
);
|
||||
expect(select).toEqual({
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
} finally {
|
||||
DatabaseModels.find = origFind;
|
||||
tableColumnModule.getTableColumns = origGetTableColumns;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
194
CLI/Tests/UtilityCommands.test.ts
Normal file
194
CLI/Tests/UtilityCommands.test.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { Command } from "commander";
|
||||
import { registerUtilityCommands } from "../Commands/UtilityCommands";
|
||||
import * as ConfigManager from "../Core/ConfigManager";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
|
||||
const CONFIG_DIR: string = path.join(os.homedir(), ".oneuptime");
|
||||
const CONFIG_FILE: string = path.join(CONFIG_DIR, "config.json");
|
||||
|
||||
describe("UtilityCommands", () => {
|
||||
let originalConfigContent: string | null = null;
|
||||
let consoleLogSpy: jest.SpyInstance;
|
||||
let exitSpy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
originalConfigContent = fs.readFileSync(CONFIG_FILE, "utf-8");
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (originalConfigContent) {
|
||||
fs.writeFileSync(CONFIG_FILE, originalConfigContent, { mode: 0o600 });
|
||||
} else if (fs.existsSync(CONFIG_FILE)) {
|
||||
fs.unlinkSync(CONFIG_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
fs.unlinkSync(CONFIG_FILE);
|
||||
}
|
||||
consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => {});
|
||||
jest.spyOn(console, "error").mockImplementation(() => {});
|
||||
exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {}) as any);
|
||||
delete process.env["ONEUPTIME_API_KEY"];
|
||||
delete process.env["ONEUPTIME_URL"];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
delete process.env["ONEUPTIME_API_KEY"];
|
||||
delete process.env["ONEUPTIME_URL"];
|
||||
});
|
||||
|
||||
function createProgram(): Command {
|
||||
const program: Command = new Command();
|
||||
program.exitOverride();
|
||||
program.configureOutput({
|
||||
writeOut: () => {},
|
||||
writeErr: () => {},
|
||||
});
|
||||
program
|
||||
.option("--api-key <key>", "API key")
|
||||
.option("--url <url>", "URL")
|
||||
.option("--context <name>", "Context");
|
||||
registerUtilityCommands(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
describe("version command", () => {
|
||||
it("should print version", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "version"]);
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
// Should print a version string (either from package.json or fallback)
|
||||
const versionArg: string = consoleLogSpy.mock.calls[0][0];
|
||||
expect(typeof versionArg).toBe("string");
|
||||
});
|
||||
});
|
||||
|
||||
describe("whoami command", () => {
|
||||
it("should show not authenticated when no credentials", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "whoami"]);
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show credentials from current context", async () => {
|
||||
ConfigManager.addContext({
|
||||
name: "test",
|
||||
apiUrl: "https://test.com",
|
||||
apiKey: "abcdefghijklm",
|
||||
});
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "whoami"]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("URL: https://test.com");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("****"),
|
||||
);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("Context: test");
|
||||
});
|
||||
|
||||
it("should mask short API keys", async () => {
|
||||
ConfigManager.addContext({
|
||||
name: "short",
|
||||
apiUrl: "https://s.com",
|
||||
apiKey: "abc",
|
||||
});
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "whoami"]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("API Key: ****");
|
||||
});
|
||||
|
||||
it("should show credentials from env vars", async () => {
|
||||
process.env["ONEUPTIME_API_KEY"] = "env-key-long-enough";
|
||||
process.env["ONEUPTIME_URL"] = "https://env.com";
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "whoami"]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("URL: https://env.com");
|
||||
});
|
||||
|
||||
it("should handle whoami outer catch block", async () => {
|
||||
// Mock getCurrentContext to throw an unexpected error
|
||||
const spy: jest.SpyInstance = jest
|
||||
.spyOn(ConfigManager, "getCurrentContext")
|
||||
.mockImplementation(() => {
|
||||
throw new Error("Unexpected crash");
|
||||
});
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "whoami"]);
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should not show context line when no context exists", async () => {
|
||||
process.env["ONEUPTIME_API_KEY"] = "env-key-long-enough";
|
||||
process.env["ONEUPTIME_URL"] = "https://env.com";
|
||||
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "whoami"]);
|
||||
|
||||
// Should NOT have a "Context:" call since no context is set
|
||||
const contextCalls: any[][] = consoleLogSpy.mock.calls.filter(
|
||||
(call: any[]) => {
|
||||
return typeof call[0] === "string" && call[0].startsWith("Context:");
|
||||
},
|
||||
);
|
||||
expect(contextCalls).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resources command", () => {
|
||||
it("should list all resources", async () => {
|
||||
/*
|
||||
* We need registerResourceCommands for discoverResources to work
|
||||
* but discoverResources is imported directly, so it should work
|
||||
*/
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync(["node", "test", "resources"]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
// Should show total count
|
||||
const lastCall: string =
|
||||
consoleLogSpy.mock.calls[consoleLogSpy.mock.calls.length - 1][0];
|
||||
expect(lastCall).toContain("Total:");
|
||||
});
|
||||
|
||||
it("should filter by type", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"resources",
|
||||
"--type",
|
||||
"database",
|
||||
]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show message when filter returns no results", async () => {
|
||||
const program: Command = createProgram();
|
||||
await program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"resources",
|
||||
"--type",
|
||||
"nonexistent",
|
||||
]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
34
CLI/Types/CLITypes.ts
Normal file
34
CLI/Types/CLITypes.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export interface CLIContext {
|
||||
name: string;
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
export interface CLIConfig {
|
||||
currentContext: string;
|
||||
contexts: Record<string, CLIContext>;
|
||||
defaults: {
|
||||
output: string;
|
||||
limit: number;
|
||||
};
|
||||
}
|
||||
|
||||
export enum OutputFormat {
|
||||
JSON = "json",
|
||||
Table = "table",
|
||||
Wide = "wide",
|
||||
}
|
||||
|
||||
export interface ResourceInfo {
|
||||
name: string;
|
||||
singularName: string;
|
||||
pluralName: string;
|
||||
apiPath: string;
|
||||
tableName: string;
|
||||
modelType: "database" | "analytics";
|
||||
}
|
||||
|
||||
export interface ResolvedCredentials {
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
}
|
||||
116
CLI/Utils/SelectFieldGenerator.ts
Normal file
116
CLI/Utils/SelectFieldGenerator.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import DatabaseModels from "Common/Models/DatabaseModels/Index";
|
||||
import AnalyticsModels from "Common/Models/AnalyticsModels/Index";
|
||||
import BaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
||||
import AnalyticsBaseModel from "Common/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
|
||||
import { getTableColumns } from "Common/Types/Database/TableColumn";
|
||||
import Permission from "Common/Types/Permission";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
interface ColumnAccessControl {
|
||||
read?: Permission[];
|
||||
}
|
||||
|
||||
function shouldIncludeField(
|
||||
columnName: string,
|
||||
accessControlForColumns: Record<string, ColumnAccessControl>,
|
||||
): boolean {
|
||||
const accessControl: ColumnAccessControl | undefined =
|
||||
accessControlForColumns[columnName];
|
||||
|
||||
return (
|
||||
!accessControl ||
|
||||
(accessControl.read !== undefined &&
|
||||
accessControl.read.length > 0 &&
|
||||
!(
|
||||
accessControl.read.length === 1 &&
|
||||
accessControl.read[0] === Permission.CurrentUser
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
export function generateAllFieldsSelect(
|
||||
tableName: string,
|
||||
modelType: "database" | "analytics",
|
||||
): JSONObject {
|
||||
try {
|
||||
if (modelType === "database") {
|
||||
const ModelClass: (new () => BaseModel) | undefined = DatabaseModels.find(
|
||||
(Model: new () => BaseModel): boolean => {
|
||||
try {
|
||||
const instance: BaseModel = new Model();
|
||||
return instance.tableName === tableName;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (!ModelClass) {
|
||||
return getDefaultSelect();
|
||||
}
|
||||
|
||||
const modelInstance: BaseModel = new ModelClass();
|
||||
const tableColumns: Record<string, unknown> =
|
||||
getTableColumns(modelInstance);
|
||||
const columnNames: string[] = Object.keys(tableColumns);
|
||||
|
||||
if (columnNames.length === 0) {
|
||||
return getDefaultSelect();
|
||||
}
|
||||
|
||||
const accessControlForColumns: Record<string, ColumnAccessControl> =
|
||||
(
|
||||
modelInstance as unknown as {
|
||||
getColumnAccessControlForAllColumns?: () => Record<
|
||||
string,
|
||||
ColumnAccessControl
|
||||
>;
|
||||
}
|
||||
).getColumnAccessControlForAllColumns?.() || {};
|
||||
|
||||
const selectObject: JSONObject = {};
|
||||
for (const columnName of columnNames) {
|
||||
if (shouldIncludeField(columnName, accessControlForColumns)) {
|
||||
selectObject[columnName] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(selectObject).length === 0) {
|
||||
return getDefaultSelect();
|
||||
}
|
||||
|
||||
return selectObject;
|
||||
}
|
||||
|
||||
if (modelType === "analytics") {
|
||||
const ModelClass: (new () => AnalyticsBaseModel) | undefined =
|
||||
AnalyticsModels.find((Model: new () => AnalyticsBaseModel): boolean => {
|
||||
try {
|
||||
const instance: AnalyticsBaseModel = new Model();
|
||||
return instance.tableName === tableName;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!ModelClass) {
|
||||
return getDefaultSelect();
|
||||
}
|
||||
|
||||
// For analytics models, just return a basic select
|
||||
return getDefaultSelect();
|
||||
}
|
||||
|
||||
return getDefaultSelect();
|
||||
} catch {
|
||||
return getDefaultSelect();
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultSelect(): JSONObject {
|
||||
return {
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
};
|
||||
}
|
||||
35
CLI/jest.config.json
Normal file
35
CLI/jest.config.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"preset": "ts-jest",
|
||||
"testEnvironment": "node",
|
||||
"testMatch": ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"],
|
||||
"collectCoverageFrom": [
|
||||
"**/*.ts",
|
||||
"!**/*.d.ts",
|
||||
"!**/node_modules/**",
|
||||
"!**/build/**",
|
||||
"!**/Tests/**",
|
||||
"!Index.ts"
|
||||
],
|
||||
"setupFilesAfterEnv": [],
|
||||
"testTimeout": 30000,
|
||||
"modulePathIgnorePatterns": ["<rootDir>/build/"],
|
||||
"moduleNameMapper": {
|
||||
"^Common/(.*)$": "<rootDir>/../Common/$1"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(@oneuptime)/)"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.ts$": ["ts-jest", {
|
||||
"tsconfig": {
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"strict": false,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitThis": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"module": "commonjs"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
17176
CLI/package-lock.json
generated
Normal file
17176
CLI/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
CLI/package.json
Normal file
41
CLI/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@oneuptime/cli",
|
||||
"version": "1.0.0",
|
||||
"description": "OneUptime CLI - Command-line interface for managing OneUptime resources",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime"
|
||||
},
|
||||
"main": "Index.ts",
|
||||
"bin": {
|
||||
"oneuptime": "./Index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node --require ts-node/register Index.ts",
|
||||
"build": "npm run compile",
|
||||
"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 --passWithNoTests",
|
||||
"link": "npm link"
|
||||
},
|
||||
"author": "OneUptime <hello@oneuptime.com> (https://oneuptime.com/)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"Common": "npm:@oneuptime/common@latest",
|
||||
"commander": "^12.1.0",
|
||||
"chalk": "^4.1.2",
|
||||
"cli-table3": "^0.6.5",
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.15.21",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.1.11",
|
||||
"ts-jest": "^29.4.6",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
44
CLI/tsconfig.json
Normal file
44
CLI/tsconfig.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"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
|
||||
},
|
||||
"exclude": ["Tests", "build", "node_modules", "jest.config.json"]
|
||||
}
|
||||
@@ -77,6 +77,7 @@ export enum AIAgentConnectionStatus {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgent,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -99,7 +100,11 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectAIAgent,
|
||||
],
|
||||
read: [Permission.ProjectOwner, Permission.ProjectAdmin],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
@@ -126,7 +131,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectAIAgent,
|
||||
],
|
||||
read: [Permission.Public],
|
||||
read: [Permission.Public, Permission.ReadAllProjectResources],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
@@ -154,7 +159,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectAIAgent,
|
||||
],
|
||||
read: [Permission.Public],
|
||||
read: [Permission.Public, Permission.ReadAllProjectResources],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
@@ -176,7 +181,7 @@ export default class AIAgent extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.Public],
|
||||
read: [Permission.Public, Permission.ReadAllProjectResources],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
@@ -202,7 +207,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectAIAgent,
|
||||
],
|
||||
read: [Permission.Public],
|
||||
read: [Permission.Public, Permission.ReadAllProjectResources],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
@@ -231,6 +236,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgent,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -258,6 +264,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgent,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -299,6 +306,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgent,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -328,7 +336,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectAIAgent,
|
||||
],
|
||||
read: [Permission.Public],
|
||||
read: [Permission.Public, Permission.ReadAllProjectResources],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
@@ -358,7 +366,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectAIAgent,
|
||||
],
|
||||
read: [Permission.Public],
|
||||
read: [Permission.Public, Permission.ReadAllProjectResources],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
@@ -421,7 +429,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectAIAgent,
|
||||
],
|
||||
read: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner, Permission.ReadAllProjectResources],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.Entity, modelType: User })
|
||||
@@ -446,7 +454,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectAIAgent,
|
||||
],
|
||||
read: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner, Permission.ReadAllProjectResources],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
@@ -490,6 +498,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgent,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -515,7 +524,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectMember,
|
||||
Permission.CreateProjectAIAgent,
|
||||
],
|
||||
read: [Permission.Public],
|
||||
read: [Permission.Public, Permission.ReadAllProjectResources],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
@@ -552,6 +561,7 @@ export default class AIAgent extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgent,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
|
||||
@@ -41,6 +41,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -85,6 +86,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -122,6 +124,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -153,6 +156,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -191,6 +195,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -222,6 +227,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -259,6 +265,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -290,6 +297,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -328,6 +336,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -352,6 +361,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -386,6 +396,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -410,6 +421,7 @@ export default class AIAgentOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -40,6 +40,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -84,6 +85,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -121,6 +123,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -152,6 +155,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -190,6 +194,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -221,6 +226,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -258,6 +264,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -289,6 +296,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -327,6 +335,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -351,6 +360,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -385,6 +395,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -409,6 +420,7 @@ export default class AIAgentOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAIAgentOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -55,6 +55,7 @@ import { AIAgentTaskMetadata } from "../../Types/AI/AIAgentTaskMetadata";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -82,6 +83,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -120,6 +122,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -150,6 +153,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -183,6 +187,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -216,6 +221,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -258,6 +264,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -293,6 +300,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -323,6 +331,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -354,6 +363,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -387,6 +397,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -415,6 +426,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -438,6 +450,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -495,7 +508,11 @@ export default class AIAgentTask extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.ProjectAdmin],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.Entity, modelType: User })
|
||||
@@ -515,7 +532,11 @@ export default class AIAgentTask extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.ProjectAdmin],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
@@ -543,6 +564,7 @@ export default class AIAgentTask extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -53,6 +53,7 @@ import LogSeverity from "../../Types/Log/LogSeverity";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -80,6 +81,7 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -118,6 +120,7 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -148,6 +151,7 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -186,6 +190,7 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -216,6 +221,7 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -254,6 +260,7 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -284,6 +291,7 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -318,6 +326,7 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -379,7 +388,11 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.ProjectAdmin],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.Entity, modelType: User })
|
||||
@@ -399,7 +412,11 @@ export default class AIAgentTaskLog extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.ProjectAdmin],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
|
||||
@@ -55,6 +55,7 @@ import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -82,6 +83,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -120,6 +122,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -150,6 +153,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -188,6 +192,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -218,6 +223,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -256,6 +262,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -286,6 +293,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -324,6 +332,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -354,6 +363,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -388,6 +398,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -420,6 +431,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -454,6 +466,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -483,6 +496,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -512,6 +526,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -548,6 +563,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -577,6 +593,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -606,6 +623,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -635,6 +653,7 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTask,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -693,7 +712,11 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.ProjectAdmin],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.Entity, modelType: User })
|
||||
@@ -713,7 +736,11 @@ export default class AIAgentTaskPullRequest extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.ProjectAdmin],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
|
||||
@@ -40,6 +40,7 @@ import TelemetryException from "./TelemetryException";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -79,6 +80,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -115,6 +117,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -145,6 +148,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -181,6 +185,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -211,6 +216,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -247,6 +253,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -277,6 +284,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -314,6 +322,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -337,6 +346,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -370,6 +380,7 @@ export default class AIAgentTaskTelemetryException extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectAIAgentTaskTelemetryException,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -57,6 +57,7 @@ import NotificationRuleWorkspaceChannel from "../../Types/Workspace/Notification
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -106,6 +107,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -142,6 +144,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -173,6 +176,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -209,6 +213,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -244,6 +249,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -281,6 +287,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -357,6 +364,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -399,6 +407,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -409,9 +418,8 @@ export default class Alert extends BaseModel {
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
title: "Deleted by User ID",
|
||||
description:
|
||||
"User ID who deleted this object (if this object was deleted by a User)",
|
||||
title: "Monitor ID",
|
||||
description: "ID of the monitor this alert belongs to",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
@@ -432,6 +440,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -443,9 +452,9 @@ export default class Alert extends BaseModel {
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.EntityArray,
|
||||
modelType: Monitor,
|
||||
modelType: OnCallDutyPolicy,
|
||||
title: "On-Call Duty Policies",
|
||||
description: "List of on-call duty policy affected by this alert.",
|
||||
description: "List of on-call duty policies affected by this alert.",
|
||||
})
|
||||
@ManyToMany(
|
||||
() => {
|
||||
@@ -456,15 +465,15 @@ export default class Alert extends BaseModel {
|
||||
@JoinTable({
|
||||
name: "AlertOnCallDutyPolicy",
|
||||
inverseJoinColumn: {
|
||||
name: "monitorId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
joinColumn: {
|
||||
name: "onCallDutyPolicyId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
joinColumn: {
|
||||
name: "alertId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
})
|
||||
public onCallDutyPolicies?: Array<OnCallDutyPolicy> = undefined; // monitors affected by this alert.
|
||||
public onCallDutyPolicies?: Array<OnCallDutyPolicy> = undefined; // on-call duty policies affected by this alert.
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
@@ -478,6 +487,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -525,6 +535,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -566,6 +577,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -579,13 +591,13 @@ export default class Alert extends BaseModel {
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
isDefaultValueColumn: true,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Current Alert State ID",
|
||||
description: "Current Alert State ID",
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
@@ -603,6 +615,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -644,6 +657,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -679,13 +693,14 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "monitorStatusWhenThisAlertWasCreatedId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: AlertState,
|
||||
modelType: MonitorStatus,
|
||||
title: "Monitor status when this alert was created",
|
||||
description: "Monitor status when this alert was created",
|
||||
})
|
||||
@@ -714,6 +729,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -748,6 +764,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -776,6 +793,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -809,6 +827,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -834,6 +853,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -841,6 +861,7 @@ export default class Alert extends BaseModel {
|
||||
isDefaultValueColumn: false,
|
||||
required: false,
|
||||
type: TableColumnType.JSON,
|
||||
computed: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.JSON,
|
||||
@@ -856,6 +877,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -881,6 +903,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -913,6 +936,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -939,6 +963,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -968,6 +993,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1002,6 +1028,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1035,6 +1062,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -1055,6 +1083,33 @@ export default class Alert extends BaseModel {
|
||||
})
|
||||
public alertNumber?: number = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: false,
|
||||
required: false,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Alert Number With Prefix",
|
||||
description: "Alert number with prefix (e.g., 'ALT-42' or '#42')",
|
||||
computed: true,
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: true,
|
||||
})
|
||||
public alertNumberWithPrefix?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
@@ -1086,6 +1141,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1127,6 +1183,7 @@ export default class Alert extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlert,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
|
||||
@@ -40,6 +40,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -75,6 +76,7 @@ export default class AlertCustomField extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -110,6 +112,7 @@ export default class AlertCustomField extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -140,6 +143,7 @@ export default class AlertCustomField extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -174,6 +178,7 @@ export default class AlertCustomField extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -208,6 +213,7 @@ export default class AlertCustomField extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -236,6 +242,7 @@ export default class AlertCustomField extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -272,6 +279,7 @@ export default class AlertCustomField extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -296,6 +304,7 @@ export default class AlertCustomField extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -329,6 +338,7 @@ export default class AlertCustomField extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertCustomField,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -53,6 +53,7 @@ import NotificationRuleWorkspaceChannel from "../../Types/Workspace/Notification
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -103,6 +104,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -139,6 +141,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -169,6 +172,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -204,6 +208,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -237,6 +242,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -254,6 +260,32 @@ export default class AlertEpisode extends BaseModel {
|
||||
})
|
||||
public episodeNumber?: number = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
isDefaultValueColumn: false,
|
||||
required: false,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Episode Number With Prefix",
|
||||
description: "Episode number with prefix (e.g., 'AE-42' or '#42')",
|
||||
computed: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: true,
|
||||
})
|
||||
public episodeNumberWithPrefix?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -266,6 +298,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -307,6 +340,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -342,6 +376,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -383,6 +418,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -417,6 +453,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -450,6 +487,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -483,6 +521,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -504,6 +543,31 @@ export default class AlertEpisode extends BaseModel {
|
||||
})
|
||||
public resolvedAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.Date,
|
||||
title: "All Alerts Resolved At",
|
||||
description:
|
||||
"When all alerts in this episode were first detected as resolved. Used for resolve delay calculation.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Date,
|
||||
nullable: true,
|
||||
unique: false,
|
||||
})
|
||||
public allAlertsResolvedAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -516,6 +580,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -557,6 +622,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -591,6 +657,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -632,6 +699,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -666,6 +734,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -708,6 +777,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -742,6 +812,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -783,6 +854,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -810,6 +882,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -818,6 +891,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
type: TableColumnType.Number,
|
||||
required: true,
|
||||
isDefaultValueColumn: true,
|
||||
computed: true,
|
||||
title: "Alert Count",
|
||||
description: "Denormalized count of alerts in this episode",
|
||||
defaultValue: 0,
|
||||
@@ -841,6 +915,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -869,6 +944,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -897,6 +973,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -929,6 +1006,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -976,6 +1054,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -1013,6 +1092,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -1082,6 +1162,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -1113,6 +1194,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -1143,6 +1225,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1176,6 +1259,7 @@ export default class AlertEpisode extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
|
||||
@@ -55,6 +55,7 @@ export enum AlertEpisodeFeedEventType {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [],
|
||||
update: [],
|
||||
@@ -90,6 +91,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -126,6 +128,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -156,6 +159,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -192,6 +196,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -221,6 +226,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -258,6 +264,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -332,6 +339,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -360,6 +368,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -388,6 +397,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -416,6 +426,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -447,6 +458,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -484,6 +496,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -512,6 +525,7 @@ export default class AlertEpisodeFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -87,6 +88,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -123,6 +125,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -153,6 +156,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -189,6 +193,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -218,6 +223,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -255,6 +261,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -329,6 +336,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -361,6 +369,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -404,6 +413,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -435,6 +445,7 @@ export default class AlertEpisodeInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -41,6 +41,7 @@ export enum AlertEpisodeMemberAddedBy {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -86,6 +87,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -122,6 +124,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -152,6 +155,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -188,6 +192,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -218,6 +223,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -254,6 +260,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -284,6 +291,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -312,6 +320,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -344,6 +353,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -381,6 +391,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -410,6 +421,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -446,6 +458,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -475,6 +488,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -512,6 +526,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -535,6 +550,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -568,6 +584,7 @@ export default class AlertEpisodeMember extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeMember,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -33,6 +33,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -78,6 +79,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -114,6 +116,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -144,6 +147,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -181,6 +185,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -211,6 +216,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -248,6 +254,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -279,6 +286,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -316,6 +324,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -339,6 +348,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -372,6 +382,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -400,6 +411,7 @@ export default class AlertEpisodeOwnerTeam extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerTeam,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -32,6 +32,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -77,6 +78,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -113,6 +115,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -143,6 +146,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -179,6 +183,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -209,6 +214,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -246,6 +252,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -277,6 +284,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -314,6 +322,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -337,6 +346,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -370,6 +380,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -398,6 +409,7 @@ export default class AlertEpisodeOwnerUser extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeOwnerUser,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -36,6 +36,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -82,6 +83,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -118,6 +120,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -148,6 +151,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -184,6 +188,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -213,6 +218,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -250,6 +256,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -324,6 +331,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -365,6 +373,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -395,6 +404,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -421,6 +431,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -428,6 +439,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
isDefaultValueColumn: false,
|
||||
required: false,
|
||||
type: TableColumnType.JSON,
|
||||
computed: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.JSON,
|
||||
@@ -448,6 +460,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -478,6 +491,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -506,6 +520,7 @@ export default class AlertEpisodeStateTimeline extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisodeStateTimeline,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -57,6 +57,7 @@ export enum AlertFeedEventType {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [],
|
||||
update: [],
|
||||
@@ -92,6 +93,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -128,6 +130,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -159,6 +162,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -195,6 +199,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -225,6 +230,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -262,6 +268,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -337,6 +344,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -367,6 +375,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -395,6 +404,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -424,6 +434,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -456,6 +467,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -493,6 +505,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -522,6 +535,7 @@ export default class AlertFeed extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertFeed,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface AlertGroupingRuleGroupByFields {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -104,6 +105,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -139,6 +141,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -168,6 +171,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -201,6 +205,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -232,6 +237,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -267,6 +273,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -301,6 +308,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -334,6 +342,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -379,6 +388,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -424,6 +434,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -469,6 +480,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -514,6 +526,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -546,6 +559,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -578,6 +592,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -610,6 +625,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -644,6 +660,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -657,13 +674,13 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
title: "Group By Monitor",
|
||||
description:
|
||||
"When enabled, alerts from different monitors will be grouped into separate episodes. When disabled, alerts from any monitor can be grouped together.",
|
||||
defaultValue: true,
|
||||
defaultValue: false,
|
||||
isDefaultValueColumn: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Boolean,
|
||||
nullable: false,
|
||||
default: true,
|
||||
default: false,
|
||||
})
|
||||
public groupByMonitor?: boolean = undefined;
|
||||
|
||||
@@ -678,6 +695,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -712,6 +730,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -746,6 +765,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -780,6 +800,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -814,6 +835,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -848,6 +870,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -879,6 +902,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -910,6 +934,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -941,6 +966,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -975,6 +1001,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1009,6 +1036,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1043,6 +1071,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1077,6 +1106,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1111,6 +1141,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1145,6 +1176,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1190,6 +1222,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1229,6 +1262,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1261,6 +1295,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1300,6 +1335,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1321,6 +1357,8 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
})
|
||||
public defaultAssignToTeamId?: ObjectID = undefined;
|
||||
|
||||
// Episode Configuration Fields
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -1332,6 +1370,145 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.EditAlertGroupingRule,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.EntityArray,
|
||||
modelType: Label,
|
||||
title: "Episode Labels",
|
||||
description:
|
||||
"Labels to automatically apply to episodes created by this rule.",
|
||||
})
|
||||
@ManyToMany(
|
||||
() => {
|
||||
return Label;
|
||||
},
|
||||
{ eager: false },
|
||||
)
|
||||
@JoinTable({
|
||||
name: "AlertGroupingRuleEpisodeLabel",
|
||||
inverseJoinColumn: {
|
||||
name: "labelId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
joinColumn: {
|
||||
name: "alertGroupingRuleId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
})
|
||||
public episodeLabels?: Array<Label> = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.CreateAlertGroupingRule,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.EditAlertGroupingRule,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.EntityArray,
|
||||
modelType: User,
|
||||
title: "Episode Owner Users",
|
||||
description:
|
||||
"Users to automatically add as owners to episodes created by this rule.",
|
||||
})
|
||||
@ManyToMany(
|
||||
() => {
|
||||
return User;
|
||||
},
|
||||
{ eager: false },
|
||||
)
|
||||
@JoinTable({
|
||||
name: "AlertGroupingRuleEpisodeOwnerUser",
|
||||
inverseJoinColumn: {
|
||||
name: "userId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
joinColumn: {
|
||||
name: "alertGroupingRuleId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
})
|
||||
public episodeOwnerUsers?: Array<User> = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.CreateAlertGroupingRule,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.EditAlertGroupingRule,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.EntityArray,
|
||||
modelType: Team,
|
||||
title: "Episode Owner Teams",
|
||||
description:
|
||||
"Teams to automatically add as owners to episodes created by this rule.",
|
||||
})
|
||||
@ManyToMany(
|
||||
() => {
|
||||
return Team;
|
||||
},
|
||||
{ eager: false },
|
||||
)
|
||||
@JoinTable({
|
||||
name: "AlertGroupingRuleEpisodeOwnerTeam",
|
||||
inverseJoinColumn: {
|
||||
name: "teamId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
joinColumn: {
|
||||
name: "alertGroupingRuleId",
|
||||
referencedColumnName: "_id",
|
||||
},
|
||||
})
|
||||
public episodeOwnerTeams?: Array<Team> = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.CreateAlertGroupingRule,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -1368,6 +1545,7 @@ export default class AlertGroupingRule extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertGroupingRule,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -87,6 +88,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -123,6 +125,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -154,6 +157,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -190,6 +194,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -220,6 +225,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -257,6 +263,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -333,6 +340,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -367,6 +375,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -410,6 +419,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -443,6 +453,7 @@ export default class AlertInternalNote extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertInternalNote,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
@@ -40,6 +40,7 @@ import { PlanType } from "../../Types/Billing/SubscriptionPlan";
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertNoteTemplate,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
delete: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -84,6 +85,7 @@ export default class AlertNoteTemplate extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertNoteTemplate,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -121,6 +123,7 @@ export default class AlertNoteTemplate extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertNoteTemplate,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -152,6 +155,7 @@ export default class AlertNoteTemplate extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertNoteTemplate,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -188,6 +192,7 @@ export default class AlertNoteTemplate extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertNoteTemplate,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -223,6 +228,7 @@ export default class AlertNoteTemplate extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertNoteTemplate,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
@@ -259,6 +265,7 @@ export default class AlertNoteTemplate extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertNoteTemplate,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@@ -297,6 +304,7 @@ export default class AlertNoteTemplate extends BaseModel {
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertNoteTemplate,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user