Compare commits

..

2 Commits

Author SHA1 Message Date
Simon Larsen
71a0edb7dd ```text
refactor: Update MonitorReport API endpoint route

This commit updates the API endpoint route for the MonitorReport model. The route is changed from "/status-page" to "/monitor-report" in the MonitorReport.ts file. This change ensures consistency and clarity in the API routes, aligning them with the model name.
2024-07-23 10:42:20 -06:00
Simon Larsen
a53b24ed05 refactor: Add Monitor Reports to SideMenu
This commit adds the "Monitor Reports" option to the SideMenu in the Monitor page. It includes the necessary code changes in the MonitorReports.tsx, MonitorReport/View/Index.tsx, MonitorReport/View/Delete.tsx, MonitorReport/View/Settings.tsx, and SideMenu.tsx files. The new option allows users to access and manage monitor reports directly from the SideMenu, improving the navigation and usability of the Monitor page.
2024-07-23 10:25:33 -06:00
4175 changed files with 198862 additions and 378815 deletions

View File

@@ -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.

View File

@@ -16,22 +16,14 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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
@@ -39,137 +31,14 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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 .
docker-build-home:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preinstall
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: npm run prerun
# build image for accounts service
- name: build docker image
uses: nick-fields/retry@v3
with:
timeout_minutes: 45
max_attempts: 3
command: sudo docker build --no-cache -f ./Home/Dockerfile .
docker-build-worker:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preinstall
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: npm run prerun
# build image for accounts service
- name: build docker image
uses: nick-fields/retry@v3
with:
timeout_minutes: 45
max_attempts: 3
command: sudo docker build --no-cache -f ./Worker/Dockerfile .
docker-build-workflow:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preinstall
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: npm run prerun
# build image for accounts service
- name: build docker image
uses: nick-fields/retry@v3
with:
timeout_minutes: 45
max_attempts: 3
command: sudo docker build --no-cache -f ./Workflow/Dockerfile .
docker-build-api-reference:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preinstall
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: npm run prerun
# build image for accounts service
- name: build docker image
uses: nick-fields/retry@v3
with:
timeout_minutes: 45
max_attempts: 3
command: sudo docker build --no-cache -f ./APIReference/Dockerfile .
docker-build-docs:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preinstall
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: npm run prerun
# build image for accounts service
- name: build docker image
uses: nick-fields/retry@v3
with:
timeout_minutes: 45
max_attempts: 3
command: sudo docker build --no-cache -f ./Docs/Dockerfile .
run: sudo docker build -f ./IsolatedVM/Dockerfile .
docker-build-otel-collector:
@@ -178,22 +47,14 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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
@@ -201,23 +62,15 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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:
@@ -226,22 +79,14 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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
@@ -249,23 +94,15 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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
@@ -273,22 +110,14 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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
@@ -296,22 +125,30 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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@v2
- 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
@@ -319,114 +156,29 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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:
docker-build-ingestor:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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 .
docker-build-server-monitor-ingest:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preinstall
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: npm run prerun
# build image 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 .
docker-build-telemetry:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preinstall
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: npm run prerun
# build image 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 .
docker-build-incoming-request-ingest:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preinstall
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: npm run prerun
# build image 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 ./Ingestor/Dockerfile .
docker-build-status-page:
runs-on: ubuntu-latest
@@ -434,22 +186,14 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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
@@ -457,19 +201,11 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- 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 .

View File

@@ -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' ]

View File

@@ -14,7 +14,7 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Install Helm
run: |
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
@@ -28,7 +28,7 @@ jobs:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v2
- run: sudo apt-get update
- run: sudo apt-get install -y curl gcc
- run: sudo apt-get install -y build-essential

View File

@@ -15,187 +15,110 @@ jobs:
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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 Model && npm install
- run: cd CommonServer && npm install
- run: cd CommonUI && npm install --force
- run: cd Accounts && npm install && npm run compile && npm run dep-check
compile-isolated-vm:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- run: cd Common && npm install
- name: Compile IsolatedVM
uses: nick-fields/retry@v3
- run: cd CommonServer && npm install
- run: cd IsolatedVM && npm install && npm run compile && npm run dep-check
compile-common-server:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
timeout_minutes: 30
max_attempts: 3
command: cd IsolatedVM && npm install && npm run compile && npm run dep-check
node-version: 18.3.0
- run: cd Common && npm install
- run: cd Model && npm install
- run: cd CommonServer && npm install && npm run compile && npm run dep-check
compile-common-ui:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: cd Common && npm install
- run: cd Model && npm install
- run: cd CommonUI && npm install --force && npm run compile && npm run dep-check
compile-common:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
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
node-version: 18.3.0
- run: cd Common && npm install
- run: cd Model && npm install && npm run compile && npm run dep-check
compile-app:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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
compile-home:
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 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
compile-worker:
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 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
compile-workflow:
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 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
compile-api-reference:
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 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
compile-docs-reference:
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 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 Model && npm install
- run: cd CommonServer && npm install
- run: cd CommonUI && npm install --force
- run: cd App && npm install && npm run compile && npm run dep-check
compile-copilot:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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 Model && npm install
- run: cd CommonServer && npm install
- run: cd Copilot && npm install && npm run compile && npm run dep-check
compile-nginx:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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 Model && npm install
- run: cd CommonServer && npm install
- run: cd CommonUI && npm install --force
- run: cd Nginx && npm install && npm run compile && npm run dep-check
compile-infrastructure-agent:
runs-on: ubuntu-latest
@@ -205,12 +128,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:
@@ -218,190 +136,111 @@ jobs:
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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 Model && npm install
- run: cd CommonServer && npm install
- run: cd CommonUI && npm install --force
- run: cd AdminDashboard && npm install && npm run compile && npm run dep-check
compile-dashboard:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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 Model && npm install
- run: cd CommonServer && npm install
- run: cd CommonUI && npm install --force
- run: cd Dashboard && npm install && npm run compile && npm run dep-check
compile-model:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: cd Common && npm install
- run: cd Model && npm install
- run: cd Model && npm install && npm run compile && npm run dep-check
compile-e2e:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
- run: sudo apt-get update
node-version: 18.3.0
- 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
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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 Model && npm install
- run: cd CommonServer && npm install
- run: cd Probe && npm install && npm run compile && npm run dep-check
compile-probe-ingest:
compile-ingestor:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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
compile-server-monitor-ingest:
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 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
compile-telemetry:
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 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
compile-incoming-request-ingest:
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 Incoming Request Ingest
uses: nick-fields/retry@v3
with:
timeout_minutes: 30
max_attempts: 3
command: cd IncomingRequestIngest && npm install && npm run compile && npm run dep-check
- run: cd Model && npm install
- run: cd CommonServer && npm install
- run: cd Ingestor && npm install && npm run compile && npm run dep-check
compile-status-page:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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 Model && npm install
- run: cd CommonServer && npm install
- run: cd CommonUI && npm install --force
- run: cd StatusPage && npm install && npm run compile && npm run dep-check
compile-test-server:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- 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 Model && npm install
- run: cd CommonServer && npm install
- run: cd TestServer && npm install && npm run compile && npm run dep-check

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@ jobs:
steps:
# Run Reliability Copilot in Docker Container
# Run Reliability Copilot in Doker Container
- name: Run Copilot
run: |
docker run --rm \

View File

@@ -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

View File

@@ -1,4 +1,4 @@
name: Incoming Request Ingest Test
name: App Test
on:
pull_request:
@@ -13,9 +13,8 @@ jobs:
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
- run: cd IncomingRequestIngest && npm install && npm run test
node-version: 18.3.0
- run: cd App && npm install && npm run test

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
name: Common Server Test
on:
pull_request:
push:
branches-ignore:
- 'hotfix-*' # excludes hotfix branches
- 'release'
jobs:
test:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: cd Common && npm install
- run: cd Model && npm install
- run: cd CommonServer && npm install
- run: cd CommonServer && bash test-setup.sh
- run: export $(grep -v '^#' config.env | xargs) && cd CommonServer && rm -rf build && npm run test

View File

@@ -12,12 +12,11 @@ jobs:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
BILLING_PRIVATE_KEY: ${{secrets.TEST_BILLING_PRIVATE_KEY}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
- run: cd Common && bash test-setup.sh
- run: cd Common && npm install && rm -rf build && npm run test
node-version: 18.3.0
- run: cd Model && npm install
- run: cd Common && npm install && npm run test

View File

@@ -1,4 +1,4 @@
name: MCP Server Test
name: CommonUI Test
on:
pull_request:
@@ -13,9 +13,11 @@ jobs:
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- run: cd Common && npm install
- run: cd MCP && npm install && npm run test
- run: cd Model && npm install
- run: cd CommonUI && npm install --force && npm run test

58
.github/workflows/test.e2e.yaml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: E2E Tests
on:
pull_request:
push:
branches-ignore:
- 'hotfix-*' # excludes hotfix branches
- 'release'
jobs:
test:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
- run: npm run dev
- name: Wait for server to start
run: bash ./Tests/Scripts/status-check.sh http://localhost
- name: Run E2E Tests. Run docker container e2e in docker compose file
run: export $(grep -v '^#' config.env | xargs) && docker compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker compose -f docker-compose.dev.yml logs e2e && exit 1)
- name: Upload test results
uses: actions/upload-artifact@v4
# Run this on failure
if: failure()
with:
# Name of the artifact to upload.
# Optional. Default is 'artifact'
name: test-results
# A file, directory or wildcard pattern that describes what to upload
# Required.
path: |
./E2E
# Duration after which artifact will expire in days. 0 means using default retention.
# Minimum 1 day.
# Maximum 90 days unless changed from the repository settings page.
# Optional. Defaults to repository settings.
retention-days: 7

View File

@@ -1,4 +1,4 @@
name: ProbeIngest Test
name: Ingestor Test
on:
pull_request:
@@ -13,9 +13,9 @@ jobs:
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
- run: cd ProbeIngest && npm install && npm run test
node-version: 18.3.0
- run: cd Ingestor && npm install && npm run test

View File

@@ -1,4 +1,4 @@
name: Telemetry Test
name: Model Test
on:
pull_request:
@@ -13,10 +13,11 @@ jobs:
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- run: cd Common && npm install
- run: cd Telemetry && npm install && npm run test
- run: cd Model && npm install
- run: cd Model && npm install && npm run test

View File

@@ -13,11 +13,12 @@ jobs:
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: latest
node-version: 18.3.0
- run: cd Common && npm install
- run: cd CommonServer && npm install
- run: cd Probe && npm install
- run: cd Probe && npm run test

View File

@@ -1,42 +0,0 @@
name: Tests
on:
pull_request:
push:
branches-ignore:
- 'hotfix-*' # excludes hotfix branches
- 'release'
jobs:
test-app:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: latest
- run: cd App && npm install && npm run test
test-home:
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 Home && npm install && npm run test
test-worker:
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 Worker && npm install && npm run test

22
.gitignore vendored
View File

@@ -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

View File

@@ -1 +1 @@
# Description: Copilot will run this script before we commit the changes to your repository.
# Description: Copilot will run this script before we commit the changes to your repository.

View File

@@ -51,3 +51,6 @@ licenses/*
certifications/*
ApiReference/public/assets/*
JavaScriptSDK/src/cli/server-monitor/out/scripts/prettify/*
CommonServer/Tests/TestingUtils/__mocks__/Stripe.mock.ts

136
.vscode/launch.json vendored
View File

@@ -63,76 +63,6 @@
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/Home",
"name": "Home: Debug with Docker",
"port": 9212,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/Worker",
"name": "Worker: Debug with Docker",
"port": 8734,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/Workflow",
"name": "Workflow: Debug with Docker",
"port": 8735,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/Docs",
"name": "Docs: Debug with Docker",
"port": 8738,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/APIReference",
"name": "API Reference: Debug with Docker",
"port": 8737,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/TestServer",
@@ -151,7 +81,7 @@
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/Probe",
"name": "Probe: Debug with Docker",
"port": 9229,
"port": 9655,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
@@ -163,8 +93,8 @@
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/ProbeIngest",
"name": "ProbeIngest: Debug with Docker",
"localRoot": "${workspaceFolder}/Ingestor",
"name": "Ingestor: Debug with Docker",
"port": 9932,
"remoteRoot": "/usr/src/app",
"request": "attach",
@@ -175,48 +105,6 @@
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/ServerMonitorIngest",
"name": "ServerMonitorIngest: Debug with Docker",
"port": 9941,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/IncomingRequestIngest",
"name": "IncomingRequestIngest: Debug with Docker",
"port": 9933,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/Telemetry",
"name": "Telemetry: Debug with Docker",
"port": 9938,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/IsolatedVM",
@@ -259,6 +147,20 @@
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/Workers",
"name": "Workers: Debug with Docker",
"port": 9654,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/StatusPage",
@@ -316,12 +218,12 @@
"autoAttachChildProcesses": true
},
{
"name": "Common: Debug Tests",
"name": "CommonServer: Debug Tests",
"type": "node",
"restart": true,
"autoAttachChildProcesses": true,
"request": "launch",
"cwd": "${workspaceRoot}/Common",
"cwd": "${workspaceRoot}/CommonServer",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run-script",

View File

@@ -1,30 +0,0 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
#/backend/node_modules
/kubernetes
/node_modules
.idea
# misc
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock
**/*/paymentService.test.js
apiTest.rest
application_security_dir
container_security_dir
# coverage
/coverage
/.nyc_output
/greenlock.d/config.json
/greenlock.d/config.json.bak
/.greenlockrc

View File

@@ -1,8 +0,0 @@
{
"query": {
"age": {
"_type": "EqualTo",
value: 10
}
}
}

View File

@@ -1,8 +0,0 @@
{
"query": {
"age": {
"_type": "GreaterThanOrNull",
"value": 10
}
}
}

View File

@@ -1,11 +0,0 @@
{
"query": {
"labels": {
"_type": "Includes",
"value": [
"aaa00000-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"bbb00000-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
]
}
}
}

View File

@@ -1,8 +0,0 @@
{
"query": {
"age": {
"_type": "LessThanOrNull",
"value": 10
}
}
}

View File

@@ -1,52 +0,0 @@
import APIReferenceRoutes from "./Routes";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import InfrastructureStatus from "Common/Server/Infrastructure/Status";
import logger from "Common/Server/Utils/Logger";
import App from "Common/Server/Utils/StartServer";
import Telemetry from "Common/Server/Utils/Telemetry";
import "ejs";
const APP_NAME: string = "reference";
const init: PromiseVoidFunction = async (): Promise<void> => {
try {
// Initialize telemetry
Telemetry.init({
serviceName: APP_NAME,
});
const statusCheck: PromiseVoidFunction = async (): Promise<void> => {
// Check the status of infrastructure components
return await InfrastructureStatus.checkStatusWithRetry({
checkClickhouseStatus: false,
checkPostgresStatus: false,
checkRedisStatus: false,
retryCount: 3,
});
};
// Initialize the app with service name and status checks
await App.init({
appName: APP_NAME,
statusOptions: {
liveCheck: statusCheck,
readyCheck: statusCheck,
},
});
await APIReferenceRoutes.init();
// Add default routes to the app
await App.addDefaultRoutes();
} catch (err) {
logger.error("App Init Failed:");
logger.error(err);
throw err;
}
};
init().catch((err: Error) => {
logger.error(err);
logger.error("Exiting node process");
process.exit(1);
});

View File

@@ -1,29 +0,0 @@
# README
This README would normally document whatever steps are necessary to get your application up and running.
### What is this repository for?
- Quick summary
- Version
- [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
### How do I get set up?
- Summary of set up
- Configuration
- Dependencies
- Database configuration
- How to run tests
- Deployment instructions
### Contribution guidelines
- Writing tests
- Code review
- Other guidelines
### Who do I talk to?
- Repo owner or admin
- Other community or team contact

View File

@@ -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,
});
}
}

View File

@@ -1,3 +0,0 @@
export const ViewsPath: string = "/usr/src/app/views";
export const StaticPath: string = "/usr/src/app/Static";
export const CodeExamplesPath: string = "/usr/src/app/CodeExamples";

View File

@@ -1,14 +0,0 @@
{
"watch": ["./","../Common/Server", "../Common/Types", "../Common/Utils", "../Common/Models"],
"ext": "ts,tsx",
"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"
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +0,0 @@
{
"name": "@oneuptime/api-reference",
"version": "1.0.0",
"description": "",
"main": "Index.ts",
"scripts": {
"start": "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
"compile": "tsc",
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
"dev": "npx nodemon",
"audit": "npm audit --audit-level=low",
"dep-check": "npm install -g depcheck && depcheck ./ --skip-missing=true",
"test": "rm -rf build && jest --detectOpenHandles --passWithNoTests",
"coverage": "jest --detectOpenHandles --coverage"
},
"author": "OneUptime <hello@oneuptime.com> (https://oneuptime.com/)",
"license": "Apache-2.0",
"dependencies": {
"Common": "file:../Common",
"ejs": "^3.1.9",
"ts-node": "^10.9.1"
},
"devDependencies": {
"@types/jest": "^29.5.11",
"@types/node": "^17.0.31",
"jest": "^28.1.0",
"nodemon": "^2.0.20"
}
}

View File

@@ -1,112 +0,0 @@
{
"ts-node": {
// these options are overrides used only by ts-node
// same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable
"compilerOptions": {
"module": "commonjs",
"resolveJsonModule": true,
}
},
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react" /* Specify what JSX code is generated. */,
"experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
// "module": "es2022" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": [
"./node_modules/@types"
], /* Specify multiple folders that act like `./node_modules/@types`. */
"types": ["node", "jest"], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./build/dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
"strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
"strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
"strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
"noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
"useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
"noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
"noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
"exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
"noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
"noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
"noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
"noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
"resolveJsonModule": true
}
}

View File

@@ -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="[&amp;>:first-child]:mt-0 [&amp;>: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 [&amp;>:first-child]:mt-0 [&amp;>: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>

View File

@@ -3,23 +3,15 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
FROM node:21.7.3-alpine3.18
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5
RUN npm config set fetch-retry-mintimeout 100000
RUN npm config set fetch-retry-maxtimeout 600000
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
@@ -44,17 +36,33 @@ COPY ./Common /usr/src/Common
WORKDIR /usr/src/Model
COPY ./Model/package*.json /usr/src/Model/
# Set version in ./Model/package.json to the APP_VERSION
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/Model/package.json
RUN npm install
COPY ./Model /usr/src/Model
WORKDIR /usr/src/CommonServer
COPY ./CommonServer/package*.json /usr/src/CommonServer/
# Set version in ./CommonServer/package.json to the APP_VERSION
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/CommonServer/package.json
RUN npm install
COPY ./CommonServer /usr/src/CommonServer
# Install CommonUI
WORKDIR /usr/src/CommonUI
COPY ./CommonUI/package*.json /usr/src/CommonUI/
# Set version in ./CommonUI/package.json to the APP_VERSION
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/CommonUI/package.json
RUN npm install --force
COPY ./CommonUI /usr/src/CommonUI
@@ -72,11 +80,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 +95,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 }}

View File

@@ -35,7 +35,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d
If you arent 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 youre 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 youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.

View File

@@ -1,8 +1,7 @@
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";
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
import logger from "CommonServer/Utils/Logger";
import App from "CommonServer/Utils/StartServer";
export const APP_NAME: string = "accounts";

View File

@@ -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');
}

View File

@@ -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"
}

File diff suppressed because it is too large Load Diff

View File

@@ -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,19 @@
},
"dependencies": {
"Common": "file:../Common",
"ejs": "^3.1.10",
"CommonServer": "file:../CommonServer",
"CommonUI": "file:../CommonUI",
"css-loader": "^6.11.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"file-loader": "^6.2.0",
"Model": "file:../Model",
"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 +48,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"
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -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">
@@ -75,11 +73,18 @@
width: auto;
}
</style>
<script src="/accounts/assets/js/tailwind-3.4.5.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<title>OneUptime Accounts</title>
<!-- Preload light, regular, medium and bold, which are fonts that are used on home -->
<link rel="preload" href="/accounts/assets/fonts/camphor/font1.woff2" as="font" type="font/woff2" crossorigin="">
<link rel="preload" href="/accounts/assets/fonts/camphor/font2.woff2" as="font" type="font/woff2" crossorigin="">
<link rel="preload" href="/accounts/assets/fonts/camphor/font3.woff2" as="font" type="font/woff2" crossorigin="">
<link rel="preload" href="/accounts/assets/fonts/camphor/font4.woff2" as="font" type="font/woff2" crossorigin="">
<!--
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/
@@ -97,16 +102,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: {

View File

@@ -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 "CommonUI/src/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>
);
}

View File

@@ -1,11 +1,11 @@
import App from "./App";
import Telemetry from "Common/UI/Utils/Telemetry/Telemetry";
import Telemetry from "CommonUI/src/Utils/Telemetry";
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
Telemetry.init({
serviceName: "accounts",
serviceName: "Accounts",
});
const root: any = ReactDOM.createRoot(

View File

@@ -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;

View File

@@ -1,11 +1,11 @@
import { FORGOT_PASSWORD_API_URL } from "../Utils/ApiPaths";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
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 OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
import User from "Common/Models/DatabaseModels/User";
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import User from "Model/Models/User";
import React, { useState } from "react";
const ForgotPassword: () => JSX.Element = () => {
@@ -56,7 +56,6 @@ const ForgotPassword: () => JSX.Element = () => {
title: "Email",
fieldType: FormFieldSchemaType.Email,
required: true,
disableSpellCheck: true,
},
]}
onSuccess={() => {

View File

@@ -1,34 +1,19 @@
import {
LOGIN_API_URL,
VERIFY_TOTP_AUTH_API_URL,
GENERATE_WEBAUTHN_AUTH_OPTIONS_API_URL,
VERIFY_WEBAUTHN_AUTH_API_URL,
} from "../Utils/ApiPaths";
import { LOGIN_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 } from "Common/UI/Components/Forms/ModelForm";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
import Link from "Common/UI/Components/Link/Link";
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 Navigation from "Common/UI/Utils/Navigation";
import UserUtil from "Common/UI/Utils/User";
import User from "Common/Models/DatabaseModels/User";
import { JSONObject } from "Common/Types/JSON";
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import { DASHBOARD_URL } from "CommonUI/src/Config";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import UiAnalytics from "CommonUI/src/Utils/Analytics";
import LoginUtil from "CommonUI/src/Utils/Login";
import Navigation from "CommonUI/src/Utils/Navigation";
import UserUtil from "CommonUI/src/Utils/User";
import User from "Model/Models/User";
import React from "react";
import useAsyncEffect from "use-async-effect";
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;
@@ -39,40 +24,6 @@ const LoginPage: () => JSX.Element = () => {
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
const [showTwoFactorAuth, setShowTwoFactorAuth] =
React.useState<boolean>(false);
const [totpAuthList, setTotpAuthList] = React.useState<UserTotpAuth[]>([]);
const [webAuthnList, setWebAuthnList] = React.useState<UserWebAuthn[]>([]);
const [selectedTotpAuth, setSelectedTotpAuth] = React.useState<
UserTotpAuth | 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>("");
useAsyncEffect(async () => {
if (Navigation.getQueryStringByName("email")) {
setInitialValues({
@@ -81,110 +32,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 => {
if (user instanceof User && user && user.email) {
UiAnalytics.userAuth(user.email);
UiAnalytics.capture("accounts/login");
}
LoginUtil.login({
user: user,
token: miscData ? miscData["token"] : undefined,
});
};
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="">
@@ -193,261 +40,89 @@ 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">
Sign in to your account
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Join thousands of business that use OneUptime to help them stay
online all the time.
</p>
</>
)}
{showTwoFactorAuth && (
<>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Two Factor Authentication
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Select two factor authentication method. You will be asked to
enter a code from the selected method.
</p>
</>
)}
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Sign in to your account
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Join thousands of business that use OneUptime to help them stay online
all the time.
</p>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
{!showTwoFactorAuth && (
<ModelForm<User>
modelType={User}
id="login-form"
name="Login"
fields={[
{
field: {
email: true,
},
fieldType: FormFieldSchemaType.Email,
placeholder: "jeff@example.com",
required: true,
disabled: Boolean(initialValues && initialValues["email"]),
title: "Email",
dataTestId: "email",
disableSpellCheck: true,
<ModelForm<User>
modelType={User}
id="login-form"
name="Login"
fields={[
{
field: {
email: 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,
fieldType: FormFieldSchemaType.Email,
placeholder: "jeff@example.com",
required: true,
disabled: Boolean(initialValues && initialValues["email"]),
title: "Email",
dataTestId: "email",
},
{
field: {
password: true,
},
]}
createOrUpdateApiUrl={apiUrl}
formType={FormType.Create}
submitButtonText={"Login"}
onBeforeCreate={(data: User) => {
setInitialValues(User.toJSON(data, User));
return Promise.resolve(data);
}}
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)
) {
const totpAuthList: Array<UserTotpAuth> =
UserTotpAuth.fromJSONArray(
(miscData as JSONObject)["totpAuthList"] as JSONArray,
UserTotpAuth,
);
const webAuthnList: Array<UserWebAuthn> =
UserWebAuthn.fromJSONArray(
(miscData as JSONObject)["webAuthnList"] as JSONArray,
UserWebAuthn,
);
setTotpAuthList(totpAuthList);
setWebAuthnList(webAuthnList);
setShowTwoFactorAuth(true);
return;
}
login(value as User, miscData as JSONObject);
}}
maxPrimaryButtonWidth={true}
footer={
<div className="actions text-center mt-4 hover:underline fw-semibold">
<div>
<Link to={new Route("/accounts/sso")}>
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
Use single sign-on (SSO) instead
</div>
</Link>
</div>
</div>
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"}
onSuccess={(value: User, miscData: JSONObject | undefined) => {
if (value && value.email) {
UiAnalytics.userAuth(value.email);
UiAnalytics.capture("accounts/login");
}
/>
)}
{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>
LoginUtil.login({
user: value,
token: miscData ? miscData["token"] : undefined,
});
}}
maxPrimaryButtonWidth={true}
footer={
<div className="actions text-center mt-4 hover:underline fw-semibold">
<div>
<Link to={new Route("/accounts/sso")}>
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
Use single sign-on (SSO) instead
</div>
);
},
)}
</div>
)}
{showTwoFactorAuth && selectedWebAuthn && (
<div className="text-center">
<div className="text-lg font-medium mb-4">
Authenticating with Security Key
</Link>
</div>
</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 && (
<BasicForm
id="two-factor-auth-form"
name="Two Factor Auth"
fields={[
{
field: {
code: true,
},
title: "Code",
description: "Enter the code from your authenticator app",
required: true,
dataTestId: "code",
fieldType: FormFieldSchemaType.Text,
},
]}
submitButtonText={"Login"}
maxPrimaryButtonWidth={true}
isLoading={isTwoFactorAuthLoading}
error={twofactorAuthError}
onSubmit={async (data: JSONObject) => {
setIsTwoFactorAuthLoading(true);
try {
const code: string = data["code"] as string;
const twoFactorAuthId: string =
selectedTotpAuth!.id?.toString() as string;
const result: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post({
url: VERIFY_TOTP_AUTH_API_URL,
data: {
data: {
...initialValues,
code: code,
twoFactorAuthId: twoFactorAuthId,
},
},
});
if (result instanceof HTTPErrorResponse) {
throw result;
}
const user: User = User.fromJSON(
result["data"] as JSONObject,
User,
) as User;
const miscData: JSONObject = (result["data"] as JSONObject)[
"miscData"
] as JSONObject;
login(user as User, miscData as JSONObject);
} catch (error) {
setTwoFactorAuthError(
API.getFriendlyErrorMessage(error as Error),
);
}
setIsTwoFactorAuthLoading(false);
}}
/>
)}
}
/>
</div>
<div className="mt-10 text-center">
{!selectedTotpAuth && !selectedWebAuthn && (
<div className="text-muted mb-0 text-gray-500">
Don&apos;t have an account?{" "}
<Link
to={new Route("/accounts/register")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Register.
</Link>
</div>
)}
{selectedTotpAuth || selectedWebAuthn ? (
<div className="text-muted mb-0 text-gray-500">
<Link
onClick={() => {
setSelectedTotpAuth(undefined);
setSelectedWebAuthn(undefined);
}}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Select a different two factor authentication method
</Link>
</div>
) : (
<></>
)}
<div className="text-muted mb-0 text-gray-500">
Don&apos;t have an account?{" "}
<Link
to={new Route("/accounts/register")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Register.
</Link>
</div>
</div>
</div>
</div>

View File

@@ -2,22 +2,22 @@ import { SERVICE_PROVIDER_LOGIN_URL } from "../Utils/ApiPaths";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import { JSONArray, JSONObject } from "Common/Types/JSON";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
import Link from "Common/UI/Components/Link/Link";
import { DASHBOARD_URL, IDENTITY_URL } from "Common/UI/Config";
import OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
import Navigation from "Common/UI/Utils/Navigation";
import UserUtil from "Common/UI/Utils/User";
import User from "Common/Models/DatabaseModels/User";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import { DASHBOARD_URL, IDENTITY_URL } from "CommonUI/src/Config";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import Navigation from "CommonUI/src/Utils/Navigation";
import UserUtil from "CommonUI/src/Utils/User";
import User from "Model/Models/User";
import React, { ReactElement, useState } from "react";
import ProjectSSO from "Common/Models/DatabaseModels/ProjectSso";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import API from "Common/UI/Utils/API/API";
import BasicForm from "Common/UI/Components/Forms/BasicForm";
import ProjectSSO from "Model/Models/ProjectSso";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import API from "CommonUI/src/Utils/API/API";
import BasicForm from "CommonUI/src/Components/Forms/BasicForm";
import Email from "Common/Types/Email";
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
import HTTPResponse from "Common/Types/API/HTTPResponse";
import StaticModelList from "Common/UI/Components/ModelList/StaticModelList";
import StaticModelList from "CommonUI/src/Components/ModelList/StaticModelList";
const LoginPage: () => JSX.Element = () => {
const apiUrl: URL = SERVICE_PROVIDER_LOGIN_URL;
@@ -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}
@@ -209,7 +208,7 @@ const LoginPage: () => JSX.Element = () => {
<div>
<Link to={new Route("/accounts/login")}>
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
Use username and password instead.
Use username and password insead.
</div>
</Link>
</div>

View File

@@ -3,23 +3,23 @@ import Route from "Common/Types/API/Route";
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 } 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 Link from "Common/UI/Components/Link/Link";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
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";
import LocalStorage from "Common/UI/Utils/LocalStorage";
import LoginUtil from "Common/UI/Utils/Login";
import ModelAPI, { ListResult } from "Common/UI/Utils/ModelAPI/ModelAPI";
import Navigation from "Common/UI/Utils/Navigation";
import UserUtil from "Common/UI/Utils/User";
import Reseller from "Common/Models/DatabaseModels/Reseller";
import User from "Common/Models/DatabaseModels/User";
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import Fields from "CommonUI/src/Components/Forms/Types/Fields";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import { BILLING_ENABLED, DASHBOARD_URL } from "CommonUI/src/Config";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import BaseAPI from "CommonUI/src/Utils/API/API";
import UiAnalytics from "CommonUI/src/Utils/Analytics";
import LocalStorage from "CommonUI/src/Utils/LocalStorage";
import LoginUtil from "CommonUI/src/Utils/Login";
import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI";
import Navigation from "CommonUI/src/Utils/Navigation";
import UserUtil from "CommonUI/src/Utils/User";
import Reseller from "Model/Models/Reseller";
import User from "Model/Models/User";
import React, { useState } from "react";
import useAsyncEffect from "use-async-effect";
@@ -104,7 +104,6 @@ const RegisterPage: () => JSX.Element = () => {
disabled: Boolean(initialValues && initialValues["email"]),
title: "Email",
dataTestId: "email",
disableSpellCheck: true,
},
{
field: {
@@ -115,7 +114,6 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
title: "Full Name",
dataTestId: "name",
disableSpellCheck: true,
},
];
@@ -130,7 +128,6 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
title: "Company Name",
dataTestId: "companyName",
disableSpellCheck: true,
},
]);
@@ -162,7 +159,6 @@ const RegisterPage: () => JSX.Element = () => {
title: "Password",
required: true,
dataTestId: "password",
disableSpellCheck: true,
},
{
field: {
@@ -179,12 +175,11 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
showEvenIfPermissionDoesNotExist: true,
dataTestId: "confirmPassword",
disableSpellCheck: true,
},
]);
if (error) {
return <ErrorMessage message={error} />;
return <ErrorMessage error={error} />;
}
if (isLoading) {

View File

@@ -1,12 +1,12 @@
import { RESET_PASSWORD_API_URL } from "../Utils/ApiPaths";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
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 OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
import Navigation from "Common/UI/Utils/Navigation";
import User from "Common/Models/DatabaseModels/User";
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import Navigation from "CommonUI/src/Utils/Navigation";
import User from "Model/Models/User";
import React, { useState } from "react";
const RegisterPage: () => JSX.Element = () => {
@@ -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}

View File

@@ -3,14 +3,14 @@ import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import ObjectID from "Common/Types/ObjectID";
import { FormType } from "Common/UI/Components/Forms/ModelForm";
import Link from "Common/UI/Components/Link/Link";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
import API from "Common/UI/Utils/API/API";
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
import Navigation from "Common/UI/Utils/Navigation";
import EmailVerificationToken from "Common/Models/DatabaseModels/EmailVerificationToken";
import { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import Link from "CommonUI/src/Components/Link/Link";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import API from "CommonUI/src/Utils/API/API";
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
import Navigation from "CommonUI/src/Utils/Navigation";
import EmailVerificationToken from "Model/Models/EmailVerificationToken";
import React, { useEffect, useState } from "react";
const VerifyEmail: () => JSX.Element = () => {

View File

@@ -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 "CommonUI/src/Config";
export const SIGNUP_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
new Route("/signup"),
@@ -9,18 +9,6 @@ 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(
IDENTITY_URL,
).addRoute(new Route("/verify-webauthn-auth"));
export const SERVICE_PROVIDER_LOGIN_URL: URL = URL.fromURL(
IDENTITY_URL,
).addRoute(new Route("/service-provider-login"));

View 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",
};

View File

@@ -3,23 +3,15 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
FROM node:21.7.3-alpine3.18
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5
RUN npm config set fetch-retry-mintimeout 100000
RUN npm config set fetch-retry-maxtimeout 600000
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
@@ -43,17 +35,33 @@ RUN npm install
COPY ./Common /usr/src/Common
WORKDIR /usr/src/Model
COPY ./Model/package*.json /usr/src/Model/
# Set version in ./Model/package.json to the APP_VERSION
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/Model/package.json
RUN npm install
COPY ./Model /usr/src/Model
WORKDIR /usr/src/CommonServer
COPY ./CommonServer/package*.json /usr/src/CommonServer/
# Set version in ./CommonServer/package.json to the APP_VERSION
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/CommonServer/package.json
RUN npm install
COPY ./CommonServer /usr/src/CommonServer
# Install CommonUI
WORKDIR /usr/src/CommonUI
COPY ./CommonUI/package*.json /usr/src/CommonUI/
# Set version in ./CommonUI/package.json to the APP_VERSION
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/CommonUI/package.json
RUN npm install --force
COPY ./CommonUI /usr/src/CommonUI
@@ -70,20 +78,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 }}

View File

@@ -35,7 +35,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d
If you arent 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 youre 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 youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.

View File

@@ -1,74 +1,12 @@
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import Express, {
ExpressApplication,
ExpressRequest,
ExpressResponse,
} 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";
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
import logger from "CommonServer/Utils/Logger";
import App from "CommonServer/Utils/StartServer";
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

View File

@@ -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');
}

View File

@@ -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"
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,19 +4,21 @@
"private": false,
"dependencies": {
"Common": "file:../Common",
"ejs": "^3.1.10",
"CommonServer": "file:../CommonServer",
"CommonUI": "file:../CommonUI",
"dotenv": "^16.4.5",
"file-loader": "^6.2.0",
"Model": "file:../Model",
"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 +43,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"
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -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,7 +19,6 @@
})(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">
@@ -68,13 +66,20 @@
width: auto;
}
</style>
<script src="/admin/assets/js/tailwind-3.4.5.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="/admin/env.js"></script>
<title>OneUptime Admin Dashboard</title>
<!-- Preload light, regular, medium and bold, which are fonts that are used on home
<link rel="preload" href="/admin/assets/fonts/camphor/font1.woff2" as="font" type="font/woff2" crossorigin="">
<link rel="preload" href="/admin/assets/fonts/camphor/font2.woff2" as="font" type="font/woff2" crossorigin="">
<link rel="preload" href="/admin/assets/fonts/camphor/font3.woff2" as="font" type="font/woff2" crossorigin="">
<link rel="preload" href="/admin/assets/fonts/camphor/font4.woff2" as="font" type="font/woff2" crossorigin=""> -->
<!--
manifest.json provides metadata used when your web app is installed on a
@@ -91,16 +96,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="/admin/dist/Index.js"></script>
<script src="/admin/dist/bundle.js"></script>
<script>
tailwind.config = {
theme: {

View File

@@ -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";
@@ -13,9 +12,9 @@ import Users from "./Pages/Users/Index";
import PageMap from "./Utils/PageMap";
import RouteMap from "./Utils/RouteMap";
import URL from "Common/Types/API/URL";
import { ACCOUNTS_URL, DASHBOARD_URL } from "Common/UI/Config";
import Navigation from "Common/UI/Utils/Navigation";
import User from "Common/UI/Utils/User";
import { ACCOUNTS_URL, DASHBOARD_URL } from "CommonUI/src/Config";
import Navigation from "CommonUI/src/Utils/Navigation";
import User from "CommonUI/src/Utils/User";
import React from "react";
import {
Route as PageRoute,
@@ -24,11 +23,6 @@ import {
useNavigate,
useParams,
} 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";
const App: () => JSX.Element = () => {
Navigation.setNavigateHook(useNavigate());
@@ -67,31 +61,6 @@ const App: () => JSX.Element = () => {
element={<Users />}
/>
<PageRoute
path={RouteMap[PageMap.USER_VIEW]?.toString() || ""}
element={<UserView />}
/>
<PageRoute
path={RouteMap[PageMap.USER_SETTINGS]?.toString() || ""}
element={<UserSettings />}
/>
<PageRoute
path={RouteMap[PageMap.USER_DELETE]?.toString() || ""}
element={<UserDelete />}
/>
<PageRoute
path={RouteMap[PageMap.PROJECT_VIEW]?.toString() || ""}
element={<ProjectView />}
/>
<PageRoute
path={RouteMap[PageMap.PROJECT_DELETE]?.toString() || ""}
element={<ProjectDelete />}
/>
<PageRoute
path={RouteMap[PageMap.LOGOUT]?.toString() || ""}
element={<Logout />}
@@ -112,11 +81,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 />}

View File

@@ -5,9 +5,9 @@ import BadDataException from "Common/Types/Exception/BadDataException";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import { JSONObject } from "Common/Types/JSON";
import API from "Common/Utils/API";
import Footer from "Common/UI/Components/Footer/Footer";
import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal";
import { HOST, HTTP_PROTOCOL } from "Common/UI/Config";
import Footer from "CommonUI/src/Components/Footer/Footer";
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
import { HOST, HTTP_PROTOCOL } from "CommonUI/src/Config";
import React from "react";
const DashboardFooter: () => JSX.Element = () => {
@@ -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={[
{

View File

@@ -1,11 +1,10 @@
import Help from "./Help";
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 Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
import Header from "CommonUI/src/Components/Header/Header";
import { DASHBOARD_URL } from "CommonUI/src/Config";
import Navigation from "CommonUI/src/Utils/Navigation";
import React, { FunctionComponent, ReactElement } from "react";
const DashboardHeader: FunctionComponent = (): ReactElement => {
@@ -28,7 +27,6 @@ const DashboardHeader: FunctionComponent = (): ReactElement => {
}
rightComponents={
<>
<EditionLabel className="mr-3 hidden md:inline-flex" />
<Button
title="Exit Admin"
buttonStyle={ButtonStyleType.NORMAL}

View File

@@ -1,9 +1,9 @@
import URL from "Common/Types/API/URL";
import IconProp from "Common/Types/Icon/IconProp";
import HeaderIconDropdownButton from "Common/UI/Components/Header/HeaderIconDropdownButton";
import IconDropdownItem from "Common/UI/Components/Header/IconDropdown/IconDropdownItem";
import IconDropdownMenu from "Common/UI/Components/Header/IconDropdown/IconDropdownMenu";
import IconDropdownRow from "Common/UI/Components/Header/IconDropdown/IconDropdownRow";
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem";
import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu";
import IconDropdownRow from "CommonUI/src/Components/Header/IconDropdown/IconDropdownRow";
import React, { ReactElement, useState } from "react";
const Help: () => JSX.Element = (): ReactElement => {
@@ -37,7 +37,7 @@ const Help: () => JSX.Element = (): ReactElement => {
setIsDropdownVisible(false);
}}
url={URL.fromString(
"https://join.slack.com/t/oneuptimesupport/shared_invite/zt-2pz5p1uhe-Fpmc7bv5ZE5xRMe7qJnwmA",
"https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ",
)}
/>
<IconDropdownItem

View File

@@ -1,7 +1,7 @@
// Tailwind
import Route from "Common/Types/API/Route";
import Image from "Common/UI/Components/Image/Image";
import OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
import Image from "CommonUI/src/Components/Image/Image";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import React, { FunctionComponent, ReactElement } from "react";
export interface ComponentProps {
@@ -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"}

View File

@@ -1,7 +1,7 @@
import IconProp from "Common/Types/Icon/IconProp";
import HeaderIconDropdownButton from "Common/UI/Components/Header/HeaderIconDropdownButton";
import NotificationItem from "Common/UI/Components/Header/Notifications/NotificationItem";
import Notifications from "Common/UI/Components/Header/Notifications/Notifications";
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
import NotificationItem from "CommonUI/src/Components/Header/Notifications/NotificationItem";
import Notifications from "CommonUI/src/Components/Header/Notifications/Notifications";
import React, { ReactElement, useState } from "react";
const DashboardHeader: () => JSX.Element = (): ReactElement => {

View File

@@ -1,16 +1,16 @@
import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan";
import IconProp from "Common/Types/Icon/IconProp";
import { FormType } from "Common/UI/Components/Forms/ModelForm";
import Field from "Common/UI/Components/Forms/Types/Field";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
import ProjectPicker from "Common/UI/Components/Header/ProjectPicker/ProjectPicker";
import ModelFormModal from "Common/UI/Components/ModelFormModal/ModelFormModal";
import { RadioButton } from "Common/UI/Components/RadioButtons/GroupRadioButtons";
import Toggle from "Common/UI/Components/Toggle/Toggle";
import { BILLING_ENABLED, getAllEnvVars } from "Common/UI/Config";
import { GetReactElementFunction } from "Common/UI/Types/FunctionTypes";
import ProjectUtil from "Common/UI/Utils/Project";
import Project from "Common/Models/DatabaseModels/Project";
import { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import Field from "CommonUI/src/Components/Forms/Types/Field";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import ProjectPicker from "CommonUI/src/Components/Header/ProjectPicker/ProjectPicker";
import ModelFormModal from "CommonUI/src/Components/ModelFormModal/ModelFormModal";
import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons";
import Toggle from "CommonUI/src/Components/Toggle/Toggle";
import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config";
import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes";
import ProjectUtil from "CommonUI/src/Utils/Project";
import Project from "Model/Models/Project";
import React, {
FunctionComponent,
ReactElement,
@@ -137,7 +137,7 @@ const DashboardProjectPicker: FunctionComponent<ComponentProps> = (
minLength: 6,
},
footerElement: getFooter(),
fieldType: FormFieldSchemaType.OptionChooserButton,
fieldType: FormFieldSchemaType.RadioButton,
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
getAllEnvVars(),
).map((plan: SubscriptionPlan): RadioButton => {

View File

@@ -1,5 +1,5 @@
import SearchBox from "Common/UI/Components/Header/SearchBox";
import Project from "Common/Models/DatabaseModels/Project";
import SearchBox from "CommonUI/src/Components/Header/SearchBox";
import Project from "Model/Models/Project";
import React, { FunctionComponent, ReactElement } from "react";
export interface ComponentProps {

View File

@@ -2,13 +2,13 @@ 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 HeaderIconDropdownButton from "Common/UI/Components/Header/HeaderIconDropdownButton";
import IconDropdownItem from "Common/UI/Components/Header/IconDropdown/IconDropdownItem";
import IconDropdownMenu from "Common/UI/Components/Header/IconDropdown/IconDropdownMenu";
import { DASHBOARD_URL } from "Common/UI/Config";
import BlankProfilePic from "Common/UI/Images/users/blank-profile.svg";
import Navigation from "Common/UI/Utils/Navigation";
import User from "Common/UI/Utils/User";
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem";
import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu";
import { DASHBOARD_URL } from "CommonUI/src/Config";
import BlankProfilePic from "CommonUI/src/Images/users/blank-profile.svg";
import Navigation from "CommonUI/src/Utils/Navigation";
import User from "CommonUI/src/Utils/User";
import React, { FunctionComponent, ReactElement, useState } from "react";
const DashboardUserProfile: FunctionComponent = (): ReactElement => {

View File

@@ -1,8 +1,8 @@
import Footer from "../Footer/Footer";
import Header from "../Header/Header";
import NavBar from "../NavBar/NavBar";
import MasterPage from "Common/UI/Components/MasterPage/MasterPage";
import TopAlert from "Common/UI/Components/TopAlert/TopAlert";
import MasterPage from "CommonUI/src/Components/MasterPage/MasterPage";
import TopAlert from "CommonUI/src/Components/TopAlert/TopAlert";
import React, { FunctionComponent, ReactElement } from "react";
export interface ComponentProps {

View File

@@ -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 "CommonUI/src/Components/Navbar/NavBar";
import NavBarItem from "CommonUI/src/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;

View File

@@ -1,11 +1,11 @@
import App from "./App";
import Telemetry from "Common/UI/Utils/Telemetry/Telemetry";
import Telemetry from "CommonUI/src/Utils/Telemetry";
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
Telemetry.init({
serviceName: "admin-dashboard",
serviceName: "AdminDashboard",
});
const root: any = ReactDOM.createRoot(

View File

@@ -1,8 +1,8 @@
import PageMap from "../../Utils/PageMap";
import RouteMap from "../../Utils/RouteMap";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import Page from "Common/UI/Components/Page/Page";
import Navigation from "Common/UI/Utils/Navigation";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import Page from "CommonUI/src/Components/Page/Page";
import Navigation from "CommonUI/src/Utils/Navigation";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
const Init: FunctionComponent = (): ReactElement => {

View File

@@ -2,13 +2,13 @@ import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import Page from "Common/UI/Components/Page/Page";
import { ACCOUNTS_URL } from "Common/UI/Config";
import UiAnalytics from "Common/UI/Utils/Analytics";
import Navigation from "Common/UI/Utils/Navigation";
import UserUtil from "Common/UI/Utils/User";
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import Page from "CommonUI/src/Components/Page/Page";
import { ACCOUNTS_URL } from "CommonUI/src/Config";
import UiAnalytics from "CommonUI/src/Utils/Analytics";
import Navigation from "CommonUI/src/Utils/Navigation";
import UserUtil from "CommonUI/src/Utils/User";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
const Logout: FunctionComponent = (): ReactElement => {
@@ -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);
};
@@ -41,7 +41,7 @@ const Logout: FunctionComponent = (): ReactElement => {
]}
>
{!error ? <PageLoader isVisible={true} /> : <></>}
{error ? <ErrorMessage message={error} /> : <></>}
{error ? <ErrorMessage error={error} /> : <></>}
</Page>
);
};

View File

@@ -3,18 +3,18 @@ import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan";
import Field from "Common/UI/Components/Forms/Types/Field";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
import Page from "Common/UI/Components/Page/Page";
import { RadioButton } from "Common/UI/Components/RadioButtons/GroupRadioButtons";
import Toggle from "Common/UI/Components/Toggle/Toggle";
import FieldType from "Common/UI/Components/Types/FieldType";
import { BILLING_ENABLED, getAllEnvVars } from "Common/UI/Config";
import { GetReactElementFunction } from "Common/UI/Types/FunctionTypes";
import Navigation from "Common/UI/Utils/Navigation";
import Project from "Common/Models/DatabaseModels/Project";
import User from "Common/Models/DatabaseModels/User";
import Field from "CommonUI/src/Components/Forms/Types/Field";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
import Page from "CommonUI/src/Components/Page/Page";
import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons";
import Toggle from "CommonUI/src/Components/Toggle/Toggle";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config";
import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes";
import Navigation from "CommonUI/src/Utils/Navigation";
import Project from "Model/Models/Project";
import User from "Model/Models/User";
import React, {
FunctionComponent,
ReactElement,
@@ -75,7 +75,7 @@ const Projects: FunctionComponent = (): ReactElement => {
minLength: 6,
},
footerElement: getFooter(),
fieldType: FormFieldSchemaType.OptionChooserButton,
fieldType: FormFieldSchemaType.RadioButton,
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
getAllEnvVars(),
).map((plan: SubscriptionPlan): RadioButton => {
@@ -187,7 +187,7 @@ const Projects: FunctionComponent = (): ReactElement => {
isEditable={false}
isCreateable={true}
name="Projects"
isViewable={true}
isViewable={false}
cardProps={{
title: "Projects",
description: "Here is a list of proejcts in OneUptime.",
@@ -241,10 +241,8 @@ const Projects: FunctionComponent = (): ReactElement => {
},
title: "Created At",
type: FieldType.DateTime,
hideOnMobile: true,
},
]}
userPreferencesKey="admin-projects-table"
/>
</Page>
);

View File

@@ -1,52 +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 ModelDelete from "Common/UI/Components/ModelDelete/ModelDelete";
import Navigation from "Common/UI/Utils/Navigation";
import React, { FunctionComponent, ReactElement } from "react";
import SideMenuComponent from "./SideMenu";
import Project from "Common/Models/DatabaseModels/Project";
import ModelPage from "Common/UI/Components/Page/ModelPage";
const DeletePage: FunctionComponent = (): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return (
<ModelPage<Project>
modelId={modelId}
modelNameField="name"
modelType={Project}
title={"Project"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
},
{
title: "Projects",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECTS] as Route,
),
},
{
title: "Project",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECT_VIEW] as Route,
),
},
]}
sideMenu={<SideMenuComponent modelId={modelId} />}
>
<ModelDelete
modelType={Project}
modelId={modelId}
onDeleteSuccess={() => {
Navigation.navigate(RouteMap[PageMap.PROJECTS] as Route);
}}
/>
</ModelPage>
);
};
export default DeletePage;

View File

@@ -1,90 +0,0 @@
import ObjectID from "Common/Types/ObjectID";
import PageMap from "../../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import Navigation from "Common/UI/Utils/Navigation";
import Project from "Common/Models/DatabaseModels/Project";
import React, { FunctionComponent, ReactElement } from "react";
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";
import ModelPage from "Common/UI/Components/Page/ModelPage";
import SideMenuComponent from "./SideMenu";
const Projects: FunctionComponent = (): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID();
return (
<ModelPage
modelId={modelId}
modelNameField="name"
modelType={Project}
title={"Project"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
},
{
title: "Projects",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECTS] as Route,
),
},
{
title: "Project",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECT_VIEW] as Route,
),
},
]}
sideMenu={<SideMenuComponent modelId={modelId} />}
>
<div>
<CardModelDetail<Project>
name="Project"
cardProps={{
title: "Project",
description: "Project details",
}}
isEditable={true}
editButtonText="Edit Project"
formFields={[
{
field: {
name: true,
},
title: "Name",
fieldType: FormFieldSchemaType.Text,
required: true,
},
]}
modelDetailProps={{
modelType: Project,
id: "model-detail-user",
fields: [
{
field: {
_id: true,
},
title: "Project ID",
fieldType: FieldType.Text,
placeholder: "-",
},
{
field: {
name: true,
},
title: "Name",
fieldType: FieldType.Text,
},
],
modelId: modelId,
}}
/>
</div>
</ModelPage>
);
};
export default Projects;

View File

@@ -1,53 +0,0 @@
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 ObjectID from "Common/Types/ObjectID";
import SideMenu from "Common/UI/Components/SideMenu/SideMenu";
import SideMenuItem from "Common/UI/Components/SideMenu/SideMenuItem";
import SideMenuSection from "Common/UI/Components/SideMenu/SideMenuSection";
import React, { FunctionComponent, ReactElement } from "react";
export interface SideMenuProps {
modelId: ObjectID;
}
const SideMenuComponent: FunctionComponent<SideMenuProps> = (
props: SideMenuProps,
): ReactElement => {
return (
<SideMenu>
<SideMenuSection title="Basic">
<SideMenuItem
link={{
title: "Overview",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECT_VIEW] as Route,
{
modelId: props.modelId,
},
),
}}
icon={IconProp.Info}
/>
</SideMenuSection>
<SideMenuSection title="Advanced">
<SideMenuItem
link={{
title: "Delete",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECT_DELETE] as Route,
{
modelId: props.modelId,
},
),
}}
icon={IconProp.Trash}
/>
</SideMenuSection>
</SideMenu>
);
};
export default SideMenuComponent;

View File

@@ -3,11 +3,11 @@ 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 FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
import Page from "CommonUI/src/Components/Page/Page";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import GlobalConfig from "Model/Models/GlobalConfig";
import React, { FunctionComponent, ReactElement } from "react";
const Settings: FunctionComponent = (): ReactElement => {

View File

@@ -3,11 +3,11 @@ 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 FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
import Page from "CommonUI/src/Components/Page/Page";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import GlobalConfig from "Model/Models/GlobalConfig";
import React, { FunctionComponent, ReactElement } from "react";
const Settings: FunctionComponent = (): ReactElement => {

View File

@@ -3,11 +3,11 @@ 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 FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
import Page from "CommonUI/src/Components/Page/Page";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import GlobalConfig from "Model/Models/GlobalConfig";
import React, { FunctionComponent, ReactElement } from "react";
const Settings: FunctionComponent = (): ReactElement => {
@@ -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(),
}}

View File

@@ -5,23 +5,21 @@ import Route from "Common/Types/API/Route";
import { Green, Red } from "Common/Types/BrandColors";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import ObjectID from "Common/Types/ObjectID";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail";
import Page from "Common/UI/Components/Page/Page";
import Pill from "Common/UI/Components/Pill/Pill";
import FieldType from "Common/UI/Components/Types/FieldType";
import DropdownUtil from "Common/UI/Utils/Dropdown";
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
import GlobalConfig, {
EmailServerType,
} from "Common/Models/DatabaseModels/GlobalConfig";
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
import Page from "CommonUI/src/Components/Page/Page";
import Pill from "CommonUI/src/Components/Pill/Pill";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import DropdownUtil from "CommonUI/src/Utils/Dropdown";
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
import GlobalConfig, { EmailServerType } from "Model/Models/GlobalConfig";
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 +41,7 @@ const Settings: FunctionComponent = (): ReactElement => {
if (globalConfig) {
setemailServerType(
globalConfig.emailServerType || EmailServerType.CustomSMTP,
globalConfig.emailServerType || EmailServerType.Internal,
);
}
@@ -61,7 +59,7 @@ const Settings: FunctionComponent = (): ReactElement => {
}
if (error) {
return <ErrorMessage message={error} />;
return <ErrorMessage error={error} />;
}
return (
@@ -106,7 +104,6 @@ const Settings: FunctionComponent = (): ReactElement => {
title: "Admin Notification Email",
fieldType: FormFieldSchemaType.Email,
required: false,
disableSpellCheck: true,
},
]}
modelDetailProps={{
@@ -127,7 +124,7 @@ const Settings: FunctionComponent = (): ReactElement => {
/>
<CardModelDetail
name="Email Server Settings"
name="Internal SMTP Settings"
cardProps={{
title: "Email Server Settings",
description:
@@ -172,7 +169,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 +197,6 @@ const Settings: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.Hostname,
required: true,
placeholder: "smtp.server.com",
disableSpellCheck: true,
},
{
field: {
@@ -231,7 +227,6 @@ const Settings: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.Text,
required: false,
placeholder: "emailuser",
disableSpellCheck: true,
},
{
field: {
@@ -242,7 +237,6 @@ const Settings: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.EncryptedText,
required: false,
placeholder: "Password",
disableSpellCheck: true,
},
{
field: {
@@ -253,9 +247,8 @@ const Settings: FunctionComponent = (): ReactElement => {
fieldType: FormFieldSchemaType.Email,
required: true,
description:
"Email used to log in to this SMTP Server. This is also the email your customers will see. ",
"This is the display email your team and customers see, when they receive emails from OneUptime.",
placeholder: "email@company.com",
disableSpellCheck: true,
},
{
field: {
@@ -268,7 +261,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={{

View File

@@ -7,16 +7,16 @@ import IsNull from "Common/Types/BaseDatabase/IsNull";
import { Green, Red } from "Common/Types/BrandColors";
import OneUptimeDate from "Common/Types/Date";
import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes";
import Banner from "Common/UI/Components/Banner/Banner";
import { ButtonStyleType } from "Common/UI/Components/Button/Button";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal";
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
import Page from "Common/UI/Components/Page/Page";
import ProbeElement from "Common/UI/Components/Probe/Probe";
import Statusbubble from "Common/UI/Components/StatusBubble/StatusBubble";
import FieldType from "Common/UI/Components/Types/FieldType";
import Probe from "Common/Models/DatabaseModels/Probe";
import Banner from "CommonUI/src/Components/Banner/Banner";
import { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
import Page from "CommonUI/src/Components/Page/Page";
import ProbeElement from "CommonUI/src/Components/Probe/Probe";
import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import Probe from "Model/Models/Probe";
import React, { FunctionComponent, ReactElement, useState } from "react";
const Settings: FunctionComponent = (): ReactElement => {
@@ -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"
@@ -101,7 +99,7 @@ const Settings: FunctionComponent = (): ReactElement => {
},
title: "Description",
fieldType: FormFieldSchemaType.LongText,
required: false,
required: true,
placeholder: "This probe is to monitor all the internal services.",
},
@@ -153,7 +151,7 @@ const Settings: FunctionComponent = (): ReactElement => {
description: true,
},
title: "Description",
type: FieldType.LongText,
type: FieldType.Text,
},
]}
columns={[
@@ -172,10 +170,8 @@ const Settings: FunctionComponent = (): ReactElement => {
field: {
description: true,
},
noValueMessage: "-",
title: "Description",
type: FieldType.LongText,
hideOnMobile: true,
type: FieldType.Text,
},
{
field: {
@@ -183,7 +179,6 @@ const Settings: FunctionComponent = (): ReactElement => {
},
title: "Status",
type: FieldType.Text,
getElement: (item: Probe): ReactElement => {
if (
item &&

View File

@@ -2,9 +2,9 @@ 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 SideMenu from "Common/UI/Components/SideMenu/SideMenu";
import SideMenuItem from "Common/UI/Components/SideMenu/SideMenuItem";
import SideMenuSection from "Common/UI/Components/SideMenu/SideMenuSection";
import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu";
import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem";
import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection";
import React, { ReactElement } from "react";
const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
@@ -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">

View File

@@ -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;

Some files were not shown because too many files have changed in this diff Show More