mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
400 Commits
7.0.1985
...
filter-ref
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aea1ec24ef | ||
|
|
7c250a25e3 | ||
|
|
e7799adc1c | ||
|
|
2c949150ef | ||
|
|
983892d8b3 | ||
|
|
054234116b | ||
|
|
d6aafa8493 | ||
|
|
b5407b47b2 | ||
|
|
f84bec7e66 | ||
|
|
43354d850b | ||
|
|
1977dc3e62 | ||
|
|
c2bd27b0f9 | ||
|
|
264c0f0347 | ||
|
|
0de98ae262 | ||
|
|
573a12075b | ||
|
|
0a85e245d7 | ||
|
|
41d01cceee | ||
|
|
dcd9aeccb3 | ||
|
|
c59ebc2373 | ||
|
|
643f4acddd | ||
|
|
b3fb21af59 | ||
|
|
2164f45a68 | ||
|
|
8a4afe992c | ||
|
|
f35e50bab3 | ||
|
|
63064c587c | ||
|
|
edd11ffade | ||
|
|
b7e97a29ee | ||
|
|
fdf6e3dfa3 | ||
|
|
b59b8257cc | ||
|
|
9bde353e93 | ||
|
|
a2c2867469 | ||
|
|
bd3ba94da8 | ||
|
|
8e6800c17b | ||
|
|
e481e043dd | ||
|
|
a4e6552c91 | ||
|
|
6ce5cde46e | ||
|
|
22014c81c4 | ||
|
|
bff63cdf58 | ||
|
|
5f9d57a099 | ||
|
|
ca100f9de9 | ||
|
|
68dbb010aa | ||
|
|
5cd213a750 | ||
|
|
26683914bc | ||
|
|
8a5adfd589 | ||
|
|
d2eefeabba | ||
|
|
1148b59416 | ||
|
|
848c0c8100 | ||
|
|
21f40961cf | ||
|
|
2f74fbe0a8 | ||
|
|
571784a523 | ||
|
|
7c60fe8009 | ||
|
|
1a2fe1d16d | ||
|
|
a28a041c8d | ||
|
|
cfcce00060 | ||
|
|
da0fa045e6 | ||
|
|
859f62bf93 | ||
|
|
6a1cc1bd8b | ||
|
|
5b9abe8aea | ||
|
|
d0ec483a0f | ||
|
|
c03a3d6f56 | ||
|
|
8761dbd75b | ||
|
|
5541922b25 | ||
|
|
8693ab065e | ||
|
|
fe4daa7937 | ||
|
|
20d7f2f8b4 | ||
|
|
7f94f3d4d4 | ||
|
|
bc46370f7c | ||
|
|
b3346a9702 | ||
|
|
f793f7dd16 | ||
|
|
766f1f6178 | ||
|
|
9f76748037 | ||
|
|
469e06280a | ||
|
|
aede9af03d | ||
|
|
0bc7cf345f | ||
|
|
95c7b10ce0 | ||
|
|
daceab164c | ||
|
|
2d28fbaf85 | ||
|
|
8d2cbe49ad | ||
|
|
86f262583c | ||
|
|
4e748d1626 | ||
|
|
1dbea8e636 | ||
|
|
3991acf5fc | ||
|
|
1285830a9b | ||
|
|
96d3243205 | ||
|
|
08cbdb15d0 | ||
|
|
f59284e4d6 | ||
|
|
0c5d0e81a5 | ||
|
|
82829a5b97 | ||
|
|
d3c188a804 | ||
|
|
85ffc36fad | ||
|
|
8d34e9ccbe | ||
|
|
c6643d7f7c | ||
|
|
1433823efe | ||
|
|
bd5902ed6d | ||
|
|
b64666f0ff | ||
|
|
9706cced86 | ||
|
|
49cb3024ef | ||
|
|
2fec007c5f | ||
|
|
0f40f5f730 | ||
|
|
120ca78760 | ||
|
|
d50fdf3464 | ||
|
|
34ddb8efdb | ||
|
|
3724cce4f0 | ||
|
|
d864992bdb | ||
|
|
3515752a95 | ||
|
|
53d66db8cb | ||
|
|
548250f837 | ||
|
|
b3b343d4aa | ||
|
|
69a8f438f7 | ||
|
|
7b571f15a9 | ||
|
|
d783eaea9d | ||
|
|
2e5aafe151 | ||
|
|
dea938bcbb | ||
|
|
7c16b14ab4 | ||
|
|
e22ff26dc0 | ||
|
|
75bd6ca6ed | ||
|
|
f0bdb76999 | ||
|
|
a9e9d14f69 | ||
|
|
21daac0400 | ||
|
|
3cdf71906b | ||
|
|
0714161460 | ||
|
|
78b7252743 | ||
|
|
2ba406f802 | ||
|
|
48ebfc17dd | ||
|
|
9d180a2dcb | ||
|
|
0f2a970ede | ||
|
|
4ab3c99fe4 | ||
|
|
ae2e452d27 | ||
|
|
825d733f92 | ||
|
|
27039bc646 | ||
|
|
1bb1d80b09 | ||
|
|
983f587ce4 | ||
|
|
e1f9325512 | ||
|
|
96c27c86ca | ||
|
|
a172596e46 | ||
|
|
3d30e9356a | ||
|
|
7c04ecec35 | ||
|
|
80511a5f8d | ||
|
|
6692fb00b8 | ||
|
|
83e207a7de | ||
|
|
d62aa7f5a6 | ||
|
|
8a130f96e2 | ||
|
|
66a470ebe1 | ||
|
|
30a59aa21e | ||
|
|
7a6a5c141b | ||
|
|
c4bf458074 | ||
|
|
ae873e296c | ||
|
|
4a973d7107 | ||
|
|
9bbf460b97 | ||
|
|
c348a8bd05 | ||
|
|
76d8f5d5ef | ||
|
|
72ac305aad | ||
|
|
1ac05450e7 | ||
|
|
67165375ac | ||
|
|
6325e36bd0 | ||
|
|
e6375087cd | ||
|
|
737e234ec0 | ||
|
|
1427d52c9f | ||
|
|
8377285a27 | ||
|
|
7414c6563d | ||
|
|
0669e96a5d | ||
|
|
54a6101315 | ||
|
|
eaaef7280f | ||
|
|
b368633ff9 | ||
|
|
37b25c3a8f | ||
|
|
191d0154b4 | ||
|
|
1f786699d2 | ||
|
|
00b5453b3f | ||
|
|
077ca1037b | ||
|
|
1be827741e | ||
|
|
d51e818d10 | ||
|
|
d312c2b0cf | ||
|
|
96a09353fb | ||
|
|
fe40c5bb8d | ||
|
|
7cbbce4067 | ||
|
|
2870d28354 | ||
|
|
899bc1d205 | ||
|
|
308dba7f24 | ||
|
|
a0e565485b | ||
|
|
35c21d7611 | ||
|
|
a0ef85b8cd | ||
|
|
818f8cc421 | ||
|
|
eaeb40ec49 | ||
|
|
74273e6e9b | ||
|
|
c092adfa3d | ||
|
|
9eec9042bd | ||
|
|
496c2ca32c | ||
|
|
499ab07cd3 | ||
|
|
c634526215 | ||
|
|
cda9921f5d | ||
|
|
d3ca5a97ea | ||
|
|
f22ae5844d | ||
|
|
a71190493a | ||
|
|
caf3377cee | ||
|
|
f0e4d6bf71 | ||
|
|
f34e4a23d6 | ||
|
|
e03ecb0e0d | ||
|
|
0a8574678c | ||
|
|
0fdf63ce0c | ||
|
|
d1586f75da | ||
|
|
f47b53c5b9 | ||
|
|
7523c3ad4c | ||
|
|
11d580a373 | ||
|
|
9a78c4c24d | ||
|
|
ec936b43e2 | ||
|
|
3bc2e639d9 | ||
|
|
fa672be557 | ||
|
|
695f16ca1e | ||
|
|
003b638170 | ||
|
|
1cf208387f | ||
|
|
625cb49939 | ||
|
|
7d44eb1a33 | ||
|
|
ab42ca1ef9 | ||
|
|
910aa113b5 | ||
|
|
1516f81180 | ||
|
|
b9f86d029a | ||
|
|
ecfc47ee59 | ||
|
|
5d9a11fb48 | ||
|
|
894d15918b | ||
|
|
73e6a20503 | ||
|
|
ef0dc5305a | ||
|
|
73dc057c99 | ||
|
|
092564434f | ||
|
|
7623fe7df4 | ||
|
|
da03636bd0 | ||
|
|
21b119d8a0 | ||
|
|
4ee62fcca4 | ||
|
|
8acfb0f798 | ||
|
|
6e2d343264 | ||
|
|
3dd03cda46 | ||
|
|
87c8993141 | ||
|
|
323601db3a | ||
|
|
1ac7d4920f | ||
|
|
617a436eb5 | ||
|
|
7e6af77d70 | ||
|
|
59a6771e73 | ||
|
|
b88320bcbb | ||
|
|
06bafdfce6 | ||
|
|
441aef4823 | ||
|
|
0f86812258 | ||
|
|
f601e93101 | ||
|
|
bf8d4757b1 | ||
|
|
4b69717687 | ||
|
|
0647f7d22a | ||
|
|
ae6852d5eb | ||
|
|
d4902784c2 | ||
|
|
e2644586b6 | ||
|
|
cc80f6fa54 | ||
|
|
32c78f24e9 | ||
|
|
8b0f23e18f | ||
|
|
b76d16262c | ||
|
|
4c60f080f0 | ||
|
|
5f40c393b1 | ||
|
|
7b22d293f2 | ||
|
|
7f6c247652 | ||
|
|
267950bb54 | ||
|
|
d4b6bf6ca0 | ||
|
|
9d0c110a74 | ||
|
|
9d3c4df5e7 | ||
|
|
ca89818cc1 | ||
|
|
6b2caebd0d | ||
|
|
f716ece35f | ||
|
|
6dcc72e708 | ||
|
|
47a718ad2a | ||
|
|
e4e3d5d362 | ||
|
|
73b4987b32 | ||
|
|
7574d3921a | ||
|
|
c6b47c3a1b | ||
|
|
a5fec2560a | ||
|
|
b139d667d2 | ||
|
|
906a42c218 | ||
|
|
529b22c97c | ||
|
|
89f3508ce0 | ||
|
|
a9cc7f41ba | ||
|
|
cc517d36dc | ||
|
|
8363279050 | ||
|
|
7c0f9c307a | ||
|
|
3a7c9f7fab | ||
|
|
ba993ba09a | ||
|
|
fca1f80243 | ||
|
|
96926ffa63 | ||
|
|
48b850d350 | ||
|
|
ab6a01e59b | ||
|
|
cd5eb62f61 | ||
|
|
d04aa43b6d | ||
|
|
308fe269fc | ||
|
|
0f92e37712 | ||
|
|
7f41cebdf7 | ||
|
|
351734d661 | ||
|
|
961de94f81 | ||
|
|
2a89d57560 | ||
|
|
039e0b17a4 | ||
|
|
77802eec58 | ||
|
|
5db4cc8d21 | ||
|
|
f44260ee41 | ||
|
|
895bacfc11 | ||
|
|
de6363574c | ||
|
|
2a07d62146 | ||
|
|
3138415bd0 | ||
|
|
527ba63c94 | ||
|
|
6910648daf | ||
|
|
9fb68afe79 | ||
|
|
350f808b44 | ||
|
|
6438953c91 | ||
|
|
7f98e469cc | ||
|
|
d49ba093f0 | ||
|
|
1ebcbeeba2 | ||
|
|
c4eb6cd44a | ||
|
|
48a58951e8 | ||
|
|
414f8c7208 | ||
|
|
cb1d640c56 | ||
|
|
8a271608e8 | ||
|
|
dd3627dca8 | ||
|
|
fe6c917862 | ||
|
|
0dfa3f912e | ||
|
|
d8bfab3ae0 | ||
|
|
d7a9adf791 | ||
|
|
fb070b9448 | ||
|
|
add313980c | ||
|
|
29a909d547 | ||
|
|
13b10ca874 | ||
|
|
d0738ad64d | ||
|
|
a21b9ca555 | ||
|
|
f0e2910bd5 | ||
|
|
8f84027813 | ||
|
|
970b6fbb92 | ||
|
|
c8db6f237f | ||
|
|
01477fd102 | ||
|
|
0bd173dd93 | ||
|
|
87b7bbad16 | ||
|
|
2524684b1f | ||
|
|
6cc00a1e5b | ||
|
|
c2285848cd | ||
|
|
506a89aeb3 | ||
|
|
7c0221e6b8 | ||
|
|
e9d8c5b2bc | ||
|
|
8e5fc32f4d | ||
|
|
2eaeec326f | ||
|
|
c5823308af | ||
|
|
f0e255eb24 | ||
|
|
bccd2fd5d2 | ||
|
|
534c798028 | ||
|
|
2a48b585a4 | ||
|
|
8de699cb94 | ||
|
|
03ffeba683 | ||
|
|
153d55b774 | ||
|
|
1b95dfa5ed | ||
|
|
d9451af991 | ||
|
|
da375dce63 | ||
|
|
d12e2f7622 | ||
|
|
d0e9120559 | ||
|
|
d35ae2f075 | ||
|
|
92076dad8b | ||
|
|
a1ded23b46 | ||
|
|
4d618e6d93 | ||
|
|
d3e461e0bd | ||
|
|
7a94f4fcbc | ||
|
|
2758b5273b | ||
|
|
79d18ab920 | ||
|
|
13dd7c2db5 | ||
|
|
2486cd93c0 | ||
|
|
980c82ce31 | ||
|
|
1f6a4dbf40 | ||
|
|
b14f518461 | ||
|
|
70d243aa7b | ||
|
|
3f3996b155 | ||
|
|
5b3915fc65 | ||
|
|
53d33eb0fb | ||
|
|
c2722cb66a | ||
|
|
2ccc7a8061 | ||
|
|
97244b5c2c | ||
|
|
90a0988e06 | ||
|
|
a3778b8ce6 | ||
|
|
79bac7fd3f | ||
|
|
112863a52b | ||
|
|
c1150c06e0 | ||
|
|
fcfb3f7e50 | ||
|
|
7c6c5ccac1 | ||
|
|
03ad9bf349 | ||
|
|
f95ab83ae2 | ||
|
|
a25951f330 | ||
|
|
5b0f450802 | ||
|
|
14d049bd0f | ||
|
|
47b5f44796 | ||
|
|
ec8bb3d67b | ||
|
|
71abac2c3f | ||
|
|
21a227d67a | ||
|
|
df0783e4d4 | ||
|
|
a9a14b1253 | ||
|
|
dc13aacb13 | ||
|
|
7f7d4c7388 | ||
|
|
f6642d5582 | ||
|
|
e85f216b9e | ||
|
|
04289df987 | ||
|
|
5e0bb766d6 | ||
|
|
e23c979911 | ||
|
|
cf43a3fb4e | ||
|
|
5d809015ee | ||
|
|
6b30e9aa2f | ||
|
|
29c4a43968 |
@@ -113,6 +113,7 @@
|
||||
"no-console": "error",
|
||||
"no-undef": "error",
|
||||
"no-empty": "error",
|
||||
"no-control-regex": "off",
|
||||
"prefer-arrow-callback": "error",
|
||||
"constructor-super": "error",
|
||||
"no-case-declarations": "error",
|
||||
|
||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -72,6 +72,22 @@ jobs:
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./App/Dockerfile .
|
||||
|
||||
docker-build-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./E2E/Dockerfile .
|
||||
|
||||
docker-build-admin-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
|
||||
1
.github/workflows/compile.yml
vendored
1
.github/workflows/compile.yml
vendored
@@ -174,6 +174,7 @@ jobs:
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: cd Common && npm install
|
||||
- run: cd E2E && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-probe:
|
||||
|
||||
64
.github/workflows/release.yml
vendored
64
.github/workflows/release.yml
vendored
@@ -151,6 +151,66 @@ jobs:
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
e2e-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
oneuptime/e2e
|
||||
ghcr.io/oneuptime/e2e
|
||||
tags: |
|
||||
type=raw,value=release,enable=true
|
||||
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}},pattern={{version}},enable=true
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Generate Dockerfile from Dockerfile.tpl
|
||||
run: npm run prerun
|
||||
|
||||
# Build and deploy e2e.
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
file: ./E2E/Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
isolated-vm-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
runs-on: ubuntu-latest
|
||||
@@ -978,4 +1038,6 @@ jobs:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
64
.github/workflows/test-release.yaml
vendored
64
.github/workflows/test-release.yaml
vendored
@@ -80,6 +80,68 @@ jobs:
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
e2e-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
oneuptime/e2e
|
||||
ghcr.io/oneuptime/e2e
|
||||
tags: |
|
||||
type=raw,value=test,enable=true
|
||||
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}}-test,pattern={{version}},enable=true
|
||||
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Generate Dockerfile from Dockerfile.tpl
|
||||
run: npm run prerun
|
||||
|
||||
# Build and deploy e2e.
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
file: ./E2E/Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
test-server-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
runs-on: ubuntu-latest
|
||||
@@ -880,7 +942,7 @@ jobs:
|
||||
|
||||
test-helm-chart:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ infrastructure-agent-docker-image-deploy, isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy]
|
||||
needs: [ infrastructure-agent-docker-image-deploy, isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
|
||||
62
.github/workflows/test.e2e.yaml
vendored
Normal file
62
.github/workflows/test.e2e.yaml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: E2E Tests
|
||||
|
||||
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:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
|
||||
- run: npm run dev
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E/playwright-report
|
||||
./E2E/test-results
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -107,4 +107,7 @@ InfrastructureAgent/build/*
|
||||
|
||||
InfrastructureAgent/err.log
|
||||
InfrastructureAgent/out.log
|
||||
InfrastructureAgent/daemon.pid
|
||||
InfrastructureAgent/daemon.pid
|
||||
App/greenlock/.greenlockrc
|
||||
App/greenlock/greenlock.d/config.json
|
||||
App/greenlock/greenlock.d/config.json.bak
|
||||
|
||||
@@ -10,7 +10,17 @@ const app: ExpressApplication = Express.getExpressApp();
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// init the app
|
||||
await App(APP_NAME, undefined, true);
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error('App Init Failed:');
|
||||
logger.error(err);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"watch": ["webpack.config.js"],
|
||||
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'HOST=localhost' >> /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3003 --mode=development"
|
||||
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3003 --mode=development"
|
||||
}
|
||||
150
Accounts/package-lock.json
generated
150
Accounts/package-lock.json
generated
@@ -11,14 +11,14 @@
|
||||
"Common": "file:../Common",
|
||||
"CommonServer": "file:../CommonServer",
|
||||
"CommonUI": "file:../CommonUI",
|
||||
"css-loader": "^6.10.0",
|
||||
"css-loader": "^6.11.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"Model": "file:../Model",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-router-dom": "^6.22.2",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"sass-loader": "^13.3.3",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
@@ -35,19 +35,19 @@
|
||||
}
|
||||
},
|
||||
"../Common": {
|
||||
"name": "common",
|
||||
"name": "@oneuptime/common",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"axios": "^1.6.7",
|
||||
"axios": "^1.6.8",
|
||||
"crypto-js": "^4.1.1",
|
||||
"json5": "^2.2.3",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"posthog-js": "^1.104.4",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"posthog-js": "^1.116.6",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"slugify": "^1.6.5",
|
||||
"typeorm": "^0.3.20",
|
||||
"uuid": "^8.3.2"
|
||||
@@ -61,49 +61,46 @@
|
||||
}
|
||||
},
|
||||
"../CommonServer": {
|
||||
"name": "common-server",
|
||||
"name": "@oneuptime/common-server",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@clickhouse/client": "^0.2.7",
|
||||
"@elastic/elasticsearch": "^8.11.0",
|
||||
"@clickhouse/client": "^0.2.10",
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@opentelemetry/api": "^1.7.0",
|
||||
"@opentelemetry/api-logs": "^0.48.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.48.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.48.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.48.0",
|
||||
"@opentelemetry/api-logs": "^0.49.1",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.43.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.49.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.49.1",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.49.1",
|
||||
"@opentelemetry/id-generator-aws-xray": "^1.2.1",
|
||||
"@opentelemetry/instrumentation-express": "^0.35.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.48.0",
|
||||
"@opentelemetry/sdk-logs": "^0.48.0",
|
||||
"@opentelemetry/sdk-logs": "^0.49.1",
|
||||
"@opentelemetry/sdk-metrics": "^1.21.0",
|
||||
"@opentelemetry/sdk-node": "^0.48.0",
|
||||
"@opentelemetry/sdk-trace-node": "^1.21.0",
|
||||
"@socket.io/redis-adapter": "^8.2.1",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"axios": "^1.6.4",
|
||||
"bullmq": "^5.3.3",
|
||||
"Common": "file:../Common",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"dotenv": "^16.4.1",
|
||||
"ejs": "^3.1.8",
|
||||
"express": "^4.17.3",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2",
|
||||
"ioredis": "^5.3.2",
|
||||
"json2csv": "^5.0.7",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"markdown-it": "^13.0.1",
|
||||
"Model": "file:../Model",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^6.9.9",
|
||||
"nodemailer": "^6.9.10",
|
||||
"pg": "^8.7.3",
|
||||
"socket.io": "^4.7.2",
|
||||
"socket.io": "^4.7.4",
|
||||
"stripe": "^10.17.0",
|
||||
"twilio": "^4.19.3",
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"vm2": "^3.9.14"
|
||||
"typeorm-extension": "^2.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^6.3.1",
|
||||
@@ -124,43 +121,48 @@
|
||||
}
|
||||
},
|
||||
"../CommonUI": {
|
||||
"name": "common-ui",
|
||||
"name": "@oneuptime/common-ui",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.1",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@opentelemetry/api": "^1.7.0",
|
||||
"@opentelemetry/context-zone": "^1.21.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.48.0",
|
||||
"@opentelemetry/instrumentation": "^0.48.0",
|
||||
"@opentelemetry/instrumentation-fetch": "^0.48.0",
|
||||
"@opentelemetry/instrumentation-xml-http-request": "^0.48.0",
|
||||
"@nivo/core": "^0.85.1",
|
||||
"@nivo/line": "^0.85.1",
|
||||
"@opentelemetry/api": "^1.8.0",
|
||||
"@opentelemetry/context-zone": "^1.22.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.49.1",
|
||||
"@opentelemetry/instrumentation": "^0.49.1",
|
||||
"@opentelemetry/instrumentation-fetch": "^0.49.1",
|
||||
"@opentelemetry/instrumentation-xml-http-request": "^0.49.1",
|
||||
"@opentelemetry/resources": "^1.21.0",
|
||||
"@opentelemetry/sdk-trace-web": "^1.21.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.21.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"Common": "file:../Common",
|
||||
"formik": "^2.2.9",
|
||||
"history": "^5.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"Model": "file:../Model",
|
||||
"moment-timezone": "^0.5.44",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-big-calendar": "^1.8.7",
|
||||
"react-big-calendar": "^1.11.2",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-dropzone": "^14.2.2",
|
||||
"react-error-boundary": "^4.0.12",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-router-dom": "^6.21.3",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.13.6",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reactflow": "^11.10.3",
|
||||
"reactflow": "^11.10.4",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"tippy.js": "^6.3.7",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"use-async-effect": "^2.2.6"
|
||||
@@ -186,9 +188,9 @@
|
||||
}
|
||||
},
|
||||
"../Model": {
|
||||
"name": "model",
|
||||
"name": "@oneuptime/model",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
"typeorm": "^0.3.20"
|
||||
@@ -265,9 +267,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz",
|
||||
"integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==",
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
|
||||
"integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
@@ -944,15 +946,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/css-loader": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz",
|
||||
"integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz",
|
||||
"integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==",
|
||||
"dependencies": {
|
||||
"icss-utils": "^5.1.0",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss-modules-extract-imports": "^3.0.0",
|
||||
"postcss-modules-local-by-default": "^4.0.4",
|
||||
"postcss-modules-scope": "^3.1.1",
|
||||
"postcss-modules-extract-imports": "^3.1.0",
|
||||
"postcss-modules-local-by-default": "^4.0.5",
|
||||
"postcss-modules-scope": "^3.2.0",
|
||||
"postcss-modules-values": "^4.0.0",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"semver": "^7.5.4"
|
||||
@@ -1920,9 +1922,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-extract-imports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
|
||||
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz",
|
||||
"integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==",
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >= 14"
|
||||
},
|
||||
@@ -1931,9 +1933,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-local-by-default": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz",
|
||||
"integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz",
|
||||
"integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==",
|
||||
"dependencies": {
|
||||
"icss-utils": "^5.0.0",
|
||||
"postcss-selector-parser": "^6.0.2",
|
||||
@@ -1947,9 +1949,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-scope": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz",
|
||||
"integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz",
|
||||
"integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.0.4"
|
||||
},
|
||||
@@ -1975,9 +1977,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.13",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
|
||||
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
|
||||
"version": "6.0.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
|
||||
"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
@@ -2085,11 +2087,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.22.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz",
|
||||
"integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==",
|
||||
"version": "6.22.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
|
||||
"integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.15.2"
|
||||
"@remix-run/router": "1.15.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -2099,12 +2101,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.22.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz",
|
||||
"integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==",
|
||||
"version": "6.22.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
|
||||
"integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.15.2",
|
||||
"react-router": "6.22.2"
|
||||
"@remix-run/router": "1.15.3",
|
||||
"react-router": "6.22.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"Common": "file:../Common",
|
||||
"CommonServer": "file:../CommonServer",
|
||||
"CommonUI": "file:../CommonUI",
|
||||
"css-loader": "^6.10.0",
|
||||
"css-loader": "^6.11.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"file-loader": "^6.2.0",
|
||||
|
||||
@@ -8,12 +8,13 @@ import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import { LOGIN_API_URL } from '../Utils/ApiPaths';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import LoginUtil from '../Utils/Login';
|
||||
import LoginUtil from 'CommonUI/src/Utils/Login';
|
||||
import UserUtil from 'CommonUI/src/Utils/User';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import { DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert';
|
||||
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
|
||||
import useAsyncEffect from 'use-async-effect';
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = LOGIN_API_URL;
|
||||
@@ -28,6 +29,16 @@ const LoginPage: () => JSX.Element = () => {
|
||||
|
||||
const [showSsoTip, setShowSSOTip] = useState<boolean>(false);
|
||||
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (Navigation.getQueryStringByName('email')) {
|
||||
setInitialValues({
|
||||
email: Navigation.getQueryStringByName('email'),
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
@@ -66,9 +77,14 @@ const LoginPage: () => JSX.Element = () => {
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: 'jeff@example.com',
|
||||
required: true,
|
||||
disabled: Boolean(
|
||||
initialValues && initialValues['email']
|
||||
),
|
||||
title: 'Email',
|
||||
dataTestId: 'email',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -85,6 +101,7 @@ const LoginPage: () => JSX.Element = () => {
|
||||
url: new Route('/accounts/forgot-password'),
|
||||
openLinkInNewTab: false,
|
||||
},
|
||||
dataTestId: 'password',
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
@@ -106,7 +123,7 @@ const LoginPage: () => JSX.Element = () => {
|
||||
}}
|
||||
maxPrimaryButtonWidth={true}
|
||||
footer={
|
||||
<div className="actions pointer text-center mt-4 hover:underline fw-semibold">
|
||||
<div className="actions text-center mt-4 hover:underline fw-semibold">
|
||||
<div>
|
||||
{!showSsoTip && (
|
||||
<div
|
||||
@@ -121,11 +138,9 @@ const LoginPage: () => JSX.Element = () => {
|
||||
|
||||
{showSsoTip && (
|
||||
<div className="text-gray-500 text-sm">
|
||||
Please sign in with your username
|
||||
and password. Once you have signed
|
||||
in, you'll be able to sign in
|
||||
via SSO that's configured for
|
||||
your project.
|
||||
Please sign in with your SSO
|
||||
provider like Okta, Auth0, Entra ID
|
||||
or any other SAML 2.0 provider.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import LoginUtil from '../Utils/Login';
|
||||
import LoginUtil from 'CommonUI/src/Utils/Login';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import UserUtil from 'CommonUI/src/Utils/User';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
@@ -104,6 +104,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues['email']),
|
||||
title: 'Email',
|
||||
dataTestId: 'email',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -113,6 +114,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
placeholder: 'Jeff Smith',
|
||||
required: true,
|
||||
title: 'Full Name',
|
||||
dataTestId: 'name',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -126,6 +128,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
placeholder: 'Acme, Inc.',
|
||||
required: true,
|
||||
title: 'Company Name',
|
||||
dataTestId: 'companyName',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -139,6 +142,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
required: true,
|
||||
placeholder: '+11234567890',
|
||||
title: 'Phone Number',
|
||||
dataTestId: 'companyPhoneNumber',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -155,6 +159,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
placeholder: 'Password',
|
||||
title: 'Password',
|
||||
required: true,
|
||||
dataTestId: 'password',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -170,6 +175,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
overrideFieldKey: 'confirmPassword',
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
dataTestId: 'confirmPassword',
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -10,7 +10,18 @@ const app: ExpressApplication = Express.getExpressApp();
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// init the app
|
||||
await App(APP_NAME, undefined, true);
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error('App Init Failed:');
|
||||
logger.error(err);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"watch": ["webpack.config.js"],
|
||||
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'HOST=localhost' >> /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3158 --mode=development"
|
||||
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3158 --mode=development"
|
||||
}
|
||||
@@ -222,16 +222,30 @@ const Projects: FunctionComponent = (): ReactElement => {
|
||||
noItemsMessage={'No projects found.'}
|
||||
formFields={fields}
|
||||
showRefreshButton={true}
|
||||
showFilterButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
columns={[
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
isFilterable: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -239,7 +253,6 @@ const Projects: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
isFilterable: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,6 @@ import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
|
||||
import Pill from 'CommonUI/src/Components/Pill/Pill';
|
||||
import { Green, Red } from 'Common/Types/BrandColors';
|
||||
@@ -324,7 +323,7 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
)}
|
||||
|
||||
{emailServerType === EmailServerType.Sendgrid ? (
|
||||
<CardModelDetail
|
||||
<CardModelDetail<GlobalConfig>
|
||||
name="Sendgrid Settings"
|
||||
cardProps={{
|
||||
title: 'Sendgrid Settings',
|
||||
@@ -376,7 +375,7 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: '',
|
||||
placeholder: 'None',
|
||||
getElement: (item: JSONObject) => {
|
||||
getElement: (item: GlobalConfig) => {
|
||||
if (
|
||||
item['sendgridApiKey'] &&
|
||||
item['sendgridFromEmail'] &&
|
||||
|
||||
@@ -7,7 +7,6 @@ import DashboardSideMenu from '../SideMenu';
|
||||
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
|
||||
import Probe from 'Model/Models/Probe';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import { Green, Red } from 'Common/Types/BrandColors';
|
||||
import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble';
|
||||
@@ -23,7 +22,7 @@ import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes';
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
const [showKeyModal, setShowKeyModal] = useState<boolean>(false);
|
||||
|
||||
const [currentProbe, setCurrentProbe] = useState<JSONObject | null>(null);
|
||||
const [currentProbe, setCurrentProbe] = useState<Probe | null>(null);
|
||||
|
||||
return (
|
||||
<Page
|
||||
@@ -78,7 +77,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
modelAPI={AdminModelAPI}
|
||||
noItemsMessage={'No probes found.'}
|
||||
showRefreshButton={true}
|
||||
showFilterButton={true}
|
||||
onBeforeCreate={(item: Probe) => {
|
||||
item.isGlobalProbe = true;
|
||||
return Promise.resolve(item);
|
||||
@@ -127,7 +125,7 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
title: 'Show ID and Key',
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
onClick: async (
|
||||
item: JSONObject,
|
||||
item: Probe,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction
|
||||
) => {
|
||||
@@ -143,6 +141,22 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
},
|
||||
]}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: 'Description',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
@@ -150,8 +164,8 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
isFilterable: true,
|
||||
getElement: (item: JSONObject): ReactElement => {
|
||||
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
return <ProbeElement probe={item} />;
|
||||
},
|
||||
},
|
||||
@@ -161,7 +175,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: 'Description',
|
||||
type: FieldType.Text,
|
||||
isFilterable: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -169,15 +182,12 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: 'Status',
|
||||
type: FieldType.Text,
|
||||
isFilterable: false,
|
||||
getElement: (item: JSONObject): ReactElement => {
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
if (
|
||||
item &&
|
||||
item['lastAlive'] &&
|
||||
OneUptimeDate.getNumberOfMinutesBetweenDates(
|
||||
OneUptimeDate.fromString(
|
||||
item['lastAlive'] as string
|
||||
),
|
||||
OneUptimeDate.fromString(item['lastAlive']),
|
||||
OneUptimeDate.getCurrentDate()
|
||||
) < 5
|
||||
) {
|
||||
|
||||
@@ -72,16 +72,37 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
]}
|
||||
showRefreshButton={true}
|
||||
showFilterButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
columns={[
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Full Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
type: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Full Name',
|
||||
type: FieldType.Text,
|
||||
isFilterable: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -89,7 +110,6 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: 'Email',
|
||||
type: FieldType.Email,
|
||||
isFilterable: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -97,7 +117,6 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
isFilterable: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -16,58 +16,74 @@ import StatusServiceHandler from './Service/Status';
|
||||
import DataTypeServiceHandler from './Service/DataType';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import { StaticPath } from './Utils/Config';
|
||||
import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
const APIReferenceFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.use('/reference', ExpressStatic(StaticPath, { maxAge: 2592000 }));
|
||||
app.use('/reference', ExpressStatic(StaticPath, { maxAge: 2592000 }));
|
||||
|
||||
// Index page
|
||||
app.get(['/reference'], (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
return res.redirect('/reference/introduction');
|
||||
});
|
||||
// Index page
|
||||
app.get(
|
||||
['/reference'],
|
||||
(_req: ExpressRequest, res: ExpressResponse) => {
|
||||
return res.redirect('/reference/introduction');
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
['/reference/page-not-found'],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
);
|
||||
app.get(
|
||||
['/reference/page-not-found'],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
// All Pages
|
||||
app.get(['/reference/:page'], (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const page: string | undefined = req.params['page'];
|
||||
// All Pages
|
||||
app.get(
|
||||
['/reference/:page'],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
const page: string | undefined = req.params['page'];
|
||||
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
|
||||
if (req.params['page'] === 'permissions') {
|
||||
return PermissionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'authentication') {
|
||||
return AuthenticationServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'pagination') {
|
||||
return PaginationServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'errors') {
|
||||
return ErrorServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'introduction') {
|
||||
return IntroductionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'status') {
|
||||
return StatusServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'data-types') {
|
||||
return DataTypeServiceHandler.executeResponse(req, res);
|
||||
} else if (currentResource) {
|
||||
return ModelServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
// page not found
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
});
|
||||
if (req.params['page'] === 'permissions') {
|
||||
return PermissionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'authentication') {
|
||||
return AuthenticationServiceHandler.executeResponse(
|
||||
req,
|
||||
res
|
||||
);
|
||||
} else if (req.params['page'] === 'pagination') {
|
||||
return PaginationServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'errors') {
|
||||
return ErrorServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'introduction') {
|
||||
return IntroductionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'status') {
|
||||
return StatusServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'data-types') {
|
||||
return DataTypeServiceHandler.executeResponse(req, res);
|
||||
} else if (currentResource) {
|
||||
return ModelServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
// page not found
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
app.get('/reference/*', (req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
});
|
||||
app.get('/reference/*', (req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default APIReferenceFeatureSet;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,73 +10,84 @@ import DocsNav, { NavGroup, NavLink } from './Utils/Nav';
|
||||
import LocalFile from 'CommonServer/Utils/LocalFile';
|
||||
import DocsRender from './Utils/Render';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
const DocsFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.get('/docs', (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.redirect('/docs/introduction/getting-started');
|
||||
});
|
||||
app.get('/docs', (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.redirect('/docs/introduction/getting-started');
|
||||
});
|
||||
|
||||
app.get(
|
||||
'/docs/:categorypath/:pagepath',
|
||||
async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fullPath: string =
|
||||
`${_req.params['categorypath']}/${_req.params['pagepath']}`.toLowerCase();
|
||||
app.get(
|
||||
'/docs/:categorypath/:pagepath',
|
||||
async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fullPath: string =
|
||||
`${_req.params['categorypath']}/${_req.params['pagepath']}`.toLowerCase();
|
||||
|
||||
// read file from Content folder.
|
||||
let contentInMarkdown: string = await LocalFile.read(
|
||||
`${ContentPath}/${fullPath}.md`
|
||||
);
|
||||
// read file from Content folder.
|
||||
let contentInMarkdown: string = await LocalFile.read(
|
||||
`${ContentPath}/${fullPath}.md`
|
||||
);
|
||||
|
||||
// remove first line from content because we dont want to show title in content. Title is already in nav.
|
||||
// remove first line from content because we dont want to show title in content. Title is already in nav.
|
||||
|
||||
contentInMarkdown = contentInMarkdown
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
.join('\n');
|
||||
contentInMarkdown = contentInMarkdown
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
.join('\n');
|
||||
|
||||
const renderedContent: string = await DocsRender.render(
|
||||
contentInMarkdown
|
||||
);
|
||||
const renderedContent: string = await DocsRender.render(
|
||||
contentInMarkdown
|
||||
);
|
||||
|
||||
const currentCategory: NavGroup | undefined = DocsNav.find(
|
||||
(category: NavGroup) => {
|
||||
return category.links.find((link: NavLink) => {
|
||||
return link.url.toLocaleLowerCase().includes(fullPath);
|
||||
const currentCategory: NavGroup | undefined = DocsNav.find(
|
||||
(category: NavGroup) => {
|
||||
return category.links.find((link: NavLink) => {
|
||||
return link.url
|
||||
.toLocaleLowerCase()
|
||||
.includes(fullPath);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const currrentNavLink: NavLink | undefined =
|
||||
currentCategory?.links.find((link: NavLink) => {
|
||||
return link.url
|
||||
.toLocaleLowerCase()
|
||||
.includes(fullPath);
|
||||
});
|
||||
|
||||
if (!currentCategory || !currrentNavLink) {
|
||||
// render not found.
|
||||
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/NotFound`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
|
||||
res.render(`${ViewsPath}/Index`, {
|
||||
nav: DocsNav,
|
||||
content: renderedContent,
|
||||
category: currentCategory,
|
||||
link: currrentNavLink,
|
||||
githubPath: fullPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
res.status(500);
|
||||
return res.render(`${ViewsPath}/ServerError`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const currrentNavLink: NavLink | undefined =
|
||||
currentCategory?.links.find((link: NavLink) => {
|
||||
return link.url.toLocaleLowerCase().includes(fullPath);
|
||||
});
|
||||
|
||||
if (!currentCategory || !currrentNavLink) {
|
||||
// render not found.
|
||||
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/NotFound`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
res.render(`${ViewsPath}/Index`, {
|
||||
nav: DocsNav,
|
||||
content: renderedContent,
|
||||
category: currentCategory,
|
||||
link: currrentNavLink,
|
||||
githubPath: fullPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
res.status(500);
|
||||
return res.render(`${ViewsPath}/ServerError`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
app.use('/docs/static', ExpressStatic(StaticPath));
|
||||
},
|
||||
};
|
||||
|
||||
app.use('/docs/static', ExpressStatic(StaticPath));
|
||||
export default DocsFeatureSet;
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
<%- include('./Partials/Head.ejs') %>
|
||||
</head>
|
||||
|
||||
<body class="flex min-h-full bg-white dark:bg-slate-900">
|
||||
<body class="flex min-h-full bg-white ">
|
||||
|
||||
<div class="flex w-full flex-col">
|
||||
<%- include('./Partials/Header.ejs') %>
|
||||
|
||||
<div class="relative mx-auto flex w-full max-w-8xl flex-auto justify-center sm:px-2 lg:px-8 xl:px-12">
|
||||
<div class="hidden lg:relative lg:block lg:flex-none">
|
||||
<div class="absolute inset-y-0 right-0 w-[50vw] bg-slate-50 dark:hidden"></div>
|
||||
<div class="absolute inset-y-0 right-0 w-[50vw] bg-slate-50 "></div>
|
||||
<div
|
||||
class="absolute bottom-0 right-0 top-16 hidden h-12 w-px bg-gradient-to-t from-slate-800 dark:block">
|
||||
class="absolute bottom-0 right-0 top-16 hidden h-12 w-px bg-gradient-to-t from-slate-800 ">
|
||||
</div>
|
||||
<div class="absolute bottom-0 right-0 top-28 hidden w-px bg-slate-800 dark:block"></div>
|
||||
<div class="absolute bottom-0 right-0 top-28 hidden w-px bg-slate-800 "></div>
|
||||
<div
|
||||
class="sticky top-[4.75rem] -ml-0.5 h-[calc(100vh-4.75rem)] w-64 overflow-y-auto overflow-x-hidden py-16 pl-0.5 pr-8 xl:w-72 xl:pr-16">
|
||||
<%- include('./Partials/Nav.ejs') %>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<article>
|
||||
<header class="mb-9 space-y-1">
|
||||
<p class="text-base font-bold text-sky-500"><%- category.title %></p>
|
||||
<h1 class="font-bold text-3xl tracking-tight text-slate-900 dark:text-white"><%- link.title %>
|
||||
<h1 class="font-bold text-3xl tracking-tight text-slate-900 "><%- link.title %>
|
||||
</h1>
|
||||
</header>
|
||||
<div
|
||||
class="prose prose-slate max-w-none dark:prose-invert dark:text-slate-400 prose-headings:scroll-mt-28 prose-headings:font-display prose-headings:font-normal lg:prose-headings:scroll-mt-[8.5rem] prose-lead:text-slate-500 dark:prose-lead:text-slate-400 prose-a:font-semibold dark:prose-a:text-sky-400 prose-a:no-underline prose-a:shadow-[inset_0_-2px_0_0_var(--tw-prose-background,#fff),inset_0_calc(-1*(var(--tw-prose-underline-size,4px)+2px))_0_0_var(--tw-prose-underline,theme(colors.sky.300))] hover:prose-a:[--tw-prose-underline-size:6px] dark:[--tw-prose-background:theme(colors.slate.900)] dark:prose-a:shadow-[inset_0_calc(-1*var(--tw-prose-underline-size,2px))_0_0_var(--tw-prose-underline,theme(colors.sky.800))] dark:hover:prose-a:[--tw-prose-underline-size:6px] prose-pre:rounded-xl prose-pre:bg-slate-900 prose-pre:shadow-lg dark:prose-pre:bg-slate-800/60 dark:prose-pre:shadow-none dark:prose-pre:ring-1 dark:prose-pre:ring-slate-300/10 dark:prose-hr:border-slate-800">
|
||||
class="prose prose-slate max-w-none prose-headings:scroll-mt-28 prose-headings:font-display prose-headings:font-normal lg:prose-headings:scroll-mt-[8.5rem] prose-lead:text-slate-500 prose-a:font-semibold prose-a:no-underline prose-a:shadow-[inset_0_-2px_0_0_var(--tw-prose-background,#fff),inset_0_calc(-1*(var(--tw-prose-underline-size,4px)+2px))_0_0_var(--tw-prose-underline,theme(colors.sky.300))] hover:prose-a:[--tw-prose-underline-size:6px] prose-pre:rounded-xl prose-pre:bg-slate-900 prose-pre:shadow-lg ">
|
||||
<%- content %>
|
||||
</div>
|
||||
</article>
|
||||
@@ -1,5 +1,5 @@
|
||||
<header
|
||||
class="sticky top-0 z-50 flex flex-none flex-wrap items-center justify-between bg-white px-4 py-5 shadow-md shadow-slate-900/5 transition duration-500 sm:px-6 lg:px-8 dark:shadow-none dark:bg-slate-900/95 dark:backdrop-blur dark:[@supports(backdrop-filter:blur(0))]:bg-slate-900/75">
|
||||
class="sticky top-0 z-50 flex flex-none flex-wrap items-center justify-between bg-white px-4 py-5 shadow-md shadow-slate-900/5 transition duration-500 sm:px-6 lg:px-8 ">
|
||||
|
||||
<div class="relative flex flex-grow basis-0 items-center"><a aria-label="Home page" href="/">
|
||||
<img class="h-8 w-auto" src="/img/3-transparent.svg" alt="">
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="relative flex basis-0 justify-end gap-6 sm:gap-8 md:flex-grow">
|
||||
<a class="group" aria-label="GitHub" target="_blank" href="https://github.com/oneuptime/oneuptime"><svg
|
||||
aria-hidden="true" viewBox="0 0 16 16"
|
||||
class="h-6 w-6 fill-slate-400 group-hover:fill-slate-500 dark:group-hover:fill-slate-300">
|
||||
class="h-6 w-6 fill-slate-400 group-hover:fill-slate-500 ">
|
||||
<path
|
||||
d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z">
|
||||
</path>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<ul role="list" class="space-y-9">
|
||||
<% for(var i=0; i<nav.length; i++) {%>
|
||||
<li>
|
||||
<h2 class="font-display font-medium text-slate-900 dark:text-white"><%- nav[i].title -%>
|
||||
<h2 class="font-display font-medium text-slate-900 "><%- nav[i].title -%>
|
||||
</h2>
|
||||
|
||||
<ul role="list"
|
||||
class="mt-2 space-y-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-4 lg:border-slate-200 dark:border-slate-800">
|
||||
class="mt-2 space-y-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-4 lg:border-slate-200 ">
|
||||
<% if(nav[i].links.length> 0) { %>
|
||||
<% for(var j=0; j<nav[i].links.length; j++) {%>
|
||||
<% if(link.url===nav[i].links[j].url) { %>
|
||||
@@ -17,7 +17,7 @@
|
||||
</li>
|
||||
<% } else { %>
|
||||
<li class="relative"><a
|
||||
class="block w-full pl-3.5 before:pointer-events-none before:absolute before:-left-1 before:top-1/2 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block dark:text-slate-400 dark:before:bg-slate-700 dark:hover:text-slate-300"
|
||||
class="block w-full pl-3.5 before:pointer-events-none before:absolute before:-left-1 before:top-1/2 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block "
|
||||
href="<%- nav[i].links[j].url -%>"><%-
|
||||
nav[i].links[j].title -%></a></li>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<dl class="mt-12 flex border-t border-slate-200 pt-6 dark:border-slate-800">
|
||||
<dl class="mt-12 flex border-t border-slate-200 pt-6 ">
|
||||
<div class="ml-auto text-right">
|
||||
<dt class="font-display text-sm font-medium text-slate-900 dark:text-white">Next</dt>
|
||||
<dt class="font-display text-sm font-medium text-slate-900 ">Next</dt>
|
||||
<dd class="mt-1"><a
|
||||
class="flex items-center gap-x-1 text-base font-semibold text-slate-500 hover:text-slate-600 dark:text-slate-400 dark:hover:text-slate-300"
|
||||
class="flex items-center gap-x-1 text-base font-semibold text-slate-500 hover:text-slate-600 "
|
||||
href="/docs/installation">Installation<svg viewBox="0 0 16 16" aria-hidden="true"
|
||||
class="h-4 w-4 flex-none fill-current">
|
||||
<path
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -474,7 +474,7 @@
|
||||
</div>
|
||||
<input id="default-range" type="range" value="1" max="100" min="0" step="1"
|
||||
onchange="updateMonitorPrice(this.value)" oninput="updateMonitorPrice(this.value)"
|
||||
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700">
|
||||
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer ">
|
||||
|
||||
<div>
|
||||
<p id="more-than-100-monitors-message" style="display: none;" class="text-base text-gray-600">If you have
|
||||
@@ -564,7 +564,7 @@
|
||||
</div>
|
||||
<input id="default-range" type="range" value="1" max="1000" min="0" step="1"
|
||||
onchange="updateTelemetryPrice(this.value, null)" oninput="updateTelemetryPrice(this.value, null)"
|
||||
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700">
|
||||
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer ">
|
||||
<div>
|
||||
<p id="more-than-10-tb-ingested-message" style="display: none;" class="text-base text-gray-600">If you are
|
||||
ingesting more than 1 TB of data every month, contact sales@oneuptime.com for a discount.</p>
|
||||
@@ -578,7 +578,7 @@
|
||||
</div>
|
||||
<input id="default-range" type="range" value="15" max="180" min="0" step="1"
|
||||
onchange="updateTelemetryPrice(null, this.value)" oninput="updateTelemetryPrice(null,this.value)"
|
||||
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700">
|
||||
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer ">
|
||||
<div>
|
||||
<p id="more-than-6-months-rentention" style="display: none;" class="text-base text-gray-600">If you're
|
||||
looking for data rention of more than 6 months. contact sales@oneuptime.com for a discount. </p>
|
||||
|
||||
@@ -182,10 +182,18 @@ router.post(
|
||||
savedUser.id!
|
||||
);
|
||||
|
||||
const token: string = JSONWebToken.sign(
|
||||
savedUser,
|
||||
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
|
||||
);
|
||||
const token: string = JSONWebToken.signUserLoginToken({
|
||||
tokenData: {
|
||||
userId: savedUser.id!,
|
||||
email: savedUser.email!,
|
||||
name: savedUser.name!,
|
||||
isMasterAdmin: savedUser.isMasterAdmin!,
|
||||
isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO.
|
||||
},
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
|
||||
// Set a cookie with token.
|
||||
CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), token, {
|
||||
@@ -275,7 +283,7 @@ router.post(
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
@@ -390,7 +398,7 @@ router.post(
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -482,7 +490,7 @@ router.post(
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -499,7 +507,7 @@ router.post(
|
||||
try {
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -573,10 +581,18 @@ router.post(
|
||||
alreadySavedUser.password.toString() ===
|
||||
user.password!.toString()
|
||||
) {
|
||||
const token: string = JSONWebToken.sign(
|
||||
alreadySavedUser,
|
||||
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
|
||||
);
|
||||
const token: string = JSONWebToken.signUserLoginToken({
|
||||
tokenData: {
|
||||
userId: alreadySavedUser.id!,
|
||||
email: alreadySavedUser.email!,
|
||||
name: alreadySavedUser.name!,
|
||||
isMasterAdmin: alreadySavedUser.isMasterAdmin!,
|
||||
isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO.
|
||||
},
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
|
||||
// Set a cookie with token.
|
||||
CookieUtil.setCookie(
|
||||
|
||||
@@ -62,10 +62,10 @@ router.post(
|
||||
|
||||
// if found then generate a token and return it.
|
||||
|
||||
const token: string = JSONWebToken.sign(
|
||||
{ resellerId: resellerId },
|
||||
OneUptimeDate.getDayInSeconds(365)
|
||||
);
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: { resellerId: resellerId },
|
||||
expiresInSeconds: OneUptimeDate.getDayInSeconds(365),
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
access: token,
|
||||
|
||||
@@ -32,6 +32,7 @@ import Hostname from 'Common/Types/API/Hostname';
|
||||
import Protocol from 'Common/Types/API/Protocol';
|
||||
import DatabaseConfig from 'CommonServer/DatabaseConfig';
|
||||
import CookieUtil from 'CommonServer/Utils/Cookie';
|
||||
import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig';
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
@@ -67,7 +68,10 @@ router.get(
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
signOnURL: true,
|
||||
issuerURL: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -92,310 +96,374 @@ router.get(
|
||||
);
|
||||
}
|
||||
|
||||
return Response.redirect(req, res, projectSSO.signOnURL);
|
||||
if (!projectSSO.issuerURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer not found')
|
||||
);
|
||||
}
|
||||
|
||||
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
|
||||
acsUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/identity/idp-login/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`
|
||||
),
|
||||
signOnUrl: projectSSO.signOnURL!,
|
||||
issuerUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`
|
||||
),
|
||||
});
|
||||
|
||||
return Response.redirect(req, res, samlRequestUrl);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/idp-login/:projectId/:projectSsoId',
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
return await loginUserWithSso(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/idp-login/:projectId/:projectSsoId',
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
try {
|
||||
const samlResponseBase64: string = req.body.SAMLResponse;
|
||||
return await loginUserWithSso(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
const samlResponse: string = Buffer.from(
|
||||
samlResponseBase64,
|
||||
'base64'
|
||||
).toString();
|
||||
type LoginUserWithSsoFunction = (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
) => Promise<void>;
|
||||
|
||||
const response: JSONObject = await xml2js.parseStringPromise(
|
||||
samlResponse
|
||||
const loginUserWithSso: LoginUserWithSsoFunction = async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const samlResponseBase64: string = req.body.SAMLResponse;
|
||||
|
||||
if (!samlResponseBase64) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('SAMLResponse not found')
|
||||
);
|
||||
}
|
||||
|
||||
let issuerUrl: string = '';
|
||||
let email: Email | null = null;
|
||||
const samlResponse: string = Buffer.from(
|
||||
samlResponseBase64,
|
||||
'base64'
|
||||
).toString();
|
||||
|
||||
if (!req.params['projectId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Project ID not found')
|
||||
);
|
||||
}
|
||||
const response: JSONObject = await xml2js.parseStringPromise(
|
||||
samlResponse
|
||||
);
|
||||
|
||||
if (!req.params['projectSsoId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Project SSO ID not found')
|
||||
);
|
||||
}
|
||||
let issuerUrl: string = '';
|
||||
let email: Email | null = null;
|
||||
|
||||
const projectSSO: ProjectSSO | null =
|
||||
await ProjectSSOService.findOneBy({
|
||||
query: {
|
||||
projectId: new ObjectID(req.params['projectId']),
|
||||
_id: req.params['projectSsoId'],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
signOnURL: true,
|
||||
issuerURL: true,
|
||||
publicCertificate: true,
|
||||
teams: {
|
||||
_id: true,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
if (!req.params['projectId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Project ID not found')
|
||||
);
|
||||
}
|
||||
|
||||
if (!projectSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('SSO Config not found')
|
||||
);
|
||||
}
|
||||
if (!req.params['projectSsoId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Project SSO ID not found')
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.issuerURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer URL not found')
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Sign on URL not found')
|
||||
);
|
||||
}
|
||||
|
||||
if (!projectSSO.publicCertificate) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Public Certificate not found')
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
SSOUtil.isPayloadValid(response);
|
||||
|
||||
if (
|
||||
!SSOUtil.isSignatureValid(
|
||||
samlResponse,
|
||||
projectSSO.publicCertificate
|
||||
)
|
||||
) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException(
|
||||
'Signature is not valid or Public Certificate configured with this SSO provider is not valid'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
issuerUrl = SSOUtil.getIssuer(response);
|
||||
email = SSOUtil.getEmail(response);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Exception) {
|
||||
return Response.sendErrorResponse(req, res, err);
|
||||
}
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new ServerException()
|
||||
);
|
||||
}
|
||||
|
||||
if (projectSSO.issuerURL.toString() !== issuerUrl) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer URL does not match')
|
||||
);
|
||||
}
|
||||
|
||||
// Check if he already belongs to the project, If he does - then log in.
|
||||
|
||||
let alreadySavedUser: User | null = await UserService.findOneBy({
|
||||
query: { email: email },
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
isMasterAdmin: true,
|
||||
isEmailVerified: true,
|
||||
profilePictureId: true,
|
||||
const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy(
|
||||
{
|
||||
query: {
|
||||
projectId: new ObjectID(req.params['projectId']),
|
||||
_id: req.params['projectSsoId'],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
signOnURL: true,
|
||||
issuerURL: true,
|
||||
publicCertificate: true,
|
||||
teams: {
|
||||
_id: true,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!projectSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('SSO Config not found')
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.issuerURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer URL not found')
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Sign on URL not found')
|
||||
);
|
||||
}
|
||||
|
||||
if (!projectSSO.publicCertificate) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Public Certificate not found')
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
SSOUtil.isPayloadValid(response);
|
||||
|
||||
if (
|
||||
!SSOUtil.isSignatureValid(
|
||||
samlResponse,
|
||||
projectSSO.publicCertificate
|
||||
)
|
||||
) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException(
|
||||
'Signature is not valid or Public Certificate configured with this SSO provider is not valid'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
issuerUrl = SSOUtil.getIssuer(response);
|
||||
email = SSOUtil.getEmail(response);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Exception) {
|
||||
return Response.sendErrorResponse(req, res, err);
|
||||
}
|
||||
return Response.sendErrorResponse(req, res, new ServerException());
|
||||
}
|
||||
|
||||
if (projectSSO.issuerURL.toString() !== issuerUrl) {
|
||||
logger.error(
|
||||
'Issuer URL does not match. It should be ' +
|
||||
projectSSO.issuerURL.toString() +
|
||||
' but it is ' +
|
||||
issuerUrl.toString()
|
||||
);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer URL does not match')
|
||||
);
|
||||
}
|
||||
|
||||
// Check if he already belongs to the project, If he does - then log in.
|
||||
|
||||
let alreadySavedUser: User | null = await UserService.findOneBy({
|
||||
query: { email: email },
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
isMasterAdmin: true,
|
||||
isEmailVerified: true,
|
||||
profilePictureId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
let isNewUser: boolean = false;
|
||||
|
||||
if (!alreadySavedUser) {
|
||||
// this should never happen because user is logged in before he signs in with SSO UNLESS he initiates the login though the IDP.
|
||||
|
||||
/// Create a user.
|
||||
|
||||
alreadySavedUser = await UserService.createByEmail({
|
||||
email,
|
||||
isEmailVerified: true,
|
||||
generateRandomPassword: true,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
let isNewUser: boolean = false;
|
||||
isNewUser = true;
|
||||
}
|
||||
|
||||
if (!alreadySavedUser) {
|
||||
// this should never happen because user is logged in before he signs in with SSO UNLESS he initiates the login though the IDP.
|
||||
// If he does not then add him to teams that he should belong and log in.
|
||||
// This should never happen because email is verified before he logs in with SSO.
|
||||
if (!alreadySavedUser.isEmailVerified && !isNewUser) {
|
||||
await AuthenticationEmail.sendVerificationEmail(alreadySavedUser!);
|
||||
|
||||
/// Create a user.
|
||||
|
||||
alreadySavedUser = await UserService.createByEmail(email, {
|
||||
isRoot: true,
|
||||
});
|
||||
|
||||
isNewUser = true;
|
||||
}
|
||||
|
||||
// If he does not then add him to teams that he should belong and log in.
|
||||
if (!alreadySavedUser.isEmailVerified && !isNewUser) {
|
||||
await AuthenticationEmail.sendVerificationEmail(
|
||||
alreadySavedUser
|
||||
);
|
||||
|
||||
return Response.render(
|
||||
req,
|
||||
res,
|
||||
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
|
||||
{
|
||||
title: 'Email not verified.',
|
||||
message:
|
||||
'Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// check if the user already belongs to the project
|
||||
const teamMemberCount: PositiveNumber =
|
||||
await TeamMemberService.countBy({
|
||||
query: {
|
||||
projectId: new ObjectID(
|
||||
req.params['projectId'] as string
|
||||
),
|
||||
userId: alreadySavedUser.id!,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (teamMemberCount.toNumber() === 0) {
|
||||
// user not in project, add him to default teams.
|
||||
|
||||
if (!projectSSO.teams || projectSSO.teams.length === 0) {
|
||||
return Response.render(
|
||||
req,
|
||||
res,
|
||||
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
|
||||
{
|
||||
title: 'No teams added.',
|
||||
message:
|
||||
'No teams have been added to this SSO config. Please contact your admin and have default teams added.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for (const team of projectSSO.teams) {
|
||||
// add user to team
|
||||
let teamMember: TeamMember = new TeamMember();
|
||||
teamMember.projectId = new ObjectID(
|
||||
req.params['projectId'] as string
|
||||
);
|
||||
teamMember.userId = alreadySavedUser.id!;
|
||||
teamMember.hasAcceptedInvitation = true;
|
||||
teamMember.invitationAcceptedAt =
|
||||
OneUptimeDate.getCurrentDate();
|
||||
teamMember.teamId = team.id!;
|
||||
|
||||
teamMember = await TeamMemberService.create({
|
||||
data: teamMember,
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isNewUser) {
|
||||
return Response.render(
|
||||
req,
|
||||
res,
|
||||
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
|
||||
{
|
||||
title: 'You have not signed up so far.',
|
||||
message:
|
||||
'You need to sign up for an account on OneUptime with this email:' +
|
||||
email.toString() +
|
||||
'. Once you have signed up, you can use SSO to log in to your project.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const projectId: ObjectID = new ObjectID(
|
||||
req.params['projectId'] as string
|
||||
);
|
||||
|
||||
const token: string = JSONWebToken.sign(
|
||||
{
|
||||
userId: alreadySavedUser.id!,
|
||||
projectId: projectId,
|
||||
email: email,
|
||||
isMasterAdmin: false,
|
||||
},
|
||||
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
|
||||
);
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(
|
||||
alreadySavedUser.id!
|
||||
);
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol =
|
||||
await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserSSOKey(projectId),
|
||||
token,
|
||||
{
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
httpOnly: true,
|
||||
}
|
||||
);
|
||||
|
||||
return Response.redirect(
|
||||
return Response.render(
|
||||
req,
|
||||
res,
|
||||
new URL(
|
||||
httpProtocol,
|
||||
host,
|
||||
new Route(DashboardRoute.toString()).addRoute(
|
||||
'/' + req.params['projectId']
|
||||
),
|
||||
'sso_token=' + token
|
||||
)
|
||||
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
|
||||
{
|
||||
title: 'Email not verified.',
|
||||
message:
|
||||
'Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.',
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
Response.sendErrorResponse(req, res, new ServerException());
|
||||
}
|
||||
|
||||
// check if the user already belongs to the project
|
||||
const teamMemberCount: PositiveNumber = await TeamMemberService.countBy(
|
||||
{
|
||||
query: {
|
||||
projectId: new ObjectID(req.params['projectId'] as string),
|
||||
userId: alreadySavedUser!.id!,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (teamMemberCount.toNumber() === 0) {
|
||||
// user not in project, add him to default teams.
|
||||
|
||||
if (!projectSSO.teams || projectSSO.teams.length === 0) {
|
||||
return Response.render(
|
||||
req,
|
||||
res,
|
||||
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
|
||||
{
|
||||
title: 'No teams added.',
|
||||
message:
|
||||
'No teams have been added to this SSO config. Please contact your admin and have default teams added.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for (const team of projectSSO.teams) {
|
||||
// add user to team
|
||||
let teamMember: TeamMember = new TeamMember();
|
||||
teamMember.projectId = new ObjectID(
|
||||
req.params['projectId'] as string
|
||||
);
|
||||
teamMember.userId = alreadySavedUser.id!;
|
||||
teamMember.hasAcceptedInvitation = true;
|
||||
teamMember.invitationAcceptedAt =
|
||||
OneUptimeDate.getCurrentDate();
|
||||
teamMember.teamId = team.id!;
|
||||
|
||||
teamMember = await TeamMemberService.create({
|
||||
data: teamMember,
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const projectId: ObjectID = new ObjectID(
|
||||
req.params['projectId'] as string
|
||||
);
|
||||
|
||||
const ssoToken: string = JSONWebToken.sign({
|
||||
data: {
|
||||
userId: alreadySavedUser.id!,
|
||||
projectId: projectId,
|
||||
name: alreadySavedUser.name!,
|
||||
email: email,
|
||||
isMasterAdmin: false,
|
||||
isGeneralLogin: false,
|
||||
},
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
|
||||
const oneUptimeToken: string = JSONWebToken.signUserLoginToken({
|
||||
tokenData: {
|
||||
userId: alreadySavedUser.id!,
|
||||
email: alreadySavedUser.email!,
|
||||
name: alreadySavedUser.name!,
|
||||
isMasterAdmin: alreadySavedUser.isMasterAdmin!,
|
||||
isGlobalLogin: false, // This is a general login without SSO. So, we will set this to false. This will give access to all the projects that dont require SSO.
|
||||
},
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
|
||||
// Set a cookie with token.
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(),
|
||||
oneUptimeToken,
|
||||
{
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
httpOnly: true,
|
||||
}
|
||||
);
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserSSOKey(projectId),
|
||||
ssoToken,
|
||||
{
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
httpOnly: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(
|
||||
alreadySavedUser.id!
|
||||
);
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
return Response.redirect(
|
||||
req,
|
||||
res,
|
||||
new URL(
|
||||
httpProtocol,
|
||||
host,
|
||||
new Route(DashboardRoute.toString()).addRoute(
|
||||
'/' + req.params['projectId']
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
Response.sendErrorResponse(req, res, new ServerException());
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -51,7 +51,7 @@ router.post(
|
||||
CookieUtil.getUserTokenKey(statusPageId)
|
||||
); // remove the cookie.
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ router.post(
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
|
||||
throw new BadDataException(
|
||||
@@ -320,7 +320,7 @@ router.post(
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -389,10 +389,12 @@ router.post(
|
||||
});
|
||||
|
||||
if (alreadySavedUser) {
|
||||
const token: string = JSONWebToken.sign(
|
||||
alreadySavedUser,
|
||||
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
|
||||
);
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
|
||||
@@ -25,6 +25,7 @@ import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivat
|
||||
import HashedString from 'Common/Types/HashedString';
|
||||
import StatusPageService from 'CommonServer/Services/StatusPageService';
|
||||
import CookieUtil from 'CommonServer/Utils/Cookie';
|
||||
import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig';
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
@@ -65,6 +66,8 @@ router.get(
|
||||
},
|
||||
select: {
|
||||
signOnURL: true,
|
||||
statusPageId: true,
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -89,7 +92,17 @@ router.get(
|
||||
);
|
||||
}
|
||||
|
||||
return Response.redirect(req, res, statusPageSSO.signOnURL);
|
||||
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
|
||||
acsUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/identity/status-page-idp-login/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`
|
||||
),
|
||||
signOnUrl: statusPageSSO.signOnURL!,
|
||||
issuerUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`
|
||||
),
|
||||
});
|
||||
|
||||
return Response.redirect(req, res, samlRequestUrl);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -270,10 +283,12 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
const token: string = JSONWebToken.sign(
|
||||
alreadySavedUser,
|
||||
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
|
||||
);
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
|
||||
@@ -5,20 +5,27 @@ import SsoAPI from './API/SSO';
|
||||
import ResellerAPI from './API/Reseller';
|
||||
import StatusPageSsoAPI from './API/StatusPageSSO';
|
||||
import StatusPageAuthenticationAPI from './API/StatusPageAuthentication';
|
||||
import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
const IdentityFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
const APP_NAME: string = 'api/identity';
|
||||
const APP_NAME: string = 'api/identity';
|
||||
|
||||
app.use([`/${APP_NAME}`, '/'], AuthenticationAPI);
|
||||
app.use([`/${APP_NAME}`, '/'], AuthenticationAPI);
|
||||
|
||||
app.use([`/${APP_NAME}`, '/'], ResellerAPI);
|
||||
app.use([`/${APP_NAME}`, '/'], ResellerAPI);
|
||||
|
||||
app.use([`/${APP_NAME}`, '/'], SsoAPI);
|
||||
app.use([`/${APP_NAME}`, '/'], SsoAPI);
|
||||
|
||||
app.use([`/${APP_NAME}`, '/'], StatusPageSsoAPI);
|
||||
app.use([`/${APP_NAME}`, '/'], StatusPageSsoAPI);
|
||||
|
||||
app.use(
|
||||
[`/${APP_NAME}/status-page`, '/status-page'],
|
||||
StatusPageAuthenticationAPI
|
||||
);
|
||||
app.use(
|
||||
[`/${APP_NAME}/status-page`, '/status-page'],
|
||||
StatusPageAuthenticationAPI
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default IdentityFeatureSet;
|
||||
|
||||
@@ -4,8 +4,34 @@ import Email from 'Common/Types/Email';
|
||||
import xmldom from 'xmldom';
|
||||
import xmlCrypto, { FileKeyInfo } from 'xml-crypto';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import Text from 'Common/Types/Text';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import zlib from 'zlib';
|
||||
|
||||
export default class SSOUtil {
|
||||
public static createSAMLRequestUrl(data: {
|
||||
acsUrl: URL;
|
||||
signOnUrl: URL;
|
||||
issuerUrl: URL;
|
||||
}): URL {
|
||||
const { acsUrl, signOnUrl } = data;
|
||||
|
||||
const samlRequest: string = `<samlp:AuthnRequest xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="${Text.generateRandomText(
|
||||
10
|
||||
).toUpperCase()}" Version="2.0" IssueInstant="${OneUptimeDate.getCurrentDate().toISOString()}" IsPassive="false" AssertionConsumerServiceURL="${acsUrl.toString()}" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ForceAuthn="false"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">${data.issuerUrl.toString()}</Issuer></samlp:AuthnRequest>`;
|
||||
|
||||
const deflated: Buffer = zlib.deflateRawSync(samlRequest);
|
||||
|
||||
const base64Encoded: string = deflated.toString('base64');
|
||||
|
||||
return URL.fromString(signOnUrl.toString()).addQueryParam(
|
||||
'SAMLRequest',
|
||||
base64Encoded,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public static isPayloadValid(payload: JSONObject): void {
|
||||
if (
|
||||
!payload['saml2p:Response'] &&
|
||||
@@ -18,11 +44,13 @@ export default class SSOUtil {
|
||||
payload =
|
||||
(payload['saml2p:Response'] as JSONObject) ||
|
||||
(payload['samlp:Response'] as JSONObject) ||
|
||||
(payload['samlp:Response'] as JSONObject);
|
||||
(payload['samlp:Response'] as JSONObject) ||
|
||||
(payload['Response'] as JSONObject);
|
||||
|
||||
const issuers: JSONArray =
|
||||
(payload['saml2:Issuer'] as JSONArray) ||
|
||||
(payload['saml:Issuer'] as JSONArray);
|
||||
(payload['saml:Issuer'] as JSONArray) ||
|
||||
(payload['Issuer'] as JSONArray);
|
||||
|
||||
if (issuers.length === 0) {
|
||||
throw new BadRequestException('Issuers not found');
|
||||
@@ -47,7 +75,8 @@ export default class SSOUtil {
|
||||
|
||||
const samlAssertion: JSONArray =
|
||||
(payload['saml2:Assertion'] as JSONArray) ||
|
||||
(payload['saml:Assertion'] as JSONArray);
|
||||
(payload['saml:Assertion'] as JSONArray) ||
|
||||
(payload['Assertion'] as JSONArray);
|
||||
|
||||
if (!samlAssertion || samlAssertion.length === 0) {
|
||||
throw new BadRequestException('SAML Assertion not found');
|
||||
@@ -55,7 +84,8 @@ export default class SSOUtil {
|
||||
|
||||
const samlSubject: JSONArray =
|
||||
((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray);
|
||||
((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)['Subject'] as JSONArray);
|
||||
|
||||
if (!samlSubject || samlSubject.length === 0) {
|
||||
throw new BadRequestException('SAML Subject not found');
|
||||
@@ -63,7 +93,8 @@ export default class SSOUtil {
|
||||
|
||||
const samlNameId: JSONArray =
|
||||
((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray);
|
||||
((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)['NameID'] as JSONArray);
|
||||
|
||||
if (!samlNameId || samlNameId.length === 0) {
|
||||
throw new BadRequestException('SAML NAME ID not found');
|
||||
@@ -120,11 +151,13 @@ export default class SSOUtil {
|
||||
|
||||
payload =
|
||||
(payload['saml2p:Response'] as JSONObject) ||
|
||||
(payload['samlp:Response'] as JSONObject);
|
||||
(payload['samlp:Response'] as JSONObject) ||
|
||||
(payload['Response'] as JSONObject);
|
||||
|
||||
const samlAssertion: JSONArray =
|
||||
(payload['saml2:Assertion'] as JSONArray) ||
|
||||
(payload['saml:Assertion'] as JSONArray);
|
||||
(payload['saml:Assertion'] as JSONArray) ||
|
||||
(payload['Assertion'] as JSONArray);
|
||||
|
||||
if (!samlAssertion || samlAssertion.length === 0) {
|
||||
throw new BadRequestException('SAML Assertion not found');
|
||||
@@ -132,7 +165,8 @@ export default class SSOUtil {
|
||||
|
||||
const samlSubject: JSONArray =
|
||||
((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray);
|
||||
((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)['Subject'] as JSONArray);
|
||||
|
||||
if (!samlSubject || samlSubject.length === 0) {
|
||||
throw new BadRequestException('SAML Subject not found');
|
||||
@@ -140,7 +174,8 @@ export default class SSOUtil {
|
||||
|
||||
const samlNameId: JSONArray =
|
||||
((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray);
|
||||
((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)['NameID'] as JSONArray);
|
||||
|
||||
if (!samlNameId || samlNameId.length === 0) {
|
||||
throw new BadRequestException('SAML NAME ID not found');
|
||||
@@ -160,11 +195,13 @@ export default class SSOUtil {
|
||||
|
||||
payload =
|
||||
(payload['saml2p:Response'] as JSONObject) ||
|
||||
(payload['samlp:Response'] as JSONObject);
|
||||
(payload['samlp:Response'] as JSONObject) ||
|
||||
(payload['Response'] as JSONObject);
|
||||
|
||||
const issuers: JSONArray =
|
||||
(payload['saml2:Issuer'] as JSONArray) ||
|
||||
(payload['saml:Issuer'] as JSONArray);
|
||||
(payload['saml:Issuer'] as JSONArray) ||
|
||||
(payload['Issuer'] as JSONArray);
|
||||
|
||||
if (issuers.length === 0) {
|
||||
throw new BadRequestException('Issuers not found');
|
||||
|
||||
@@ -32,7 +32,7 @@ router.post(
|
||||
customTwilioConfig: body['customTwilioConfig'] as any,
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -137,7 +137,7 @@ router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -44,7 +44,7 @@ router.post(
|
||||
(body['userOnCallLogTimelineId'] as ObjectID) || undefined,
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ router.post(
|
||||
}
|
||||
);
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -135,7 +135,7 @@ router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -98,7 +98,7 @@ router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -7,11 +7,18 @@ import SmsAPI from './API/SMS';
|
||||
import CallAPI from './API/Call';
|
||||
import SMTPConfigAPI from './API/SMTPConfig';
|
||||
import './Utils/Handlebars';
|
||||
import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
|
||||
const APP_NAME: string = 'api/notification';
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
const NotificationFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const APP_NAME: string = 'api/notification';
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.use([`/${APP_NAME}/email`, '/email'], MailAPI);
|
||||
app.use([`/${APP_NAME}/sms`, '/sms'], SmsAPI);
|
||||
app.use([`/${APP_NAME}/call`, '/call'], CallAPI);
|
||||
app.use([`/${APP_NAME}/smtp-config`, '/smtp-config'], SMTPConfigAPI);
|
||||
app.use([`/${APP_NAME}/email`, '/email'], MailAPI);
|
||||
app.use([`/${APP_NAME}/sms`, '/sms'], SmsAPI);
|
||||
app.use([`/${APP_NAME}/call`, '/call'], CallAPI);
|
||||
app.use([`/${APP_NAME}/smtp-config`, '/smtp-config'], SMTPConfigAPI);
|
||||
},
|
||||
};
|
||||
|
||||
export default NotificationFeatureSet;
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Resources Affected: " text=resourcesAffected }}
|
||||
{{> DetailBoxField title="Severity: " text=incidentSeverity }}
|
||||
{{> DetailBoxField title="Root Cause: " text=rootCause }}
|
||||
{{> DetailBoxField title="Root Cause: " text="" }}
|
||||
{{> DetailBoxField title="" text=rootCause }}
|
||||
{{> DetailBoxField title="Description: " text="" }}
|
||||
{{> DetailBoxField title="" text=incidentDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Resources Affected: " text=resourcesAffected }}
|
||||
{{> DetailBoxField title="Severity: " text=incidentSeverity }}
|
||||
{{> DetailBoxField title="Root Cause: " text=rootCause }}
|
||||
{{> DetailBoxField title="Root Cause: " text="" }}
|
||||
{{> DetailBoxField title="" text=rootCause }}
|
||||
{{> DetailBoxField title="Description: " text="" }}
|
||||
{{> DetailBoxField title="" text=incidentDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Monitor Name:" text=monitorName }}
|
||||
{{> DetailBoxField title="New Status: " text=currentStatus }}
|
||||
{{> DetailBoxField title="Root Cause: " text=rootCause }}
|
||||
{{> DetailBoxField title="Root Cause: " text="" }}
|
||||
{{> DetailBoxField title="" text=rootCause }}
|
||||
{{> DetailBoxField title="Status changed at: " text="" }}
|
||||
{{> DetailBoxField title="" text=statusChangedAt }}
|
||||
{{> DetailBoxField title="Description: " text="" }}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"manager":"/usr/src/app/FeatureSet/Workers/Utils/Greenlock/Manager.ts","configDir":"./greenlock.d"}
|
||||
@@ -0,0 +1,50 @@
|
||||
import DataMigrationBase from './DataMigrationBase';
|
||||
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
|
||||
import TelemetryService from 'Model/Models/TelemetryService';
|
||||
import TelemetryServiceService from 'CommonServer/Services/TelemetryServiceService';
|
||||
import { BrightColors } from 'Common/Types/BrandColors';
|
||||
import ArrayUtil from 'Common/Types/ArrayUtil';
|
||||
|
||||
export default class AddTelemetryServiceColor extends DataMigrationBase {
|
||||
public constructor() {
|
||||
super('AddTelemetryServiceColor');
|
||||
}
|
||||
|
||||
public override async migrate(): Promise<void> {
|
||||
// get all the users with email isVerified true.
|
||||
|
||||
const services: Array<TelemetryService> =
|
||||
await TelemetryServiceService.findBy({
|
||||
query: {},
|
||||
select: {
|
||||
_id: true,
|
||||
serviceColor: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const service of services) {
|
||||
if (!service.serviceColor) {
|
||||
(service.serviceColor =
|
||||
ArrayUtil.selectItemByRandom(BrightColors)),
|
||||
await TelemetryServiceService.updateOneById({
|
||||
id: service.id!,
|
||||
data: {
|
||||
serviceColor: service.serviceColor,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async rollback(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import DataMigrationBase from './DataMigrationBase';
|
||||
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
|
||||
import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import StatusPageDomain from 'Model/Models/StatusPageDomain';
|
||||
|
||||
export default class GenerateNewCertsForStatusPage extends DataMigrationBase {
|
||||
public constructor() {
|
||||
super('GenerateNewCertsForStatusPage');
|
||||
}
|
||||
|
||||
public override async migrate(): Promise<void> {
|
||||
// get all domains in greenlock certs.
|
||||
const statusPageDomains: Array<StatusPageDomain> =
|
||||
await StatusPageDomainService.findBy({
|
||||
query: {},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
});
|
||||
|
||||
// now order these domains
|
||||
|
||||
for (const statusPageDomain of statusPageDomains) {
|
||||
// get status page domain.
|
||||
|
||||
try {
|
||||
await StatusPageDomainService.orderCert(statusPageDomain);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async rollback(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@ import MigrateToMeteredSubscription from './MigrateToMeteredSubscription';
|
||||
import MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage from './MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage';
|
||||
import UpdateActiveMonitorCountToBillingProvider from './UpdateActiveMonitorCountToBillingProvider';
|
||||
import UpdateGlobalConfigFromEnv from './UpdateGlobalCongfigFromEnv';
|
||||
import AddTelemetryServiceColor from './AddTelemetryServiceColor';
|
||||
import MoveGreenlockCertsToAcmeCerts from './MoveGreenlockCertsToAcmeCerts';
|
||||
import GenerateNewCertsForStatusPage from './GenerateNewCertsForStatusPage';
|
||||
|
||||
// This is the order in which the migrations will be run. Add new migrations to the end of the array.
|
||||
|
||||
@@ -47,6 +50,9 @@ const DataMigrations: Array<DataMigrationBase> = [
|
||||
new ChangeLogSeverityColumnTypeFromTextToNumber(),
|
||||
new AddAttributeColumnToSpanAndLog(),
|
||||
new AddSecretKeyToIncomingRequestMonitor(),
|
||||
new AddTelemetryServiceColor(),
|
||||
new MoveGreenlockCertsToAcmeCerts(),
|
||||
new GenerateNewCertsForStatusPage(),
|
||||
];
|
||||
|
||||
export default DataMigrations;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import GreenlockCertificateService from 'CommonServer/Services/GreenlockCertificateService';
|
||||
import DataMigrationBase from './DataMigrationBase';
|
||||
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
|
||||
import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import GreenlockCertificate from 'Model/Models/GreenlockCertificate';
|
||||
import StatusPageDomain from 'Model/Models/StatusPageDomain';
|
||||
|
||||
export default class MoveGreenlockCertsToAcmeCerts extends DataMigrationBase {
|
||||
public constructor() {
|
||||
super('MoveGreenlockCertsToAcmeCerts');
|
||||
}
|
||||
|
||||
public override async migrate(): Promise<void> {
|
||||
const allDomains: Array<string> = [];
|
||||
|
||||
// get all domains in greenlock certs.
|
||||
const greenlockCerts: GreenlockCertificate[] =
|
||||
await GreenlockCertificateService.findBy({
|
||||
query: {},
|
||||
select: {
|
||||
key: true,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_MAX,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const greenlockCert of greenlockCerts) {
|
||||
if (!greenlockCert.key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (allDomains.includes(greenlockCert.key!)) {
|
||||
// this domain already exists in acme certs.
|
||||
continue;
|
||||
}
|
||||
|
||||
allDomains.push(greenlockCert.key!);
|
||||
}
|
||||
|
||||
// now order these domains
|
||||
|
||||
for (const domain of allDomains) {
|
||||
// get status page domain.
|
||||
|
||||
const statusPageDomain: StatusPageDomain | null =
|
||||
await StatusPageDomainService.findOneBy({
|
||||
query: {
|
||||
fullDomain: domain,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!statusPageDomain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await StatusPageDomainService.orderCert(statusPageDomain);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async rollback(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -34,10 +34,8 @@ import './Jobs/TelemetryService/DeleteOldData';
|
||||
import './Jobs/ScheduledMaintenancePublicNote/SendNotificationToSubscribers';
|
||||
|
||||
// Certs Routers
|
||||
import StatusPageCerts from './Jobs/StatusPageCerts/StatusPageCerts';
|
||||
import './Jobs/StatusPageCerts/StatusPageCerts';
|
||||
|
||||
// Express
|
||||
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
|
||||
import JobDictionary from './Utils/JobDictionary';
|
||||
|
||||
// Monitor Owners
|
||||
@@ -84,13 +82,6 @@ import './Jobs/MonitorMetrics/MonitorMetricsByMinute';
|
||||
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
|
||||
const APP_NAME: string = 'api/workers';
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
//cert routes.
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, StatusPageCerts);
|
||||
|
||||
const WorkersFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
try {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,456 +1,51 @@
|
||||
import { EVERY_HOUR, EVERY_MINUTE } from 'Common/Utils/CronTime';
|
||||
import { EVERY_FIFTEEN_MINUTE } from 'Common/Utils/CronTime';
|
||||
import RunCron from '../../Utils/Cron';
|
||||
import { IsDevelopment } from 'CommonServer/EnvironmentConfig';
|
||||
import StatusPageDomain from 'Model/Models/StatusPageDomain';
|
||||
import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService';
|
||||
// @ts-ignore
|
||||
import Greenlock from 'greenlock';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const greenlock: any = Greenlock.create({
|
||||
configFile: '/usr/src/app/FeatureSet/Workers/greenlockrc',
|
||||
packageRoot: `/usr/src/app`,
|
||||
manager: '/usr/src/app/FeatureSet/Workers/Utils/Greenlock/Manager.ts',
|
||||
approveDomains: async (opts: any) => {
|
||||
const domain: StatusPageDomain | null =
|
||||
await StatusPageDomainService.findOneBy({
|
||||
query: {
|
||||
fullDomain: opts.domain,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
fullDomain: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!domain) {
|
||||
throw new BadDataException(
|
||||
`Domain ${opts.domain} does not exist in StatusPageDomain`
|
||||
);
|
||||
}
|
||||
|
||||
return opts; // or Promise.resolve(opts);
|
||||
},
|
||||
store: {
|
||||
module: '/usr/src/app/FeatureSet/Workers/Utils/Greenlock/Store.ts',
|
||||
},
|
||||
// Staging for testing environments
|
||||
// staging: IsDevelopment,
|
||||
|
||||
// This should be the contact who receives critical bug and security notifications
|
||||
// Optionally, you may receive other (very few) updates, such as important new features
|
||||
maintainerEmail: 'lets-encrypt@oneuptime.com',
|
||||
|
||||
// for an RFC 8555 / RFC 7231 ACME client user agent
|
||||
packageAgent: 'oneuptime/1.0.0',
|
||||
|
||||
notify: function (event: string, details: any) {
|
||||
if ('error' === event) {
|
||||
logger.error('Greenlock Notify: ' + event);
|
||||
logger.error(details);
|
||||
}
|
||||
logger.info('Greenlock Notify: ' + event);
|
||||
logger.info(details);
|
||||
},
|
||||
|
||||
agreeToTerms: true,
|
||||
challenges: {
|
||||
'http-01': {
|
||||
module: '/usr/src/app/FeatureSet/Workers/Utils/Greenlock/HttpChallenge.ts',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Delete
|
||||
router.delete(
|
||||
`/certs`,
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
if (!body['domain']) {
|
||||
throw new BadDataException('Domain is required');
|
||||
}
|
||||
|
||||
await greenlock.remove({
|
||||
subject: body['domain'],
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Create
|
||||
router.post(
|
||||
`/certs`,
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
if (!body['domain']) {
|
||||
throw new BadDataException('Domain is required');
|
||||
}
|
||||
|
||||
await greenlock.add({
|
||||
subject: body['domain'],
|
||||
altnames: [body['domain']],
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Create
|
||||
router.get(
|
||||
`/certs`,
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
if (!body['domain']) {
|
||||
throw new BadDataException('Domain is required');
|
||||
}
|
||||
|
||||
const site: JSONObject = await greenlock.get({
|
||||
servername: body['domain'] as string,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, site);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
RunCron(
|
||||
'StatusPageCerts:RenewCerts',
|
||||
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
|
||||
{
|
||||
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE,
|
||||
runOnStartup: true,
|
||||
},
|
||||
async () => {
|
||||
logger.info('Renewing Certs...');
|
||||
await greenlock.renew();
|
||||
await StatusPageDomainService.renewCertsWhichAreExpiringSoon();
|
||||
logger.info('Renew Completed...');
|
||||
}
|
||||
);
|
||||
|
||||
RunCron(
|
||||
'StatusPageCerts:OrderCerts',
|
||||
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
|
||||
async () => {
|
||||
// Fetch all domains where certs are added to greenlock.
|
||||
|
||||
const domains: Array<StatusPageDomain> =
|
||||
await StatusPageDomainService.findBy({
|
||||
query: {
|
||||
isAddedToGreenlock: true,
|
||||
isSslProvisioned: false,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
greenlockConfig: true,
|
||||
fullDomain: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info(`Certificates to Order: ${domains.length}`);
|
||||
|
||||
for (const domain of domains) {
|
||||
logger.info(
|
||||
`StatusPageCerts:OrderCerts - Ordering Certificate ${domain.fullDomain}`
|
||||
);
|
||||
|
||||
greenlock.order(domain.greenlockConfig).catch((err: any) => {
|
||||
logger.error(
|
||||
`StatusPageCerts:OrderCerts - Failed for domain ${domain.fullDomain}`
|
||||
);
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
RunCron(
|
||||
'StatusPageCerts:AddCerts',
|
||||
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
|
||||
async () => {
|
||||
const domains: Array<StatusPageDomain> =
|
||||
await StatusPageDomainService.findBy({
|
||||
query: {
|
||||
isAddedToGreenlock: false,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
fullDomain: true,
|
||||
cnameVerificationToken: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const domain of domains) {
|
||||
logger.info(
|
||||
`StatusPageCerts:AddCerts - Checking CNAME ${domain.fullDomain}`
|
||||
);
|
||||
|
||||
// Check CNAME validation and if that fails. Remove certs from Greenlock.
|
||||
const isValid: boolean = await checkCnameValidation(
|
||||
domain.fullDomain!,
|
||||
domain.cnameVerificationToken!
|
||||
);
|
||||
|
||||
if (isValid) {
|
||||
logger.info(
|
||||
`StatusPageCerts:AddCerts - CNAME for ${domain.fullDomain} is valid. Adding domain to greenlock.`
|
||||
);
|
||||
|
||||
await StatusPageDomainService.updateOneById({
|
||||
id: domain.id!,
|
||||
data: {
|
||||
isCnameVerified: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
await greenlock.add({
|
||||
subject: domain.fullDomain,
|
||||
altnames: [domain.fullDomain],
|
||||
});
|
||||
|
||||
await StatusPageDomainService.updateOneById({
|
||||
id: domain.id!,
|
||||
data: {
|
||||
isAddedToGreenlock: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`StatusPageCerts:AddCerts - ${domain.fullDomain} added to greenlock.`
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`StatusPageCerts:AddCerts - CNAME for ${domain.fullDomain} is invalid. Removing cert`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
RunCron(
|
||||
'StatusPageCerts:RemoveCerts',
|
||||
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
|
||||
async () => {
|
||||
// Fetch all domains where certs are added to greenlock.
|
||||
|
||||
const domains: Array<StatusPageDomain> =
|
||||
await StatusPageDomainService.findBy({
|
||||
query: {
|
||||
isAddedToGreenlock: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
fullDomain: true,
|
||||
cnameVerificationToken: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const domain of domains) {
|
||||
logger.info(
|
||||
`StatusPageCerts:RemoveCerts - Checking CNAME ${domain.fullDomain}`
|
||||
);
|
||||
|
||||
// Check CNAME validation and if that fails. Remove certs from Greenlock.
|
||||
const isValid: boolean = await checkCnameValidation(
|
||||
domain.fullDomain!,
|
||||
domain.cnameVerificationToken!
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
logger.info(
|
||||
`StatusPageCerts:RemoveCerts - CNAME for ${domain.fullDomain} is invalid. Removing domain from greenlock.`
|
||||
);
|
||||
|
||||
await greenlock.remove({
|
||||
subject: domain.fullDomain,
|
||||
});
|
||||
|
||||
await StatusPageDomainService.updateOneById({
|
||||
id: domain.id!,
|
||||
data: {
|
||||
isAddedToGreenlock: false,
|
||||
isCnameVerified: false,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`StatusPageCerts:RemoveCerts - ${domain.fullDomain} removed from greenlock.`
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`StatusPageCerts:RemoveCerts - CNAME for ${domain.fullDomain} is valid`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
RunCron(
|
||||
'StatusPageCerts:CheckSslProvisioningStatus',
|
||||
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
|
||||
{
|
||||
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE,
|
||||
runOnStartup: true,
|
||||
},
|
||||
async () => {
|
||||
// Fetch all domains where certs are added to greenlock.
|
||||
|
||||
const domains: Array<StatusPageDomain> =
|
||||
await StatusPageDomainService.findBy({
|
||||
query: {
|
||||
isAddedToGreenlock: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
fullDomain: true,
|
||||
cnameVerificationToken: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const domain of domains) {
|
||||
logger.info(
|
||||
`StatusPageCerts:RemoveCerts - Checking CNAME ${domain.fullDomain}`
|
||||
);
|
||||
|
||||
// Check CNAME validation and if that fails. Remove certs from Greenlock.
|
||||
const isValid: boolean = await isSslProvisioned(
|
||||
domain.fullDomain!,
|
||||
domain.cnameVerificationToken!
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
await StatusPageDomainService.updateOneById({
|
||||
id: domain.id!,
|
||||
data: {
|
||||
isSslProvisioned: false,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await StatusPageDomainService.updateOneById({
|
||||
id: domain.id!,
|
||||
data: {
|
||||
isSslProvisioned: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
await StatusPageDomainService.updateSslProvisioningStatusForAllDomains();
|
||||
}
|
||||
);
|
||||
|
||||
type CheckCnameValidationFunction = (
|
||||
fulldomain: string,
|
||||
token: string
|
||||
) => Promise<boolean>;
|
||||
|
||||
const checkCnameValidation: CheckCnameValidationFunction = async (
|
||||
fulldomain: string,
|
||||
token: string
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const result: AxiosResponse = await axios.get(
|
||||
'http://' +
|
||||
fulldomain +
|
||||
'/status-page-api/cname-verification/' +
|
||||
token
|
||||
);
|
||||
|
||||
if (result.status === 200) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
logger.info('Failed checking for CNAME ' + fulldomain);
|
||||
logger.info('Token: ' + token);
|
||||
logger.info(err);
|
||||
return false;
|
||||
RunCron(
|
||||
'StatusPageCerts:OrderSSL',
|
||||
{
|
||||
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE,
|
||||
runOnStartup: true,
|
||||
},
|
||||
async () => {
|
||||
await StatusPageDomainService.orderSSLForDomainsWhichAreNotOrderedYet();
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
type IsSSLProvisionedFunction = (
|
||||
fulldomain: string,
|
||||
token: string
|
||||
) => Promise<boolean>;
|
||||
|
||||
const isSslProvisioned: IsSSLProvisionedFunction = async (
|
||||
fulldomain: string,
|
||||
token: string
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const result: AxiosResponse = await axios.get(
|
||||
'https://' +
|
||||
fulldomain +
|
||||
'/status-page-api/cname-verification/' +
|
||||
token
|
||||
);
|
||||
|
||||
if (result.status === 200) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
return false;
|
||||
RunCron(
|
||||
'StatusPageCerts:VerifyCnameWhoseCnameisNotVerified',
|
||||
{
|
||||
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE,
|
||||
runOnStartup: true,
|
||||
},
|
||||
async () => {
|
||||
await StatusPageDomainService.verifyCnameWhoseCnameisNotVerified();
|
||||
}
|
||||
};
|
||||
|
||||
export default router;
|
||||
);
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import GreenlockChallenge from 'Model/Models/GreenlockChallenge';
|
||||
import GreenlockChallengeService from 'CommonServer/Services/GreenlockChallengeService';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
|
||||
// because greenlock package expects module.exports.
|
||||
module.exports = {
|
||||
create: (_opts: any) => {
|
||||
return {
|
||||
init: async (): Promise<null> => {
|
||||
logger.info('Greenlock HTTP Challenge Init');
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
|
||||
set: async (data: any): Promise<null> => {
|
||||
logger.info('Greenlock HTTP Challenge Set');
|
||||
logger.info(data);
|
||||
|
||||
const ch: any = data.challenge;
|
||||
const key: string = ch.identifier.value + '#' + ch.token;
|
||||
const token: string = ch.token;
|
||||
|
||||
let challenge: GreenlockChallenge | null =
|
||||
await GreenlockChallengeService.findOneBy({
|
||||
query: {
|
||||
key: key,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!challenge) {
|
||||
challenge = new GreenlockChallenge();
|
||||
challenge.key = key;
|
||||
challenge.token = token;
|
||||
challenge.challenge = ch.keyAuthorization;
|
||||
|
||||
await GreenlockChallengeService.create({
|
||||
data: challenge,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
challenge.challenge = ch.keyAuthorization;
|
||||
challenge.token = token;
|
||||
await GreenlockChallengeService.updateOneById({
|
||||
id: challenge.id!,
|
||||
data: challenge,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
return null;
|
||||
},
|
||||
|
||||
get: async (data: any): Promise<null | any> => {
|
||||
logger.info('Greenlock HTTP Challenge Get');
|
||||
logger.info(data);
|
||||
|
||||
const ch: any = data.challenge;
|
||||
const key: string = ch.identifier.value + '#' + ch.token;
|
||||
|
||||
const challenge: GreenlockChallenge | null =
|
||||
await GreenlockChallengeService.findOneBy({
|
||||
query: {
|
||||
key: key,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!challenge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { keyAuthorization: challenge.challenge };
|
||||
},
|
||||
|
||||
remove: async (data: any): Promise<null> => {
|
||||
logger.info('Greenlock HTTP Challenge Remove');
|
||||
logger.info(data);
|
||||
|
||||
const ch: any = data.challenge;
|
||||
const key: string = ch.identifier.value + '#' + ch.token;
|
||||
await GreenlockChallengeService.deleteOneBy({
|
||||
query: {
|
||||
key: key,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,190 +0,0 @@
|
||||
// Docs: https://git.rootprojects.org/root/greenlock-manager.js
|
||||
|
||||
import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService';
|
||||
import StatusPageDomain from 'Model/Models/StatusPageDomain';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import GreenlockCertificate from 'Model/Models/GreenlockCertificate';
|
||||
import GreenlockCertificateService from 'CommonServer/Services/GreenlockCertificateService';
|
||||
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import JSONFunctions from 'Common/Types/JSONFunctions';
|
||||
|
||||
// because greenlock package expects module.exports.
|
||||
module.exports = {
|
||||
create: () => {
|
||||
return {
|
||||
// Get
|
||||
get: async ({
|
||||
servername,
|
||||
}: {
|
||||
servername: string;
|
||||
}): Promise<JSONObject | undefined> => {
|
||||
// Required: find the certificate with the subject of `servername`
|
||||
// Optional (multi-domain certs support): find a certificate with `servername` as an altname
|
||||
// Optional (wildcard support): find a certificate with `wildname` as an altname
|
||||
|
||||
// { subject, altnames, renewAt, deletedAt, challenges, ... }
|
||||
logger.info('Greenlock Manager Get');
|
||||
logger.info(servername);
|
||||
const domain: StatusPageDomain | null =
|
||||
await StatusPageDomainService.findOneBy({
|
||||
query: {
|
||||
fullDomain: servername,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
greenlockConfig: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!domain || !domain.greenlockConfig) {
|
||||
logger.info(
|
||||
'Greenlock Manager GET ' +
|
||||
servername +
|
||||
' - No domain found.'
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
logger.info('Greenlock Manager GET ' + servername + ' RESULT');
|
||||
logger.info(domain.greenlockConfig);
|
||||
|
||||
return domain.greenlockConfig;
|
||||
},
|
||||
|
||||
find: async function (args: any) {
|
||||
logger.info('Manager Find: ');
|
||||
logger.info(JSON.stringify(args, null, 2));
|
||||
|
||||
// { subject, servernames, altnames, renewBefore }
|
||||
|
||||
// i.e. find certs more than 30 days old
|
||||
args.issuedBefore = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
||||
// i.e. find certs more that will expire in less than 45 days
|
||||
//args.expiresBefore = Date.now() + 45 * 24 * 60 * 60 * 1000;
|
||||
const issuedBefore: any = args.issuedBefore || Infinity;
|
||||
const expiresBefore: any = args.expiresBefore || Infinity; //Date.now() + 21 * 24 * 60 * 60 * 1000;
|
||||
const renewBefore: any = args.renewBefore || Infinity; //Date.now() + 21 * 24 * 60 * 60 * 1000;
|
||||
// if there's anything to match, only return matches
|
||||
// if there's nothing to match, return everything
|
||||
const nameKeys: Array<string> = ['subject', 'altnames'];
|
||||
const matchAll: boolean = !nameKeys.some((k: string) => {
|
||||
return k in args;
|
||||
});
|
||||
|
||||
const querynames: string = (args.altnames || []).slice(0);
|
||||
|
||||
const greenlockCertificates: Array<GreenlockCertificate> =
|
||||
await GreenlockCertificateService.findBy({
|
||||
query: {
|
||||
isKeyPair: false,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
select: {
|
||||
blob: true,
|
||||
key: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const sites: Array<JSONObject> = greenlockCertificates
|
||||
.filter((i: GreenlockCertificate) => {
|
||||
return i.blob;
|
||||
})
|
||||
.map((i: GreenlockCertificate) => {
|
||||
return JSONFunctions.parseJSONObject(i.blob!);
|
||||
})
|
||||
.filter((site: any) => {
|
||||
logger.info('Filter Site: ');
|
||||
logger.info(site);
|
||||
if (site.deletedAt) {
|
||||
logger.info('Filter Site: DeletedAt');
|
||||
return false;
|
||||
}
|
||||
if (site.expiresAt >= expiresBefore) {
|
||||
logger.info('Filter Site: expiresAt');
|
||||
return false;
|
||||
}
|
||||
if (site.issuedAt >= issuedBefore) {
|
||||
logger.info('Filter Site: issuedAt');
|
||||
return false;
|
||||
}
|
||||
if (site.renewAt >= renewBefore) {
|
||||
logger.info('Filter Site: renewAt');
|
||||
return false;
|
||||
}
|
||||
|
||||
// after attribute filtering, before cert filtering
|
||||
if (matchAll) {
|
||||
logger.info('Filter Site: MatchAll');
|
||||
return true;
|
||||
}
|
||||
|
||||
// if subject is specified, don't return anything else
|
||||
if (site.subject === args.subject) {
|
||||
logger.info('Filter Site: Subject');
|
||||
return true;
|
||||
}
|
||||
|
||||
// altnames, servername, and wildname all get rolled into one
|
||||
return site.altnames.some((altname: any) => {
|
||||
return querynames.includes(altname);
|
||||
});
|
||||
});
|
||||
|
||||
logger.info('Sites: ');
|
||||
logger.info(sites);
|
||||
return sites;
|
||||
},
|
||||
|
||||
// Set
|
||||
set: async (opts: any) => {
|
||||
logger.info('Greenlock Manager Set');
|
||||
logger.info(opts);
|
||||
|
||||
// { subject, altnames, renewAt, deletedAt }
|
||||
// Required: updated `renewAt` and `deletedAt` for certificate matching `subject`
|
||||
|
||||
if (!opts.subject) {
|
||||
return;
|
||||
}
|
||||
|
||||
const domain: StatusPageDomain | null =
|
||||
await StatusPageDomainService.findOneBy({
|
||||
query: {
|
||||
fullDomain: opts['subject'] as string,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
greenlockConfig: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
await StatusPageDomainService.updateOneById({
|
||||
id: domain.id!,
|
||||
data: {
|
||||
greenlockConfig: opts,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,220 +0,0 @@
|
||||
/// https://git.rootprojects.org/root/greenlock-store-memory.js/src/branch/master/index.js
|
||||
|
||||
import GreenlockCertificate from 'Model/Models/GreenlockCertificate';
|
||||
import GreenlockCertificateService from 'CommonServer/Services/GreenlockCertificateService';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import JSONFunctions from 'Common/Types/JSONFunctions';
|
||||
|
||||
type SaveCertificateFunction = (
|
||||
id: string,
|
||||
blob: string,
|
||||
isKeyPair: boolean
|
||||
) => Promise<null>;
|
||||
|
||||
type GetCertificateFunction = (
|
||||
id: string,
|
||||
isKeyPair: boolean
|
||||
) => Promise<null | string>;
|
||||
|
||||
module.exports = {
|
||||
create: (_opts: any) => {
|
||||
const saveCertificate: SaveCertificateFunction = async (
|
||||
id: string,
|
||||
blob: string,
|
||||
isKeyPair: boolean
|
||||
): Promise<null> => {
|
||||
logger.info('Save Certificates: ' + id);
|
||||
|
||||
let cert: GreenlockCertificate | null =
|
||||
await GreenlockCertificateService.findOneBy({
|
||||
query: {
|
||||
key: id,
|
||||
isKeyPair: isKeyPair,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
isKeyPair: isKeyPair,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!cert) {
|
||||
cert = new GreenlockCertificate();
|
||||
cert.key = id;
|
||||
cert.blob = blob;
|
||||
cert.isKeyPair = isKeyPair;
|
||||
|
||||
await GreenlockCertificateService.create({
|
||||
data: cert,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
cert.blob = blob;
|
||||
cert.isKeyPair = isKeyPair;
|
||||
await GreenlockCertificateService.updateOneById({
|
||||
id: cert.id!,
|
||||
data: cert,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
return null;
|
||||
};
|
||||
|
||||
const getCertificate: GetCertificateFunction = async (
|
||||
id: string,
|
||||
isKeyPair: boolean
|
||||
): Promise<null | string> => {
|
||||
logger.info('Get Certificate - ' + id);
|
||||
|
||||
const cert: GreenlockCertificate | null =
|
||||
await GreenlockCertificateService.findOneBy({
|
||||
query: {
|
||||
key: id,
|
||||
isKeyPair: isKeyPair,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
blob: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!cert || !cert.blob) {
|
||||
logger.info('Certificate not found');
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.info('Certificate found');
|
||||
return cert.blob;
|
||||
};
|
||||
|
||||
type SaveKeyPairFunction = (id: string, blob: string) => Promise<null>;
|
||||
|
||||
const saveKeypair: SaveKeyPairFunction = async (
|
||||
id: string,
|
||||
blob: string
|
||||
): Promise<null> => {
|
||||
logger.info('Save Keypair: ' + id);
|
||||
return await saveCertificate(id, blob, true);
|
||||
};
|
||||
|
||||
type GetKeypairFunction = (id: string) => Promise<null | string>;
|
||||
|
||||
const getKeypair: GetKeypairFunction = async (
|
||||
id: string
|
||||
): Promise<null | string> => {
|
||||
logger.info('Get Keypair: ' + id);
|
||||
return await getCertificate(id, true);
|
||||
};
|
||||
|
||||
return {
|
||||
accounts: {
|
||||
// Whenever a new keypair is used to successfully create an account, we need to save its keypair
|
||||
setKeypair: async (opts: any): Promise<null> => {
|
||||
logger.info('Accounts Set Keypair: ');
|
||||
logger.info(JSON.stringify(opts, null, 2));
|
||||
const id: string =
|
||||
opts.account.id || opts.email || 'default';
|
||||
const keypair: any = opts.keypair;
|
||||
|
||||
return await saveKeypair(id, JSON.stringify(keypair)); // Must return or Promise `null` instead of `undefined`
|
||||
},
|
||||
// We need a way to retrieve a prior account's keypair for renewals and additional ACME certificate "orders"
|
||||
checkKeypair: async (opts: any): Promise<any | null> => {
|
||||
logger.info('Accounts Check Keypair: ');
|
||||
logger.info(JSON.stringify(opts, null, 2));
|
||||
const id: string =
|
||||
opts.account.id || opts.email || 'default';
|
||||
const keyblob: any = await getKeypair(id);
|
||||
|
||||
if (!keyblob) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSONFunctions.parse(keyblob);
|
||||
},
|
||||
},
|
||||
|
||||
certificates: {
|
||||
setKeypair: async (opts: any): Promise<null> => {
|
||||
logger.info('Certificates Set Keypair: ');
|
||||
logger.info(JSON.stringify(opts, null, 2));
|
||||
// The ID is a string that doesn't clash between accounts and certificates.
|
||||
// That's all you need to know... unless you're doing something special (in which case you're on your own).
|
||||
const id: string =
|
||||
opts.certificate.kid ||
|
||||
opts.certificate.id ||
|
||||
opts.subject;
|
||||
const keypair: any = opts.keypair;
|
||||
|
||||
return await saveKeypair(id, JSON.stringify(keypair)); // Must return or Promise `null` instead of `undefined`
|
||||
// Side Note: you can use the "keypairs" package to convert between
|
||||
// public and private for jwk and pem, as well as convert JWK <-> PEM
|
||||
},
|
||||
|
||||
// You won't be able to use a certificate without it's private key, gotta save it
|
||||
checkKeypair: async (opts: any): Promise<any | null> => {
|
||||
logger.info('Certificates Check Keypair: ');
|
||||
logger.info(JSON.stringify(opts, null, 2));
|
||||
const id: string =
|
||||
opts.certificate.kid ||
|
||||
opts.certificate.id ||
|
||||
opts.subject;
|
||||
const keyblob: any = await getKeypair(id);
|
||||
|
||||
if (!keyblob) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSONFunctions.parse(keyblob);
|
||||
},
|
||||
|
||||
// And you'll also need to save certificates. You may find the metadata useful to save
|
||||
// (perhaps to delete expired keys), but the same information can also be redireved from
|
||||
// the key using the "cert-info" package.
|
||||
set: async (opts: any): Promise<null> => {
|
||||
logger.info('Certificates Set: ');
|
||||
logger.info(JSON.stringify(opts, null, 2));
|
||||
const id: string = opts.certificate.id || opts.subject;
|
||||
const pems: any = opts.pems;
|
||||
|
||||
return await saveCertificate(
|
||||
id,
|
||||
JSON.stringify(pems),
|
||||
false
|
||||
); // Must return or Promise `null` instead of `undefined`
|
||||
},
|
||||
|
||||
// This is actually the first thing to be called after approveDomins(),
|
||||
// but it's easiest to implement last since it's not useful until there
|
||||
// are certs that can actually be loaded from storage.
|
||||
check: async (opts: any): Promise<null | any> => {
|
||||
logger.info('Certificates Check: ');
|
||||
logger.info(JSON.stringify(opts, null, 2));
|
||||
const id: string = opts.certificate?.id || opts.subject;
|
||||
const certblob: any = await getCertificate(id, false);
|
||||
|
||||
if (!certblob) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSONFunctions.parse(certblob);
|
||||
},
|
||||
},
|
||||
|
||||
options: {},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -13,11 +13,14 @@ export default class ComponentCodeAPI {
|
||||
|
||||
public constructor() {
|
||||
this.router = Express.getRouter();
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
// init all component code.
|
||||
/// Get all the components.
|
||||
for (const key in Components) {
|
||||
const ComponentCode: ComponentCode | undefined = Components[key];
|
||||
|
||||
if (ComponentCode instanceof TriggerCode) {
|
||||
const instance: TriggerCode = ComponentCode;
|
||||
instance
|
||||
|
||||
@@ -5,7 +5,7 @@ import Express, {
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import ManualAPI from './API/Manual';
|
||||
import ComponentCode from './API/ComponentCode';
|
||||
import ComponentCodeAPI from './API/ComponentCode';
|
||||
import { QueueJob, QueueName } from 'CommonServer/Infrastructure/Queue';
|
||||
import QueueWorker from 'CommonServer/Infrastructure/QueueWorker';
|
||||
import RunWorkflow from './Services/RunWorkflow';
|
||||
@@ -16,27 +16,30 @@ import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
|
||||
const APP_NAME: string = 'api/workflow';
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.use(`/${APP_NAME}/manual`, new ManualAPI().router);
|
||||
|
||||
app.use(`/${APP_NAME}`, new WorkflowAPI().router);
|
||||
|
||||
app.get(
|
||||
`/${APP_NAME}/docs/:componentName`,
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.sendFile(
|
||||
'/usr/src/app/FeatureSet/Workflow/Docs/ComponentDocumentation/' +
|
||||
req.params['componentName']
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
app.use(`/${APP_NAME}`, new ComponentCode().router);
|
||||
|
||||
const WorkflowFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
try {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.use(`/${APP_NAME}/manual`, new ManualAPI().router);
|
||||
|
||||
app.use(`/${APP_NAME}`, new WorkflowAPI().router);
|
||||
|
||||
app.get(
|
||||
`/${APP_NAME}/docs/:componentName`,
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.sendFile(
|
||||
'/usr/src/app/FeatureSet/Workflow/Docs/ComponentDocumentation/' +
|
||||
req.params['componentName']
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const componentCodeAPI: ComponentCodeAPI = new ComponentCodeAPI();
|
||||
componentCodeAPI.init();
|
||||
|
||||
app.use(`/${APP_NAME}`, componentCodeAPI.router);
|
||||
|
||||
// Job process.
|
||||
QueueWorker.getWorker(
|
||||
QueueName.Workflow,
|
||||
|
||||
49
App/Index.ts
49
App/Index.ts
@@ -9,23 +9,37 @@ import { PostgresAppInstance } from 'CommonServer/Infrastructure/PostgresDatabas
|
||||
import { ClickhouseAppInstance } from 'CommonServer/Infrastructure/ClickhouseDatabase';
|
||||
import Realtime from 'CommonServer/Utils/Realtime';
|
||||
|
||||
// import featuresets.
|
||||
import './FeatureSet/Identity/Index';
|
||||
import './FeatureSet/Notification/Index';
|
||||
import './FeatureSet/Docs/Index';
|
||||
import './FeatureSet/BaseAPI/Index';
|
||||
import './FeatureSet/ApiReference/Index';
|
||||
// import FeatureSets.
|
||||
import IdentityRoutes from './FeatureSet/Identity/Index';
|
||||
import NotificationRoutes from './FeatureSet/Notification/Index';
|
||||
import DocsRoutes from './FeatureSet/Docs/Index';
|
||||
import BaseAPIRoutes from './FeatureSet/BaseAPI/Index';
|
||||
import APIReferenceRoutes from './FeatureSet/ApiReference/Index';
|
||||
import Workers from './FeatureSet/Workers/Index';
|
||||
import Workflow from './FeatureSet/Workflow/Index';
|
||||
import HomeRoutes from './FeatureSet/Home/Index';
|
||||
import InfrastructureStatus from 'CommonServer/Infrastructure/Status';
|
||||
|
||||
// home should be in the end.
|
||||
import './FeatureSet/Home/Index';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
const statusCheck: PromiseVoidFunction = async (): Promise<void> => {
|
||||
return await InfrastructureStatus.checkStatus({
|
||||
checkClickhouseStatus: true,
|
||||
checkPostgresStatus: true,
|
||||
checkRedisStatus: true,
|
||||
});
|
||||
};
|
||||
|
||||
// init the app
|
||||
await App(process.env['SERVICE_NAME'] || 'app');
|
||||
await App.init({
|
||||
appName: process.env['SERVICE_NAME'] || 'app',
|
||||
statusOptions: {
|
||||
liveCheck: statusCheck,
|
||||
readyCheck: statusCheck,
|
||||
},
|
||||
});
|
||||
|
||||
// connect to the database.
|
||||
await PostgresAppInstance.connect(
|
||||
@@ -39,13 +53,22 @@ const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
ClickhouseAppInstance.getDatasourceOptions()
|
||||
);
|
||||
|
||||
Realtime.init();
|
||||
await Realtime.init();
|
||||
|
||||
// init workers
|
||||
// init featuresets
|
||||
await IdentityRoutes.init();
|
||||
await NotificationRoutes.init();
|
||||
await DocsRoutes.init();
|
||||
await BaseAPIRoutes.init();
|
||||
await APIReferenceRoutes.init();
|
||||
await Workers.init();
|
||||
|
||||
// init workflow
|
||||
await Workflow.init();
|
||||
|
||||
// home should be in the end because it has the catch all route.
|
||||
await HomeRoutes.init();
|
||||
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error('App Init Failed:');
|
||||
logger.error(err);
|
||||
|
||||
1
App/greenlock/README.md
Normal file
1
App/greenlock/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# This directory is for the .greenlockrc file
|
||||
231
App/package-lock.json
generated
231
App/package-lock.json
generated
@@ -1,19 +1,18 @@
|
||||
{
|
||||
"name": "app",
|
||||
"name": "@oneuptime/app",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "app",
|
||||
"name": "@oneuptime/app",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "^8.1.0",
|
||||
"Common": "file:../Common",
|
||||
"CommonServer": "file:../CommonServer",
|
||||
"ejs": "^3.1.9",
|
||||
"greenlock": "^4.0.4",
|
||||
"handlebars": "^4.7.8",
|
||||
"marked": "^12.0.0",
|
||||
"Model": "file:../Model",
|
||||
@@ -36,19 +35,19 @@
|
||||
}
|
||||
},
|
||||
"../Common": {
|
||||
"name": "common",
|
||||
"name": "@oneuptime/common",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"axios": "^1.6.7",
|
||||
"axios": "^1.6.8",
|
||||
"crypto-js": "^4.1.1",
|
||||
"json5": "^2.2.3",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"posthog-js": "^1.105.4",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"posthog-js": "^1.116.6",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"slugify": "^1.6.5",
|
||||
"typeorm": "^0.3.20",
|
||||
"uuid": "^8.3.2"
|
||||
@@ -62,49 +61,46 @@
|
||||
}
|
||||
},
|
||||
"../CommonServer": {
|
||||
"name": "common-server",
|
||||
"name": "@oneuptime/common-server",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@clickhouse/client": "^0.2.9",
|
||||
"@elastic/elasticsearch": "^8.11.0",
|
||||
"@clickhouse/client": "^0.2.10",
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@opentelemetry/api": "^1.7.0",
|
||||
"@opentelemetry/api-logs": "^0.48.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.48.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.48.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.48.0",
|
||||
"@opentelemetry/api-logs": "^0.49.1",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.43.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.49.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.49.1",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.49.1",
|
||||
"@opentelemetry/id-generator-aws-xray": "^1.2.1",
|
||||
"@opentelemetry/instrumentation-express": "^0.35.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.48.0",
|
||||
"@opentelemetry/sdk-logs": "^0.48.0",
|
||||
"@opentelemetry/sdk-logs": "^0.49.1",
|
||||
"@opentelemetry/sdk-metrics": "^1.21.0",
|
||||
"@opentelemetry/sdk-node": "^0.48.0",
|
||||
"@opentelemetry/sdk-trace-node": "^1.21.0",
|
||||
"@socket.io/redis-adapter": "^8.2.1",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"axios": "^1.6.7",
|
||||
"bullmq": "^5.3.3",
|
||||
"Common": "file:../Common",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"dotenv": "^16.4.1",
|
||||
"ejs": "^3.1.8",
|
||||
"express": "^4.17.3",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2",
|
||||
"ioredis": "^5.3.2",
|
||||
"json2csv": "^5.0.7",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"markdown-it": "^13.0.1",
|
||||
"Model": "file:../Model",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^6.9.9",
|
||||
"nodemailer": "^6.9.10",
|
||||
"pg": "^8.7.3",
|
||||
"socket.io": "^4.7.4",
|
||||
"stripe": "^10.17.0",
|
||||
"twilio": "^4.21.0",
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"vm2": "^3.9.14"
|
||||
"typeorm-extension": "^2.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^6.3.1",
|
||||
@@ -125,9 +121,9 @@
|
||||
}
|
||||
},
|
||||
"../Model": {
|
||||
"name": "model",
|
||||
"name": "@oneuptime/model",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
"typeorm": "^0.3.20"
|
||||
@@ -785,14 +781,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@greenlock/manager": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@greenlock/manager/-/manager-3.1.0.tgz",
|
||||
"integrity": "sha512-PBy5CMK+j4oD7sj7hF5qE+xKEOSiiuL2hHd5X5ttEbtnTSDKjNeqbrR5k2ZddwVNdjOVeBIeuqlm81IFZ+Ftew==",
|
||||
"dependencies": {
|
||||
"greenlock-manager-fs": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/load-nyc-config": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||
@@ -1187,117 +1175,6 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@root/acme": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.1.0.tgz",
|
||||
"integrity": "sha512-GAyaW63cpSYd2KvVp5lHLbCWeEhJPKZK9nsJvZJOKsD9Uv88KEttn4FpDZEJ+2q3Jsey0DWpuQ2I4ft0JV9p2w==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@root/csr": "^0.8.1",
|
||||
"@root/encoding": "^1.0.1",
|
||||
"@root/keypairs": "^0.10.0",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/request": "^1.6.1",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@root/acme/node_modules/@root/keypairs": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.10.3.tgz",
|
||||
"integrity": "sha512-hbmjVbk/G99Z1XlUzJM4VOAaR8jm4vMnfwpZL301FA24ubJ/bOjj6enCrz9gKsQvBO6RY4/R4MgUuWpXeqeZZQ==",
|
||||
"dependencies": {
|
||||
"@root/encoding": "^1.0.1",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@root/asn1": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/asn1/-/asn1-1.0.0.tgz",
|
||||
"integrity": "sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig==",
|
||||
"dependencies": {
|
||||
"@root/encoding": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@root/csr": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz",
|
||||
"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==",
|
||||
"dependencies": {
|
||||
"@root/asn1": "^1.0.0",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@root/encoding": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz",
|
||||
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ=="
|
||||
},
|
||||
"node_modules/@root/greenlock": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@root/greenlock/-/greenlock-4.0.5.tgz",
|
||||
"integrity": "sha512-KR9w3mYE9aH33FCibI8oSYBQV+f7lc3MVPdZ9nxY2tqRLmJp05cMOMz340mtG14VnWDuznLj4TbBj3sHIuoQPQ==",
|
||||
"dependencies": {
|
||||
"@greenlock/manager": "^3.1.0",
|
||||
"@root/acme": "^3.1.0",
|
||||
"@root/csr": "^0.8.1",
|
||||
"@root/keypairs": "^0.10.0",
|
||||
"@root/mkdirp": "^1.0.0",
|
||||
"@root/request": "^1.6.1",
|
||||
"acme-http-01-standalone": "^3.0.5",
|
||||
"cert-info": "^1.5.1",
|
||||
"greenlock-store-fs": "^3.2.2",
|
||||
"safe-replace": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"greenlock": "bin/greenlock.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@root/greenlock/node_modules/@root/keypairs": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.10.3.tgz",
|
||||
"integrity": "sha512-hbmjVbk/G99Z1XlUzJM4VOAaR8jm4vMnfwpZL301FA24ubJ/bOjj6enCrz9gKsQvBO6RY4/R4MgUuWpXeqeZZQ==",
|
||||
"dependencies": {
|
||||
"@root/encoding": "^1.0.1",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@root/keypairs": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz",
|
||||
"integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==",
|
||||
"dependencies": {
|
||||
"@root/encoding": "^1.0.1",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@root/mkdirp": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz",
|
||||
"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA=="
|
||||
},
|
||||
"node_modules/@root/pem": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.4.tgz",
|
||||
"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA=="
|
||||
},
|
||||
"node_modules/@root/request": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.9.2.tgz",
|
||||
"integrity": "sha512-wVaL9yVV9oDR9UNbPZa20qgY+4Ch6YN8JUkaE4el/uuS5dmhD8Lusm/ku8qJVNtmQA56XLzEDCRS6/vfpiHK2A=="
|
||||
},
|
||||
"node_modules/@root/x509": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@root/x509/-/x509-0.7.2.tgz",
|
||||
"integrity": "sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ==",
|
||||
"dependencies": {
|
||||
"@root/asn1": "^1.0.0",
|
||||
"@root/encoding": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@sendgrid/client": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.0.tgz",
|
||||
@@ -1705,11 +1582,6 @@
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acme-http-01-standalone": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/acme-http-01-standalone/-/acme-http-01-standalone-3.0.5.tgz",
|
||||
"integrity": "sha512-W4GfK+39GZ+u0mvxRVUcVFCG6gposfzEnSBF20T/NUwWAKG59wQT1dUbS1NixRIAsRuhpGc4Jx659cErFQH0Pg=="
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.11.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
|
||||
@@ -2053,14 +1925,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/cert-info": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz",
|
||||
"integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==",
|
||||
"bin": {
|
||||
"cert-info": "bin/cert-info.js"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -2681,42 +2545,6 @@
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/greenlock": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/greenlock/-/greenlock-4.0.4.tgz",
|
||||
"integrity": "sha512-f96lHXk0DIZCyem4IpMea/ME4LnWUHHSJr79r6UxF//gBRFy9toSq7ndiSO9hL1A2WDZSv+j28kFcbf+/az9Ew==",
|
||||
"dependencies": {
|
||||
"@greenlock/manager": "^3.1.0",
|
||||
"@root/acme": "^3.0.9",
|
||||
"@root/csr": "^0.8.1",
|
||||
"@root/greenlock": "^4.0.4",
|
||||
"@root/keypairs": "^0.9.0",
|
||||
"@root/mkdirp": "^1.0.0",
|
||||
"@root/request": "^1.4.2",
|
||||
"acme-http-01-standalone": "^3.0.5",
|
||||
"cert-info": "^1.5.1",
|
||||
"greenlock-store-fs": "^3.2.2",
|
||||
"safe-replace": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/greenlock-manager-fs": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.1.1.tgz",
|
||||
"integrity": "sha512-np6qdnPIOZx40PAcSQcqK1eMPWjTKxsxcgRd/OVg0ai49WC1Ds74CTrwmB84pq2n53ikbnDBQFmKEQ4AC0DK8w==",
|
||||
"dependencies": {
|
||||
"@root/mkdirp": "^1.0.0",
|
||||
"safe-replace": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/greenlock-store-fs": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.2.2.tgz",
|
||||
"integrity": "sha512-92ejLB4DyV4qv/2b6VLGF2nKfYQeIfg3o+e/1cIoYLjlIaUFdbBXkzLTRozFlHsQPZt2ALi5qYrpC9IwH7GK8A==",
|
||||
"dependencies": {
|
||||
"@root/mkdirp": "^1.0.0",
|
||||
"safe-replace": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||
@@ -4469,11 +4297,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safe-replace": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz",
|
||||
"integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw=="
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"Common": "file:../Common",
|
||||
"CommonServer": "file:../CommonServer",
|
||||
"ejs": "^3.1.9",
|
||||
"greenlock": "^4.0.4",
|
||||
"handlebars": "^4.7.8",
|
||||
"marked": "^12.0.0",
|
||||
"Model": "file:../Model",
|
||||
|
||||
@@ -18,6 +18,7 @@ import Dictionary from '../Types/Dictionary';
|
||||
import ModelPermission from '../Types/BaseDatabase/ModelPermission';
|
||||
import Permission, { UserTenantAccessPermission } from '../Types/Permission';
|
||||
import { PlanSelect } from '../Types/Billing/SubscriptionPlan';
|
||||
import { JSONValue } from '../Types/JSON';
|
||||
|
||||
export type AnalyticsBaseModelType = { new (): AnalyticsBaseModel };
|
||||
|
||||
@@ -259,6 +260,17 @@ export default class AnalyticsBaseModel extends CommonModel {
|
||||
return column.isDefaultValueColumn;
|
||||
}
|
||||
|
||||
public getDefaultValueForColumn(columnName: string): JSONValue {
|
||||
const column: AnalyticsTableColumn | null =
|
||||
this.getTableColumn(columnName);
|
||||
|
||||
if (!column) {
|
||||
throw new BadDataException('Column ' + columnName + ' not found');
|
||||
}
|
||||
|
||||
return column.defaultValue;
|
||||
}
|
||||
|
||||
public getColumnBillingAccessControl(
|
||||
columnName: string
|
||||
): ColumnBillingAccessControl | null {
|
||||
|
||||
@@ -78,12 +78,27 @@ export default class CommonModel {
|
||||
typeof value === 'string'
|
||||
) {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
value = JSONFunctions.parse(value);
|
||||
} catch (e) {
|
||||
value = {};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
column.type === TableColumnType.JSONArray &&
|
||||
typeof value === 'string'
|
||||
) {
|
||||
try {
|
||||
value = JSONFunctions.parseJSONArray(value);
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
throw new BadDataException('Not an array');
|
||||
}
|
||||
} catch (e) {
|
||||
value = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
column.type === TableColumnType.Number &&
|
||||
typeof value === 'string'
|
||||
@@ -204,6 +219,8 @@ export default class CommonModel {
|
||||
recordValue as Array<CommonModel>,
|
||||
column.nestedModelType
|
||||
);
|
||||
} else {
|
||||
json[column.key] = recordValue;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -221,16 +238,16 @@ export default class CommonModel {
|
||||
): Array<TBaseModel> {
|
||||
const models: Array<CommonModel> = [];
|
||||
|
||||
jsonArray.forEach((json: JSONObject | CommonModel) => {
|
||||
for (const json of jsonArray) {
|
||||
if (json instanceof CommonModel) {
|
||||
models.push(json);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
const model: CommonModel = new modelType();
|
||||
model.fromJSON(json);
|
||||
models.push(model);
|
||||
});
|
||||
}
|
||||
|
||||
return models as Array<TBaseModel>;
|
||||
}
|
||||
|
||||
@@ -371,6 +371,10 @@ export default class BaseModel extends BaseEntity {
|
||||
}
|
||||
}
|
||||
|
||||
public isTableColumn(columnName: string): boolean {
|
||||
return Boolean(getTableColumn(this, columnName));
|
||||
}
|
||||
|
||||
public isEntityColumn(columnName: string): boolean {
|
||||
const tableColumnType: TableColumnMetadata = getTableColumn(
|
||||
this,
|
||||
|
||||
@@ -2,20 +2,20 @@ import Color from '../../Types/Color';
|
||||
import {
|
||||
Black,
|
||||
White,
|
||||
slate,
|
||||
Purple,
|
||||
Pink,
|
||||
Red,
|
||||
Orange,
|
||||
Yellow,
|
||||
Green,
|
||||
Teal,
|
||||
Cyan,
|
||||
VeryLightGrey,
|
||||
Grey,
|
||||
LightGrey,
|
||||
Moroon,
|
||||
Blue,
|
||||
Slate500,
|
||||
Purple500,
|
||||
Pink500,
|
||||
Red500,
|
||||
Orange500,
|
||||
Yellow500,
|
||||
Green500,
|
||||
Teal500,
|
||||
Cyan500,
|
||||
VeryLightGray,
|
||||
Gray500,
|
||||
LightGray,
|
||||
Moroon500,
|
||||
Blue500,
|
||||
} from '../../Types/BrandColors';
|
||||
|
||||
describe('Color', () => {
|
||||
@@ -41,100 +41,100 @@ describe('Color', () => {
|
||||
});
|
||||
describe('slate', () => {
|
||||
it('should be an instance with the hex code of slate', () => {
|
||||
const color: Color = slate;
|
||||
expect(slate).toBe(color);
|
||||
expect(slate.color).toBe('#564ab1');
|
||||
const color: Color = Slate500;
|
||||
expect(Slate500).toBe(color);
|
||||
expect(Slate500.color).toBe('#64748b');
|
||||
});
|
||||
});
|
||||
describe('Purple', () => {
|
||||
it('should be an instance with the hex code of Purple', () => {
|
||||
const color: Color = Purple;
|
||||
expect(Purple).toBe(color);
|
||||
expect(Purple.color).toBe('#6f42c1');
|
||||
describe('Purple500', () => {
|
||||
it('should be an instance with the hex code of Purple500', () => {
|
||||
const color: Color = Purple500;
|
||||
expect(Purple500).toBe(color);
|
||||
expect(Purple500.color).toBe('#a855f7');
|
||||
});
|
||||
});
|
||||
describe('Pink', () => {
|
||||
it('should be an instance with the hex code of Pink', () => {
|
||||
const color: Color = Pink;
|
||||
expect(Pink).toBe(color);
|
||||
expect(Pink.color).toBe('#e83e8c');
|
||||
describe('Pink500', () => {
|
||||
it('should be an instance with the hex code of Pink500', () => {
|
||||
const color: Color = Pink500;
|
||||
expect(Pink500).toBe(color);
|
||||
expect(Pink500.color).toBe('#ec4899');
|
||||
});
|
||||
});
|
||||
describe('Red', () => {
|
||||
it('should be an instance with the hex code of Red', () => {
|
||||
const color: Color = Red;
|
||||
expect(Red).toBe(color);
|
||||
expect(Red.color).toBe('#fd625e');
|
||||
describe('Red500', () => {
|
||||
it('should be an instance with the hex code of Red500', () => {
|
||||
const color: Color = Red500;
|
||||
expect(Red500).toBe(color);
|
||||
expect(Red500.color).toBe('#ef4444');
|
||||
});
|
||||
});
|
||||
describe('Orange', () => {
|
||||
it('should be an instance with the hex code of Orange', () => {
|
||||
const color: Color = Orange;
|
||||
expect(Orange).toBe(color);
|
||||
expect(Orange.color).toBe('#f1734f');
|
||||
describe('Orange500', () => {
|
||||
it('should be an instance with the hex code of Orange500', () => {
|
||||
const color: Color = Orange500;
|
||||
expect(Orange500).toBe(color);
|
||||
expect(Orange500.color).toBe('#f97316');
|
||||
});
|
||||
});
|
||||
describe('Yellow', () => {
|
||||
it('should be an instance with the hex code of Yellow', () => {
|
||||
const color: Color = Yellow;
|
||||
expect(Yellow).toBe(color);
|
||||
expect(Yellow.color).toBe('#ffbf53');
|
||||
describe('Yellow500', () => {
|
||||
it('should be an instance with the hex code of Yellow500', () => {
|
||||
const color: Color = Yellow500;
|
||||
expect(Yellow500).toBe(color);
|
||||
expect(Yellow500.color).toBe('#ffbf53');
|
||||
});
|
||||
});
|
||||
describe('Green', () => {
|
||||
it('should be an instance with the hex code of Green', () => {
|
||||
const color: Color = Green;
|
||||
expect(Green).toBe(color);
|
||||
expect(Green.color).toBe('#2ab57d');
|
||||
describe('Green500', () => {
|
||||
it('should be an instance with the hex code of Green500', () => {
|
||||
const color: Color = Green500;
|
||||
expect(Green500).toBe(color);
|
||||
expect(Green500.color).toBe('#22c55e');
|
||||
});
|
||||
});
|
||||
describe('Teal', () => {
|
||||
it('should be an instance with the hex code of Teal', () => {
|
||||
const color: Color = Teal;
|
||||
expect(Teal).toBe(color);
|
||||
expect(Teal.color).toBe('#050505');
|
||||
describe('Teal500', () => {
|
||||
it('should be an instance with the hex code of Teal500', () => {
|
||||
const color: Color = Teal500;
|
||||
expect(Teal500).toBe(color);
|
||||
expect(Teal500.color).toBe('#14b8a6');
|
||||
});
|
||||
});
|
||||
describe('Cyan', () => {
|
||||
it('should be an instance with the hex code of Cyan', () => {
|
||||
const color: Color = Cyan;
|
||||
expect(Cyan).toBe(color);
|
||||
expect(Cyan.color).toBe('#4ba6ef');
|
||||
describe('Cyan500', () => {
|
||||
it('should be an instance with the hex code of Cyan500', () => {
|
||||
const color: Color = Cyan500;
|
||||
expect(Cyan500).toBe(color);
|
||||
expect(Cyan500.color).toBe('#06b6d4');
|
||||
});
|
||||
});
|
||||
describe('VeryLightGrey', () => {
|
||||
it('should be an instance with the hex code of VeryLightGrey', () => {
|
||||
const color: Color = VeryLightGrey;
|
||||
expect(VeryLightGrey).toBe(color);
|
||||
expect(VeryLightGrey.color).toBe('#c2c2c2');
|
||||
describe('VeryLightGray', () => {
|
||||
it('should be an instance with the hex code of VeryLightGray', () => {
|
||||
const color: Color = VeryLightGray;
|
||||
expect(VeryLightGray).toBe(color);
|
||||
expect(VeryLightGray.color).toBe('#c2c2c2');
|
||||
});
|
||||
});
|
||||
describe('Grey', () => {
|
||||
it('should be an instance with the hex code of Grey', () => {
|
||||
const color: Color = Grey;
|
||||
expect(Grey).toBe(color);
|
||||
expect(Grey.color).toBe('#575757');
|
||||
describe('Gray500', () => {
|
||||
it('should be an instance with the hex code of Gray500', () => {
|
||||
const color: Color = Gray500;
|
||||
expect(Gray500).toBe(color);
|
||||
expect(Gray500.color).toBe('#6b7280');
|
||||
});
|
||||
});
|
||||
describe('LightGrey', () => {
|
||||
it('should be an instance with the hex code of LightGrey', () => {
|
||||
const color: Color = LightGrey;
|
||||
expect(LightGrey).toBe(color);
|
||||
expect(LightGrey.color).toBe('#908B8B');
|
||||
describe('LightGray', () => {
|
||||
it('should be an instance with the hex code of LightGray', () => {
|
||||
const color: Color = LightGray;
|
||||
expect(LightGray).toBe(color);
|
||||
expect(LightGray.color).toBe('#908B8B');
|
||||
});
|
||||
});
|
||||
describe('Moroon', () => {
|
||||
it('should be an instance with the hex code of Moroon', () => {
|
||||
const color: Color = Moroon;
|
||||
expect(Moroon).toBe(color);
|
||||
expect(Moroon.color).toBe('#b70400');
|
||||
describe('Moroon500', () => {
|
||||
it('should be an instance with the hex code of Moroon500', () => {
|
||||
const color: Color = Moroon500;
|
||||
expect(Moroon500).toBe(color);
|
||||
expect(Moroon500.color).toBe('#b70400');
|
||||
});
|
||||
});
|
||||
describe('Blue', () => {
|
||||
it('should be an instance with the hex code of Blue', () => {
|
||||
const color: Color = Blue;
|
||||
expect(Blue).toBe(color);
|
||||
expect(Blue.color).toBe('#3686be');
|
||||
describe('Blue500', () => {
|
||||
it('should be an instance with the hex code of Blue500', () => {
|
||||
const color: Color = Blue500;
|
||||
expect(Blue500).toBe(color);
|
||||
expect(Blue500.color).toBe('#3b82f6');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,6 @@ describe('DatabaseNotConnectedException', () => {
|
||||
});
|
||||
|
||||
test('should return 3 as the code for DatabaseNotConnectedException', () => {
|
||||
expect(new DatabaseNotConnectedException().code).toBe(3);
|
||||
expect(new DatabaseNotConnectedException().code).toBe(500);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,8 @@ export default class Route extends DatabaseProperty {
|
||||
route = route.toString();
|
||||
}
|
||||
|
||||
route = route?.replace(/\/+/g, '/'); // remove multiple slashes from route and replace with single slash
|
||||
|
||||
if (route) {
|
||||
this.route = route;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ enum ColumnType {
|
||||
Text = 'Text',
|
||||
NestedModel = 'Nested Model',
|
||||
JSON = 'JSON',
|
||||
JSONArray = 'JSON Array',
|
||||
Decimal = 'Decimal',
|
||||
ArrayNumber = 'Array of Numbers',
|
||||
ArrayText = 'Array of Text',
|
||||
|
||||
@@ -33,6 +33,10 @@ export default class ArrayUtil {
|
||||
};
|
||||
}
|
||||
|
||||
public static selectItemByRandom<T>(array: Array<T>): T {
|
||||
return array[Math.floor(Math.random() * array.length)]!;
|
||||
}
|
||||
|
||||
public static distinctByFieldName(
|
||||
array: Array<any>,
|
||||
fieldName: string
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Color from './Color';
|
||||
|
||||
// Standard Colors
|
||||
export const Black: Color = Color.fromString('#000000');
|
||||
export const White: Color = Color.fromString('#ffffff');
|
||||
export const slate: Color = Color.fromString('#564ab1');
|
||||
@@ -11,37 +12,56 @@ export const Yellow: Color = Color.fromString('#ffbf53');
|
||||
export const Green: Color = Color.fromString('#2ab57d');
|
||||
export const Teal: Color = Color.fromString('#050505');
|
||||
export const Cyan: Color = Color.fromString('#4ba6ef');
|
||||
export const VeryLightGrey: Color = Color.fromString('#c2c2c2');
|
||||
export const VeryLightGray: Color = Color.fromString('#c2c2c2');
|
||||
export const Grey: Color = Color.fromString('#575757');
|
||||
export const LightGrey: Color = Color.fromString('#908B8B');
|
||||
export const LightGray: Color = Color.fromString('#908B8B');
|
||||
export const Moroon: Color = Color.fromString('#b70400');
|
||||
export const Blue: Color = Color.fromString('#3686be');
|
||||
|
||||
// these are *-500 colors from tailwindcss
|
||||
export const Zinc500: Color = Color.fromString('#71717a');
|
||||
export const Neutra500l: Color = Color.fromString('#737373');
|
||||
export const Stone500: Color = Color.fromString('#78716c');
|
||||
export const Slate500: Color = Color.fromString('#64748b');
|
||||
export const Purple500: Color = Color.fromString('#a855f7');
|
||||
export const Pink500: Color = Color.fromString('#ec4899');
|
||||
export const Red500: Color = Color.fromString('#ef4444');
|
||||
export const Orange500: Color = Color.fromString('#f97316');
|
||||
export const Yellow500: Color = Color.fromString('#ffbf53');
|
||||
export const Green500: Color = Color.fromString('#22c55e');
|
||||
export const Teal500: Color = Color.fromString('#14b8a6');
|
||||
export const Cyan500: Color = Color.fromString('#06b6d4');
|
||||
export const Gray500: Color = Color.fromString('#6b7280');
|
||||
export const Moroon500: Color = Color.fromString('#b70400');
|
||||
export const Blue500: Color = Color.fromString('#3b82f6');
|
||||
export const Indigo500: Color = Color.fromString('#6366f1');
|
||||
export const Amber500: Color = Color.fromString('#f59e0b');
|
||||
export const Lime500: Color = Color.fromString('#84cc16');
|
||||
export const Emerald500: Color = Color.fromString('#10b981');
|
||||
export const Sky500: Color = Color.fromString('#0ea5e9');
|
||||
export const Violet500: Color = Color.fromString('#8b5cf6');
|
||||
export const Fuchsia500: Color = Color.fromString('#d946ef');
|
||||
export const Rose500: Color = Color.fromString('#f43f5e');
|
||||
|
||||
export const ChartColors: Color[] = [
|
||||
export const BrightColors: Color[] = [
|
||||
Black,
|
||||
Indigo500,
|
||||
Green,
|
||||
Red,
|
||||
Cyan,
|
||||
Pink,
|
||||
Orange,
|
||||
Purple,
|
||||
Yellow,
|
||||
];
|
||||
|
||||
export const EventColorList: Color[] = [
|
||||
Color.fromString('#d50000'),
|
||||
Color.fromString('#e67c73'),
|
||||
Color.fromString('#f4511e'),
|
||||
Color.fromString('#f6bf26'),
|
||||
Color.fromString('#33b679'),
|
||||
Color.fromString('#0b8043'),
|
||||
|
||||
Color.fromString('#039be5'),
|
||||
Color.fromString('#3f51b5'),
|
||||
|
||||
Color.fromString('#65a30d'),
|
||||
Color.fromString('#8e24aa'),
|
||||
|
||||
Color.fromString('#616161'),
|
||||
Green500,
|
||||
Red500,
|
||||
Cyan500,
|
||||
Pink500,
|
||||
Orange500,
|
||||
Purple500,
|
||||
Yellow500,
|
||||
Teal500,
|
||||
Gray500,
|
||||
Moroon500,
|
||||
Blue500,
|
||||
Rose500,
|
||||
Fuchsia500,
|
||||
Violet500,
|
||||
Sky500,
|
||||
Emerald500,
|
||||
Lime500,
|
||||
Amber500,
|
||||
];
|
||||
|
||||
@@ -849,6 +849,38 @@ export default class OneUptimeDate {
|
||||
return minutes;
|
||||
}
|
||||
|
||||
public static convertMinutesToDaysHoursAndMinutes(minutes: number): string {
|
||||
// should output 2 days, 3 hours and 4 minutes. If the days are 0, it should not show the days. If the hours are 0, it should not show the hours. If the minutes are 0, it should not show the minutes.
|
||||
|
||||
const days: number = Math.floor(minutes / (24 * 60));
|
||||
const hours: number = Math.floor((minutes % (24 * 60)) / 60);
|
||||
const mins: number = minutes % 60;
|
||||
|
||||
let formattedString: string = '';
|
||||
|
||||
if (days > 0) {
|
||||
formattedString += days + ' days';
|
||||
}
|
||||
|
||||
if (hours > 0) {
|
||||
if (formattedString.length > 0) {
|
||||
formattedString += ', ';
|
||||
}
|
||||
|
||||
formattedString += hours + ' hours';
|
||||
}
|
||||
|
||||
if (mins >= 0) {
|
||||
if (formattedString.length > 0) {
|
||||
formattedString += ', ';
|
||||
}
|
||||
|
||||
formattedString += mins + ' minutes';
|
||||
}
|
||||
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
public static getDateAsFormattedArrayInMultipleTimezones(
|
||||
date: string | Date,
|
||||
onlyShowDate?: boolean
|
||||
|
||||
@@ -53,7 +53,10 @@ export default class Email extends DatabaseProperty {
|
||||
}
|
||||
|
||||
public static isValid(value: string): boolean {
|
||||
const re: RegExp = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,10}$/i;
|
||||
// from https://datatracker.ietf.org/doc/html/rfc5322
|
||||
|
||||
const re: RegExp =
|
||||
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/i;
|
||||
const isValid: boolean = re.test(value);
|
||||
if (!isValid) {
|
||||
return false;
|
||||
@@ -68,6 +71,10 @@ export default class Email extends DatabaseProperty {
|
||||
};
|
||||
}
|
||||
|
||||
public static fromString(value: string): Email {
|
||||
return new Email(value);
|
||||
}
|
||||
|
||||
public static override fromJSON(json: JSONObject): Email {
|
||||
if (json['_type'] === ObjectType.Email) {
|
||||
return new Email((json['value'] as string) || '');
|
||||
|
||||
@@ -2,7 +2,7 @@ enum ExceptionCode {
|
||||
NotImplementedException = 0,
|
||||
GeneralException = 1,
|
||||
APIException = 2,
|
||||
DatabaseNotConnectedException = 3,
|
||||
DatabaseNotConnectedException = 500,
|
||||
BadOperationException = 5,
|
||||
WebRequestException = 6,
|
||||
BadDataException = 400,
|
||||
|
||||
@@ -8,6 +8,64 @@ import SerializableObjectDictionary from './SerializableObjectDictionary';
|
||||
import JSON5 from 'json5';
|
||||
|
||||
export default class JSONFunctions {
|
||||
public static nestJson(obj: JSONObject): JSONObject {
|
||||
// obj could be in this format:
|
||||
|
||||
/**
|
||||
* {
|
||||
"http.url.protocol": "http",
|
||||
"http.url.hostname": "localhost",
|
||||
"http.host": "localhost",
|
||||
*/
|
||||
|
||||
// we want to convert it to this format:
|
||||
|
||||
/**
|
||||
* {
|
||||
*
|
||||
* "http": {
|
||||
* "url": {
|
||||
* "protocol": "http",
|
||||
* "hostname": "localhost"
|
||||
* },
|
||||
* "host": "localhost",
|
||||
* "method": "POST",
|
||||
* "scheme": "http",
|
||||
* "client_ip": "
|
||||
* ...
|
||||
*
|
||||
* },
|
||||
*/
|
||||
|
||||
const result: JSONObject = {};
|
||||
|
||||
for (const key in obj) {
|
||||
const keys: Array<string> = key.split('.');
|
||||
|
||||
let currentObj: JSONObject = result;
|
||||
|
||||
for (let i: number = 0; i < keys.length; i++) {
|
||||
const k: string | undefined = keys[i];
|
||||
|
||||
if (!k) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i === keys.length - 1) {
|
||||
currentObj[k] = obj[key];
|
||||
} else {
|
||||
if (!currentObj[k]) {
|
||||
currentObj[k] = {};
|
||||
}
|
||||
|
||||
currentObj = currentObj[k] as JSONObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static isEmptyObject(
|
||||
obj: JSONObject | BaseModel | null | undefined
|
||||
): boolean {
|
||||
@@ -294,6 +352,10 @@ export default class JSONFunctions {
|
||||
return newVal;
|
||||
}
|
||||
|
||||
public static toFormattedString(val: JSONValue): string {
|
||||
return JSON.stringify(val, null, 4);
|
||||
}
|
||||
|
||||
public static anyObjectToJSONObject(val: any): JSONObject {
|
||||
return JSON.parse(JSON.stringify(val));
|
||||
}
|
||||
@@ -320,4 +382,14 @@ export default class JSONFunctions {
|
||||
|
||||
return returnObj;
|
||||
}
|
||||
|
||||
public static flattenArray(val: JSONArray): JSONArray {
|
||||
const returnArr: JSONArray = [];
|
||||
|
||||
for (const obj of val) {
|
||||
returnArr.push(this.flattenObject(obj as JSONObject));
|
||||
}
|
||||
|
||||
return returnArr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ export default interface JSONWebTokenData extends JSONObject {
|
||||
isMasterAdmin: boolean;
|
||||
statusPageId?: ObjectID | undefined; // for status page logins.
|
||||
projectId?: ObjectID | undefined; // for SSO logins.
|
||||
isGlobalLogin: boolean; // If this is OneUptime username and password login. This is true, if this is SSO login. Then, this is false.
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ enum ProductType {
|
||||
Logs = 'Logs',
|
||||
Traces = 'Traces',
|
||||
Metrics = 'Metrics',
|
||||
ActiveMonitoring = 'ActiveMonitoring',
|
||||
ActiveMonitoring = 'Active Monitoring',
|
||||
}
|
||||
|
||||
export default ProductType;
|
||||
|
||||
@@ -23,6 +23,7 @@ export default class MonitorCriteria extends DatabaseProperty {
|
||||
|
||||
public static getDefaultMonitorCriteria(arg: {
|
||||
monitorType: MonitorType;
|
||||
monitorName: string;
|
||||
onlineMonitorStatusId: ObjectID;
|
||||
offlineMonitorStatusId: ObjectID;
|
||||
defaultIncidentSeverityId: ObjectID;
|
||||
@@ -33,12 +34,14 @@ export default class MonitorCriteria extends DatabaseProperty {
|
||||
monitorType: arg.monitorType,
|
||||
monitorStatusId: arg.offlineMonitorStatusId,
|
||||
incidentSeverityId: arg.defaultIncidentSeverityId,
|
||||
monitorName: arg.monitorName,
|
||||
});
|
||||
|
||||
const onlineCriteria: MonitorCriteriaInstance | null =
|
||||
MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({
|
||||
monitorType: arg.monitorType,
|
||||
monitorStatusId: arg.onlineMonitorStatusId,
|
||||
monitorName: arg.monitorName,
|
||||
});
|
||||
|
||||
monitorCriteria.data = {
|
||||
|
||||
@@ -53,6 +53,7 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
public static getDefaultOnlineMonitorCriteriaInstance(arg: {
|
||||
monitorType: MonitorType;
|
||||
monitorStatusId: ObjectID;
|
||||
monitorName: string;
|
||||
}): MonitorCriteriaInstance | null {
|
||||
if (arg.monitorType === MonitorType.IncomingRequest) {
|
||||
const monitorCriteriaInstance: MonitorCriteriaInstance =
|
||||
@@ -72,8 +73,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
incidents: [],
|
||||
changeMonitorStatus: true,
|
||||
createIncidents: false,
|
||||
name: 'Check if online',
|
||||
description: `This criteria checks if the ${arg.monitorType} is online`,
|
||||
name: `Check if ${arg.monitorName} is online`,
|
||||
description: `This criteria checks if the ${arg.monitorName} is online`,
|
||||
};
|
||||
|
||||
return monitorCriteriaInstance;
|
||||
@@ -97,8 +98,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
incidents: [],
|
||||
changeMonitorStatus: true,
|
||||
createIncidents: false,
|
||||
name: 'Check if online',
|
||||
description: `This criteria checks if the ${arg.monitorType} is online`,
|
||||
name: `Check if ${arg.monitorName} is online`,
|
||||
description: `This criteria checks if the ${arg.monitorName} is online`,
|
||||
};
|
||||
|
||||
return monitorCriteriaInstance;
|
||||
@@ -122,8 +123,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
incidents: [],
|
||||
changeMonitorStatus: true,
|
||||
createIncidents: false,
|
||||
name: 'Check if online',
|
||||
description: `This criteria checks if the ${arg.monitorType} is online`,
|
||||
name: `Check if ${arg.monitorName} is online`,
|
||||
description: `This criteria checks if the ${arg.monitorName} is online`,
|
||||
};
|
||||
|
||||
return monitorCriteriaInstance;
|
||||
@@ -153,8 +154,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
incidents: [],
|
||||
changeMonitorStatus: true,
|
||||
createIncidents: false,
|
||||
name: 'Check if online',
|
||||
description: `This criteria checks if the ${arg.monitorType} is online`,
|
||||
name: `Check if ${arg.monitorName} is online`,
|
||||
description: `This criteria checks if the ${arg.monitorName} is online`,
|
||||
};
|
||||
|
||||
if (
|
||||
@@ -178,6 +179,7 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
monitorType: MonitorType;
|
||||
monitorStatusId: ObjectID;
|
||||
incidentSeverityId: ObjectID;
|
||||
monitorName: string;
|
||||
}): MonitorCriteriaInstance {
|
||||
const monitorCriteriaInstance: MonitorCriteriaInstance =
|
||||
new MonitorCriteriaInstance();
|
||||
@@ -201,8 +203,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
],
|
||||
incidents: [
|
||||
{
|
||||
title: `${arg.monitorType} is offline`,
|
||||
description: `${arg.monitorType} is currently offline.`,
|
||||
title: `${arg.monitorName} is offline`,
|
||||
description: `${arg.monitorName} is currently offline.`,
|
||||
incidentSeverityId: arg.incidentSeverityId,
|
||||
autoResolveIncident: true,
|
||||
id: ObjectID.generate().toString(),
|
||||
@@ -211,8 +213,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
],
|
||||
changeMonitorStatus: true,
|
||||
createIncidents: true,
|
||||
name: 'Check if offline',
|
||||
description: `This criteria checks if the ${arg.monitorType} is offline`,
|
||||
name: `Check if ${arg.monitorName} is offline`,
|
||||
description: `This criteria checks if the ${arg.monitorName} is offline`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -238,8 +240,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
],
|
||||
incidents: [
|
||||
{
|
||||
title: `${arg.monitorType} is offline`,
|
||||
description: `${arg.monitorType} is currently offline.`,
|
||||
title: `${arg.monitorName} is offline`,
|
||||
description: `${arg.monitorName} is currently offline.`,
|
||||
incidentSeverityId: arg.incidentSeverityId,
|
||||
autoResolveIncident: true,
|
||||
id: ObjectID.generate().toString(),
|
||||
@@ -248,8 +250,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
],
|
||||
changeMonitorStatus: true,
|
||||
createIncidents: true,
|
||||
name: 'Check if offline',
|
||||
description: `This criteria checks if the ${arg.monitorType} is offline`,
|
||||
name: `Check if ${arg.monitorName} is offline`,
|
||||
description: `This criteria checks if the ${arg.monitorName} is offline`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -267,8 +269,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
],
|
||||
incidents: [
|
||||
{
|
||||
title: `${arg.monitorType} is offline`,
|
||||
description: `${arg.monitorType} is currently offline.`,
|
||||
title: `${arg.monitorName} is offline`,
|
||||
description: `${arg.monitorName} is currently offline.`,
|
||||
incidentSeverityId: arg.incidentSeverityId,
|
||||
autoResolveIncident: true,
|
||||
id: ObjectID.generate().toString(),
|
||||
@@ -277,8 +279,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
],
|
||||
changeMonitorStatus: true,
|
||||
createIncidents: true,
|
||||
name: 'Check if offline',
|
||||
description: `This criteria checks if the ${arg.monitorType} is offline`,
|
||||
name: `Check if ${arg.monitorName} is offline`,
|
||||
description: `This criteria checks if the ${arg.monitorName} is offline`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -296,8 +298,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
],
|
||||
incidents: [
|
||||
{
|
||||
title: `${arg.monitorType} is offline`,
|
||||
description: `${arg.monitorType} is currently offline.`,
|
||||
title: `${arg.monitorName} is offline`,
|
||||
description: `${arg.monitorName} is currently offline.`,
|
||||
incidentSeverityId: arg.incidentSeverityId,
|
||||
autoResolveIncident: true,
|
||||
id: ObjectID.generate().toString(),
|
||||
@@ -306,8 +308,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
|
||||
],
|
||||
changeMonitorStatus: true,
|
||||
createIncidents: true,
|
||||
name: 'Check if offline',
|
||||
description: `This criteria checks if the ${arg.monitorType} is offline`,
|
||||
name: `Check if ${arg.monitorName} is offline`,
|
||||
description: `This criteria checks if the ${arg.monitorName} is offline`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
}
|
||||
|
||||
public static getDefaultMonitorStep(arg: {
|
||||
monitorName: string;
|
||||
monitorType: MonitorType;
|
||||
onlineMonitorStatusId: ObjectID;
|
||||
offlineMonitorStatusId: ObjectID;
|
||||
|
||||
@@ -36,6 +36,7 @@ export default class MonitorSteps extends DatabaseProperty {
|
||||
public static getDefaultMonitorSteps(arg: {
|
||||
defaultMonitorStatusId: ObjectID;
|
||||
monitorType: MonitorType;
|
||||
monitorName: string;
|
||||
onlineMonitorStatusId: ObjectID;
|
||||
offlineMonitorStatusId: ObjectID;
|
||||
defaultIncidentSeverityId: ObjectID;
|
||||
|
||||
@@ -118,4 +118,36 @@ export class MonitorTypeHelper {
|
||||
|
||||
return monitorTypeProps[0].title;
|
||||
}
|
||||
|
||||
public static isProbableMonitors(monitorType: MonitorType): boolean {
|
||||
const isProbeableMonitor: boolean =
|
||||
monitorType === MonitorType.API ||
|
||||
monitorType === MonitorType.Website ||
|
||||
monitorType === MonitorType.IP ||
|
||||
monitorType === MonitorType.Ping ||
|
||||
monitorType === MonitorType.Port ||
|
||||
monitorType === MonitorType.SSLCertificate;
|
||||
return isProbeableMonitor;
|
||||
}
|
||||
|
||||
public static doesMonitorTypeHaveDocumentation(
|
||||
monitorType: MonitorType
|
||||
): boolean {
|
||||
return (
|
||||
monitorType === MonitorType.IncomingRequest ||
|
||||
monitorType === MonitorType.Server
|
||||
);
|
||||
}
|
||||
|
||||
public static doesMonitorTypeHaveInterval(
|
||||
monitorType: MonitorType
|
||||
): boolean {
|
||||
return this.isProbableMonitors(monitorType);
|
||||
}
|
||||
|
||||
public static doesMonitorTypeHaveCriteria(
|
||||
monitorType: MonitorType
|
||||
): boolean {
|
||||
return monitorType !== MonitorType.Manual;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ enum WorkflowStatus {
|
||||
Success = 'Success',
|
||||
Error = 'Error',
|
||||
Timeout = 'Timeout',
|
||||
WorkflowCountExceeded = 'WorkflowCountExceeded',
|
||||
WorkflowCountExceeded = 'Workflow Count Exceeded',
|
||||
}
|
||||
|
||||
export default WorkflowStatus;
|
||||
|
||||
@@ -343,6 +343,9 @@ export default class API {
|
||||
);
|
||||
}
|
||||
|
||||
throw new APIException('Endpoint is not available');
|
||||
// get url from error
|
||||
const url: string = error?.config?.url || '';
|
||||
|
||||
throw new APIException(`URL ${url ? url + ' ' : ''}is not available.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ export const EVERY_HOUR: string = '1 * * * *';
|
||||
export const EVERY_FIVE_MINUTE: string = '*/5 * * * *';
|
||||
export const EVERY_FIVE_SECONDS: string = '*/5 * * * * *';
|
||||
export const EVERY_WEEK: string = '0 0 * * 0';
|
||||
export const EVERY_FIFTEEN_MINUTE: string = '*/15 * * * *';
|
||||
|
||||
33
Common/package-lock.json
generated
33
Common/package-lock.json
generated
@@ -11,13 +11,13 @@
|
||||
"dependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"axios": "^1.6.7",
|
||||
"axios": "^1.6.8",
|
||||
"crypto-js": "^4.1.1",
|
||||
"json5": "^2.2.3",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"posthog-js": "^1.111.0",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"posthog-js": "^1.126.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"slugify": "^1.6.5",
|
||||
"typeorm": "^0.3.20",
|
||||
"uuid": "^8.3.2"
|
||||
@@ -1382,11 +1382,11 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
|
||||
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
|
||||
"version": "1.6.8",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
|
||||
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.4",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
@@ -2146,9 +2146,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -3845,9 +3845,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/posthog-js": {
|
||||
"version": "1.111.0",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.111.0.tgz",
|
||||
"integrity": "sha512-0Bf1hnclW3WLV7UMrIOMk6kVeEWJMNxe8NhkWLraus1uB63p05pPUB8QnpU6JLbx4h800DvtOxAfXbiJN+ozWw==",
|
||||
"version": "1.126.0",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.126.0.tgz",
|
||||
"integrity": "sha512-8qCdPE9RZkyXI3kKCnkXWxK0jn2mLZg6g5a6KezDPqH7mHTG66v7ANU31hcwzQGV5F5UW1GXw0xL0PaC3HkA6g==",
|
||||
"deprecated": "This version of posthog-js is deprecated, please update posthog-js, and do not use this version! Check out our JS docs at https://posthog.com/docs/libraries/js",
|
||||
"dependencies": {
|
||||
"fflate": "^0.4.8",
|
||||
"preact": "^10.19.3"
|
||||
@@ -3934,9 +3935,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/reflect-metadata": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz",
|
||||
"integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw=="
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
"json5": "^2.2.3",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"posthog-js": "^1.116.2",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"posthog-js": "^1.126.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"slugify": "^1.6.5",
|
||||
"typeorm": "^0.3.20",
|
||||
"uuid": "^8.3.2"
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import PartialEntity from 'Common/Types/Database/PartialEntity';
|
||||
import { UserPermission } from 'Common/Types/Permission';
|
||||
import CommonAPI from './CommonAPI';
|
||||
import GroupBy from '../Types/Database/GroupBy';
|
||||
|
||||
export default class BaseAPI<
|
||||
TBaseModel extends BaseModel,
|
||||
@@ -237,6 +238,7 @@ export default class BaseAPI<
|
||||
let query: Query<BaseModel> = {};
|
||||
let select: Select<BaseModel> = {};
|
||||
let sort: Sort<BaseModel> = {};
|
||||
let groupBy: GroupBy<BaseModel> | undefined;
|
||||
|
||||
if (req.body) {
|
||||
query = JSONFunctions.deserialize(
|
||||
@@ -250,6 +252,10 @@ export default class BaseAPI<
|
||||
sort = JSONFunctions.deserialize(
|
||||
req.body['sort']
|
||||
) as Sort<BaseModel>;
|
||||
|
||||
groupBy = JSONFunctions.deserialize(
|
||||
req.body['groupBy']
|
||||
) as GroupBy<BaseModel>;
|
||||
}
|
||||
|
||||
const databaseProps: DatabaseCommonInteractionProps =
|
||||
@@ -260,6 +266,7 @@ export default class BaseAPI<
|
||||
select,
|
||||
skip: skip,
|
||||
limit: limit,
|
||||
groupBy: groupBy,
|
||||
sort: sort,
|
||||
props: databaseProps,
|
||||
});
|
||||
@@ -342,7 +349,7 @@ export default class BaseAPI<
|
||||
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
|
||||
public async updateItem(
|
||||
@@ -370,7 +377,7 @@ export default class BaseAPI<
|
||||
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
|
||||
public async createItem(
|
||||
|
||||
@@ -25,6 +25,7 @@ import { UserPermission } from 'Common/Types/Permission';
|
||||
import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel';
|
||||
import AnalyticsDatabaseService from '../Services/AnalyticsDatabaseService';
|
||||
import CommonAPI from './CommonAPI';
|
||||
import GroupBy from '../Types/AnalyticsDatabase/GroupBy';
|
||||
|
||||
export default class BaseAnalyticsAPI<
|
||||
TAnalyticsDataModel extends AnalyticsDataModel,
|
||||
@@ -235,6 +236,7 @@ export default class BaseAnalyticsAPI<
|
||||
let query: Query<AnalyticsDataModel> = {};
|
||||
let select: Select<AnalyticsDataModel> = {};
|
||||
let sort: Sort<AnalyticsDataModel> = {};
|
||||
let groupBy: GroupBy<AnalyticsDataModel> = {};
|
||||
|
||||
if (req.body) {
|
||||
query = JSONFunctions.deserialize(
|
||||
@@ -248,6 +250,10 @@ export default class BaseAnalyticsAPI<
|
||||
sort = JSONFunctions.deserialize(
|
||||
req.body['sort']
|
||||
) as Sort<AnalyticsDataModel>;
|
||||
|
||||
groupBy = JSONFunctions.deserialize(
|
||||
req.body['groupBy']
|
||||
) as GroupBy<AnalyticsDataModel>;
|
||||
}
|
||||
|
||||
const databaseProps: DatabaseCommonInteractionProps =
|
||||
@@ -259,11 +265,13 @@ export default class BaseAnalyticsAPI<
|
||||
skip: skip,
|
||||
limit: limit,
|
||||
sort: sort,
|
||||
groupBy: groupBy,
|
||||
props: databaseProps,
|
||||
});
|
||||
|
||||
const count: PositiveNumber = await this.service.countBy({
|
||||
query,
|
||||
groupBy: groupBy,
|
||||
props: databaseProps,
|
||||
});
|
||||
|
||||
@@ -340,7 +348,7 @@ export default class BaseAnalyticsAPI<
|
||||
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
|
||||
public async updateItem(
|
||||
@@ -370,7 +378,7 @@ export default class BaseAnalyticsAPI<
|
||||
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
|
||||
public async createItem(
|
||||
|
||||
@@ -166,7 +166,7 @@ export default class UserAPI extends BaseAPI<
|
||||
},
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import version from './VersionAPI';
|
||||
import status from './StatusAPI';
|
||||
import StatusAPI, { StatusAPIOptions } from './StatusAPI';
|
||||
import Express, { ExpressApplication } from '../Utils/Express';
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
type InitFunction = (appName: string) => void;
|
||||
export interface InitOptions {
|
||||
appName: string;
|
||||
statusOptions: StatusAPIOptions;
|
||||
}
|
||||
|
||||
const init: InitFunction = (appName: string): void => {
|
||||
app.use([`/${appName}`, '/'], version);
|
||||
app.use([`/${appName}`, '/'], status);
|
||||
type InitFunction = (data: InitOptions) => void;
|
||||
|
||||
const init: InitFunction = (data: InitOptions): void => {
|
||||
app.use([`/${data.appName}`, '/'], version);
|
||||
app.use([`/${data.appName}`, '/'], StatusAPI.init(data.statusOptions));
|
||||
};
|
||||
|
||||
export default init;
|
||||
|
||||
@@ -111,7 +111,7 @@ router.post(
|
||||
return Response.sendErrorResponse(req, res, err as Exception);
|
||||
}
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -4,26 +4,78 @@ import Express, {
|
||||
ExpressRouter,
|
||||
} from '../Utils/Express';
|
||||
import LocalCache from '../Infrastructure/LocalCache';
|
||||
import Response from '../Utils/Response';
|
||||
import ServerException from 'Common/Types/Exception/ServerException';
|
||||
import logger from '../Utils/Logger';
|
||||
import Exception from 'Common/Types/Exception/Exception';
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
export interface StatusAPIOptions {
|
||||
readyCheck: () => Promise<void>;
|
||||
liveCheck: () => Promise<void>;
|
||||
}
|
||||
|
||||
router.get('/app-name', (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.send({ app: LocalCache.getString('app', 'name') });
|
||||
});
|
||||
export default class StatusAPI {
|
||||
public static init(options: StatusAPIOptions): ExpressRouter {
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
// General status
|
||||
router.get('/status', (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.send({ status: 'ok' });
|
||||
});
|
||||
router.get(
|
||||
'/app-name',
|
||||
(_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.send({ app: LocalCache.getString('app', 'name') });
|
||||
}
|
||||
);
|
||||
|
||||
//Healthy probe
|
||||
router.get('/status/healthy', (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.send({ status: 'healthy' });
|
||||
});
|
||||
// General status
|
||||
router.get('/status', (req: ExpressRequest, res: ExpressResponse) => {
|
||||
Response.sendJsonObjectResponse(req, res, {
|
||||
status: 'ok',
|
||||
});
|
||||
});
|
||||
|
||||
//Liveness probe
|
||||
router.get('/status/live', (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.send({ status: 'live' });
|
||||
});
|
||||
//Healthy probe
|
||||
router.get(
|
||||
'/status/ready',
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
logger.info('Ready check');
|
||||
await options.readyCheck();
|
||||
Response.sendJsonObjectResponse(req, res, {
|
||||
status: 'ok',
|
||||
});
|
||||
} catch (e) {
|
||||
Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
e instanceof Exception
|
||||
? e
|
||||
: new ServerException('Server is not ready')
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
//Liveness probe
|
||||
router.get(
|
||||
'/status/live',
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
logger.info('Live check');
|
||||
await options.readyCheck();
|
||||
Response.sendJsonObjectResponse(req, res, {
|
||||
status: 'ok',
|
||||
});
|
||||
} catch (e) {
|
||||
Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
e instanceof Exception
|
||||
? e
|
||||
: new ServerException('Server is not ready')
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,7 @@ import Response from '../Utils/Response';
|
||||
import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import StatusPageFooterLinkService from '../Services/StatusPageFooterLinkService';
|
||||
import {
|
||||
LIMIT_INFINITY,
|
||||
LIMIT_PER_PROJECT,
|
||||
} from 'Common/Types/Database/LimitMax';
|
||||
import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
|
||||
import StatusPageFooterLink from 'Model/Models/StatusPageFooterLink';
|
||||
import StatusPageHeaderLinkService from '../Services/StatusPageHeaderLinkService';
|
||||
import StatusPageHeaderLink from 'Model/Models/StatusPageHeaderLink';
|
||||
@@ -53,8 +50,6 @@ import ScheduledMaintenanceStateTimelineService from '../Services/ScheduledMaint
|
||||
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
|
||||
import Query from '../Types/Database/Query';
|
||||
import JSONFunctions from 'Common/Types/JSONFunctions';
|
||||
import GreenlockChallenge from 'Model/Models/GreenlockChallenge';
|
||||
import GreenlockChallengeService from '../Services/GreenlockChallengeService';
|
||||
import NotFoundException from 'Common/Types/Exception/NotFoundException';
|
||||
import logger from '../Utils/Logger';
|
||||
import Email from 'Common/Types/Email';
|
||||
@@ -77,6 +72,8 @@ import CommonAPI from './CommonAPI';
|
||||
import Phone from 'Common/Types/Phone';
|
||||
import StatusPageHistoryChartBarColorRule from 'Model/Models/StatusPageHistoryChartBarColorRule';
|
||||
import StatusPageHistoryChartBarColorRuleService from '../Services/StatusPageHistoryChartBarColorRuleService';
|
||||
import AcmeChallenge from 'Model/Models/AcmeChallenge';
|
||||
import AcmeChallengeService from '../Services/AcmeChallengeService';
|
||||
|
||||
export default class StatusPageAPI extends BaseAPI<
|
||||
StatusPage,
|
||||
@@ -125,7 +122,7 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -135,8 +132,8 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
.getCrudApiPath()
|
||||
?.toString()}/.well-known/acme-challenge/:token`,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const challenge: GreenlockChallenge | null =
|
||||
await GreenlockChallengeService.findOneBy({
|
||||
const challenge: AcmeChallenge | null =
|
||||
await AcmeChallengeService.findOneBy({
|
||||
query: {
|
||||
token: req.params['token'] as string,
|
||||
},
|
||||
@@ -748,6 +745,7 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
monitorId: true,
|
||||
createdAt: true,
|
||||
endsAt: true,
|
||||
startsAt: true,
|
||||
monitorStatus: {
|
||||
name: true,
|
||||
color: true,
|
||||
@@ -755,10 +753,10 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
} as any,
|
||||
},
|
||||
sort: {
|
||||
createdAt: SortOrder.Ascending,
|
||||
createdAt: SortOrder.Descending,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_INFINITY, // This can be optimized.
|
||||
limit: LIMIT_MAX, // This can be optimized.
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
@@ -776,6 +774,7 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
monitorId: true,
|
||||
createdAt: true,
|
||||
endsAt: true,
|
||||
startsAt: true,
|
||||
monitorStatus: {
|
||||
name: true,
|
||||
color: true,
|
||||
@@ -783,15 +782,32 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
} as any,
|
||||
},
|
||||
sort: {
|
||||
createdAt: SortOrder.Ascending,
|
||||
createdAt: SortOrder.Descending,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_INFINITY, // This can be optimized.
|
||||
limit: LIMIT_MAX, // This can be optimized.
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// sort monitorStatusTimelines by createdAt.
|
||||
monitorStatusTimelines = monitorStatusTimelines.sort(
|
||||
(
|
||||
a: MonitorStatusTimeline,
|
||||
b: MonitorStatusTimeline
|
||||
) => {
|
||||
if (!a.createdAt || !b.createdAt) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (
|
||||
b.createdAt!.getTime() -
|
||||
a.createdAt!.getTime()
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// check if status page has active incident.
|
||||
@@ -1002,9 +1018,8 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
},
|
||||
select: scheduledEventsSelect,
|
||||
sort: {
|
||||
createdAt: SortOrder.Ascending,
|
||||
startsAt: SortOrder.Ascending,
|
||||
},
|
||||
|
||||
skip: 0,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
props: {
|
||||
@@ -1023,9 +1038,8 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
},
|
||||
select: scheduledEventsSelect,
|
||||
sort: {
|
||||
createdAt: SortOrder.Ascending,
|
||||
startsAt: SortOrder.Ascending,
|
||||
},
|
||||
|
||||
skip: 0,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
props: {
|
||||
@@ -1219,7 +1233,7 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
) => {
|
||||
try {
|
||||
await this.subscribeToStatusPage(req);
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
@@ -1265,7 +1279,7 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
try {
|
||||
await this.subscribeToStatusPage(req);
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
@@ -2049,7 +2063,13 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
|
||||
await StatusPageSubscriberService.updateOneById({
|
||||
id: statusPageSubscriber.id!,
|
||||
data: statusPageSubscriber,
|
||||
data: {
|
||||
statusPageResources:
|
||||
statusPageSubscriber.statusPageResources!,
|
||||
isSubscribedToAllResources:
|
||||
statusPageSubscriber.isSubscribedToAllResources!,
|
||||
isUnsubscribed: statusPageSubscriber.isUnsubscribed,
|
||||
} as any,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
|
||||
259
CommonServer/API/StatusPageDomainAPI.ts
Normal file
259
CommonServer/API/StatusPageDomainAPI.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import StatusPageDomain from 'Model/Models/StatusPageDomain';
|
||||
import BaseAPI from './BaseAPI';
|
||||
import StatusPageDomainService, {
|
||||
Service as StatusPageDomainServiceType,
|
||||
} from '../Services/StatusPageDomainService';
|
||||
import {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
NextFunction,
|
||||
} from '../Utils/Express';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import Response from '../Utils/Response';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import UserMiddleware from '../Middleware/UserAuthorization';
|
||||
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
|
||||
import CommonAPI from './CommonAPI';
|
||||
import logger from '../Utils/Logger';
|
||||
import PositiveNumber from 'Common/Types/PositiveNumber';
|
||||
import { StatusPageCNameRecord } from '../EnvironmentConfig';
|
||||
|
||||
export default class StatusPageDomainAPI extends BaseAPI<
|
||||
StatusPageDomain,
|
||||
StatusPageDomainServiceType
|
||||
> {
|
||||
public constructor() {
|
||||
super(StatusPageDomain, StatusPageDomainService);
|
||||
|
||||
// CNAME verification api. THis API will be used from the dashboard to validate the CNAME MANUALLY.
|
||||
this.router.get(
|
||||
`${new this.entityType()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/verify-cname/:id`,
|
||||
UserMiddleware.getUserMiddleware,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
if (!StatusPageCNameRecord) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
`Custom Domains not enabled for this
|
||||
OneUptime installation. Please contact
|
||||
your server admin to enable this
|
||||
feature.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const databaseProps: DatabaseCommonInteractionProps =
|
||||
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
||||
|
||||
const id: ObjectID = new ObjectID(
|
||||
req.params['id'] as string
|
||||
);
|
||||
|
||||
// check if the user can read the domain.
|
||||
|
||||
const domainCount: PositiveNumber =
|
||||
await StatusPageDomainService.countBy({
|
||||
query: {
|
||||
_id: id.toString(),
|
||||
},
|
||||
props: databaseProps,
|
||||
});
|
||||
|
||||
if (domainCount.toNumber() === 0) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'The domain does not exist or user does not have access to it.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const domain: StatusPageDomain | null =
|
||||
await StatusPageDomainService.findOneBy({
|
||||
query: {
|
||||
_id: id.toString(),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
fullDomain: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!domain) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('Invalid token.')
|
||||
);
|
||||
}
|
||||
|
||||
if (!domain.fullDomain) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('Invalid domain.')
|
||||
);
|
||||
}
|
||||
|
||||
const isValid: boolean =
|
||||
await StatusPageDomainService.isCnameValid(
|
||||
domain.fullDomain!
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'CNAME is not verified. Please make sure you have the correct record and please verify CNAME again. If you are sure that the record is correct, please wait for some time for the DNS to propagate.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Provision SSL API. THis API will be used from the dashboard to validate the CNAME MANUALLY.
|
||||
this.router.get(
|
||||
`${new this.entityType()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/order-ssl/:id`,
|
||||
UserMiddleware.getUserMiddleware,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
if (!StatusPageCNameRecord) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
`Custom Domains not enabled for this
|
||||
OneUptime installation. Please contact
|
||||
your server admin to enable this
|
||||
feature.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const databaseProps: DatabaseCommonInteractionProps =
|
||||
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
||||
|
||||
const id: ObjectID = new ObjectID(
|
||||
req.params['id'] as string
|
||||
);
|
||||
|
||||
// check if the user can read the domain.
|
||||
|
||||
const domainCount: PositiveNumber =
|
||||
await StatusPageDomainService.countBy({
|
||||
query: {
|
||||
_id: id.toString(),
|
||||
},
|
||||
props: databaseProps,
|
||||
});
|
||||
|
||||
if (domainCount.toNumber() === 0) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'The domain does not exist or user does not have access to it.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const domain: StatusPageDomain | null =
|
||||
await StatusPageDomainService.findOneBy({
|
||||
query: {
|
||||
_id: id.toString(),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
fullDomain: true,
|
||||
cnameVerificationToken: true,
|
||||
isCnameVerified: true,
|
||||
isSslProvisioned: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!domain) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('Invalid token.')
|
||||
);
|
||||
}
|
||||
|
||||
if (!domain.cnameVerificationToken) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('Invalid token.')
|
||||
);
|
||||
}
|
||||
|
||||
if (!domain.isCnameVerified) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'CNAME is not verified. Please verify CNAME first before you provision SSL.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (domain.isSslProvisioned) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('SSL is already provisioned.')
|
||||
);
|
||||
}
|
||||
|
||||
if (!domain.fullDomain) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('Invalid domain.')
|
||||
);
|
||||
}
|
||||
|
||||
logger.info('Ordering SSL');
|
||||
|
||||
// provision SSL
|
||||
await StatusPageDomainService.orderCert(domain);
|
||||
|
||||
logger.info(
|
||||
'SSL Provisioned for domain - ' + domain.fullDomain
|
||||
);
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ export default class UserCallAPI extends BaseAPI<
|
||||
},
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -115,7 +115,7 @@ export default class UserCallAPI extends BaseAPI<
|
||||
|
||||
await this.service.resendVerificationCode(req.body.itemId);
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export default class UserEmailAPI extends BaseAPI<
|
||||
},
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -116,7 +116,7 @@ export default class UserEmailAPI extends BaseAPI<
|
||||
|
||||
await this.service.resendVerificationCode(req.body.itemId);
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> {
|
||||
},
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -111,7 +111,7 @@ export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> {
|
||||
|
||||
await this.service.resendVerificationCode(req.body.itemId);
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import Hostname from 'Common/Types/API/Hostname';
|
||||
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import BillingConfig from './BillingConfig';
|
||||
import Email from 'Common/Types/Email';
|
||||
import Protocol from 'Common/Types/API/Protocol';
|
||||
|
||||
export enum ConfigLogLevel {
|
||||
INFO = 'INFO',
|
||||
@@ -24,6 +26,14 @@ export const DatabaseHost: Hostname = Hostname.fromString(
|
||||
process.env['DATABASE_HOST'] || 'postgres'
|
||||
);
|
||||
|
||||
export const LetsEncryptNotificationEmail: Email = Email.fromString(
|
||||
process.env['LETS_ENCRYPT_NOTIFICATION_EMAIL'] ||
|
||||
'notifications@example.com'
|
||||
);
|
||||
|
||||
export const LetsEncryptAccountKey: string =
|
||||
process.env['LETS_ENCRYPT_ACCOUNT_KEY'] || '';
|
||||
|
||||
export const DatabasePort: Port = new Port(
|
||||
process.env['DATABASE_PORT'] || '5432'
|
||||
);
|
||||
@@ -136,6 +146,9 @@ export const ClickhouseHost: Hostname = Hostname.fromString(
|
||||
process.env['CLICKHOUSE_HOST'] || 'clickhouse'
|
||||
);
|
||||
|
||||
export const StatusPageCNameRecord: string =
|
||||
process.env['STATUS_PAGE_CNAME_RECORD'] || '';
|
||||
|
||||
export const ClickhousePort: Port = new Port(
|
||||
process.env['CLICKHOUSE_PORT'] || '8123'
|
||||
);
|
||||
@@ -155,3 +168,8 @@ export const AppVersion: string = process.env['APP_VERSION'] || 'unknown';
|
||||
|
||||
export const LogLevel: ConfigLogLevel =
|
||||
(process.env['LOG_LEVEL'] as ConfigLogLevel) || ConfigLogLevel.ERROR;
|
||||
|
||||
export const HttpProtocol: Protocol =
|
||||
process.env['HTTP_PROTOCOL'] === 'https' ? Protocol.HTTPS : Protocol.HTTP;
|
||||
|
||||
export const Host: string = process.env['HOST'] || '';
|
||||
|
||||
@@ -98,6 +98,32 @@ export default class ClickhouseDatabase {
|
||||
this.dataSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async checkConnnectionStatus(): Promise<boolean> {
|
||||
// Ping clickhouse to check if the connection is still alive
|
||||
try {
|
||||
const result: PingResult | undefined =
|
||||
await this.getDataSource()?.ping();
|
||||
|
||||
if (!result) {
|
||||
throw new DatabaseNotConnectedException(
|
||||
'Clickhouse Database is not connected'
|
||||
);
|
||||
}
|
||||
|
||||
if (result?.success === false) {
|
||||
throw new DatabaseNotConnectedException(
|
||||
'Clickhouse Database is not connected'
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error('Clickhouse Connection Lost');
|
||||
logger.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const ClickhouseAppInstance: ClickhouseDatabase =
|
||||
|
||||
@@ -71,6 +71,26 @@ export default class Database {
|
||||
this.dataSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async checkConnnectionStatus(): Promise<boolean> {
|
||||
// check popstgres connection to see if it is still alive
|
||||
|
||||
try {
|
||||
const result: any = await this.dataSource?.query(
|
||||
`SELECT COUNT(domain) FROM "AcmeChallenge"`
|
||||
); // this is a dummy query to check if the connection is still alive
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error('Postgres Connection Lost');
|
||||
logger.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const PostgresAppInstance: Database = new Database();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user