mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
314 Commits
eslint
...
telemetry-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bfc0be25d | ||
|
|
d9ce813689 | ||
|
|
93f1b97e88 | ||
|
|
c617e49a63 | ||
|
|
de7d06e5d7 | ||
|
|
9b862162b6 | ||
|
|
0ecf3fa1e0 | ||
|
|
b5fa564cb3 | ||
|
|
b771e1486e | ||
|
|
3f1479d8f5 | ||
|
|
dcdf1e21ae | ||
|
|
cc3f003be5 | ||
|
|
0e53e26695 | ||
|
|
ad1e5fefc6 | ||
|
|
8ea77dc6d1 | ||
|
|
2222ca301f | ||
|
|
0f66c70205 | ||
|
|
88dc64f182 | ||
|
|
94c50b980a | ||
|
|
9866919873 | ||
|
|
e025f996e4 | ||
|
|
cd4c326f6e | ||
|
|
c668731389 | ||
|
|
e6a10edca6 | ||
|
|
847f7fa5c9 | ||
|
|
03cb9a67b4 | ||
|
|
9791aa1259 | ||
|
|
0df7209723 | ||
|
|
0de8e2d818 | ||
|
|
5f9a1091de | ||
|
|
6ef17b1720 | ||
|
|
9c41a20ebb | ||
|
|
eddb5a81c9 | ||
|
|
3861d09930 | ||
|
|
733901a870 | ||
|
|
131c3034d6 | ||
|
|
0c9175c728 | ||
|
|
e2eff652e3 | ||
|
|
43e9530fdb | ||
|
|
0000cbc53e | ||
|
|
8b26cac8f0 | ||
|
|
e8417b23d9 | ||
|
|
1bd5671d17 | ||
|
|
56d3e7e1f2 | ||
|
|
34888e9b7f | ||
|
|
7c09423766 | ||
|
|
9ba8251c58 | ||
|
|
7a5efd99ca | ||
|
|
1e595be586 | ||
|
|
1d63168606 | ||
|
|
82601a3e8f | ||
|
|
17bf63e83b | ||
|
|
885a6ca36e | ||
|
|
dec35bbc03 | ||
|
|
77dcb16e8b | ||
|
|
b10a4c7db8 | ||
|
|
8d82f1128b | ||
|
|
056c069c69 | ||
|
|
0466950c2d | ||
|
|
4442437ad7 | ||
|
|
08f39af9ec | ||
|
|
7bf58d23bc | ||
|
|
afb0cbcc2a | ||
|
|
7393d9c2bc | ||
|
|
7b8efb2785 | ||
|
|
efea6f4c11 | ||
|
|
8dbf724f36 | ||
|
|
fa4c705023 | ||
|
|
106e41206e | ||
|
|
dc0c71b063 | ||
|
|
0cadb54117 | ||
|
|
0ba315342c | ||
|
|
d87b292691 | ||
|
|
ea3abf2ca0 | ||
|
|
3e507c0259 | ||
|
|
5385c8e65c | ||
|
|
771ad54110 | ||
|
|
6743193872 | ||
|
|
b94d862cae | ||
|
|
77d9b2f98f | ||
|
|
2565087dba | ||
|
|
5f330b5ea6 | ||
|
|
c5f6816d7b | ||
|
|
585b98c83d | ||
|
|
40635d5b07 | ||
|
|
fd96f7a1ec | ||
|
|
f585ffe756 | ||
|
|
9a86978c9d | ||
|
|
3bf87cf9eb | ||
|
|
b7b5288f9c | ||
|
|
2833dbb474 | ||
|
|
2a6dda5fe2 | ||
|
|
e9e5b5a329 | ||
|
|
c0e055343e | ||
|
|
c657397d3b | ||
|
|
91cf4995b1 | ||
|
|
7b7c1e5951 | ||
|
|
b67583f8d3 | ||
|
|
460b8459d4 | ||
|
|
8328ece011 | ||
|
|
6f4045ffe9 | ||
|
|
54ce2c71c0 | ||
|
|
0a00cd9581 | ||
|
|
162c5d9db9 | ||
|
|
8be8c23ed9 | ||
|
|
f2b6fffb4c | ||
|
|
626312d495 | ||
|
|
77287868c4 | ||
|
|
f7d221900a | ||
|
|
d51a420532 | ||
|
|
0567f92ddd | ||
|
|
e88e29f9e5 | ||
|
|
e50f6e14f0 | ||
|
|
933b3e96a1 | ||
|
|
00b67974ed | ||
|
|
a05a7d9122 | ||
|
|
fdf440f308 | ||
|
|
34c6a32c48 | ||
|
|
0b078226f3 | ||
|
|
e0a5927bd1 | ||
|
|
bed1f0dbb5 | ||
|
|
4fef8ccfb5 | ||
|
|
bb29b4be4b | ||
|
|
078d89d8ae | ||
|
|
6b4727bc9f | ||
|
|
95aaa68010 | ||
|
|
976686b5af | ||
|
|
f59064108e | ||
|
|
44d8254e7d | ||
|
|
d284a6ff54 | ||
|
|
1908f02447 | ||
|
|
f50e20d896 | ||
|
|
bd8ed04e1b | ||
|
|
27c54221cb | ||
|
|
cf589ba0c6 | ||
|
|
43eaff3a6d | ||
|
|
30e96de2d9 | ||
|
|
43c4f44f1f | ||
|
|
af6f5af11d | ||
|
|
79603c9bb4 | ||
|
|
e2074b010e | ||
|
|
2187edc158 | ||
|
|
a9778cf9d0 | ||
|
|
c450c3fa1b | ||
|
|
3aaed9c901 | ||
|
|
70a9944f6a | ||
|
|
ad48aae0ba | ||
|
|
14f9950c38 | ||
|
|
4acedfdd62 | ||
|
|
deff7c9464 | ||
|
|
b407286ef0 | ||
|
|
9348076df0 | ||
|
|
09a7e155b7 | ||
|
|
127cc6b9b1 | ||
|
|
ea5474527c | ||
|
|
9111b831a7 | ||
|
|
dcabdd0a55 | ||
|
|
058b585eda | ||
|
|
25da20a968 | ||
|
|
4f4d9946ff | ||
|
|
0987634e54 | ||
|
|
c099f3a3ef | ||
|
|
a3f5e268b5 | ||
|
|
2599841d37 | ||
|
|
6c8a0c8cdc | ||
|
|
0d24bb6351 | ||
|
|
a4c5354152 | ||
|
|
a906713f6e | ||
|
|
e85e351797 | ||
|
|
3f9c1fd46e | ||
|
|
6c3edbfcb0 | ||
|
|
d3f212c9ec | ||
|
|
1964db22f1 | ||
|
|
b6a70bbd6c | ||
|
|
f36609712b | ||
|
|
6737d97f30 | ||
|
|
d22b40e91a | ||
|
|
e2e4db5073 | ||
|
|
2415f97ec6 | ||
|
|
0910c9f6f6 | ||
|
|
1a02f5b73e | ||
|
|
b6cabf12d5 | ||
|
|
27ef73be20 | ||
|
|
00b35c4d9a | ||
|
|
369bdfc841 | ||
|
|
8150f44244 | ||
|
|
6a646c06b3 | ||
|
|
422656caf3 | ||
|
|
5223d1bd6d | ||
|
|
42f0b90291 | ||
|
|
3d4f28fc0f | ||
|
|
e74c6a7030 | ||
|
|
6be976102c | ||
|
|
261072b1fd | ||
|
|
e7659bdf08 | ||
|
|
2a8c45cea0 | ||
|
|
b197416247 | ||
|
|
fe0e745640 | ||
|
|
28ea4d9b83 | ||
|
|
3d7cb148d0 | ||
|
|
0d0760e450 | ||
|
|
a6f66a59bf | ||
|
|
f09419ef50 | ||
|
|
4c90dc66a0 | ||
|
|
99c2e34ab5 | ||
|
|
0fb0e9b047 | ||
|
|
7ae6a3c5ea | ||
|
|
59f0526936 | ||
|
|
bfcd5c1753 | ||
|
|
76df1a5889 | ||
|
|
422ee6c192 | ||
|
|
808b3512f3 | ||
|
|
742796fd67 | ||
|
|
9027369e10 | ||
|
|
a87f3ac896 | ||
|
|
db75cc1a5b | ||
|
|
2deeb32b78 | ||
|
|
bc8b8eb982 | ||
|
|
ef3fe66e72 | ||
|
|
17a0b65a4b | ||
|
|
01a1a0f69e | ||
|
|
1260e482cf | ||
|
|
6e5081a4e3 | ||
|
|
dd93998296 | ||
|
|
94956b045a | ||
|
|
b2f650a865 | ||
|
|
4390a37184 | ||
|
|
9b08d1a9e4 | ||
|
|
dbef1071e0 | ||
|
|
98fbbe301e | ||
|
|
6805083725 | ||
|
|
27213fa235 | ||
|
|
baa98e333e | ||
|
|
ccdcf2c679 | ||
|
|
85edd12c2d | ||
|
|
97cc28b182 | ||
|
|
b0041e6993 | ||
|
|
a66d78743d | ||
|
|
bc78491478 | ||
|
|
7751870ccf | ||
|
|
df6ffb15d4 | ||
|
|
df20f343e9 | ||
|
|
e4ade513ce | ||
|
|
3c2af1dc38 | ||
|
|
af5d714642 | ||
|
|
3f315be279 | ||
|
|
095493cec9 | ||
|
|
3bdf87f34f | ||
|
|
26bb6f1e74 | ||
|
|
20db81a5f6 | ||
|
|
f9b097a112 | ||
|
|
b70b70e27e | ||
|
|
93df459662 | ||
|
|
1aceb81c85 | ||
|
|
023aea94c2 | ||
|
|
d8dff468ab | ||
|
|
082d800c34 | ||
|
|
e7f4c962b8 | ||
|
|
f6074fe8f4 | ||
|
|
b45678f167 | ||
|
|
6d53678135 | ||
|
|
7b77dd4a53 | ||
|
|
51bb54e771 | ||
|
|
48b095f548 | ||
|
|
786ec6ce7a | ||
|
|
401fbb58e3 | ||
|
|
5243ae6b8d | ||
|
|
a06a0f1d16 | ||
|
|
e0bc906484 | ||
|
|
139ff2d638 | ||
|
|
7be603ef08 | ||
|
|
d23548f984 | ||
|
|
3573e59634 | ||
|
|
c4e4e7e488 | ||
|
|
be3e8447ab | ||
|
|
2615e39c19 | ||
|
|
49b6550581 | ||
|
|
e870efc3c4 | ||
|
|
38862adf5a | ||
|
|
06b92e2745 | ||
|
|
1a8e0682c7 | ||
|
|
e0189356d5 | ||
|
|
dad07b9f80 | ||
|
|
8d59710306 | ||
|
|
5e9227eb4f | ||
|
|
1e63173564 | ||
|
|
76fded39ee | ||
|
|
c82f3c1f71 | ||
|
|
24d9219a62 | ||
|
|
6ed588d7aa | ||
|
|
17c1862eac | ||
|
|
37b1846363 | ||
|
|
da3e6bc3af | ||
|
|
a35ea2ba66 | ||
|
|
b2fa2e40f4 | ||
|
|
babecb5170 | ||
|
|
6e0e643337 | ||
|
|
2a59faa6b4 | ||
|
|
9d7d44afda | ||
|
|
8f1e67da3a | ||
|
|
c09f863d58 | ||
|
|
3c8c2a3feb | ||
|
|
4c4169e245 | ||
|
|
34c5cb1e94 | ||
|
|
f5fca46e5b | ||
|
|
48e3c24c6e | ||
|
|
e9c94876c0 | ||
|
|
fcd6c8ea7d | ||
|
|
81726ce7b2 | ||
|
|
6349c60bdb | ||
|
|
74cf119444 | ||
|
|
2efb630640 | ||
|
|
f66241d12f | ||
|
|
070190dd31 |
@@ -56,5 +56,4 @@ settings.json
|
||||
|
||||
GoSDK/tester/
|
||||
|
||||
Llama/Models/*
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: 'Bug: <Title of the issue>'
|
||||
title: '<Title of the issue>'
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: 'Enhancement: <Title of the issue>'
|
||||
title: '<Title of the issue>'
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
|
||||
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
@@ -179,6 +179,21 @@ jobs:
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./Ingestor/Dockerfile .
|
||||
|
||||
docker-build-telemetry-ingestor:
|
||||
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 probe api
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./TelemetryIngestor/Dockerfile .
|
||||
|
||||
docker-build-status-page:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
14
.github/workflows/compile.yml
vendored
14
.github/workflows/compile.yml
vendored
@@ -214,6 +214,20 @@ jobs:
|
||||
- run: cd Model && npm install
|
||||
- run: cd CommonServer && npm install
|
||||
- run: cd Ingestor && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-telemetry-ingestor:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: cd Common && npm install
|
||||
- run: cd Model && npm install
|
||||
- run: cd CommonServer && npm install
|
||||
- run: cd TelemetryIngestor && npm install && npm run compile && npm run dep-check
|
||||
|
||||
|
||||
compile-status-page:
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
name: Misc / Dependabot Automerge
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ahmadnassri/action-dependabot-auto-merge@v2
|
||||
with:
|
||||
target: minor
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
31
.github/workflows/playwright.yml.skip
vendored
31
.github/workflows/playwright.yml.skip
vendored
@@ -1,31 +0,0 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BASE_URL: http://localhost
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run Server in Docker
|
||||
run: npm run dev
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: cd Playwright && npm install && npx playwright install && npx playwright test
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
380
.github/workflows/release.yml
vendored
380
.github/workflows/release.yml
vendored
@@ -18,35 +18,12 @@ jobs:
|
||||
token: ${{secrets.github_token}}
|
||||
- run: echo "Build number is ${{ steps.buildnumber.outputs.build_number }}"
|
||||
|
||||
github-release:
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/release'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
- run: echo "${{needs.generate-build-number.outputs.build_number}}"
|
||||
- name: "Build Changelog"
|
||||
id: build_changelog
|
||||
uses: mikepenz/release-changelog-builder-action@v4.2.0
|
||||
with:
|
||||
configuration: "./Scripts/Release/ChangelogConfig.json"
|
||||
- run: echo "Changelog:"
|
||||
- run: echo "${{steps.build_changelog.outputs.changelog}}"
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: "7.0.${{needs.generate-build-number.outputs.build_number}}"
|
||||
artifactErrorsFailBuild: true
|
||||
body: |
|
||||
${{steps.build_changelog.outputs.changelog}}
|
||||
|
||||
|
||||
|
||||
helm-chart-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
env:
|
||||
CI_COMMIT_AUTHOR: Continuous Integration
|
||||
steps:
|
||||
@@ -92,7 +69,7 @@ jobs:
|
||||
git push origin master
|
||||
|
||||
nginx-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -152,7 +129,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
e2e-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -212,7 +189,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
isolated-vm-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -272,7 +249,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
test-server-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -332,7 +309,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
otel-collector-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -394,7 +371,7 @@ jobs:
|
||||
|
||||
|
||||
status-page-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -454,7 +431,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
test-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -514,7 +491,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
ingestor-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -574,7 +551,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
probe-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -635,7 +612,7 @@ jobs:
|
||||
|
||||
|
||||
haraka-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -695,7 +672,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
admin-dashboard-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -756,7 +733,7 @@ jobs:
|
||||
|
||||
|
||||
dashboard-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -816,7 +793,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
app-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -877,7 +854,7 @@ jobs:
|
||||
|
||||
|
||||
copilot-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -937,7 +914,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
accounts-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -999,7 +976,7 @@ jobs:
|
||||
|
||||
publish-npm-packages:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
NPM_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
|
||||
@@ -1011,9 +988,225 @@ jobs:
|
||||
run: npm run prerun
|
||||
- name: Publish Infrastructure Agent
|
||||
run: bash ./Scripts/NPM/PublishAllPackages.sh
|
||||
|
||||
|
||||
llm-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
|
||||
- name: Docker Meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
oneuptime/llm
|
||||
ghcr.io/oneuptime/llm
|
||||
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
|
||||
|
||||
# Cannot do this, no space on the runner
|
||||
# - name: Download the Model from Hugging Face
|
||||
# run: mkdir -p ./LLM/Models && cd ./LLM/Models && git clone https://${{ secrets.HUGGING_FACE_USERNAME }}:${{ secrets.HUGGING_FACE_PASSWORD }}@huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct
|
||||
|
||||
- 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 nginx.
|
||||
|
||||
- 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: ./LLM/Dockerfile
|
||||
context: ./LLM
|
||||
# arm64 is not supported by the base image of the LLM
|
||||
platforms: linux/amd64
|
||||
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-e2e-release-saas:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
test-e2e-release-self-hosted:
|
||||
runs-on: ubuntu-latest
|
||||
# After all the jobs runs
|
||||
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
github-release:
|
||||
needs: [test-e2e-release-saas, test-e2e-release-self-hosted, generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/release'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
- run: echo "${{needs.generate-build-number.outputs.build_number}}"
|
||||
- name: "Build Changelog"
|
||||
id: build_changelog
|
||||
uses: mikepenz/release-changelog-builder-action@v4.2.0
|
||||
with:
|
||||
configuration: "./Scripts/Release/ChangelogConfig.json"
|
||||
- run: echo "Changelog:"
|
||||
- run: echo "${{steps.build_changelog.outputs.changelog}}"
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: "7.0.${{needs.generate-build-number.outputs.build_number}}"
|
||||
artifactErrorsFailBuild: true
|
||||
body: |
|
||||
${{steps.build_changelog.outputs.changelog}}
|
||||
|
||||
|
||||
infrastructure-agent-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [github-release, generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -1046,106 +1239,3 @@ jobs:
|
||||
tag_name: 7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
test-e2e-release-saas:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, infrastructure-agent-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, github-release, generate-build-number, nginx-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
test-e2e-release-self-hosted:
|
||||
runs-on: ubuntu-latest
|
||||
# After all the jobs runs
|
||||
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, infrastructure-agent-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, github-release, generate-build-number, nginx-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# 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
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
40
.github/workflows/reliability-copilot.yml
vendored
Normal file
40
.github/workflows/reliability-copilot.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
name: "OneUptime Reliability Copilot"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
# Run every day at midnight UTC
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze Code
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
permissions:
|
||||
actions: read
|
||||
# We will never commit to the main/master branch. We will always create a PR.
|
||||
contents: write
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Run Reliability Copilot in Doker Container
|
||||
- name: Run Copilot
|
||||
run: |
|
||||
docker run --rm -v $(pwd):/repository -w /repository oneuptime/copilot:test \
|
||||
-e ONEUPTIME_URL='https://test.oneuptime.com' \
|
||||
-e ONEUPTIME_REPOSITORY_SECRET_KEY=${{ secrets.COPILOT_ONEUPTIME_REPOSITORY_SECRET_KEY }} \
|
||||
-e GITHUB_TOKEN=${{ github.token }} \
|
||||
-e GITHUB_USERNAME='simlarsen' \
|
||||
-e ONEUPTIME_LLAMA_SERVER_URL='http://57.128.112.160:8547'
|
||||
86
.github/workflows/test-release.yaml
vendored
86
.github/workflows/test-release.yaml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
- "master"
|
||||
|
||||
jobs:
|
||||
|
||||
generate-build-number:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
@@ -18,6 +19,89 @@ jobs:
|
||||
token: ${{secrets.github_token}}
|
||||
- run: echo "Build number is ${{ steps.buildnumber.outputs.build_number }}"
|
||||
|
||||
llm-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
|
||||
- name: Docker Meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
oneuptime/llm
|
||||
ghcr.io/oneuptime/llm
|
||||
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
|
||||
|
||||
# Cannot do this, no space on the runner
|
||||
# - name: Download the Model from Hugging Face
|
||||
# run: mkdir -p ./LLM/Models && cd ./LLM/Models && git clone https://${{ secrets.HUGGING_FACE_USERNAME }}:${{ secrets.HUGGING_FACE_PASSWORD }}@huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct
|
||||
|
||||
- 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 nginx.
|
||||
|
||||
- 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: ./LLM/Dockerfile
|
||||
context: ./LLM
|
||||
# arm64 is not supported by the base image of the LLM
|
||||
platforms: linux/amd64
|
||||
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}}
|
||||
|
||||
|
||||
nginx-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
@@ -941,7 +1025,7 @@ jobs:
|
||||
|
||||
test-helm-chart:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [copilot-docker-image-deploy, isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy]
|
||||
needs: [llm-docker-image-deploy, copilot-docker-image-deploy, isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
|
||||
4
.github/workflows/test.e2e.yaml
vendored
4
.github/workflows/test.e2e.yaml
vendored
@@ -32,12 +32,10 @@ jobs:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
|
||||
- run: npm run dev
|
||||
- name: Sleep for 2 minutes to wait for server to start
|
||||
run: sleep 120
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- 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)
|
||||
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
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -93,10 +93,9 @@ Haraka/dkim/keys/public_base64.txt
|
||||
|
||||
HelmChart/Values/*.values.yaml
|
||||
|
||||
Llama/Models/tokenizer*
|
||||
Llama/Models/llama*
|
||||
LLM/__pycache__/*
|
||||
|
||||
Llama/__pycache__/*
|
||||
LLM/Models/*
|
||||
|
||||
Examples/otel-dotnet/obj/*
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ForgotPasswordPage from "./Pages/ForgotPassword";
|
||||
import LoginPage from "./Pages/Login";
|
||||
import LoginWithSSO from "./Pages/LoginWithSSO";
|
||||
import NotFound from "./Pages/NotFound";
|
||||
import RegisterPage from "./Pages/Register";
|
||||
import ResetPasswordPage from "./Pages/ResetPassword";
|
||||
@@ -24,6 +25,8 @@ function App(): ReactElement {
|
||||
<Routes>
|
||||
<Route path="/accounts" element={<LoginPage />} />
|
||||
<Route path="/accounts/login" element={<LoginPage />} />
|
||||
|
||||
<Route path="/accounts/sso" element={<LoginWithSSO />} />
|
||||
<Route
|
||||
path="/accounts/forgot-password"
|
||||
element={<ForgotPasswordPage />}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { LOGIN_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import Alert, { AlertType } from "CommonUI/src/Components/Alerts/Alert";
|
||||
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
@@ -13,7 +12,7 @@ import LoginUtil from "CommonUI/src/Utils/Login";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import UserUtil from "CommonUI/src/Utils/User";
|
||||
import User from "Model/Models/User";
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
@@ -23,12 +22,6 @@ const LoginPage: () => JSX.Element = () => {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}
|
||||
|
||||
const showSsoMessage: boolean = Boolean(
|
||||
Navigation.getQueryStringByName("sso"),
|
||||
);
|
||||
|
||||
const [showSsoTip, setShowSSOTip] = useState<boolean>(false);
|
||||
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
@@ -56,16 +49,6 @@ const LoginPage: () => JSX.Element = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{showSsoMessage && (
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md mt-8">
|
||||
{" "}
|
||||
<Alert
|
||||
type={AlertType.DANGER}
|
||||
title="You must be logged into OneUptime account to use single sign-on (SSO) for your project. Logging in to OneUptime account and single sign on (SSO) for your project are two separate steps. Please use the form below to log in to your OneUptime account before you use SSO."
|
||||
/>{" "}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
@@ -120,23 +103,11 @@ const LoginPage: () => JSX.Element = () => {
|
||||
footer={
|
||||
<div className="actions text-center mt-4 hover:underline fw-semibold">
|
||||
<div>
|
||||
{!showSsoTip && (
|
||||
<div
|
||||
onClick={() => {
|
||||
setShowSSOTip(true);
|
||||
}}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
|
||||
>
|
||||
<Link to={new Route("/accounts/sso")}>
|
||||
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
|
||||
Use single sign-on (SSO) instead
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSsoTip && (
|
||||
<div className="text-gray-500 text-sm">
|
||||
Please sign in with your SSO provider like Okta, Auth0,
|
||||
Entra ID or any other SAML 2.0 provider.
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
235
Accounts/src/Pages/LoginWithSSO.tsx
Normal file
235
Accounts/src/Pages/LoginWithSSO.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
import { SERVICE_PROVIDER_LOGIN_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import { DASHBOARD_URL, IDENTITY_URL } from "CommonUI/src/Config";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import UserUtil from "CommonUI/src/Utils/User";
|
||||
import User from "Model/Models/User";
|
||||
import React, { ReactElement, useState } from "react";
|
||||
import ProjectSSO from "Model/Models/ProjectSso";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import API from "CommonUI/src/Utils/API/API";
|
||||
import BasicForm from "CommonUI/src/Components/Forms/BasicForm";
|
||||
import Email from "Common/Types/Email";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import StaticModelList from "CommonUI/src/Components/ModelList/StaticModelList";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = SERVICE_PROVIDER_LOGIN_URL;
|
||||
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}
|
||||
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [projectSsoConfigList, setProjectSsoConfigList] = useState<
|
||||
Array<ProjectSSO>
|
||||
>([]);
|
||||
|
||||
type FetchSSOConfigsFunction = (email: Email) => Promise<void>;
|
||||
|
||||
const fetchSsoConfigs: FetchSSOConfigsFunction = async (
|
||||
email: Email,
|
||||
): Promise<void> => {
|
||||
if (email) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// get sso config by email.
|
||||
const listResult: HTTPErrorResponse | HTTPResponse<JSONArray> =
|
||||
await API.get(
|
||||
URL.fromString(apiUrl.toString()).addQueryParam(
|
||||
"email",
|
||||
email.toString(),
|
||||
),
|
||||
);
|
||||
|
||||
if (listResult instanceof HTTPErrorResponse) {
|
||||
throw listResult;
|
||||
}
|
||||
|
||||
if (!listResult.data || (listResult.data as JSONArray).length === 0) {
|
||||
setError(
|
||||
"No SSO configuration found for the email: " + email.toString(),
|
||||
);
|
||||
} else {
|
||||
setProjectSsoConfigList(
|
||||
ProjectSSO.fromJSONArray(listResult["data"], ProjectSSO),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(API.getFriendlyErrorMessage(error as Error));
|
||||
}
|
||||
} else {
|
||||
setError("Email is required to perform this action");
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
type GetSsoConfigModelListFunction = (
|
||||
configs: Array<ProjectSSO>,
|
||||
) => ReactElement;
|
||||
|
||||
const getSsoConfigModelList: GetSsoConfigModelListFunction = (
|
||||
configs: Array<ProjectSSO>,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<StaticModelList<ProjectSSO>
|
||||
list={configs}
|
||||
titleField="name"
|
||||
selectedItems={[]}
|
||||
descriptionField="description"
|
||||
onClick={(item: ProjectSSO) => {
|
||||
setIsLoading(true);
|
||||
Navigation.navigate(
|
||||
URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route(
|
||||
`/sso/${item.projectId?.toString()}/${item.id?.toString()}`,
|
||||
),
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
type GetProjectNameFunction = (projectId: string) => string;
|
||||
|
||||
const getProjectName: GetProjectNameFunction = (
|
||||
projectId: string,
|
||||
): string => {
|
||||
const projectNames: Array<string | undefined> = projectSsoConfigList
|
||||
.filter((config: ProjectSSO) => {
|
||||
return config.projectId?.toString() === projectId.toString();
|
||||
})
|
||||
.map((config: ProjectSSO) => {
|
||||
return config.project?.name;
|
||||
});
|
||||
return projectNames[0] || "Project";
|
||||
};
|
||||
|
||||
if (projectSsoConfigList.length > 0 && !error && !isLoading) {
|
||||
const projectIds: Array<string> = projectSsoConfigList.map(
|
||||
(config: ProjectSSO) => {
|
||||
return config.projectId?.toString() as string;
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-10 text-center text-xl tracking-tight text-gray-900">
|
||||
Select Project
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Select the project you want to login to.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{projectIds.map((projectId: string) => {
|
||||
return (
|
||||
<div key={projectId}>
|
||||
<h3 className="mt-6 font-medium tracking-tight">
|
||||
{getProjectName(projectId)}
|
||||
</h3>
|
||||
{getSsoConfigModelList(
|
||||
projectSsoConfigList.filter((config: ProjectSSO) => {
|
||||
return (
|
||||
config.projectId?.toString() === projectId.toString()
|
||||
);
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Login with SSO
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Login with your SSO provider to access your account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<BasicForm
|
||||
modelType={User}
|
||||
id="login-form"
|
||||
error={error}
|
||||
name="Login"
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
},
|
||||
]}
|
||||
maxPrimaryButtonWidth={true}
|
||||
submitButtonText="Login with SSO"
|
||||
onSubmit={async (data: JSONObject) => {
|
||||
await fetchSsoConfigs(data["email"] as Email);
|
||||
}}
|
||||
footer={
|
||||
<div className="actions text-center mt-4 hover:underline fw-semibold">
|
||||
<div>
|
||||
<Link to={new Route("/accounts/login")}>
|
||||
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
|
||||
Use username and password insead.
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/register")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Register.
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
@@ -9,6 +9,10 @@ export const LOGIN_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route("/login"),
|
||||
);
|
||||
|
||||
export const SERVICE_PROVIDER_LOGIN_URL: URL = URL.fromURL(
|
||||
IDENTITY_URL,
|
||||
).addRoute(new Route("/service-provider-login"));
|
||||
|
||||
export const FORGOT_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route("/forgot-password"),
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@
|
||||
applyStylesTo("h3", "mb-5 scroll-mt-24 mt-10 font-bold text-base")
|
||||
applyStylesTo("p", "mb-5")
|
||||
applyStylesTo("link", "text-emerald-500 hover:text-emerald-600")
|
||||
applyStylesTo("model-inline-code", "rounded p-0.5 px-1 text-sm text-gray-50 bg-gray-600 border-2 border-gray-600 shadow")
|
||||
applyStylesTo("inline-code", "rounded p-0.5 px-1 text-sm text-gray-500 bg-gray-100 border-2 border-gray-200")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import BaseAnalyticsAPI from "CommonServer/API/BaseAnalyticsAPI";
|
||||
import BillingInvoiceAPI from "CommonServer/API/BillingInvoiceAPI";
|
||||
import BillingPaymentMethodAPI from "CommonServer/API/BillingPaymentMethodAPI";
|
||||
import CodeRepositoryAPI from "CommonServer/API/CodeRepositoryAPI";
|
||||
import CopilotActionAPI from "CommonServer/API/CopilotActionAPI";
|
||||
import FileAPI from "CommonServer/API/FileAPI";
|
||||
import GlobalConfigAPI from "CommonServer/API/GlobalConfigAPI";
|
||||
import MonitorGroupAPI from "CommonServer/API/MonitorGroupAPI";
|
||||
@@ -30,9 +31,6 @@ import ApiKeyService, {
|
||||
import CallLogService, {
|
||||
Service as CallLogServiceType,
|
||||
} from "CommonServer/Services/CallLogService";
|
||||
import CopilotEventService, {
|
||||
Service as CopilotEventServiceType,
|
||||
} from "CommonServer/Services/CopilotEventService";
|
||||
import DomainService, {
|
||||
Service as DomainServiceType,
|
||||
} from "CommonServer/Services/DomainService";
|
||||
@@ -288,6 +286,15 @@ import WorkflowService, {
|
||||
import WorkflowVariableService, {
|
||||
Service as WorkflowVariableServiceType,
|
||||
} from "CommonServer/Services/WorkflowVariableService";
|
||||
|
||||
import ProbeOwnerTeamService, {
|
||||
Service as ProbeOwnerTeamServiceType,
|
||||
} from "CommonServer/Services/ProbeOwnerTeamService";
|
||||
|
||||
import ProbeOwnerUserService, {
|
||||
Service as ProbeOwnerUserServiceType,
|
||||
} from "CommonServer/Services/ProbeOwnerUserService";
|
||||
|
||||
import FeatureSet from "CommonServer/Types/FeatureSet";
|
||||
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
|
||||
import Log from "Model/AnalyticsModels/Log";
|
||||
@@ -297,7 +304,6 @@ import Span from "Model/AnalyticsModels/Span";
|
||||
import ApiKey from "Model/Models/ApiKey";
|
||||
import ApiKeyPermission from "Model/Models/ApiKeyPermission";
|
||||
import CallLog from "Model/Models/CallLog";
|
||||
import CopilotEvent from "Model/Models/CopilotEvent";
|
||||
import Domain from "Model/Models/Domain";
|
||||
import EmailLog from "Model/Models/EmailLog";
|
||||
import EmailVerificationToken from "Model/Models/EmailVerificationToken";
|
||||
@@ -380,6 +386,8 @@ import UserOnCallLog from "Model/Models/UserOnCallLog";
|
||||
import Workflow from "Model/Models/Workflow";
|
||||
import WorkflowLog from "Model/Models/WorkflowLog";
|
||||
import WorkflowVariable from "Model/Models/WorkflowVariable";
|
||||
import ProbeOwnerTeam from "Model/Models/ProbeOwnerTeam";
|
||||
import ProbeOwnerUser from "Model/Models/ProbeOwnerUser";
|
||||
|
||||
const BaseAPIFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
@@ -387,12 +395,6 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
const APP_NAME: string = "api";
|
||||
|
||||
//attach api's
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<User, UserServiceType>(User, UserService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAnalyticsAPI<Log, LogServiceType>(Log, LogService).getRouter(),
|
||||
@@ -457,6 +459,22 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ProbeOwnerUser, ProbeOwnerUserServiceType>(
|
||||
ProbeOwnerUser,
|
||||
ProbeOwnerUserService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ProbeOwnerTeam, ProbeOwnerTeamServiceType>(
|
||||
ProbeOwnerTeam,
|
||||
ProbeOwnerTeamService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<MonitorSecret, MonitorSecretServiceType>(
|
||||
@@ -510,14 +528,6 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<CopilotEvent, CopilotEventServiceType>(
|
||||
CopilotEvent,
|
||||
CopilotEventService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ServiceCatalogOwnerUser, ServiceCatalogOwnerUserServiceType>(
|
||||
@@ -1029,6 +1039,11 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new CodeRepositoryAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new CopilotActionAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new UserNotificationLogTimelineAPI().getRouter(),
|
||||
@@ -1211,6 +1226,12 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, NotificationAPI);
|
||||
|
||||
//attach api's
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<User, UserServiceType>(User, UserService).getRouter(),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -56,6 +56,37 @@ If you don't like to use npm or do not have it installed, run this instead:
|
||||
sudo bash -c "(export $(grep -v '^#' config.env | xargs) && docker compose up --remove-orphans -d)"
|
||||
```
|
||||
|
||||
|
||||
### Accessing OneUptime
|
||||
|
||||
OneUptime should run at: http://localhost. You need to register a new account for your instance to start using it.
|
||||
|
||||
### Setting up TLS/SSL Certificates
|
||||
|
||||
OneUptime **does not** support setting up SSL/TLS certificates. You need to set up SSL/TLS certificates on your own.
|
||||
|
||||
If you need to use SSL/TLS certificates, follow these steps:
|
||||
|
||||
1. Use a reverse proxy like Nginx or Caddy.
|
||||
2. Use Let's Encrypt to provision the certificates.
|
||||
3. Point the reverse proxy to the OneUptime server.
|
||||
4. Update the following settings:
|
||||
- Set `HTTP_PROTOCOL` env var to `https`.
|
||||
- Change `HOST` env var to the domain name of the server where the reverse proxy is hosted.
|
||||
|
||||
## Production Readiness Checklist
|
||||
|
||||
Ideally do not deploy OneUptime in production with docker-compose. We highly recommend using Kubernetes. There's a helm chart available for OneUptime [here](https://artifacthub.io/packages/helm/oneuptime/oneuptime).
|
||||
|
||||
If you still want to deploy OneUptime in production with docker-compose, please consider the following:
|
||||
|
||||
- **SSL/TLS**: Set up SSL/TLS certificates. OneUptime does not support setting up SSL/TLS certificates. You need to set up SSL/TLS certificates on your own. Please see above.
|
||||
- **Secrets**: Make sure you have random secrets in your `config.env` file. There are some default secrets in that file. Please replace them with random long strings.
|
||||
- **Backups**: Regularly backup your databases (Clickhouse, Postgres). Redis is used as a cache and is stateless and can be safely ignored.
|
||||
- **Updates**: Please regularly update OneUptime. We release updates every day. We recommend you to update the software aleast once a week if you're running in production.
|
||||
|
||||
### Updating OneUptime
|
||||
|
||||
To update:
|
||||
|
||||
```
|
||||
@@ -64,9 +95,6 @@ git pull
|
||||
npm run update
|
||||
```
|
||||
|
||||
|
||||
### Things to consider
|
||||
|
||||
- In our Docker setup, we employ a local logging driver. OneUptime, particularly within the probe and ingestor containers, generates a substantial amount of logs. To prevent your storage from becoming full, it's crucial to limit the logging storage in Docker. For detailed instructions on how to do this, please refer to the official Docker documentation [here](https://docs.docker.com/config/containers/logging/local/).
|
||||
|
||||
OneUptime should run at: http://localhost. You need to register a new account for your instance to start using it. If you would like to use https, please use a reverse proxy like Nginx.
|
||||
- In our Docker setup, we employ a local logging driver. OneUptime, particularly within the probe and ingestor containers, generates a substantial amount of logs. To prevent your storage from becoming full, it's crucial to limit the logging storage in Docker. For detailed instructions on how to do this, please refer to the official Docker documentation [here](https://docs.docker.com/config/containers/logging/local/).
|
||||
@@ -43,7 +43,7 @@ Measure and optimize the performance of your online apps and services. Track key
|
||||
|
||||
Detect and diagnose errors in your online services. Get detailed error reports with stack traces, context, and user feedback. Replace tools like Sentry.
|
||||
|
||||
##### Reliability Autopilot
|
||||
##### Reliability Copilot
|
||||
|
||||
Scan your code and fix performance issues and errors automatically. Get recommendations for improving the reliability of your online services.
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ To run a probe, please make sure you have docker installed. You can run custom p
|
||||
|
||||
```
|
||||
docker run --name oneuptime-probe --network host -e PROBE_KEY=<probe-key> -e PROBE_ID=<probe-id> -e ONEUPTIME_URL=https://oneuptime.com -d oneuptime/probe:release
|
||||
|
||||
```
|
||||
|
||||
If you are self hosting OneUptime, you can change `INGESTOR_URL` to your custom self hosted instance.
|
||||
|
||||
@@ -29,7 +29,6 @@ We use OpenTelemetry to collect application logs. OneUptime currently supports l
|
||||
- [JavaScript / Typescript / NodeJS / Browser](https://opentelemetry.io/docs/instrumentation/js/)
|
||||
- [Python](https://opentelemetry.io/docs/instrumentation/python/)
|
||||
- [Ruby](https://opentelemetry.io/docs/instrumentation/ruby/)
|
||||
- [Swift](https://opentelemetry.io/docs/instrumentation/swift/)
|
||||
- [PHP](https://opentelemetry.io/docs/instrumentation/php/)
|
||||
- [Erlang](https://opentelemetry.io/docs/instrumentation/erlang/)
|
||||
- [Rust](https://opentelemetry.io/docs/instrumentation/rust/)
|
||||
|
||||
@@ -8,8 +8,20 @@ usage() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# if there's no $HOME env var then set it to /usr
|
||||
|
||||
if [ -z "$HOME" ]; then
|
||||
HOME=/usr
|
||||
fi
|
||||
|
||||
# Default parameters
|
||||
BINDIR=/usr/bin
|
||||
BINDIR=$HOME/bin
|
||||
|
||||
# Make sure bindir exists
|
||||
if [ ! -d "$BINDIR" ]; then
|
||||
mkdir -p "$BINDIR"
|
||||
fi
|
||||
|
||||
DEBUG=0
|
||||
|
||||
# Parse command-line options
|
||||
@@ -41,6 +53,9 @@ case $ARCH in
|
||||
aarch64)
|
||||
ARCH=arm64
|
||||
;;
|
||||
*arm64*)
|
||||
ARCH=arm64
|
||||
;;
|
||||
*arm*)
|
||||
ARCH=arm
|
||||
;;
|
||||
@@ -65,8 +80,32 @@ echo "Fetching the latest release: $TAG"
|
||||
# Construct the URL for the binary release
|
||||
URL="https://github.com/${REPO}/releases/download/${TAG}/oneuptime-infrastructure-agent_${OS}_${ARCH}.tar.gz"
|
||||
|
||||
# Check if wget is installed otherwise install it, do it for all os'es
|
||||
|
||||
if ! command -v wget > /dev/null; then
|
||||
if [ "$OS" = "darwin" ]; then
|
||||
brew install wget
|
||||
fi
|
||||
if [ "$OS" = "linux" ]; then
|
||||
apt-get install wget
|
||||
fi
|
||||
if [ "$OS" = "freebsd" ]; then
|
||||
pkg install wget
|
||||
fi
|
||||
if [ "$OS" = "openbsd" ]; then
|
||||
pkg_add wget
|
||||
fi
|
||||
fi
|
||||
|
||||
# Download and extract the binary
|
||||
curl -sL "${URL}" | tar xz -C "${BINDIR}"
|
||||
wget "${URL}"
|
||||
|
||||
# if darwin
|
||||
|
||||
tar -xvzf "oneuptime-infrastructure-agent_${OS}_${ARCH}.tar.gz" -C "${BINDIR}"
|
||||
|
||||
# delete the downlaoded file
|
||||
rm "oneuptime-infrastructure-agent_${OS}_${ARCH}.tar.gz"
|
||||
|
||||
# Check if the binary is executable
|
||||
if [ ! -x "${BINDIR}/oneuptime-infrastructure-agent" ]; then
|
||||
@@ -74,4 +113,49 @@ if [ ! -x "${BINDIR}/oneuptime-infrastructure-agent" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "oneuptime-infrastructure-agent installed successfully to ${BINDIR}. Please configure the agent using 'oneuptime-infrastructure-agent configure'."
|
||||
# Now add binary to path
|
||||
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
echo "export PATH=$PATH:$BINDIR" >> $HOME/.bashrc
|
||||
source $HOME/.bashrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.bash_profile" ]; then
|
||||
echo "export PATH=$PATH:$BINDIR" >> $HOME/.bash_profile
|
||||
source $HOME/.bash_profile
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.zshrc" ]; then
|
||||
echo "export PATH=$PATH:$BINDIR" >> $HOME/.zshrc
|
||||
source $HOME/.zshrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.profile" ]; then
|
||||
echo "export PATH=$PATH:$BINDIR" >> $HOME/.profile
|
||||
source $HOME/.profile
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.bash_login" ]; then
|
||||
echo "export PATH=$PATH:$BINDIR" >> $HOME/.bash_login
|
||||
source $HOME/.bash_login
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.bash_logout" ]; then
|
||||
echo "export PATH=$PATH:$BINDIR" >> $HOME/.bash_logout
|
||||
source $HOME/.bash_logout
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.bash_aliases" ]; then
|
||||
echo "export PATH=$PATH:$BINDIR" >> $HOME/.bash_aliases
|
||||
source $HOME/.bash_aliases
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
echo "export PATH=$PATH:$BINDIR" >> $HOME/.bashrc
|
||||
source $HOME/.bashrc
|
||||
fi
|
||||
|
||||
|
||||
echo "oneuptime-infrastructure-agent has been installed to ${BINDIR}"
|
||||
echo "oneuptime-infrastructure-agent installed successfully to ${BINDIR}. Please configure the agent using 'oneuptime-infrastructure-agent configure'."
|
||||
echo "Please reload your shell or open a new shell session to use the oneuptime-infrastructure-agent command."
|
||||
|
||||
@@ -58,7 +58,7 @@ const HomeFeatureSet: FeatureSet = {
|
||||
{
|
||||
name: "Public Status Page",
|
||||
plans: {
|
||||
free: "Unlimited",
|
||||
free: "1",
|
||||
growth: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
enterprise: "Unlimited",
|
||||
@@ -67,7 +67,7 @@ const HomeFeatureSet: FeatureSet = {
|
||||
{
|
||||
name: "Subscribers",
|
||||
plans: {
|
||||
free: "Unlimited",
|
||||
free: "100",
|
||||
growth: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
enterprise: "Unlimited",
|
||||
@@ -209,6 +209,15 @@ const HomeFeatureSet: FeatureSet = {
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Synthetic Monitoring (with Playwright)",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "IPv4 Monitoring",
|
||||
@@ -219,6 +228,7 @@ const HomeFeatureSet: FeatureSet = {
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "IPv6 Monitoring",
|
||||
plans: {
|
||||
@@ -247,17 +257,16 @@ const HomeFeatureSet: FeatureSet = {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Container Monitoring",
|
||||
name: "Network Monitoring",
|
||||
plans: {
|
||||
free: "Coming Soon",
|
||||
growth: "Coming Soon",
|
||||
scale: "Coming Soon",
|
||||
enterprise: "Coming Soon",
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Network Monitoring",
|
||||
name: "Container Monitoring",
|
||||
plans: {
|
||||
free: "Coming Soon",
|
||||
growth: "Coming Soon",
|
||||
@@ -383,13 +392,330 @@ const HomeFeatureSet: FeatureSet = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Logs Management",
|
||||
data: [
|
||||
{
|
||||
name: "Ingest with OpenTelemetry",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ingest with Fluentd",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ingest +1000 Sources",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Application Logs",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Container Logs",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Data Rentention",
|
||||
plans: {
|
||||
free: "15 days",
|
||||
growth: "Custom",
|
||||
scale: "Custom",
|
||||
enterprise: "Custom",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Workflows",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Advanced Team Permissions",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Telemetry / APM",
|
||||
data: [
|
||||
{
|
||||
name: "Metrics",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Traces",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Error Tracking",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ingest Pricing",
|
||||
plans: {
|
||||
free: "$0.10/GB",
|
||||
growth: "$0.10/GB",
|
||||
scale: "$0.10/GB",
|
||||
enterprise: "$0.10/GB",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Data Rentention",
|
||||
plans: {
|
||||
free: "15 days",
|
||||
growth: "Custom",
|
||||
scale: "Custom",
|
||||
enterprise: "Custom",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Workflows",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Advanced Team Permissions",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Error Tracking",
|
||||
data: [
|
||||
{
|
||||
name: "Track Errors and Exceptions",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Cross Microservice Issues",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Distributed Tracing",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Stack Traces",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Version Management",
|
||||
plans: {
|
||||
free: true,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Data Rentention",
|
||||
plans: {
|
||||
free: "15 days",
|
||||
growth: "Custom",
|
||||
scale: "Custom",
|
||||
enterprise: "Custom",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Workflows",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Advanced Team Permissions",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Reliability Copilot",
|
||||
data: [
|
||||
{
|
||||
name: "Scan your Codebase",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fix Errors Automatically",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fix Performance Issues",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fix DB Queries Automatically",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fix Frontend Issues",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Integrate with GitHub, GitLab",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Integrate with CI/CD",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Integrate with Issue Tracker",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Integrates with Slack / Team",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Advanced Workflows",
|
||||
plans: {
|
||||
free: false,
|
||||
growth: true,
|
||||
scale: true,
|
||||
enterprise: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Support and More",
|
||||
data: [
|
||||
{
|
||||
name: "Support",
|
||||
plans: {
|
||||
free: "Community Support",
|
||||
free: "Email Support",
|
||||
growth: "Email Support",
|
||||
scale: "Email and Chat Support",
|
||||
enterprise: "Email, Chat, Phone Support",
|
||||
@@ -398,7 +724,7 @@ const HomeFeatureSet: FeatureSet = {
|
||||
{
|
||||
name: "Support SLA",
|
||||
plans: {
|
||||
free: false,
|
||||
free: "5 business day",
|
||||
growth: "1 business day",
|
||||
scale: "6 hours",
|
||||
enterprise: "1 hour priority",
|
||||
@@ -407,7 +733,7 @@ const HomeFeatureSet: FeatureSet = {
|
||||
{
|
||||
name: "Service SLA",
|
||||
plans: {
|
||||
free: false,
|
||||
free: "99.00%",
|
||||
growth: "99.90%",
|
||||
scale: "99.95%",
|
||||
enterprise: "99.99%",
|
||||
|
||||
12
App/FeatureSet/Home/Static/img/skillable-logo.svg
Normal file
12
App/FeatureSet/Home/Static/img/skillable-logo.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="302" height="135" viewBox="0 0 302 135" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M225.829 109.243C225.862 105.573 225.426 101.97 223.866 98.6024C221.081 92.5075 215.51 91.2447 211.014 92.8274C208.028 93.8713 206.149 96.1779 204.84 98.9559C202.894 103.098 202.508 107.509 202.894 112.021C203.179 115.203 203.967 118.234 205.662 120.961C206.954 123.032 208.682 124.649 211.031 125.44C216.232 127.208 221.366 124.901 223.782 119.716C225.325 116.399 225.795 112.863 225.795 109.243M202.004 127.932C202.004 129.178 202.021 131.164 202.004 132.41C201.971 133.825 201.619 134.195 200.31 134.212C198.364 134.246 196.417 134.246 194.471 134.212C193.246 134.195 192.961 133.892 192.844 132.68C192.81 132.242 192.827 131.804 192.827 131.367V67.0336C192.827 64.2219 192.861 64.1882 195.595 64.1882C197.323 64.1882 199.052 64.1714 200.78 64.1882C202.474 64.2219 202.726 64.4576 202.726 66.2086C202.726 73.2295 202.726 80.2504 202.726 87.2713V89.056C203.951 88.0121 204.89 87.0524 205.981 86.2948C211.886 82.1698 218.883 82.4392 224.352 85.2677C228.882 87.6922 232.355 92.1876 234.067 97.4238C236.801 105.825 236.768 114.26 233.312 122.477C230.828 128.386 226.701 132.73 220.359 134.313C213.615 135.997 207.608 134.582 202.894 129.127C202.407 128.555 202.155 127.831 202.004 127.915" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M151.135 109.344C151.252 112.964 151.47 116.601 153.014 119.985C154.071 122.275 155.564 124.211 157.98 125.103C161.084 126.518 165.882 126.265 169.12 123.15C170.261 122.039 171.301 120.709 172.023 119.278C174.539 114.227 174.942 108.873 173.868 103.384C173.315 100.572 172.09 98.0636 170.194 95.858C165.932 90.908 156.587 90.4534 153.165 98.3161C151.621 101.852 151.269 105.539 151.135 109.344ZM174.841 89.2412C174.841 88.231 174.841 87.2208 174.841 86.2274C174.858 84.3922 175.093 84.1397 176.905 84.1228C178.851 84.106 180.797 84.0892 182.744 84.1228C184.153 84.1565 184.421 84.4596 184.438 85.8907C184.472 87.7427 184.438 131.602 184.438 131.602C184.438 134.094 184.321 134.195 181.905 134.212C180.227 134.212 178.549 134.229 176.871 134.212C175.613 134.178 175.345 133.959 175.227 132.697C175.093 131.367 175.076 129.733 174.992 128.386C174.992 128.386 174.422 128.942 174.019 129.363C171.1 132.73 167.409 134.515 163.013 134.885C153.651 135.677 146.923 130.39 143.635 122.359C141.387 116.904 140.967 111.179 141.471 105.371C141.856 100.993 142.947 96.8008 145.128 92.9621C147.93 88.0626 151.873 84.5943 157.493 83.5167C163.6 82.3381 169.087 83.6514 173.516 88.332C173.835 88.6687 174.17 88.9886 174.489 89.3254C174.489 89.3254 174.741 89.6284 174.791 89.6116C174.808 89.6116 174.791 89.2412 174.791 89.2412" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M74.6115 80.2504L81.8427 87.0692L66.0046 104.209C66.7093 105.438 77.145 123.47 81.8931 131.451C83.0339 133.37 82.5977 134.38 80.2488 134.246C78.1516 134.111 76.0376 134.145 73.9237 134.246C72.6821 134.296 71.9607 133.841 71.3399 132.764C67.6656 126.164 59.1761 111.348 59.1761 111.348C59.1761 111.348 53.5388 117.729 52.2134 119.177C51.5926 119.85 51.3074 120.574 51.3241 121.517C51.3745 124.884 51.3577 132.511 51.3074 132.949C51.2235 133.774 50.7537 134.229 49.9148 134.229C47.5324 134.229 45.1667 134.229 42.7843 134.229C41.9454 134.229 41.4756 133.791 41.4085 132.949V66.7811C41.3918 64.2724 41.4756 64.1882 43.9084 64.1882C45.8043 64.1882 47.7002 64.1545 49.5793 64.1882C50.955 64.2219 51.257 64.5586 51.3241 65.9561C51.3409 66.3938 51.3241 104.781 51.3241 105.354L74.6115 80.2335V80.2504Z" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M270.675 104.142H292.436C292.218 100.875 291.53 97.8784 289.416 95.4371C286.514 92.0866 282.722 91.6657 278.729 92.6254C274.249 93.7029 270.625 99.0064 270.692 104.142M270.407 112.998C271.011 117.342 272.185 121.197 275.759 123.891C277.789 125.423 280.121 126.012 282.604 125.979C285.977 125.945 289.114 125.036 291.849 122.982C292.067 122.813 292.302 122.679 292.637 122.46C295.003 124.177 297.402 125.911 299.902 127.73C297.302 130.323 294.517 132.293 291.211 133.471C286.178 135.239 281.027 135.525 275.91 134.06C268.813 132.023 264.25 127.124 262.001 120.187C259.334 111.954 259.451 103.653 262.907 95.6391C265.793 88.9718 270.81 84.6616 278.041 83.4494C283.98 82.4392 289.584 83.4157 294.366 87.406C298.426 90.7901 300.506 95.3024 301.462 100.438C302.134 104.041 302.066 107.694 301.848 111.331C301.764 112.661 301.462 112.93 300.187 112.981C299.801 112.981 271.128 112.981 270.424 112.981" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.523 90.4702C33.8754 89.881 33.1371 89.1402 32.9861 89.0055C26.7281 83.9713 19.5976 82.0688 11.6785 83.6851C7.0143 84.6279 3.22255 87.6417 1.36023 91.7667C-0.602759 96.3968 -0.585981 100.976 2.36689 105.253C3.89366 107.458 6.05798 108.957 8.47396 109.95C12.3999 111.482 21.4767 114.648 23.7416 115.759C24.7147 116.18 25.4697 116.904 25.8389 117.931C26.812 120.473 26.5267 124.514 22.2652 125.743C19.866 126.433 17.45 126.316 15.034 125.895C11.3262 125.255 9.12829 124.362 6.25931 121.921C5.90698 121.618 5.25265 121.601 4.93387 121.921C4.56476 122.292 0.135458 127.141 0.135458 127.141C-0.23365 127.915 0.252902 128.37 1.17567 129.195C1.89711 129.834 4.4641 131.602 5.60498 132.141C11.3597 134.852 17.3997 135.609 23.6242 134.498C30.2346 133.32 34.731 129.38 35.6035 122.426C36.1236 118.234 35.5699 114.26 32.6003 110.994C30.8554 109.075 28.6911 107.711 26.3086 106.802C23.2886 105.64 14.7656 102.946 12.3328 101.633C10.5879 100.875 9.69873 99.5284 9.71551 97.5417C9.71551 95.4371 10.5208 93.8207 12.3664 92.8611C13.1717 92.457 14.0945 92.1876 14.9837 92.0697C19.262 91.5478 23.1209 92.6759 26.5771 95.2014C26.812 95.3697 27.0301 95.5381 27.265 95.7233C27.7683 96.1105 28.4729 96.0432 28.9092 95.5886C28.9092 95.5886 33.3217 90.8407 33.523 90.5039" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M242.975 99.0906V66.7811C242.975 64.3229 243.11 64.1882 245.542 64.1882C247.438 64.1882 249.317 64.1545 251.213 64.1882C252.539 64.2219 252.824 64.5249 252.874 65.8214C252.891 66.1413 252.874 110.001 252.874 131.585C252.874 134.094 252.774 134.195 250.358 134.195C248.462 134.195 246.583 134.212 244.687 134.195C243.412 134.178 243.059 133.808 242.992 132.545C242.959 132.107 242.992 109.816 242.992 99.0906" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M124.089 134.178H134.005V62.3362H124.089V134.178Z" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M105.717 134.178H115.616V82.3381H105.717V134.178Z" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M87.3458 134.178H97.2279V96.9019H87.3458V134.178Z" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M146.705 0L90.834 63.307L83.1227 56.057L72 68.585L90.748 87L158 11.136L146.705 0Z" fill="#a5b4fc"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
@@ -1,6 +1,6 @@
|
||||
<div class="relative overflow-hidden bg-white pt-32 pb-32">
|
||||
<div class="max-w-3xl1 px-6">
|
||||
<p class="text-5xl font-bold tracking-tight text-gray-900 sm:text-center">OneUptime is 7+ tools combined
|
||||
<p class="text-5xl font-bold tracking-tight text-gray-900 sm:text-center">OneUptime is 8+ tools combined
|
||||
into one.</p>
|
||||
<p class="mt-5 text-xl text-gray-500 sm:text-center mb-12">Everything from monitoring, status pages,
|
||||
incident management, on-call schedules - we've got it and we are just getting started!</p>
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
@@ -524,8 +524,7 @@
|
||||
<div class="bg-white">
|
||||
<div class="mx-auto max-w-7xl py-24 pt-10 px-6 lg:px-8">
|
||||
<div class="sm:align-center sm:flex sm:flex-col">
|
||||
<h1 class="text-5xl font-bold tracking-tight text-gray-900 sm:text-center">Telemetry: Logs, Traces, Metrics and
|
||||
more.</h1>
|
||||
<h1 class="text-5xl font-bold tracking-tight text-gray-900 sm:text-center">Logs, Traces, Metrics, Copilot and more.</h1>
|
||||
<p class="mt-5 text-xl text-gray-500 sm:text-center">We truly envision to be the best open-source observability
|
||||
platform out there. All of this to help you build reliable software all of the time. Coming very soon.</p>
|
||||
|
||||
@@ -546,7 +545,7 @@
|
||||
</div>
|
||||
<div>Logs Management</div>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-gray-500">Ingest logs from any source</p>
|
||||
<p class="mt-4 text-sm text-gray-500">Ingest logs of from any source and search in seconds.</p>
|
||||
</div>
|
||||
<div class="px-6 pt-6 pb-8">
|
||||
|
||||
@@ -629,18 +628,20 @@
|
||||
<span class="text-sm text-gray-500">Integrate with Slack / Teams</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Alerting</span>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h3 class="text-sm font-medium text-gray-900 mt-6">Coming soon</h3>
|
||||
<ul role="list" class="mt-6 space-y-4">
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Alerting</span>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
@@ -677,7 +678,7 @@
|
||||
</div>
|
||||
<div>APM</div>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-gray-500">Monitor performance of your app</p>
|
||||
<p class="mt-4 text-sm text-gray-500">Monitor performance of any app, any stack, any environment.</p>
|
||||
</div>
|
||||
<div class="px-6 pt-6 pb-8">
|
||||
|
||||
@@ -757,7 +758,7 @@
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
@@ -805,20 +806,101 @@
|
||||
</div>
|
||||
<div>Error Tracking</div>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-gray-500">See issues that really matter</p>
|
||||
<p class="mt-4 text-sm text-gray-500">Track software errors. See issues that really matter.</p>
|
||||
</div>
|
||||
<div class="px-6 pt-6 pb-8">
|
||||
|
||||
<h3 class="text-sm font-medium text-gray-900">Coming soon</h3>
|
||||
<h3 class="text-sm font-medium text-gray-900">What's included</h3>
|
||||
<ul role="list" class="mt-6 space-y-4">
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Track errors in any stack</span>
|
||||
</li>
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Custom Queries</span>
|
||||
</li>
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Session Replay</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Cross Microservice Issues</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Distributed Tracing</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Stack Traces</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Version Management</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Issue Owners</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Integrate with Slack / Teams</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
<span class="text-sm text-gray-500">Advanced Workflows</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3 class="text-sm font-medium text-gray-900 mt-6">Coming soon</h3>
|
||||
<ul role="list" class="mt-6 space-y-4">
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
@@ -828,86 +910,7 @@
|
||||
<span class="text-sm text-gray-500">Dashboards and Reports</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Session Replay</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Cross Microservice Issues</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Distributed Tracing</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Stack Traces</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Version Management</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Releases</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Issue Owners</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Integrate with Slack / Teams</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Advanced Workflows</span>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -924,29 +927,24 @@
|
||||
<div class="p-6">
|
||||
<div class="text-lg font-medium leading-6 text-gray-900 flex space-x-2">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div>Reliability Autopilot</div>
|
||||
<div>Reliability Copilot</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-sm text-gray-500">Automatically fix issues.</p>
|
||||
<p class="mt-4 text-sm text-gray-500">Fix issues with your code automatically.</p>
|
||||
</div>
|
||||
<div class="px-6 pt-6 pb-8">
|
||||
|
||||
<h3 class="text-sm font-medium text-gray-900">Coming soon</h3>
|
||||
<h3 class="text-sm font-medium text-gray-900">What's included</h3>
|
||||
<ul role="list" class="mt-6 space-y-4">
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -955,7 +953,8 @@
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -964,7 +963,8 @@
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -975,7 +975,8 @@
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -984,7 +985,8 @@
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -993,7 +995,8 @@
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1003,7 +1006,8 @@
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1012,7 +1016,8 @@
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1021,7 +1026,8 @@
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1029,12 +1035,20 @@
|
||||
</li>
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
<%- include('./Partials/tick-icon') %>
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="text-sm text-gray-500">Advanced Workflows</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3 class="text-sm font-medium text-gray-900 mt-6">Coming soon</h3>
|
||||
<ul role="list" class="mt-6 space-y-4">
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
|
||||
<%- include('./Partials/comingsoon-icon') %>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<img class="h-6 mt-2" src="/img/viewsonic.svg" alt="ViewSonic">
|
||||
</div>
|
||||
<div class="mt-4 ml-8 flex justify-center flex-shrink-0 flex-grow lg:ml-4 lg:flex-grow-0">
|
||||
<img class="h-9 mt-1" src="/img/Siemens-logo.svg" alt="Siemens">
|
||||
<img class="h-12 -mt-4" src="/img/skillable-logo.svg" alt="Skillable">
|
||||
</div>
|
||||
<div class="mt-4 ml-8 flex justify-center flex-shrink-0 flex-grow lg:ml-4 lg:flex-grow-0">
|
||||
<img class="h-11 -mt-2" src="/img/sodexo.svg" alt="Sodexo">
|
||||
|
||||
@@ -51,10 +51,10 @@
|
||||
To: "opacity-0 translate-y-1"
|
||||
-->
|
||||
<div onmouseenter="showProductMenu()" onmouseover="showProductMenu()" onmouseleave="hideProductMenu()"
|
||||
class="absolute z-20 -ml-4 mt-3 w-screen max-w-md transform px-2 sm:px-0 lg:left-1/2 lg:ml-0 lg:-translate-x-1/2"
|
||||
class="absolute z-20 -ml-4 mt-3 w-screen max-w-4xl transform px-2 sm:px-0 lg:left-1/2 lg:ml-0 lg:-translate-x-1/2"
|
||||
id="product-menu" style="visibility: collapse;">
|
||||
<div class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5">
|
||||
<div class="relative grid gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8">
|
||||
<div class="relative grid grid-cols-2 gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8">
|
||||
<a href="/product/status-page" class="-m-3 flex items-start rounded-lg p-3 hover:bg-gray-50">
|
||||
<!-- Heroicon name: outline/chart-bar -->
|
||||
<svg class="h-6 w-6 flex-shrink-0 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
@@ -165,7 +165,7 @@
|
||||
|
||||
</div>
|
||||
<div class="space-y-6 bg-gray-50 px-5 py-5 sm:flex sm:space-y-0 sm:space-x-10 sm:px-8">
|
||||
<div class="flow-root">
|
||||
<div class="flow-root w-1/2">
|
||||
<a href="/enterprise/demo"
|
||||
class="-m-3 flex items-center rounded-md p-3 text-base font-medium text-gray-900 hover:bg-gray-100">
|
||||
<!-- Heroicon name: outline/play -->
|
||||
@@ -178,7 +178,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flow-root">
|
||||
<div class="flow-root w-1/2">
|
||||
<a href="mailto:sales@oneuptime.com"
|
||||
class="-m-3 flex items-center rounded-md p-3 text-base font-medium text-gray-900 hover:bg-gray-100">
|
||||
<!-- Heroicon name: outline/phone -->
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<h2 class="text-3xl font-bold tracking-tight text-gray-900">On-call policy for every service.</h2>
|
||||
<p class="mt-4 text-lg text-gray-500">Build unlimited on-call policies one for each team and for every service. Design custom worklfows to alert right stakeholders at the right time.</p>
|
||||
<p class="mt-4 text-lg text-gray-500">Build unlimited on-call policies one for each team and for every service. Design custom workflows to alert right stakeholders at the right time.</p>
|
||||
<div class="mt-6">
|
||||
<a href="/accounts/register" class="rounded-md bg-indigo-600 px-3.5 py-1.5 text-base font-semibold leading-7 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 hover:text-white">Get started</a>
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Unlimited Status Pages</span>
|
||||
<span class="text-sm text-gray-500">1 Status Page</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
@@ -64,22 +64,13 @@
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Unlimited Subscribers</span>
|
||||
<span class="text-sm text-gray-500">100 Subscribers</span>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Unlimited Status Page Viewers</span>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
@@ -103,6 +94,52 @@
|
||||
<span class="text-sm text-gray-500">Active Monitors at $1/month</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Incident Management</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Logs, Traces and Metrics</span>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">5 Business Day Email Support</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">99.00% SLA</span>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -133,6 +170,39 @@
|
||||
<span class="text-sm text-gray-500">Everything in Free Plan </span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Unlimited Status Pages </span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Unlimited Subscribers </span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">On Call Rotation and Alerts</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -155,16 +225,7 @@
|
||||
<span class="text-sm text-gray-500">API Access</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Advanced Workflows</span>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
@@ -262,16 +323,7 @@
|
||||
<span class="text-sm text-gray-500">6 hour Chat or Email Support</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">99.99% SLA</span>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
@@ -283,6 +335,28 @@
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Dedicated Account Executive</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Dedicated Support Channel</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">99.99% SLA</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -293,7 +367,9 @@
|
||||
<p class="mt-4 text-sm text-gray-500">Enterprise grade offering. The best of us for your team.</p>
|
||||
<p class="mt-8">
|
||||
<span class="text-4xl font-bold tracking-tight text-gray-900">Custom</span>
|
||||
<span class="text-base font-medium text-gray-500">/yr</span>
|
||||
<span class="text-base font-medium text-gray-500">
|
||||
<div class='tooltip'>Please contact sales<span class='tooltiptext'>Please contact sales or request a demo. The best of us for the best companies around. We will quote a cusom price to match your needs.</span></div>
|
||||
</span>
|
||||
</p>
|
||||
<a href="/enterprise/demo"
|
||||
class="mt-8 block w-full hover:text-white rounded-md border border-gray-800 bg-gray-800 py-2 text-center text-sm font-semibold text-white hover:bg-gray-900">Request
|
||||
@@ -365,7 +441,7 @@
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Dedicated Support Channel</span>
|
||||
<span class="text-sm text-gray-500">Custom Data Retention</span>
|
||||
</li>
|
||||
|
||||
<li class="flex space-x-3">
|
||||
@@ -376,8 +452,10 @@
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-sm text-gray-500">Custom Data Retention</span>
|
||||
<span class="text-sm text-gray-500">Private Cloud or SaaS</span>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="flex space-x-3">
|
||||
<!-- Heroicon name: mini/check -->
|
||||
<svg class="h-5 w-5 flex-shrink-0 text-green-500" xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -624,8 +702,7 @@
|
||||
<div class="bg-white">
|
||||
<div class="mx-auto max-w-7xl py-24 px-6 sm:py-32 lg:px-8 lg:py-40">
|
||||
<div class="mx-auto max-w-3xl text-center">
|
||||
<h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">All-in-one platform for all your SRE
|
||||
needs.</h2>
|
||||
<h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">OneUptime is 8+ tools combined into one.</h2>
|
||||
<p class="mx-auto mt-4 max-w-xl text-lg leading-8 text-gray-600">With OneUptime, you get a complete SRE
|
||||
toolchain out-of-the-box. One interface. One conversation. One permission model. Thousands of features.
|
||||
You'll be amazed at everything OneUptime can do today. And we're just getting started.</p>
|
||||
@@ -683,6 +760,56 @@
|
||||
resources are not operational. Alert right people at the right time.</dd>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="relative">
|
||||
<dt>
|
||||
<!-- Heroicon name: outline/check -->
|
||||
<svg class="absolute mt-1 h-6 w-6 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
<p class="ml-10 text-lg font-semibold leading-8 text-gray-900">Error Tracking</p>
|
||||
</dt>
|
||||
<dd class="mt-2 ml-10 text-base leading-7 text-gray-600">Track errors in your applicaton. See issues that really matter. Automatically fix errors (powered by OneUptime Copilot) </dd>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<dt>
|
||||
<!-- Heroicon name: outline/check -->
|
||||
<svg class="absolute mt-1 h-6 w-6 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
<p class="ml-10 text-lg font-semibold leading-8 text-gray-900">Logs Management</p>
|
||||
</dt>
|
||||
<dd class="mt-2 ml-10 text-base leading-7 text-gray-600">Ingest logs from any source and search in seconds. Native OpenTelemetry Support. Ingest TB's and search in seconds.</dd>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<dt>
|
||||
<!-- Heroicon name: outline/check -->
|
||||
<svg class="absolute mt-1 h-6 w-6 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
<p class="ml-10 text-lg font-semibold leading-8 text-gray-900">APM</p>
|
||||
</dt>
|
||||
<dd class="mt-2 ml-10 text-base leading-7 text-gray-600">Monitor performance of any app, any service, any stack.
|
||||
Improve poorly performing resources. Get alerted by SMS, Email or Call when things go wrong.</dd>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<dt>
|
||||
<!-- Heroicon name: outline/check -->
|
||||
<svg class="absolute mt-1 h-6 w-6 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
<p class="ml-10 text-lg font-semibold leading-8 text-gray-900">Workflows</p>
|
||||
</dt>
|
||||
<dd class="mt-2 ml-10 text-base leading-7 text-gray-600">Integrate with any software or service. Drag and Drop workflows. Supports Webhooks and API's. 5000+ Integrations. Write Custom Code</dd>
|
||||
</div>
|
||||
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1347,10 +1474,6 @@
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function switchToYearlyBilling() {
|
||||
document.getElementById("yearly-billing-btn").classList = "relative w-1/2 whitespace-nowrap rounded-md border-gray-200 bg-white py-2 text-sm font-medium text-gray-900 shadow-sm focus:z-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:w-auto sm:px-8"
|
||||
document.getElementById("monthly-billing-btn").classList = "relative ml-0.5 w-1/2 whitespace-nowrap rounded-md border border-transparent py-2 text-sm font-medium text-gray-700 focus:z-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:w-auto sm:px-8"
|
||||
@@ -1367,7 +1490,6 @@
|
||||
for (let i = 0; i < document.getElementsByClassName("billing-period").length; i++) {
|
||||
document.getElementsByClassName("billing-period")[i].innerHTML = "<div class='tooltip'>/mo per user billed yearly<span class='tooltiptext'>Users are people in your team who create and manage OneUptime resources like monitors, incidents, etc. They are NOT Status Page Subscribers or Status Page Private Viewers (these are free).</span></div>"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function switchToMonthlyBilling() {
|
||||
|
||||
@@ -30,7 +30,6 @@ import Express, {
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import EmailVerificationToken from "Model/Models/EmailVerificationToken";
|
||||
@@ -89,6 +88,7 @@ router.post(
|
||||
select: {
|
||||
_id: true,
|
||||
password: true,
|
||||
timezone: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -121,6 +121,7 @@ router.post(
|
||||
_id: true,
|
||||
name: true,
|
||||
isMasterAdmin: true,
|
||||
timezone: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -179,23 +180,10 @@ router.post(
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(savedUser.id!);
|
||||
|
||||
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, {
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
||||
httpOnly: true,
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user: savedUser,
|
||||
isGlobalLogin: true,
|
||||
});
|
||||
|
||||
logger.info("User signed up: " + savedUser.email?.toString());
|
||||
@@ -534,6 +522,7 @@ router.post(
|
||||
isMasterAdmin: true,
|
||||
isEmailVerified: true,
|
||||
profilePictureId: true,
|
||||
timezone: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -573,23 +562,10 @@ router.post(
|
||||
) {
|
||||
logger.info("User logged in: " + alreadySavedUser.email?.toString());
|
||||
|
||||
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(res, CookieUtil.getUserTokenKey(), token, {
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
||||
httpOnly: true,
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user: alreadySavedUser,
|
||||
isGlobalLogin: true,
|
||||
});
|
||||
|
||||
return Response.sendEntityResponse(req, res, alreadySavedUser, User);
|
||||
|
||||
@@ -5,6 +5,7 @@ import Hostname from "Common/Types/API/Hostname";
|
||||
import Protocol from "Common/Types/API/Protocol";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import Email from "Common/Types/Email";
|
||||
import BadRequestException from "Common/Types/Exception/BadRequestException";
|
||||
@@ -19,6 +20,8 @@ import AccessTokenService from "CommonServer/Services/AccessTokenService";
|
||||
import ProjectSSOService from "CommonServer/Services/ProjectSsoService";
|
||||
import TeamMemberService from "CommonServer/Services/TeamMemberService";
|
||||
import UserService from "CommonServer/Services/UserService";
|
||||
import QueryHelper from "CommonServer/Types/Database/QueryHelper";
|
||||
import Select from "CommonServer/Types/Database/Select";
|
||||
import CookieUtil from "CommonServer/Utils/Cookie";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
@@ -26,9 +29,9 @@ import Express, {
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import Project from "Model/Models/Project";
|
||||
import ProjectSSO from "Model/Models/ProjectSso";
|
||||
import TeamMember from "Model/Models/TeamMember";
|
||||
import User from "Model/Models/User";
|
||||
@@ -36,6 +39,115 @@ import xml2js from "xml2js";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
// This route is used to get the SSO config for the user.
|
||||
// when the user logs in from OneUptime and not from the IDP.
|
||||
|
||||
router.get(
|
||||
"/service-provider-login",
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
if (!req.query["email"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Email is required"),
|
||||
);
|
||||
}
|
||||
|
||||
const email: Email = new Email(req.query["email"] as string);
|
||||
|
||||
if (!email) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Email is required"),
|
||||
);
|
||||
}
|
||||
|
||||
// get sso config for this user.
|
||||
|
||||
const user: User | null = await UserService.findOneBy({
|
||||
query: { email: email },
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("No SSO config found for this user"),
|
||||
);
|
||||
}
|
||||
|
||||
const userId: ObjectID = user.id!;
|
||||
|
||||
if (!userId) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("No SSO config found for this user"),
|
||||
);
|
||||
}
|
||||
|
||||
const projectUserBelongsTo: Array<ObjectID> = (
|
||||
await TeamMemberService.findBy({
|
||||
query: { userId: userId },
|
||||
select: {
|
||||
projectId: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
})
|
||||
).map((teamMember: TeamMember) => {
|
||||
return teamMember.projectId!;
|
||||
});
|
||||
|
||||
if (projectUserBelongsTo.length === 0) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("No SSO config found for this user"),
|
||||
);
|
||||
}
|
||||
|
||||
const projectSSOList: Array<ProjectSSO> = await ProjectSSOService.findBy({
|
||||
query: {
|
||||
projectId: QueryHelper.any(projectUserBelongsTo),
|
||||
isEnabled: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
select: {
|
||||
name: true,
|
||||
description: true,
|
||||
_id: true,
|
||||
projectId: true,
|
||||
project: {
|
||||
name: true,
|
||||
} as Select<Project>,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return Response.sendEntityArrayResponse(
|
||||
req,
|
||||
res,
|
||||
projectSSOList,
|
||||
projectSSOList.length,
|
||||
ProjectSSO,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/sso/:projectId/:projectSsoId",
|
||||
async (
|
||||
@@ -284,6 +396,7 @@ const loginUserWithSso: LoginUserWithSsoFunction = async (
|
||||
isMasterAdmin: true,
|
||||
isEmailVerified: true,
|
||||
profilePictureId: true,
|
||||
timezone: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -374,38 +487,18 @@ const loginUserWithSso: LoginUserWithSsoFunction = async (
|
||||
|
||||
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)),
|
||||
alreadySavedUser.email = email;
|
||||
|
||||
CookieUtil.setSSOCookie({
|
||||
user: alreadySavedUser,
|
||||
projectId: projectId,
|
||||
expressResponse: res,
|
||||
});
|
||||
|
||||
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,
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user: alreadySavedUser,
|
||||
isGlobalLogin: false,
|
||||
});
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
|
||||
@@ -273,14 +273,14 @@ export default class MailService {
|
||||
mail.vars["year"] = OneUptimeDate.getCurrentYear().toString();
|
||||
}
|
||||
|
||||
mail.body = mail.templateType
|
||||
? await this.compileEmailBody(mail.templateType, mail.vars)
|
||||
: this.compileText(mail.body || "", mail.vars);
|
||||
mail.subject = this.compileText(mail.subject, mail.vars);
|
||||
|
||||
const emailServerType: EmailServerType = await getEmailServerType();
|
||||
|
||||
try {
|
||||
const emailServerType: EmailServerType = await getEmailServerType();
|
||||
|
||||
mail.body = mail.templateType
|
||||
? await this.compileEmailBody(mail.templateType, mail.vars)
|
||||
: this.compileText(mail.body || "", mail.vars);
|
||||
mail.subject = this.compileText(mail.subject, mail.vars);
|
||||
|
||||
if (
|
||||
(!options || !options.emailServer) &&
|
||||
emailServerType === EmailServerType.Sendgrid
|
||||
|
||||
@@ -12,11 +12,18 @@
|
||||
{{> DetailBoxField title="Incident Title:" text=incidentTitle }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Resources Affected: " text=resourcesAffected }}
|
||||
{{> DetailBoxField title="Incident Declared By: " text=declaredBy }}
|
||||
{{> DetailBoxField title="Incident Declared At: " text="" }}
|
||||
{{> DetailBoxField title="" text=declaredAt }}
|
||||
{{> DetailBoxField title="Severity: " text=incidentSeverity }}
|
||||
{{> DetailBoxField title="Root Cause: " text="" }}
|
||||
{{> DetailBoxField title="" text=rootCause }}
|
||||
{{> DetailBoxField title="Description: " text="" }}
|
||||
{{> DetailBoxField title="" text=incidentDescription }}
|
||||
{{#ifNotCond remediationNotes ""}}
|
||||
{{> DetailBoxField title="Remediation Notes: " text="" }}
|
||||
{{> DetailBoxField title="" text=remediationNotes }}
|
||||
{{/ifNotCond}}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Monitor Name:" text=monitorName }}
|
||||
{{> DetailBoxField title="New Status: " text=currentStatus }}
|
||||
{{#ifNotCond rootCause ""}}
|
||||
{{> DetailBoxField title="Root Cause: " text="" }}
|
||||
{{> DetailBoxField title="" text=rootCause }}
|
||||
{{/ifNotCond}}
|
||||
{{> DetailBoxField title="Status changed at: " text="" }}
|
||||
{{> DetailBoxField title="" text=statusChangedAt }}
|
||||
{{> DetailBoxField title="Description: " text="" }}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=title }}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Monitor Name:" text=monitorName }}
|
||||
{{> DetailBoxField title="Monitor Description: " text="" }}
|
||||
{{> DetailBoxField title="" text=monitorDescription }}
|
||||
{{> DetailBoxField title="Probe Status: " text=currentStatus }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
{{> InfoBlock info="You can view this monitor by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=monitorViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=monitorViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of the probe changes."}}
|
||||
|
||||
{{> OwnerInfo this }}
|
||||
{{> UnsubscribeOwnerEmail this }}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -1,7 +1,7 @@
|
||||
{{#if title}}
|
||||
<p
|
||||
style="Margin:0;font-size:16px;font-family:'inter','helvetica neue',helvetica,arial,sans-serif;line-height:30px;color:#424761">
|
||||
<strong>{{{title}}} </strong>{{{text}}} </span></p>
|
||||
<p style="Margin:0;font-size:16px;font-family:'inter','helvetica neue',helvetica,arial,sans-serif;line-height:30px;color:#424761">
|
||||
{{#if title}}
|
||||
<strong>{{{title}}} </strong>{{{text}}} </span>
|
||||
{{else}}
|
||||
{{{text}}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</p>
|
||||
@@ -0,0 +1,33 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=title}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Probe Name:" text=probeName }}
|
||||
{{> DetailBoxField title="Probe Description:" text=probeDescription }}
|
||||
{{> DetailBoxField title="Probe Status:" text=probeStatus }}
|
||||
{{> DetailBoxField title="Status Since:" text="" }}
|
||||
{{> DetailBoxField title="" text=lastAlive }}
|
||||
{{> DetailBoxField title="Project Name: " text=projectName }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this probe by going to Project Settings > Probes "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=viewProbesLink buttonText="View Probes"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=viewProbesLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this probe changes."}}
|
||||
|
||||
{{> OwnerInfo this }}
|
||||
{{> UnsubscribeOwnerEmail this }}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
28
App/FeatureSet/Notification/Templates/ProbeOwnerAdded.hbs
Normal file
28
App/FeatureSet/Notification/Templates/ProbeOwnerAdded.hbs
Normal file
@@ -0,0 +1,28 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Probe: " probeName) }}
|
||||
|
||||
{{> InfoBlock info="You have been added as the owner of this probe."}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Probe Name:" text=probeName }}
|
||||
{{> DetailBoxField title="Probe Description: " text=probeDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this probe by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=viewProbeLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=viewProbeLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this probe changes."}}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -61,6 +61,10 @@ import QueueWorker from "CommonServer/Infrastructure/QueueWorker";
|
||||
import FeatureSet from "CommonServer/Types/FeatureSet";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
|
||||
// Probes
|
||||
import "./Jobs/Probe/SendOwnerAddedNotification";
|
||||
import "./Jobs/Probe/UpdateConnectionStatus";
|
||||
|
||||
const WorkersFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
try {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FileRoute } from "Common/ServiceRoute";
|
||||
import Hostname from "Common/Types/API/Hostname";
|
||||
import Protocol from "Common/Types/API/Protocol";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import LIMIT_MAX from "Common/Types/Database/LimitMax";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import SMS from "Common/Types/SMS/SMS";
|
||||
@@ -49,6 +49,7 @@ RunCron(
|
||||
statusPages: {
|
||||
_id: true,
|
||||
},
|
||||
showAnnouncementAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -62,45 +63,12 @@ RunCron(
|
||||
continue;
|
||||
}
|
||||
|
||||
const statusPages: Array<StatusPage> = await StatusPageService.findBy({
|
||||
query: {
|
||||
_id: QueryHelper.any(
|
||||
announcement.statusPages.map((sp: StatusPage) => {
|
||||
return sp.id!;
|
||||
}),
|
||||
),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
pageTitle: true,
|
||||
projectId: true,
|
||||
isPublicStatusPage: true,
|
||||
logoFileId: true,
|
||||
smtpConfig: {
|
||||
_id: true,
|
||||
hostname: true,
|
||||
port: true,
|
||||
username: true,
|
||||
password: true,
|
||||
fromEmail: true,
|
||||
fromName: true,
|
||||
secure: true,
|
||||
},
|
||||
callSmsConfig: {
|
||||
_id: true,
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
const statusPages: Array<StatusPage> =
|
||||
await StatusPageSubscriberService.getStatusPagesToSendNotification(
|
||||
announcement.statusPages.map((sp: StatusPage) => {
|
||||
return sp.id!;
|
||||
}),
|
||||
);
|
||||
|
||||
await StatusPageAnnouncementService.updateOneById({
|
||||
id: announcement.id!,
|
||||
@@ -114,41 +82,42 @@ RunCron(
|
||||
});
|
||||
|
||||
for (const statuspage of statusPages) {
|
||||
if (!statuspage.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const subscribers: Array<StatusPageSubscriber> =
|
||||
await StatusPageSubscriberService.getSubscribersByStatusPage(
|
||||
statuspage.id!,
|
||||
{
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
||||
statuspage.id,
|
||||
);
|
||||
const statusPageName: string =
|
||||
statuspage.pageTitle || statuspage.name || "Status Page";
|
||||
|
||||
// Send email to Email subscribers.
|
||||
|
||||
for (const subscriber of subscribers) {
|
||||
if (!subscriber._id) {
|
||||
try {
|
||||
if (!statuspage.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const unsubscribeUrl: string =
|
||||
StatusPageSubscriberService.getUnsubscribeLink(
|
||||
URL.fromString(statusPageURL),
|
||||
subscriber.id!,
|
||||
).toString();
|
||||
const subscribers: Array<StatusPageSubscriber> =
|
||||
await StatusPageSubscriberService.getSubscribersByStatusPage(
|
||||
statuspage.id!,
|
||||
{
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (subscriber.subscriberPhone) {
|
||||
const sms: SMS = {
|
||||
message: `
|
||||
const statusPageURL: string =
|
||||
await StatusPageService.getStatusPageURL(statuspage.id);
|
||||
const statusPageName: string =
|
||||
statuspage.pageTitle || statuspage.name || "Status Page";
|
||||
|
||||
// Send email to Email subscribers.
|
||||
|
||||
for (const subscriber of subscribers) {
|
||||
try {
|
||||
if (!subscriber._id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const unsubscribeUrl: string =
|
||||
StatusPageSubscriberService.getUnsubscribeLink(
|
||||
URL.fromString(statusPageURL),
|
||||
subscriber.id!,
|
||||
).toString();
|
||||
|
||||
if (subscriber.subscriberPhone) {
|
||||
const sms: SMS = {
|
||||
message: `
|
||||
Announcement - ${statusPageName}
|
||||
|
||||
${announcement.title || ""}
|
||||
@@ -157,58 +126,66 @@ RunCron(
|
||||
|
||||
To update notification preferences or unsubscribe, visit ${unsubscribeUrl}
|
||||
`,
|
||||
to: subscriber.subscriberPhone,
|
||||
};
|
||||
to: subscriber.subscriberPhone,
|
||||
};
|
||||
|
||||
// send sms here.
|
||||
SmsService.sendSms(sms, {
|
||||
projectId: statuspage.projectId,
|
||||
customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig(
|
||||
statuspage.callSmsConfig,
|
||||
),
|
||||
}).catch((err: Error) => {
|
||||
// send sms here.
|
||||
SmsService.sendSms(sms, {
|
||||
projectId: statuspage.projectId,
|
||||
customTwilioConfig:
|
||||
ProjectCallSMSConfigService.toTwilioConfig(
|
||||
statuspage.callSmsConfig,
|
||||
),
|
||||
}).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (subscriber.subscriberEmail) {
|
||||
// send email here.
|
||||
|
||||
MailService.sendMail(
|
||||
{
|
||||
toEmail: subscriber.subscriberEmail,
|
||||
templateType:
|
||||
EmailTemplateType.SubscriberAnnouncementCreated,
|
||||
vars: {
|
||||
statusPageName: statusPageName,
|
||||
statusPageUrl: statusPageURL,
|
||||
logoUrl: statuspage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute("/image/" + statuspage.logoFileId)
|
||||
.toString()
|
||||
: "",
|
||||
isPublicStatusPage: statuspage.isPublicStatusPage
|
||||
? "true"
|
||||
: "false",
|
||||
announcementTitle: announcement.title || "",
|
||||
announcementDescription: await Markdown.convertToHTML(
|
||||
announcement.description || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
unsubscribeUrl: unsubscribeUrl,
|
||||
},
|
||||
subject: "[Announcement] " + statusPageName,
|
||||
},
|
||||
{
|
||||
mailServer: ProjectSMTPConfigService.toEmailServer(
|
||||
statuspage.smtpConfig,
|
||||
),
|
||||
projectId: statuspage.projectId,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (subscriber.subscriberEmail) {
|
||||
// send email here.
|
||||
|
||||
MailService.sendMail(
|
||||
{
|
||||
toEmail: subscriber.subscriberEmail,
|
||||
templateType: EmailTemplateType.SubscriberAnnouncementCreated,
|
||||
vars: {
|
||||
statusPageName: statusPageName,
|
||||
statusPageUrl: statusPageURL,
|
||||
logoUrl: statuspage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute("/image/" + statuspage.logoFileId)
|
||||
.toString()
|
||||
: "",
|
||||
isPublicStatusPage: statuspage.isPublicStatusPage
|
||||
? "true"
|
||||
: "false",
|
||||
announcementTitle: announcement.title || "",
|
||||
announcementDescription: await Markdown.convertToHTML(
|
||||
announcement.description || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
unsubscribeUrl: unsubscribeUrl,
|
||||
},
|
||||
subject: "[Announcement] " + statusPageName,
|
||||
},
|
||||
{
|
||||
mailServer: ProjectSMTPConfigService.toEmailServer(
|
||||
statuspage.smtpConfig,
|
||||
),
|
||||
projectId: statuspage.projectId,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,100 +128,103 @@ RunCron(
|
||||
);
|
||||
|
||||
for (const statuspage of statusPages) {
|
||||
if (!statuspage.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const subscribers: Array<StatusPageSubscriber> =
|
||||
await StatusPageSubscriberService.getSubscribersByStatusPage(
|
||||
statuspage.id!,
|
||||
{
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
||||
statuspage.id,
|
||||
);
|
||||
const statusPageName: string =
|
||||
statuspage.pageTitle || statuspage.name || "Status Page";
|
||||
|
||||
// Send email to Email subscribers.
|
||||
|
||||
const resourcesAffectedString: string =
|
||||
statusPageToResources[statuspage._id!]
|
||||
?.map((r: StatusPageResource) => {
|
||||
return r.displayName;
|
||||
})
|
||||
.join(", ") || "None";
|
||||
|
||||
for (const subscriber of subscribers) {
|
||||
if (!subscriber._id) {
|
||||
try {
|
||||
if (!statuspage.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const shouldNotifySubscriber: boolean =
|
||||
StatusPageSubscriberService.shouldSendNotification({
|
||||
subscriber: subscriber,
|
||||
statusPageResources: statusPageToResources[statuspage._id!] || [],
|
||||
statusPage: statuspage,
|
||||
});
|
||||
|
||||
if (!shouldNotifySubscriber) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const unsubscribeUrl: string =
|
||||
StatusPageSubscriberService.getUnsubscribeLink(
|
||||
URL.fromString(statusPageURL),
|
||||
subscriber.id!,
|
||||
).toString();
|
||||
|
||||
if (subscriber.subscriberEmail) {
|
||||
// send email here.
|
||||
|
||||
MailService.sendMail(
|
||||
const subscribers: Array<StatusPageSubscriber> =
|
||||
await StatusPageSubscriberService.getSubscribersByStatusPage(
|
||||
statuspage.id!,
|
||||
{
|
||||
toEmail: subscriber.subscriberEmail,
|
||||
templateType: EmailTemplateType.SubscriberIncidentCreated,
|
||||
vars: {
|
||||
statusPageName: statusPageName,
|
||||
statusPageUrl: statusPageURL,
|
||||
logoUrl: statuspage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute("/image/" + statuspage.logoFileId)
|
||||
.toString()
|
||||
: "",
|
||||
isPublicStatusPage: statuspage.isPublicStatusPage
|
||||
? "true"
|
||||
: "false",
|
||||
resourcesAffected: resourcesAffectedString,
|
||||
incidentSeverity: incident.incidentSeverity?.name || " - ",
|
||||
incidentTitle: incident.title || "",
|
||||
incidentDescription: await Markdown.convertToHTML(
|
||||
incident.description || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
unsubscribeUrl: unsubscribeUrl,
|
||||
},
|
||||
subject: "[Incident] " + statusPageName,
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
{
|
||||
mailServer: ProjectSMTPConfigService.toEmailServer(
|
||||
statuspage.smtpConfig,
|
||||
),
|
||||
projectId: statuspage.projectId,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if (subscriber.subscriberPhone) {
|
||||
const sms: SMS = {
|
||||
message: `
|
||||
const statusPageURL: string =
|
||||
await StatusPageService.getStatusPageURL(statuspage.id);
|
||||
const statusPageName: string =
|
||||
statuspage.pageTitle || statuspage.name || "Status Page";
|
||||
|
||||
// Send email to Email subscribers.
|
||||
|
||||
const resourcesAffectedString: string =
|
||||
statusPageToResources[statuspage._id!]
|
||||
?.map((r: StatusPageResource) => {
|
||||
return r.displayName;
|
||||
})
|
||||
.join(", ") || "None";
|
||||
|
||||
for (const subscriber of subscribers) {
|
||||
try {
|
||||
if (!subscriber._id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const shouldNotifySubscriber: boolean =
|
||||
StatusPageSubscriberService.shouldSendNotification({
|
||||
subscriber: subscriber,
|
||||
statusPageResources:
|
||||
statusPageToResources[statuspage._id!] || [],
|
||||
statusPage: statuspage,
|
||||
});
|
||||
|
||||
if (!shouldNotifySubscriber) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const unsubscribeUrl: string =
|
||||
StatusPageSubscriberService.getUnsubscribeLink(
|
||||
URL.fromString(statusPageURL),
|
||||
subscriber.id!,
|
||||
).toString();
|
||||
|
||||
if (subscriber.subscriberEmail) {
|
||||
// send email here.
|
||||
|
||||
MailService.sendMail(
|
||||
{
|
||||
toEmail: subscriber.subscriberEmail,
|
||||
templateType: EmailTemplateType.SubscriberIncidentCreated,
|
||||
vars: {
|
||||
statusPageName: statusPageName,
|
||||
statusPageUrl: statusPageURL,
|
||||
logoUrl: statuspage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute("/image/" + statuspage.logoFileId)
|
||||
.toString()
|
||||
: "",
|
||||
isPublicStatusPage: statuspage.isPublicStatusPage
|
||||
? "true"
|
||||
: "false",
|
||||
resourcesAffected: resourcesAffectedString,
|
||||
incidentSeverity:
|
||||
incident.incidentSeverity?.name || " - ",
|
||||
incidentTitle: incident.title || "",
|
||||
incidentDescription: await Markdown.convertToHTML(
|
||||
incident.description || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
unsubscribeUrl: unsubscribeUrl,
|
||||
},
|
||||
subject: "[Incident] " + statusPageName,
|
||||
},
|
||||
{
|
||||
mailServer: ProjectSMTPConfigService.toEmailServer(
|
||||
statuspage.smtpConfig,
|
||||
),
|
||||
projectId: statuspage.projectId,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (subscriber.subscriberPhone) {
|
||||
const sms: SMS = {
|
||||
message: `
|
||||
Incident - ${statusPageName}
|
||||
|
||||
Title: ${incident.title || ""}
|
||||
@@ -236,19 +239,26 @@ RunCron(
|
||||
|
||||
To update notification preferences or unsubscribe, visit ${unsubscribeUrl}
|
||||
`,
|
||||
to: subscriber.subscriberPhone,
|
||||
};
|
||||
to: subscriber.subscriberPhone,
|
||||
};
|
||||
|
||||
// send sms here.
|
||||
SmsService.sendSms(sms, {
|
||||
projectId: statuspage.projectId,
|
||||
customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig(
|
||||
statuspage.callSmsConfig,
|
||||
),
|
||||
}).catch((err: Error) => {
|
||||
// send sms here.
|
||||
SmsService.sendSms(sms, {
|
||||
projectId: statuspage.projectId,
|
||||
customTwilioConfig:
|
||||
ProjectCallSMSConfigService.toTwilioConfig(
|
||||
statuspage.callSmsConfig,
|
||||
),
|
||||
}).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import RunCron from "../../Utils/Cron";
|
||||
import { CallRequestMessage } from "Common/Types/Call/CallRequest";
|
||||
import LIMIT_MAX from "Common/Types/Database/LimitMax";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { EmailEnvelope } from "Common/Types/Email/EmailMessage";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
@@ -10,9 +11,13 @@ import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import IncidentService from "CommonServer/Services/IncidentService";
|
||||
import ProjectService from "CommonServer/Services/ProjectService";
|
||||
import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService";
|
||||
import Select from "CommonServer/Types/Database/Select";
|
||||
import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Incident from "Model/Models/Incident";
|
||||
import IncidentState from "Model/Models/IncidentState";
|
||||
import Monitor from "Model/Models/Monitor";
|
||||
import Project from "Model/Models/Project";
|
||||
import User from "Model/Models/User";
|
||||
|
||||
RunCron(
|
||||
@@ -36,10 +41,11 @@ RunCron(
|
||||
projectId: true,
|
||||
project: {
|
||||
name: true,
|
||||
},
|
||||
} as Select<Project>,
|
||||
remediationNotes: true,
|
||||
currentIncidentState: {
|
||||
name: true,
|
||||
},
|
||||
} as Select<IncidentState>,
|
||||
incidentSeverity: {
|
||||
name: true,
|
||||
},
|
||||
@@ -47,10 +53,20 @@ RunCron(
|
||||
monitors: {
|
||||
name: true,
|
||||
},
|
||||
createdByProbe: {
|
||||
name: true,
|
||||
},
|
||||
createdByUser: {
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const incident of incidents) {
|
||||
const incidentIdentifiedDate: Date =
|
||||
await IncidentService.getIncidentIdentifiedDate(incident.id!);
|
||||
|
||||
await IncidentService.updateOneById({
|
||||
id: incident.id!,
|
||||
data: {
|
||||
@@ -78,63 +94,100 @@ RunCron(
|
||||
continue;
|
||||
}
|
||||
|
||||
const vars: Dictionary<string> = {
|
||||
incidentTitle: incident.title!,
|
||||
projectName: incident.project!.name!,
|
||||
currentState: incident.currentIncidentState!.name!,
|
||||
incidentDescription: await Markdown.convertToHTML(
|
||||
incident.description! || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
resourcesAffected:
|
||||
incident
|
||||
.monitors!.map((monitor: Monitor) => {
|
||||
return monitor.name!;
|
||||
})
|
||||
.join(", ") || "None",
|
||||
incidentSeverity: incident.incidentSeverity!.name!,
|
||||
rootCause:
|
||||
incident.rootCause || "No root cause identified for this incident",
|
||||
incidentViewLink: (
|
||||
await IncidentService.getIncidentLinkInDashboard(
|
||||
incident.projectId!,
|
||||
incident.id!,
|
||||
)
|
||||
).toString(),
|
||||
};
|
||||
let declaredBy: string = "OneUptime";
|
||||
|
||||
if (doesResourceHasOwners === true) {
|
||||
vars["isOwner"] = "true";
|
||||
if (incident.createdByProbe && incident.createdByProbe.name) {
|
||||
declaredBy = incident.createdByProbe.name;
|
||||
}
|
||||
|
||||
if (
|
||||
incident.createdByUser &&
|
||||
incident.createdByUser.name &&
|
||||
incident.createdByUser.email
|
||||
) {
|
||||
declaredBy = `${incident.createdByUser.name.toString()} (${incident.createdByUser.email.toString()})`;
|
||||
}
|
||||
|
||||
for (const user of owners) {
|
||||
const emailMessage: EmailEnvelope = {
|
||||
templateType: EmailTemplateType.IncidentOwnerResourceCreated,
|
||||
vars: vars,
|
||||
subject: "[Incident] " + incident.title!,
|
||||
};
|
||||
try {
|
||||
const vars: Dictionary<string> = {
|
||||
incidentTitle: incident.title!,
|
||||
projectName: incident.project!.name!,
|
||||
currentState: incident.currentIncidentState!.name!,
|
||||
incidentDescription: await Markdown.convertToHTML(
|
||||
incident.description! || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
resourcesAffected:
|
||||
incident
|
||||
.monitors!.map((monitor: Monitor) => {
|
||||
return monitor.name!;
|
||||
})
|
||||
.join(", ") || "None",
|
||||
incidentSeverity: incident.incidentSeverity!.name!,
|
||||
declaredAt: OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones(
|
||||
{
|
||||
date: incidentIdentifiedDate,
|
||||
timezones: user.timezone ? [user.timezone] : [],
|
||||
},
|
||||
),
|
||||
declaredBy: declaredBy,
|
||||
remediationNotes:
|
||||
(await Markdown.convertToHTML(
|
||||
incident.remediationNotes! || "",
|
||||
MarkdownContentType.Email,
|
||||
)) || "",
|
||||
rootCause:
|
||||
(await Markdown.convertToHTML(
|
||||
incident.rootCause ||
|
||||
"No root cause identified for this incident",
|
||||
MarkdownContentType.Email,
|
||||
)) || "",
|
||||
incidentViewLink: (
|
||||
await IncidentService.getIncidentLinkInDashboard(
|
||||
incident.projectId!,
|
||||
incident.id!,
|
||||
)
|
||||
).toString(),
|
||||
};
|
||||
|
||||
const sms: SMSMessage = {
|
||||
message: `This is a message from OneUptime. New incident created: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`,
|
||||
};
|
||||
if (doesResourceHasOwners === true) {
|
||||
vars["isOwner"] = "true";
|
||||
}
|
||||
|
||||
const callMessage: CallRequestMessage = {
|
||||
data: [
|
||||
{
|
||||
sayMessage: `This is a message from OneUptime. New incident created: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
const emailMessage: EmailEnvelope = {
|
||||
templateType: EmailTemplateType.IncidentOwnerResourceCreated,
|
||||
vars: vars,
|
||||
subject: "[New Incident] " + incident.title!,
|
||||
};
|
||||
|
||||
await UserNotificationSettingService.sendUserNotification({
|
||||
userId: user.id!,
|
||||
projectId: incident.projectId!,
|
||||
emailEnvelope: emailMessage,
|
||||
smsMessage: sms,
|
||||
callRequestMessage: callMessage,
|
||||
eventType:
|
||||
NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION,
|
||||
});
|
||||
const sms: SMSMessage = {
|
||||
message: `This is a message from OneUptime. New incident created: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`,
|
||||
};
|
||||
|
||||
const callMessage: CallRequestMessage = {
|
||||
data: [
|
||||
{
|
||||
sayMessage: `This is a message from OneUptime. New incident created: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await UserNotificationSettingService.sendUserNotification({
|
||||
userId: user.id!,
|
||||
projectId: incident.projectId!,
|
||||
emailEnvelope: emailMessage,
|
||||
smsMessage: sms,
|
||||
callRequestMessage: callMessage,
|
||||
eventType:
|
||||
NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
"Error in sending incident created resource notification",
|
||||
);
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -129,37 +129,39 @@ RunCron(
|
||||
continue;
|
||||
}
|
||||
|
||||
const vars: Dictionary<string> = {
|
||||
incidentTitle: incident.title!,
|
||||
projectName: incidentStateTimeline.project!.name!,
|
||||
currentState: incidentState!.name!,
|
||||
incidentDescription: await Markdown.convertToHTML(
|
||||
incident.description! || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
resourcesAffected:
|
||||
incident
|
||||
.monitors!.map((monitor: Monitor) => {
|
||||
return monitor.name!;
|
||||
})
|
||||
.join(", ") || "None",
|
||||
stateChangedAt: OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones(
|
||||
incidentStateTimeline.createdAt!,
|
||||
),
|
||||
incidentSeverity: incidentWithSeverity.incidentSeverity!.name!,
|
||||
incidentViewLink: (
|
||||
await IncidentService.getIncidentLinkInDashboard(
|
||||
incidentStateTimeline.projectId!,
|
||||
incident.id!,
|
||||
)
|
||||
).toString(),
|
||||
};
|
||||
|
||||
if (doesResourceHasOwners === true) {
|
||||
vars["isOwner"] = "true";
|
||||
}
|
||||
|
||||
for (const user of owners) {
|
||||
const vars: Dictionary<string> = {
|
||||
incidentTitle: incident.title!,
|
||||
projectName: incidentStateTimeline.project!.name!,
|
||||
currentState: incidentState!.name!,
|
||||
incidentDescription: await Markdown.convertToHTML(
|
||||
incident.description! || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
resourcesAffected:
|
||||
incident
|
||||
.monitors!.map((monitor: Monitor) => {
|
||||
return monitor.name!;
|
||||
})
|
||||
.join(", ") || "None",
|
||||
stateChangedAt:
|
||||
OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
|
||||
date: incidentStateTimeline.createdAt!,
|
||||
timezones: user.timezone ? [user.timezone] : [],
|
||||
}),
|
||||
incidentSeverity: incidentWithSeverity.incidentSeverity!.name!,
|
||||
incidentViewLink: (
|
||||
await IncidentService.getIncidentLinkInDashboard(
|
||||
incidentStateTimeline.projectId!,
|
||||
incident.id!,
|
||||
)
|
||||
).toString(),
|
||||
};
|
||||
|
||||
if (doesResourceHasOwners === true) {
|
||||
vars["isOwner"] = "true";
|
||||
}
|
||||
|
||||
const emailMessage: EmailEnvelope = {
|
||||
templateType: EmailTemplateType.IncidentOwnerStateChanged,
|
||||
vars: vars,
|
||||
|
||||
@@ -5,6 +5,7 @@ import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/Incomin
|
||||
import MonitorType from "Common/Types/Monitor/MonitorType";
|
||||
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import MonitorService from "CommonServer/Services/MonitorService";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import ProbeMonitorResponseService from "CommonServer/Utils/Probe/ProbeMonitorResponse";
|
||||
import Monitor from "Model/Models/Monitor";
|
||||
|
||||
@@ -12,6 +13,8 @@ RunCron(
|
||||
"IncomingRequestMonitor:CheckHeartbeat",
|
||||
{ schedule: EVERY_MINUTE, runOnStartup: false },
|
||||
async () => {
|
||||
logger.debug("Checking IncomingRequestMonitor:CheckHeartbeat");
|
||||
|
||||
const incomingRequestMonitors: Array<Monitor> = await MonitorService.findBy(
|
||||
{
|
||||
query: {
|
||||
@@ -31,28 +34,46 @@ RunCron(
|
||||
},
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
`Found ${incomingRequestMonitors.length} incoming request monitors`,
|
||||
);
|
||||
|
||||
logger.debug(incomingRequestMonitors);
|
||||
|
||||
for (const monitor of incomingRequestMonitors) {
|
||||
if (!monitor.monitorSteps) {
|
||||
continue;
|
||||
try {
|
||||
if (!monitor.monitorSteps) {
|
||||
logger.debug("Monitor has no steps. Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
const processRequest: boolean = shouldProcessRequest(monitor);
|
||||
|
||||
logger.debug(
|
||||
`Monitor: ${monitor.id} should process request: ${processRequest}`,
|
||||
);
|
||||
|
||||
if (!processRequest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const incomingRequest: IncomingMonitorRequest = {
|
||||
monitorId: monitor.id!,
|
||||
requestHeaders: undefined,
|
||||
requestBody: undefined,
|
||||
requestMethod: undefined,
|
||||
incomingRequestReceivedAt:
|
||||
monitor.incomingRequestReceivedAt || monitor.createdAt!,
|
||||
onlyCheckForIncomingRequestReceivedAt: true,
|
||||
};
|
||||
|
||||
await ProbeMonitorResponseService.processProbeResponse(incomingRequest);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error while processing incoming request monitor: ${monitor.id?.toString()}`,
|
||||
);
|
||||
logger.error(error);
|
||||
}
|
||||
|
||||
const processRequest: boolean = shouldProcessRequest(monitor);
|
||||
|
||||
if (!processRequest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const incomingRequest: IncomingMonitorRequest = {
|
||||
monitorId: monitor.id!,
|
||||
requestHeaders: undefined,
|
||||
requestBody: undefined,
|
||||
requestMethod: undefined,
|
||||
incomingRequestReceivedAt:
|
||||
monitor.incomingRequestReceivedAt || monitor.createdAt!,
|
||||
onlyCheckForIncomingRequestReceivedAt: true,
|
||||
};
|
||||
|
||||
await ProbeMonitorResponseService.processProbeResponse(incomingRequest);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PlanType } from "Common/Types/Billing/SubscriptionPlan";
|
||||
import RunCron from "../../Utils/Cron";
|
||||
import LIMIT_MAX from "Common/Types/Database/LimitMax";
|
||||
import Sleep from "Common/Types/Sleep";
|
||||
@@ -45,6 +46,16 @@ RunCron(
|
||||
for (const project of projects) {
|
||||
try {
|
||||
if (project.id) {
|
||||
const plan: {
|
||||
plan: PlanType | null;
|
||||
isSubscriptionUnpaid: boolean;
|
||||
} = await ProjectService.getCurrentPlan(project.id);
|
||||
|
||||
if (plan.isSubscriptionUnpaid) {
|
||||
// ignore and report when subscription is active.
|
||||
continue;
|
||||
}
|
||||
|
||||
await LogDataIngestMeteredPlan.reportQuantityToBillingProvider(
|
||||
project.id,
|
||||
);
|
||||
|
||||
@@ -86,33 +86,37 @@ RunCron(
|
||||
continue;
|
||||
}
|
||||
|
||||
const vars: Dictionary<string> = {
|
||||
monitorName: monitor.name!,
|
||||
projectName: monitorStatusTimeline.project!.name!,
|
||||
currentStatus: monitorStatus!.name!,
|
||||
monitorDescription: await Markdown.convertToHTML(
|
||||
monitor.description! || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
statusChangedAt:
|
||||
OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones(
|
||||
monitorStatusTimeline.createdAt!,
|
||||
),
|
||||
monitorViewLink: (
|
||||
await MonitorService.getMonitorLinkInDashboard(
|
||||
monitorStatusTimeline.projectId!,
|
||||
monitor.id!,
|
||||
)
|
||||
).toString(),
|
||||
rootCause:
|
||||
monitorStatusTimeline.rootCause || "No root cause identified.",
|
||||
};
|
||||
|
||||
if (doesResourceHasOwners === true) {
|
||||
vars["isOwner"] = "true";
|
||||
}
|
||||
|
||||
for (const user of owners) {
|
||||
const vars: Dictionary<string> = {
|
||||
monitorName: monitor.name!,
|
||||
projectName: monitorStatusTimeline.project!.name!,
|
||||
currentStatus: monitorStatus!.name!,
|
||||
monitorDescription: await Markdown.convertToHTML(
|
||||
monitor.description! || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
statusChangedAt:
|
||||
OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
|
||||
date: monitorStatusTimeline.createdAt!,
|
||||
timezones: user.timezone ? [user.timezone] : [],
|
||||
}),
|
||||
monitorViewLink: (
|
||||
await MonitorService.getMonitorLinkInDashboard(
|
||||
monitorStatusTimeline.projectId!,
|
||||
monitor.id!,
|
||||
)
|
||||
).toString(),
|
||||
rootCause:
|
||||
(await Markdown.convertToHTML(
|
||||
monitorStatusTimeline.rootCause || "",
|
||||
MarkdownContentType.Email,
|
||||
)) || "",
|
||||
};
|
||||
|
||||
if (doesResourceHasOwners === true) {
|
||||
vars["isOwner"] = "true";
|
||||
}
|
||||
|
||||
const emailMessage: EmailEnvelope = {
|
||||
templateType: EmailTemplateType.MonitorOwnerStatusChanged,
|
||||
vars: vars,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import RunCron from "../../Utils/Cron";
|
||||
import SubscriptionPlan, {
|
||||
PlanSelect,
|
||||
PlanType,
|
||||
} from "Common/Types/Billing/SubscriptionPlan";
|
||||
import LIMIT_MAX from "Common/Types/Database/LimitMax";
|
||||
import { EVERY_WEEK } from "Common/Utils/CronTime";
|
||||
@@ -39,7 +39,7 @@ RunCron(
|
||||
try {
|
||||
if (project.paymentProviderPlanId) {
|
||||
// get subscription detail.
|
||||
const planName: PlanSelect = SubscriptionPlan.getPlanSelect(
|
||||
const planName: PlanType = SubscriptionPlan.getPlanType(
|
||||
project.paymentProviderPlanId as string,
|
||||
);
|
||||
|
||||
|
||||
190
App/FeatureSet/Workers/Jobs/Probe/SendOwnerAddedNotification.ts
Normal file
190
App/FeatureSet/Workers/Jobs/Probe/SendOwnerAddedNotification.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import RunCron from "../../Utils/Cron";
|
||||
import { CallRequestMessage } from "Common/Types/Call/CallRequest";
|
||||
import LIMIT_MAX from "Common/Types/Database/LimitMax";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { EmailEnvelope } from "Common/Types/Email/EmailMessage";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import { SMSMessage } from "Common/Types/SMS/SMS";
|
||||
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import ProbeOwnerTeamService from "CommonServer/Services/ProbeOwnerTeamService";
|
||||
import ProbeOwnerUserService from "CommonServer/Services/ProbeOwnerUserService";
|
||||
import TeamMemberService from "CommonServer/Services/TeamMemberService";
|
||||
import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService";
|
||||
import ProbeOwnerTeam from "Model/Models/ProbeOwnerTeam";
|
||||
import ProbeOwnerUser from "Model/Models/ProbeOwnerUser";
|
||||
import User from "Model/Models/User";
|
||||
import Probe from "Model/Models/Probe";
|
||||
import ProbeService from "CommonServer/Services/ProbeService";
|
||||
|
||||
RunCron(
|
||||
"ProbeOwner:SendOwnerAddedEmail",
|
||||
{ schedule: EVERY_MINUTE, runOnStartup: false },
|
||||
async () => {
|
||||
const probeOwnerTeams: Array<ProbeOwnerTeam> =
|
||||
await ProbeOwnerTeamService.findBy({
|
||||
query: {
|
||||
isOwnerNotified: false,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
select: {
|
||||
_id: true,
|
||||
probeId: true,
|
||||
teamId: true,
|
||||
},
|
||||
});
|
||||
|
||||
const probeOwnersMap: Dictionary<Array<User>> = {};
|
||||
|
||||
for (const probeOwnerTeam of probeOwnerTeams) {
|
||||
const probeId: ObjectID = probeOwnerTeam.probeId!;
|
||||
const teamId: ObjectID = probeOwnerTeam.teamId!;
|
||||
|
||||
const users: Array<User> = await TeamMemberService.getUsersInTeams([
|
||||
teamId,
|
||||
]);
|
||||
|
||||
if (probeOwnersMap[probeId.toString()] === undefined) {
|
||||
probeOwnersMap[probeId.toString()] = [];
|
||||
}
|
||||
|
||||
for (const user of users) {
|
||||
(probeOwnersMap[probeId.toString()] as Array<User>).push(user);
|
||||
}
|
||||
|
||||
// mark this as notified.
|
||||
await ProbeOwnerTeamService.updateOneById({
|
||||
id: probeOwnerTeam.id!,
|
||||
data: {
|
||||
isOwnerNotified: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const probeOwnerUsers: Array<ProbeOwnerUser> =
|
||||
await ProbeOwnerUserService.findBy({
|
||||
query: {
|
||||
isOwnerNotified: false,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
select: {
|
||||
_id: true,
|
||||
probeId: true,
|
||||
userId: true,
|
||||
user: {
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const probeOwnerUser of probeOwnerUsers) {
|
||||
const probeId: ObjectID = probeOwnerUser.probeId!;
|
||||
const user: User = probeOwnerUser.user!;
|
||||
|
||||
if (probeOwnersMap[probeId.toString()] === undefined) {
|
||||
probeOwnersMap[probeId.toString()] = [];
|
||||
}
|
||||
|
||||
(probeOwnersMap[probeId.toString()] as Array<User>).push(user);
|
||||
|
||||
// mark this as notified.
|
||||
await ProbeOwnerUserService.updateOneById({
|
||||
id: probeOwnerUser.id!,
|
||||
data: {
|
||||
isOwnerNotified: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// send email to all of these users.
|
||||
|
||||
for (const probeId in probeOwnersMap) {
|
||||
if (!probeOwnersMap[probeId]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((probeOwnersMap[probeId] as Array<User>).length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const users: Array<User> = probeOwnersMap[probeId] as Array<User>;
|
||||
|
||||
// get all scheduled events of all the projects.
|
||||
const probe: Probe | null = await ProbeService.findOneById({
|
||||
id: new ObjectID(probeId),
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
description: true,
|
||||
projectId: true,
|
||||
project: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!probe) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const vars: Dictionary<string> = {
|
||||
probeName: probe.name!,
|
||||
probeDescription: probe.description || "No description provided",
|
||||
projectName: probe.project!.name!,
|
||||
viewProbeLink: (
|
||||
await ProbeService.getLinkInDashboard(probe.projectId!, probe.id!)
|
||||
).toString(),
|
||||
};
|
||||
|
||||
for (const user of users) {
|
||||
const emailMessage: EmailEnvelope = {
|
||||
templateType: EmailTemplateType.ProbeOwnerAdded,
|
||||
vars: vars,
|
||||
subject: "[Probe] Owner of " + probe.name,
|
||||
};
|
||||
|
||||
const sms: SMSMessage = {
|
||||
message: `This is a message from OneUptime. You have been added as the owner of the probe: ${probe.name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`,
|
||||
};
|
||||
|
||||
const callMessage: CallRequestMessage = {
|
||||
data: [
|
||||
{
|
||||
sayMessage: `This is a message from OneUptime. You have been added as the owner of the probe: ${probe.name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await UserNotificationSettingService.sendUserNotification({
|
||||
userId: user.id!,
|
||||
projectId: probe.projectId!,
|
||||
emailEnvelope: emailMessage,
|
||||
smsMessage: sms,
|
||||
callRequestMessage: callMessage,
|
||||
eventType:
|
||||
NotificationSettingEventType.SEND_PROBE_OWNER_ADDED_NOTIFICATION,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
94
App/FeatureSet/Workers/Jobs/Probe/UpdateConnectionStatus.ts
Normal file
94
App/FeatureSet/Workers/Jobs/Probe/UpdateConnectionStatus.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import RunCron from "../../Utils/Cron";
|
||||
import LIMIT_MAX from "Common/Types/Database/LimitMax";
|
||||
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import ProbeService from "CommonServer/Services/ProbeService";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Probe, { ProbeConnectionStatus } from "Model/Models/Probe";
|
||||
|
||||
RunCron(
|
||||
"Probe:UpdateConnectionStatus",
|
||||
{ schedule: EVERY_MINUTE, runOnStartup: false },
|
||||
async () => {
|
||||
logger.debug("Checking Probe:UpdateConnectionStatus");
|
||||
|
||||
const probes: Array<Probe> = await ProbeService.findBy({
|
||||
query: {},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
lastAlive: true,
|
||||
connectionStatus: true,
|
||||
projectId: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
});
|
||||
|
||||
logger.debug(`Found ${probes.length} incoming request monitors`);
|
||||
|
||||
logger.debug(probes);
|
||||
|
||||
for (const probe of probes) {
|
||||
try {
|
||||
// if the lastAlive is more than 2 minutes old, then set the connection status to false
|
||||
|
||||
if (!probe.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let connectionStatus: ProbeConnectionStatus =
|
||||
ProbeConnectionStatus.Connected;
|
||||
|
||||
if (!probe.lastAlive) {
|
||||
connectionStatus = ProbeConnectionStatus.Disconnected;
|
||||
}
|
||||
|
||||
if (
|
||||
probe.lastAlive &&
|
||||
OneUptimeDate.getDifferenceInMinutes(
|
||||
OneUptimeDate.getCurrentDate(),
|
||||
probe.lastAlive,
|
||||
) > 2
|
||||
) {
|
||||
connectionStatus = ProbeConnectionStatus.Disconnected;
|
||||
} else {
|
||||
connectionStatus = ProbeConnectionStatus.Connected;
|
||||
}
|
||||
|
||||
if (!probe.lastAlive) {
|
||||
connectionStatus = ProbeConnectionStatus.Disconnected;
|
||||
}
|
||||
|
||||
let shouldUpdateConnectionStatus: boolean = false;
|
||||
|
||||
if (probe.connectionStatus !== connectionStatus) {
|
||||
shouldUpdateConnectionStatus = true;
|
||||
}
|
||||
|
||||
if (!shouldUpdateConnectionStatus) {
|
||||
continue; // no need to update the connection status.
|
||||
}
|
||||
|
||||
// now update the connection status
|
||||
probe.connectionStatus = connectionStatus;
|
||||
|
||||
if (shouldUpdateConnectionStatus) {
|
||||
await ProbeService.updateOneById({
|
||||
id: probe.id!,
|
||||
data: {
|
||||
connectionStatus: connectionStatus,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -235,9 +235,10 @@ RunCron(
|
||||
: "false",
|
||||
resourcesAffected: resourcesAffected,
|
||||
scheduledAt:
|
||||
OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones(
|
||||
event.startsAt!,
|
||||
),
|
||||
OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
|
||||
date: event.startsAt!,
|
||||
timezones: statuspage.subscriberTimezones || [],
|
||||
}),
|
||||
eventTitle: event.title || "",
|
||||
eventDescription: await Markdown.convertToHTML(
|
||||
event.description || "",
|
||||
|
||||
@@ -38,6 +38,7 @@ RunCron(
|
||||
select: {
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
startsAt: true,
|
||||
projectId: true,
|
||||
project: {
|
||||
name: true,
|
||||
@@ -90,30 +91,32 @@ RunCron(
|
||||
continue;
|
||||
}
|
||||
|
||||
const vars: Dictionary<string> = {
|
||||
scheduledMaintenanceTitle: scheduledMaintenance.title!,
|
||||
projectName: scheduledMaintenanceStateTimeline.project!.name!,
|
||||
currentState: scheduledMaintenanceState!.name!,
|
||||
scheduledMaintenanceDescription: await Markdown.convertToHTML(
|
||||
scheduledMaintenance.description! || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
stateChangedAt: OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones(
|
||||
scheduledMaintenanceStateTimeline.createdAt!,
|
||||
),
|
||||
scheduledMaintenanceViewLink: (
|
||||
await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard(
|
||||
scheduledMaintenanceStateTimeline.projectId!,
|
||||
scheduledMaintenance.id!,
|
||||
)
|
||||
).toString(),
|
||||
};
|
||||
|
||||
if (doesResourceHasOwners === true) {
|
||||
vars["isOwner"] = "true";
|
||||
}
|
||||
|
||||
for (const user of owners) {
|
||||
const vars: Dictionary<string> = {
|
||||
scheduledMaintenanceTitle: scheduledMaintenance.title!,
|
||||
projectName: scheduledMaintenanceStateTimeline.project!.name!,
|
||||
currentState: scheduledMaintenanceState!.name!,
|
||||
scheduledMaintenanceDescription: await Markdown.convertToHTML(
|
||||
scheduledMaintenance.description! || "",
|
||||
MarkdownContentType.Email,
|
||||
),
|
||||
stateChangedAt:
|
||||
OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
|
||||
date: scheduledMaintenanceStateTimeline.startsAt!,
|
||||
timezones: user.timezone ? [user.timezone] : [],
|
||||
}),
|
||||
scheduledMaintenanceViewLink: (
|
||||
await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard(
|
||||
scheduledMaintenanceStateTimeline.projectId!,
|
||||
scheduledMaintenance.id!,
|
||||
)
|
||||
).toString(),
|
||||
};
|
||||
|
||||
if (doesResourceHasOwners === true) {
|
||||
vars["isOwner"] = "true";
|
||||
}
|
||||
|
||||
const emailMessage: EmailEnvelope = {
|
||||
templateType: EmailTemplateType.ScheduledMaintenanceOwnerStateChanged,
|
||||
vars: vars,
|
||||
|
||||
@@ -7,6 +7,7 @@ import ServerMonitorResponse from "Common/Types/Monitor/ServerMonitor/ServerMoni
|
||||
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import MonitorService from "CommonServer/Services/MonitorService";
|
||||
import QueryHelper from "CommonServer/Types/Database/QueryHelper";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import ProbeMonitorResponseService from "CommonServer/Utils/Probe/ProbeMonitorResponse";
|
||||
import Monitor from "Model/Models/Monitor";
|
||||
|
||||
@@ -36,26 +37,34 @@ RunCron(
|
||||
});
|
||||
|
||||
for (const monitor of serverMonitors) {
|
||||
if (!monitor.monitorSteps) {
|
||||
continue;
|
||||
try {
|
||||
if (!monitor.monitorSteps) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const processRequest: boolean = shouldProcessRequest(monitor);
|
||||
|
||||
if (!processRequest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const serverMonitorResponse: ServerMonitorResponse = {
|
||||
monitorId: monitor.id!,
|
||||
onlyCheckRequestReceivedAt: true,
|
||||
requestReceivedAt:
|
||||
monitor.serverMonitorRequestReceivedAt || monitor.createdAt!,
|
||||
hostname: "",
|
||||
};
|
||||
|
||||
await ProbeMonitorResponseService.processProbeResponse(
|
||||
serverMonitorResponse,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error in ServerMonitor:CheckOnlineStatus for monitorId: ${monitor.id}`,
|
||||
);
|
||||
logger.error(error);
|
||||
}
|
||||
|
||||
const processRequest: boolean = shouldProcessRequest(monitor);
|
||||
|
||||
if (!processRequest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const serverMonitorResponse: ServerMonitorResponse = {
|
||||
monitorId: monitor.id!,
|
||||
onlyCheckRequestReceivedAt: true,
|
||||
requestReceivedAt:
|
||||
monitor.serverMonitorRequestReceivedAt || monitor.createdAt!,
|
||||
};
|
||||
|
||||
await ProbeMonitorResponseService.processProbeResponse(
|
||||
serverMonitorResponse,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -49,3 +49,15 @@ RunCron(
|
||||
await StatusPageDomainService.verifyCnameWhoseCnameisNotVerified();
|
||||
},
|
||||
);
|
||||
|
||||
RunCron(
|
||||
"StatusPageCerts:CheckOrderStatus",
|
||||
{
|
||||
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE,
|
||||
runOnStartup: true,
|
||||
},
|
||||
async () => {
|
||||
// checks if the certificate exists for the domains that have ordered certificates, otherwise orders again,
|
||||
await StatusPageDomainService.checkOrderStatus();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan";
|
||||
import { PlanType } from "Common/Types/Billing/SubscriptionPlan";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
@@ -88,7 +88,7 @@ export default class QueueWorkflow {
|
||||
|
||||
//check project and plan
|
||||
const projectPlan: {
|
||||
plan: PlanSelect | null;
|
||||
plan: PlanType | null;
|
||||
isSubscriptionUnpaid: boolean;
|
||||
} = await ProjectService.getCurrentPlan(workflow.projectId);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import ColumnBillingAccessControl from "../Types/BaseDatabase/ColumnBillingAcces
|
||||
import EnableWorkflowOn from "../Types/BaseDatabase/EnableWorkflowOn";
|
||||
import ModelPermission from "../Types/BaseDatabase/ModelPermission";
|
||||
import TableBillingAccessControl from "../Types/BaseDatabase/TableBillingAccessControl";
|
||||
import { PlanSelect } from "../Types/Billing/SubscriptionPlan";
|
||||
import { PlanType } from "../Types/Billing/SubscriptionPlan";
|
||||
import Dictionary from "../Types/Dictionary";
|
||||
import BadDataException from "../Types/Exception/BadDataException";
|
||||
import { JSONValue } from "../Types/JSON";
|
||||
@@ -358,19 +358,19 @@ export default class AnalyticsBaseModel extends CommonModel {
|
||||
return this.accessControl?.delete || [];
|
||||
}
|
||||
|
||||
public getReadBillingPlan(): PlanSelect | null {
|
||||
public getReadBillingPlan(): PlanType | null {
|
||||
return this.tableBillingAccessControl?.read || null;
|
||||
}
|
||||
|
||||
public getCreateBillingPlan(): PlanSelect | null {
|
||||
public getCreateBillingPlan(): PlanType | null {
|
||||
return this.tableBillingAccessControl?.create || null;
|
||||
}
|
||||
|
||||
public getUpdateBillingPlan(): PlanSelect | null {
|
||||
public getUpdateBillingPlan(): PlanType | null {
|
||||
return this.tableBillingAccessControl?.update || null;
|
||||
}
|
||||
|
||||
public getDeleteBillingPlan(): PlanSelect | null {
|
||||
public getDeleteBillingPlan(): PlanType | null {
|
||||
return this.tableBillingAccessControl?.delete || null;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ColumnAccessControl } from "../Types/BaseDatabase/AccessControl";
|
||||
import ColumnBillingAccessControl from "../Types/BaseDatabase/ColumnBillingAccessControl";
|
||||
import EnableWorkflowOn from "../Types/BaseDatabase/EnableWorkflowOn";
|
||||
import ModelPermission from "../Types/BaseDatabase/ModelPermission";
|
||||
import { PlanSelect } from "../Types/Billing/SubscriptionPlan";
|
||||
import { PlanType } from "../Types/Billing/SubscriptionPlan";
|
||||
import { getColumnAccessControlForAllColumns } from "../Types/Database/AccessControl/ColumnAccessControl";
|
||||
import { getColumnBillingAccessControlForAllColumns } from "../Types/Database/AccessControl/ColumnBillingAccessControl";
|
||||
import Columns from "../Types/Database/Columns";
|
||||
@@ -96,10 +96,10 @@ export default class BaseModel extends BaseEntity {
|
||||
public updateRecordPermissions!: Array<Permission>;
|
||||
|
||||
// Billing Plans.
|
||||
public createBillingPlan!: PlanSelect | null;
|
||||
public readBillingPlan!: PlanSelect | null;
|
||||
public updateBillingPlan!: PlanSelect | null;
|
||||
public deleteBillingPlan!: PlanSelect | null;
|
||||
public createBillingPlan!: PlanType | null;
|
||||
public readBillingPlan!: PlanType | null;
|
||||
public updateBillingPlan!: PlanType | null;
|
||||
public deleteBillingPlan!: PlanType | null;
|
||||
|
||||
public allowAccessIfSubscriptionIsUnpaid!: boolean;
|
||||
|
||||
@@ -465,19 +465,19 @@ export default class BaseModel extends BaseEntity {
|
||||
return this.readRecordPermissions;
|
||||
}
|
||||
|
||||
public getReadBillingPlan(): PlanSelect | null {
|
||||
public getReadBillingPlan(): PlanType | null {
|
||||
return this.readBillingPlan;
|
||||
}
|
||||
|
||||
public getCreateBillingPlan(): PlanSelect | null {
|
||||
public getCreateBillingPlan(): PlanType | null {
|
||||
return this.createBillingPlan;
|
||||
}
|
||||
|
||||
public getUpdateBillingPlan(): PlanSelect | null {
|
||||
public getUpdateBillingPlan(): PlanType | null {
|
||||
return this.updateBillingPlan;
|
||||
}
|
||||
|
||||
public getDeleteBillingPlan(): PlanSelect | null {
|
||||
public getDeleteBillingPlan(): PlanType | null {
|
||||
return this.deleteBillingPlan;
|
||||
}
|
||||
|
||||
|
||||
9
Common/Tests/MockType.ts
Normal file
9
Common/Tests/MockType.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type MockFunction = jest.Mock<any, any>;
|
||||
|
||||
type GetJestMockFunction = () => MockFunction;
|
||||
|
||||
const getJestMockFunction: GetJestMockFunction = (): MockFunction => {
|
||||
return jest.fn() as MockFunction;
|
||||
};
|
||||
|
||||
export default getJestMockFunction;
|
||||
11
Common/Tests/Spy.ts
Normal file
11
Common/Tests/Spy.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
type GetJestSpyOnFunction = (
|
||||
obj: any,
|
||||
method: string,
|
||||
) => jest.SpyInstance<any, any>;
|
||||
|
||||
export const getJestSpyOn: GetJestSpyOnFunction = (
|
||||
obj: any,
|
||||
method: string,
|
||||
): jest.SpyInstance<any, any> => {
|
||||
return jest.spyOn(obj, method) as jest.SpyInstance<any, any>;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import SubscriptionPlan, {
|
||||
PlanSelect,
|
||||
PlanType,
|
||||
} from "../../../Types/Billing/SubscriptionPlan";
|
||||
import BadDataException from "../../../Types/Exception/BadDataException";
|
||||
import { JSONObject } from "../../../Types/JSON";
|
||||
@@ -118,29 +118,26 @@ describe("SubscriptionPlan", () => {
|
||||
expect(isValidPlanId).toBe(true);
|
||||
});
|
||||
});
|
||||
describe("getPlanSelect", () => {
|
||||
describe("getPlanType", () => {
|
||||
it("should return the plan name if valid planId is passed", () => {
|
||||
new SubscriptionPlan(
|
||||
monthlyPlanId,
|
||||
"yearly_plan_id",
|
||||
PlanSelect.Free,
|
||||
PlanType.Free,
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
30,
|
||||
);
|
||||
const result: PlanSelect = SubscriptionPlan.getPlanSelect(
|
||||
monthlyPlanId,
|
||||
env,
|
||||
);
|
||||
expect(result).toBe(PlanSelect.Free);
|
||||
const result: PlanType = SubscriptionPlan.getPlanType(monthlyPlanId, env);
|
||||
expect(result).toBe(PlanType.Free);
|
||||
});
|
||||
it("should throw an error if invalid PlanId is passed", () => {
|
||||
SubscriptionPlan.getSubscriptionPlanById = jest
|
||||
.fn()
|
||||
.mockReturnValue(undefined);
|
||||
expect(() => {
|
||||
SubscriptionPlan.getPlanSelect("invalid-plan-id", env);
|
||||
SubscriptionPlan.getPlanType("invalid-plan-id", env);
|
||||
}).toThrow(BadDataException);
|
||||
});
|
||||
});
|
||||
@@ -170,7 +167,7 @@ describe("SubscriptionPlan", () => {
|
||||
const featureSubscriptionPlan: SubscriptionPlan = new SubscriptionPlan(
|
||||
"growth_monthly_plan_id",
|
||||
"growth_yearly_plan_id",
|
||||
PlanSelect.Growth,
|
||||
PlanType.Growth,
|
||||
9,
|
||||
99,
|
||||
2,
|
||||
@@ -179,15 +176,15 @@ describe("SubscriptionPlan", () => {
|
||||
const currentSubscriptionPlan: SubscriptionPlan = new SubscriptionPlan(
|
||||
"monthly_plan_id",
|
||||
"yearly_plan_id",
|
||||
PlanSelect.Free,
|
||||
PlanType.Free,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
7,
|
||||
);
|
||||
const result: boolean = SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
|
||||
PlanSelect.Growth,
|
||||
PlanSelect.Free,
|
||||
PlanType.Growth,
|
||||
PlanType.Free,
|
||||
env,
|
||||
);
|
||||
expect(featureSubscriptionPlan.getPlanOrder()).toBeGreaterThan(
|
||||
@@ -204,7 +201,7 @@ describe("SubscriptionPlan", () => {
|
||||
const featureSubscriptionPlan: SubscriptionPlan = new SubscriptionPlan(
|
||||
"growth_monthly_plan_id",
|
||||
"growth_yearly_plan_id",
|
||||
PlanSelect.Growth,
|
||||
PlanType.Growth,
|
||||
9,
|
||||
99,
|
||||
2,
|
||||
@@ -213,15 +210,15 @@ describe("SubscriptionPlan", () => {
|
||||
const currentSubscriptionPlan: SubscriptionPlan = new SubscriptionPlan(
|
||||
monthlyPlanId,
|
||||
"yearly_plan_id",
|
||||
PlanSelect.Free,
|
||||
PlanType.Free,
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
7,
|
||||
);
|
||||
const result: boolean = SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
|
||||
PlanSelect.Growth,
|
||||
PlanSelect.Free,
|
||||
PlanType.Growth,
|
||||
PlanType.Free,
|
||||
env,
|
||||
);
|
||||
expect(featureSubscriptionPlan.getPlanOrder()).toBeLessThan(
|
||||
@@ -234,14 +231,14 @@ describe("SubscriptionPlan", () => {
|
||||
it("should return the correct SubscriptionPlan when a valid planSelect is provided", () => {
|
||||
const plan: SubscriptionPlan =
|
||||
SubscriptionPlan.getSubscriptionPlanFromPlanSelect(
|
||||
PlanSelect.Growth,
|
||||
PlanType.Growth,
|
||||
env,
|
||||
);
|
||||
expect(plan).toEqual(plan);
|
||||
expect(plan.getName()).toEqual(PlanSelect.Growth);
|
||||
expect(plan.getName()).toEqual(PlanType.Growth);
|
||||
});
|
||||
it("should throw a BadDataException when an invalid planSelect is provided", () => {
|
||||
const planSelect: PlanSelect = PlanSelect.Scale;
|
||||
const planSelect: PlanType = PlanType.Scale;
|
||||
SubscriptionPlan.getSubscriptionPlans = jest.fn().mockReturnValue([]);
|
||||
expect(() => {
|
||||
SubscriptionPlan.getSubscriptionPlanFromPlanSelect(planSelect, env);
|
||||
|
||||
@@ -7,6 +7,27 @@ export default class ArrayUtil {
|
||||
});
|
||||
}
|
||||
|
||||
public static shuffle<T>(array: Array<T>): Array<T> {
|
||||
const shuffledArray: Array<T> = [...array];
|
||||
for (let i: number = shuffledArray.length - 1; i > 0; i--) {
|
||||
const j: number = Math.floor(Math.random() * (i + 1));
|
||||
|
||||
if (!shuffledArray[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!shuffledArray[j]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[shuffledArray[i] as any, shuffledArray[j] as any] = [
|
||||
shuffledArray[j],
|
||||
shuffledArray[i],
|
||||
];
|
||||
}
|
||||
return shuffledArray;
|
||||
}
|
||||
|
||||
public static removeDuplicatesFromObjectIDArray(
|
||||
array: Array<ObjectID>,
|
||||
): Array<ObjectID> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PlanSelect } from "../Billing/SubscriptionPlan";
|
||||
import { PlanType } from "../Billing/SubscriptionPlan";
|
||||
|
||||
export default interface ColumnBillingAccessControl {
|
||||
create: PlanSelect;
|
||||
read: PlanSelect;
|
||||
update: PlanSelect;
|
||||
create: PlanType;
|
||||
read: PlanType;
|
||||
update: PlanType;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PlanSelect } from "../Billing/SubscriptionPlan";
|
||||
import { PlanType } from "../Billing/SubscriptionPlan";
|
||||
import Dictionary from "../Dictionary";
|
||||
import ObjectID from "../ObjectID";
|
||||
import {
|
||||
@@ -18,7 +18,7 @@ export default interface DatabaseCommonInteractionProps {
|
||||
isRoot?: boolean | undefined;
|
||||
isMultiTenantRequest?: boolean | undefined;
|
||||
ignoreHooks?: boolean | undefined;
|
||||
currentPlan?: PlanSelect | undefined;
|
||||
currentPlan?: PlanType | undefined;
|
||||
isSubscriptionUnpaid?: boolean | undefined;
|
||||
isMasterAdmin?: boolean | undefined;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { PlanSelect } from "../Billing/SubscriptionPlan";
|
||||
import { PlanType } from "../Billing/SubscriptionPlan";
|
||||
|
||||
export default interface TableBillingAccessControl {
|
||||
create: PlanSelect;
|
||||
read: PlanSelect;
|
||||
update: PlanSelect;
|
||||
delete: PlanSelect;
|
||||
create: PlanType;
|
||||
read: PlanType;
|
||||
update: PlanType;
|
||||
delete: PlanType;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import BadDataException from "../Exception/BadDataException";
|
||||
import { JSONObject } from "../JSON";
|
||||
|
||||
export enum PlanSelect {
|
||||
export enum PlanType {
|
||||
Free = "Free",
|
||||
Growth = "Growth",
|
||||
Enterprise = "Enterprise",
|
||||
@@ -157,10 +157,10 @@ export default class SubscriptionPlan {
|
||||
return Boolean(this.getSubscriptionPlanById(planId, env));
|
||||
}
|
||||
|
||||
public static getPlanSelect(
|
||||
public static getPlanType(
|
||||
planId: string,
|
||||
env?: JSONObject | undefined,
|
||||
): PlanSelect {
|
||||
): PlanType {
|
||||
const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById(
|
||||
planId,
|
||||
env,
|
||||
@@ -169,11 +169,11 @@ export default class SubscriptionPlan {
|
||||
throw new BadDataException("Plan ID is invalid");
|
||||
}
|
||||
|
||||
return plan.getName() as PlanSelect;
|
||||
return plan.getName() as PlanType;
|
||||
}
|
||||
|
||||
public static getSubscriptionPlanFromPlanSelect(
|
||||
planSelect: PlanSelect,
|
||||
planSelect: PlanType,
|
||||
env?: JSONObject | undefined,
|
||||
): SubscriptionPlan {
|
||||
const plan: SubscriptionPlan | undefined = this.getSubscriptionPlans(
|
||||
@@ -190,8 +190,8 @@ export default class SubscriptionPlan {
|
||||
}
|
||||
|
||||
public static isFeatureAccessibleOnCurrentPlan(
|
||||
featurePlan: PlanSelect,
|
||||
currentPlan: PlanSelect,
|
||||
featurePlan: PlanType,
|
||||
currentPlan: PlanType,
|
||||
env?: JSONObject | undefined,
|
||||
): boolean {
|
||||
const featureSubscriptionPlan: SubscriptionPlan | undefined =
|
||||
|
||||
11
Common/Types/CookieName.ts
Normal file
11
Common/Types/CookieName.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
enum CookieName {
|
||||
UserID = "user-id",
|
||||
Email = "user-email",
|
||||
Token = "user-token",
|
||||
Name = "user-name",
|
||||
Timezone = "user-timezone",
|
||||
IsMasterAdmin = "user-is-master-admin",
|
||||
ProfilePicID = "user-profile-pic-id",
|
||||
}
|
||||
|
||||
export default CookieName;
|
||||
@@ -1,6 +1,6 @@
|
||||
enum CopilotEventStatus {
|
||||
enum CopilotActionStatus {
|
||||
PR_CREATED = "Pull Request Created", // PR created and waiting for review
|
||||
NO_ACTION_REQUIRED = "No Action Required", // No PR needed. All is good.
|
||||
}
|
||||
|
||||
export default CopilotEventStatus;
|
||||
export default CopilotActionStatus;
|
||||
10
Common/Types/Copilot/CopilotActionType.ts
Normal file
10
Common/Types/Copilot/CopilotActionType.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
enum CopilotActionType {
|
||||
IMPROVE_COMMENTS = "Improve Comments",
|
||||
IMRPOVE_README = "Improve Readme",
|
||||
FIX_GRAMMAR_AND_SPELLING = "Fix Grammar and Spelling",
|
||||
IMPROVE_VARIABLE_NAMES = "Improve Variable Names",
|
||||
REFACTOR_CODE = "Refactor Code",
|
||||
WRITE_UNIT_TESTS = "Write Unit Tests",
|
||||
}
|
||||
|
||||
export default CopilotActionType;
|
||||
@@ -1,6 +0,0 @@
|
||||
enum CopilotEventType {
|
||||
IMPROVE_COMMENTS = "IMPROVE_COMMENTS",
|
||||
FIX_GRAMMAR_AND_SPELLING = "FIX_GRAMMAR_AND_SPELLING",
|
||||
}
|
||||
|
||||
export default CopilotEventType;
|
||||
@@ -4,6 +4,7 @@ import BadDataException from "./Exception/BadDataException";
|
||||
import { JSONObject, ObjectType } from "./JSON";
|
||||
import PositiveNumber from "./PositiveNumber";
|
||||
import moment from "moment-timezone";
|
||||
import Timezone from "./Timezone";
|
||||
|
||||
export const Moment: typeof moment = moment;
|
||||
|
||||
@@ -868,10 +869,48 @@ export default class OneUptimeDate {
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
public static getDateAsFormattedArrayInMultipleTimezones(
|
||||
date: string | Date,
|
||||
onlyShowDate?: boolean,
|
||||
): Array<string> {
|
||||
public static getGmtOffsetByTimezone(timezone: Timezone): number {
|
||||
return moment.tz(timezone).utcOffset();
|
||||
}
|
||||
|
||||
public static getGmtOffsetFriendlyStringByTimezone(
|
||||
timezone: Timezone,
|
||||
): string {
|
||||
const offset: number = this.getGmtOffsetByTimezone(timezone);
|
||||
return this.getGmtOffsetFriendlyString(offset) + " " + timezone;
|
||||
}
|
||||
|
||||
public static getGmtOffsetFriendlyString(offset: number): string {
|
||||
const hours: number = Math.abs(offset) / 60;
|
||||
const minutes: number = Math.abs(offset) % 60;
|
||||
|
||||
let formattedString: string = "GMT";
|
||||
|
||||
if (offset < 0) {
|
||||
formattedString += "-";
|
||||
} else {
|
||||
formattedString += "+";
|
||||
}
|
||||
|
||||
// remove decimals from hours
|
||||
formattedString += Math.floor(hours);
|
||||
|
||||
if (minutes > 0) {
|
||||
formattedString += ":" + minutes;
|
||||
}
|
||||
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
public static getDateAsFormattedArrayInMultipleTimezones(data: {
|
||||
date: string | Date;
|
||||
onlyShowDate?: boolean | undefined;
|
||||
timezones?: Array<Timezone> | undefined;
|
||||
}): Array<string> {
|
||||
let date: string | Date = data.date;
|
||||
const onlyShowDate: boolean | undefined = data.onlyShowDate;
|
||||
let timezones: Array<Timezone> | undefined = data.timezones;
|
||||
|
||||
date = this.fromString(date);
|
||||
|
||||
let formatstring: string = "MMM DD YYYY, HH:mm";
|
||||
@@ -883,53 +922,61 @@ export default class OneUptimeDate {
|
||||
// convert this date into GMT, EST, PST, IST, ACT with moment
|
||||
const timezoneDates: Array<string> = [];
|
||||
|
||||
timezoneDates.push(
|
||||
moment(date).tz("UTC").format(formatstring) +
|
||||
" " +
|
||||
(onlyShowDate ? "" : "GMT"),
|
||||
);
|
||||
timezoneDates.push(
|
||||
moment(date).tz("America/New_York").format(formatstring) +
|
||||
" " +
|
||||
(onlyShowDate ? "" : "EST"),
|
||||
);
|
||||
timezoneDates.push(
|
||||
moment(date).tz("America/Los_Angeles").format(formatstring) +
|
||||
" " +
|
||||
(onlyShowDate ? "" : "PST"),
|
||||
);
|
||||
timezoneDates.push(
|
||||
moment(date).tz("Asia/Kolkata").format(formatstring) +
|
||||
" " +
|
||||
(onlyShowDate ? "" : "IST"),
|
||||
);
|
||||
timezoneDates.push(
|
||||
moment(date).tz("Australia/Sydney").format(formatstring) +
|
||||
" " +
|
||||
(onlyShowDate ? "" : "AEST"),
|
||||
);
|
||||
if (!timezones || timezones.length === 0) {
|
||||
timezones = [
|
||||
Timezone.UTC,
|
||||
Timezone.EST,
|
||||
Timezone.AmericaLos_Angeles,
|
||||
Timezone.AsiaKolkata,
|
||||
Timezone.AustraliaSydney,
|
||||
]; // default timezones.
|
||||
}
|
||||
|
||||
for (let i: number = 0; i < timezones.length; i++) {
|
||||
timezoneDates.push(
|
||||
moment(date)
|
||||
.tz(timezones[i] as string)
|
||||
.format(formatstring) +
|
||||
" " +
|
||||
(onlyShowDate
|
||||
? ""
|
||||
: this.getZoneAbbrByTimezone(timezones[i] as Timezone)),
|
||||
);
|
||||
}
|
||||
|
||||
return timezoneDates;
|
||||
}
|
||||
|
||||
public static getDateAsFormattedHTMLInMultipleTimezones(
|
||||
date: string | Date,
|
||||
onlyShowDate?: boolean,
|
||||
): string {
|
||||
return this.getDateAsFormattedArrayInMultipleTimezones(
|
||||
public static getDateAsFormattedHTMLInMultipleTimezones(data: {
|
||||
date: string | Date;
|
||||
onlyShowDate?: boolean;
|
||||
timezones?: Array<Timezone> | undefined; // if this is skipped, then it will show the default timezones in the order of UTC, EST, PST, IST, ACT
|
||||
}): string {
|
||||
const date: string | Date = data.date;
|
||||
const onlyShowDate: boolean | undefined = data.onlyShowDate;
|
||||
const timezones: Array<Timezone> | undefined = data.timezones;
|
||||
|
||||
return this.getDateAsFormattedArrayInMultipleTimezones({
|
||||
date,
|
||||
onlyShowDate,
|
||||
).join("<br/>");
|
||||
timezones,
|
||||
}).join("<br/>");
|
||||
}
|
||||
|
||||
public static getDateAsFormattedStringInMultipleTimezones(
|
||||
date: string | Date,
|
||||
onlyShowDate?: boolean,
|
||||
): string {
|
||||
return this.getDateAsFormattedArrayInMultipleTimezones(
|
||||
public static getDateAsFormattedStringInMultipleTimezones(data: {
|
||||
date: string | Date;
|
||||
onlyShowDate?: boolean | undefined;
|
||||
timezones?: Array<Timezone> | undefined; // if this is skipped, then it will show the default timezones in the order of UTC, EST, PST, IST, ACT
|
||||
}): string {
|
||||
const date: string | Date = data.date;
|
||||
const onlyShowDate: boolean | undefined = data.onlyShowDate;
|
||||
const timezones: Array<Timezone> | undefined = data.timezones;
|
||||
|
||||
return this.getDateAsFormattedArrayInMultipleTimezones({
|
||||
date,
|
||||
onlyShowDate,
|
||||
).join("\n");
|
||||
timezones,
|
||||
}).join("\n");
|
||||
}
|
||||
|
||||
public static getDateAsLocalFormattedString(
|
||||
@@ -961,7 +1008,21 @@ export default class OneUptimeDate {
|
||||
}
|
||||
|
||||
public static getCurrentTimezoneString(): string {
|
||||
return moment.tz(moment.tz.guess()).zoneAbbr();
|
||||
return this.getZoneAbbrByTimezone(this.getCurrentTimezone());
|
||||
}
|
||||
|
||||
public static getZoneAbbrByTimezone(timezone: Timezone): string {
|
||||
let zoneAbbr: string = moment.tz(timezone).zoneAbbr();
|
||||
|
||||
if (zoneAbbr.startsWith("+") || zoneAbbr.startsWith("-")) {
|
||||
zoneAbbr = "GMT" + zoneAbbr;
|
||||
}
|
||||
|
||||
return zoneAbbr;
|
||||
}
|
||||
|
||||
public static getCurrentTimezone(): Timezone {
|
||||
return moment.tz.guess() as Timezone;
|
||||
}
|
||||
|
||||
public static getDateString(date: Date): string {
|
||||
@@ -998,7 +1059,7 @@ export default class OneUptimeDate {
|
||||
return moment(date["value"]).toDate();
|
||||
}
|
||||
|
||||
throw new BadDataException("Invalid date");
|
||||
throw new BadDataException("Invalid date: " + date.toString());
|
||||
}
|
||||
|
||||
public static asDateForDatabaseQuery(date: string | Date): string {
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class Domain extends DatabaseProperty {
|
||||
"|",
|
||||
);
|
||||
const secondTLDs: Array<string> =
|
||||
"ac|academy|accountant|accountants|actor|adult|ag|agency|ai|airforce|am|amsterdam|apartments|app|archi|army|art|asia|associates|at|attorney|au|auction|auto|autos|baby|band|bar|barcelona|bargains|basketball|bayern|be|beauty|beer|berlin|best|bet|bid|bike|bingo|bio|biz|biz.pl|black|blog|blue|boats|boston|boutique|broker|build|builders|business|buzz|bz|ca|cab|cafe|camera|camp|capital|car|cards|care|careers|cars|casa|cash|casino|catering|cc|center|ceo|ch|charity|chat|cheap|church|city|cl|claims|cleaning|clinic|clothing|cloud|club|cn|co|co.in|co.jp|co.kr|co.nz|co.uk|co.za|coach|codes|coffee|college|com|com.ag|com.au|com.br|com.bz|com.cn|com.co|com.es|com.ky|com.mx|com.pe|com.ph|com.pl|com.ru|com.tw|community|company|computer|condos|construction|consulting|contact|contractors|cooking|cool|country|coupons|courses|credit|creditcard|cricket|cruises|cymru|cz|dance|date|dating|de|deals|degree|delivery|democrat|dental|dentist|design|dev|diamonds|digital|direct|directory|discount|dk|doctor|dog|domains|download|earth|education|email|energy|engineer|engineering|enterprises|equipment|es|estate|eu|events|exchange|expert|exposed|express|fail|faith|family|fan|fans|farm|fashion|film|finance|financial|firm.in|fish|fishing|fit|fitness|flights|florist|fm|football|forsale|foundation|fr|fun|fund|furniture|futbol|fyi|gallery|games|garden|gay|gen.in|gg|gifts|gives|giving|glass|global|gmbh|gold|golf|graphics|gratis|green|gripe|group|gs|guide|guru|hair|haus|health|healthcare|hockey|holdings|holiday|homes|horse|hospital|host|house|idv.tw|immo|immobilien|in|inc|ind.in|industries|info|info.pl|ink|institute|insure|international|investments|io|irish|ist|istanbul|it|jetzt|jewelry|jobs|jp|kaufen|kids|kim|kitchen|kiwi|kr|ky|la|land|lat|law|lawyer|lease|legal|lgbt|life|lighting|limited|limo|live|llc|llp|loan|loans|london|love|ltd|ltda|luxury|maison|makeup|management|market|marketing|mba|me|me.uk|media|melbourne|memorial|men|menu|miami|mobi|moda|moe|money|monster|mortgage|motorcycles|movie|ms|music|mx|nagoya|name|navy|ne.kr|net|net.ag|net.au|net.br|net.bz|net.cn|net.co|net.in|net.ky|net.nz|net.pe|net.ph|net.pl|net.ru|network|news|ninja|nl|no|nom.co|nom.es|nom.pe|nrw|nyc|okinawa|one|onl|online|org|org.ag|org.au|org.cn|org.es|org.in|org.ky|org.nz|org.pe|org.ph|org.pl|org.ru|org.uk|organic|page|paris|partners|parts|party|pe|pet|ph|photography|photos|pictures|pink|pizza|pl|place|plumbing|plus|poker|porn|press|pro|productions|promo|properties|protection|pub|pw|quebec|quest|racing|re.kr|realestate|recipes|red|rehab|reise|reisen|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|rip|rocks|rodeo|rugby|run|ryukyu|sale|salon|sarl|school|schule|science|se|security|services|sex|sg|sh|shiksha|shoes|shop|shopping|show|singles|site|ski|skin|soccer|social|software|solar|solutions|space|storage|store|stream|studio|study|style|supplies|supply|support|surf|surgery|sydney|systems|tax|taxi|team|tech|technology|tel|tennis|theater|theatre|tickets|tienda|tips|tires|today|tokyo|tools|tours|town|toys|trade|trading|training|travel|tube|tv|tw|uk|university|uno|us|vacations|vc|vegas|ventures|vet|viajes|video|villas|vin|vip|vision|vodka|vote|voto|voyage|wales|watch|web|webcam|website|wedding|wiki|win|wine|work|works|world|ws|wtf|xxx|xyz|yachts|yoga|yokohama|zone|移动|dev|com|edu|gov|net|mil|org|nom|sch|sbs|caa|res|off|gob|int|tur|ip6|uri|urn|asn|act|nsw|qld|tas|vic|pro|biz|adm|adv|agr|arq|art|ato|bio|bmd|cim|cng|cnt|ecn|eco|emp|eng|esp|etc|eti|far|fnd|fot|fst|g12|ggf|imb|ind|inf|jor|jus|leg|lel|mat|med|mus|not|ntr|odo|ppg|psc|psi|qsl|rec|slg|srv|teo|tmp|trd|vet|zlg|web|ltd|sld|pol|fin|k12|lib|pri|aip|fie|eun|sci|prd|cci|pvt|mod|idv|rel|sex|gen|nic|abr|bas|cal|cam|emr|fvg|laz|lig|lom|mar|mol|pmn|pug|sar|sic|taa|tos|umb|vao|vda|ven|mie|北海道|和歌山|神奈川|鹿児島|ass|rep|tra|per|ngo|soc|grp|plc|its|air|and|bus|can|ddr|jfk|mad|nrw|nyc|ski|spy|tcm|ulm|usa|war|fhs|vgs|dep|eid|fet|fla|flå|gol|hof|hol|sel|vik|cri|iwi|ing|abo|fam|gok|gon|gop|gos|aid|atm|gsm|sos|elk|waw|est|aca|bar|cpa|jur|law|sec|plo|www|bir|cbg|jar|khv|msk|nov|nsk|ptz|rnd|spb|stv|tom|tsk|udm|vrn|cmw|kms|nkz|snz|pub|fhv|red|ens|nat|rns|rnu|bbs|tel|bel|kep|nhs|dni|fed|isa|nsn|gub|e12|tec|орг|обр|упр|alt|nis|jpn|mex|ath|iki|nid|gda|inc|za|ovh".split(
|
||||
"ac|academy|accountant|accountants|actor|adult|aero|ag|agency|ai|airforce|am|amsterdam|apartments|app|archi|army|art|asia|associates|at|attorney|au|auction|auto|autos|baby|band|bar|barcelona|bargains|basketball|bayern|be|beauty|beer|berlin|best|bet|bid|bike|bingo|bio|biz|biz.pl|black|blog|blue|boats|boston|boutique|broker|build|builders|business|buzz|bz|ca|cab|cafe|camera|camp|capital|car|cards|care|careers|cars|casa|cash|casino|catering|cc|center|ceo|ch|charity|chat|cheap|church|city|cl|claims|cleaning|clinic|clothing|cloud|club|cn|co|co.in|co.jp|co.kr|co.nz|co.uk|co.za|coach|codes|coffee|college|com|com.ag|com.au|com.br|com.bz|com.cn|com.co|com.es|com.ky|com.mx|com.pe|com.ph|com.pl|com.ru|com.tw|community|company|computer|condos|construction|consulting|contact|contractors|cooking|cool|country|coupons|courses|credit|creditcard|cricket|cruises|cymru|cz|dance|date|dating|de|deals|degree|delivery|democrat|dental|dentist|design|dev|diamonds|digital|direct|directory|discount|dk|doctor|dog|domains|download|earth|education|email|energy|engineer|engineering|enterprises|equipment|es|estate|eu|events|exchange|expert|exposed|express|fail|faith|family|fan|fans|farm|fashion|film|finance|financial|firm.in|fish|fishing|fit|fitness|flights|florist|fm|football|forsale|foundation|fr|fun|fund|furniture|futbol|fyi|gallery|games|garden|gay|gen.in|gg|gifts|gives|giving|glass|global|gmbh|gold|golf|graphics|gratis|green|gripe|group|gs|guide|guru|hair|haus|health|healthcare|hockey|holdings|holiday|homes|horse|hospital|host|house|idv.tw|immo|immobilien|in|inc|ind.in|industries|info|info.pl|ink|institute|insure|international|investments|io|irish|ist|istanbul|it|jetzt|jewelry|jobs|jp|kaufen|kids|kim|kitchen|kiwi|kr|ky|la|land|lat|law|lawyer|lease|legal|lgbt|life|lighting|limited|limo|live|llc|llp|loan|loans|london|love|ltd|ltda|luxury|maison|makeup|management|market|marketing|mba|me|me.uk|media|melbourne|memorial|men|menu|miami|mobi|moda|moe|money|monster|mortgage|motorcycles|movie|ms|music|mx|nagoya|name|navy|ne.kr|net|net.ag|net.au|net.br|net.bz|net.cn|net.co|net.in|net.ky|net.nz|net.pe|net.ph|net.pl|net.ru|network|news|ninja|nl|no|nom.co|nom.es|nom.pe|nrw|nyc|okinawa|one|onl|online|org|org.ag|org.au|org.cn|org.es|org.in|org.ky|org.nz|org.pe|org.ph|org.pl|org.ru|org.uk|organic|page|paris|partners|parts|party|pe|pet|ph|photography|photos|pictures|pink|pizza|pl|place|plumbing|plus|poker|porn|press|pro|productions|promo|properties|protection|pub|pw|quebec|quest|racing|re.kr|realestate|recipes|red|rehab|reise|reisen|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|rip|rocks|rodeo|rugby|run|ryukyu|sale|salon|sarl|school|schule|science|se|security|services|sex|sg|sh|shiksha|shoes|shop|shopping|show|singles|site|ski|skin|soccer|social|software|solar|solutions|space|storage|store|stream|studio|study|style|supplies|supply|support|surf|surgery|sydney|systems|tax|taxi|team|tech|technology|tel|tennis|theater|theatre|tickets|tienda|tips|tires|today|tokyo|tools|tours|town|toys|trade|trading|training|travel|tube|tv|tw|uk|university|uno|us|vacations|vc|vegas|ventures|vet|viajes|video|villas|vin|vip|vision|vodka|vote|voto|voyage|wales|watch|web|webcam|website|wedding|wiki|win|wine|work|works|world|ws|wtf|xxx|xyz|yachts|yoga|yokohama|zone|移动|dev|com|edu|gov|net|mil|org|nom|sch|sbs|caa|res|off|gob|int|tur|ip6|uri|urn|asn|act|nsw|qld|tas|vic|pro|biz|adm|adv|agr|arq|art|ato|bio|bmd|cim|cng|cnt|ecn|eco|emp|eng|esp|etc|eti|far|fnd|fot|fst|g12|ggf|imb|ind|inf|jor|jus|leg|lel|mat|med|mus|not|ntr|odo|ppg|psc|psi|qsl|rec|slg|srv|teo|tmp|trd|vet|zlg|web|ltd|sld|pol|fin|k12|lib|pri|aip|fie|eun|sci|prd|cci|pvt|mod|idv|rel|sex|gen|nic|abr|bas|cal|cam|emr|fvg|laz|lig|lom|mar|mol|pmn|pug|sar|sic|taa|tos|umb|vao|vda|ven|mie|北海道|和歌山|神奈川|鹿児島|ass|rep|tra|per|ngo|soc|grp|plc|its|air|and|bus|can|ddr|jfk|mad|nrw|nyc|ski|spy|tcm|ulm|usa|war|fhs|vgs|dep|eid|fet|fla|flå|gol|hof|hol|sel|vik|cri|iwi|ing|abo|fam|gok|gon|gop|gos|aid|atm|gsm|sos|elk|waw|est|aca|bar|cpa|jur|law|sec|plo|www|bir|cbg|jar|khv|msk|nov|nsk|ptz|rnd|spb|stv|tom|tsk|udm|vrn|cmw|kms|nkz|snz|pub|fhv|red|ens|nat|rns|rnu|bbs|tel|bel|kep|nhs|dni|fed|isa|nsn|gub|e12|tec|орг|обр|упр|alt|nis|jpn|mex|ath|iki|nid|gda|inc|za|ovh".split(
|
||||
"|",
|
||||
);
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ enum EmailTemplateType {
|
||||
ForgotPassword = "ForgotPassword.hbs",
|
||||
ProbeOffline = "ProbeOffline.hbs",
|
||||
SignupWelcomeEmail = "SignupWelcomeEmail.hbs",
|
||||
ProbeConnectionStatusChange = "ProbeConnectionStatusChange.hbs",
|
||||
EmailVerified = "EmailVerified.hbs",
|
||||
PasswordChanged = "PasswordChanged.hbs",
|
||||
ProbeOwnerAdded = "ProbeOwnerAdded.hbs",
|
||||
InviteMember = "InviteMember.hbs",
|
||||
EmailChanged = "EmailChanged.hbs",
|
||||
SubscribedToStatusPage = "SubscribedToStatusPage.hbs",
|
||||
@@ -20,6 +22,7 @@ enum EmailTemplateType {
|
||||
SMTPTest = "SMTPTest.hbs",
|
||||
MonitorOwnerAdded = "MonitorOwnerAdded.hbs",
|
||||
MonitorOwnerResourceCreated = "MonitorOwnerResourceCreated.hbs",
|
||||
MonitorProbesStatus = "MonitorProbesStatus.hbs",
|
||||
MonitorOwnerStatusChanged = "MonitorOwnerStatusChanged.hbs",
|
||||
IncidentOwnerAdded = "IncidentOwnerAdded.hbs",
|
||||
IncidentOwnerStateChanged = "IncidentOwnerStateChanged.hbs",
|
||||
|
||||
@@ -2,7 +2,18 @@ import Exception from "./Exception";
|
||||
import ExceptionCode from "./ExceptionCode";
|
||||
|
||||
export default class APIException extends Exception {
|
||||
public constructor(message: string) {
|
||||
private _error: Error | null = null;
|
||||
public get error(): Error | null {
|
||||
return this._error || null;
|
||||
}
|
||||
public set error(v: Error | null) {
|
||||
this._error = v;
|
||||
}
|
||||
|
||||
public constructor(message: string, error?: Error) {
|
||||
super(ExceptionCode.APIException, message);
|
||||
if (error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ enum IconProp {
|
||||
Minus = "Minus",
|
||||
MinusSmall = "MinusSmall",
|
||||
Template = "Template",
|
||||
NoSignal = "NoSignal",
|
||||
EyeSlash = "EyeSlash",
|
||||
}
|
||||
|
||||
export default IconProp;
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface MemoryMetrics {
|
||||
|
||||
export interface CPUMetrics {
|
||||
percentUsed: number;
|
||||
cores: number;
|
||||
}
|
||||
|
||||
export interface BasicDiskMetrics {
|
||||
|
||||
@@ -103,13 +103,35 @@ export enum FilterCondition {
|
||||
}
|
||||
|
||||
export class CriteriaFilterUtil {
|
||||
public static getEvaluateOverTimeTypeByCriteriaFilter(
|
||||
criteriaFilter: CriteriaFilter | undefined,
|
||||
): Array<EvaluateOverTimeType> {
|
||||
if (!criteriaFilter) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (criteriaFilter.checkOn === CheckOn.IsOnline) {
|
||||
return [EvaluateOverTimeType.AllValues, EvaluateOverTimeType.AnyValue];
|
||||
}
|
||||
|
||||
return [
|
||||
EvaluateOverTimeType.Average,
|
||||
EvaluateOverTimeType.Sum,
|
||||
EvaluateOverTimeType.MaximumValue,
|
||||
EvaluateOverTimeType.MunimumValue,
|
||||
EvaluateOverTimeType.AllValues,
|
||||
EvaluateOverTimeType.AnyValue,
|
||||
];
|
||||
}
|
||||
|
||||
public static isEvaluateOverTimeFilter(checkOn: CheckOn): boolean {
|
||||
return (
|
||||
checkOn === CheckOn.ResponseStatusCode ||
|
||||
checkOn === CheckOn.ResponseTime ||
|
||||
checkOn === CheckOn.DiskUsagePercent ||
|
||||
checkOn === CheckOn.CPUUsagePercent ||
|
||||
checkOn === CheckOn.MemoryUsagePercent
|
||||
checkOn === CheckOn.MemoryUsagePercent ||
|
||||
checkOn === CheckOn.IsOnline
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,15 +142,20 @@ export class CriteriaFilterUtil {
|
||||
monitorType === MonitorType.API ||
|
||||
monitorType === MonitorType.Website
|
||||
) {
|
||||
return [CheckOn.ResponseStatusCode, CheckOn.ResponseTime];
|
||||
return [
|
||||
CheckOn.IsOnline,
|
||||
CheckOn.ResponseStatusCode,
|
||||
CheckOn.ResponseTime,
|
||||
];
|
||||
} else if (
|
||||
monitorType === MonitorType.Ping ||
|
||||
monitorType === MonitorType.IP ||
|
||||
monitorType === MonitorType.Port
|
||||
) {
|
||||
return [CheckOn.ResponseTime];
|
||||
return [CheckOn.IsOnline, CheckOn.ResponseTime];
|
||||
} else if (monitorType === MonitorType.Server) {
|
||||
return [
|
||||
CheckOn.IsOnline,
|
||||
CheckOn.DiskUsagePercent,
|
||||
CheckOn.CPUUsagePercent,
|
||||
CheckOn.MemoryUsagePercent,
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface CriteriaIncident {
|
||||
description: string;
|
||||
incidentSeverityId?: ObjectID | undefined;
|
||||
autoResolveIncident?: boolean | undefined;
|
||||
remediationNotes?: string | undefined;
|
||||
id: string;
|
||||
onCallPolicyIds?: Array<ObjectID> | undefined;
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ export class MonitorTypeHelper {
|
||||
return monitorTypeProps[0].title;
|
||||
}
|
||||
|
||||
public static isProbableMonitors(monitorType: MonitorType): boolean {
|
||||
public static isProbableMonitor(monitorType: MonitorType): boolean {
|
||||
const isProbeableMonitor: boolean =
|
||||
monitorType === MonitorType.API ||
|
||||
monitorType === MonitorType.Website ||
|
||||
@@ -172,7 +172,7 @@ export class MonitorTypeHelper {
|
||||
}
|
||||
|
||||
public static doesMonitorTypeHaveInterval(monitorType: MonitorType): boolean {
|
||||
return this.isProbableMonitors(monitorType);
|
||||
return this.isProbableMonitor(monitorType);
|
||||
}
|
||||
|
||||
public static doesMonitorTypeHaveCriteria(monitorType: MonitorType): boolean {
|
||||
|
||||
@@ -9,8 +9,10 @@ export interface ServerProcess {
|
||||
|
||||
export default interface ServerMonitorResponse {
|
||||
monitorId: ObjectID;
|
||||
hostname: string; // Hostname of the server
|
||||
basicInfrastructureMetrics?: BasicInfrastructureMetrics | undefined;
|
||||
requestReceivedAt: Date;
|
||||
onlyCheckRequestReceivedAt: boolean;
|
||||
processes?: ServerProcess[] | undefined;
|
||||
failureCause?: string | undefined;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ enum NotificationSettingEventType {
|
||||
SEND_MONITOR_OWNER_ADDED_NOTIFICATION = "Send notification when I am added as a owner to the monitor",
|
||||
SEND_MONITOR_CREATED_OWNER_NOTIFICATION = "Send monitor created notification when I am the owner of the monitor",
|
||||
SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION = "Send monitor status changed notification when I am the owner of the monitor",
|
||||
SEND_MONITOR_NOTIFICATION_WHEN_PORBE_STATUS_CHANGES = "Send monitor notification when probe status changes",
|
||||
SEND_MONITOR_NOTIFICATION_WHEN_NO_PROBES_ARE_MONITORING_THE_MONITOR = "Send monitor notification when no probes are monitoring the monitor",
|
||||
|
||||
// Scheduled Maintenance
|
||||
SEND_SCHEDULED_MAINTENANCE_CREATED_OWNER_NOTIFICATION = "Send event created notification when I am the owner of the event",
|
||||
@@ -20,6 +22,10 @@ enum NotificationSettingEventType {
|
||||
SEND_STATUS_PAGE_ANNOUNCEMENT_CREATED_OWNER_NOTIFICATION = "Send status page announcement created notification when I am the owner of the status page",
|
||||
SEND_STATUS_PAGE_CREATED_OWNER_NOTIFICATION = "Send status page created notification when I am the owner of the status page",
|
||||
SEND_STATUS_PAGE_OWNER_ADDED_NOTIFICATION = "Send notification when I am added as a owner to the status page",
|
||||
|
||||
// Probe Status change Notification
|
||||
SEND_PROBE_STATUS_CHANGED_OWNER_NOTIFICATION = "Send probe status changed notification when I am the owner of the probe",
|
||||
SEND_PROBE_OWNER_ADDED_NOTIFICATION = "Send notification when I am added as a owner to the probe",
|
||||
}
|
||||
|
||||
export default NotificationSettingEventType;
|
||||
|
||||
@@ -465,7 +465,17 @@ enum Permission {
|
||||
EditCodeRepository = "EditCodeRepository",
|
||||
ReadCodeRepository = "ReadCodeRepository",
|
||||
|
||||
ReadCopilotEvent = "ReadCopilotEvent",
|
||||
ReadCopilotAction = "ReadCopilotAction",
|
||||
|
||||
CreateProbeOwnerTeam = "CreateProbeOwnerTeam",
|
||||
DeleteProbeOwnerTeam = "DeleteProbeOwnerTeam",
|
||||
EditProbeOwnerTeam = "EditProbeOwnerTeam",
|
||||
ReadProbeOwnerTeam = "ReadProbeOwnerTeam",
|
||||
|
||||
CreateProbeOwnerUser = "CreateProbeOwnerUser",
|
||||
DeleteProbeOwnerUser = "DeleteProbeOwnerUser",
|
||||
EditProbeOwnerUser = "EditProbeOwnerUser",
|
||||
ReadProbeOwnerUser = "ReadProbeOwnerUser",
|
||||
|
||||
CreateServiceRepository = "CreateServiceRepository",
|
||||
DeleteServiceRepository = "DeleteServiceRepository",
|
||||
@@ -1978,28 +1988,28 @@ export class PermissionHelper {
|
||||
title: "Create Probe",
|
||||
description: "This permission can create probe this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
isAccessControlPermission: true,
|
||||
},
|
||||
{
|
||||
permission: Permission.DeleteProjectProbe,
|
||||
title: "Delete Probe",
|
||||
description: "This permission can delete probe of this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
isAccessControlPermission: true,
|
||||
},
|
||||
{
|
||||
permission: Permission.EditProjectProbe,
|
||||
title: "Edit Probe",
|
||||
description: "This permission can edit probe of this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
isAccessControlPermission: true,
|
||||
},
|
||||
{
|
||||
permission: Permission.ReadProjectProbe,
|
||||
title: "Read Probe",
|
||||
description: "This permission can read probe of this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
isAccessControlPermission: true,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -2439,13 +2449,71 @@ export class PermissionHelper {
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.ReadCopilotEvent,
|
||||
permission: Permission.ReadCopilotAction,
|
||||
title: "Read Copilot Event",
|
||||
description: "This permission can read Copilot Event of this project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.CreateProbeOwnerTeam,
|
||||
title: "Create Probe Owner Team",
|
||||
description: "This permission can create owners for probes.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.DeleteProbeOwnerTeam,
|
||||
title: "Delete Probe Owner Team",
|
||||
description: "This permission can delete owners for probes",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.EditProbeOwnerTeam,
|
||||
title: "Edit Probe Owner Team",
|
||||
description: "This permission can edit owners for probes",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.ReadProbeOwnerTeam,
|
||||
title: "Read Probe Owner Team",
|
||||
description: "This permission can read owners for probes",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.CreateProbeOwnerUser,
|
||||
title: "Create Probe Owner User",
|
||||
description: "This permission can create owners for probes.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.DeleteProbeOwnerUser,
|
||||
title: "Delete Probe Owner User",
|
||||
description: "This permission can delete owners for probes",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.EditProbeOwnerUser,
|
||||
title: "Edit Probe Owner User",
|
||||
description: "This permission can edit owners for probes",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.ReadProbeOwnerUser,
|
||||
title: "Read Probe Owner User",
|
||||
description: "This permission can read owners for probes",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.CreateServiceCatalog,
|
||||
title: "Create Service Catalog",
|
||||
|
||||
@@ -14,6 +14,7 @@ export enum ServiceLanguage {
|
||||
TypeScript = "TypeScript",
|
||||
JavaScript = "JavaScript",
|
||||
Shell = "Shell",
|
||||
Markdown = "Markdown",
|
||||
Other = "Other",
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
enum TimezoneCode {
|
||||
UTC = "UTC",
|
||||
GMT = "GMT",
|
||||
EST = "EST",
|
||||
EDT = "EDT",
|
||||
CST = "CST",
|
||||
CDT = "CDT",
|
||||
MST = "MST",
|
||||
MDT = "MDT",
|
||||
PST = "PST",
|
||||
PDT = "PDT",
|
||||
AKST = "AKST",
|
||||
AKDT = "AKDT",
|
||||
HST = "HST",
|
||||
HDT = "HDT",
|
||||
AEST = "AEST",
|
||||
AEDT = "AEDT",
|
||||
ACST = "ACST",
|
||||
ACDT = "ACDT",
|
||||
AWST = "AWST",
|
||||
AWDT = "AWDT",
|
||||
NZST = "NZST",
|
||||
NZDT = "NZDT",
|
||||
NFT = "NFT",
|
||||
IDLW = "IDLW",
|
||||
NT = "NT",
|
||||
CAT = "CAT",
|
||||
EAT = "EAT",
|
||||
IST = "IST",
|
||||
MSK = "MSK",
|
||||
EET = "EET",
|
||||
CET = "CET",
|
||||
WAT = "WAT",
|
||||
GMT1 = "GMT+1",
|
||||
GMT2 = "GMT+2",
|
||||
GMT3 = "GMT+3",
|
||||
GMT4 = "GMT+4",
|
||||
GMT5 = "GMT+5",
|
||||
GMT6 = "GMT+6",
|
||||
GMT7 = "GMT+7",
|
||||
GMT8 = "GMT+8",
|
||||
GMT9 = "GMT+9",
|
||||
GMT10 = "GMT+10",
|
||||
GMT11 = "GMT+11",
|
||||
GMT12 = "GMT+12",
|
||||
GMT13 = "GMT+13",
|
||||
GMT14 = "GMT+14",
|
||||
GMT_1 = "GMT-1",
|
||||
GMT_2 = "GMT-2",
|
||||
GMT_3 = "GMT-3",
|
||||
GMT_4 = "GMT-4",
|
||||
GMT_5 = "GMT-5",
|
||||
GMT_6 = "GMT-6",
|
||||
GMT_7 = "GMT-7",
|
||||
GMT_8 = "GMT-8",
|
||||
GMT_9 = "GMT-9",
|
||||
GMT_10 = "GMT-10",
|
||||
GMT_11 = "GMT-11",
|
||||
GMT_12 = "GMT-12",
|
||||
GMT_13 = "GMT-13",
|
||||
}
|
||||
|
||||
export default TimezoneCode;
|
||||
@@ -322,7 +322,10 @@ export default class API {
|
||||
// get url from error
|
||||
const url: string = error?.config?.url || "";
|
||||
|
||||
throw new APIException(`URL ${url ? url + " " : ""}is not available.`);
|
||||
throw new APIException(
|
||||
`Error occurred while making request to ${url}.`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
public static getFriendlyErrorMessage(error: AxiosError | Error): string {
|
||||
|
||||
9
Common/Utils/Memory.ts
Normal file
9
Common/Utils/Memory.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import NumberUtil from "./Number";
|
||||
|
||||
export default class MemoryUtil {
|
||||
public static convertToGb(memoryInBytes: number): number {
|
||||
const gb: number = memoryInBytes / 1024 / 1024 / 1024;
|
||||
//return two decimal places
|
||||
return NumberUtil.convertToTwoDecimalPlaces(gb);
|
||||
}
|
||||
}
|
||||
5
Common/Utils/Number.ts
Normal file
5
Common/Utils/Number.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default class NumberUtil {
|
||||
public static convertToTwoDecimalPlaces(value: number): number {
|
||||
return Math.round(value * 100) / 100;
|
||||
}
|
||||
}
|
||||
40
Common/Utils/ServiceLanguage.ts
Normal file
40
Common/Utils/ServiceLanguage.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import ServiceLanguage from "../Types/ServiceCatalog/ServiceLanguage";
|
||||
|
||||
export default class ServiceLanguageUtil {
|
||||
public static getLanguageByFileExtension(data: {
|
||||
fileExtension: string;
|
||||
}): ServiceLanguage {
|
||||
const { fileExtension } = data;
|
||||
|
||||
switch (fileExtension) {
|
||||
case "js":
|
||||
return ServiceLanguage.JavaScript;
|
||||
case "ts":
|
||||
return ServiceLanguage.TypeScript;
|
||||
case "py":
|
||||
return ServiceLanguage.Python;
|
||||
case "rb":
|
||||
return ServiceLanguage.Ruby;
|
||||
case "java":
|
||||
return ServiceLanguage.Java;
|
||||
case "php":
|
||||
return ServiceLanguage.PHP;
|
||||
case "cs":
|
||||
return ServiceLanguage.CSharp;
|
||||
case "cpp":
|
||||
return ServiceLanguage.CPlusPlus;
|
||||
case "rs":
|
||||
return ServiceLanguage.Rust;
|
||||
case "swift":
|
||||
return ServiceLanguage.Swift;
|
||||
case "kt":
|
||||
return ServiceLanguage.Kotlin;
|
||||
case "go":
|
||||
return ServiceLanguage.Go;
|
||||
case "sh":
|
||||
return ServiceLanguage.Shell;
|
||||
default:
|
||||
return ServiceLanguage.Other;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Common/package-lock.json
generated
8
Common/package-lock.json
generated
@@ -16,7 +16,7 @@
|
||||
"json5": "^2.2.3",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"posthog-js": "^1.133.0",
|
||||
"posthog-js": "^1.138.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"slugify": "^1.6.5",
|
||||
"typeorm": "^0.3.20",
|
||||
@@ -3846,9 +3846,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/posthog-js": {
|
||||
"version": "1.133.0",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.133.0.tgz",
|
||||
"integrity": "sha512-d+TfOqWTPRGoFuxaxRaGhh/XCg1tR5TyjdxCIW1Qp1XQE22zqite2/vg5l+mE6VdZfjMqeBStx0wjmxOj3uUDA==",
|
||||
"version": "1.138.0",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.138.0.tgz",
|
||||
"integrity": "sha512-Pmvt5KmYPT3Je0auBq3Q3YSvHkPHUiW8Iy1UwS8mN/bQS19u8ls1UoMe6yiGijvnvHYmORjkMu6RYbmlTiKFZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fflate": "^0.4.8",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"json5": "^2.2.3",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"posthog-js": "^1.133.0",
|
||||
"posthog-js": "^1.138.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"slugify": "^1.6.5",
|
||||
"typeorm": "^0.3.20",
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
NextFunction,
|
||||
OneUptimeRequest,
|
||||
} from "../Utils/Express";
|
||||
import Response from "../Utils/Response";
|
||||
import BaseAPI from "./BaseAPI";
|
||||
@@ -53,7 +54,10 @@ export default class UserAPI extends BaseAPI<
|
||||
);
|
||||
});
|
||||
|
||||
if (userPermissions.length === 0) {
|
||||
if (
|
||||
userPermissions.length === 0 &&
|
||||
!(req as OneUptimeRequest).userAuthorization?.isMasterAdmin
|
||||
) {
|
||||
throw new BadDataException(
|
||||
`You need ${Permission.ProjectOwner} or ${Permission.EditInvoices} permission to pay invoices.`,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import UserMiddleware from "../Middleware/UserAuthorization";
|
||||
import CodeRepositoryAuthorization from "../Middleware/CodeRepositoryAuthorization";
|
||||
import CodeRepositoryService, {
|
||||
Service as CodeRepositoryServiceType,
|
||||
} from "../Services/CodeRepositoryService";
|
||||
import CopilotEventService from "../Services/CopilotEventService";
|
||||
import ServiceRepositoryService from "../Services/ServiceRepositoryService";
|
||||
import {
|
||||
ExpressRequest,
|
||||
@@ -15,7 +14,6 @@ import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import CodeRepository from "Model/Models/CodeRepository";
|
||||
import CopilotEvent from "Model/Models/CopilotEvent";
|
||||
import ServiceRepository from "Model/Models/ServiceRepository";
|
||||
|
||||
export default class CodeRepositoryAPI extends BaseAPI<
|
||||
@@ -25,11 +23,25 @@ export default class CodeRepositoryAPI extends BaseAPI<
|
||||
public constructor() {
|
||||
super(CodeRepository, CodeRepositoryService);
|
||||
|
||||
this.router.get(
|
||||
`${new this.entityType()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/is-valid/:secretkey`,
|
||||
CodeRepositoryAuthorization.isAuthorizedRepository,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
`${new this.entityType()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/get-code-repository/:secretkey`,
|
||||
UserMiddleware.getUserMiddleware,
|
||||
CodeRepositoryAuthorization.isAuthorizedRepository,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const secretkey: string = req.params["secretkey"]!;
|
||||
@@ -98,84 +110,5 @@ export default class CodeRepositoryAPI extends BaseAPI<
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
`${new this.entityType()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/get-copilot-events-by-file/:secretkey`,
|
||||
UserMiddleware.getUserMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const secretkey: string = req.params["secretkey"]!;
|
||||
|
||||
if (!secretkey) {
|
||||
throw new BadDataException("Secret key is required");
|
||||
}
|
||||
|
||||
const filePath: string = req.body["filePath"]!;
|
||||
|
||||
if (!filePath) {
|
||||
throw new BadDataException("File path is required");
|
||||
}
|
||||
|
||||
const serviceCatalogId: string = req.body["serviceCatalogId"]!;
|
||||
|
||||
if (!serviceCatalogId) {
|
||||
throw new BadDataException("Service catalog id is required");
|
||||
}
|
||||
|
||||
const codeRepository: CodeRepository | null =
|
||||
await CodeRepositoryService.findOneBy({
|
||||
query: {
|
||||
secretToken: new ObjectID(secretkey),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!codeRepository) {
|
||||
throw new BadDataException(
|
||||
"Code repository not found. Secret key is invalid.",
|
||||
);
|
||||
}
|
||||
|
||||
const copilotEvents: Array<CopilotEvent> =
|
||||
await CopilotEventService.findBy({
|
||||
query: {
|
||||
codeRepositoryId: codeRepository.id!,
|
||||
filePath: filePath,
|
||||
serviceCatalogId: new ObjectID(serviceCatalogId),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
codeRepositoryId: true,
|
||||
serviceCatalogId: true,
|
||||
filePath: true,
|
||||
copilotEventStatus: true,
|
||||
copilotEventType: true,
|
||||
createdAt: true,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
copilotEvents: CopilotEvent.toJSONArray(
|
||||
copilotEvents,
|
||||
CopilotEvent,
|
||||
),
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { IsBillingEnabled } from "../EnvironmentConfig";
|
||||
import ProjectService from "../Services/ProjectService";
|
||||
import { ExpressRequest, OneUptimeRequest } from "../Utils/Express";
|
||||
import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps";
|
||||
import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan";
|
||||
import { PlanType } from "Common/Types/Billing/SubscriptionPlan";
|
||||
import UserType from "Common/Types/UserType";
|
||||
|
||||
export default class CommonAPI {
|
||||
@@ -47,7 +47,7 @@ export default class CommonAPI {
|
||||
|
||||
if (IsBillingEnabled && props.tenantId) {
|
||||
const plan: {
|
||||
plan: PlanSelect | null;
|
||||
plan: PlanType | null;
|
||||
isSubscriptionUnpaid: boolean;
|
||||
} = await ProjectService.getCurrentPlan(props.tenantId!);
|
||||
props.currentPlan = plan.plan || undefined;
|
||||
|
||||
160
CommonServer/API/CopilotActionAPI.ts
Normal file
160
CommonServer/API/CopilotActionAPI.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import CodeRepository from "Model/Models/CodeRepository";
|
||||
import CopilotActionService, {
|
||||
Service as CopilotActionServiceType,
|
||||
} from "../Services/CopilotActionService";
|
||||
import {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
NextFunction,
|
||||
} from "../Utils/Express";
|
||||
import Response from "../Utils/Response";
|
||||
import BaseAPI from "./BaseAPI";
|
||||
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import CopilotAction from "Model/Models/CopilotAction";
|
||||
import CodeRepositoryService from "../Services/CodeRepositoryService";
|
||||
import CodeRepositoryAuthorization from "../Middleware/CodeRepositoryAuthorization";
|
||||
|
||||
export default class CopilotActionAPI extends BaseAPI<
|
||||
CopilotAction,
|
||||
CopilotActionServiceType
|
||||
> {
|
||||
public constructor() {
|
||||
super(CopilotAction, CopilotActionService);
|
||||
|
||||
this.router.get(
|
||||
`${new this.entityType()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/copilot-actions-by-file/:secretkey`,
|
||||
CodeRepositoryAuthorization.isAuthorizedRepository,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const secretkey: string = req.params["secretkey"]!;
|
||||
|
||||
if (!secretkey) {
|
||||
throw new BadDataException("Secret key is required");
|
||||
}
|
||||
|
||||
const filePath: string = req.body["filePath"]!;
|
||||
|
||||
if (!filePath) {
|
||||
throw new BadDataException("File path is required");
|
||||
}
|
||||
|
||||
const serviceCatalogId: string = req.body["serviceCatalogId"]!;
|
||||
|
||||
if (!serviceCatalogId) {
|
||||
throw new BadDataException("Service catalog id is required");
|
||||
}
|
||||
|
||||
const codeRepository: CodeRepository | null =
|
||||
await CodeRepositoryService.findOneBy({
|
||||
query: {
|
||||
secretToken: new ObjectID(secretkey),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!codeRepository) {
|
||||
throw new BadDataException(
|
||||
"Code repository not found. Secret key is invalid.",
|
||||
);
|
||||
}
|
||||
|
||||
const copilotActions: Array<CopilotAction> =
|
||||
await CopilotActionService.findBy({
|
||||
query: {
|
||||
codeRepositoryId: codeRepository.id!,
|
||||
filePath: filePath,
|
||||
serviceCatalogId: new ObjectID(serviceCatalogId),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
codeRepositoryId: true,
|
||||
serviceCatalogId: true,
|
||||
filePath: true,
|
||||
copilotActionStatus: true,
|
||||
copilotActionType: true,
|
||||
createdAt: true,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
copilotActions: CopilotAction.toJSONArray(
|
||||
copilotActions,
|
||||
CopilotAction,
|
||||
),
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.router.post(
|
||||
`${new this.entityType()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/add-copilot-action/:secretkey`,
|
||||
CodeRepositoryAuthorization.isAuthorizedRepository,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const secretkey: string = req.params["secretkey"]!;
|
||||
|
||||
if (!secretkey) {
|
||||
throw new BadDataException("Secret key is required");
|
||||
}
|
||||
|
||||
const codeRepository: CodeRepository | null =
|
||||
await CodeRepositoryService.findOneBy({
|
||||
query: {
|
||||
secretToken: new ObjectID(secretkey),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!codeRepository) {
|
||||
throw new BadDataException(
|
||||
"Code repository not found. Secret key is invalid.",
|
||||
);
|
||||
}
|
||||
|
||||
const copilotAction: CopilotAction = CopilotAction.fromJSON(
|
||||
req.body["copilotAction"],
|
||||
CopilotAction,
|
||||
) as CopilotAction;
|
||||
|
||||
copilotAction.codeRepositoryId = codeRepository.id!;
|
||||
copilotAction.projectId = codeRepository.projectId!;
|
||||
|
||||
await CopilotActionService.create({
|
||||
data: copilotAction,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ export default class Ingestor extends BaseAPI<Probe, ProbeServiceType> {
|
||||
description: true,
|
||||
lastAlive: true,
|
||||
iconFileId: true,
|
||||
connectionStatus: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { IsBillingEnabled } from "../EnvironmentConfig";
|
||||
import UserMiddleware from "../Middleware/UserAuthorization";
|
||||
import ProjectService, {
|
||||
Service as ProjectServiceType,
|
||||
} from "../Services/ProjectService";
|
||||
import ResellerService from "../Services/ResellerService";
|
||||
import TeamMemberService from "../Services/TeamMemberService";
|
||||
import Select from "../Types/Database/Select";
|
||||
import {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
@@ -38,6 +40,17 @@ export default class ProjectAPI extends BaseAPI<Project, ProjectServiceType> {
|
||||
);
|
||||
}
|
||||
|
||||
const projectSelect: Select<Project> = {
|
||||
_id: true,
|
||||
name: true,
|
||||
trialEndsAt: true,
|
||||
paymentProviderPlanId: true,
|
||||
resellerId: true,
|
||||
isFeatureFlagMonitorGroupsEnabled: true,
|
||||
paymentProviderMeteredSubscriptionStatus: true,
|
||||
paymentProviderSubscriptionStatus: true,
|
||||
};
|
||||
|
||||
const teamMembers: Array<TeamMember> = await TeamMemberService.findBy(
|
||||
{
|
||||
query: {
|
||||
@@ -45,16 +58,7 @@ export default class ProjectAPI extends BaseAPI<Project, ProjectServiceType> {
|
||||
hasAcceptedInvitation: true,
|
||||
},
|
||||
select: {
|
||||
project: {
|
||||
_id: true,
|
||||
name: true,
|
||||
trialEndsAt: true,
|
||||
paymentProviderPlanId: true,
|
||||
resellerId: true,
|
||||
isFeatureFlagMonitorGroupsEnabled: true,
|
||||
paymentProviderMeteredSubscriptionStatus: true,
|
||||
paymentProviderSubscriptionStatus: true,
|
||||
},
|
||||
project: projectSelect,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
@@ -66,6 +70,47 @@ export default class ProjectAPI extends BaseAPI<Project, ProjectServiceType> {
|
||||
|
||||
const projects: Array<Project> = [];
|
||||
|
||||
// if billing enabled and is master admin then get all the projects with customer support enabled.
|
||||
|
||||
if (
|
||||
IsBillingEnabled &&
|
||||
(req as OneUptimeRequest).userAuthorization?.isMasterAdmin
|
||||
) {
|
||||
const customerSupportProjects: Array<Project> =
|
||||
await ProjectService.findBy({
|
||||
query: {
|
||||
letCustomerSupportAccessProject: true,
|
||||
},
|
||||
select: projectSelect,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const customerSupportProject of customerSupportProjects) {
|
||||
if (!customerSupportProject) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!customerSupportProject._id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
projects.findIndex((project: Project) => {
|
||||
return (
|
||||
project._id?.toString() ===
|
||||
customerSupportProject!._id?.toString()
|
||||
);
|
||||
}) === -1
|
||||
) {
|
||||
projects.push(customerSupportProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const teamMember of teamMembers) {
|
||||
if (!teamMember.project) {
|
||||
continue;
|
||||
|
||||
@@ -15,7 +15,7 @@ import Response from "../Utils/Response";
|
||||
import BaseAPI from "./BaseAPI";
|
||||
import StatusCode from "Common/Types/API/StatusCode";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan";
|
||||
import { PlanType } from "Common/Types/Billing/SubscriptionPlan";
|
||||
import Email from "Common/Types/Email";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
@@ -126,7 +126,7 @@ export default class ResellerPlanAPI extends BaseAPI<
|
||||
promoCode.resellerId = resellerPlan?.reseller.id as ObjectID;
|
||||
promoCode.resellerPlanId = resellerPlan?.id as ObjectID;
|
||||
promoCode.userEmail = userEmail;
|
||||
promoCode.planType = resellerPlan?.planType as PlanSelect;
|
||||
promoCode.planType = resellerPlan?.planType as PlanType;
|
||||
promoCode.resellerLicenseId = licenseKey || "";
|
||||
|
||||
await PromoCodeService.create({
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import UserService, {
|
||||
Service as UserServiceType,
|
||||
} from "../Services/UserService";
|
||||
import BaseAPI from "./BaseAPI";
|
||||
import User from "Model/Models/User";
|
||||
|
||||
export default class UserAPI extends BaseAPI<User, UserServiceType> {
|
||||
public constructor() {
|
||||
super(User, UserService);
|
||||
}
|
||||
}
|
||||
@@ -210,3 +210,18 @@ export const AllowedActiveMonitorCountInFreePlan: number = process.env[
|
||||
process.env["ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN"].toString(),
|
||||
)
|
||||
: 10;
|
||||
|
||||
export const AllowedStatusPageCountInFreePlan: number = process.env[
|
||||
"ALLOWED_STATUS_PAGE_COUNT_IN_FREE_PLAN"
|
||||
]
|
||||
? parseInt(process.env["ALLOWED_STATUS_PAGE_COUNT_IN_FREE_PLAN"].toString())
|
||||
: 1;
|
||||
|
||||
export const AllowedSubscribersCountInFreePlan: number = process.env[
|
||||
"ALLOWED_SUBSCRIBERS_COUNT_IN_FREE_PLAN"
|
||||
]
|
||||
? parseInt(process.env["ALLOWED_SUBSCRIBERS_COUNT_IN_FREE_PLAN"].toString())
|
||||
: 100;
|
||||
|
||||
export const NotificationWebhookOnCreateUser: string =
|
||||
process.env["NOTIFICATION_WEBHOOK_ON_CREATED_USER"] || "";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user