mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
1 Commits
postmortem
...
manual-wor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a18207b04f |
17
.github/instructions/instructions.md
vendored
17
.github/instructions/instructions.md
vendored
@@ -1,17 +0,0 @@
|
||||
---
|
||||
applyTo: '**'
|
||||
---
|
||||
|
||||
# Building and Compiling
|
||||
|
||||
If you would like to compile or build any project. Please cd into the directory and run the following command:
|
||||
|
||||
```
|
||||
npm run compile
|
||||
```
|
||||
|
||||
Ths will make sure there are no type / syntax errors.
|
||||
|
||||
# Typescript Types.
|
||||
|
||||
Please do not use "any" types. Please create proper types where required.
|
||||
273
.github/workflows/build.yml
vendored
273
.github/workflows/build.yml
vendored
@@ -19,19 +19,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Accounts/Dockerfile .
|
||||
run: sudo docker build -f ./Accounts/Dockerfile .
|
||||
|
||||
docker-build-isolated-vm:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -42,19 +34,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./IsolatedVM/Dockerfile .
|
||||
run: sudo docker build -f ./IsolatedVM/Dockerfile .
|
||||
|
||||
docker-build-home:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -65,19 +49,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Home/Dockerfile .
|
||||
run: sudo docker build -f ./Home/Dockerfile .
|
||||
|
||||
docker-build-worker:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -88,19 +64,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Worker/Dockerfile .
|
||||
run: sudo docker build -f ./Worker/Dockerfile .
|
||||
|
||||
docker-build-workflow:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -111,19 +79,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Workflow/Dockerfile .
|
||||
run: sudo docker build -f ./Workflow/Dockerfile .
|
||||
|
||||
docker-build-api-reference:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -134,19 +94,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./APIReference/Dockerfile .
|
||||
run: sudo docker build -f ./APIReference/Dockerfile .
|
||||
|
||||
docker-build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -157,19 +109,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Docs/Dockerfile .
|
||||
run: sudo docker build -f ./Docs/Dockerfile .
|
||||
|
||||
|
||||
docker-build-otel-collector:
|
||||
@@ -181,19 +125,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./OTelCollector/Dockerfile .
|
||||
run: sudo docker build -f ./OTelCollector/Dockerfile .
|
||||
|
||||
docker-build-app:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -204,20 +140,12 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./App/Dockerfile .
|
||||
run: sudo docker build -f ./App/Dockerfile .
|
||||
|
||||
|
||||
docker-build-copilot:
|
||||
@@ -229,19 +157,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Copilot/Dockerfile .
|
||||
run: sudo docker build -f ./Copilot/Dockerfile .
|
||||
|
||||
docker-build-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -252,20 +172,12 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./E2E/Dockerfile .
|
||||
run: sudo docker build -f ./E2E/Dockerfile .
|
||||
|
||||
docker-build-admin-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -276,19 +188,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for home
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./AdminDashboard/Dockerfile .
|
||||
run: sudo docker build -f ./AdminDashboard/Dockerfile .
|
||||
|
||||
docker-build-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -299,19 +203,27 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for home
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Dashboard/Dockerfile .
|
||||
run: sudo docker build -f ./Dashboard/Dockerfile .
|
||||
|
||||
docker-build-haraka:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
# build images
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./Haraka/Dockerfile .
|
||||
|
||||
|
||||
docker-build-probe:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -322,19 +234,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Probe/Dockerfile .
|
||||
run: sudo docker build -f ./Probe/Dockerfile .
|
||||
|
||||
docker-build-probe-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -345,19 +249,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./ProbeIngest/Dockerfile .
|
||||
run: sudo docker build -f ./ProbeIngest/Dockerfile .
|
||||
|
||||
docker-build-server-monitor-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -368,21 +264,13 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./ServerMonitorIngest/Dockerfile .
|
||||
run: sudo docker build -f ./ServerMonitorIngest/Dockerfile .
|
||||
|
||||
docker-build-telemetry:
|
||||
docker-build-open-telemetry-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
@@ -391,19 +279,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Telemetry/Dockerfile .
|
||||
run: sudo docker build -f ./OpenTelemetryIngest/Dockerfile .
|
||||
|
||||
docker-build-incoming-request-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -414,19 +294,26 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./IncomingRequestIngest/Dockerfile .
|
||||
run: sudo docker build -f ./IncomingRequestIngest/Dockerfile .
|
||||
|
||||
docker-build-fluent-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./FluentIngest/Dockerfile .
|
||||
|
||||
docker-build-status-page:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -437,19 +324,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for home
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./StatusPage/Dockerfile .
|
||||
run: sudo docker build -f ./StatusPage/Dockerfile .
|
||||
|
||||
docker-build-test-server:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -460,16 +339,8 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for mail service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./TestServer/Dockerfile .
|
||||
run: sudo docker build -f ./TestServer/Dockerfile .
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -31,6 +31,8 @@ jobs:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript', 'typescript', 'go' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
|
||||
184
.github/workflows/compile.yml
vendored
184
.github/workflows/compile.yml
vendored
@@ -20,12 +20,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Accounts
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Accounts && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Accounts && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-isolated-vm:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -37,12 +32,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile IsolatedVM
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd IsolatedVM && npm install && npm run compile && npm run dep-check
|
||||
- run: cd IsolatedVM && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-common:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -53,12 +43,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- name: Compile Common
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Common && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Common && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-app:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -70,12 +55,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile App
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd App && npm install && npm run compile && npm run dep-check
|
||||
- run: cd App && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-home:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -87,12 +67,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Home
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Home && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Home && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-worker:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -104,12 +79,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Worker
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Worker && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Worker && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-workflow:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -121,12 +91,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Workflow
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Workflow && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Workflow && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-api-reference:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -138,12 +103,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile API Reference
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd APIReference && npm install && npm run compile && npm run dep-check
|
||||
- run: cd APIReference && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-docs-reference:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -155,12 +115,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Docs Reference
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Docs && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Docs && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-copilot:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -172,12 +127,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Copilot
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Copilot && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Copilot && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-nginx:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -190,12 +140,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
|
||||
- name: Compile Nginx
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Nginx && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Nginx && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-infrastructure-agent:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -205,12 +150,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
# Setup Go
|
||||
- uses: actions/setup-go@v5
|
||||
- name: Compile Infrastructure Agent
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd InfrastructureAgent && go build .
|
||||
- run: cd InfrastructureAgent && go build .
|
||||
|
||||
|
||||
compile-admin-dashboard:
|
||||
@@ -224,12 +164,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
|
||||
- name: Compile Admin Dashboard
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd AdminDashboard && npm install && npm run compile && npm run dep-check
|
||||
- run: cd AdminDashboard && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -242,12 +177,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
|
||||
- name: Compile Dashboard
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Dashboard && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Dashboard && npm install && npm run compile && npm run dep-check
|
||||
|
||||
|
||||
compile-e2e:
|
||||
@@ -261,12 +191,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: sudo apt-get update
|
||||
- run: cd Common && npm install
|
||||
- name: Compile E2E
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd E2E && npm install && npm run compile && npm run dep-check
|
||||
- run: cd E2E && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-probe:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -278,12 +203,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Probe
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Probe && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Probe && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-probe-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -295,12 +215,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Probe Ingest
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd ProbeIngest && npm install && npm run compile && npm run dep-check
|
||||
- run: cd ProbeIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-server-monitor-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -312,14 +227,9 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Server Monitor Ingest
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd ServerMonitorIngest && npm install && npm run compile && npm run dep-check
|
||||
- run: cd ServerMonitorIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-telemetry:
|
||||
compile-open-telemetry-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
@@ -329,12 +239,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Telemetry
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Telemetry && npm install && npm run compile && npm run dep-check
|
||||
- run: cd OpenTelemetryIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
|
||||
compile-incoming-request-ingest:
|
||||
@@ -347,12 +252,20 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Incoming Request Ingest
|
||||
uses: nick-fields/retry@v3
|
||||
- run: cd IncomingRequestIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-fluent-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd IncomingRequestIngest && npm install && npm run compile && npm run dep-check
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd FluentIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
|
||||
compile-status-page:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -365,12 +278,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
|
||||
- name: Compile Status Page
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd StatusPage && npm install && npm run compile && npm run dep-check
|
||||
- run: cd StatusPage && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-test-server:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -382,26 +290,4 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Test Server
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd TestServer && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-mcp:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile MCP
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd MCP && npm update @oneuptime/common && npm install && npm run compile && npm run dep-check
|
||||
- run: cd TestServer && npm install && npm run compile && npm run dep-check
|
||||
49
.github/workflows/npm-audit-fix.yml
vendored
49
.github/workflows/npm-audit-fix.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: NPM Audit Fix
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
npm-audit-fix:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Run npm audit fix across packages
|
||||
run: npm run audit-fix
|
||||
|
||||
- name: Detect changes
|
||||
id: changes
|
||||
run: |
|
||||
if git status --porcelain | grep .; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: "chore: npm audit fix"
|
||||
title: "chore: npm audit fix"
|
||||
body: |
|
||||
Automated npm audit fix run.
|
||||
Workflow: ${{ github.workflow }}
|
||||
Run ID: ${{ github.run_id }}
|
||||
branch: chore/npm-audit-fix
|
||||
delete-branch: true
|
||||
74
.github/workflows/openapi-spec-generation.yml
vendored
74
.github/workflows/openapi-spec-generation.yml
vendored
@@ -1,74 +0,0 @@
|
||||
name: OpenAPI Spec Generation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'hotfix-*'
|
||||
- 'release'
|
||||
|
||||
jobs:
|
||||
generate-openapi-spec:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{ github.run_number }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Common dependencies
|
||||
run: cd Common && npm install
|
||||
|
||||
- name: Install root dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Install Script dependencies
|
||||
run: cd Scripts && npm install
|
||||
|
||||
- name: Generate OpenAPI specification
|
||||
run: npm run generate-openapi-spec
|
||||
|
||||
- name: Check if OpenAPI spec was generated
|
||||
run: |
|
||||
if [ -f "./openapi.json" ]; then
|
||||
echo "✅ OpenAPI spec file generated successfully"
|
||||
echo "📄 File size: $(du -h ./openapi.json | cut -f1)"
|
||||
echo "📊 Spec contains $(jq '.paths | length' ./openapi.json) API paths"
|
||||
echo "🏷️ API version: $(jq -r '.info.version' ./openapi.json)"
|
||||
echo "📝 API title: $(jq -r '.info.title' ./openapi.json)"
|
||||
else
|
||||
echo "❌ OpenAPI spec file was not generated"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Validate OpenAPI spec format
|
||||
run: |
|
||||
# Check if the file is valid JSON
|
||||
if jq empty ./openapi.json; then
|
||||
echo "✅ OpenAPI spec is valid JSON"
|
||||
else
|
||||
echo "❌ OpenAPI spec is not valid JSON"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if it has required OpenAPI fields
|
||||
if jq -e '.openapi and .info and .paths' ./openapi.json > /dev/null; then
|
||||
echo "✅ OpenAPI spec has required fields"
|
||||
else
|
||||
echo "❌ OpenAPI spec missing required fields (openapi, info, paths)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload OpenAPI spec as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: openapi-spec
|
||||
path: ./openapi.json
|
||||
retention-days: 30
|
||||
2110
.github/workflows/release.yml
vendored
2110
.github/workflows/release.yml
vendored
File diff suppressed because it is too large
Load Diff
101
.github/workflows/terraform-provider-generation.yml
vendored
101
.github/workflows/terraform-provider-generation.yml
vendored
@@ -1,101 +0,0 @@
|
||||
name: Terraform Provider Generation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- develop
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
generate-terraform-provider:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{ github.run_number }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
cache: 'npm'
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Install Common dependencies
|
||||
run: cd Common && npm install
|
||||
|
||||
- name: Install root dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Install Script dependencies
|
||||
run: cd Scripts && npm install
|
||||
|
||||
- name: Generate Terraform provider
|
||||
run: npm run generate-terraform-provider
|
||||
|
||||
- name: Verify provider generation
|
||||
run: |
|
||||
PROVIDER_DIR="./Terraform"
|
||||
|
||||
# Check if provider directory was created
|
||||
if [ ! -d "$PROVIDER_DIR" ]; then
|
||||
echo "❌ Terraform provider directory not created"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Provider directory created: $PROVIDER_DIR"
|
||||
|
||||
# Count generated files
|
||||
GO_FILES=$(find "$PROVIDER_DIR" -name "*.go" | wc -l)
|
||||
echo "📊 Generated Go files: $GO_FILES"
|
||||
|
||||
if [ "$GO_FILES" -eq 0 ]; then
|
||||
echo "❌ No Go files were generated"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for essential files
|
||||
if [ -f "$PROVIDER_DIR/go.mod" ]; then
|
||||
echo "✅ Go module file created"
|
||||
fi
|
||||
|
||||
if [ -f "$PROVIDER_DIR/README.md" ]; then
|
||||
echo "✅ Documentation created"
|
||||
fi
|
||||
|
||||
# Show directory structure for debugging
|
||||
echo "📁 Provider directory structure:"
|
||||
ls -la "$PROVIDER_DIR" || true
|
||||
|
||||
- name: Test Go build
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: |
|
||||
PROVIDER_DIR="./Terraform"
|
||||
if [ -d "$PROVIDER_DIR" ] && [ -f "$PROVIDER_DIR/go.mod" ]; then
|
||||
cd "$PROVIDER_DIR"
|
||||
echo "🔨 Testing Go build..."
|
||||
go mod tidy
|
||||
go build -v ./...
|
||||
echo "✅ Go build successful"
|
||||
else
|
||||
echo "⚠️ Cannot test build - missing go.mod or provider directory"
|
||||
fi
|
||||
|
||||
- name: Upload Terraform provider as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Terraform
|
||||
path: ./Terraform/
|
||||
retention-days: 30
|
||||
1803
.github/workflows/test-release.yaml
vendored
1803
.github/workflows/test-release.yaml
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
name: MCP Server Test
|
||||
name: Fluent Ingest Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -16,6 +16,5 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd MCP && npm install && npm run test
|
||||
node-version: latest
|
||||
- run: cd FluentIngest && npm install && npm run test
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Telemetry Test
|
||||
name: OpenTelemetryIngest Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -17,6 +17,5 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd Telemetry && npm install && npm run test
|
||||
- run: cd OpenTelemetryIngest && npm install && npm run test
|
||||
|
||||
22
.gitignore
vendored
22
.gitignore
vendored
@@ -86,6 +86,9 @@ Backups/*.tar
|
||||
|
||||
.env
|
||||
|
||||
Haraka/dkim/keys/private_base64.txt
|
||||
Haraka/dkim/keys/public_base64.txt
|
||||
|
||||
.eslintcache
|
||||
|
||||
HelmChart/Values/*.values.yaml
|
||||
@@ -109,22 +112,3 @@ App/greenlock/greenlock.d/config.json
|
||||
App/greenlock/greenlock.d/config.json.bak
|
||||
Examples/otel-dotnet/bin/Debug/net6.0/Grpc.Core.Api.dll.txt
|
||||
InfrastructureAgent/oneuptime-infrastructure-agent
|
||||
|
||||
# ESLint cache
|
||||
.eslintcache*
|
||||
|
||||
# Terraform generated files
|
||||
openapi.json
|
||||
|
||||
Terraform/**
|
||||
|
||||
TerraformTest/**
|
||||
|
||||
terraform-provider-example/**
|
||||
|
||||
# MCP Server
|
||||
MCP/build/
|
||||
MCP/.env
|
||||
MCP/node_modules
|
||||
Dashboard/public/sw.js
|
||||
.claude/settings.local.json
|
||||
|
||||
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@@ -205,8 +205,8 @@
|
||||
},
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/Telemetry",
|
||||
"name": "Telemetry: Debug with Docker",
|
||||
"localRoot": "${workspaceFolder}/OpenTelemetryIngest",
|
||||
"name": "OpenTelemetryIngest: Debug with Docker",
|
||||
"port": 9938,
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"request": "attach",
|
||||
@@ -217,6 +217,20 @@
|
||||
"restart": true,
|
||||
"autoAttachChildProcesses": true
|
||||
},
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/FluentIngest",
|
||||
"name": "Fluent Ingest: Debug with Docker",
|
||||
"port": 9937,
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"restart": true,
|
||||
"autoAttachChildProcesses": true
|
||||
},
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/IsolatedVM",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"query": {
|
||||
"age": {
|
||||
"_type": "EqualTo",
|
||||
value: 10
|
||||
}
|
||||
"name": "Hello",
|
||||
// other filters
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"query": {
|
||||
"age": {
|
||||
"_type": "GreaterThanOrNull",
|
||||
"value": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"query": {
|
||||
"labels": {
|
||||
"_type": "Includes",
|
||||
"value": [
|
||||
"aaa00000-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
"bbb00000-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"query": {
|
||||
"age": {
|
||||
"_type": "LessThanOrNull",
|
||||
"value": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
@@ -14,12 +14,9 @@ RUN npm config set fetch-retry-maxtimeout 600000
|
||||
|
||||
ARG GIT_SHA
|
||||
ARG APP_VERSION
|
||||
ARG IS_ENTERPRISE_EDITION=false
|
||||
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV IS_ENTERPRISE_EDITION=${IS_ENTERPRISE_EDITION}
|
||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
@@ -68,8 +65,6 @@ CMD [ "npm", "run", "dev" ]
|
||||
COPY ./APIReference /usr/src/app
|
||||
# Bundle app source
|
||||
RUN npm run compile
|
||||
# Set permission to write logs and cache in case container run as non root
|
||||
RUN chown -R 1000:1000 "/tmp/npm" && chmod -R 2777 "/tmp/npm"
|
||||
#Run the app
|
||||
CMD [ "npm", "start" ]
|
||||
{{ end }}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import AuthenticationServiceHandler from "./Service/Authentication";
|
||||
import DataTypeServiceHandler from "./Service/DataType";
|
||||
import ErrorServiceHandler from "./Service/Errors";
|
||||
import OpenAPIServiceHandler from "./Service/OpenAPI";
|
||||
import IntroductionServiceHandler from "./Service/Introduction";
|
||||
import ModelServiceHandler from "./Service/Model";
|
||||
import PageNotFoundServiceHandler from "./Service/PageNotFound";
|
||||
@@ -66,8 +65,6 @@ const APIReferenceFeatureSet: FeatureSet = {
|
||||
return ErrorServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "introduction") {
|
||||
return IntroductionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "openapi") {
|
||||
return OpenAPIServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "status") {
|
||||
return StatusServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "data-types") {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Retrieve resources documentation
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
@@ -17,7 +15,7 @@ export default class ServiceHandler {
|
||||
|
||||
// Extract page parameter from request
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Set default page title and description for the authentication page
|
||||
pageTitle = "Authentication";
|
||||
@@ -28,7 +26,6 @@ export default class ServiceHandler {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
@@ -13,9 +11,9 @@ export default class ServiceHandler {
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
pageData["selectCode"] = await LocalCache.getOrSetString(
|
||||
pageData.selectCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"select",
|
||||
async () => {
|
||||
@@ -23,7 +21,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["sortCode"] = await LocalCache.getOrSetString(
|
||||
pageData.sortCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"sort",
|
||||
async () => {
|
||||
@@ -31,7 +29,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["equalToCode"] = await LocalCache.getOrSetString(
|
||||
pageData.equalToCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to",
|
||||
async () => {
|
||||
@@ -39,7 +37,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["equalToOrNullCode"] = await LocalCache.getOrSetString(
|
||||
pageData.equalToOrNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to-or-null",
|
||||
async () => {
|
||||
@@ -49,7 +47,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["greaterThanCode"] = await LocalCache.getOrSetString(
|
||||
pageData.greaterThanCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than",
|
||||
async () => {
|
||||
@@ -59,7 +57,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["greaterThanOrEqualCode"] = await LocalCache.getOrSetString(
|
||||
pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than-or-equal",
|
||||
async () => {
|
||||
@@ -69,7 +67,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["lessThanCode"] = await LocalCache.getOrSetString(
|
||||
pageData.lessThanCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than",
|
||||
async () => {
|
||||
@@ -79,7 +77,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["lessThanOrEqualCode"] = await LocalCache.getOrSetString(
|
||||
pageData.lessThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
@@ -89,37 +87,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["includesCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"includes",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/Includes.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData["lessThanOrNullCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThanOrNull.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData["greaterThanOrNullCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThanOrNull.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData["isNullCode"] = await LocalCache.getOrSetString(
|
||||
pageData.isNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"is-null",
|
||||
async () => {
|
||||
@@ -127,7 +95,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["notNullCode"] = await LocalCache.getOrSetString(
|
||||
pageData.notNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-null",
|
||||
async () => {
|
||||
@@ -135,7 +103,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["notEqualToCode"] = await LocalCache.getOrSetString(
|
||||
pageData.notEqualToCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-equals",
|
||||
async () => {
|
||||
@@ -149,7 +117,6 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: "data-types",
|
||||
pageTitle: "Data Types",
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription:
|
||||
"Data Types that can be used to interact with OneUptime API",
|
||||
resources: Resources,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Fetch a list of resources used in the application
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
@@ -18,7 +16,7 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the 'page' parameter from the request
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Set the default page title and description
|
||||
pageTitle = "Errors";
|
||||
@@ -29,7 +27,6 @@ export default class ServiceHandler {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Get all resources and featured resources from ResourceUtil
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
@@ -21,10 +19,10 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the requested page from the URL parameters
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Set featured resources for the page
|
||||
pageData["featuredResources"] = FeaturedResources;
|
||||
pageData.featuredResources = FeaturedResources;
|
||||
|
||||
// Set page title and description
|
||||
pageTitle = "Introduction";
|
||||
@@ -35,7 +33,6 @@ export default class ServiceHandler {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
|
||||
@@ -3,10 +3,7 @@ import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import PageNotFoundServiceHandler from "./PageNotFound";
|
||||
import { AppApiRoute } from "Common/ServiceRoute";
|
||||
import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl";
|
||||
import {
|
||||
getTableColumns,
|
||||
TableColumnMetadata,
|
||||
} from "Common/Types/Database/TableColumn";
|
||||
import { getTableColumns } from "Common/Types/Database/TableColumn";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Permission, {
|
||||
@@ -16,7 +13,6 @@ import Permission, {
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
|
||||
// Get all resources and resource dictionary
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
@@ -36,7 +32,7 @@ export default class ServiceHandler {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
let page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Check if page is provided
|
||||
if (!page) {
|
||||
@@ -59,9 +55,7 @@ export default class ServiceHandler {
|
||||
page = "model";
|
||||
|
||||
// Get table columns for current resource
|
||||
const tableColumns: Dictionary<TableColumnMetadata> = getTableColumns(
|
||||
currentResource.model,
|
||||
);
|
||||
const tableColumns: any = getTableColumns(currentResource.model);
|
||||
|
||||
// Filter out columns with no access
|
||||
for (const key in tableColumns) {
|
||||
@@ -82,14 +76,7 @@ export default class ServiceHandler {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tableColumns[key] && tableColumns[key]!.hideColumnInDocumentation) {
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tableColumns[key]) {
|
||||
(tableColumns[key] as any).permissions = accessControl;
|
||||
}
|
||||
tableColumns[key].permissions = accessControl;
|
||||
}
|
||||
|
||||
// Remove unnecessary columns
|
||||
@@ -99,11 +86,11 @@ export default class ServiceHandler {
|
||||
delete tableColumns["version"];
|
||||
|
||||
// Set page data
|
||||
pageData["title"] = currentResource.model.singularName;
|
||||
pageData["description"] = currentResource.model.tableDescription;
|
||||
pageData["columns"] = tableColumns;
|
||||
pageData.title = currentResource.model.singularName;
|
||||
pageData.description = currentResource.model.tableDescription;
|
||||
pageData.columns = tableColumns;
|
||||
|
||||
pageData["tablePermissions"] = {
|
||||
pageData.tablePermissions = {
|
||||
read: currentResource.model.readRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
@@ -127,7 +114,7 @@ export default class ServiceHandler {
|
||||
};
|
||||
|
||||
// Cache the list request data
|
||||
pageData["listRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.listRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-request",
|
||||
async () => {
|
||||
@@ -137,7 +124,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the item request data
|
||||
pageData["itemRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.itemRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-request",
|
||||
async () => {
|
||||
@@ -147,7 +134,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the item response data
|
||||
pageData["itemResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.itemResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-response",
|
||||
async () => {
|
||||
@@ -159,7 +146,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the count request data
|
||||
pageData["countRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.countRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-request",
|
||||
async () => {
|
||||
@@ -171,7 +158,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the count response data
|
||||
pageData["countResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.countResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-response",
|
||||
async () => {
|
||||
@@ -182,7 +169,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["updateRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.updateRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-request",
|
||||
async () => {
|
||||
@@ -193,7 +180,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["updateResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.updateResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-response",
|
||||
async () => {
|
||||
@@ -204,7 +191,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["createRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.createRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-request",
|
||||
async () => {
|
||||
@@ -215,7 +202,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["createResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.createResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-response",
|
||||
async () => {
|
||||
@@ -226,7 +213,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["deleteRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.deleteRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-request",
|
||||
async () => {
|
||||
@@ -237,7 +224,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["deleteResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.deleteResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-response",
|
||||
async () => {
|
||||
@@ -249,7 +236,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Get list response from cache or set it if it's not available
|
||||
pageData["listResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.listResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-response",
|
||||
async () => {
|
||||
@@ -261,22 +248,20 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Generate a unique ID for the example object
|
||||
pageData["exampleObjectID"] = ObjectID.generate();
|
||||
pageData.exampleObjectID = ObjectID.generate();
|
||||
|
||||
// Construct the API path for the current resource
|
||||
pageData["apiPath"] =
|
||||
pageData.apiPath =
|
||||
AppApiRoute.toString() + currentResource.model.crudApiPath?.toString();
|
||||
|
||||
// Check if the current resource is a master admin API
|
||||
pageData["isMasterAdminApiDocs"] =
|
||||
currentResource.model.isMasterAdminApiDocs;
|
||||
pageData.isMasterAdminApiDocs = currentResource.model.isMasterAdminApiDocs;
|
||||
|
||||
// Render the index page with the required data
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import {
|
||||
Host,
|
||||
HttpProtocol,
|
||||
IsBillingEnabled,
|
||||
} from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Fetch a list of resources used in the application
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
// Handles the HTTP response for a given request
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
|
||||
// Get the 'page' parameter from the request
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {
|
||||
hostUrl: new URL(HttpProtocol, Host).toString(),
|
||||
};
|
||||
|
||||
// Set the default page title and description
|
||||
pageTitle = "OneUptime OpenAPI Specification";
|
||||
pageDescription =
|
||||
"Learn more about the OpenAPI specification for OneUptime";
|
||||
|
||||
// Render the response using the given view and data
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
@@ -18,7 +17,6 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: "404", // The page type (404 in this case)
|
||||
pageTitle: "Page Not Found", // The page title
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: "Page you're looking for is not found.", // The page description
|
||||
resources: Resources, // The array of model documentation resources
|
||||
pageData: {}, // An empty object to hold any additional page data
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources(); // Get all resources from ResourceUtil
|
||||
|
||||
@@ -16,14 +14,14 @@ export default class ServiceHandler {
|
||||
let pageTitle: string = ""; // Initialize page title
|
||||
let pageDescription: string = ""; // Initialize page description
|
||||
const page: string | undefined = req.params["page"]; // Get the page parameter from the request
|
||||
const pageData: Dictionary<unknown> = {}; // Initialize page data object
|
||||
const pageData: any = {}; // Initialize page data object
|
||||
|
||||
// Set page title and description
|
||||
pageTitle = "Pagination";
|
||||
pageDescription = "Learn how to paginate requests with OneUptime API";
|
||||
|
||||
// Get response and request code from LocalCache or LocalFile
|
||||
pageData["responseCode"] = await LocalCache.getOrSetString(
|
||||
pageData.responseCode = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"response",
|
||||
async () => {
|
||||
@@ -34,7 +32,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["requestCode"] = await LocalCache.getOrSetString(
|
||||
pageData.requestCode = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"request",
|
||||
async () => {
|
||||
@@ -49,8 +47,7 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page, // Pass the page parameter
|
||||
resources: Resources, // Pass all resources
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled, // Pass the page title
|
||||
pageTitle: pageTitle, // Pass the page title
|
||||
pageDescription: pageDescription, // Pass the page description
|
||||
pageData: pageData, // Pass the page data
|
||||
});
|
||||
|
||||
@@ -2,8 +2,6 @@ import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { PermissionHelper, PermissionProps } from "Common/Types/Permission";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
@@ -18,14 +16,14 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the requested page
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Set page title and description
|
||||
pageTitle = "Permissions";
|
||||
pageDescription = "Learn how permissions work with OneUptime";
|
||||
|
||||
// Filter permissions to only include those assignable to tenants
|
||||
pageData["permissions"] = PermissionHelper.getAllPermissionProps().filter(
|
||||
pageData.permissions = PermissionHelper.getAllPermissionProps().filter(
|
||||
(i: PermissionProps) => {
|
||||
return i.isAssignableToTenant;
|
||||
},
|
||||
@@ -36,7 +34,6 @@ export default class ServiceHandler {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
@@ -18,7 +17,6 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: "status",
|
||||
pageTitle: "Status",
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: "200 - Success",
|
||||
resources: resources, // Pass resources to the template
|
||||
pageData: {}, // Pass empty data to the template
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
{
|
||||
"watch": ["./","../Common/Server", "../Common/Types", "../Common/Utils", "../Common/Models"],
|
||||
"ext": "ts,tsx",
|
||||
"ext": "ts,json,tsx,env,js,jsx,hbs",
|
||||
"ignore": [
|
||||
"./node_modules/**",
|
||||
"./public/**",
|
||||
"./bin/**",
|
||||
"./build/**",
|
||||
"greenlock.d/*"
|
||||
],
|
||||
"watchOptions": {"useFsEvents": false, "interval": 500},
|
||||
"env": {"TS_NODE_TRANSPILE_ONLY": "1", "TS_NODE_FILES": "false"},
|
||||
"exec": "node -r ts-node/register/transpile-only Index.ts"
|
||||
"exec": "node --inspect=0.0.0.0:9229 --require ts-node/register Index.ts"
|
||||
}
|
||||
436
APIReference/package-lock.json
generated
436
APIReference/package-lock.json
generated
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "@oneuptime/api-reference",
|
||||
"name": "@oneuptime/app",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@oneuptime/api-reference",
|
||||
"name": "@oneuptime/app",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
"ejs": "^3.1.9",
|
||||
"handlebars": "^4.7.8",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -25,54 +26,47 @@
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@asteasolutions/zod-to-openapi": "^7.3.2",
|
||||
"@bull-board/express": "^5.21.4",
|
||||
"@clickhouse/client": "^1.10.1",
|
||||
"@clickhouse/client": "^0.2.10",
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@hcaptcha/react-hcaptcha": "^1.14.0",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.206.0",
|
||||
"@opentelemetry/api-logs": "^0.52.1",
|
||||
"@opentelemetry/context-zone": "^1.25.1",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.207.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.207.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.207.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.207.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.52.1",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.52.1",
|
||||
"@opentelemetry/id-generator-aws-xray": "^1.2.2",
|
||||
"@opentelemetry/instrumentation": "^0.207.0",
|
||||
"@opentelemetry/instrumentation-fetch": "^0.207.0",
|
||||
"@opentelemetry/instrumentation-xml-http-request": "^0.207.0",
|
||||
"@opentelemetry/instrumentation": "^0.52.1",
|
||||
"@opentelemetry/instrumentation-fetch": "^0.52.1",
|
||||
"@opentelemetry/instrumentation-xml-http-request": "^0.52.1",
|
||||
"@opentelemetry/resources": "^1.25.1",
|
||||
"@opentelemetry/sdk-logs": "^0.207.0",
|
||||
"@opentelemetry/sdk-logs": "^0.52.1",
|
||||
"@opentelemetry/sdk-metrics": "^1.25.1",
|
||||
"@opentelemetry/sdk-node": "^0.207.0",
|
||||
"@opentelemetry/sdk-node": "^0.52.1",
|
||||
"@opentelemetry/sdk-trace-node": "^1.25.1",
|
||||
"@opentelemetry/sdk-trace-web": "^1.25.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.37.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.26.0",
|
||||
"@remixicon/react": "^4.2.0",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/web-push": "^3.6.4",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^1.12.0",
|
||||
"botbuilder": "^4.23.3",
|
||||
"bullmq": "^5.61.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"axios": "^1.7.2",
|
||||
"bullmq": "^5.3.3",
|
||||
"Common": "file:../Common",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"express": "^4.21.1",
|
||||
"express": "^4.19.2",
|
||||
"formik": "^2.4.6",
|
||||
"history": "^5.3.0",
|
||||
"ioredis": "^5.3.2",
|
||||
@@ -80,51 +74,48 @@
|
||||
"json5": "^2.2.3",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^12.0.2",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^7.0.7",
|
||||
"nodemailer": "^6.9.10",
|
||||
"otpauth": "^9.3.1",
|
||||
"pg": "^8.16.3",
|
||||
"playwright": "^1.56.0",
|
||||
"posthog-js": "^1.275.3",
|
||||
"pg": "^8.7.3",
|
||||
"posthog-js": "^1.139.6",
|
||||
"prop-types": "^15.8.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-big-calendar": "^1.19.4",
|
||||
"react-big-calendar": "^1.13.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.2.2",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-router-dom": "^6.24.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
"react-syntax-highlighter": "^16.0.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"recharts": "^2.12.7",
|
||||
"redis-semaphore": "^5.5.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"slackify-markdown": "^4.4.0",
|
||||
"slugify": "^1.6.5",
|
||||
"socket.io": "^4.7.4",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"stripe": "^10.17.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.26",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"universal-cookie": "^7.2.1",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"use-async-effect": "^2.2.6",
|
||||
"uuid": "^8.3.2",
|
||||
"web-push": "^3.6.7",
|
||||
"zod": "^3.25.76"
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
@@ -138,6 +129,7 @@
|
||||
"@types/jest": "^28.1.4",
|
||||
"@types/json2csv": "^5.0.3",
|
||||
"@types/jsonwebtoken": "^8.5.9",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/node-cron": "^3.0.7",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
@@ -151,7 +143,6 @@
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-mock-extended": "^3.0.5",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"sass": "^1.89.2",
|
||||
"ts-jest": "^28.0.5"
|
||||
}
|
||||
},
|
||||
@@ -247,20 +238,89 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"version": "7.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
|
||||
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
"@babel/highlight": "^7.23.4",
|
||||
"chalk": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
|
||||
@@ -436,21 +496,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
||||
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -465,28 +523,109 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
|
||||
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz",
|
||||
"integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.4"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.6",
|
||||
"@babel/types": "^7.23.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
|
||||
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
|
||||
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.5"
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
|
||||
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -657,15 +796,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/parser": "^7.27.2",
|
||||
"@babel/types": "^7.27.1"
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -693,14 +831,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
|
||||
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
|
||||
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
"@babel/helper-string-parser": "^7.23.4",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1617,23 +1755,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
"fill-range": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -1863,11 +1999,10 @@
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@@ -1936,10 +2071,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
@@ -2086,10 +2220,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -2106,11 +2239,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -2246,6 +2378,26 @@
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.6.1",
|
||||
"wordwrap": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
@@ -2405,7 +2557,6 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@@ -3103,15 +3254,13 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
@@ -3264,13 +3413,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"braces": "^3.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3297,6 +3445,14 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@@ -3309,6 +3465,11 @@
|
||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@@ -3551,11 +3712,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@@ -3801,7 +3961,6 @@
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3975,12 +4134,20 @@
|
||||
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
@@ -4076,6 +4243,18 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.17.4",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
|
||||
"integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"uglifyjs": "bin/uglifyjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undefsafe": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||
@@ -4161,6 +4340,11 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "",
|
||||
"main": "Index.ts",
|
||||
"scripts": {
|
||||
"start": "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
|
||||
"start": "node --require ts-node/register Index.ts",
|
||||
"compile": "tsc",
|
||||
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
|
||||
"dev": "npx nodemon",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<p>You can use OneUptime API Key on Request Header when you're making a request. You can use <code
|
||||
class="rounded p-0.5 px-1 text-sm text-gray-500 bg-gray-100 border-2 border-gray-200">Authorization</code>
|
||||
header with your API Key when you make a request.</p>
|
||||
<%- include('../partials/code', {title: "Example request with API Key" , requestUrl: "" , requestType: "" , code: "curl --header \"ApiKey: {secret-api-key}\" https://oneuptime.com/api/\<path\>" }) -%>
|
||||
<%- include('../partials/code', {title: "Example request with API Key" , requestUrl: "" , requestType: "" , code: "curl --header \"ApiKey: {secret-api-key}\" --header \"ProjectID: {project-id}\" https://oneuptime.com/api/\<path\>" }) -%>
|
||||
<p class="text-sm">Please don't commit your OneUptime API Key to GitHub, or on any other source control
|
||||
project. Please regenerate a new API Key, if your API Key is committed by mistake.</p>
|
||||
</article>
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of an Equal To Query</p>
|
||||
<p>Here is an example of Equal To Query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -153,7 +153,7 @@
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a Not Equal To Query</p>
|
||||
<p>Here is an example of Not Equal To Query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -191,7 +191,7 @@
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of an is null query</p>
|
||||
<p>Here is an example of is null query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -229,7 +229,7 @@
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of an is not null query</p>
|
||||
<p>Here is an example of is not null query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -266,7 +266,7 @@
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a greater than query</p>
|
||||
<p>Here is an example of greater than query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -304,7 +304,7 @@
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a greater than or equal query</p>
|
||||
<p>Here is an example of greater or equal than query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -342,7 +342,7 @@
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a less than query</p>
|
||||
<p>Here is an example of less than query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -380,7 +380,7 @@
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a less than or equal query</p>
|
||||
<p>Here is an example of less or equal than query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -395,44 +395,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="example-using-cursors" class="scroll-mt-24">
|
||||
Inlcudes
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>
|
||||
Includes will get objects that match any of the values in the array. It is used to
|
||||
filter objects that have a field that matches any of the values in the array. For example, if you
|
||||
want to get all objects that have a label with ID `aaa00000-aaaa-aaaa-aaaa-aaaaaaaaaaaa` or
|
||||
`bbb00000-bbbb-bbbb-bbbb-bbbbbbbbbbbb`, you can use the `includes` query type.
|
||||
</p>
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a less than or equal query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<%- include('../partials/code', {title: "Example Not EqualTo Request Body" , requestUrl: "" , requestType: "" ,
|
||||
code: pageData.includesCode }) -%>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
@@ -1,53 +0,0 @@
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<h1>OneUptime OpenAPI Specification</h1>
|
||||
<p class="lead">In this guide, we will look at how to import and work with the OneUptime OpenAPI specification. The OpenAPI spec provides a comprehensive reference for all available API endpoints.</p>
|
||||
<p>The OneUptime API follows the OpenAPI 3.0 specification, which provides a standardized way to describe REST APIs. You can import our OpenAPI spec into various tools like Swagger Editor, Postman, or other API documentation tools to explore and test our endpoints.</p>
|
||||
<h2 id="importing-spec" class="scroll-mt-24">
|
||||
Importing the OpenAPI Spec
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>To import the OneUptime OpenAPI specification into Swagger Editor, follow these simple steps:</p>
|
||||
<div class="my-6">
|
||||
<ol class="list-decimal pl-6">
|
||||
<li class="mb-2">Open <a href="https://editor.swagger.io" target="_blank" class="text-blue-600 hover:text-blue-800">editor.swagger.io</a> in your web browser</li>
|
||||
<li class="mb-2">Click on <strong>File</strong> in the menu bar</li>
|
||||
<li class="mb-2">Select <strong>Import URL</strong> from the dropdown menu</li>
|
||||
<li class="mb-2">Enter the URL: <code class="inline-code"><%= pageData.hostUrl %>api/openapi/spec</code></li>
|
||||
<li class="mb-2">Click <strong>Import</strong> to load the specification</li>
|
||||
</ol>
|
||||
</div>
|
||||
<h2 id="spec-url" class="scroll-mt-24">
|
||||
Specification URL
|
||||
</h2>
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Endpoint</dt>
|
||||
<dd><code class="inline-code"><%= pageData.hostUrl %>api/openapi/spec</code></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Complete OpenAPI 3.0 specification for the OneUptime API including all endpoints, request/response schemas, and authentication methods.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h2 id="benefits" class="scroll-mt-24">
|
||||
Benefits of Using the OpenAPI Spec
|
||||
</h2>
|
||||
<div class="my-6">
|
||||
<ul class="list-disc pl-6">
|
||||
<li class="mb-2"><strong>Interactive Documentation:</strong> Test API endpoints directly in Swagger Editor</li>
|
||||
<li class="mb-2"><strong>Code Generation:</strong> Generate client SDKs in multiple programming languages</li>
|
||||
<li class="mb-2"><strong>Validation:</strong> Ensure your requests match the expected schema</li>
|
||||
<li class="mb-2"><strong>Import to Tools:</strong> Use with Postman, Insomnia, or other API testing tools</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
@@ -13,7 +13,7 @@
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>In this example, we request the list of monitors. As a result, we get a list of three monitors and can
|
||||
<p>In this example, we request the list fo monitors. As a result, we get a list of three monitors and can
|
||||
tell by the <code class="inline-code">count</code> attribute that we have reached the end of the
|
||||
result set</p>
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
@@ -108,7 +108,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<%- include('../partials/code', {title: "Example Pagination Request", requestUrl: "/api/monitors/get-list?skip=0&limit=3", requestType: "POST", code: pageData.requestCode }) -%>
|
||||
<%- include('../partials/code', {title: "Example Pagination Request", requestUrl: "/api/monitors/get-list?skip=0&limit=3", requestType: "GET", code: pageData.requestCode }) -%>
|
||||
<%- include('../partials/code', {title: "Example Pagination Response" , requestUrl: "", requestType: "", code: pageData.responseCode }) -%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<html lang="en" class="js-focus-visible ctshmsrlsm idc0_345">
|
||||
<%- include('../partials/head', {
|
||||
enableGoogleTagManager: typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false,
|
||||
}) -%>
|
||||
<%- include('../partials/head') -%>
|
||||
|
||||
<body onload="applyStyles()" class="bg-white antialiased " data-new-gr-c-s-check-loaded="14.1095.0"
|
||||
data-gr-ext-installed="">
|
||||
|
||||
@@ -103,8 +103,6 @@
|
||||
</style>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %>
|
||||
|
||||
<!-- Google Tag Manager -->
|
||||
<script>(function (w, d, s, l, i) {
|
||||
w[l] = w[l] || []; w[l].push({
|
||||
@@ -115,7 +113,6 @@
|
||||
'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
|
||||
})(window, document, 'script', 'dataLayer', 'GTM-PKQD5WH');</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
<% } %>
|
||||
|
||||
|
||||
<style>
|
||||
@@ -135,6 +132,7 @@
|
||||
<link rel="apple-touch-icon-precomposed" href="/img/ou-wb.svg">
|
||||
<link rel="icon" href="/img/ou-wb.svg">
|
||||
<link rel="image_src" type="image/png" href="/img/hou-wb.svg">
|
||||
<link rel="canonical" href="/">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<meta property="og:title" content="OneUptime - One Complete Observability platform.">
|
||||
<meta property="og:url" content="https://oneuptime.com">
|
||||
|
||||
@@ -146,9 +146,6 @@
|
||||
with us on Slack</a></li>
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="/support">Support</a></li>
|
||||
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="/reference/openapi" type="_blank">OpenAPI Spec</a></li>
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="https://github.com/oneuptime/oneuptime">GitHub</a></li>
|
||||
</ul>
|
||||
@@ -235,10 +232,6 @@
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
href="/reference/errors"><span class="truncate">Errors</span></a></li>
|
||||
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
href="/reference/openapi"><span class="truncate">OpenAPI Spec</span></a></li>
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
@@ -14,12 +14,9 @@ RUN npm config set fetch-retry-maxtimeout 600000
|
||||
|
||||
ARG GIT_SHA
|
||||
ARG APP_VERSION
|
||||
ARG IS_ENTERPRISE_EDITION=false
|
||||
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV IS_ENTERPRISE_EDITION=${IS_ENTERPRISE_EDITION}
|
||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
@@ -72,11 +69,12 @@ RUN npm install
|
||||
# - 3003: accounts
|
||||
EXPOSE 3003
|
||||
|
||||
|
||||
RUN npm i -D webpack-cli
|
||||
|
||||
{{ if eq .Env.ENVIRONMENT "development" }}
|
||||
|
||||
|
||||
RUN mkdir /usr/src/app/dev-env
|
||||
RUN touch /usr/src/app/dev-env/.env
|
||||
RUN npm i -D webpack-dev-server
|
||||
|
||||
#Run the app
|
||||
CMD [ "npm", "run", "dev" ]
|
||||
@@ -86,8 +84,6 @@ COPY ./Accounts /usr/src/app
|
||||
# Bundle app source
|
||||
|
||||
RUN npm run build
|
||||
# Set permission to write logs and cache in case container run as non root
|
||||
RUN chown -R 1000:1000 "/tmp/npm" && chmod -R 2777 "/tmp/npm"
|
||||
#Run the app
|
||||
CMD [ "npm", "start" ]
|
||||
{{ end }}
|
||||
|
||||
@@ -35,7 +35,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies ( Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import App from "Common/Server/Utils/StartServer";
|
||||
import "ejs";
|
||||
|
||||
export const APP_NAME: string = "accounts";
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
const { createConfig, build, watch } = require('Common/UI/esbuild-config');
|
||||
|
||||
const config = createConfig({
|
||||
serviceName: 'Accounts',
|
||||
publicPath: '/accounts/dist/',
|
||||
});
|
||||
|
||||
if (process.argv.includes('--watch')) {
|
||||
watch(config, 'Accounts');
|
||||
} else {
|
||||
build(config, 'Accounts');
|
||||
}
|
||||
@@ -1,17 +1,4 @@
|
||||
{
|
||||
"watch": ["./","../Common/UI", "../Common/Types", "../Common/Utils", "../Common/Models"],
|
||||
"ext": "ts,tsx",
|
||||
"ignore": [
|
||||
"./node_modules/**",
|
||||
"./public/**",
|
||||
"./bin/**",
|
||||
"./public/**",
|
||||
"./public/dist/**",
|
||||
"./build/*",
|
||||
"./build/**",
|
||||
"./build/dist/*",
|
||||
"./build/dist/**",
|
||||
"../Common/Server/**"
|
||||
],
|
||||
"exec": "npm run dev-build && npm run start"
|
||||
"watch": ["webpack.config.js"],
|
||||
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3003 --mode=development"
|
||||
}
|
||||
2121
Accounts/package-lock.json
generated
2121
Accounts/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,8 @@
|
||||
"version": "0.1.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev-build": "NODE_ENV=development node esbuild.config.js",
|
||||
"dev": "npx nodemon",
|
||||
"build": "NODE_ENV=production node esbuild.config.js",
|
||||
"analyze": "analyze=true NODE_ENV=production node esbuild.config.js",
|
||||
"build": "webpack build --mode=production",
|
||||
"test": "",
|
||||
"compile": "tsc",
|
||||
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
|
||||
@@ -29,11 +27,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
|
||||
"ejs": "^3.1.10",
|
||||
"css-loader": "^6.11.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.20.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"sass-loader": "^13.3.3",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
"use-async-effect": "^2.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -42,7 +45,7 @@
|
||||
"@types/react-dom": "^18.0.4",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"nodemon": "^2.0.20",
|
||||
|
||||
"ts-node": "^10.9.1"
|
||||
"ts-node": "^10.9.1",
|
||||
"webpack": "^5.76.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<meta name="slack-app-id" content="ACVBMTPJQ">
|
||||
<meta name="description" content="OneUptime — the complete open-source observability platform.">
|
||||
|
||||
<% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %>
|
||||
<!-- Google Tag Manager -->
|
||||
<script>(function (w, d, s, l, i) {
|
||||
w[l] = w[l] || []; w[l].push({
|
||||
@@ -23,7 +22,6 @@
|
||||
'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
|
||||
})(window, document, 'script', 'dataLayer', 'GTM-PKQD5WH');</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
<% } %>
|
||||
|
||||
<link rel="manifest" href="/accounts/assets/img/favicons/ma">
|
||||
|
||||
@@ -97,16 +95,14 @@
|
||||
</head>
|
||||
|
||||
<body class="h-full bg-gray-50">
|
||||
<% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %>
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PKQD5WH" height="0" width="0"
|
||||
style="display:none;visibility:hidden"></iframe></noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
<% } %>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="/accounts/dist/Index.js"></script>
|
||||
<script src="/accounts/dist/bundle.js"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
@@ -1,4 +1,12 @@
|
||||
import React, { ReactElement, lazy, Suspense } from "react";
|
||||
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";
|
||||
import VerifyEmail from "./Pages/VerifyEmail";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, { ReactElement } from "react";
|
||||
import {
|
||||
Route,
|
||||
Routes,
|
||||
@@ -6,38 +14,6 @@ import {
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import PageLoader from "Common/UI/Components/Loader/PageLoader";
|
||||
|
||||
// Lazy load page components
|
||||
const ForbiddenPage: React.LazyExoticComponent<() => JSX.Element> = lazy(() => {
|
||||
return import("./Pages/Forbidden");
|
||||
});
|
||||
const ForgotPasswordPage: React.LazyExoticComponent<() => JSX.Element> = lazy(
|
||||
() => {
|
||||
return import("./Pages/ForgotPassword");
|
||||
},
|
||||
);
|
||||
const LoginPage: React.LazyExoticComponent<() => JSX.Element> = lazy(() => {
|
||||
return import("./Pages/Login");
|
||||
});
|
||||
const LoginWithSSO: React.LazyExoticComponent<() => JSX.Element> = lazy(() => {
|
||||
return import("./Pages/LoginWithSSO");
|
||||
});
|
||||
const NotFound: React.LazyExoticComponent<() => JSX.Element> = lazy(() => {
|
||||
return import("./Pages/NotFound");
|
||||
});
|
||||
const RegisterPage: React.LazyExoticComponent<() => JSX.Element> = lazy(() => {
|
||||
return import("./Pages/Register");
|
||||
});
|
||||
const ResetPasswordPage: React.LazyExoticComponent<() => JSX.Element> = lazy(
|
||||
() => {
|
||||
return import("./Pages/ResetPassword");
|
||||
},
|
||||
);
|
||||
const VerifyEmail: React.LazyExoticComponent<() => JSX.Element> = lazy(() => {
|
||||
return import("./Pages/VerifyEmail");
|
||||
});
|
||||
|
||||
function App(): ReactElement {
|
||||
Navigation.setNavigateHook(useNavigate());
|
||||
@@ -46,29 +22,24 @@ function App(): ReactElement {
|
||||
|
||||
return (
|
||||
<div className="m-auto h-screen">
|
||||
<Suspense fallback={<PageLoader isVisible={true} />}>
|
||||
<Routes>
|
||||
<Route path="/accounts" element={<LoginPage />} />
|
||||
<Route path="/accounts/login" element={<LoginPage />} />
|
||||
<Route path="/accounts/forbidden" element={<ForbiddenPage />} />
|
||||
<Route path="/accounts/sso" element={<LoginWithSSO />} />
|
||||
<Route
|
||||
path="/accounts/forgot-password"
|
||||
element={<ForgotPasswordPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/accounts/reset-password/:token"
|
||||
element={<ResetPasswordPage />}
|
||||
/>
|
||||
<Route path="/accounts/register" element={<RegisterPage />} />
|
||||
<Route
|
||||
path="/accounts/verify-email/:token"
|
||||
element={<VerifyEmail />}
|
||||
/>
|
||||
{/* 👇️ only match this when no other routes match */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
<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 />}
|
||||
/>
|
||||
<Route
|
||||
path="/accounts/reset-password/:token"
|
||||
element={<ResetPasswordPage />}
|
||||
/>
|
||||
<Route path="/accounts/register" element={<RegisterPage />} />
|
||||
<Route path="/accounts/verify-email/:token" element={<VerifyEmail />} />
|
||||
{/* 👇️ only match this when no other routes match */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import App from "./App";
|
||||
import Telemetry from "Common/UI/Utils/Telemetry/Telemetry";
|
||||
import Telemetry from "Common/UI/Utils/Telemetry";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const ForbiddenPage: () => JSX.Element = () => {
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Forbidden
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
You do not have permission to access this page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForbiddenPage;
|
||||
@@ -56,7 +56,6 @@ const ForgotPassword: () => JSX.Element = () => {
|
||||
title: "Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]}
|
||||
onSuccess={() => {
|
||||
|
||||
@@ -1,44 +1,28 @@
|
||||
import {
|
||||
LOGIN_API_URL,
|
||||
VERIFY_TOTP_AUTH_API_URL,
|
||||
GENERATE_WEBAUTHN_AUTH_OPTIONS_API_URL,
|
||||
VERIFY_WEBAUTHN_AUTH_API_URL,
|
||||
VERIFY_TWO_FACTOR_AUTH_API_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 ModelForm, {
|
||||
FormType,
|
||||
ModelField,
|
||||
} from "Common/UI/Components/Forms/ModelForm";
|
||||
import { CustomElementProps } from "Common/UI/Components/Forms/Types/Field";
|
||||
import FormValues from "Common/UI/Components/Forms/Types/FormValues";
|
||||
import ModelForm, { FormType } from "Common/UI/Components/Forms/ModelForm";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "Common/UI/Components/Link/Link";
|
||||
import Captcha from "Common/UI/Components/Captcha/Captcha";
|
||||
import {
|
||||
DASHBOARD_URL,
|
||||
CAPTCHA_ENABLED,
|
||||
CAPTCHA_SITE_KEY,
|
||||
} from "Common/UI/Config";
|
||||
import { DASHBOARD_URL } from "Common/UI/Config";
|
||||
import OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import EditionLabel from "Common/UI/Components/EditionLabel/EditionLabel";
|
||||
import UiAnalytics from "Common/UI/Utils/Analytics";
|
||||
import LoginUtil from "Common/UI/Utils/Login";
|
||||
import UserTotpAuth from "Common/Models/DatabaseModels/UserTotpAuth";
|
||||
import UserWebAuthn from "Common/Models/DatabaseModels/UserWebAuthn";
|
||||
import UserTwoFactorAuth from "Common/Models/DatabaseModels/UserTwoFactorAuth";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import UserUtil from "Common/UI/Utils/User";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import React from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
import StaticModelList from "Common/UI/Components/ModelList/StaticModelList";
|
||||
import BasicForm from "Common/UI/Components/Forms/BasicForm";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import Base64 from "Common/Utils/Base64";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import ComponentLoader from "Common/UI/Components/ComponentLoader/ComponentLoader";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = LOGIN_API_URL;
|
||||
@@ -52,115 +36,19 @@ const LoginPage: () => JSX.Element = () => {
|
||||
const [showTwoFactorAuth, setShowTwoFactorAuth] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const [totpAuthList, setTotpAuthList] = React.useState<UserTotpAuth[]>([]);
|
||||
const [twoFactorAuthList, setTwoFactorAuthList] = React.useState<
|
||||
UserTwoFactorAuth[]
|
||||
>([]);
|
||||
|
||||
const [webAuthnList, setWebAuthnList] = React.useState<UserWebAuthn[]>([]);
|
||||
|
||||
const [selectedTotpAuth, setSelectedTotpAuth] = React.useState<
|
||||
UserTotpAuth | undefined
|
||||
const [selectedTwoFactorAuth, setSelectedTwoFactorAuth] = React.useState<
|
||||
UserTwoFactorAuth | undefined
|
||||
>(undefined);
|
||||
|
||||
const [selectedWebAuthn, setSelectedWebAuthn] = React.useState<
|
||||
UserWebAuthn | undefined
|
||||
>(undefined);
|
||||
|
||||
type TwoFactorMethod = {
|
||||
type: "totp" | "webauthn";
|
||||
item: UserTotpAuth | UserWebAuthn;
|
||||
};
|
||||
|
||||
const twoFactorMethods: TwoFactorMethod[] = [
|
||||
...totpAuthList.map((item: UserTotpAuth) => {
|
||||
return { type: "totp" as const, item };
|
||||
}),
|
||||
...webAuthnList.map((item: UserWebAuthn) => {
|
||||
return { type: "webauthn" as const, item };
|
||||
}),
|
||||
];
|
||||
|
||||
const [isTwoFactorAuthLoading, setIsTwoFactorAuthLoading] =
|
||||
React.useState<boolean>(false);
|
||||
const [twofactorAuthError, setTwoFactorAuthError] =
|
||||
React.useState<string>("");
|
||||
|
||||
const isCaptchaEnabled: boolean =
|
||||
CAPTCHA_ENABLED && Boolean(CAPTCHA_SITE_KEY);
|
||||
|
||||
const [shouldResetCaptcha, setShouldResetCaptcha] =
|
||||
React.useState<boolean>(false);
|
||||
const [captchaResetSignal, setCaptchaResetSignal] = React.useState<number>(0);
|
||||
|
||||
const handleCaptchaReset: () => void = React.useCallback(() => {
|
||||
setCaptchaResetSignal((current: number) => {
|
||||
return current + 1;
|
||||
});
|
||||
}, []);
|
||||
let loginFields: Array<ModelField<User>> = [
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues["email"]),
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: "Password",
|
||||
required: true,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
sideLink: {
|
||||
text: "Forgot password?",
|
||||
url: new Route("/accounts/forgot-password"),
|
||||
openLinkInNewTab: false,
|
||||
},
|
||||
dataTestId: "password",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
];
|
||||
|
||||
if (isCaptchaEnabled) {
|
||||
loginFields = loginFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
captchaToken: true,
|
||||
},
|
||||
overrideFieldKey: "captchaToken",
|
||||
fieldType: FormFieldSchemaType.CustomComponent,
|
||||
title: "Human Verification",
|
||||
description:
|
||||
"Complete the captcha challenge so we know you're not a bot.",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
getCustomElement: (
|
||||
_values: FormValues<User>,
|
||||
customProps: CustomElementProps,
|
||||
) => {
|
||||
return (
|
||||
<Captcha
|
||||
siteKey={CAPTCHA_SITE_KEY}
|
||||
resetSignal={captchaResetSignal}
|
||||
error={customProps.error}
|
||||
onTokenChange={(token: string) => {
|
||||
customProps.onChange?.(token);
|
||||
}}
|
||||
onBlur={customProps.onBlur}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (Navigation.getQueryStringByName("email")) {
|
||||
setInitialValues({
|
||||
@@ -169,96 +57,6 @@ const LoginPage: () => JSX.Element = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (selectedWebAuthn) {
|
||||
setIsTwoFactorAuthLoading(true);
|
||||
try {
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: GENERATE_WEBAUTHN_AUTH_OPTIONS_API_URL,
|
||||
data: {
|
||||
data: {
|
||||
email: initialValues["email"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result instanceof HTTPErrorResponse) {
|
||||
throw result;
|
||||
}
|
||||
|
||||
const data: any = result.data as any;
|
||||
|
||||
// Convert base64url strings back to Uint8Array
|
||||
data.options.challenge = Base64.base64UrlToUint8Array(
|
||||
data.options.challenge,
|
||||
);
|
||||
if (data.options.allowCredentials) {
|
||||
data.options.allowCredentials.forEach((cred: any) => {
|
||||
cred.id = Base64.base64UrlToUint8Array(cred.id);
|
||||
});
|
||||
}
|
||||
|
||||
// Use WebAuthn API
|
||||
const credential: PublicKeyCredential =
|
||||
(await navigator.credentials.get({
|
||||
publicKey: data.options,
|
||||
})) as PublicKeyCredential;
|
||||
|
||||
const assertionResponse: AuthenticatorAssertionResponse =
|
||||
credential.response as AuthenticatorAssertionResponse;
|
||||
|
||||
// Verify
|
||||
const verifyResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: VERIFY_WEBAUTHN_AUTH_API_URL,
|
||||
data: {
|
||||
data: {
|
||||
...initialValues,
|
||||
challenge: data.challenge,
|
||||
credential: {
|
||||
id: credential.id,
|
||||
rawId: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(credential.rawId),
|
||||
),
|
||||
response: {
|
||||
authenticatorData: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.authenticatorData),
|
||||
),
|
||||
clientDataJSON: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.clientDataJSON),
|
||||
),
|
||||
signature: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.signature),
|
||||
),
|
||||
userHandle: assertionResponse.userHandle
|
||||
? Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.userHandle),
|
||||
)
|
||||
: null,
|
||||
},
|
||||
type: credential.type,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (verifyResult instanceof HTTPErrorResponse) {
|
||||
throw verifyResult;
|
||||
}
|
||||
|
||||
const user: User = User.fromJSON(
|
||||
verifyResult.data as JSONObject,
|
||||
User,
|
||||
) as User;
|
||||
const miscData: JSONObject = {};
|
||||
|
||||
login(user as User, miscData);
|
||||
} catch (error) {
|
||||
setTwoFactorAuthError(API.getFriendlyErrorMessage(error as Error));
|
||||
}
|
||||
setIsTwoFactorAuthLoading(false);
|
||||
}
|
||||
}, [selectedWebAuthn]);
|
||||
|
||||
type LoginFunction = (user: User, miscData: JSONObject) => void;
|
||||
|
||||
const login: LoginFunction = (user: User, miscData: JSONObject): void => {
|
||||
@@ -281,9 +79,6 @@ const LoginPage: () => JSX.Element = () => {
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<div className="mt-4 flex justify-center">
|
||||
<EditionLabel />
|
||||
</div>
|
||||
{!showTwoFactorAuth && (
|
||||
<>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
@@ -316,64 +111,59 @@ const LoginPage: () => JSX.Element = () => {
|
||||
modelType={User}
|
||||
id="login-form"
|
||||
name="Login"
|
||||
fields={loginFields}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues["email"]),
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: "Password",
|
||||
required: true,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
sideLink: {
|
||||
text: "Forgot password?",
|
||||
url: new Route("/accounts/forgot-password"),
|
||||
openLinkInNewTab: false,
|
||||
},
|
||||
dataTestId: "password",
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Login"}
|
||||
onBeforeCreate={(data: User, miscDataProps: JSONObject) => {
|
||||
if (isCaptchaEnabled) {
|
||||
const captchaToken: string | undefined = (
|
||||
miscDataProps["captchaToken"] as string | undefined
|
||||
)
|
||||
?.toString()
|
||||
.trim();
|
||||
|
||||
if (!captchaToken) {
|
||||
throw new Error(
|
||||
"Please complete the captcha challenge before signing in.",
|
||||
);
|
||||
}
|
||||
|
||||
miscDataProps["captchaToken"] = captchaToken;
|
||||
setShouldResetCaptcha(true);
|
||||
}
|
||||
|
||||
onBeforeCreate={(data: User) => {
|
||||
setInitialValues(User.toJSON(data, User));
|
||||
return Promise.resolve(data);
|
||||
}}
|
||||
onLoadingChange={(loading: boolean) => {
|
||||
if (!isCaptchaEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loading && shouldResetCaptcha) {
|
||||
setShouldResetCaptcha(false);
|
||||
handleCaptchaReset();
|
||||
}
|
||||
}}
|
||||
onSuccess={(
|
||||
value: User | JSONObject,
|
||||
miscData: JSONObject | undefined,
|
||||
) => {
|
||||
if (
|
||||
miscData &&
|
||||
((((miscData as JSONObject)["totpAuthList"] as JSONArray)
|
||||
?.length || 0) > 0 ||
|
||||
(((miscData as JSONObject)["webAuthnList"] as JSONArray)
|
||||
?.length || 0) > 0)
|
||||
(miscData as JSONObject)["twoFactorAuth"] === true
|
||||
) {
|
||||
const totpAuthList: Array<UserTotpAuth> =
|
||||
UserTotpAuth.fromJSONArray(
|
||||
(miscData as JSONObject)["totpAuthList"] as JSONArray,
|
||||
UserTotpAuth,
|
||||
const twoFactorAuthList: Array<UserTwoFactorAuth> =
|
||||
UserTwoFactorAuth.fromJSONArray(
|
||||
(miscData as JSONObject)[
|
||||
"twoFactorAuthList"
|
||||
] as JSONArray,
|
||||
UserTwoFactorAuth,
|
||||
);
|
||||
const webAuthnList: Array<UserWebAuthn> =
|
||||
UserWebAuthn.fromJSONArray(
|
||||
(miscData as JSONObject)["webAuthnList"] as JSONArray,
|
||||
UserWebAuthn,
|
||||
);
|
||||
setTotpAuthList(totpAuthList);
|
||||
setWebAuthnList(webAuthnList);
|
||||
setTwoFactorAuthList(twoFactorAuthList);
|
||||
setShowTwoFactorAuth(true);
|
||||
return;
|
||||
}
|
||||
@@ -395,53 +185,19 @@ const LoginPage: () => JSX.Element = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{showTwoFactorAuth && !selectedTotpAuth && !selectedWebAuthn && (
|
||||
<div className="space-y-4">
|
||||
{twoFactorMethods.map(
|
||||
(method: TwoFactorMethod, index: number) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="cursor-pointer p-4 border border-gray-300 rounded-lg hover:bg-gray-50"
|
||||
onClick={() => {
|
||||
if (method.type === "totp") {
|
||||
setSelectedTotpAuth(method.item as UserTotpAuth);
|
||||
} else {
|
||||
setSelectedWebAuthn(method.item as UserWebAuthn);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="font-medium">
|
||||
{(method.item as any).name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{method.type === "totp"
|
||||
? "Authenticator App"
|
||||
: "Security Key"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
{showTwoFactorAuth && !selectedTwoFactorAuth && (
|
||||
<StaticModelList<UserTwoFactorAuth>
|
||||
titleField="name"
|
||||
descriptionField=""
|
||||
selectedItems={[]}
|
||||
list={twoFactorAuthList}
|
||||
onClick={(item: UserTwoFactorAuth) => {
|
||||
setSelectedTwoFactorAuth(item);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showTwoFactorAuth && selectedWebAuthn && (
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-medium mb-4">
|
||||
Authenticating with Security Key
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mb-4">
|
||||
Please follow the instructions on your security key device.
|
||||
</div>
|
||||
{isTwoFactorAuthLoading && <ComponentLoader />}
|
||||
{twofactorAuthError && (
|
||||
<ErrorMessage message={twofactorAuthError} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showTwoFactorAuth && selectedTotpAuth && (
|
||||
{showTwoFactorAuth && selectedTwoFactorAuth && (
|
||||
<BasicForm
|
||||
id="two-factor-auth-form"
|
||||
name="Two Factor Auth"
|
||||
@@ -467,17 +223,14 @@ const LoginPage: () => JSX.Element = () => {
|
||||
try {
|
||||
const code: string = data["code"] as string;
|
||||
const twoFactorAuthId: string =
|
||||
selectedTotpAuth!.id?.toString() as string;
|
||||
selectedTwoFactorAuth.id?.toString() as string;
|
||||
|
||||
const result: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.post({
|
||||
url: VERIFY_TOTP_AUTH_API_URL,
|
||||
await API.post(VERIFY_TWO_FACTOR_AUTH_API_URL, {
|
||||
data: {
|
||||
data: {
|
||||
...initialValues,
|
||||
code: code,
|
||||
twoFactorAuthId: twoFactorAuthId,
|
||||
},
|
||||
...initialValues,
|
||||
code: code,
|
||||
twoFactorAuthId: twoFactorAuthId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -506,7 +259,7 @@ const LoginPage: () => JSX.Element = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-10 text-center">
|
||||
{!selectedTotpAuth && !selectedWebAuthn && (
|
||||
{!selectedTwoFactorAuth && (
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
@@ -517,12 +270,11 @@ const LoginPage: () => JSX.Element = () => {
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{selectedTotpAuth || selectedWebAuthn ? (
|
||||
{selectedTwoFactorAuth ? (
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
<Link
|
||||
onClick={() => {
|
||||
setSelectedTotpAuth(undefined);
|
||||
setSelectedWebAuthn(undefined);
|
||||
setSelectedTwoFactorAuth(undefined);
|
||||
}}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
|
||||
@@ -42,12 +42,12 @@ const LoginPage: () => JSX.Element = () => {
|
||||
try {
|
||||
// get sso config by email.
|
||||
const listResult: HTTPErrorResponse | HTTPResponse<JSONArray> =
|
||||
await API.get({
|
||||
url: URL.fromString(apiUrl.toString()).addQueryParam(
|
||||
await API.get(
|
||||
URL.fromString(apiUrl.toString()).addQueryParam(
|
||||
"email",
|
||||
email.toString(),
|
||||
),
|
||||
});
|
||||
);
|
||||
|
||||
if (listResult instanceof HTTPErrorResponse) {
|
||||
throw listResult;
|
||||
@@ -196,7 +196,6 @@ const LoginPage: () => JSX.Element = () => {
|
||||
required: true,
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]}
|
||||
maxPrimaryButtonWidth={true}
|
||||
|
||||
@@ -4,22 +4,12 @@ import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import ModelForm, {
|
||||
FormType,
|
||||
ModelField,
|
||||
} from "Common/UI/Components/Forms/ModelForm";
|
||||
import { CustomElementProps } from "Common/UI/Components/Forms/Types/Field";
|
||||
import ModelForm, { FormType } from "Common/UI/Components/Forms/ModelForm";
|
||||
import Fields from "Common/UI/Components/Forms/Types/Fields";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import FormValues from "Common/UI/Components/Forms/Types/FormValues";
|
||||
import Link from "Common/UI/Components/Link/Link";
|
||||
import PageLoader from "Common/UI/Components/Loader/PageLoader";
|
||||
import Captcha from "Common/UI/Components/Captcha/Captcha";
|
||||
import {
|
||||
BILLING_ENABLED,
|
||||
DASHBOARD_URL,
|
||||
CAPTCHA_ENABLED,
|
||||
CAPTCHA_SITE_KEY,
|
||||
} from "Common/UI/Config";
|
||||
import { BILLING_ENABLED, DASHBOARD_URL } from "Common/UI/Config";
|
||||
import OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import BaseAPI from "Common/UI/Utils/API/API";
|
||||
import UiAnalytics from "Common/UI/Utils/Analytics";
|
||||
@@ -46,19 +36,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
const isCaptchaEnabled: boolean =
|
||||
CAPTCHA_ENABLED && Boolean(CAPTCHA_SITE_KEY);
|
||||
|
||||
const [shouldResetCaptcha, setShouldResetCaptcha] =
|
||||
React.useState<boolean>(false);
|
||||
const [captchaResetSignal, setCaptchaResetSignal] = React.useState<number>(0);
|
||||
|
||||
const handleCaptchaReset: () => void = React.useCallback(() => {
|
||||
setCaptchaResetSignal((current: number) => {
|
||||
return current + 1;
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}
|
||||
@@ -116,7 +93,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
let formFields: Array<ModelField<User>> = [
|
||||
let formFields: Fields<User> = [
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
@@ -127,7 +104,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
disabled: Boolean(initialValues && initialValues["email"]),
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -138,7 +114,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
required: true,
|
||||
title: "Full Name",
|
||||
dataTestId: "name",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -153,7 +128,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
required: true,
|
||||
title: "Company Name",
|
||||
dataTestId: "companyName",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -185,7 +159,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
title: "Password",
|
||||
required: true,
|
||||
dataTestId: "password",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -202,43 +175,9 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
dataTestId: "confirmPassword",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]);
|
||||
|
||||
if (isCaptchaEnabled) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
captchaToken: true,
|
||||
},
|
||||
overrideFieldKey: "captchaToken",
|
||||
fieldType: FormFieldSchemaType.CustomComponent,
|
||||
title: "Human Verification",
|
||||
description:
|
||||
"Complete the captcha challenge so we know you're not a bot.",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
getCustomElement: (
|
||||
_values: FormValues<User>,
|
||||
customProps: CustomElementProps,
|
||||
) => {
|
||||
return (
|
||||
<Captcha
|
||||
siteKey={CAPTCHA_SITE_KEY}
|
||||
resetSignal={captchaResetSignal}
|
||||
error={customProps.error}
|
||||
onTokenChange={(token: string) => {
|
||||
customProps.onChange?.(token);
|
||||
}}
|
||||
onBlur={customProps.onBlur}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage message={error} />;
|
||||
}
|
||||
@@ -278,27 +217,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={formFields}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
onBeforeCreate={(
|
||||
item: User,
|
||||
miscDataProps: JSONObject,
|
||||
): Promise<User> => {
|
||||
if (isCaptchaEnabled) {
|
||||
const captchaToken: string | undefined = (
|
||||
miscDataProps["captchaToken"] as string | undefined
|
||||
)
|
||||
?.toString()
|
||||
.trim();
|
||||
|
||||
if (!captchaToken) {
|
||||
throw new Error(
|
||||
"Please complete the captcha challenge before signing up.",
|
||||
);
|
||||
}
|
||||
|
||||
miscDataProps["captchaToken"] = captchaToken;
|
||||
setShouldResetCaptcha(true);
|
||||
}
|
||||
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
const utmParams: Dictionary<string> = UserUtil.getUtmParams();
|
||||
|
||||
if (utmParams && Object.keys(utmParams).length > 0) {
|
||||
@@ -316,16 +235,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Sign Up"}
|
||||
onLoadingChange={(loading: boolean) => {
|
||||
if (!isCaptchaEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loading && shouldResetCaptcha) {
|
||||
setShouldResetCaptcha(false);
|
||||
handleCaptchaReset();
|
||||
}
|
||||
}}
|
||||
onSuccess={(value: User, miscData: JSONObject | undefined) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
|
||||
@@ -68,7 +68,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
title: "New Password",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -84,7 +83,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
overrideFieldKey: "confirmPassword",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { IDENTITY_URL, APP_API_URL } from "Common/UI/Config";
|
||||
import { IDENTITY_URL } from "Common/UI/Config";
|
||||
|
||||
export const SIGNUP_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route("/signup"),
|
||||
@@ -9,17 +9,9 @@ export const LOGIN_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route("/login"),
|
||||
);
|
||||
|
||||
export const VERIFY_TOTP_AUTH_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route("/verify-totp-auth"),
|
||||
);
|
||||
|
||||
export const GENERATE_WEBAUTHN_AUTH_OPTIONS_API_URL: URL = URL.fromURL(
|
||||
APP_API_URL,
|
||||
).addRoute(new Route("/user-webauthn/generate-authentication-options"));
|
||||
|
||||
export const VERIFY_WEBAUTHN_AUTH_API_URL: URL = URL.fromURL(
|
||||
export const VERIFY_TWO_FACTOR_AUTH_API_URL: URL = URL.fromURL(
|
||||
IDENTITY_URL,
|
||||
).addRoute(new Route("/verify-webauthn-auth"));
|
||||
).addRoute(new Route("/verify-two-factor-auth"));
|
||||
|
||||
export const SERVICE_PROVIDER_LOGIN_URL: URL = URL.fromURL(
|
||||
IDENTITY_URL,
|
||||
|
||||
84
Accounts/webpack.config.js
Normal file
84
Accounts/webpack.config.js
Normal file
@@ -0,0 +1,84 @@
|
||||
require("ts-loader");
|
||||
require("file-loader");
|
||||
require("style-loader");
|
||||
require("css-loader");
|
||||
require("sass-loader");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const dotenv = require("dotenv");
|
||||
const express = require("express");
|
||||
|
||||
const readEnvFile = (pathToFile) => {
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
|
||||
const env = {};
|
||||
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/accounts/dist/",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
|
||||
alias: {
|
||||
react: path.resolve("./node_modules/react"),
|
||||
},
|
||||
},
|
||||
externals: {
|
||||
"react-native-sqlite-storage": "react-native-sqlite-storage",
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
process: {
|
||||
env: {
|
||||
...readEnvFile("/usr/src/app/dev-env/.env"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: "ts-loader",
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ["style-loader", "css-loader", "sass-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: "file-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use(
|
||||
"/accounts/assets",
|
||||
express.static(path.resolve(__dirname, "public", "assets")),
|
||||
);
|
||||
return middlewares;
|
||||
},
|
||||
},
|
||||
devtool: "eval-source-map",
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
@@ -14,12 +14,9 @@ RUN npm config set fetch-retry-maxtimeout 600000
|
||||
|
||||
ARG GIT_SHA
|
||||
ARG APP_VERSION
|
||||
ARG IS_ENTERPRISE_EDITION=false
|
||||
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV IS_ENTERPRISE_EDITION=${IS_ENTERPRISE_EDITION}
|
||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
@@ -70,20 +67,19 @@ RUN npm install
|
||||
# - 3158: AdminDashboard
|
||||
EXPOSE 3158
|
||||
|
||||
|
||||
RUN npm i -D webpack-cli
|
||||
|
||||
{{ if eq .Env.ENVIRONMENT "development" }}
|
||||
#Run the app
|
||||
|
||||
|
||||
RUN mkdir /usr/src/app/dev-env
|
||||
RUN touch /usr/src/app/dev-env/.env
|
||||
RUN npm i -D webpack-dev-server
|
||||
CMD [ "npm", "run", "dev" ]
|
||||
{{ else }}
|
||||
# Copy app source
|
||||
COPY ./AdminDashboard /usr/src/app
|
||||
# Bundle app source
|
||||
RUN npm run build
|
||||
# Set permission to write logs and cache in case container run as non root
|
||||
RUN chown -R 1000:1000 "/tmp/npm" && chmod -R 2777 "/tmp/npm"
|
||||
#Run the app
|
||||
CMD [ "npm", "start" ]
|
||||
{{ end }}
|
||||
|
||||
@@ -35,7 +35,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies ( Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
|
||||
@@ -1,74 +1,12 @@
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Express, {
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import App from "Common/Server/Utils/StartServer";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import UserMiddleware from "Common/Server/Middleware/UserAuthorization";
|
||||
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
|
||||
import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import "ejs";
|
||||
import JSONWebTokenData from "Common/Types/JsonWebTokenData";
|
||||
|
||||
export const APP_NAME: string = "admin";
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
type EnsureMasterAdminAccessFunction = (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
) => Promise<JSONObject>;
|
||||
|
||||
const ensureMasterAdminAccess: EnsureMasterAdminAccessFunction = async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<JSONObject> => {
|
||||
try {
|
||||
const accessToken: string | undefined =
|
||||
UserMiddleware.getAccessTokenFromExpressRequest(req);
|
||||
|
||||
if (!accessToken) {
|
||||
Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthorizedException(
|
||||
"Unauthorized: Only master admins can access the admin dashboard.",
|
||||
),
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
const authData: JSONWebTokenData = JSONWebToken.decode(accessToken);
|
||||
|
||||
if (!authData.isMasterAdmin) {
|
||||
Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthorizedException(
|
||||
"Unauthorized: Only master admins can access the admin dashboard.",
|
||||
),
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthorizedException(
|
||||
"Unauthorized: Only master admins can access the admin dashboard.",
|
||||
),
|
||||
);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// init the app
|
||||
@@ -80,7 +18,6 @@ const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
getVariablesToRenderIndexPage: ensureMasterAdminAccess,
|
||||
});
|
||||
|
||||
// add default routes
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
const { createConfig, build, watch } = require('Common/UI/esbuild-config');
|
||||
|
||||
const config = createConfig({
|
||||
serviceName: 'AdminDashboard',
|
||||
publicPath: '/admin/dist/',
|
||||
});
|
||||
|
||||
if (process.argv.includes('--watch')) {
|
||||
watch(config, 'AdminDashboard');
|
||||
} else {
|
||||
build(config, 'AdminDashboard');
|
||||
}
|
||||
@@ -1,17 +1,4 @@
|
||||
{
|
||||
"watch": ["./","../Common/UI", "../Common/Types", "../Common/Utils", "../Common/Models"],
|
||||
"ext": "ts,tsx",
|
||||
"ignore": [
|
||||
"./node_modules/**",
|
||||
"./public/**",
|
||||
"./bin/**",
|
||||
"./public/**",
|
||||
"./public/dist/**",
|
||||
"./build/*",
|
||||
"./build/**",
|
||||
"./build/dist/*",
|
||||
"./build/dist/**",
|
||||
"../Common/Server/**"
|
||||
],
|
||||
"exec": " npm run dev-build && npm run start"
|
||||
"watch": ["webpack.config.js"],
|
||||
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3158 --mode=development"
|
||||
}
|
||||
20085
AdminDashboard/package-lock.json
generated
20085
AdminDashboard/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,19 @@
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
|
||||
"ejs": "^3.1.10",
|
||||
|
||||
"dotenv": "^16.4.5",
|
||||
"file-loader": "^6.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.30.1"
|
||||
"react-router-dom": "^6.23.1",
|
||||
"style-loader": "^3.3.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev-build": "NODE_ENV=development node esbuild.config.js",
|
||||
"dev": "npx nodemon",
|
||||
"build": "NODE_ENV=production node esbuild.config.js",
|
||||
"analyze": "analyze=true NODE_ENV=production node esbuild.config.js",
|
||||
"build": "webpack build --mode=production",
|
||||
"test": "react-app-rewired test",
|
||||
"eject": "echo 'esbuild does not require eject'",
|
||||
"eject": "webpack eject",
|
||||
"compile": "tsc",
|
||||
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
|
||||
"start": "node --require ts-node/register Serve.ts",
|
||||
@@ -41,8 +41,13 @@
|
||||
"@types/react": "^18.2.38",
|
||||
"@types/react-dom": "^18.0.4",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"css-loader": "^6.8.1",
|
||||
"nodemon": "^2.0.20",
|
||||
|
||||
"ts-node": "^10.9.1"
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"sass": "^1.51.0",
|
||||
"sass-loader": "^12.6.0",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"webpack": "^5.76.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<link rel="icon" type="image/png" sizes="194x194" href="/favicon-194x194.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#121212">
|
||||
<meta name="theme-color" content="#121212">
|
||||
56
AdminDashboard/public/assets/img/favicons/site.webmanifest
Normal file
56
AdminDashboard/public/assets/img/favicons/site.webmanifest
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "OneUptime Account",
|
||||
"short_name": "OneUptime",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/accounts/assets/img/favicons/android-chrome-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/accounts/assets/img/favicons/android-chrome-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/accounts/assets/img/favicons/android-chrome-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/accounts/assets/img/favicons/android-chrome-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/accounts/assets/img/favicons/android-chrome-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/accounts/assets/img/favicons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/accounts/assets/img/favicons/android-chrome-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/accounts/assets/img/favicons/android-chrome-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/accounts/assets/img/favicons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"theme_color": "#121212",
|
||||
"background_color": "#121212",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<meta name="slack-app-id" content="ACVBMTPJQ">
|
||||
<meta name="description" content="OneUptime — the complete open-source observability platform.">
|
||||
|
||||
<% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %>
|
||||
<!-- Google Tag Manager -->
|
||||
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
@@ -20,8 +19,8 @@
|
||||
})(window,document,'script','dataLayer','GTM-PKQD5WH');
|
||||
</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
<% } %>
|
||||
|
||||
<link rel="manifest" href="/admin/assets/img/favicons/ma">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/admin/assets/img/favicons/apple-touch-icon.png">
|
||||
<link rel="shortcut icon" href="/admin/assets/img/favicons/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/admin/assets/img/favicons/favicon-32x32.png">
|
||||
@@ -74,18 +73,30 @@
|
||||
|
||||
<title>OneUptime Admin Dashboard</title>
|
||||
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
</head>
|
||||
<body class="h-full bg-gray-50">
|
||||
<% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %>
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PKQD5WH"
|
||||
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
<% } %>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="/admin/dist/Index.js"></script>
|
||||
<script src="/admin/dist/bundle.js"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
25
AdminDashboard/public/manifest.json
Normal file
25
AdminDashboard/public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "./assets/img/favicons/favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import Projects from "./Pages/Projects/Index";
|
||||
import SettingsAPIKey from "./Pages/Settings/APIKey/Index";
|
||||
import SettingsAuthentication from "./Pages/Settings/Authentication/Index";
|
||||
import SettingsCallSMS from "./Pages/Settings/CallSMS/Index";
|
||||
import SettingsWhatsApp from "./Pages/Settings/WhatsApp/Index";
|
||||
// Settings Pages.
|
||||
import SettingsEmail from "./Pages/Settings/Email/Index";
|
||||
import SettingsProbes from "./Pages/Settings/Probes/Index";
|
||||
@@ -26,7 +25,6 @@ import {
|
||||
} from "react-router-dom";
|
||||
import UserView from "./Pages/Users/View/Index";
|
||||
import UserDelete from "./Pages/Users/View/Delete";
|
||||
import UserSettings from "./Pages/Users/View/Settings";
|
||||
import ProjectView from "./Pages/Projects/View/Index";
|
||||
import ProjectDelete from "./Pages/Projects/View/Delete";
|
||||
|
||||
@@ -72,11 +70,6 @@ const App: () => JSX.Element = () => {
|
||||
element={<UserView />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.USER_SETTINGS]?.toString() || ""}
|
||||
element={<UserSettings />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.USER_DELETE]?.toString() || ""}
|
||||
element={<UserDelete />}
|
||||
@@ -112,11 +105,6 @@ const App: () => JSX.Element = () => {
|
||||
element={<SettingsCallSMS />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_WHATSAPP]?.toString() || ""}
|
||||
element={<SettingsWhatsApp />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_PROBES]?.toString() || ""}
|
||||
element={<SettingsProbes />}
|
||||
|
||||
@@ -54,9 +54,9 @@ const DashboardFooter: () => JSX.Element = () => {
|
||||
const fetchAppVersion: (appName: string) => Promise<JSONObject> = async (
|
||||
appName: string,
|
||||
): Promise<JSONObject> => {
|
||||
const response: HTTPResponse<JSONObject> = await API.get<JSONObject>({
|
||||
url: URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`),
|
||||
});
|
||||
const response: HTTPResponse<JSONObject> = await API.get<JSONObject>(
|
||||
URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`),
|
||||
);
|
||||
|
||||
if (response.data) {
|
||||
return response.data as JSONObject;
|
||||
@@ -67,7 +67,7 @@ const DashboardFooter: () => JSX.Element = () => {
|
||||
return (
|
||||
<>
|
||||
<Footer
|
||||
className="bg-white px-8"
|
||||
className="bg-white h-16 inset-x-0 bottom-0 px-8"
|
||||
copyright="HackerBay, Inc."
|
||||
links={[
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@ import Logo from "./Logo";
|
||||
import UserProfile from "./UserProfile";
|
||||
import Button, { ButtonStyleType } from "Common/UI/Components/Button/Button";
|
||||
import Header from "Common/UI/Components/Header/Header";
|
||||
import EditionLabel from "Common/UI/Components/EditionLabel/EditionLabel";
|
||||
import { DASHBOARD_URL } from "Common/UI/Config";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
@@ -28,7 +27,6 @@ const DashboardHeader: FunctionComponent = (): ReactElement => {
|
||||
}
|
||||
rightComponents={
|
||||
<>
|
||||
<EditionLabel className="mr-3 hidden md:inline-flex" />
|
||||
<Button
|
||||
title="Exit Admin"
|
||||
buttonStyle={ButtonStyleType.NORMAL}
|
||||
|
||||
@@ -17,9 +17,7 @@ const Logo: FunctionComponent<ComponentProps> = (
|
||||
<Image
|
||||
className="block h-8 w-auto"
|
||||
onClick={() => {
|
||||
if (props.onClick) {
|
||||
props.onClick();
|
||||
}
|
||||
props.onClick && props.onClick();
|
||||
}}
|
||||
imageUrl={Route.fromString(`${OneUptimeLogo}`)}
|
||||
alt={"OneUptime"}
|
||||
|
||||
@@ -2,33 +2,36 @@ import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import NavBar, { NavItem } from "Common/UI/Components/Navbar/NavBar";
|
||||
import NavBar from "Common/UI/Components/Navbar/NavBar";
|
||||
import NavBarItem from "Common/UI/Components/Navbar/NavBarItem";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const DashboardNavbar: FunctionComponent = (): ReactElement => {
|
||||
// Build the navigation items
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
id: "users-nav-bar-item",
|
||||
title: "Users",
|
||||
icon: IconProp.User,
|
||||
route: RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route),
|
||||
},
|
||||
{
|
||||
id: "projects-nav-bar-item",
|
||||
title: "Projects",
|
||||
icon: IconProp.Folder,
|
||||
route: RouteUtil.populateRouteParams(RouteMap[PageMap.PROJECTS] as Route),
|
||||
},
|
||||
{
|
||||
id: "settings-nav-bar-item",
|
||||
title: "Settings",
|
||||
icon: IconProp.Settings,
|
||||
route: RouteUtil.populateRouteParams(RouteMap[PageMap.SETTINGS] as Route),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<NavBar>
|
||||
<NavBarItem
|
||||
title="Users"
|
||||
icon={IconProp.User}
|
||||
route={RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route)}
|
||||
></NavBarItem>
|
||||
|
||||
return <NavBar items={navItems} />;
|
||||
<NavBarItem
|
||||
title="Projects"
|
||||
icon={IconProp.Folder}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.PROJECTS] as Route,
|
||||
)}
|
||||
></NavBarItem>
|
||||
|
||||
<NavBarItem
|
||||
title="Settings"
|
||||
icon={IconProp.Settings}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
)}
|
||||
></NavBarItem>
|
||||
</NavBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardNavbar;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import App from "./App";
|
||||
import Telemetry from "Common/UI/Utils/Telemetry/Telemetry";
|
||||
import Telemetry from "Common/UI/Utils/Telemetry";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
@@ -16,7 +16,7 @@ const Logout: FunctionComponent = (): ReactElement => {
|
||||
|
||||
const logout: PromiseVoidFunction = async (): Promise<void> => {
|
||||
UiAnalytics.logout();
|
||||
UserUtil.logout();
|
||||
await UserUtil.logout();
|
||||
Navigation.navigate(ACCOUNTS_URL);
|
||||
};
|
||||
|
||||
|
||||
@@ -241,10 +241,8 @@ const Projects: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
hideOnMobile: true,
|
||||
},
|
||||
]}
|
||||
userPreferencesKey="admin-projects-table"
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -72,9 +72,9 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPrimaryPhoneNumber: true,
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: "Primary Twilio Phone Number",
|
||||
title: "Twilio Phone Number",
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: true,
|
||||
description: "You can find this in your Twilio console.",
|
||||
@@ -83,20 +83,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioSecondaryPhoneNumbers: true,
|
||||
},
|
||||
title: "Secondary Twilio Phone Number",
|
||||
fieldType: FormFieldSchemaType.LongText,
|
||||
required: true,
|
||||
description:
|
||||
"If you have bought more phone numbers from Twilio for specific countries, you can add them here.",
|
||||
placeholder: "+1234567890, +4444444444",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
@@ -111,20 +97,12 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPrimaryPhoneNumber: true,
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: "Primary Twilio Phone Number",
|
||||
title: "Twilio Phone Number",
|
||||
fieldType: FieldType.Phone,
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioSecondaryPhoneNumbers: true,
|
||||
},
|
||||
title: "Secondary Twilio Phone Numbers",
|
||||
fieldType: FieldType.LongText,
|
||||
placeholder: "None",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
|
||||
@@ -21,7 +21,7 @@ import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
const [emailServerType, setemailServerType] = React.useState<EmailServerType>(
|
||||
EmailServerType.CustomSMTP,
|
||||
EmailServerType.Internal,
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(true);
|
||||
@@ -43,7 +43,7 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
|
||||
if (globalConfig) {
|
||||
setemailServerType(
|
||||
globalConfig.emailServerType || EmailServerType.CustomSMTP,
|
||||
globalConfig.emailServerType || EmailServerType.Internal,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
title: "Admin Notification Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: false,
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
@@ -127,7 +126,7 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
/>
|
||||
|
||||
<CardModelDetail
|
||||
name="Email Server Settings"
|
||||
name="Internal SMTP Settings"
|
||||
cardProps={{
|
||||
title: "Email Server Settings",
|
||||
description:
|
||||
@@ -172,7 +171,7 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
cardProps={{
|
||||
title: "Custom Email and SMTP Settings",
|
||||
description:
|
||||
"Please configure your SMTP server here to send emails.",
|
||||
"If you have not enabled Internal SMTP server to send emails. Please configure your SMTP server here.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit SMTP Config"
|
||||
@@ -200,7 +199,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
fieldType: FormFieldSchemaType.Hostname,
|
||||
required: true,
|
||||
placeholder: "smtp.server.com",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -231,7 +229,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
placeholder: "emailuser",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -242,7 +239,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: false,
|
||||
placeholder: "Password",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -255,7 +251,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
description:
|
||||
"Email used to log in to this SMTP Server. This is also the email your customers will see. ",
|
||||
placeholder: "email@company.com",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -268,7 +263,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
description:
|
||||
"This is the display name your team and customers see, when they receive emails from OneUptime.",
|
||||
placeholder: "Company, Inc.",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
|
||||
@@ -54,11 +54,9 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
title="Need help with setting up Global Probes?"
|
||||
description="Here is a guide which will help you get set up"
|
||||
link={Route.fromString("/docs/probe/custom-probe")}
|
||||
hideOnMobile={true}
|
||||
/>
|
||||
|
||||
<ModelTable<Probe>
|
||||
userPreferencesKey={"admin-probes-table"}
|
||||
modelType={Probe}
|
||||
id="probes-table"
|
||||
name="Settings > Global Probes"
|
||||
@@ -153,7 +151,7 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
type: FieldType.LongText,
|
||||
type: FieldType.Text,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
@@ -174,8 +172,7 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
noValueMessage: "-",
|
||||
title: "Description",
|
||||
type: FieldType.LongText,
|
||||
hideOnMobile: true,
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -183,7 +180,6 @@ const Settings: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: "Status",
|
||||
type: FieldType.Text,
|
||||
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
if (
|
||||
item &&
|
||||
|
||||
@@ -50,15 +50,6 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "WhatsApp",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_WHATSAPP] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.WhatsApp}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Monitoring">
|
||||
|
||||
@@ -1,491 +0,0 @@
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "Common/UI/Components/Page/Page";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import GlobalConfig from "Common/Models/DatabaseModels/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
import Card from "Common/UI/Components/Card/Card";
|
||||
import MarkdownViewer from "Common/UI/Components/Markdown.tsx/MarkdownViewer";
|
||||
import BasicForm from "Common/UI/Components/Forms/BasicForm";
|
||||
import Alert, { AlertType } from "Common/UI/Components/Alerts/Alert";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import { APP_API_URL } from "Common/UI/Config";
|
||||
import WhatsAppTemplateMessages, {
|
||||
WhatsAppTemplateId,
|
||||
WhatsAppTemplateIds,
|
||||
WhatsAppTemplateLanguage,
|
||||
} from "Common/Types/WhatsApp/WhatsAppTemplates";
|
||||
|
||||
type ToFriendlyName = (value: string) => string;
|
||||
|
||||
const toFriendlyName: ToFriendlyName = (value: string): string => {
|
||||
return value
|
||||
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
||||
.replace(/_/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
};
|
||||
|
||||
type ExtractTemplateVariables = (template: string) => Array<string>;
|
||||
|
||||
const extractTemplateVariables: ExtractTemplateVariables = (
|
||||
template: string,
|
||||
): Array<string> => {
|
||||
const matches: RegExpMatchArray | null = template.match(/\{\{(.*?)\}\}/g);
|
||||
|
||||
if (!matches) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const uniqueVariables: Set<string> = new Set<string>();
|
||||
|
||||
for (const match of matches) {
|
||||
const variable: string = match.replace("{{", "").replace("}}", "").trim();
|
||||
|
||||
if (variable) {
|
||||
uniqueVariables.add(variable);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(uniqueVariables).sort((a: string, b: string) => {
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
};
|
||||
|
||||
type BuildWhatsAppSetupMarkdown = () => string;
|
||||
|
||||
const buildWhatsAppSetupMarkdown: BuildWhatsAppSetupMarkdown = (): string => {
|
||||
const templateKeys: Array<keyof typeof WhatsAppTemplateIds> = Object.keys(
|
||||
WhatsAppTemplateIds,
|
||||
) as Array<keyof typeof WhatsAppTemplateIds>;
|
||||
|
||||
const appApiBaseUrl: string = APP_API_URL.toString().replace(/\/$/, "");
|
||||
const primaryWebhookUrl: string = `${appApiBaseUrl}/notification/whatsapp/webhook`;
|
||||
|
||||
const description: string =
|
||||
"Follow these steps to connect Meta WhatsApp with OneUptime so notifications can be delivered via WhatsApp.";
|
||||
|
||||
const prerequisitesList: Array<string> = [
|
||||
"Meta Business Manager admin access for the WhatsApp Business Account.",
|
||||
"A WhatsApp Business phone number approved for API messaging.",
|
||||
"Admin access to OneUptime with permission to edit global notification settings.",
|
||||
"A webhook verify token string that you'll configure identically in Meta and OneUptime.",
|
||||
];
|
||||
|
||||
const setupStepsList: Array<string> = [
|
||||
"Sign in to the [Meta Business Manager](https://business.facebook.com/) with admin access to your WhatsApp Business Account.",
|
||||
"From **Business Settings → Accounts → WhatsApp Accounts**, create or select the account that owns your sender phone number.",
|
||||
"In Buisness Portfolio, create a system user and assign it to the WhatsApp Business Account with the role of **Admin**.",
|
||||
"Generate a token for this system user and this will be your long-lived access token. Make sure to select the **whatsapp_business_management** and **whatsapp_business_messaging** permissions when generating the token.",
|
||||
"Paste the access token, phone number ID, and webhook verify token into the **Meta WhatsApp Settings** card above, then save.",
|
||||
"For the **Business Account ID**, go to **Business Settings → Business Info** (or **Business Settings → WhatsApp Accounts → Settings**) and copy the **WhatsApp Business Account ID** value.",
|
||||
"To locate the **App ID** and **App Secret**, open [Meta for Developers](https://developers.facebook.com/apps/), select your WhatsApp app, then navigate to **Settings → Basic**. The App ID is shown at the top; click **Show** next to **App Secret** to reveal and copy it.",
|
||||
"Create each template listed below in the Meta WhatsApp Manager. Make sure the template name, language, and variables match exactly. You can however change the content to your preference. Please make sure it's approved by Meta.",
|
||||
"Send a test notification from OneUptime to confirm that WhatsApp delivery succeeds.",
|
||||
];
|
||||
|
||||
const prerequisitesMarkdown: string = prerequisitesList
|
||||
.map((item: string) => {
|
||||
return `- ${item}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const setupStepsMarkdown: string = setupStepsList
|
||||
.map((item: string, index: number) => {
|
||||
return `${index + 1}. ${item}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const tableRows: string = templateKeys
|
||||
.map((enumKey: keyof typeof WhatsAppTemplateIds) => {
|
||||
const templateId: WhatsAppTemplateId = WhatsAppTemplateIds[enumKey];
|
||||
const friendlyName: string = toFriendlyName(enumKey.toString());
|
||||
const templateMessage: string = WhatsAppTemplateMessages[templateId];
|
||||
const language: string = WhatsAppTemplateLanguage[templateId] || "en";
|
||||
const variables: Array<string> =
|
||||
extractTemplateVariables(templateMessage);
|
||||
const variableList: string =
|
||||
variables.length > 0
|
||||
? variables
|
||||
.map((variable: string) => {
|
||||
return `\`${variable}\``;
|
||||
})
|
||||
.join(", ")
|
||||
: "_None_";
|
||||
|
||||
return `| ${friendlyName} | \`${templateId}\` | ${language} | ${variableList} |`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const templateBodies: string = templateKeys
|
||||
.map((enumKey: keyof typeof WhatsAppTemplateIds) => {
|
||||
const templateId: WhatsAppTemplateId = WhatsAppTemplateIds[enumKey];
|
||||
const friendlyName: string = toFriendlyName(enumKey.toString());
|
||||
const templateMessage: string = WhatsAppTemplateMessages[templateId];
|
||||
const language: string = WhatsAppTemplateLanguage[templateId] || "en";
|
||||
const variables: Array<string> =
|
||||
extractTemplateVariables(templateMessage);
|
||||
const variableMarkdown: string =
|
||||
variables.length > 0
|
||||
? variables
|
||||
.map((variable: string) => {
|
||||
return `- \`${variable}\``;
|
||||
})
|
||||
.join("\n")
|
||||
: "_None_";
|
||||
const variablesHeading: string = variables.length
|
||||
? `**Variables (${variables.length})**`
|
||||
: "**Variables**";
|
||||
|
||||
return [
|
||||
`#### ${friendlyName}`,
|
||||
"",
|
||||
`**Template Name:** \`${templateId}\``,
|
||||
`**Language:** ${language}`,
|
||||
"",
|
||||
variablesHeading,
|
||||
variableMarkdown,
|
||||
"",
|
||||
"**Body**",
|
||||
"```plaintext",
|
||||
templateMessage,
|
||||
"```",
|
||||
"",
|
||||
"---",
|
||||
].join("\n");
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const templateSummaryTable: string = [
|
||||
"| Friendly Name | Template Name | Language | Variables |",
|
||||
"| --- | --- | --- | --- |",
|
||||
tableRows,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
|
||||
const webhookSection: string = [
|
||||
"### Configure Meta Webhook Subscription",
|
||||
"1. In the OneUptime Admin Dashboard, open **Settings → WhatsApp → Meta WhatsApp Settings** and enter a strong value in **Webhook Verify Token**. Save the form so the encrypted token is stored in Global Config.",
|
||||
"2. Keep that verify token handy—Meta does not generate one for you. You'll paste the exact same value when configuring the callback.",
|
||||
"3. In [Meta for Developers](https://developers.facebook.com/apps/), select your WhatsApp app and navigate to **WhatsApp → Configuration → Webhooks**.",
|
||||
`4. Click **Configure**, then supply one of the following callback URLs when Meta asks for your endpoint:\n - \`${primaryWebhookUrl}\`\n `,
|
||||
"5. Paste the verify token from step 1 into Meta's **Verify Token** field and submit. Meta will call the callback URL and expect that value to match before it approves the subscription.",
|
||||
"6. After verification succeeds, subscribe to the **messages** field (and any other WhatsApp webhook categories you need) so delivery status updates are forwarded to OneUptime.",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
|
||||
return [
|
||||
description,
|
||||
"### Prerequisites",
|
||||
prerequisitesMarkdown,
|
||||
"### Setup Steps",
|
||||
setupStepsMarkdown,
|
||||
webhookSection,
|
||||
"### Required WhatsApp Templates",
|
||||
templateSummaryTable,
|
||||
"### Template Bodies",
|
||||
"> Copy the exact template body below—including punctuation and spacing—when creating each template inside Meta. The variables list shows every placeholder that must be configured in WhatsApp Manager.",
|
||||
templateBodies,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
};
|
||||
|
||||
const whatsappSetupMarkdown: string = buildWhatsAppSetupMarkdown();
|
||||
|
||||
const SettingsWhatsApp: FunctionComponent = (): ReactElement => {
|
||||
const [isSendingTest, setIsSendingTest] = useState<boolean>(false);
|
||||
const [testError, setTestError] = useState<string>("");
|
||||
const [testSuccess, setTestSuccess] = useState<string>("");
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "WhatsApp",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_WHATSAPP] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
<CardModelDetail
|
||||
name="Meta WhatsApp Settings"
|
||||
cardProps={{
|
||||
title: "Meta WhatsApp Settings",
|
||||
description:
|
||||
"Configure Meta WhatsApp credentials. These values are used to send WhatsApp notifications from OneUptime.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Meta WhatsApp Config"
|
||||
formSteps={[
|
||||
{
|
||||
title: "Credentials",
|
||||
id: "meta-credentials",
|
||||
},
|
||||
{
|
||||
title: "Meta App",
|
||||
id: "meta-app",
|
||||
},
|
||||
]}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAccessToken: true,
|
||||
},
|
||||
title: "Access Token",
|
||||
stepId: "meta-credentials",
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: true,
|
||||
description:
|
||||
"Long-lived access token generated in the Meta WhatsApp Business Platform.",
|
||||
placeholder: "EAAG...",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppPhoneNumberId: true,
|
||||
},
|
||||
title: "Phone Number ID",
|
||||
stepId: "meta-credentials",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
"The WhatsApp Business phone number ID associated with your sending number.",
|
||||
placeholder: "123456789012345",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppBusinessAccountId: true,
|
||||
},
|
||||
title: "Business Account ID",
|
||||
stepId: "meta-credentials",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
description:
|
||||
"Optional Business Account ID that owns the WhatsApp templates.",
|
||||
placeholder: "123456789012345",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppWebhookVerifyToken: true,
|
||||
},
|
||||
title: "Webhook Verify Token",
|
||||
stepId: "meta-credentials",
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: false,
|
||||
description:
|
||||
"Secret token configured in Meta to validate webhook subscription requests.",
|
||||
placeholder: "Webhook verify token",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAppId: true,
|
||||
},
|
||||
title: "App ID",
|
||||
stepId: "meta-app",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
description:
|
||||
"Optional Facebook App ID tied to your WhatsApp integration.",
|
||||
placeholder: "987654321098765",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAppSecret: true,
|
||||
},
|
||||
title: "App Secret",
|
||||
stepId: "meta-app",
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: false,
|
||||
description:
|
||||
"Optional Facebook App Secret used for webhook signature verification.",
|
||||
placeholder: "Facebook App Secret",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config-meta-whatsapp",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAccessToken: true,
|
||||
},
|
||||
title: "Access Token",
|
||||
fieldType: FieldType.HiddenText,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppPhoneNumberId: true,
|
||||
},
|
||||
title: "Phone Number ID",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppBusinessAccountId: true,
|
||||
},
|
||||
title: "Business Account ID",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppWebhookVerifyToken: true,
|
||||
},
|
||||
title: "Webhook Verify Token",
|
||||
fieldType: FieldType.HiddenText,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAppId: true,
|
||||
},
|
||||
title: "App ID",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
metaWhatsAppAppSecret: true,
|
||||
},
|
||||
title: "App Secret",
|
||||
fieldType: FieldType.HiddenText,
|
||||
placeholder: "Not Configured",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Card
|
||||
title="Send Test WhatsApp Message"
|
||||
description="Send a test WhatsApp template message to confirm your Meta configuration."
|
||||
>
|
||||
{testSuccess ? (
|
||||
<Alert
|
||||
type={AlertType.SUCCESS}
|
||||
title={testSuccess}
|
||||
className="mb-4"
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<BasicForm
|
||||
id="send-test-whatsapp-form"
|
||||
name="Send Test WhatsApp Message"
|
||||
isLoading={isSendingTest}
|
||||
error={testError || ""}
|
||||
submitButtonText="Send Test Message"
|
||||
maxPrimaryButtonWidth={true}
|
||||
initialValues={{
|
||||
phoneNumber: "",
|
||||
}}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
phoneNumber: true,
|
||||
},
|
||||
title: "Recipient WhatsApp Number",
|
||||
description:
|
||||
"Enter the full international phone number (including country code) that should receive the test message.",
|
||||
placeholder: "+11234567890",
|
||||
required: true,
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]}
|
||||
onSubmit={async (
|
||||
values: JSONObject,
|
||||
onSubmitSuccessful?: () => void,
|
||||
) => {
|
||||
const toPhone: string = String(values["phoneNumber"] || "").trim();
|
||||
|
||||
if (!toPhone) {
|
||||
setTestSuccess("");
|
||||
setTestError(
|
||||
"Please enter a WhatsApp number before sending a test message.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSendingTest(true);
|
||||
setTestError("");
|
||||
setTestSuccess("");
|
||||
|
||||
try {
|
||||
const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
||||
await API.post({
|
||||
url: URL.fromString(APP_API_URL.toString()).addRoute(
|
||||
"/notification/whatsapp/test",
|
||||
),
|
||||
data: {
|
||||
toPhone,
|
||||
templateKey: WhatsAppTemplateIds.TestNotification,
|
||||
templateLanguageCode:
|
||||
WhatsAppTemplateLanguage[
|
||||
WhatsAppTemplateIds.TestNotification
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw response;
|
||||
}
|
||||
|
||||
if (response.isFailure()) {
|
||||
throw new Error("Failed to send test WhatsApp message.");
|
||||
}
|
||||
|
||||
setTestSuccess(
|
||||
"Test WhatsApp message sent successfully. Check the recipient device to confirm delivery.",
|
||||
);
|
||||
|
||||
if (onSubmitSuccessful) {
|
||||
onSubmitSuccessful();
|
||||
}
|
||||
} catch (err) {
|
||||
setTestError(API.getFriendlyMessage(err));
|
||||
} finally {
|
||||
setIsSendingTest(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="Meta WhatsApp Setup Guide"
|
||||
description="Steps to connect Meta WhatsApp and the templates you must provision."
|
||||
>
|
||||
<MarkdownViewer text={whatsappSetupMarkdown} />
|
||||
</Card>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsWhatsApp;
|
||||
@@ -26,7 +26,6 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
>
|
||||
<ModelTable<User>
|
||||
modelType={User}
|
||||
userPreferencesKey="admin-users-table"
|
||||
id="users-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
@@ -48,7 +47,6 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
placeholder: "email@company.com",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -58,7 +56,6 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
required: true,
|
||||
placeholder: "Password",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -116,7 +113,6 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: "Email Verified",
|
||||
type: FieldType.Boolean,
|
||||
hideOnMobile: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
@@ -124,7 +120,6 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
hideOnMobile: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import SideMenuComponent from "./SideMenu";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import ModelPage from "Common/UI/Components/Page/ModelPage";
|
||||
import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
|
||||
const UserSettings: FunctionComponent = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
|
||||
|
||||
return (
|
||||
<ModelPage<User>
|
||||
modelId={modelId}
|
||||
modelNameField="email"
|
||||
modelType={User}
|
||||
title={"User"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Users",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route),
|
||||
},
|
||||
{
|
||||
title: "User",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USER_VIEW] as Route,
|
||||
{
|
||||
modelId: modelId,
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USER_SETTINGS] as Route,
|
||||
{
|
||||
modelId: modelId,
|
||||
},
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<SideMenuComponent modelId={modelId} />}
|
||||
>
|
||||
<CardModelDetail<User>
|
||||
name="user-master-admin-settings"
|
||||
cardProps={{
|
||||
title: "Master Admin Access",
|
||||
description:
|
||||
"Grant or revoke master admin access for this user. Master admins can manage every project and workspace.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Update Access"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
isMasterAdmin: true,
|
||||
},
|
||||
title: "Master Admin",
|
||||
description:
|
||||
"Enable to give this user full access to the entire platform.",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: User,
|
||||
id: "user-master-admin-settings-detail",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
isMasterAdmin: true,
|
||||
},
|
||||
title: "Master Admin",
|
||||
fieldType: FieldType.Boolean,
|
||||
placeholder: "No",
|
||||
},
|
||||
],
|
||||
modelId: modelId,
|
||||
}}
|
||||
/>
|
||||
</ModelPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserSettings;
|
||||
@@ -30,18 +30,6 @@ const SideMenuComponent: FunctionComponent<SideMenuProps> = (
|
||||
}}
|
||||
icon={IconProp.Info}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USER_SETTINGS] as Route,
|
||||
{
|
||||
modelId: props.modelId,
|
||||
},
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Settings}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Advanced">
|
||||
|
||||
@@ -6,7 +6,6 @@ enum PageMap {
|
||||
|
||||
USERS = "USERS",
|
||||
USER_VIEW = "USER_VIEW",
|
||||
USER_SETTINGS = "USER_SETTINGS",
|
||||
USER_DELETE = "USER_DELETE",
|
||||
|
||||
PROJECTS = "PROJECTS",
|
||||
@@ -16,7 +15,6 @@ enum PageMap {
|
||||
SETTINGS_HOST = "SETTINGS_HOST",
|
||||
SETTINGS_SMTP = "SETTINGS_SMTP",
|
||||
SETTINGS_CALL_AND_SMS = "SETTINGS_CALL_AND_SMS",
|
||||
SETTINGS_WHATSAPP = "SETTINGS_WHATSAPP",
|
||||
SETTINGS_PROBES = "SETTINGS_PROBES",
|
||||
SETTINGS_AUTHENTICATION = "SETTINGS_AUTHENTICATION",
|
||||
SETTINGS_API_KEY = "SETTINGS_API_KEY",
|
||||
|
||||
@@ -18,9 +18,6 @@ const RouteMap: Dictionary<Route> = {
|
||||
|
||||
[PageMap.USERS]: new Route(`/admin/users`),
|
||||
[PageMap.USER_VIEW]: new Route(`/admin/users/${RouteParams.ModelID}`),
|
||||
[PageMap.USER_SETTINGS]: new Route(
|
||||
`/admin/users/${RouteParams.ModelID}/settings`,
|
||||
),
|
||||
[PageMap.USER_DELETE]: new Route(
|
||||
`/admin/users/${RouteParams.ModelID}/delete`,
|
||||
),
|
||||
@@ -28,7 +25,6 @@ const RouteMap: Dictionary<Route> = {
|
||||
[PageMap.SETTINGS_HOST]: new Route(`/admin/settings/host`),
|
||||
[PageMap.SETTINGS_SMTP]: new Route(`/admin/settings/smtp`),
|
||||
[PageMap.SETTINGS_CALL_AND_SMS]: new Route(`/admin/settings/call-and-sms`),
|
||||
[PageMap.SETTINGS_WHATSAPP]: new Route(`/admin/settings/whatsapp`),
|
||||
[PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`),
|
||||
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
|
||||
`/admin/settings/authentication`,
|
||||
|
||||
84
AdminDashboard/webpack.config.js
Normal file
84
AdminDashboard/webpack.config.js
Normal file
@@ -0,0 +1,84 @@
|
||||
require("ts-loader");
|
||||
require("file-loader");
|
||||
require("style-loader");
|
||||
require("css-loader");
|
||||
require("sass-loader");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const dotenv = require("dotenv");
|
||||
const express = require("express");
|
||||
|
||||
const readEnvFile = (pathToFile) => {
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
|
||||
const env = {};
|
||||
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/admin/dist/",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
|
||||
alias: {
|
||||
react: path.resolve("./node_modules/react"),
|
||||
},
|
||||
},
|
||||
externals: {
|
||||
"react-native-sqlite-storage": "react-native-sqlite-storage",
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
process: {
|
||||
env: {
|
||||
...readEnvFile("/usr/src/app/dev-env/.env"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: "ts-loader",
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ["style-loader", "css-loader", "sass-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: "file-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use(
|
||||
"/admin/assets",
|
||||
express.static(path.resolve(__dirname, "public", "assets")),
|
||||
);
|
||||
return middlewares;
|
||||
},
|
||||
},
|
||||
devtool: "eval-source-map",
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
@@ -14,12 +14,9 @@ RUN npm config set fetch-retry-maxtimeout 600000
|
||||
|
||||
ARG GIT_SHA
|
||||
ARG APP_VERSION
|
||||
ARG IS_ENTERPRISE_EDITION=false
|
||||
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
ENV IS_ENTERPRISE_EDITION=${IS_ENTERPRISE_EDITION}
|
||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
@@ -68,8 +65,6 @@ CMD [ "npm", "run", "dev" ]
|
||||
COPY ./App /usr/src/app
|
||||
# Bundle app source
|
||||
RUN npm run compile
|
||||
# Set permission to write logs and cache in case container run as non root
|
||||
RUN chown -R 1000:1000 "/tmp/npm" && chmod -R 2777 "/tmp/npm"
|
||||
#Run the app
|
||||
CMD [ "npm", "start" ]
|
||||
{{ end }}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import BaseAPI from "Common/Server/API/BaseAPI";
|
||||
import BaseAnalyticsAPI from "Common/Server/API/BaseAnalyticsAPI";
|
||||
import BillingAPI from "Common/Server/API/BillingAPI";
|
||||
import BillingInvoiceAPI from "Common/Server/API/BillingInvoiceAPI";
|
||||
import BillingPaymentMethodAPI from "Common/Server/API/BillingPaymentMethodAPI";
|
||||
import CopilotCodeRepositoryAPI from "Common/Server/API/CopilotCodeRepositoryAPI";
|
||||
@@ -14,33 +13,20 @@ import TelemetryAPI from "Common/Server/API/TelemetryAPI";
|
||||
import ProbeAPI from "Common/Server/API/ProbeAPI";
|
||||
import ProjectAPI from "Common/Server/API/ProjectAPI";
|
||||
import ProjectSsoAPI from "Common/Server/API/ProjectSSO";
|
||||
import WhatsAppLogAPI from "./WhatsAppLogAPI";
|
||||
|
||||
// Import API
|
||||
import ResellerPlanAPI from "Common/Server/API/ResellerPlanAPI";
|
||||
import EnterpriseLicenseAPI from "Common/Server/API/EnterpriseLicenseAPI";
|
||||
import MonitorAPI from "Common/Server/API/MonitorAPI";
|
||||
import ShortLinkAPI from "Common/Server/API/ShortLinkAPI";
|
||||
import StatusPageAPI from "Common/Server/API/StatusPageAPI";
|
||||
import WorkspaceNotificationRuleAPI from "Common/Server/API/WorkspaceNotificationRuleAPI";
|
||||
import StatusPageDomainAPI from "Common/Server/API/StatusPageDomainAPI";
|
||||
import StatusPageSubscriberAPI from "Common/Server/API/StatusPageSubscriberAPI";
|
||||
import UserCallAPI from "Common/Server/API/UserCallAPI";
|
||||
import UserTotpAuthAPI from "Common/Server/API/UserTotpAuthAPI";
|
||||
import UserWebAuthnAPI from "Common/Server/API/UserWebAuthnAPI";
|
||||
import UserTwoFactorAuthAPI from "Common/Server/API/UserTwoFactorAuthAPI";
|
||||
import MonitorTest from "Common/Models/DatabaseModels/MonitorTest";
|
||||
import IncidentInternalNoteAPI from "Common/Server/API/IncidentInternalNoteAPI";
|
||||
import IncidentPublicNoteAPI from "Common/Server/API/IncidentPublicNoteAPI";
|
||||
import ScheduledMaintenanceInternalNoteAPI from "Common/Server/API/ScheduledMaintenanceInternalNoteAPI";
|
||||
import ScheduledMaintenancePublicNoteAPI from "Common/Server/API/ScheduledMaintenancePublicNoteAPI";
|
||||
import IncidentAPI from "Common/Server/API/IncidentAPI";
|
||||
// User Notification methods.
|
||||
import UserEmailAPI from "Common/Server/API/UserEmailAPI";
|
||||
import UserNotificationLogTimelineAPI from "Common/Server/API/UserOnCallLogTimelineAPI";
|
||||
import UserSMSAPI from "Common/Server/API/UserSmsAPI";
|
||||
import UserWhatsAppAPI from "Common/Server/API/UserWhatsAppAPI";
|
||||
import UserPushAPI from "Common/Server/API/UserPushAPI";
|
||||
import UserAPI from "Common/Server/API/UserAPI";
|
||||
import ApiKeyPermissionService, {
|
||||
Service as ApiKeyPermissionServiceType,
|
||||
} from "Common/Server/Services/ApiKeyPermissionService";
|
||||
@@ -66,7 +52,9 @@ import EmailVerificationTokenService, {
|
||||
import AlertCustomFieldService, {
|
||||
Service as AlertCustomFieldServiceType,
|
||||
} from "Common/Server/Services/AlertCustomFieldService";
|
||||
import AlertInternalNoteAPI from "Common/Server/API/AlertInternalNoteAPI";
|
||||
import AlertInternalNoteService, {
|
||||
Service as AlertInternalNoteServiceType,
|
||||
} from "Common/Server/Services/AlertInternalNoteService";
|
||||
import AlertNoteTemplateService, {
|
||||
Service as AlertNoteTemplateServiceType,
|
||||
} from "Common/Server/Services/AlertNoteTemplateService";
|
||||
@@ -97,12 +85,12 @@ import AlertStateTimelineService, {
|
||||
import IncidentCustomFieldService, {
|
||||
Service as IncidentCustomFieldServiceType,
|
||||
} from "Common/Server/Services/IncidentCustomFieldService";
|
||||
import IncidentInternalNoteService, {
|
||||
Service as IncidentInternalNoteServiceType,
|
||||
} from "Common/Server/Services/IncidentInternalNoteService";
|
||||
import IncidentNoteTemplateService, {
|
||||
Service as IncidentNoteTemplateServiceType,
|
||||
} from "Common/Server/Services/IncidentNoteTemplateService";
|
||||
import IncidentPostmortemTemplateService, {
|
||||
Service as IncidentPostmortemTemplateServiceType,
|
||||
} from "Common/Server/Services/IncidentPostmortemTemplateService";
|
||||
import TableViewService, {
|
||||
Service as TableViewServiceType,
|
||||
} from "Common/Server/Services/TableViewService";
|
||||
@@ -112,6 +100,12 @@ import IncidentOwnerTeamService, {
|
||||
import IncidentOwnerUserService, {
|
||||
Service as IncidentOwnerUserServiceType,
|
||||
} from "Common/Server/Services/IncidentOwnerUserService";
|
||||
import IncidentPublicNoteService, {
|
||||
Service as IncidentPublicNoteServiceType,
|
||||
} from "Common/Server/Services/IncidentPublicNoteService";
|
||||
import IncidentService, {
|
||||
Service as IncidentServiceType,
|
||||
} from "Common/Server/Services/IncidentService";
|
||||
import IncidentSeverityService, {
|
||||
Service as IncidentSeverityServiceType,
|
||||
} from "Common/Server/Services/IncidentSeverityService";
|
||||
@@ -140,6 +134,9 @@ import LogService, {
|
||||
LogService as LogServiceType,
|
||||
} from "Common/Server/Services/LogService";
|
||||
|
||||
import TelemetryAttributeService, {
|
||||
TelemetryAttributeService as TelemetryAttributeServiceType,
|
||||
} from "Common/Server/Services/TelemetryAttributeService";
|
||||
import CopilotActionTypePriorityService, {
|
||||
Service as CopilotActionTypePriorityServiceType,
|
||||
} from "Common/Server/Services/CopilotActionTypePriorityService";
|
||||
@@ -171,17 +168,15 @@ import MonitorProbeService, {
|
||||
import MonitorSecretService, {
|
||||
Service as MonitorSecretServiceType,
|
||||
} from "Common/Server/Services/MonitorSecretService";
|
||||
import MonitorService, {
|
||||
Service as MonitorServiceType,
|
||||
} from "Common/Server/Services/MonitorService";
|
||||
import MonitorStatusService, {
|
||||
Service as MonitorStatusServiceType,
|
||||
} from "Common/Server/Services/MonitorStatusService";
|
||||
import MonitorTimelineStatusService, {
|
||||
Service as MonitorTimelineStatusServiceType,
|
||||
} from "Common/Server/Services/MonitorStatusTimelineService";
|
||||
// user override
|
||||
import OnCallDutyPolicyUserOverrideService, {
|
||||
Service as OnCallDutyPolicyUserOverrideServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyUserOverrideService";
|
||||
import OnCallDutyPolicyUserOverride from "Common/Models/DatabaseModels/OnCallDutyPolicyUserOverride";
|
||||
import OnCallDutyPolicyCustomFieldService, {
|
||||
Service as OnCallDutyPolicyCustomFieldServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyCustomFieldService";
|
||||
@@ -212,6 +207,9 @@ import OnCallDutyPolicyScheduleLayerUserService, {
|
||||
import OnCallDutyPolicyScheduleService, {
|
||||
Service as OnCallDutyPolicyScheduleServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyScheduleService";
|
||||
import OnCallDutyPolicyService, {
|
||||
Service as OnCallDutyPolicyServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyService";
|
||||
import ProjectCallSMSConfigService, {
|
||||
Service as ProjectCallSMSConfigServiceType,
|
||||
} from "Common/Server/Services/ProjectCallSMSConfigService";
|
||||
@@ -227,6 +225,9 @@ import ResellerService, {
|
||||
import ScheduledMaintenanceCustomFieldService, {
|
||||
Service as ScheduledMaintenanceCustomFieldServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenanceCustomFieldService";
|
||||
import ScheduledMaintenanceInternalNoteService, {
|
||||
Service as ScheduledMaintenanceInternalNoteServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenanceInternalNoteService";
|
||||
import ScheduledMaintenanceNoteTemplateService, {
|
||||
Service as ScheduledMaintenanceNoteTemplateServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenanceNoteTemplateService";
|
||||
@@ -236,6 +237,9 @@ import ScheduledMaintenanceOwnerTeamService, {
|
||||
import ScheduledMaintenanceOwnerUserService, {
|
||||
Service as ScheduledMaintenanceOwnerUserServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenanceOwnerUserService";
|
||||
import ScheduledMaintenancePublicNoteService, {
|
||||
Service as ScheduledMaintenancePublicNoteServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenancePublicNoteService";
|
||||
import ScheduledMaintenanceService, {
|
||||
Service as ScheduledMaintenanceServiceType,
|
||||
} from "Common/Server/Services/ScheduledMaintenanceService";
|
||||
@@ -276,13 +280,12 @@ import ShortLinkService, {
|
||||
import SmsLogService, {
|
||||
Service as SmsLogServiceType,
|
||||
} from "Common/Server/Services/SmsLogService";
|
||||
import PushNotificationLogService, {
|
||||
Service as PushNotificationLogServiceType,
|
||||
} from "Common/Server/Services/PushNotificationLogService";
|
||||
import SpanService, {
|
||||
SpanService as SpanServiceType,
|
||||
} from "Common/Server/Services/SpanService";
|
||||
import StatusPageAnnouncementAPI from "Common/Server/API/StatusPageAnnouncementAPI";
|
||||
import StatusPageAnnouncementService, {
|
||||
Service as StatusPageAnnouncementServiceType,
|
||||
} from "Common/Server/Services/StatusPageAnnouncementService";
|
||||
import StatusPageCustomFieldService, {
|
||||
Service as StatusPageCustomFieldServiceType,
|
||||
} from "Common/Server/Services/StatusPageCustomFieldService";
|
||||
@@ -319,9 +322,6 @@ import TeamMemberService, {
|
||||
import TeamPermissionService, {
|
||||
Service as TeamPermissionServiceType,
|
||||
} from "Common/Server/Services/TeamPermissionService";
|
||||
import TeamComplianceSettingService, {
|
||||
TeamComplianceSettingService as TeamComplianceSettingServiceType,
|
||||
} from "Common/Server/Services/TeamComplianceSettingService";
|
||||
import TeamService, {
|
||||
Service as TeamServiceType,
|
||||
} from "Common/Server/Services/TeamService";
|
||||
@@ -340,6 +340,9 @@ import UserNotificationSettingService, {
|
||||
import UserOnCallLogService, {
|
||||
Service as UserNotificationLogServiceType,
|
||||
} from "Common/Server/Services/UserOnCallLogService";
|
||||
import UserService, {
|
||||
Service as UserServiceType,
|
||||
} from "Common/Server/Services/UserService";
|
||||
import WorkflowLogService, {
|
||||
Service as WorkflowLogServiceType,
|
||||
} from "Common/Server/Services/WorkflowLogService";
|
||||
@@ -365,7 +368,6 @@ import TelemetryExceptionService, {
|
||||
import ExceptionInstanceService, {
|
||||
ExceptionInstanceService as ExceptionInstanceServiceType,
|
||||
} from "Common/Server/Services/ExceptionInstanceService";
|
||||
import AcmeChallengeAPI from "Common/Server/API/AcmeChallengeAPI";
|
||||
|
||||
import FeatureSet from "Common/Server/Types/FeatureSet";
|
||||
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
|
||||
@@ -375,8 +377,6 @@ import Span from "Common/Models/AnalyticsModels/Span";
|
||||
import ApiKey from "Common/Models/DatabaseModels/ApiKey";
|
||||
import ApiKeyPermission from "Common/Models/DatabaseModels/ApiKeyPermission";
|
||||
import CallLog from "Common/Models/DatabaseModels/CallLog";
|
||||
import PushNotificationLog from "Common/Models/DatabaseModels/PushNotificationLog";
|
||||
import WorkspaceNotificationLog from "Common/Models/DatabaseModels/WorkspaceNotificationLog";
|
||||
import Domain from "Common/Models/DatabaseModels/Domain";
|
||||
import EmailLog from "Common/Models/DatabaseModels/EmailLog";
|
||||
import EmailVerificationToken from "Common/Models/DatabaseModels/EmailVerificationToken";
|
||||
@@ -384,6 +384,7 @@ import Dashboard from "Common/Models/DatabaseModels/Dashboard";
|
||||
|
||||
import Alert from "Common/Models/DatabaseModels/Alert";
|
||||
import AlertCustomField from "Common/Models/DatabaseModels/AlertCustomField";
|
||||
import AlertInternalNote from "Common/Models/DatabaseModels/AlertInternalNote";
|
||||
import AlertNoteTemplate from "Common/Models/DatabaseModels/AlertNoteTemplate";
|
||||
import AlertOwnerTeam from "Common/Models/DatabaseModels/AlertOwnerTeam";
|
||||
import AlertOwnerUser from "Common/Models/DatabaseModels/AlertOwnerUser";
|
||||
@@ -391,11 +392,13 @@ import AlertSeverity from "Common/Models/DatabaseModels/AlertSeverity";
|
||||
import AlertState from "Common/Models/DatabaseModels/AlertState";
|
||||
import AlertStateTimeline from "Common/Models/DatabaseModels/AlertStateTimeline";
|
||||
|
||||
import Incident from "Common/Models/DatabaseModels/Incident";
|
||||
import IncidentCustomField from "Common/Models/DatabaseModels/IncidentCustomField";
|
||||
import IncidentInternalNote from "Common/Models/DatabaseModels/IncidentInternalNote";
|
||||
import IncidentNoteTemplate from "Common/Models/DatabaseModels/IncidentNoteTemplate";
|
||||
import IncidentPostmortemTemplate from "Common/Models/DatabaseModels/IncidentPostmortemTemplate";
|
||||
import IncidentOwnerTeam from "Common/Models/DatabaseModels/IncidentOwnerTeam";
|
||||
import IncidentOwnerUser from "Common/Models/DatabaseModels/IncidentOwnerUser";
|
||||
import IncidentPublicNote from "Common/Models/DatabaseModels/IncidentPublicNote";
|
||||
import IncidentSeverity from "Common/Models/DatabaseModels/IncidentSeverity";
|
||||
import IncidentState from "Common/Models/DatabaseModels/IncidentState";
|
||||
import IncidentStateTimeline from "Common/Models/DatabaseModels/IncidentStateTimeline";
|
||||
@@ -404,6 +407,7 @@ import IncidentTemplateOwnerTeam from "Common/Models/DatabaseModels/IncidentTemp
|
||||
import IncidentTemplateOwnerUser from "Common/Models/DatabaseModels/IncidentTemplateOwnerUser";
|
||||
|
||||
import Label from "Common/Models/DatabaseModels/Label";
|
||||
import Monitor from "Common/Models/DatabaseModels/Monitor";
|
||||
import MonitorCustomField from "Common/Models/DatabaseModels/MonitorCustomField";
|
||||
import MonitorGroupOwnerTeam from "Common/Models/DatabaseModels/MonitorGroupOwnerTeam";
|
||||
import MonitorGroupOwnerUser from "Common/Models/DatabaseModels/MonitorGroupOwnerUser";
|
||||
@@ -414,6 +418,7 @@ import MonitorProbe from "Common/Models/DatabaseModels/MonitorProbe";
|
||||
import MonitorSecret from "Common/Models/DatabaseModels/MonitorSecret";
|
||||
import MonitorStatus from "Common/Models/DatabaseModels/MonitorStatus";
|
||||
import MonitorTimelineStatus from "Common/Models/DatabaseModels/MonitorStatusTimeline";
|
||||
import OnCallDutyPolicy from "Common/Models/DatabaseModels/OnCallDutyPolicy";
|
||||
import OnCallDutyPolicyCustomField from "Common/Models/DatabaseModels/OnCallDutyPolicyCustomField";
|
||||
import OnCallDutyPolicyEscalationRule from "Common/Models/DatabaseModels/OnCallDutyPolicyEscalationRule";
|
||||
import OnCallDutyPolicyEscalationRuleSchedule from "Common/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule";
|
||||
@@ -430,9 +435,11 @@ import PromoCode from "Common/Models/DatabaseModels/PromoCode";
|
||||
import Reseller from "Common/Models/DatabaseModels/Reseller";
|
||||
import ScheduledMaintenance from "Common/Models/DatabaseModels/ScheduledMaintenance";
|
||||
import ScheduledMaintenanceCustomField from "Common/Models/DatabaseModels/ScheduledMaintenanceCustomField";
|
||||
import ScheduledMaintenanceInternalNote from "Common/Models/DatabaseModels/ScheduledMaintenanceInternalNote";
|
||||
import ScheduledMaintenanceNoteTemplate from "Common/Models/DatabaseModels/ScheduledMaintenanceNoteTemplate";
|
||||
import ScheduledMaintenanceOwnerTeam from "Common/Models/DatabaseModels/ScheduledMaintenanceOwnerTeam";
|
||||
import ScheduledMaintenanceOwnerUser from "Common/Models/DatabaseModels/ScheduledMaintenanceOwnerUser";
|
||||
import ScheduledMaintenancePublicNote from "Common/Models/DatabaseModels/ScheduledMaintenancePublicNote";
|
||||
import ScheduledMaintenanceState from "Common/Models/DatabaseModels/ScheduledMaintenanceState";
|
||||
import ScheduledMaintenanceStateTimeline from "Common/Models/DatabaseModels/ScheduledMaintenanceStateTimeline";
|
||||
import ServiceCatalog from "Common/Models/DatabaseModels/ServiceCatalog";
|
||||
@@ -441,6 +448,7 @@ import ServiceCatalogOwnerUser from "Common/Models/DatabaseModels/ServiceCatalog
|
||||
import ServiceCopilotCodeRepository from "Common/Models/DatabaseModels/ServiceCopilotCodeRepository";
|
||||
import ShortLink from "Common/Models/DatabaseModels/ShortLink";
|
||||
import SmsLog from "Common/Models/DatabaseModels/SmsLog";
|
||||
import StatusPageAnnouncement from "Common/Models/DatabaseModels/StatusPageAnnouncement";
|
||||
// Custom Fields API
|
||||
import StatusPageCustomField from "Common/Models/DatabaseModels/StatusPageCustomField";
|
||||
import StatusPageFooterLink from "Common/Models/DatabaseModels/StatusPageFooterLink";
|
||||
@@ -456,9 +464,9 @@ import StatusPageSSO from "Common/Models/DatabaseModels/StatusPageSso";
|
||||
import Team from "Common/Models/DatabaseModels/Team";
|
||||
import TeamMember from "Common/Models/DatabaseModels/TeamMember";
|
||||
import TeamPermission from "Common/Models/DatabaseModels/TeamPermission";
|
||||
import TeamComplianceSetting from "Common/Models/DatabaseModels/TeamComplianceSetting";
|
||||
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
|
||||
import TelemetryUsageBilling from "Common/Models/DatabaseModels/TelemetryUsageBilling";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import UserNotificationRule from "Common/Models/DatabaseModels/UserNotificationRule";
|
||||
import UserNotificationSetting from "Common/Models/DatabaseModels/UserNotificationSetting";
|
||||
import UserOnCallLog from "Common/Models/DatabaseModels/UserOnCallLog";
|
||||
@@ -468,12 +476,10 @@ import WorkflowVariable from "Common/Models/DatabaseModels/WorkflowVariable";
|
||||
import ProbeOwnerTeam from "Common/Models/DatabaseModels/ProbeOwnerTeam";
|
||||
import ProbeOwnerUser from "Common/Models/DatabaseModels/ProbeOwnerUser";
|
||||
import ServiceCatalogDependency from "Common/Models/DatabaseModels/ServiceCatalogDependency";
|
||||
import TelemetryAttribute from "Common/Models/AnalyticsModels/TelemetryAttribute";
|
||||
import ExceptionInstance from "Common/Models/AnalyticsModels/ExceptionInstance";
|
||||
import TelemetyException from "Common/Models/DatabaseModels/TelemetryException";
|
||||
import CopilotActionTypePriority from "Common/Models/DatabaseModels/CopilotActionTypePriority";
|
||||
import WorkspaceNotificationLogService, {
|
||||
Service as WorkspaceNotificationLogServiceType,
|
||||
} from "Common/Server/Services/WorkspaceNotificationLogService";
|
||||
|
||||
// scheduled maintenance template
|
||||
import ScheduledMaintenanceTemplate from "Common/Models/DatabaseModels/ScheduledMaintenanceTemplate";
|
||||
@@ -507,7 +513,6 @@ import ScheduledMaintenanceFeedService, {
|
||||
} from "Common/Server/Services/ScheduledMaintenanceFeedService";
|
||||
|
||||
import SlackAPI from "Common/Server/API/SlackAPI";
|
||||
import MicrosoftTeamsAPI from "Common/Server/API/MicrosoftTeamsAPI";
|
||||
|
||||
import WorkspaceProjectAuthToken from "Common/Models/DatabaseModels/WorkspaceProjectAuthToken";
|
||||
import WorkspaceProjectAuthTokenService, {
|
||||
@@ -525,65 +530,10 @@ import WorkspaceSettingService, {
|
||||
Service as WorkspaceSettingServiceType,
|
||||
} from "Common/Server/Services/WorkspaceSettingService";
|
||||
|
||||
import MonitorFeed from "Common/Models/DatabaseModels/MonitorFeed";
|
||||
import MonitorFeedService, {
|
||||
Service as MonitorFeedServiceType,
|
||||
} from "Common/Server/Services/MonitorFeedService";
|
||||
|
||||
// MetricType.
|
||||
import MetricTypeService, {
|
||||
Service as MetricTypeServiceType,
|
||||
} from "Common/Server/Services/MetricTypeService";
|
||||
import MetricType from "Common/Models/DatabaseModels/MetricType";
|
||||
|
||||
import OnCallDutyPolicyAPI from "Common/Server/API/OnCallDutyPolicyAPI";
|
||||
import TeamComplianceAPI from "Common/Server/API/TeamComplianceAPI";
|
||||
|
||||
import OnCallDutyPolicyFeed from "Common/Models/DatabaseModels/OnCallDutyPolicyFeed";
|
||||
import OnCallDutyPolicyFeedService, {
|
||||
Service as OnCallDutyPolicyFeedServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyFeedService";
|
||||
|
||||
import OnCallDutyPolicyOwnerTeam from "Common/Models/DatabaseModels/OnCallDutyPolicyOwnerTeam";
|
||||
import OnCallDutyPolicyOwnerTeamService, {
|
||||
Service as OnCallDutyPolicyOwnerTeamServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyOwnerTeamService";
|
||||
|
||||
import OnCallDutyPolicyOwnerUser from "Common/Models/DatabaseModels/OnCallDutyPolicyOwnerUser";
|
||||
import OnCallDutyPolicyOwnerUserService, {
|
||||
Service as OnCallDutyPolicyOwnerUserServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyOwnerUserService";
|
||||
import MonitorLog from "Common/Models/AnalyticsModels/MonitorLog";
|
||||
import MonitorLogService, {
|
||||
Service as MonitorLogServiceType,
|
||||
} from "Common/Server/Services/MonitorLogService";
|
||||
|
||||
//OnCallDutyPolicyTimeLog
|
||||
import OnCallDutyPolicyTimeLog from "Common/Models/DatabaseModels/OnCallDutyPolicyTimeLog";
|
||||
import OnCallDutyPolicyTimeLogService, {
|
||||
Service as OnCallDutyPolicyTimeLogServiceType,
|
||||
} from "Common/Server/Services/OnCallDutyPolicyTimeLogService";
|
||||
|
||||
// statu spage announcement templates
|
||||
import StatusPageAnnouncementTemplate from "Common/Models/DatabaseModels/StatusPageAnnouncementTemplate";
|
||||
import StatusPageAnnouncementTemplateService, {
|
||||
Service as StatusPageAnnouncementTemplateServiceType,
|
||||
} from "Common/Server/Services/StatusPageAnnouncementTemplateService";
|
||||
|
||||
// ProjectSCIM
|
||||
import ProjectSCIM from "Common/Models/DatabaseModels/ProjectSCIM";
|
||||
import ProjectSCIMService, {
|
||||
Service as ProjectSCIMServiceType,
|
||||
} from "Common/Server/Services/ProjectSCIMService";
|
||||
|
||||
// StatusPageSCIM
|
||||
import StatusPageSCIM from "Common/Models/DatabaseModels/StatusPageSCIM";
|
||||
import StatusPageSCIMService, {
|
||||
Service as StatusPageSCIMServiceType,
|
||||
} from "Common/Server/Services/StatusPageSCIMService";
|
||||
|
||||
// Open API Spec
|
||||
import OpenAPI from "Common/Server/API/OpenAPI";
|
||||
import WorkspaceNotificationRule from "Common/Models/DatabaseModels/WorkspaceNotificationRule";
|
||||
import WorkspaceNotificationRuleService, {
|
||||
Service as WorkspaceNotificationRuleServiceType,
|
||||
} from "Common/Server/Services/WorkspaceNotificationRuleService";
|
||||
|
||||
const BaseAPIFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
@@ -593,16 +543,9 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new AcmeChallengeAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, OpenAPI.getRouter());
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAnalyticsAPI<MonitorLog, MonitorLogServiceType>(
|
||||
MonitorLog,
|
||||
MonitorLogService,
|
||||
new BaseAnalyticsAPI<TelemetryAttribute, TelemetryAttributeServiceType>(
|
||||
TelemetryAttribute,
|
||||
TelemetryAttributeService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
@@ -614,93 +557,16 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// Project SCIM
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<ProjectSCIM, ProjectSCIMServiceType>(
|
||||
ProjectSCIM,
|
||||
ProjectSCIMService,
|
||||
).getRouter(),
|
||||
);
|
||||
// notification rule
|
||||
|
||||
// Status Page SCIM
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<StatusPageSCIM, StatusPageSCIMServiceType>(
|
||||
StatusPageSCIM,
|
||||
StatusPageSCIMService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// status page announcement templates
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
StatusPageAnnouncementTemplate,
|
||||
StatusPageAnnouncementTemplateServiceType
|
||||
WorkspaceNotificationRule,
|
||||
WorkspaceNotificationRuleServiceType
|
||||
>(
|
||||
StatusPageAnnouncementTemplate,
|
||||
StatusPageAnnouncementTemplateService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// OnCallDutyPolicyTimeLogService
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<OnCallDutyPolicyTimeLog, OnCallDutyPolicyTimeLogServiceType>(
|
||||
OnCallDutyPolicyTimeLog,
|
||||
OnCallDutyPolicyTimeLogService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// on-call policy owner user.
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
OnCallDutyPolicyOwnerUser,
|
||||
OnCallDutyPolicyOwnerUserServiceType
|
||||
>(
|
||||
OnCallDutyPolicyOwnerUser,
|
||||
OnCallDutyPolicyOwnerUserService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// on-call policy owner team.
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
OnCallDutyPolicyOwnerTeam,
|
||||
OnCallDutyPolicyOwnerTeamServiceType
|
||||
>(
|
||||
OnCallDutyPolicyOwnerTeam,
|
||||
OnCallDutyPolicyOwnerTeamService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// on-call policy feed.
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<OnCallDutyPolicyFeed, OnCallDutyPolicyFeedServiceType>(
|
||||
OnCallDutyPolicyFeed,
|
||||
OnCallDutyPolicyFeedService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// monitor feed
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<MonitorFeed, MonitorFeedServiceType>(
|
||||
MonitorFeed,
|
||||
MonitorFeedService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// MetricType
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<MetricType, MetricTypeServiceType>(
|
||||
MetricType,
|
||||
MetricTypeService,
|
||||
WorkspaceNotificationRule,
|
||||
WorkspaceNotificationRuleService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
@@ -729,18 +595,6 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// user override
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
OnCallDutyPolicyUserOverride,
|
||||
OnCallDutyPolicyUserOverrideServiceType
|
||||
>(
|
||||
OnCallDutyPolicyUserOverride,
|
||||
OnCallDutyPolicyUserOverrideService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AlertFeed, AlertFeedServiceType>(
|
||||
@@ -824,7 +678,10 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new AlertInternalNoteAPI().getRouter(),
|
||||
new BaseAPI<AlertInternalNote, AlertInternalNoteServiceType>(
|
||||
AlertInternalNote,
|
||||
AlertInternalNoteService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
@@ -1021,7 +878,10 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new StatusPageAnnouncementAPI().getRouter(),
|
||||
new BaseAPI<StatusPageAnnouncement, StatusPageAnnouncementServiceType>(
|
||||
StatusPageAnnouncement,
|
||||
StatusPageAnnouncementService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
@@ -1142,14 +1002,6 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<TeamComplianceSetting, TeamComplianceSettingServiceType>(
|
||||
TeamComplianceSetting,
|
||||
TeamComplianceSettingService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<MonitorStatus, MonitorStatusServiceType>(
|
||||
@@ -1265,7 +1117,13 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new IncidentAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<Incident, IncidentServiceType>(
|
||||
Incident,
|
||||
IncidentService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
@@ -1363,17 +1221,6 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncidentPostmortemTemplate,
|
||||
IncidentPostmortemTemplateServiceType
|
||||
>(
|
||||
IncidentPostmortemTemplate,
|
||||
IncidentPostmortemTemplateService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
@@ -1511,28 +1358,15 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<SmsLog, SmsLogServiceType>(SmsLog, SmsLogService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new WhatsAppLogAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<PushNotificationLog, PushNotificationLogServiceType>(
|
||||
PushNotificationLog,
|
||||
PushNotificationLogService,
|
||||
new BaseAPI<Monitor, MonitorServiceType>(
|
||||
Monitor,
|
||||
MonitorService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
WorkspaceNotificationLog,
|
||||
WorkspaceNotificationLogServiceType
|
||||
>(WorkspaceNotificationLog, WorkspaceNotificationLogService).getRouter(),
|
||||
new BaseAPI<SmsLog, SmsLogServiceType>(SmsLog, SmsLogService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
@@ -1574,29 +1408,12 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
MonitorTimelineStatusService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new ShortLinkAPI().getRouter());
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new MonitorAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new StatusPageAPI().getRouter(),
|
||||
);
|
||||
|
||||
// OnCallDutyPolicyAPI
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new OnCallDutyPolicyAPI().getRouter(),
|
||||
);
|
||||
|
||||
// TeamComplianceAPI
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new TeamComplianceAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new WorkspaceNotificationRuleAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new FileAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
@@ -1616,15 +1433,7 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new ResellerPlanAPI().getRouter(),
|
||||
);
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new EnterpriseLicenseAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new SlackAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new MicrosoftTeamsAPI().getRouter(),
|
||||
);
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new GlobalConfigAPI().getRouter(),
|
||||
@@ -1652,19 +1461,10 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserCallAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new UserTotpAuthAPI().getRouter(),
|
||||
);
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new UserWebAuthnAPI().getRouter(),
|
||||
new UserTwoFactorAuthAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserEmailAPI().getRouter());
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserSMSAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new UserWhatsAppAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserPushAPI().getRouter());
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new ProbeAPI().getRouter());
|
||||
|
||||
app.use(
|
||||
@@ -1684,26 +1484,50 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
new BillingInvoiceAPI().getRouter(),
|
||||
);
|
||||
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new BillingAPI().getRouter());
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new ScheduledMaintenancePublicNoteAPI().getRouter(),
|
||||
new BaseAPI<
|
||||
ScheduledMaintenancePublicNote,
|
||||
ScheduledMaintenancePublicNoteServiceType
|
||||
>(
|
||||
ScheduledMaintenancePublicNote,
|
||||
ScheduledMaintenancePublicNoteService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new ScheduledMaintenanceInternalNoteAPI().getRouter(),
|
||||
new BaseAPI<
|
||||
ScheduledMaintenanceInternalNote,
|
||||
ScheduledMaintenanceInternalNoteServiceType
|
||||
>(
|
||||
ScheduledMaintenanceInternalNote,
|
||||
ScheduledMaintenanceInternalNoteService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new IncidentPublicNoteAPI().getRouter(),
|
||||
new BaseAPI<IncidentPublicNote, IncidentPublicNoteServiceType>(
|
||||
IncidentPublicNote,
|
||||
IncidentPublicNoteService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new IncidentInternalNoteAPI().getRouter(),
|
||||
new BaseAPI<IncidentInternalNote, IncidentInternalNoteServiceType>(
|
||||
IncidentInternalNote,
|
||||
IncidentInternalNoteService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<OnCallDutyPolicy, OnCallDutyPolicyServiceType>(
|
||||
OnCallDutyPolicy,
|
||||
OnCallDutyPolicyService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
@@ -1820,7 +1644,10 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, TelemetryAPI);
|
||||
|
||||
//attach api's
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<User, UserServiceType>(User, UserService).getRouter(),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import BaseAPI from "Common/Server/API/BaseAPI";
|
||||
import WhatsAppLog from "Common/Models/DatabaseModels/WhatsAppLog";
|
||||
import WhatsAppLogService, {
|
||||
Service as WhatsAppLogServiceType,
|
||||
} from "Common/Server/Services/WhatsAppLogService";
|
||||
|
||||
export default class WhatsAppLogAPI extends BaseAPI<
|
||||
WhatsAppLog,
|
||||
WhatsAppLogServiceType
|
||||
> {
|
||||
public constructor() {
|
||||
super(WhatsAppLog, WhatsAppLogService);
|
||||
}
|
||||
}
|
||||
@@ -24,72 +24,23 @@ import AccessTokenService from "Common/Server/Services/AccessTokenService";
|
||||
import EmailVerificationTokenService from "Common/Server/Services/EmailVerificationTokenService";
|
||||
import MailService from "Common/Server/Services/MailService";
|
||||
import UserService from "Common/Server/Services/UserService";
|
||||
import UserTotpAuthService from "Common/Server/Services/UserTotpAuthService";
|
||||
import UserSessionService, {
|
||||
SessionMetadata,
|
||||
} from "Common/Server/Services/UserSessionService";
|
||||
import UserTwoFactorAuthService from "Common/Server/Services/UserTwoFactorAuthService";
|
||||
import CookieUtil from "Common/Server/Utils/Cookie";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
extractDeviceInfo,
|
||||
getClientIp,
|
||||
headerValueToString,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import CaptchaUtil from "Common/Server/Utils/Captcha";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import TotpAuth from "Common/Server/Utils/TotpAuth";
|
||||
import TwoFactorAuth from "Common/Server/Utils/TwoFactorAuth";
|
||||
import EmailVerificationToken from "Common/Models/DatabaseModels/EmailVerificationToken";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import UserSession from "Common/Models/DatabaseModels/UserSession";
|
||||
import UserTotpAuth from "Common/Models/DatabaseModels/UserTotpAuth";
|
||||
import UserWebAuthn from "Common/Models/DatabaseModels/UserWebAuthn";
|
||||
import UserWebAuthnService from "Common/Server/Services/UserWebAuthnService";
|
||||
import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException";
|
||||
import UserTwoFactorAuth from "Common/Models/DatabaseModels/UserTwoFactorAuth";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;
|
||||
|
||||
type FinalizeUserLoginInput = {
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
user: User;
|
||||
isGlobalLogin: boolean;
|
||||
};
|
||||
|
||||
const finalizeUserLogin: (
|
||||
data: FinalizeUserLoginInput,
|
||||
) => Promise<SessionMetadata> = async (
|
||||
data: FinalizeUserLoginInput,
|
||||
): Promise<SessionMetadata> => {
|
||||
const { req, res, user, isGlobalLogin } = data;
|
||||
|
||||
const sessionMetadata: SessionMetadata =
|
||||
await UserSessionService.createSession({
|
||||
userId: user.id!,
|
||||
isGlobalLogin,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
});
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user,
|
||||
isGlobalLogin,
|
||||
sessionId: sessionMetadata.session.id!,
|
||||
refreshToken: sessionMetadata.refreshToken,
|
||||
refreshTokenExpiresAt: sessionMetadata.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return sessionMetadata;
|
||||
};
|
||||
|
||||
router.post(
|
||||
"/signup",
|
||||
async (
|
||||
@@ -108,16 +59,6 @@ router.post(
|
||||
);
|
||||
}
|
||||
|
||||
const miscDataProps: JSONObject =
|
||||
(req.body["miscDataProps"] as JSONObject) || {};
|
||||
|
||||
await CaptchaUtil.verifyCaptcha({
|
||||
token:
|
||||
(miscDataProps["captchaToken"] as string | undefined) ||
|
||||
(req.body["captchaToken"] as string | undefined),
|
||||
remoteIp: getClientIp(req) || null,
|
||||
});
|
||||
|
||||
const data: JSONObject = req.body["data"];
|
||||
|
||||
/* Creating a type that is a partial of the TBaseModel type. */
|
||||
@@ -242,9 +183,9 @@ router.post(
|
||||
if (savedUser) {
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(savedUser.id!);
|
||||
await finalizeUserLogin({
|
||||
req,
|
||||
res,
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user: savedUser,
|
||||
isGlobalLogin: true,
|
||||
});
|
||||
@@ -544,127 +485,6 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/refresh-token",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req);
|
||||
|
||||
if (!refreshToken) {
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException(
|
||||
"Refresh token missing. Please login again.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const session: UserSession | null =
|
||||
await UserSessionService.findActiveSessionByRefreshToken(refreshToken);
|
||||
|
||||
if (!session || !session.id) {
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
session.refreshTokenExpiresAt &&
|
||||
OneUptimeDate.hasExpired(session.refreshTokenExpiresAt)
|
||||
) {
|
||||
await UserSessionService.revokeSessionById(session.id, {
|
||||
reason: "Refresh token expired",
|
||||
});
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (!session.userId) {
|
||||
await UserSessionService.revokeSessionById(session.id, {
|
||||
reason: "Session missing user",
|
||||
});
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
const user: User | null = await UserService.findOneById({
|
||||
id: session.userId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
isMasterAdmin: true,
|
||||
profilePictureId: true,
|
||||
timezone: true,
|
||||
enableTwoFactorAuth: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
await UserSessionService.revokeSessionById(session.id, {
|
||||
reason: "User not found",
|
||||
});
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Account no longer exists."),
|
||||
);
|
||||
}
|
||||
|
||||
const additionalInfo: JSONObject = (session.additionalInfo ||
|
||||
{}) as JSONObject;
|
||||
const isGlobalLogin: boolean =
|
||||
typeof additionalInfo["isGlobalLogin"] === "boolean"
|
||||
? (additionalInfo["isGlobalLogin"] as boolean)
|
||||
: true;
|
||||
|
||||
const renewedSession: SessionMetadata =
|
||||
await UserSessionService.renewSessionWithNewRefreshToken({
|
||||
session,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
});
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user,
|
||||
isGlobalLogin,
|
||||
sessionId: renewedSession.session.id!,
|
||||
refreshToken: renewedSession.refreshToken,
|
||||
refreshTokenExpiresAt: renewedSession.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/logout",
|
||||
async (
|
||||
@@ -673,15 +493,6 @@ router.post(
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req);
|
||||
|
||||
if (refreshToken) {
|
||||
await UserSessionService.revokeSessionByRefreshToken(refreshToken, {
|
||||
reason: "User logout",
|
||||
});
|
||||
}
|
||||
|
||||
CookieUtil.removeAllCookies(req, res);
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
@@ -692,7 +503,7 @@ router.post(
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/verify-totp-auth",
|
||||
"/verify-two-factor-auth",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
@@ -702,25 +513,7 @@ router.post(
|
||||
req: req,
|
||||
res: res,
|
||||
next: next,
|
||||
verifyTotpAuth: true,
|
||||
verifyWebAuthn: false,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/verify-webauthn-auth",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
return login({
|
||||
req: req,
|
||||
res: res,
|
||||
next: next,
|
||||
verifyTotpAuth: false,
|
||||
verifyWebAuthn: true,
|
||||
verifyTwoFactorAuth: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -736,111 +529,62 @@ router.post(
|
||||
req: req,
|
||||
res: res,
|
||||
next: next,
|
||||
verifyTotpAuth: false,
|
||||
verifyWebAuthn: false,
|
||||
verifyTwoFactorAuth: false,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
type FetchTotpAuthListFunction = (userId: ObjectID) => Promise<{
|
||||
totpAuthList: Array<UserTotpAuth>;
|
||||
webAuthnList: Array<UserWebAuthn>;
|
||||
}>;
|
||||
|
||||
const fetchTotpAuthList: FetchTotpAuthListFunction = async (
|
||||
type FetchTwoFactorAuthListFunction = (
|
||||
userId: ObjectID,
|
||||
): Promise<{
|
||||
totpAuthList: Array<UserTotpAuth>;
|
||||
webAuthnList: Array<UserWebAuthn>;
|
||||
}> => {
|
||||
const totpAuthList: Array<UserTotpAuth> = await UserTotpAuthService.findBy({
|
||||
query: {
|
||||
userId: userId,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
userId: true,
|
||||
name: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
) => Promise<Array<UserTwoFactorAuth>>;
|
||||
|
||||
const webAuthnList: Array<UserWebAuthn> = await UserWebAuthnService.findBy({
|
||||
query: {
|
||||
userId: userId,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
userId: true,
|
||||
name: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
const fetchTwoFactorAuthList: FetchTwoFactorAuthListFunction = async (
|
||||
userId: ObjectID,
|
||||
): Promise<Array<UserTwoFactorAuth>> => {
|
||||
const twoFactorAuthList: Array<UserTwoFactorAuth> =
|
||||
await UserTwoFactorAuthService.findBy({
|
||||
query: {
|
||||
userId: userId,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
userId: true,
|
||||
name: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
totpAuthList: totpAuthList || [],
|
||||
webAuthnList: webAuthnList || [],
|
||||
};
|
||||
return twoFactorAuthList;
|
||||
};
|
||||
|
||||
type LoginFunction = (options: {
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
next: NextFunction;
|
||||
verifyTotpAuth: boolean;
|
||||
verifyWebAuthn: boolean;
|
||||
verifyTwoFactorAuth: boolean;
|
||||
}) => Promise<void>;
|
||||
|
||||
const login: LoginFunction = async (options: {
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
next: NextFunction;
|
||||
verifyTotpAuth: boolean;
|
||||
verifyWebAuthn: boolean;
|
||||
verifyTwoFactorAuth: boolean;
|
||||
}): Promise<void> => {
|
||||
const req: ExpressRequest = options.req;
|
||||
const res: ExpressResponse = options.res;
|
||||
const next: NextFunction = options.next;
|
||||
const verifyTotpAuth: boolean = options.verifyTotpAuth;
|
||||
const verifyWebAuthn: boolean = options.verifyWebAuthn;
|
||||
const verifyTwoFactorAuth: boolean = options.verifyTwoFactorAuth;
|
||||
|
||||
try {
|
||||
const miscDataProps: JSONObject =
|
||||
(req.body["miscDataProps"] as JSONObject) || {};
|
||||
|
||||
if (!verifyTotpAuth && !verifyWebAuthn) {
|
||||
await CaptchaUtil.verifyCaptcha({
|
||||
token:
|
||||
(miscDataProps["captchaToken"] as string | undefined) ||
|
||||
(req.body["captchaToken"] as string | undefined),
|
||||
remoteIp: getClientIp(req) || null,
|
||||
});
|
||||
}
|
||||
|
||||
const data: JSONObject = req.body["data"];
|
||||
|
||||
logger.debug("Login request data: " + JSON.stringify(req.body, null, 2));
|
||||
|
||||
const user: User = BaseModel.fromJSON(data as JSONObject, User) as User;
|
||||
|
||||
if (!user.email || !user.password) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Email and password are required."),
|
||||
);
|
||||
}
|
||||
|
||||
await user.password?.hashValue(EncryptionSecret);
|
||||
|
||||
const alreadySavedUser: User | null = await UserService.findOneBy({
|
||||
@@ -894,21 +638,13 @@ const login: LoginFunction = async (options: {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
alreadySavedUser.enableTwoFactorAuth &&
|
||||
!verifyTotpAuth &&
|
||||
!verifyWebAuthn
|
||||
) {
|
||||
if (alreadySavedUser.enableTwoFactorAuth && !verifyTwoFactorAuth) {
|
||||
// If two factor auth is enabled then we will send the user to the two factor auth page.
|
||||
|
||||
const { totpAuthList, webAuthnList } = await fetchTotpAuthList(
|
||||
alreadySavedUser.id!,
|
||||
);
|
||||
const twoFactorAuthList: Array<UserTwoFactorAuth> =
|
||||
await fetchTwoFactorAuthList(alreadySavedUser.id!);
|
||||
|
||||
if (
|
||||
(!totpAuthList || totpAuthList.length === 0) &&
|
||||
(!webAuthnList || webAuthnList.length === 0)
|
||||
) {
|
||||
if (!twoFactorAuthList || twoFactorAuthList.length === 0) {
|
||||
const errorMessage: string = IsBillingEnabled
|
||||
? "Two Factor Authentication is enabled but no two factor auth is setup. Please contact OneUptime support for help."
|
||||
: "Two Factor Authentication is enabled but no two factor auth is setup. Please contact your server admin to disable two factor auth for this account.";
|
||||
@@ -920,76 +656,69 @@ const login: LoginFunction = async (options: {
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEntityResponse(req, res, alreadySavedUser, User, {
|
||||
return Response.sendEntityResponse(req, res, user, User, {
|
||||
miscData: {
|
||||
totpAuthList: UserTotpAuth.toJSONArray(totpAuthList, UserTotpAuth),
|
||||
webAuthnList: UserWebAuthn.toJSONArray(webAuthnList, UserWebAuthn),
|
||||
twoFactorAuthList: UserTwoFactorAuth.toJSONArray(
|
||||
twoFactorAuthList,
|
||||
UserTwoFactorAuth,
|
||||
),
|
||||
twoFactorAuth: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (verifyTotpAuth || verifyWebAuthn) {
|
||||
if (verifyTotpAuth) {
|
||||
// code from req
|
||||
const code: string = data["code"] as string;
|
||||
const twoFactorAuthId: string = data["twoFactorAuthId"] as string;
|
||||
if (verifyTwoFactorAuth) {
|
||||
// code from req
|
||||
const code: string = data["code"] as string;
|
||||
const twoFactorAuthId: string = data["twoFactorAuthId"] as string;
|
||||
|
||||
const totpAuth: UserTotpAuth | null =
|
||||
await UserTotpAuthService.findOneBy({
|
||||
query: {
|
||||
_id: twoFactorAuthId,
|
||||
userId: alreadySavedUser.id!,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twoFactorSecret: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!totpAuth) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Invalid two factor auth id."),
|
||||
);
|
||||
}
|
||||
|
||||
const isVerified: boolean = TotpAuth.verifyToken({
|
||||
token: code,
|
||||
secret: totpAuth.twoFactorSecret!,
|
||||
email: alreadySavedUser.email!,
|
||||
const twoFactorAuth: UserTwoFactorAuth | null =
|
||||
await UserTwoFactorAuthService.findOneBy({
|
||||
query: {
|
||||
_id: twoFactorAuthId,
|
||||
userId: alreadySavedUser.id!,
|
||||
isVerified: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twoFactorSecret: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isVerified) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Invalid code."),
|
||||
);
|
||||
}
|
||||
} else if (verifyWebAuthn) {
|
||||
const expectedChallenge: string = data["challenge"] as string;
|
||||
const credential: any = data["credential"];
|
||||
|
||||
await UserWebAuthnService.verifyAuthentication({
|
||||
userId: alreadySavedUser.id!.toString(),
|
||||
challenge: expectedChallenge,
|
||||
credential: credential,
|
||||
});
|
||||
if (!twoFactorAuth) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Invalid two factor auth id."),
|
||||
);
|
||||
}
|
||||
} // Refresh Permissions for this user here.
|
||||
|
||||
const isVerified: boolean = TwoFactorAuth.verifyToken({
|
||||
token: code,
|
||||
secret: twoFactorAuth.twoFactorSecret!,
|
||||
email: alreadySavedUser.email!,
|
||||
});
|
||||
|
||||
if (!isVerified) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("Invalid code."),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(alreadySavedUser.id!);
|
||||
|
||||
if (alreadySavedUser.password.toString() === user.password!.toString()) {
|
||||
logger.info("User logged in: " + alreadySavedUser.email?.toString());
|
||||
|
||||
await finalizeUserLogin({
|
||||
req,
|
||||
res,
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user: alreadySavedUser,
|
||||
isGlobalLogin: true,
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,9 +20,6 @@ import AccessTokenService from "Common/Server/Services/AccessTokenService";
|
||||
import ProjectSSOService from "Common/Server/Services/ProjectSsoService";
|
||||
import TeamMemberService from "Common/Server/Services/TeamMemberService";
|
||||
import UserService from "Common/Server/Services/UserService";
|
||||
import UserSessionService, {
|
||||
SessionMetadata,
|
||||
} from "Common/Server/Services/UserSessionService";
|
||||
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
|
||||
import Select from "Common/Server/Types/Database/Select";
|
||||
import CookieUtil from "Common/Server/Utils/Cookie";
|
||||
@@ -31,9 +28,6 @@ import Express, {
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
extractDeviceInfo,
|
||||
getClientIp,
|
||||
headerValueToString,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
@@ -46,20 +40,12 @@ import Name from "Common/Types/Name";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;
|
||||
|
||||
/*
|
||||
* This route is used to get the SSO config for the user.
|
||||
* when the user logs in from OneUptime and not from the IDP.
|
||||
*/
|
||||
// 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,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
try {
|
||||
if (!req.query["email"]) {
|
||||
return Response.sendErrorResponse(
|
||||
@@ -164,11 +150,7 @@ router.get(
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
||||
if (err instanceof Exception) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return next(new ServerException());
|
||||
Response.sendErrorResponse(req, res, err as Exception);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -178,7 +160,7 @@ router.get(
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
_next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!req.params["projectId"]) {
|
||||
@@ -254,42 +236,22 @@ router.get(
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
||||
if (err instanceof Exception) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return next(new ServerException());
|
||||
Response.sendErrorResponse(req, res, err as Exception);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/idp-login/:projectId/:projectSsoId",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
await loginUserWithSso(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
return await loginUserWithSso(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/idp-login/:projectId/:projectSsoId",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
await loginUserWithSso(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
return await loginUserWithSso(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -472,10 +434,8 @@ const loginUserWithSso: LoginUserWithSsoFunction = async (
|
||||
isNewUser = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* If he does not then add him to teams that he should belong and log in.
|
||||
* This should never happen because email is verified before he logs in with SSO.
|
||||
*/
|
||||
// If he does not then add him to teams that he should belong and log in.
|
||||
// This should never happen because email is verified before he logs in with SSO.
|
||||
if (!alreadySavedUser.isEmailVerified && !isNewUser) {
|
||||
await AuthenticationEmail.sendVerificationEmail(alreadySavedUser!);
|
||||
|
||||
@@ -547,31 +507,15 @@ const loginUserWithSso: LoginUserWithSsoFunction = async (
|
||||
expressResponse: res,
|
||||
});
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(alreadySavedUser.id!);
|
||||
|
||||
const sessionMetadata: SessionMetadata =
|
||||
await UserSessionService.createSession({
|
||||
userId: alreadySavedUser.id!,
|
||||
isGlobalLogin: false,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
additionalInfo: {
|
||||
projectId: projectId.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user: alreadySavedUser,
|
||||
isGlobalLogin: false,
|
||||
sessionId: sessionMetadata.session.id!,
|
||||
refreshToken: sessionMetadata.refreshToken,
|
||||
refreshTokenExpiresAt: sessionMetadata.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(alreadySavedUser.id!);
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
|
||||
@@ -1,145 +1,35 @@
|
||||
import BaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
||||
import { StatusPageApiRoute } from "Common/ServiceRoute";
|
||||
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 OneUptimeDate from "Common/Types/Date";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PositiveNumber from "Common/Types/PositiveNumber";
|
||||
import DatabaseConfig from "Common/Server/DatabaseConfig";
|
||||
import { EncryptionSecret } from "Common/Server/EnvironmentConfig";
|
||||
import MailService from "Common/Server/Services/MailService";
|
||||
import StatusPagePrivateUserService from "Common/Server/Services/StatusPagePrivateUserService";
|
||||
import StatusPageService from "Common/Server/Services/StatusPageService";
|
||||
import StatusPagePrivateUserSessionService, {
|
||||
SessionMetadata as StatusPageSessionMetadata,
|
||||
} from "Common/Server/Services/StatusPagePrivateUserSessionService";
|
||||
import CookieUtil from "Common/Server/Utils/Cookie";
|
||||
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
extractDeviceInfo,
|
||||
getClientIp,
|
||||
headerValueToString,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import StatusPage from "Common/Models/DatabaseModels/StatusPage";
|
||||
import StatusPagePrivateUser from "Common/Models/DatabaseModels/StatusPagePrivateUser";
|
||||
import StatusPagePrivateUserSession from "Common/Models/DatabaseModels/StatusPagePrivateUserSession";
|
||||
import { MASTER_PASSWORD_COOKIE_IDENTIFIER } from "Common/Types/StatusPage/MasterPassword";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;
|
||||
|
||||
type MasterPasswordAuthInput = {
|
||||
req: ExpressRequest;
|
||||
res?: ExpressResponse;
|
||||
statusPageId: ObjectID;
|
||||
};
|
||||
|
||||
const hasValidMasterPasswordSession: (
|
||||
data: MasterPasswordAuthInput,
|
||||
) => boolean = (data: MasterPasswordAuthInput): boolean => {
|
||||
const token: string | undefined = CookieUtil.getCookieFromExpressRequest(
|
||||
data.req,
|
||||
CookieUtil.getStatusPageMasterPasswordKey(data.statusPageId),
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload: JSONObject = JSONWebToken.decodeJsonPayload(token);
|
||||
|
||||
return (
|
||||
payload["statusPageId"] === data.statusPageId.toString() &&
|
||||
payload["type"] === MASTER_PASSWORD_COOKIE_IDENTIFIER
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const respondWithMasterPasswordAccess: (
|
||||
data: MasterPasswordAuthInput & { res: ExpressResponse },
|
||||
) => boolean = (
|
||||
data: MasterPasswordAuthInput & { res: ExpressResponse },
|
||||
): boolean => {
|
||||
if (!hasValidMasterPasswordSession(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Response.sendEmptySuccessResponse(data.req, data.res);
|
||||
return true;
|
||||
};
|
||||
|
||||
type FinalizeStatusPageLoginInput = {
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
user: StatusPagePrivateUser;
|
||||
};
|
||||
|
||||
const finalizeStatusPageLogin: (data: FinalizeStatusPageLoginInput) => Promise<{
|
||||
sessionMetadata: StatusPageSessionMetadata;
|
||||
accessToken: string;
|
||||
}> = async (
|
||||
data: FinalizeStatusPageLoginInput,
|
||||
): Promise<{
|
||||
sessionMetadata: StatusPageSessionMetadata;
|
||||
accessToken: string;
|
||||
}> => {
|
||||
const { req, res, user } = data;
|
||||
|
||||
if (!user.projectId) {
|
||||
throw new BadDataException(
|
||||
"Status page user is missing associated projectId.",
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.statusPageId) {
|
||||
throw new BadDataException(
|
||||
"Status page user is missing associated statusPageId.",
|
||||
);
|
||||
}
|
||||
|
||||
const sessionMetadata: StatusPageSessionMetadata =
|
||||
await StatusPagePrivateUserSessionService.createSession({
|
||||
projectId: user.projectId,
|
||||
statusPageId: user.statusPageId,
|
||||
statusPagePrivateUserId: user.id!,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
});
|
||||
|
||||
const accessToken: string = CookieUtil.setStatusPagePrivateUserCookie({
|
||||
expressResponse: res,
|
||||
user,
|
||||
statusPageId: user.statusPageId,
|
||||
sessionId: sessionMetadata.session.id!,
|
||||
refreshToken: sessionMetadata.refreshToken,
|
||||
refreshTokenExpiresAt: sessionMetadata.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return {
|
||||
sessionMetadata,
|
||||
accessToken,
|
||||
};
|
||||
};
|
||||
|
||||
router.post(
|
||||
"/logout/:statuspageid",
|
||||
async (
|
||||
@@ -156,21 +46,7 @@ router.post(
|
||||
req.params["statuspageid"].toString(),
|
||||
);
|
||||
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req, statusPageId);
|
||||
|
||||
if (refreshToken) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionByRefreshToken(
|
||||
refreshToken,
|
||||
{
|
||||
reason: "User logged out",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(res, CookieUtil.getRefreshTokenKey(statusPageId));
|
||||
CookieUtil.removeStatusPageMasterPasswordCookie(res, statusPageId);
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId)); // remove the cookie.
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
@@ -179,258 +55,6 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/refresh-token/:statuspageid",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const statusPageIdParam: string | undefined = req.params["statuspageid"];
|
||||
|
||||
if (!statusPageIdParam) {
|
||||
throw new BadDataException("Status Page ID is required.");
|
||||
}
|
||||
|
||||
const statusPageId: ObjectID = new ObjectID(statusPageIdParam.toString());
|
||||
|
||||
const refreshToken: string | undefined =
|
||||
CookieUtil.getRefreshTokenFromExpressRequest(req, statusPageId);
|
||||
|
||||
if (!refreshToken) {
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
if (
|
||||
respondWithMasterPasswordAccess({
|
||||
req,
|
||||
res,
|
||||
statusPageId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException(
|
||||
"Refresh token missing. Please login again.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const session: StatusPagePrivateUserSession | null =
|
||||
await StatusPagePrivateUserSessionService.findActiveSessionByRefreshToken(
|
||||
refreshToken,
|
||||
);
|
||||
|
||||
if (!session || !session.id || !session.statusPageId) {
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
if (
|
||||
respondWithMasterPasswordAccess({
|
||||
req,
|
||||
res,
|
||||
statusPageId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (session.statusPageId.toString() !== statusPageId.toString()) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionById(
|
||||
session.id,
|
||||
{
|
||||
reason: "Status page mismatch",
|
||||
},
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
if (
|
||||
respondWithMasterPasswordAccess({
|
||||
req,
|
||||
res,
|
||||
statusPageId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
session.refreshTokenExpiresAt &&
|
||||
OneUptimeDate.hasExpired(session.refreshTokenExpiresAt)
|
||||
) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionById(
|
||||
session.id,
|
||||
{
|
||||
reason: "Refresh token expired",
|
||||
},
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
if (
|
||||
respondWithMasterPasswordAccess({
|
||||
req,
|
||||
res,
|
||||
statusPageId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
if (!session.statusPagePrivateUserId) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionById(
|
||||
session.id,
|
||||
{
|
||||
reason: "Session missing user",
|
||||
},
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
if (
|
||||
respondWithMasterPasswordAccess({
|
||||
req,
|
||||
res,
|
||||
statusPageId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Session expired. Please login again."),
|
||||
);
|
||||
}
|
||||
|
||||
const user: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneById({
|
||||
id: session.statusPagePrivateUserId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
await StatusPagePrivateUserSessionService.revokeSessionById(
|
||||
session.id,
|
||||
{
|
||||
reason: "User not found",
|
||||
},
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId));
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getRefreshTokenKey(statusPageId),
|
||||
);
|
||||
|
||||
if (
|
||||
respondWithMasterPasswordAccess({
|
||||
req,
|
||||
res,
|
||||
statusPageId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new NotAuthenticatedException("Account no longer exists."),
|
||||
);
|
||||
}
|
||||
|
||||
const renewedSession: StatusPageSessionMetadata =
|
||||
await StatusPagePrivateUserSessionService.renewSessionWithNewRefreshToken(
|
||||
{
|
||||
session,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
},
|
||||
);
|
||||
|
||||
const accessToken: string = CookieUtil.setStatusPagePrivateUserCookie({
|
||||
expressResponse: res,
|
||||
user,
|
||||
statusPageId: user.statusPageId!,
|
||||
sessionId: renewedSession.session.id!,
|
||||
refreshToken: renewedSession.refreshToken,
|
||||
refreshTokenExpiresAt: renewedSession.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
});
|
||||
|
||||
return Response.sendEntityResponse(
|
||||
req,
|
||||
res,
|
||||
user,
|
||||
StatusPagePrivateUser,
|
||||
{
|
||||
miscData: {
|
||||
token: accessToken,
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/forgot-password",
|
||||
async (
|
||||
@@ -522,8 +146,6 @@ router.post(
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
const statusPageIdString: string | null =
|
||||
statusPage.id?.toString() || statusPage._id?.toString() || null;
|
||||
|
||||
MailService.sendMail(
|
||||
{
|
||||
@@ -532,13 +154,12 @@ router.post(
|
||||
templateType: EmailTemplateType.StatusPageForgotPassword,
|
||||
vars: {
|
||||
statusPageName: statusPageName!,
|
||||
logoUrl:
|
||||
statusPage.logoFileId && statusPageIdString
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(StatusPageApiRoute)
|
||||
.addRoute(`/logo/${statusPageIdString}`)
|
||||
.toString()
|
||||
: "",
|
||||
logoUrl: statusPage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute("/image/" + statusPage.logoFileId)
|
||||
.toString()
|
||||
: "",
|
||||
homeURL: statusPageURL,
|
||||
tokenVerifyUrl: URL.fromString(statusPageURL)
|
||||
.addRoute("/reset-password/" + token)
|
||||
@@ -547,7 +168,6 @@ router.post(
|
||||
},
|
||||
{
|
||||
projectId: statusPage.projectId!,
|
||||
statusPageId: statusPage.id!,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
@@ -667,8 +287,6 @@ router.post(
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
const statusPageIdString: string | null =
|
||||
statusPage.id?.toString() || statusPage._id?.toString() || null;
|
||||
|
||||
MailService.sendMail(
|
||||
{
|
||||
@@ -678,18 +296,16 @@ router.post(
|
||||
vars: {
|
||||
homeURL: statusPageURL,
|
||||
statusPageName: statusPageName || "",
|
||||
logoUrl:
|
||||
statusPage.logoFileId && statusPageIdString
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(StatusPageApiRoute)
|
||||
.addRoute(`/logo/${statusPageIdString}`)
|
||||
.toString()
|
||||
: "",
|
||||
logoUrl: statusPage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute("/image/" + statusPage.logoFileId)
|
||||
.toString()
|
||||
: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
projectId: statusPage.projectId!,
|
||||
statusPageId: statusPage.id!,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
@@ -758,7 +374,6 @@ router.post(
|
||||
password: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -766,38 +381,31 @@ router.post(
|
||||
});
|
||||
|
||||
if (alreadySavedUser) {
|
||||
const { accessToken } = await finalizeStatusPageLogin({
|
||||
req,
|
||||
res,
|
||||
user: alreadySavedUser,
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30),
|
||||
),
|
||||
});
|
||||
|
||||
const sanitizedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneById({
|
||||
id: alreadySavedUser.id!,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!sanitizedUser && (alreadySavedUser as any).password) {
|
||||
delete (alreadySavedUser as any).password;
|
||||
}
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
|
||||
token,
|
||||
{
|
||||
httpOnly: true,
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
||||
},
|
||||
);
|
||||
|
||||
return Response.sendEntityResponse(
|
||||
req,
|
||||
res,
|
||||
sanitizedUser || alreadySavedUser,
|
||||
alreadySavedUser,
|
||||
StatusPagePrivateUser,
|
||||
{
|
||||
miscData: {
|
||||
token: accessToken,
|
||||
token: token,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,606 +0,0 @@
|
||||
import SCIMMiddleware from "Common/Server/Middleware/SCIMAuthorization";
|
||||
import StatusPagePrivateUserService from "Common/Server/Services/StatusPagePrivateUserService";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
OneUptimeRequest,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Email from "Common/Types/Email";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import StatusPagePrivateUser from "Common/Models/DatabaseModels/StatusPagePrivateUser";
|
||||
import StatusPageSCIM from "Common/Models/DatabaseModels/StatusPageSCIM";
|
||||
import BadRequestException from "Common/Types/Exception/BadRequestException";
|
||||
import NotFoundException from "Common/Types/Exception/NotFoundException";
|
||||
import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import {
|
||||
formatUserForSCIM,
|
||||
generateServiceProviderConfig,
|
||||
} from "../Utils/SCIMUtils";
|
||||
import Text from "Common/Types/Text";
|
||||
import HashedString from "Common/Types/HashedString";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
// SCIM Service Provider Configuration - GET /status-page-scim/v2/ServiceProviderConfig
|
||||
router.get(
|
||||
"/status-page-scim/v2/:statusPageScimId/ServiceProviderConfig",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM ServiceProviderConfig - scimId: ${req.params["statusPageScimId"]!}`,
|
||||
);
|
||||
|
||||
const serviceProviderConfig: JSONObject = generateServiceProviderConfig(
|
||||
req,
|
||||
req.params["statusPageScimId"]!,
|
||||
"status-page",
|
||||
);
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, serviceProviderConfig);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Status Page Users endpoint - GET /status-page-scim/v2/Users
|
||||
router.get(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Users list request for statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
);
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
const statusPageId: ObjectID = bearerData["statusPageId"] as ObjectID;
|
||||
|
||||
// Parse query parameters
|
||||
const startIndex: number =
|
||||
parseInt(req.query["startIndex"] as string) || 1;
|
||||
const count: number = Math.min(
|
||||
parseInt(req.query["count"] as string) || 100,
|
||||
LIMIT_PER_PROJECT,
|
||||
);
|
||||
const filter: string = req.query["filter"] as string;
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Users - statusPageId: ${statusPageId}, startIndex: ${startIndex}, count: ${count}, filter: ${filter || "none"}`,
|
||||
);
|
||||
|
||||
// Build query for status page users
|
||||
const query: any = {
|
||||
statusPageId: statusPageId,
|
||||
};
|
||||
|
||||
// Handle SCIM filter for userName
|
||||
if (filter) {
|
||||
const emailMatch: RegExpMatchArray | null = filter.match(
|
||||
/userName eq "([^"]+)"/i,
|
||||
);
|
||||
if (emailMatch) {
|
||||
const email: string = emailMatch[1]!;
|
||||
logger.debug(
|
||||
`Status Page SCIM Users list - statusPageScimId: ${req.params["statusPageScimId"]!}, filter by email: ${email}`,
|
||||
);
|
||||
|
||||
if (email) {
|
||||
if (Email.isValid(email)) {
|
||||
query.email = new Email(email);
|
||||
logger.debug(
|
||||
`Status Page SCIM Users list - statusPageScimId: ${req.params["statusPageScimId"]!}, filtering by email: ${email}`,
|
||||
);
|
||||
} else {
|
||||
logger.debug(
|
||||
`Status Page SCIM Users list - statusPageScimId: ${req.params["statusPageScimId"]!}, invalid email format in filter: ${email}`,
|
||||
);
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: 0,
|
||||
startIndex: startIndex,
|
||||
itemsPerPage: 0,
|
||||
Resources: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all private users for this status page
|
||||
const statusPageUsers: Array<StatusPagePrivateUser> =
|
||||
await StatusPagePrivateUserService.findBy({
|
||||
query: query,
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_MAX,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Users - found ${statusPageUsers.length} users`,
|
||||
);
|
||||
|
||||
// Format users for SCIM
|
||||
const users: Array<JSONObject> = statusPageUsers.map(
|
||||
(user: StatusPagePrivateUser) => {
|
||||
return formatUserForSCIM(
|
||||
user,
|
||||
req,
|
||||
req.params["statusPageScimId"]!,
|
||||
"status-page",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Paginate the results
|
||||
const paginatedUsers: Array<JSONObject> = users.slice(
|
||||
(startIndex - 1) * count,
|
||||
startIndex * count,
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Users response prepared with ${users.length} users`,
|
||||
);
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: users.length,
|
||||
startIndex: startIndex,
|
||||
itemsPerPage: paginatedUsers.length,
|
||||
Resources: paginatedUsers,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Get Individual Status Page User - GET /status-page-scim/v2/Users/{id}
|
||||
router.get(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Get individual user request for userId: ${req.params["userId"]}, statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
);
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
const statusPageId: ObjectID = bearerData["statusPageId"] as ObjectID;
|
||||
const userId: string = req.params["userId"]!;
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Get user - statusPageId: ${statusPageId}, userId: ${userId}`,
|
||||
);
|
||||
|
||||
if (!userId) {
|
||||
throw new BadRequestException("User ID is required");
|
||||
}
|
||||
|
||||
// Check if user exists and belongs to this status page
|
||||
const statusPageUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
statusPageId: statusPageId,
|
||||
_id: new ObjectID(userId),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
if (!statusPageUser) {
|
||||
logger.debug(
|
||||
`Status Page SCIM Get user - user not found for userId: ${userId}`,
|
||||
);
|
||||
throw new NotFoundException(
|
||||
"User not found or not part of this status page",
|
||||
);
|
||||
}
|
||||
|
||||
const user: JSONObject = formatUserForSCIM(
|
||||
statusPageUser,
|
||||
req,
|
||||
req.params["statusPageScimId"]!,
|
||||
"status-page",
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Get user - returning user with id: ${statusPageUser.id}`,
|
||||
);
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Create Status Page User - POST /status-page-scim/v2/Users
|
||||
router.post(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Create user request for statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
);
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
const statusPageId: ObjectID = bearerData["statusPageId"] as ObjectID;
|
||||
const scimConfig: StatusPageSCIM = bearerData[
|
||||
"scimConfig"
|
||||
] as StatusPageSCIM;
|
||||
|
||||
if (!scimConfig.autoProvisionUsers) {
|
||||
throw new BadRequestException(
|
||||
"Auto-provisioning is disabled for this status page",
|
||||
);
|
||||
}
|
||||
|
||||
const scimUser: JSONObject = req.body;
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Create user - statusPageId: ${statusPageId}`,
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
`Request body for Status Page SCIM Create user: ${JSON.stringify(scimUser, null, 2)}`,
|
||||
);
|
||||
|
||||
// Extract user data from SCIM payload
|
||||
const email: string =
|
||||
(scimUser["userName"] as string) ||
|
||||
((scimUser["emails"] as JSONObject[])?.[0]?.["value"] as string);
|
||||
|
||||
if (!email) {
|
||||
throw new BadRequestException("Email is required for user creation");
|
||||
}
|
||||
|
||||
logger.debug(`Status Page SCIM Create user - email: ${email}`);
|
||||
|
||||
// Check if user already exists for this status page
|
||||
let user: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
statusPageId: statusPageId,
|
||||
email: new Email(email),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
logger.debug(
|
||||
`Status Page SCIM Create user - creating new user with email: ${email}`,
|
||||
);
|
||||
|
||||
const privateUser: StatusPagePrivateUser = new StatusPagePrivateUser();
|
||||
privateUser.statusPageId = statusPageId;
|
||||
privateUser.email = new Email(email);
|
||||
privateUser.password = new HashedString(Text.generateRandomText(32));
|
||||
privateUser.projectId = bearerData["projectId"] as ObjectID;
|
||||
|
||||
// Create new status page private user
|
||||
user = await StatusPagePrivateUserService.create({
|
||||
data: privateUser as any,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
} else {
|
||||
logger.debug(
|
||||
`Status Page SCIM Create user - user already exists with id: ${user.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
const createdUser: JSONObject = formatUserForSCIM(
|
||||
user,
|
||||
req,
|
||||
req.params["statusPageScimId"]!,
|
||||
"status-page",
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Create user - returning created user with id: ${user.id}`,
|
||||
);
|
||||
|
||||
res.status(201);
|
||||
return Response.sendJsonObjectResponse(req, res, createdUser);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const handleStatusPageUserUpdate: (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
) => Promise<void> = async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Update user request for userId: ${req.params["userId"]}, statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
);
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
const statusPageId: ObjectID = bearerData["statusPageId"] as ObjectID;
|
||||
const userId: string = req.params["userId"]!;
|
||||
const scimUser: JSONObject = req.body;
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Update user - statusPageId: ${statusPageId}, userId: ${userId}`,
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
`Request body for Status Page SCIM Update user: ${JSON.stringify(scimUser, null, 2)}`,
|
||||
);
|
||||
|
||||
if (!userId) {
|
||||
throw new BadRequestException("User ID is required");
|
||||
}
|
||||
|
||||
// Check if user exists and belongs to this status page
|
||||
const statusPageUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
statusPageId: statusPageId,
|
||||
_id: new ObjectID(userId),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
if (!statusPageUser) {
|
||||
logger.debug(
|
||||
`Status Page SCIM Update user - user not found for userId: ${userId}`,
|
||||
);
|
||||
throw new NotFoundException(
|
||||
"User not found or not part of this status page",
|
||||
);
|
||||
}
|
||||
|
||||
// Update user information
|
||||
const email: string =
|
||||
(scimUser["userName"] as string) ||
|
||||
((scimUser["emails"] as JSONObject[])?.[0]?.["value"] as string);
|
||||
const active: boolean = scimUser["active"] as boolean;
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Update user - email: ${email}, active: ${active}`,
|
||||
);
|
||||
|
||||
// Handle user deactivation by deleting from status page
|
||||
if (active === false) {
|
||||
logger.debug(
|
||||
`Status Page SCIM Update user - user marked as inactive, removing from status page`,
|
||||
);
|
||||
|
||||
const scimConfig: StatusPageSCIM = bearerData[
|
||||
"scimConfig"
|
||||
] as StatusPageSCIM;
|
||||
if (scimConfig.autoDeprovisionUsers) {
|
||||
await StatusPagePrivateUserService.deleteOneById({
|
||||
id: new ObjectID(userId),
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Update user - user removed from status page`,
|
||||
);
|
||||
|
||||
// Return empty response for deleted user
|
||||
return Response.sendJsonObjectResponse(req, res, {});
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare update data
|
||||
const updateData: {
|
||||
email?: Email;
|
||||
} = {};
|
||||
|
||||
if (email && email !== statusPageUser.email?.toString()) {
|
||||
updateData.email = new Email(email);
|
||||
}
|
||||
|
||||
// Only update if there are changes
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
logger.debug(
|
||||
`Status Page SCIM Update user - updating user with data: ${JSON.stringify(updateData)}`,
|
||||
);
|
||||
|
||||
await StatusPagePrivateUserService.updateOneById({
|
||||
id: new ObjectID(userId),
|
||||
data: updateData,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
logger.debug(`Status Page SCIM Update user - user updated successfully`);
|
||||
|
||||
// Fetch updated user
|
||||
const updatedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneById({
|
||||
id: new ObjectID(userId),
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
if (updatedUser) {
|
||||
const user: JSONObject = formatUserForSCIM(
|
||||
updatedUser,
|
||||
req,
|
||||
req.params["statusPageScimId"]!,
|
||||
"status-page",
|
||||
);
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Update user - no updates made, returning existing user`,
|
||||
);
|
||||
|
||||
// If no updates were made, return the existing user
|
||||
const user: JSONObject = formatUserForSCIM(
|
||||
statusPageUser,
|
||||
req,
|
||||
req.params["statusPageScimId"]!,
|
||||
"status-page",
|
||||
);
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
};
|
||||
|
||||
// Update Status Page User - PUT /status-page-scim/v2/Users/{id}
|
||||
router.put(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
handleStatusPageUserUpdate,
|
||||
);
|
||||
|
||||
// Update Status Page User - PATCH /status-page-scim/v2/Users/{id}
|
||||
router.patch(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
handleStatusPageUserUpdate,
|
||||
);
|
||||
|
||||
// Delete Status Page User - DELETE /status-page-scim/v2/Users/{id}
|
||||
router.delete(
|
||||
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
|
||||
SCIMMiddleware.isAuthorizedSCIMRequest,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
logger.debug(
|
||||
`Status Page SCIM Delete user request for userId: ${req.params["userId"]}, statusPageScimId: ${req.params["statusPageScimId"]}`,
|
||||
);
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
const statusPageId: ObjectID = bearerData["statusPageId"] as ObjectID;
|
||||
const scimConfig: StatusPageSCIM = bearerData[
|
||||
"scimConfig"
|
||||
] as StatusPageSCIM;
|
||||
const userId: string = req.params["userId"]!;
|
||||
|
||||
if (!scimConfig.autoDeprovisionUsers) {
|
||||
throw new BadRequestException(
|
||||
"Auto-deprovisioning is disabled for this status page",
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Delete user - statusPageId: ${statusPageId}, userId: ${userId}`,
|
||||
);
|
||||
|
||||
if (!userId) {
|
||||
throw new BadRequestException("User ID is required");
|
||||
}
|
||||
|
||||
// Check if user exists and belongs to this status page
|
||||
const statusPageUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
statusPageId: statusPageId,
|
||||
_id: new ObjectID(userId),
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
if (!statusPageUser) {
|
||||
logger.debug(
|
||||
`Status Page SCIM Delete user - user not found for userId: ${userId}`,
|
||||
);
|
||||
// SCIM spec says to return 404 for non-existent resources
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
// Delete the user from status page
|
||||
await StatusPagePrivateUserService.deleteOneById({
|
||||
id: new ObjectID(userId),
|
||||
props: { isRoot: true },
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`Status Page SCIM Delete user - user deleted successfully for userId: ${userId}`,
|
||||
);
|
||||
|
||||
// Return 204 No Content for successful deletion
|
||||
res.status(204);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -1,5 +1,6 @@
|
||||
import SSOUtil from "../Utils/SSO";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import Email from "Common/Types/Email";
|
||||
import BadRequestException from "Common/Types/Exception/BadRequestException";
|
||||
import Exception from "Common/Types/Exception/Exception";
|
||||
@@ -7,11 +8,9 @@ import ServerException from "Common/Types/Exception/ServerException";
|
||||
import HashedString from "Common/Types/HashedString";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PositiveNumber from "Common/Types/PositiveNumber";
|
||||
import { Host, HttpProtocol } from "Common/Server/EnvironmentConfig";
|
||||
import StatusPagePrivateUserService from "Common/Server/Services/StatusPagePrivateUserService";
|
||||
import StatusPagePrivateUserSessionService, {
|
||||
SessionMetadata as StatusPageSessionMetadata,
|
||||
} from "Common/Server/Services/StatusPagePrivateUserSessionService";
|
||||
import StatusPageService from "Common/Server/Services/StatusPageService";
|
||||
import StatusPageSsoService from "Common/Server/Services/StatusPageSsoService";
|
||||
import CookieUtil from "Common/Server/Utils/Cookie";
|
||||
@@ -20,10 +19,8 @@ import Express, {
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
extractDeviceInfo,
|
||||
getClientIp,
|
||||
headerValueToString,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import StatusPagePrivateUser from "Common/Models/DatabaseModels/StatusPagePrivateUser";
|
||||
@@ -33,8 +30,6 @@ import xml2js from "xml2js";
|
||||
// Initialize Express router.
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
const ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;
|
||||
|
||||
// Define a GET route for SSO in a status page context.
|
||||
router.get(
|
||||
"/status-page-sso/:statusPageId/:statusPageSsoId",
|
||||
@@ -120,11 +115,7 @@ router.get(
|
||||
|
||||
router.post(
|
||||
"/status-page-idp-login/:statusPageId/:statusPageSsoId",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
try {
|
||||
const samlResponseBase64: string = req.body.SAMLResponse;
|
||||
|
||||
@@ -290,30 +281,24 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
if (!alreadySavedUser.projectId) {
|
||||
alreadySavedUser.projectId = projectId;
|
||||
}
|
||||
|
||||
const sessionMetadata: StatusPageSessionMetadata =
|
||||
await StatusPagePrivateUserSessionService.createSession({
|
||||
projectId: alreadySavedUser.projectId!,
|
||||
statusPageId: statusPageId,
|
||||
statusPagePrivateUserId: alreadySavedUser.id!,
|
||||
ipAddress: getClientIp(req),
|
||||
userAgent: headerValueToString(req.headers["user-agent"]),
|
||||
...extractDeviceInfo(req),
|
||||
});
|
||||
|
||||
const token: string = CookieUtil.setStatusPagePrivateUserCookie({
|
||||
expressResponse: res,
|
||||
user: alreadySavedUser,
|
||||
statusPageId: statusPageId,
|
||||
sessionId: sessionMetadata.session.id!,
|
||||
refreshToken: sessionMetadata.refreshToken,
|
||||
refreshTokenExpiresAt: sessionMetadata.refreshTokenExpiresAt,
|
||||
accessTokenExpiresInSeconds: ACCESS_TOKEN_EXPIRY_SECONDS,
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30),
|
||||
),
|
||||
});
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
|
||||
token,
|
||||
|
||||
{
|
||||
httpOnly: true,
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
||||
},
|
||||
);
|
||||
|
||||
// get status page URL.
|
||||
const statusPageURL: string =
|
||||
await StatusPageService.getStatusPageFirstURL(statusPageId);
|
||||
@@ -327,11 +312,7 @@ router.post(
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
if (err instanceof Exception) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return next(new ServerException());
|
||||
Response.sendErrorResponse(req, res, new ServerException());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import AuthenticationAPI from "./API/Authentication";
|
||||
import ResellerAPI from "./API/Reseller";
|
||||
import SsoAPI from "./API/SSO";
|
||||
import SCIMAPI from "./API/SCIM";
|
||||
import StatusPageAuthenticationAPI from "./API/StatusPageAuthentication";
|
||||
import StatusPageSsoAPI from "./API/StatusPageSSO";
|
||||
import StatusPageSCIMAPI from "./API/StatusPageSCIM";
|
||||
import FeatureSet from "Common/Server/Types/FeatureSet";
|
||||
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
|
||||
import "ejs";
|
||||
@@ -21,10 +19,6 @@ const IdentityFeatureSet: FeatureSet = {
|
||||
|
||||
app.use([`/${APP_NAME}`, "/"], SsoAPI);
|
||||
|
||||
app.use([`/${APP_NAME}`, "/"], SCIMAPI);
|
||||
|
||||
app.use([`/${APP_NAME}`, "/"], StatusPageSCIMAPI);
|
||||
|
||||
app.use([`/${APP_NAME}`, "/"], StatusPageSsoAPI);
|
||||
|
||||
app.use(
|
||||
|
||||
@@ -35,8 +35,6 @@ export default class AuthenticationEmail {
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
logger.debug("Sending verification email");
|
||||
|
||||
MailService.sendMail({
|
||||
toEmail: user.email!,
|
||||
subject: "Please verify email.",
|
||||
@@ -52,13 +50,8 @@ export default class AuthenticationEmail {
|
||||
).toString(),
|
||||
homeUrl: new URL(httpProtocol, host).toString(),
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
logger.debug("Verification email sent");
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
logger.debug("Error sending verification email");
|
||||
logger.error(err);
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,265 +0,0 @@
|
||||
import { ExpressRequest } from "Common/Server/Utils/Express";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import Email from "Common/Types/Email";
|
||||
import Name from "Common/Types/Name";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
|
||||
/**
|
||||
* Shared SCIM utility functions for both Project SCIM and Status Page SCIM
|
||||
*/
|
||||
|
||||
// Base interface for SCIM user-like objects - compatible with User model
|
||||
export interface SCIMUser {
|
||||
id?: ObjectID | null;
|
||||
email?: Email;
|
||||
name?: Name | string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse name information from SCIM user payload
|
||||
*/
|
||||
export const parseNameFromSCIM: (scimUser: JSONObject) => string = (
|
||||
scimUser: JSONObject,
|
||||
): string => {
|
||||
logger.debug(
|
||||
`SCIM - Parsing name from SCIM user: ${JSON.stringify(scimUser, null, 2)}`,
|
||||
);
|
||||
|
||||
const givenName: string =
|
||||
((scimUser["name"] as JSONObject)?.["givenName"] as string) || "";
|
||||
const familyName: string =
|
||||
((scimUser["name"] as JSONObject)?.["familyName"] as string) || "";
|
||||
const formattedName: string = (scimUser["name"] as JSONObject)?.[
|
||||
"formatted"
|
||||
] as string;
|
||||
|
||||
// Construct full name: prefer formatted, then combine given+family, then fallback to displayName
|
||||
if (formattedName) {
|
||||
return formattedName;
|
||||
} else if (givenName || familyName) {
|
||||
return `${givenName} ${familyName}`.trim();
|
||||
} else if (scimUser["displayName"]) {
|
||||
return scimUser["displayName"] as string;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse full name into SCIM name format
|
||||
*/
|
||||
export const parseNameToSCIMFormat: (fullName: string) => {
|
||||
givenName: string;
|
||||
familyName: string;
|
||||
formatted: string;
|
||||
} = (
|
||||
fullName: string,
|
||||
): { givenName: string; familyName: string; formatted: string } => {
|
||||
const nameParts: string[] = fullName.trim().split(/\s+/);
|
||||
const givenName: string = nameParts[0] || "";
|
||||
const familyName: string = nameParts.slice(1).join(" ") || "";
|
||||
|
||||
return {
|
||||
givenName,
|
||||
familyName,
|
||||
formatted: fullName,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Format user object for SCIM response
|
||||
*/
|
||||
export const formatUserForSCIM: (
|
||||
user: SCIMUser,
|
||||
req: ExpressRequest,
|
||||
scimId: string,
|
||||
scimType: "project" | "status-page",
|
||||
) => JSONObject = (
|
||||
user: SCIMUser,
|
||||
req: ExpressRequest,
|
||||
scimId: string,
|
||||
scimType: "project" | "status-page",
|
||||
): JSONObject => {
|
||||
const baseUrl: string = `${req.protocol}://${req.get("host")}`;
|
||||
const userName: string = user.email?.toString() || "";
|
||||
const fullName: string =
|
||||
user.name?.toString() || userName.split("@")[0] || "Unknown User";
|
||||
|
||||
const nameData: { givenName: string; familyName: string; formatted: string } =
|
||||
parseNameToSCIMFormat(fullName);
|
||||
|
||||
// Determine the correct endpoint path based on SCIM type
|
||||
const endpointPath: string =
|
||||
scimType === "project"
|
||||
? `/scim/v2/${scimId}/Users/${user.id?.toString()}`
|
||||
: `/status-page-scim/v2/${scimId}/Users/${user.id?.toString()}`;
|
||||
|
||||
return {
|
||||
schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
||||
id: user.id?.toString(),
|
||||
userName: userName,
|
||||
displayName: nameData.formatted,
|
||||
name: {
|
||||
formatted: nameData.formatted,
|
||||
familyName: nameData.familyName,
|
||||
givenName: nameData.givenName,
|
||||
},
|
||||
emails: [
|
||||
{
|
||||
value: userName,
|
||||
type: "work",
|
||||
primary: true,
|
||||
},
|
||||
],
|
||||
active: true,
|
||||
meta: {
|
||||
resourceType: "User",
|
||||
created: user.createdAt?.toISOString(),
|
||||
lastModified: user.updatedAt?.toISOString(),
|
||||
location: `${baseUrl}${endpointPath}`,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract email from SCIM user payload
|
||||
*/
|
||||
export const extractEmailFromSCIM: (scimUser: JSONObject) => string = (
|
||||
scimUser: JSONObject,
|
||||
): string => {
|
||||
return (
|
||||
(scimUser["userName"] as string) ||
|
||||
((scimUser["emails"] as JSONObject[])?.[0]?.["value"] as string) ||
|
||||
""
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract active status from SCIM user payload
|
||||
*/
|
||||
export const extractActiveFromSCIM: (scimUser: JSONObject) => boolean = (
|
||||
scimUser: JSONObject,
|
||||
): boolean => {
|
||||
return scimUser["active"] !== false; // Default to true if not specified
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate SCIM ServiceProviderConfig response
|
||||
*/
|
||||
export const generateServiceProviderConfig: (
|
||||
req: ExpressRequest,
|
||||
scimId: string,
|
||||
scimType: "project" | "status-page",
|
||||
documentationUrl?: string,
|
||||
) => JSONObject = (
|
||||
req: ExpressRequest,
|
||||
scimId: string,
|
||||
scimType: "project" | "status-page",
|
||||
documentationUrl: string = "https://oneuptime.com/docs/identity/scim",
|
||||
): JSONObject => {
|
||||
const baseUrl: string = `${req.protocol}://${req.get("host")}`;
|
||||
const endpointPath: string =
|
||||
scimType === "project"
|
||||
? `/scim/v2/${scimId}`
|
||||
: `/status-page-scim/v2/${scimId}`;
|
||||
|
||||
return {
|
||||
schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
|
||||
documentationUri: documentationUrl,
|
||||
patch: {
|
||||
supported: true,
|
||||
},
|
||||
bulk: {
|
||||
supported: true,
|
||||
maxOperations: 1000,
|
||||
maxPayloadSize: 1048576,
|
||||
},
|
||||
filter: {
|
||||
supported: true,
|
||||
maxResults: 200,
|
||||
},
|
||||
changePassword: {
|
||||
supported: false,
|
||||
},
|
||||
sort: {
|
||||
supported: true,
|
||||
},
|
||||
etag: {
|
||||
supported: false,
|
||||
},
|
||||
authenticationSchemes: [
|
||||
{
|
||||
type: "httpbearer",
|
||||
name: "HTTP Bearer",
|
||||
description: "Authentication scheme using HTTP Bearer Token",
|
||||
primary: true,
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
location: `${baseUrl}${endpointPath}/ServiceProviderConfig`,
|
||||
resourceType: "ServiceProviderConfig",
|
||||
created: "2023-01-01T00:00:00Z",
|
||||
lastModified: "2023-01-01T00:00:00Z",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate SCIM ListResponse for users
|
||||
*/
|
||||
export const generateUsersListResponse: (
|
||||
users: JSONObject[],
|
||||
startIndex: number,
|
||||
totalResults: number,
|
||||
) => JSONObject = (
|
||||
users: JSONObject[],
|
||||
startIndex: number,
|
||||
totalResults: number,
|
||||
): JSONObject => {
|
||||
return {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: totalResults,
|
||||
startIndex: startIndex,
|
||||
itemsPerPage: users.length,
|
||||
Resources: users,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate SCIM ListResponse for groups
|
||||
*/
|
||||
export const generateGroupsListResponse: (
|
||||
groups: JSONObject[],
|
||||
startIndex: number,
|
||||
totalResults: number,
|
||||
) => JSONObject = (
|
||||
groups: JSONObject[],
|
||||
startIndex: number,
|
||||
totalResults: number,
|
||||
): JSONObject => {
|
||||
return {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: totalResults,
|
||||
startIndex: startIndex,
|
||||
itemsPerPage: groups.length,
|
||||
Resources: groups,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse query parameters for SCIM list requests
|
||||
*/
|
||||
export const parseSCIMQueryParams: (req: ExpressRequest) => {
|
||||
startIndex: number;
|
||||
count: number;
|
||||
} = (req: ExpressRequest): { startIndex: number; count: number } => {
|
||||
const startIndex: number = parseInt(req.query["startIndex"] as string) || 1;
|
||||
const count: number = Math.min(
|
||||
parseInt(req.query["count"] as string) || 100,
|
||||
200, // SCIM recommended max
|
||||
);
|
||||
|
||||
return { startIndex, count };
|
||||
};
|
||||
@@ -184,17 +184,15 @@ export default class SSOUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* get displayName attribute.
|
||||
* {
|
||||
* "$": {
|
||||
* "Name": "http://schemas.microsoft.com/identity/claims/displayname"
|
||||
* },
|
||||
* "AttributeValue": [
|
||||
* "Nawaz Dhandala"
|
||||
* ]
|
||||
* },
|
||||
*/
|
||||
// get displayName attribute.
|
||||
// {
|
||||
// "$": {
|
||||
// "Name": "http://schemas.microsoft.com/identity/claims/displayname"
|
||||
// },
|
||||
// "AttributeValue": [
|
||||
// "Nawaz Dhandala"
|
||||
// ]
|
||||
// },
|
||||
|
||||
for (let i: number = 0; i < samlAttribute.length; i++) {
|
||||
const attribute: JSONObject = samlAttribute[i] as JSONObject;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user