mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
1 Commits
safe-vm
...
msp-server
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c3e830a85 |
@@ -33,15 +33,6 @@ stop
|
||||
|
||||
nohup.out*
|
||||
|
||||
# Large directories not needed for Docker builds
|
||||
E2E/playwright-report
|
||||
E2E/test-results
|
||||
Terraform
|
||||
HelmChart
|
||||
Scripts
|
||||
.git
|
||||
GoSDK
|
||||
|
||||
encrypted-credentials.tar
|
||||
encrypted-credentials/
|
||||
|
||||
|
||||
17
.github/instructions/instructions.md
vendored
17
.github/instructions/instructions.md
vendored
@@ -1,17 +0,0 @@
|
||||
---
|
||||
applyTo: '**'
|
||||
---
|
||||
|
||||
# Building and Compiling
|
||||
|
||||
If you would like to compile or build any project. Please cd into the directory and run the following command:
|
||||
|
||||
```
|
||||
npm run compile
|
||||
```
|
||||
|
||||
Ths will make sure there are no type / syntax errors.
|
||||
|
||||
# Typescript Types.
|
||||
|
||||
Please do not use "any" types. Please create proper types where required.
|
||||
303
.github/workflows/build.yml
vendored
303
.github/workflows/build.yml
vendored
@@ -19,19 +19,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Accounts/Dockerfile .
|
||||
run: sudo docker build -f ./Accounts/Dockerfile .
|
||||
|
||||
docker-build-isolated-vm:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -42,19 +34,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./IsolatedVM/Dockerfile .
|
||||
run: sudo docker build -f ./IsolatedVM/Dockerfile .
|
||||
|
||||
docker-build-home:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -65,19 +49,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Home/Dockerfile .
|
||||
run: sudo docker build -f ./Home/Dockerfile .
|
||||
|
||||
docker-build-worker:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -88,19 +64,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Worker/Dockerfile .
|
||||
run: sudo docker build -f ./Worker/Dockerfile .
|
||||
|
||||
docker-build-workflow:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -111,19 +79,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Workflow/Dockerfile .
|
||||
run: sudo docker build -f ./Workflow/Dockerfile .
|
||||
|
||||
docker-build-api-reference:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -134,19 +94,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./APIReference/Dockerfile .
|
||||
run: sudo docker build -f ./APIReference/Dockerfile .
|
||||
|
||||
docker-build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -157,19 +109,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Docs/Dockerfile .
|
||||
run: sudo docker build -f ./Docs/Dockerfile .
|
||||
|
||||
|
||||
docker-build-otel-collector:
|
||||
@@ -181,19 +125,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./OTelCollector/Dockerfile .
|
||||
run: sudo docker build -f ./OTelCollector/Dockerfile .
|
||||
|
||||
docker-build-app:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -204,22 +140,29 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./App/Dockerfile .
|
||||
run: sudo docker build -f ./App/Dockerfile .
|
||||
|
||||
|
||||
docker-build-copilot:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./Copilot/Dockerfile .
|
||||
|
||||
docker-build-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -229,20 +172,12 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./E2E/Dockerfile .
|
||||
run: sudo docker build -f ./E2E/Dockerfile .
|
||||
|
||||
docker-build-admin-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -253,19 +188,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for home
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./AdminDashboard/Dockerfile .
|
||||
run: sudo docker build -f ./AdminDashboard/Dockerfile .
|
||||
|
||||
docker-build-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -276,19 +203,27 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for home
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Dashboard/Dockerfile .
|
||||
run: sudo docker build -f ./Dashboard/Dockerfile .
|
||||
|
||||
docker-build-haraka:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
# build images
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./Haraka/Dockerfile .
|
||||
|
||||
|
||||
docker-build-probe:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -299,19 +234,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Probe/Dockerfile .
|
||||
run: sudo docker build -f ./Probe/Dockerfile .
|
||||
|
||||
docker-build-probe-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -322,19 +249,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./ProbeIngest/Dockerfile .
|
||||
run: sudo docker build -f ./ProbeIngest/Dockerfile .
|
||||
|
||||
docker-build-server-monitor-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -345,21 +264,13 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./ServerMonitorIngest/Dockerfile .
|
||||
run: sudo docker build -f ./ServerMonitorIngest/Dockerfile .
|
||||
|
||||
docker-build-telemetry:
|
||||
docker-build-open-telemetry-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
@@ -368,19 +279,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./Telemetry/Dockerfile .
|
||||
run: sudo docker build -f ./OpenTelemetryIngest/Dockerfile .
|
||||
|
||||
docker-build-incoming-request-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -391,19 +294,26 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./IncomingRequestIngest/Dockerfile .
|
||||
run: sudo docker build -f ./IncomingRequestIngest/Dockerfile .
|
||||
|
||||
docker-build-fluent-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
# build image probe api
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./FluentIngest/Dockerfile .
|
||||
|
||||
docker-build-status-page:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -414,62 +324,23 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
run: npm run prerun
|
||||
|
||||
# build image for home
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./StatusPage/Dockerfile .
|
||||
run: sudo docker build -f ./StatusPage/Dockerfile .
|
||||
|
||||
docker-build-test-server:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preinstall
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: npm run prerun
|
||||
- name: Preinstall
|
||||
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 .
|
||||
|
||||
docker-build-ai-agent:
|
||||
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 ai agent service
|
||||
- name: build docker image
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 45
|
||||
max_attempts: 3
|
||||
command: sudo docker build --no-cache -f ./AIAgent/Dockerfile .
|
||||
run: sudo docker build -f ./TestServer/Dockerfile .
|
||||
|
||||
238
.github/workflows/compile.yml
vendored
238
.github/workflows/compile.yml
vendored
@@ -20,12 +20,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Accounts
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Accounts && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Accounts && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-isolated-vm:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -37,12 +32,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile IsolatedVM
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd IsolatedVM && npm install && npm run compile && npm run dep-check
|
||||
- run: cd IsolatedVM && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-common:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -53,12 +43,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- name: Compile Common
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Common && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Common && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-app:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -70,12 +55,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile App
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd App && npm install && npm run compile && npm run dep-check
|
||||
- run: cd App && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-home:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -87,12 +67,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Home
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Home && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Home && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-worker:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -104,12 +79,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Worker
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Worker && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Worker && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-workflow:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -121,12 +91,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Workflow
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Workflow && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Workflow && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-api-reference:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -138,12 +103,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile API Reference
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd APIReference && npm install && npm run compile && npm run dep-check
|
||||
- run: cd APIReference && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-docs-reference:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -155,12 +115,19 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Docs Reference
|
||||
uses: nick-fields/retry@v3
|
||||
- run: cd Docs && 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
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Docs && npm install && npm run compile && npm run dep-check
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd Copilot && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-nginx:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -173,12 +140,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
|
||||
- name: Compile Nginx
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Nginx && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Nginx && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-infrastructure-agent:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -188,12 +150,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
# Setup Go
|
||||
- uses: actions/setup-go@v5
|
||||
- name: Compile Infrastructure Agent
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd InfrastructureAgent && go build .
|
||||
- run: cd InfrastructureAgent && go build .
|
||||
|
||||
|
||||
compile-admin-dashboard:
|
||||
@@ -207,12 +164,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
|
||||
- name: Compile Admin Dashboard
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd AdminDashboard && npm install && npm run compile && npm run dep-check
|
||||
- run: cd AdminDashboard && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -225,12 +177,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
|
||||
- name: Compile Dashboard
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Dashboard && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Dashboard && npm install && npm run compile && npm run dep-check
|
||||
|
||||
|
||||
compile-e2e:
|
||||
@@ -244,12 +191,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: sudo apt-get update
|
||||
- run: cd Common && npm install
|
||||
- name: Compile E2E
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd E2E && npm install && npm run compile && npm run dep-check
|
||||
- run: cd E2E && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-probe:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -261,12 +203,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Probe
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Probe && npm install && npm run compile && npm run dep-check
|
||||
- run: cd Probe && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-probe-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -278,12 +215,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Probe Ingest
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd ProbeIngest && npm install && npm run compile && npm run dep-check
|
||||
- run: cd ProbeIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-server-monitor-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -295,14 +227,9 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Server Monitor Ingest
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd ServerMonitorIngest && npm install && npm run compile && npm run dep-check
|
||||
- run: cd ServerMonitorIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-telemetry:
|
||||
compile-open-telemetry-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
@@ -312,12 +239,7 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Telemetry
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd Telemetry && npm install && npm run compile && npm run dep-check
|
||||
- run: cd OpenTelemetryIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
|
||||
compile-incoming-request-ingest:
|
||||
@@ -330,12 +252,20 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Incoming Request Ingest
|
||||
uses: nick-fields/retry@v3
|
||||
- run: cd IncomingRequestIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-fluent-ingest:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd IncomingRequestIngest && npm install && npm run compile && npm run dep-check
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd FluentIngest && npm install && npm run compile && npm run dep-check
|
||||
|
||||
|
||||
compile-status-page:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -348,12 +278,7 @@ jobs:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
|
||||
- name: Compile Status Page
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd StatusPage && npm install && npm run compile && npm run dep-check
|
||||
- run: cd StatusPage && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-test-server:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -365,77 +290,4 @@ jobs:
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile Test Server
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd TestServer && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-mcp:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile MCP
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd MCP && npm update @oneuptime/common && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-mobile-app:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install && npm run compile
|
||||
- name: Compile MobileApp
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd MobileApp && npm install && npm run compile
|
||||
|
||||
compile-ai-agent:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
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 AIAgent
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd AIAgent && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-cli:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- name: Compile CLI
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd CLI && npm install && npm run compile && npm run dep-check
|
||||
- run: cd TestServer && npm install && npm run compile && npm run dep-check
|
||||
49
.github/workflows/npm-audit-fix.yml
vendored
49
.github/workflows/npm-audit-fix.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: NPM Audit Fix
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
npm-audit-fix:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Run npm audit fix across packages
|
||||
run: npm run audit-fix
|
||||
|
||||
- name: Detect changes
|
||||
id: changes
|
||||
run: |
|
||||
if git status --porcelain | grep .; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: "chore: npm audit fix"
|
||||
title: "chore: npm audit fix"
|
||||
body: |
|
||||
Automated npm audit fix run.
|
||||
Workflow: ${{ github.workflow }}
|
||||
Run ID: ${{ github.run_id }}
|
||||
branch: chore/npm-audit-fix
|
||||
delete-branch: true
|
||||
314
.github/workflows/publish-mcp.yml
vendored
Normal file
314
.github/workflows/publish-mcp.yml
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
name: Publish OneUptime MCP Server
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
paths:
|
||||
- 'MCP/**'
|
||||
- '.github/workflows/publish-mcp.yml'
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
paths:
|
||||
- 'MCP/**'
|
||||
- '.github/workflows/publish-mcp.yml'
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'MCP/package-lock.json'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./MCP
|
||||
run: npm ci
|
||||
|
||||
- name: Compile TypeScript
|
||||
working-directory: ./MCP
|
||||
run: npm run compile
|
||||
|
||||
- name: Run tests
|
||||
working-directory: ./MCP
|
||||
run: npm test
|
||||
|
||||
- name: Run audit
|
||||
working-directory: ./MCP
|
||||
run: npm audit --audit-level=low
|
||||
|
||||
build:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event_name == 'release'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'MCP/package-lock.json'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./MCP
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
working-directory: ./MCP
|
||||
run: npm run compile
|
||||
|
||||
- name: Generate OpenAPI tools dynamically
|
||||
working-directory: ./MCP
|
||||
run: |
|
||||
echo "Generating MCP tools from OpenAPI spec..."
|
||||
# This will be run at build time to ensure tools are up to date
|
||||
node -e "
|
||||
const generator = require('./build/Service/DynamicMCPGenerator.js');
|
||||
generator.default.initialize().then(() => {
|
||||
const models = generator.default.getAvailableModels();
|
||||
console.log('Generated tools for models:', models.map(m => m.name).join(', '));
|
||||
}).catch(console.error);
|
||||
"
|
||||
|
||||
- name: Create tarball
|
||||
working-directory: ./MCP
|
||||
run: npm pack
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mcp-server-package
|
||||
path: MCP/*.tgz
|
||||
|
||||
publish-npm:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'release'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: mcp-server-package
|
||||
path: MCP/
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./MCP
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
working-directory: ./MCP
|
||||
run: npm run compile
|
||||
|
||||
- name: Publish to NPM
|
||||
working-directory: ./MCP
|
||||
run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish-docker:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event_name == 'release'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
oneuptime/mcp-server
|
||||
ghcr.io/${{ github.repository }}/mcp-server
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./MCP
|
||||
file: ./MCP/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
publish-mcp-registry:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'release'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./MCP
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
working-directory: ./MCP
|
||||
run: npm run compile
|
||||
|
||||
- name: Create MCP registry entry
|
||||
working-directory: ./MCP
|
||||
run: |
|
||||
# Create a registry entry for the MCP server
|
||||
cat > mcp-registry.json << EOF
|
||||
{
|
||||
"name": "@oneuptime/mcp-server",
|
||||
"version": "$(node -p "require('./package.json').version")",
|
||||
"description": "OneUptime MCP Server - Dynamic monitoring and incident management tools",
|
||||
"author": "OneUptime <hello@oneuptime.com>",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://oneuptime.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime.git",
|
||||
"directory": "MCP"
|
||||
},
|
||||
"capabilities": {
|
||||
"tools": true,
|
||||
"resources": false,
|
||||
"prompts": false
|
||||
},
|
||||
"tags": [
|
||||
"monitoring",
|
||||
"alerting",
|
||||
"incident-management",
|
||||
"uptime",
|
||||
"observability",
|
||||
"oneuptime"
|
||||
],
|
||||
"executable": {
|
||||
"npm": "@oneuptime/mcp-server",
|
||||
"docker": "oneuptime/mcp-server:latest"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Upload MCP registry entry
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mcp-registry-entry
|
||||
path: MCP/mcp-registry.json
|
||||
|
||||
create-release-notes:
|
||||
needs: [test, build]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'release'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./MCP
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
working-directory: ./MCP
|
||||
run: npm run compile
|
||||
|
||||
- name: Generate release notes
|
||||
working-directory: ./MCP
|
||||
run: |
|
||||
# Generate comprehensive release notes
|
||||
cat > release-notes.md << EOF
|
||||
# OneUptime MCP Server Release
|
||||
|
||||
## Features
|
||||
- **Dynamic Tool Generation**: Automatically generates MCP tools from OpenAPI specification
|
||||
- **Complete CRUD Operations**: Support for Create, Read, Update, Delete operations on all OneUptime models
|
||||
- **Model Discovery**: Built-in tools to explore available models and their schemas
|
||||
- **OpenAPI Integration**: Direct integration with OneUptime's OpenAPI specification
|
||||
|
||||
## Available Models
|
||||
\`\`\`
|
||||
$(node -e "
|
||||
const generator = require('./build/Service/DynamicMCPGenerator.js');
|
||||
generator.default.initialize().then(() => {
|
||||
const models = generator.default.getAvailableModels();
|
||||
models.forEach(model => console.log('- ' + model.name + ': ' + model.description));
|
||||
}).catch(() => console.log('Unable to load models'));
|
||||
")
|
||||
\`\`\`
|
||||
|
||||
## Installation
|
||||
|
||||
### NPM
|
||||
\`\`\`bash
|
||||
npm install -g @oneuptime/mcp-server
|
||||
\`\`\`
|
||||
|
||||
### Docker
|
||||
\`\`\`bash
|
||||
docker run --rm -i oneuptime/mcp-server:latest
|
||||
\`\`\`
|
||||
|
||||
## Configuration
|
||||
Set the following environment variables:
|
||||
- \`ONEUPTIME_URL\`: Your OneUptime instance URL
|
||||
- \`ONEUPTIME_PROJECT_ID\`: Your project ID
|
||||
- \`ONEUPTIME_API_KEY\`: Your API key
|
||||
|
||||
## Usage
|
||||
The MCP server provides tools for:
|
||||
- Managing monitors and incidents
|
||||
- Creating and updating status pages
|
||||
- Managing teams and users
|
||||
- Working with all OneUptime models
|
||||
|
||||
## Generated Tools
|
||||
All tools are dynamically generated from the OpenAPI specification, ensuring they stay up-to-date with the latest API changes.
|
||||
EOF
|
||||
|
||||
- name: Upload release notes
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-notes
|
||||
path: MCP/release-notes.md
|
||||
2285
.github/workflows/release.yml
vendored
2285
.github/workflows/release.yml
vendored
File diff suppressed because it is too large
Load Diff
32
.github/workflows/reliability-copilot.yml
vendored
Normal file
32
.github/workflows/reliability-copilot.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
name: "OneUptime Reliability Copilot"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
# Run every day at midnight UTC
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze Code
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
# Run Reliability Copilot in Docker Container
|
||||
- name: Run Copilot
|
||||
run: |
|
||||
docker run --rm \
|
||||
-e ONEUPTIME_URL="https://test.oneuptime.com" \
|
||||
-e ONEUPTIME_REPOSITORY_SECRET_KEY="${{ secrets.COPILOT_ONEUPTIME_REPOSITORY_SECRET_KEY }}" \
|
||||
-e CODE_REPOSITORY_PASSWORD="${{ github.token }}" \
|
||||
-e CODE_REPOSITORY_USERNAME="simlarsen" \
|
||||
-e OPENAI_API_KEY="${{ secrets.OPENAI_API_KEY }}" \
|
||||
--net=host oneuptime/copilot:test
|
||||
143
.github/workflows/terraform-provider-e2e.yml
vendored
143
.github/workflows/terraform-provider-e2e.yml
vendored
@@ -1,143 +0,0 @@
|
||||
name: Terraform Provider E2E Tests
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
terraform-e2e-tests:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{ github.run_number }}
|
||||
APP_TAG: latest
|
||||
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
tool-cache: true
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
|
||||
- name: Additional Disk Cleanup
|
||||
run: |
|
||||
echo "=== Initial disk space ==="
|
||||
df -h
|
||||
|
||||
echo "=== Removing unnecessary tools and libraries ==="
|
||||
# Remove Android SDK (if not already removed)
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
|
||||
# Remove .NET SDK and runtime
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /etc/skel/.dotnet || true
|
||||
|
||||
# Remove Haskell/GHC
|
||||
sudo rm -rf /opt/ghc || true
|
||||
sudo rm -rf /usr/local/.ghcup || true
|
||||
|
||||
# Remove CodeQL
|
||||
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
|
||||
|
||||
# Remove Boost
|
||||
sudo rm -rf /usr/local/share/boost || true
|
||||
|
||||
# Remove Swift
|
||||
sudo rm -rf /usr/share/swift || true
|
||||
|
||||
# Remove Julia
|
||||
sudo rm -rf /usr/local/julia* || true
|
||||
|
||||
# Remove Rust (cargo/rustup)
|
||||
sudo rm -rf /usr/share/rust || true
|
||||
sudo rm -rf /home/runner/.rustup || true
|
||||
sudo rm -rf /home/runner/.cargo || true
|
||||
|
||||
# Remove unnecessary hostedtoolcache items
|
||||
sudo rm -rf /opt/hostedtoolcache/Python || true
|
||||
sudo rm -rf /opt/hostedtoolcache/PyPy || true
|
||||
sudo rm -rf /opt/hostedtoolcache/Ruby || true
|
||||
sudo rm -rf /opt/hostedtoolcache/Java* || true
|
||||
|
||||
# Remove additional large directories
|
||||
sudo rm -rf /usr/share/miniconda || true
|
||||
sudo rm -rf /usr/local/graalvm || true
|
||||
sudo rm -rf /usr/local/share/chromium || true
|
||||
sudo rm -rf /usr/local/share/powershell || true
|
||||
sudo rm -rf /usr/share/az_* || true
|
||||
|
||||
# Remove documentation
|
||||
sudo rm -rf /usr/share/doc || true
|
||||
sudo rm -rf /usr/share/man || true
|
||||
|
||||
# Remove unnecessary locales
|
||||
sudo rm -rf /usr/share/locale || true
|
||||
|
||||
# Clean apt cache
|
||||
sudo apt-get clean || true
|
||||
sudo rm -rf /var/lib/apt/lists/* || true
|
||||
sudo rm -rf /var/cache/apt/archives/* || true
|
||||
|
||||
# Clean tmp
|
||||
sudo rm -rf /tmp/* || true
|
||||
|
||||
echo "=== Moving Docker data to /mnt for more space ==="
|
||||
# Stop docker
|
||||
sudo systemctl stop docker || true
|
||||
|
||||
# Move docker data directory to /mnt (which has ~70GB)
|
||||
sudo mv /var/lib/docker /mnt/docker || true
|
||||
sudo mkdir -p /var/lib/docker || true
|
||||
sudo mount --bind /mnt/docker /var/lib/docker || true
|
||||
|
||||
# Restart docker
|
||||
sudo systemctl start docker || true
|
||||
|
||||
echo "=== Final disk space ==="
|
||||
df -h
|
||||
|
||||
echo "=== Docker info ==="
|
||||
docker info | grep -E "Docker Root Dir|Storage Driver" || true
|
||||
|
||||
- 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: 'stable'
|
||||
cache: true
|
||||
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@v3
|
||||
with:
|
||||
terraform_version: "1.6.0"
|
||||
terraform_wrapper: false
|
||||
|
||||
- name: Run E2E Tests
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 60
|
||||
max_attempts: 3
|
||||
command: |
|
||||
chmod +x ./E2E/Terraform/e2e-tests/scripts/*.sh
|
||||
./E2E/Terraform/e2e-tests/scripts/index.sh
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Install Common dependencies
|
||||
@@ -77,21 +77,17 @@ jobs:
|
||||
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
|
||||
run: |
|
||||
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
|
||||
|
||||
1865
.github/workflows/test-release.yaml
vendored
1865
.github/workflows/test-release.yaml
vendored
File diff suppressed because it is too large
Load Diff
23
.github/workflows/test.ai-agent.yaml
vendored
23
.github/workflows/test.ai-agent.yaml
vendored
@@ -1,23 +0,0 @@
|
||||
name: AIAgent Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'hotfix-*'
|
||||
- 'release'
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd AIAgent && npm install && npm run test
|
||||
@@ -1,7 +1,7 @@
|
||||
name: CLI Test
|
||||
name: Fluent Ingest Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'hotfix-*' # excludes hotfix branches
|
||||
@@ -17,5 +17,4 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd CLI && npm install && npm run test
|
||||
- run: cd FluentIngest && npm install && npm run test
|
||||
21
.github/workflows/test.mcp.yaml
vendored
21
.github/workflows/test.mcp.yaml
vendored
@@ -1,21 +0,0 @@
|
||||
name: MCP 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@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd MCP && npm install && npm run test
|
||||
39
.github/workflows/test.mobile-app.yaml
vendored
39
.github/workflows/test.mobile-app.yaml
vendored
@@ -1,39 +0,0 @@
|
||||
name: MobileApp Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'hotfix-*' # excludes hotfix branches
|
||||
- 'release'
|
||||
|
||||
jobs:
|
||||
expo-doctor:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd MobileApp && npm install
|
||||
- name: Run Expo Doctor
|
||||
run: cd MobileApp && npx expo-doctor@latest
|
||||
|
||||
expo-web-export:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd MobileApp && npm install
|
||||
- name: Export Web Bundle
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: cd MobileApp && npx expo export --platform web
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Telemetry Test
|
||||
name: OpenTelemetryIngest Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -17,6 +17,5 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
- run: cd Common && npm install
|
||||
- run: cd Telemetry && npm install && npm run test
|
||||
- run: cd OpenTelemetryIngest && npm install && npm run test
|
||||
|
||||
26
.gitignore
vendored
26
.gitignore
vendored
@@ -86,6 +86,9 @@ Backups/*.tar
|
||||
|
||||
.env
|
||||
|
||||
Haraka/dkim/keys/private_base64.txt
|
||||
Haraka/dkim/keys/public_base64.txt
|
||||
|
||||
.eslintcache
|
||||
|
||||
HelmChart/Values/*.values.yaml
|
||||
@@ -116,25 +119,4 @@ InfrastructureAgent/oneuptime-infrastructure-agent
|
||||
# Terraform generated files
|
||||
openapi.json
|
||||
|
||||
Terraform/terraform-provider-oneuptime/**
|
||||
Terraform/openapi.json
|
||||
TerraformTest/**
|
||||
|
||||
terraform-provider-example/**
|
||||
|
||||
# MCP Server
|
||||
MCP/build/
|
||||
MCP/.env
|
||||
MCP/node_modules
|
||||
Dashboard/public/sw.js
|
||||
.claude/settings.local.json
|
||||
Common/.claude/settings.local.json
|
||||
E2E/Terraform/e2e-tests/test-env.sh
|
||||
|
||||
# Terraform state and plan files
|
||||
*.tfplan
|
||||
tfplan
|
||||
terraform.tfstate
|
||||
terraform.tfstate.backup
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
Terraform/**
|
||||
15
.oneuptime/README.md
Normal file
15
.oneuptime/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## OneUptime Copilot
|
||||
|
||||
This folder contains the configuration files for the OneUptime Copilot. The Copilot is a tool that automatically improves your code. It can fix issues, improve code quality, and help you ship faster.
|
||||
|
||||
This folder has the following structure:
|
||||
|
||||
- `config.js`: The configuration file for the Copilot. You can customize the Copilot's behavior by changing this file.
|
||||
- `scripts`: A folder containing scripts that the Copilot runs. These are hooks that run at different stages of the Copilot's process.
|
||||
- `on-after-clone.sh`: A script that runs after the Copilot clones your repository.
|
||||
- `on-before-code-change.sh`: A script that runs before the Copilot makes changes to your code.
|
||||
- `on-after-code-change.sh`: A script that runs after the Copilot makes changes to your code.
|
||||
- `on-before-commit.sh`: A script that runs before the Copilot commits changes to your repository.
|
||||
- `on-after-commit.sh`: A script that runs after the Copilot commits changes to your repository.
|
||||
|
||||
|
||||
10
.oneuptime/config.js
Normal file
10
.oneuptime/config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
// This is the configuration file for the oneuptime copilot.
|
||||
|
||||
const getCopilotConfig = () => {
|
||||
return {
|
||||
// The version of the schema for this configuration file.
|
||||
schemaVersion: '1.0',
|
||||
}
|
||||
}
|
||||
|
||||
export default getCopilotConfig;
|
||||
16
.oneuptime/scripts/on-after-clone.sh
Normal file
16
.oneuptime/scripts/on-after-clone.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
# Description: Copilot clones your repository and to improve your code.
|
||||
# This scirpt runs after the clone process is completed.
|
||||
# Some of the common tasks you can do here are:
|
||||
# 1. Install dependencies
|
||||
# 2. Run linting
|
||||
# 3. Run tests
|
||||
# 4. Run build
|
||||
# 5. Run any other command that you want to run after the clone process is completed.
|
||||
# If this script fails, copilot will not proceed with the next steps to improve your code.
|
||||
# This step is to ensure that the code is in a good state before we start improving it.
|
||||
# If you want to skip this script, you can keep this file empty.
|
||||
# It's highly recommended to run linting and tests in this script to ensure the code is in a good state.
|
||||
# This scirpt will run on ubuntu machine. So, make sure the commands you run are compatible with ubuntu.
|
||||
|
||||
npm install
|
||||
npm run lint
|
||||
13
.oneuptime/scripts/on-after-code-change.sh
Normal file
13
.oneuptime/scripts/on-after-code-change.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
# Description: Copilot will run this script after we make improvements to your code and write it to disk.
|
||||
# Some of the common tasks you can do here are:
|
||||
# 1. Run linting
|
||||
# 2. Run tests
|
||||
# 3. Run build
|
||||
# 4. Run any other command that you want to run after the code is changed.
|
||||
# If this script fails, copilot will not commit the changes to your repository.
|
||||
# This step is to ensure that the code is in a good state before we commit the changes.
|
||||
# If you want to skip this script, you can keep this file empty.
|
||||
# It's highly recommended to run linting and tests in this script to ensure the code is in a good state.
|
||||
# This scirpt will run on ubuntu machine. So, make sure the commands you run are compatible with ubuntu.
|
||||
|
||||
npm run fix
|
||||
1
.oneuptime/scripts/on-after-commit.sh
Normal file
1
.oneuptime/scripts/on-after-commit.sh
Normal file
@@ -0,0 +1 @@
|
||||
# Description: Copilot will run this script after the commit process is completed.
|
||||
9
.oneuptime/scripts/on-before-code-change.sh
Normal file
9
.oneuptime/scripts/on-before-code-change.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
# Description: Copilot will run this script before we make changes to your code.
|
||||
# Some of the common tasks you can do here are:
|
||||
# 1. Install dependencies
|
||||
# 2. Run any other command that you want to run before the code is changed.
|
||||
# If this script fails, copilot will not make any changes to the code.
|
||||
# This step is to ensure that the code is in a good state before we start making changes.
|
||||
# If you want to skip this script, you can keep this file empty.
|
||||
# It's highly recommended to run things like installing dependencies in this script.
|
||||
# This scirpt will run on ubuntu machine. So, make sure the commands you run are compatible with ubuntu.
|
||||
1
.oneuptime/scripts/on-before-commit.sh
Normal file
1
.oneuptime/scripts/on-before-commit.sh
Normal file
@@ -0,0 +1 @@
|
||||
# Description: Copilot will run this script before we commit the changes to your repository.
|
||||
32
.vscode/launch.json
vendored
32
.vscode/launch.json
vendored
@@ -19,6 +19,20 @@
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/TestServer",
|
||||
"name": "Copilot: Debug with Docker",
|
||||
"port": 9985,
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"restart": true,
|
||||
"autoAttachChildProcesses": true
|
||||
},
|
||||
{
|
||||
"name": "Debug Infrastructure Agent",
|
||||
"type": "go",
|
||||
@@ -191,8 +205,8 @@
|
||||
},
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/Telemetry",
|
||||
"name": "Telemetry: Debug with Docker",
|
||||
"localRoot": "${workspaceFolder}/OpenTelemetryIngest",
|
||||
"name": "OpenTelemetryIngest: Debug with Docker",
|
||||
"port": 9938,
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"request": "attach",
|
||||
@@ -203,6 +217,20 @@
|
||||
"restart": true,
|
||||
"autoAttachChildProcesses": true
|
||||
},
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/FluentIngest",
|
||||
"name": "Fluent Ingest: Debug with Docker",
|
||||
"port": 9937,
|
||||
"remoteRoot": "/usr/src/app",
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"restart": true,
|
||||
"autoAttachChildProcesses": true
|
||||
},
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"localRoot": "${workspaceFolder}/IsolatedVM",
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "Common/Server/Utils/Express";
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPMethod from "Common/Types/API/HTTPMethod";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import API from "Common/Utils/API";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import AIAgentAPIRequest from "../Utils/AIAgentAPIRequest";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
/*
|
||||
* Metrics endpoint for Keda autoscaling
|
||||
* Returns the number of pending AI agent tasks
|
||||
*/
|
||||
router.get(
|
||||
"/queue-size",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
/*
|
||||
* Get the pending task count from OneUptime API
|
||||
* This is the correct metric - the number of tasks waiting to be processed
|
||||
*/
|
||||
const pendingTaskCountUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/api/ai-agent-task/get-pending-task-count");
|
||||
|
||||
logger.debug(
|
||||
"Fetching pending task count from OneUptime API for KEDA scaling",
|
||||
);
|
||||
|
||||
// Use AI Agent authentication (AI Agent key and AI Agent ID)
|
||||
const requestBody: JSONObject = AIAgentAPIRequest.getDefaultRequestBody();
|
||||
|
||||
const result: HTTPResponse<JSONObject> | HTTPErrorResponse =
|
||||
await API.fetch<JSONObject>({
|
||||
method: HTTPMethod.POST,
|
||||
url: pendingTaskCountUrl,
|
||||
data: requestBody,
|
||||
headers: {},
|
||||
});
|
||||
|
||||
if (result instanceof HTTPErrorResponse) {
|
||||
logger.error("Error fetching pending task count from OneUptime API");
|
||||
logger.error(result);
|
||||
throw result;
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
"Pending task count fetched successfully from OneUptime API",
|
||||
);
|
||||
logger.debug(result.data);
|
||||
|
||||
// Extract count from the response - this is the number of tasks pending to be processed
|
||||
let queueSize: number = (result.data["count"] as number) || 0;
|
||||
|
||||
// if string then convert to number
|
||||
if (typeof queueSize === "string") {
|
||||
const parsedQueueSize: number = parseInt(queueSize, 10);
|
||||
if (!isNaN(parsedQueueSize)) {
|
||||
queueSize = parsedQueueSize;
|
||||
} else {
|
||||
logger.warn(
|
||||
"Pending task count is not a valid number, defaulting to 0",
|
||||
);
|
||||
queueSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Pending task count for KEDA: ${queueSize}`);
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
queueSize: queueSize,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error("Error in metrics queue-size endpoint");
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -1,103 +0,0 @@
|
||||
import {
|
||||
CodeAgent,
|
||||
CodeAgentType,
|
||||
getCodeAgentDisplayName,
|
||||
} from "./CodeAgentInterface";
|
||||
import OpenCodeAgent from "./OpenCodeAgent";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
// Factory class to create code agents
|
||||
export default class CodeAgentFactory {
|
||||
// Default agent type to use
|
||||
private static defaultAgentType: CodeAgentType = CodeAgentType.OpenCode;
|
||||
|
||||
// Create an agent of the specified type
|
||||
public static createAgent(type: CodeAgentType): CodeAgent {
|
||||
logger.debug(`Creating code agent: ${getCodeAgentDisplayName(type)}`);
|
||||
|
||||
switch (type) {
|
||||
case CodeAgentType.OpenCode:
|
||||
return new OpenCodeAgent();
|
||||
|
||||
/*
|
||||
* Future agents can be added here:
|
||||
* case CodeAgentType.Goose:
|
||||
* return new GooseAgent();
|
||||
* case CodeAgentType.ClaudeCode:
|
||||
* return new ClaudeCodeAgent();
|
||||
*/
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown code agent type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the default agent
|
||||
public static createDefaultAgent(): CodeAgent {
|
||||
return this.createAgent(this.defaultAgentType);
|
||||
}
|
||||
|
||||
// Set the default agent type
|
||||
public static setDefaultAgentType(type: CodeAgentType): void {
|
||||
this.defaultAgentType = type;
|
||||
}
|
||||
|
||||
// Get the default agent type
|
||||
public static getDefaultAgentType(): CodeAgentType {
|
||||
return this.defaultAgentType;
|
||||
}
|
||||
|
||||
// Get all available agent types
|
||||
public static getAvailableAgentTypes(): Array<CodeAgentType> {
|
||||
return Object.values(CodeAgentType);
|
||||
}
|
||||
|
||||
// Check if an agent type is available on the system
|
||||
public static async isAgentAvailable(type: CodeAgentType): Promise<boolean> {
|
||||
try {
|
||||
const agent: CodeAgent = this.createAgent(type);
|
||||
return await agent.isAvailable();
|
||||
} catch (error) {
|
||||
logger.error(`Error checking agent availability for ${type}:`);
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the first available agent
|
||||
public static async getFirstAvailableAgent(): Promise<CodeAgent | null> {
|
||||
for (const type of this.getAvailableAgentTypes()) {
|
||||
if (await this.isAgentAvailable(type)) {
|
||||
return this.createAgent(type);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create agent with fallback
|
||||
* Tries to create the specified type, falls back to first available
|
||||
*/
|
||||
public static async createAgentWithFallback(
|
||||
preferredType?: CodeAgentType,
|
||||
): Promise<CodeAgent> {
|
||||
// If preferred type is specified and available, use it
|
||||
if (preferredType && (await this.isAgentAvailable(preferredType))) {
|
||||
return this.createAgent(preferredType);
|
||||
}
|
||||
|
||||
// Try the default type
|
||||
if (await this.isAgentAvailable(this.defaultAgentType)) {
|
||||
return this.createAgent(this.defaultAgentType);
|
||||
}
|
||||
|
||||
// Fall back to first available
|
||||
const agent: CodeAgent | null = await this.getFirstAvailableAgent();
|
||||
|
||||
if (!agent) {
|
||||
throw new Error("No code agents are available on this system");
|
||||
}
|
||||
|
||||
return agent;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import LlmType from "Common/Types/LLM/LlmType";
|
||||
import TaskLogger from "../Utils/TaskLogger";
|
||||
|
||||
// Configuration for the LLM provider
|
||||
export interface CodeAgentLLMConfig {
|
||||
llmType: LlmType;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
modelName?: string;
|
||||
}
|
||||
|
||||
// The task to be executed by the code agent
|
||||
export interface CodeAgentTask {
|
||||
workingDirectory: string;
|
||||
prompt: string;
|
||||
context?: string;
|
||||
timeoutMs?: number;
|
||||
servicePath?: string; // Path within the repo where the service code lives
|
||||
}
|
||||
|
||||
// Result from the code agent execution
|
||||
export interface CodeAgentResult {
|
||||
success: boolean;
|
||||
filesModified: Array<string>;
|
||||
summary: string;
|
||||
logs: Array<string>;
|
||||
error?: string;
|
||||
exitCode?: number;
|
||||
}
|
||||
|
||||
// Progress event from the code agent
|
||||
export interface CodeAgentProgressEvent {
|
||||
type: "stdout" | "stderr" | "status";
|
||||
message: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
// Callback type for progress events
|
||||
export type CodeAgentProgressCallback = (
|
||||
event: CodeAgentProgressEvent,
|
||||
) => void | Promise<void>;
|
||||
|
||||
/*
|
||||
* Abstract interface for code agents
|
||||
* This allows us to support multiple agents (OpenCode, Goose, Claude Code, etc.)
|
||||
*/
|
||||
export interface CodeAgent {
|
||||
// Name of the agent (e.g., "OpenCode", "Goose", "ClaudeCode")
|
||||
readonly name: string;
|
||||
|
||||
// Initialize the agent with LLM configuration
|
||||
initialize(config: CodeAgentLLMConfig, logger?: TaskLogger): Promise<void>;
|
||||
|
||||
// Execute a task and return the result
|
||||
executeTask(task: CodeAgentTask): Promise<CodeAgentResult>;
|
||||
|
||||
// Set a callback for progress events (streaming output)
|
||||
onProgress(callback: CodeAgentProgressCallback): void;
|
||||
|
||||
// Check if the agent is available on the system
|
||||
isAvailable(): Promise<boolean>;
|
||||
|
||||
// Abort the current task execution
|
||||
abort(): Promise<void>;
|
||||
|
||||
// Clean up any resources used by the agent
|
||||
cleanup(): Promise<void>;
|
||||
}
|
||||
|
||||
// Enum for supported code agent types
|
||||
export enum CodeAgentType {
|
||||
OpenCode = "OpenCode",
|
||||
/*
|
||||
* Future agents:
|
||||
* Goose = "Goose",
|
||||
* ClaudeCode = "ClaudeCode",
|
||||
* Aider = "Aider",
|
||||
*/
|
||||
}
|
||||
|
||||
// Helper function to get display name for agent type
|
||||
export function getCodeAgentDisplayName(type: CodeAgentType): string {
|
||||
switch (type) {
|
||||
case CodeAgentType.OpenCode:
|
||||
return "OpenCode AI";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if an agent type is valid
|
||||
export function isValidCodeAgentType(type: string): type is CodeAgentType {
|
||||
return Object.values(CodeAgentType).includes(type as CodeAgentType);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Export all code agent related types and classes
|
||||
export {
|
||||
CodeAgent,
|
||||
CodeAgentLLMConfig,
|
||||
CodeAgentTask,
|
||||
CodeAgentResult,
|
||||
CodeAgentProgressEvent,
|
||||
CodeAgentProgressCallback,
|
||||
CodeAgentType,
|
||||
getCodeAgentDisplayName,
|
||||
isValidCodeAgentType,
|
||||
} from "./CodeAgentInterface";
|
||||
|
||||
export { default as CodeAgentFactory } from "./CodeAgentFactory";
|
||||
export { default as OpenCodeAgent } from "./OpenCodeAgent";
|
||||
@@ -1,562 +0,0 @@
|
||||
import {
|
||||
CodeAgent,
|
||||
CodeAgentLLMConfig,
|
||||
CodeAgentTask,
|
||||
CodeAgentResult,
|
||||
CodeAgentProgressCallback,
|
||||
CodeAgentProgressEvent,
|
||||
} from "./CodeAgentInterface";
|
||||
import TaskLogger from "../Utils/TaskLogger";
|
||||
import Execute from "Common/Server/Utils/Execute";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import LlmType from "Common/Types/LLM/LlmType";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import path from "path";
|
||||
import { ChildProcess, spawn } from "child_process";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
|
||||
// OpenCode configuration file structure
|
||||
interface OpenCodeConfig {
|
||||
provider?: Record<string, unknown>;
|
||||
model?: string;
|
||||
small_model?: string;
|
||||
disabled_providers?: Array<string>;
|
||||
enabled_providers?: Array<string>;
|
||||
}
|
||||
|
||||
export default class OpenCodeAgent implements CodeAgent {
|
||||
public readonly name: string = "OpenCode";
|
||||
|
||||
private config: CodeAgentLLMConfig | null = null;
|
||||
private taskLogger: TaskLogger | null = null;
|
||||
private progressCallback: CodeAgentProgressCallback | null = null;
|
||||
private currentProcess: ChildProcess | null = null;
|
||||
private aborted: boolean = false;
|
||||
|
||||
// Track original opencode.json content for restoration
|
||||
private originalOpenCodeConfig: string | null = null;
|
||||
private openCodeConfigPath: string | null = null;
|
||||
|
||||
// Default timeout: 30 minutes
|
||||
private static readonly DEFAULT_TIMEOUT_MS: number = 30 * 60 * 1000;
|
||||
|
||||
public async initialize(
|
||||
config: CodeAgentLLMConfig,
|
||||
taskLogger?: TaskLogger,
|
||||
): Promise<void> {
|
||||
this.config = config;
|
||||
|
||||
if (taskLogger) {
|
||||
this.taskLogger = taskLogger;
|
||||
}
|
||||
|
||||
await this.log(`Initializing ${this.name} with ${config.llmType} provider`);
|
||||
}
|
||||
|
||||
public async executeTask(task: CodeAgentTask): Promise<CodeAgentResult> {
|
||||
if (!this.config) {
|
||||
return this.createErrorResult(
|
||||
"Agent not initialized. Call initialize() first.",
|
||||
);
|
||||
}
|
||||
|
||||
this.aborted = false;
|
||||
const logs: Array<string> = [];
|
||||
const timeoutMs: number =
|
||||
task.timeoutMs || OpenCodeAgent.DEFAULT_TIMEOUT_MS;
|
||||
|
||||
try {
|
||||
await this.log(`Executing task in directory: ${task.workingDirectory}`);
|
||||
|
||||
// Create OpenCode config file in the working directory
|
||||
await this.createOpenCodeConfig(task.workingDirectory);
|
||||
|
||||
// Build the prompt
|
||||
const fullPrompt: string = this.buildFullPrompt(task);
|
||||
|
||||
await this.log("Starting OpenCode execution...");
|
||||
logs.push(`Prompt: ${fullPrompt.substring(0, 500)}...`);
|
||||
|
||||
// Execute OpenCode
|
||||
const output: string = await this.runOpenCode(
|
||||
task.workingDirectory,
|
||||
fullPrompt,
|
||||
timeoutMs,
|
||||
(event: CodeAgentProgressEvent) => {
|
||||
logs.push(`[${event.type}] ${event.message}`);
|
||||
if (this.progressCallback) {
|
||||
this.progressCallback(event);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
logs.push(
|
||||
`Output: ${output.substring(0, 1000)}${output.length > 1000 ? "..." : ""}`,
|
||||
);
|
||||
|
||||
if (this.aborted) {
|
||||
return this.createErrorResult("Task was aborted", logs);
|
||||
}
|
||||
|
||||
// Check for modified files
|
||||
const modifiedFiles: Array<string> = await this.getModifiedFiles(
|
||||
task.workingDirectory,
|
||||
);
|
||||
|
||||
// Restore or delete opencode.json before returning
|
||||
await this.restoreOpenCodeConfig();
|
||||
|
||||
await this.log(
|
||||
`OpenCode completed. ${modifiedFiles.length} files modified.`,
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
filesModified: modifiedFiles,
|
||||
summary: this.extractSummary(output),
|
||||
logs,
|
||||
exitCode: 0,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage: string =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
// Restore or delete opencode.json on error
|
||||
await this.restoreOpenCodeConfig();
|
||||
|
||||
await this.log(`OpenCode execution failed: ${errorMessage}`);
|
||||
logs.push(`Error: ${errorMessage}`);
|
||||
|
||||
return this.createErrorResult(errorMessage, logs);
|
||||
}
|
||||
}
|
||||
|
||||
public onProgress(callback: CodeAgentProgressCallback): void {
|
||||
this.progressCallback = callback;
|
||||
}
|
||||
|
||||
public async isAvailable(): Promise<boolean> {
|
||||
try {
|
||||
const result: string = await Execute.executeCommandFile({
|
||||
command: "opencode",
|
||||
args: ["--version"],
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
logger.debug(`OpenCode version check: ${result}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.debug("OpenCode is not available:");
|
||||
logger.debug(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async abort(): Promise<void> {
|
||||
this.aborted = true;
|
||||
|
||||
if (this.currentProcess) {
|
||||
this.currentProcess.kill("SIGTERM");
|
||||
this.currentProcess = null;
|
||||
}
|
||||
|
||||
await this.log("OpenCode execution aborted");
|
||||
}
|
||||
|
||||
public async cleanup(): Promise<void> {
|
||||
if (this.currentProcess) {
|
||||
this.currentProcess.kill("SIGTERM");
|
||||
this.currentProcess = null;
|
||||
}
|
||||
|
||||
this.config = null;
|
||||
this.progressCallback = null;
|
||||
}
|
||||
|
||||
// Create OpenCode configuration file in the workspace
|
||||
private async createOpenCodeConfig(workingDirectory: string): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw new Error("Config not initialized");
|
||||
}
|
||||
|
||||
const configPath: string = path.join(workingDirectory, "opencode.json");
|
||||
this.openCodeConfigPath = configPath;
|
||||
|
||||
// Check if opencode.json already exists and backup its content
|
||||
try {
|
||||
const existingContent: string = await LocalFile.read(configPath);
|
||||
this.originalOpenCodeConfig = existingContent;
|
||||
await this.log("Backed up existing opencode.json from repository");
|
||||
} catch {
|
||||
// File doesn't exist, which is the normal case
|
||||
this.originalOpenCodeConfig = null;
|
||||
}
|
||||
|
||||
const openCodeConfig: OpenCodeConfig = {
|
||||
model: this.getModelString(),
|
||||
small_model: this.getSmallModelString(),
|
||||
};
|
||||
|
||||
// Set enabled providers based on LLM type
|
||||
if (this.config.llmType === LlmType.Anthropic) {
|
||||
openCodeConfig.enabled_providers = ["anthropic"];
|
||||
} else if (this.config.llmType === LlmType.OpenAI) {
|
||||
openCodeConfig.enabled_providers = ["openai"];
|
||||
}
|
||||
|
||||
await LocalFile.write(configPath, JSON.stringify(openCodeConfig, null, 2));
|
||||
|
||||
await this.log(`Created OpenCode config at ${configPath}`);
|
||||
}
|
||||
|
||||
// Restore or delete opencode.json after execution
|
||||
private async restoreOpenCodeConfig(): Promise<void> {
|
||||
if (!this.openCodeConfigPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.originalOpenCodeConfig !== null) {
|
||||
// Restore the original file content
|
||||
await LocalFile.write(
|
||||
this.openCodeConfigPath,
|
||||
this.originalOpenCodeConfig,
|
||||
);
|
||||
await this.log("Restored original opencode.json from repository");
|
||||
} else {
|
||||
// Delete the file we created
|
||||
await LocalFile.deleteFile(this.openCodeConfigPath);
|
||||
await this.log("Deleted generated opencode.json config file");
|
||||
}
|
||||
} catch (error) {
|
||||
// Log but don't throw - cleanup failure shouldn't fail the task
|
||||
logger.warn(`Failed to restore/delete opencode.json: ${error}`);
|
||||
}
|
||||
|
||||
// Reset the tracking variables
|
||||
this.openCodeConfigPath = null;
|
||||
this.originalOpenCodeConfig = null;
|
||||
}
|
||||
|
||||
// Get the model string in OpenCode format (provider/model)
|
||||
private getModelString(): string {
|
||||
if (!this.config) {
|
||||
throw new Error("Config not initialized");
|
||||
}
|
||||
|
||||
const provider: string = this.getProviderName();
|
||||
const model: string = this.config.modelName || this.getDefaultModel();
|
||||
|
||||
return `${provider}/${model}`;
|
||||
}
|
||||
|
||||
// Get the small model string for quick operations
|
||||
private getSmallModelString(): string {
|
||||
if (!this.config) {
|
||||
throw new Error("Config not initialized");
|
||||
}
|
||||
|
||||
const provider: string = this.getProviderName();
|
||||
const smallModel: string = this.getDefaultSmallModel();
|
||||
|
||||
return `${provider}/${smallModel}`;
|
||||
}
|
||||
|
||||
// Get provider name for OpenCode config
|
||||
private getProviderName(): string {
|
||||
if (!this.config) {
|
||||
return "anthropic";
|
||||
}
|
||||
|
||||
switch (this.config.llmType) {
|
||||
case LlmType.Anthropic:
|
||||
return "anthropic";
|
||||
case LlmType.OpenAI:
|
||||
return "openai";
|
||||
case LlmType.Ollama:
|
||||
return "ollama";
|
||||
default:
|
||||
throw new BadDataException("Unsupported LLM type for OpenCode agent");
|
||||
}
|
||||
}
|
||||
|
||||
// Get default model based on provider
|
||||
private getDefaultModel(): string {
|
||||
if (!this.config) {
|
||||
return "claude-sonnet-4-20250514";
|
||||
}
|
||||
|
||||
switch (this.config.llmType) {
|
||||
case LlmType.Anthropic:
|
||||
return "claude-sonnet-4-20250514";
|
||||
case LlmType.OpenAI:
|
||||
return "gpt-4o";
|
||||
case LlmType.Ollama:
|
||||
return "llama2";
|
||||
default:
|
||||
throw new BadDataException("Unsupported LLM type for OpenCode agent");
|
||||
}
|
||||
}
|
||||
|
||||
// Get default small model for quick operations
|
||||
private getDefaultSmallModel(): string {
|
||||
if (!this.config) {
|
||||
return "claude-haiku-4-20250514";
|
||||
}
|
||||
|
||||
switch (this.config.llmType) {
|
||||
case LlmType.Anthropic:
|
||||
return "claude-haiku-4-20250514";
|
||||
case LlmType.OpenAI:
|
||||
return "gpt-4o-mini";
|
||||
case LlmType.Ollama:
|
||||
return "llama2";
|
||||
default:
|
||||
throw new BadDataException("Unsupported LLM type for OpenCode agent");
|
||||
}
|
||||
}
|
||||
|
||||
// Build the full prompt including context
|
||||
private buildFullPrompt(task: CodeAgentTask): string {
|
||||
let prompt: string = task.prompt;
|
||||
|
||||
if (task.context) {
|
||||
prompt = `${task.context}\n\n${prompt}`;
|
||||
}
|
||||
|
||||
if (task.servicePath) {
|
||||
prompt = `The service code is located at: ${task.servicePath}\n\n${prompt}`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
// Run OpenCode in non-interactive mode
|
||||
private async runOpenCode(
|
||||
workingDirectory: string,
|
||||
prompt: string,
|
||||
timeoutMs: number,
|
||||
onOutput: (event: CodeAgentProgressEvent) => void,
|
||||
): Promise<string> {
|
||||
return new Promise(
|
||||
(resolve: (value: string) => void, reject: (reason: Error) => void) => {
|
||||
if (!this.config) {
|
||||
reject(new Error("Config not initialized"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set environment variables for API key
|
||||
const env: NodeJS.ProcessEnv = { ...process.env };
|
||||
|
||||
if (this.config.apiKey) {
|
||||
switch (this.config.llmType) {
|
||||
case LlmType.Anthropic:
|
||||
env["ANTHROPIC_API_KEY"] = this.config.apiKey;
|
||||
break;
|
||||
case LlmType.OpenAI:
|
||||
env["OPENAI_API_KEY"] = this.config.apiKey;
|
||||
break;
|
||||
case LlmType.Ollama:
|
||||
if (this.config.baseUrl) {
|
||||
env["OLLAMA_HOST"] = this.config.baseUrl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Use CLI mode flags to ensure output goes to stdout/stderr instead of TUI
|
||||
* Pass prompt via stdin using "-" to avoid command line argument issues with long prompts
|
||||
*/
|
||||
const args: Array<string> = [
|
||||
"run",
|
||||
"--print-logs",
|
||||
"--log-level",
|
||||
"DEBUG",
|
||||
"--format",
|
||||
"default",
|
||||
"-", // Read prompt from stdin
|
||||
];
|
||||
|
||||
logger.debug(
|
||||
`Running: opencode ${args.join(" ")} (prompt via stdin, ${prompt.length} chars)`,
|
||||
);
|
||||
|
||||
const child: ChildProcess = spawn("opencode", args, {
|
||||
cwd: workingDirectory,
|
||||
env,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
this.currentProcess = child;
|
||||
|
||||
// Write prompt to stdin and close it
|
||||
if (child.stdin) {
|
||||
child.stdin.write(prompt);
|
||||
child.stdin.end();
|
||||
}
|
||||
|
||||
let stdout: string = "";
|
||||
let stderr: string = "";
|
||||
|
||||
// Set timeout
|
||||
const timeout: ReturnType<typeof setTimeout> = setTimeout(() => {
|
||||
if (child.pid) {
|
||||
child.kill("SIGTERM");
|
||||
reject(
|
||||
new Error(
|
||||
`OpenCode execution timed out after ${timeoutMs / 1000} seconds`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}, timeoutMs);
|
||||
|
||||
child.stdout?.on("data", (data: Buffer) => {
|
||||
const text: string = data.toString();
|
||||
stdout += text;
|
||||
|
||||
// Stream to console immediately
|
||||
const trimmedText: string = text.trim();
|
||||
if (trimmedText) {
|
||||
logger.info(`[OpenCode stdout] ${trimmedText}`);
|
||||
|
||||
// Stream to task logger for server-side logging
|
||||
if (this.taskLogger) {
|
||||
this.taskLogger
|
||||
.info(`[OpenCode] ${trimmedText}`)
|
||||
.catch((err: Error) => {
|
||||
logger.error(`Failed to log OpenCode output: ${err.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onOutput({
|
||||
type: "stdout",
|
||||
message: trimmedText,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
});
|
||||
|
||||
child.stderr?.on("data", (data: Buffer) => {
|
||||
const text: string = data.toString();
|
||||
stderr += text;
|
||||
|
||||
// Stream to console immediately
|
||||
const trimmedText: string = text.trim();
|
||||
if (trimmedText) {
|
||||
logger.warn(`[OpenCode stderr] ${trimmedText}`);
|
||||
|
||||
// Stream to task logger for server-side logging
|
||||
if (this.taskLogger) {
|
||||
this.taskLogger
|
||||
.warning(`[OpenCode stderr] ${trimmedText}`)
|
||||
.catch((err: Error) => {
|
||||
logger.error(`Failed to log OpenCode stderr: ${err.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onOutput({
|
||||
type: "stderr",
|
||||
message: trimmedText,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
});
|
||||
|
||||
child.on("close", (code: number | null) => {
|
||||
clearTimeout(timeout);
|
||||
this.currentProcess = null;
|
||||
|
||||
if (this.aborted) {
|
||||
reject(new Error("Execution aborted"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (code === 0 || code === null) {
|
||||
resolve(stdout);
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`OpenCode exited with code ${code}. stderr: ${stderr.substring(0, 500)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
child.on("error", (error: Error) => {
|
||||
clearTimeout(timeout);
|
||||
this.currentProcess = null;
|
||||
reject(error);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Get list of modified files using git
|
||||
private async getModifiedFiles(
|
||||
workingDirectory: string,
|
||||
): Promise<Array<string>> {
|
||||
try {
|
||||
const result: string = await Execute.executeCommandFile({
|
||||
command: "git",
|
||||
args: ["status", "--porcelain"],
|
||||
cwd: workingDirectory,
|
||||
});
|
||||
|
||||
if (!result.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return result
|
||||
.split("\n")
|
||||
.filter((line: string) => {
|
||||
return line.trim().length > 0;
|
||||
})
|
||||
.map((line: string) => {
|
||||
// Git status format: "XY filename"
|
||||
return line.substring(3).trim();
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error getting modified files:");
|
||||
logger.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Extract summary from OpenCode output
|
||||
private extractSummary(output: string): string {
|
||||
// Try to extract a meaningful summary from the output
|
||||
const lines: Array<string> = output.split("\n").filter((line: string) => {
|
||||
return line.trim().length > 0;
|
||||
});
|
||||
|
||||
// Return last few meaningful lines as summary
|
||||
const summaryLines: Array<string> = lines.slice(-5);
|
||||
|
||||
return summaryLines.join("\n") || "No summary available";
|
||||
}
|
||||
|
||||
// Create error result helper
|
||||
private createErrorResult(
|
||||
errorMessage: string,
|
||||
logs: Array<string> = [],
|
||||
): CodeAgentResult {
|
||||
return {
|
||||
success: false,
|
||||
filesModified: [],
|
||||
summary: "",
|
||||
logs,
|
||||
error: errorMessage,
|
||||
exitCode: 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Logging helper
|
||||
private async log(message: string): Promise<void> {
|
||||
if (this.taskLogger) {
|
||||
await this.taskLogger.info(`[${this.name}] ${message}`);
|
||||
} else {
|
||||
logger.debug(`[${this.name}] ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import URL from "Common/Types/API/URL";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Port from "Common/Types/Port";
|
||||
|
||||
if (!process.env["ONEUPTIME_URL"]) {
|
||||
logger.error("ONEUPTIME_URL is not set");
|
||||
process.exit();
|
||||
}
|
||||
|
||||
export const ONEUPTIME_URL: URL = URL.fromString(
|
||||
process.env["ONEUPTIME_URL"] || "https://oneuptime.com",
|
||||
);
|
||||
|
||||
export const AI_AGENT_ID: ObjectID | null = process.env["AI_AGENT_ID"]
|
||||
? new ObjectID(process.env["AI_AGENT_ID"])
|
||||
: null;
|
||||
|
||||
if (!process.env["AI_AGENT_KEY"]) {
|
||||
logger.error("AI_AGENT_KEY is not set");
|
||||
process.exit();
|
||||
}
|
||||
|
||||
export const AI_AGENT_KEY: string = process.env["AI_AGENT_KEY"];
|
||||
|
||||
export const AI_AGENT_NAME: string | null =
|
||||
process.env["AI_AGENT_NAME"] || null;
|
||||
|
||||
export const AI_AGENT_DESCRIPTION: string | null =
|
||||
process.env["AI_AGENT_DESCRIPTION"] || null;
|
||||
|
||||
export const HOSTNAME: string = process.env["HOSTNAME"] || "localhost";
|
||||
|
||||
export const PORT: Port = new Port(
|
||||
process.env["PORT"] ? parseInt(process.env["PORT"]) : 3875,
|
||||
);
|
||||
@@ -1,84 +0,0 @@
|
||||
import { PORT } from "./Config";
|
||||
import AliveJob from "./Jobs/Alive";
|
||||
import startTaskProcessingLoop from "./Jobs/ProcessScheduledTasks";
|
||||
import Register from "./Services/Register";
|
||||
import MetricsAPI from "./API/Metrics";
|
||||
import {
|
||||
getTaskHandlerRegistry,
|
||||
FixExceptionTaskHandler,
|
||||
} from "./TaskHandlers/Index";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import App from "Common/Server/Utils/StartServer";
|
||||
import Telemetry from "Common/Server/Utils/Telemetry";
|
||||
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
|
||||
import "ejs";
|
||||
|
||||
const APP_NAME: string = "ai-agent";
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// Initialize telemetry
|
||||
Telemetry.init({
|
||||
serviceName: APP_NAME,
|
||||
});
|
||||
|
||||
logger.info("AI Agent Service - Starting...");
|
||||
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: PORT,
|
||||
isFrontendApp: false,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
|
||||
// Add metrics API routes for KEDA autoscaling
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
app.use("/metrics", MetricsAPI);
|
||||
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
|
||||
try {
|
||||
// Register this AI Agent.
|
||||
await Register.registerAIAgent();
|
||||
|
||||
logger.debug("AI Agent registered");
|
||||
|
||||
AliveJob();
|
||||
|
||||
// Register task handlers
|
||||
logger.debug("Registering task handlers...");
|
||||
const taskHandlerRegistry: ReturnType<typeof getTaskHandlerRegistry> =
|
||||
getTaskHandlerRegistry();
|
||||
taskHandlerRegistry.register(new FixExceptionTaskHandler());
|
||||
logger.debug(
|
||||
`Registered ${taskHandlerRegistry.getHandlerCount()} task handler(s): ${taskHandlerRegistry.getRegisteredTaskTypes().join(", ")}`,
|
||||
);
|
||||
|
||||
// Start task processing loop (runs in background)
|
||||
startTaskProcessingLoop().catch((err: Error) => {
|
||||
logger.error("Task processing loop failed:");
|
||||
logger.error(err);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error("Register AI Agent failed");
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
} 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);
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import Register from "../Services/Register";
|
||||
import AIAgentAPIRequest from "../Utils/AIAgentAPIRequest";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import BasicCron from "Common/Server/Utils/BasicCron";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
const InitJob: VoidFunction = (): void => {
|
||||
BasicCron({
|
||||
jobName: "AIAgent:Alive",
|
||||
options: {
|
||||
schedule: EVERY_MINUTE,
|
||||
runOnStartup: false,
|
||||
},
|
||||
runFunction: async () => {
|
||||
logger.debug("Checking if AI Agent is alive...");
|
||||
|
||||
const aiAgentId: string | undefined = LocalCache.getString(
|
||||
"AI_AGENT",
|
||||
"AI_AGENT_ID",
|
||||
);
|
||||
|
||||
if (!aiAgentId) {
|
||||
logger.warn(
|
||||
"AI Agent is not registered yet. Skipping alive check. Trying to register AI Agent again...",
|
||||
);
|
||||
await Register.registerAIAgent();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("AI Agent ID: " + aiAgentId.toString());
|
||||
|
||||
const aliveUrl: URL = URL.fromString(ONEUPTIME_URL.toString()).addRoute(
|
||||
"/api/ai-agent/alive",
|
||||
);
|
||||
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: aliveUrl,
|
||||
data: AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
});
|
||||
|
||||
if (result.isSuccess()) {
|
||||
logger.debug("AI Agent update sent to server successfully.");
|
||||
} else {
|
||||
logger.error("Failed to send AI Agent update to server.");
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default InitJob;
|
||||
@@ -1,257 +0,0 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import AIAgentAPIRequest from "../Utils/AIAgentAPIRequest";
|
||||
import AIAgentTaskLog from "../Utils/AIAgentTaskLog";
|
||||
import TaskLogger from "../Utils/TaskLogger";
|
||||
import BackendAPI from "../Utils/BackendAPI";
|
||||
import {
|
||||
getTaskHandlerRegistry,
|
||||
TaskContext,
|
||||
TaskMetadata,
|
||||
TaskHandler,
|
||||
TaskResult,
|
||||
} from "../TaskHandlers/Index";
|
||||
import TaskHandlerRegistry from "../TaskHandlers/TaskHandlerRegistry";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import AIAgentTaskStatus from "Common/Types/AI/AIAgentTaskStatus";
|
||||
import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Sleep from "Common/Types/Sleep";
|
||||
|
||||
// Type for task data from the API
|
||||
interface AIAgentTaskData {
|
||||
_id: string;
|
||||
projectId: string;
|
||||
taskType: AIAgentTaskType;
|
||||
metadata: TaskMetadata;
|
||||
createdAt: string;
|
||||
status?: AIAgentTaskStatus;
|
||||
}
|
||||
|
||||
// Type for API response containing task
|
||||
interface GetPendingTaskResponse {
|
||||
task: AIAgentTaskData | null;
|
||||
}
|
||||
|
||||
const SLEEP_WHEN_NO_TASKS_MS: number = 60 * 1000; // 1 minute
|
||||
|
||||
type ExecuteTaskFunction = (task: AIAgentTaskData) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Execute an AI Agent task using the registered task handler
|
||||
*/
|
||||
const executeTask: ExecuteTaskFunction = async (
|
||||
task: AIAgentTaskData,
|
||||
): Promise<void> => {
|
||||
const taskIdString: string = task._id;
|
||||
const projectIdString: string = task.projectId;
|
||||
const taskId: ObjectID = new ObjectID(taskIdString);
|
||||
const projectId: ObjectID = new ObjectID(projectIdString);
|
||||
const taskType: AIAgentTaskType = task.taskType;
|
||||
const metadata: TaskMetadata = task.metadata || {};
|
||||
const createdAt: Date = new Date(task.createdAt);
|
||||
|
||||
// Get the task handler from the registry
|
||||
const registry: TaskHandlerRegistry = getTaskHandlerRegistry();
|
||||
const handler: TaskHandler | undefined = registry.getHandler(taskType);
|
||||
|
||||
if (!handler) {
|
||||
throw new Error(`No handler registered for task type: ${taskType}`);
|
||||
}
|
||||
|
||||
// Create task logger
|
||||
const taskLogger: TaskLogger = new TaskLogger({
|
||||
taskId: taskIdString,
|
||||
context: `${handler.name}`,
|
||||
});
|
||||
|
||||
// Create backend API client
|
||||
const backendAPI: BackendAPI = new BackendAPI();
|
||||
|
||||
// Build task context
|
||||
const context: TaskContext = {
|
||||
taskId,
|
||||
projectId,
|
||||
taskType,
|
||||
metadata,
|
||||
logger: taskLogger,
|
||||
backendAPI,
|
||||
createdAt,
|
||||
startedAt: new Date(),
|
||||
};
|
||||
|
||||
try {
|
||||
// Log handler starting
|
||||
await taskLogger.info(
|
||||
`Starting ${handler.name} for task type: ${taskType}`,
|
||||
);
|
||||
|
||||
// Validate metadata if the handler supports it
|
||||
if (handler.validateMetadata && !handler.validateMetadata(metadata)) {
|
||||
throw new Error(`Invalid metadata for task type: ${taskType}`);
|
||||
}
|
||||
|
||||
// Execute the task handler
|
||||
const result: TaskResult = await handler.execute(context);
|
||||
|
||||
// Log result
|
||||
if (result.success) {
|
||||
await taskLogger.info(`Task completed: ${result.message}`);
|
||||
|
||||
if (result.pullRequestsCreated && result.pullRequestsCreated > 0) {
|
||||
await taskLogger.info(
|
||||
`Created ${result.pullRequestsCreated} pull request(s): ${result.pullRequestUrls?.join(", ") || ""}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await taskLogger.warning(`Task did not succeed: ${result.message}`);
|
||||
}
|
||||
|
||||
// Flush all pending logs
|
||||
await taskLogger.flush();
|
||||
|
||||
/*
|
||||
* If the task was not successful and we want to report it as an error
|
||||
* Note: Based on user requirements, "no fix found" should be Completed, not Error
|
||||
* Only throw if there was an actual error (not just "no action taken")
|
||||
*/
|
||||
if (!result.success && result.data?.["isError"]) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ensure logs are flushed even on error
|
||||
await taskLogger.flush();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const startTaskProcessingLoop: () => Promise<void> =
|
||||
async (): Promise<void> => {
|
||||
logger.info("Starting AI Agent task processing loop...");
|
||||
|
||||
const getPendingTaskUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/api/ai-agent-task/get-pending-task");
|
||||
|
||||
const updateTaskStatusUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/api/ai-agent-task/update-task-status");
|
||||
|
||||
/* Continuous loop to process tasks */
|
||||
while (true) {
|
||||
try {
|
||||
/* Fetch one scheduled task */
|
||||
const getPendingTaskResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: getPendingTaskUrl,
|
||||
data: AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
});
|
||||
|
||||
if (!getPendingTaskResult.isSuccess()) {
|
||||
logger.error("Failed to fetch pending task from server");
|
||||
logger.debug(
|
||||
`Sleeping for ${SLEEP_WHEN_NO_TASKS_MS / 1000} seconds before retrying...`,
|
||||
);
|
||||
await Sleep.sleep(SLEEP_WHEN_NO_TASKS_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
const responseData: GetPendingTaskResponse =
|
||||
getPendingTaskResult.data as unknown as GetPendingTaskResponse;
|
||||
const task: AIAgentTaskData | null = responseData.task;
|
||||
|
||||
if (!task || !task._id) {
|
||||
logger.debug("No pending tasks available");
|
||||
logger.debug(
|
||||
`Sleeping for ${SLEEP_WHEN_NO_TASKS_MS / 1000} seconds before checking again...`,
|
||||
);
|
||||
await Sleep.sleep(SLEEP_WHEN_NO_TASKS_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
const taskId: string = task._id;
|
||||
const taskType: string = task.taskType || "Unknown";
|
||||
logger.info(`Processing task: ${taskId} (type: ${taskType})`);
|
||||
|
||||
try {
|
||||
/* Mark task as InProgress */
|
||||
const inProgressResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: updateTaskStatusUrl,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: taskId,
|
||||
status: AIAgentTaskStatus.InProgress,
|
||||
},
|
||||
});
|
||||
|
||||
if (!inProgressResult.isSuccess()) {
|
||||
logger.error(
|
||||
`Failed to mark task ${taskId} as InProgress. Skipping.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Send task started log */
|
||||
await AIAgentTaskLog.sendTaskStartedLog(taskId);
|
||||
|
||||
/* Execute the task using the handler system */
|
||||
await executeTask(task);
|
||||
|
||||
/* Mark task as Completed */
|
||||
const completedResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: updateTaskStatusUrl,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: taskId,
|
||||
status: AIAgentTaskStatus.Completed,
|
||||
},
|
||||
});
|
||||
|
||||
if (!completedResult.isSuccess()) {
|
||||
logger.error(`Failed to mark task ${taskId} as Completed`);
|
||||
} else {
|
||||
/* Send task completed log */
|
||||
await AIAgentTaskLog.sendTaskCompletedLog(taskId);
|
||||
logger.info(`Task completed successfully: ${taskId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
/* Mark task as Error with error message */
|
||||
const errorMessage: string =
|
||||
error instanceof Error ? error.message : "Unknown error occurred";
|
||||
|
||||
const errorResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: updateTaskStatusUrl,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: taskId,
|
||||
status: AIAgentTaskStatus.Error,
|
||||
statusMessage: errorMessage,
|
||||
},
|
||||
});
|
||||
|
||||
if (!errorResult.isSuccess()) {
|
||||
logger.error(
|
||||
`Failed to mark task ${taskId} as Error: ${errorMessage}`,
|
||||
);
|
||||
}
|
||||
|
||||
/* Send task error log */
|
||||
await AIAgentTaskLog.sendTaskErrorLog(taskId, errorMessage);
|
||||
|
||||
logger.error(`Task failed: ${taskId} - ${errorMessage}`);
|
||||
logger.error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error in task processing loop:");
|
||||
logger.error(error);
|
||||
logger.debug(
|
||||
`Sleeping for ${SLEEP_WHEN_NO_TASKS_MS / 1000} seconds before retrying...`,
|
||||
);
|
||||
await Sleep.sleep(SLEEP_WHEN_NO_TASKS_MS);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default startTaskProcessingLoop;
|
||||
@@ -1,127 +0,0 @@
|
||||
import {
|
||||
ONEUPTIME_URL,
|
||||
AI_AGENT_ID,
|
||||
AI_AGENT_KEY,
|
||||
AI_AGENT_NAME,
|
||||
AI_AGENT_DESCRIPTION,
|
||||
} from "../Config";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import Sleep from "Common/Types/Sleep";
|
||||
import API from "Common/Utils/API";
|
||||
import { HasClusterKey } from "Common/Server/EnvironmentConfig";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import ClusterKeyAuthorization from "Common/Server/Middleware/ClusterKeyAuthorization";
|
||||
|
||||
export default class Register {
|
||||
public static async registerAIAgent(): Promise<void> {
|
||||
// register AI agent with 10 retries and 30 second interval between each retry.
|
||||
|
||||
let currentRetry: number = 0;
|
||||
|
||||
const maxRetry: number = 10;
|
||||
|
||||
const retryIntervalInSeconds: number = 30;
|
||||
|
||||
while (currentRetry < maxRetry) {
|
||||
try {
|
||||
logger.debug(`Registering AI Agent. Attempt: ${currentRetry + 1}`);
|
||||
await Register._registerAIAgent();
|
||||
logger.debug(`AI Agent registered successfully.`);
|
||||
break;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to register AI Agent. Retrying after ${retryIntervalInSeconds} seconds...`,
|
||||
);
|
||||
logger.error(error);
|
||||
currentRetry++;
|
||||
await Sleep.sleep(retryIntervalInSeconds * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async _registerAIAgent(): Promise<void> {
|
||||
if (HasClusterKey) {
|
||||
// Clustered mode: Auto-register and get ID from server
|
||||
const aiAgentRegistrationUrl: URL = URL.fromString(
|
||||
ONEUPTIME_URL.toString(),
|
||||
).addRoute("/api/ai-agent/register");
|
||||
|
||||
logger.debug("Registering AI Agent...");
|
||||
logger.debug("Sending request to: " + aiAgentRegistrationUrl.toString());
|
||||
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: aiAgentRegistrationUrl,
|
||||
data: {
|
||||
aiAgentKey: AI_AGENT_KEY,
|
||||
aiAgentName: AI_AGENT_NAME,
|
||||
aiAgentDescription: AI_AGENT_DESCRIPTION,
|
||||
clusterKey: ClusterKeyAuthorization.getClusterKey(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
logger.error(
|
||||
`Failed to register AI Agent. Status: ${result.statusCode}`,
|
||||
);
|
||||
logger.error(result.data);
|
||||
throw new Error(
|
||||
"Failed to register AI Agent: HTTP " + result.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug("AI Agent Registered");
|
||||
logger.debug(result.data);
|
||||
|
||||
const aiAgentId: string | undefined = result.data["_id"] as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (!aiAgentId) {
|
||||
logger.error("AI Agent ID not found in response");
|
||||
logger.error(result.data);
|
||||
throw new Error("AI Agent ID not found in registration response");
|
||||
}
|
||||
|
||||
LocalCache.setString("AI_AGENT", "AI_AGENT_ID", aiAgentId);
|
||||
} else {
|
||||
// Non-clustered mode: Validate AI agent by sending alive request
|
||||
if (!AI_AGENT_ID) {
|
||||
logger.error("AI_AGENT_ID or ONEUPTIME_SECRET should be set");
|
||||
return process.exit();
|
||||
}
|
||||
|
||||
const aliveUrl: URL = URL.fromString(ONEUPTIME_URL.toString()).addRoute(
|
||||
"/api/ai-agent/alive",
|
||||
);
|
||||
|
||||
logger.debug("Registering AI Agent...");
|
||||
logger.debug("Sending request to: " + aliveUrl.toString());
|
||||
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: aliveUrl,
|
||||
data: {
|
||||
aiAgentKey: AI_AGENT_KEY.toString(),
|
||||
aiAgentId: AI_AGENT_ID.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
if (result.isSuccess()) {
|
||||
LocalCache.setString(
|
||||
"AI_AGENT",
|
||||
"AI_AGENT_ID",
|
||||
AI_AGENT_ID.toString() as string,
|
||||
);
|
||||
logger.debug("AI Agent registered successfully");
|
||||
} else {
|
||||
throw new Error("Failed to register AI Agent: " + result.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`AI Agent ID: ${LocalCache.getString("AI_AGENT", "AI_AGENT_ID") || "Unknown"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,454 +0,0 @@
|
||||
import {
|
||||
BaseTaskHandler,
|
||||
TaskContext,
|
||||
TaskResult,
|
||||
TaskMetadata,
|
||||
TaskResultData,
|
||||
} from "./TaskHandlerInterface";
|
||||
import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType";
|
||||
import {
|
||||
LLMConfig,
|
||||
ExceptionDetails,
|
||||
CodeRepositoryInfo,
|
||||
RepositoryToken,
|
||||
} from "../Utils/BackendAPI";
|
||||
import RepositoryManager, {
|
||||
RepositoryConfig,
|
||||
CloneResult,
|
||||
} from "../Utils/RepositoryManager";
|
||||
import PullRequestCreator, {
|
||||
PullRequestResult,
|
||||
} from "../Utils/PullRequestCreator";
|
||||
import WorkspaceManager, { WorkspaceInfo } from "../Utils/WorkspaceManager";
|
||||
import {
|
||||
CodeAgentFactory,
|
||||
CodeAgent,
|
||||
CodeAgentType,
|
||||
CodeAgentTask,
|
||||
CodeAgentResult,
|
||||
CodeAgentProgressEvent,
|
||||
CodeAgentLLMConfig,
|
||||
} from "../CodeAgents/Index";
|
||||
|
||||
// Metadata structure for Fix Exception tasks
|
||||
export interface FixExceptionMetadata extends TaskMetadata {
|
||||
exceptionId: string;
|
||||
serviceId?: string;
|
||||
stackTrace?: string;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export default class FixExceptionTaskHandler extends BaseTaskHandler<FixExceptionMetadata> {
|
||||
public readonly taskType: AIAgentTaskType = AIAgentTaskType.FixException;
|
||||
public readonly name: string = "Fix Exception Handler";
|
||||
|
||||
// Default timeout for code agent execution (30 minutes)
|
||||
private static readonly CODE_AGENT_TIMEOUT_MS: number = 30 * 60 * 1000;
|
||||
|
||||
public async execute(
|
||||
context: TaskContext<FixExceptionMetadata>,
|
||||
): Promise<TaskResult> {
|
||||
const metadata: FixExceptionMetadata = context.metadata;
|
||||
|
||||
await this.log(
|
||||
context,
|
||||
`Starting Fix Exception task for exception: ${metadata.exceptionId} (taskId: ${context.taskId.toString()})`,
|
||||
);
|
||||
|
||||
let workspace: WorkspaceInfo | null = null;
|
||||
|
||||
try {
|
||||
// Step 1: Get LLM configuration for the project
|
||||
await this.log(context, "Fetching LLM provider configuration...");
|
||||
const llmConfig: LLMConfig = await context.backendAPI.getLLMConfig(
|
||||
context.projectId.toString(),
|
||||
);
|
||||
await this.log(
|
||||
context,
|
||||
`Using LLM provider: ${llmConfig.llmType}${llmConfig.modelName ? ` (${llmConfig.modelName})` : ""}`,
|
||||
);
|
||||
|
||||
// Step 2: Get exception details
|
||||
await this.log(context, "Fetching exception details...");
|
||||
const exceptionDetails: ExceptionDetails =
|
||||
await context.backendAPI.getExceptionDetails(metadata.exceptionId);
|
||||
|
||||
if (!exceptionDetails.service) {
|
||||
await this.log(context, "No service linked to this exception", "error");
|
||||
return this.createFailureResult("No service linked to this exception", {
|
||||
isError: true,
|
||||
});
|
||||
}
|
||||
|
||||
await this.log(
|
||||
context,
|
||||
`Exception: ${exceptionDetails.exception.message.substring(0, 100)}...`,
|
||||
);
|
||||
await this.log(context, `Service: ${exceptionDetails.service.name}`);
|
||||
|
||||
// Step 3: Get linked code repositories
|
||||
await this.log(context, "Finding linked code repositories...");
|
||||
const repositories: Array<CodeRepositoryInfo> =
|
||||
await context.backendAPI.getCodeRepositories(
|
||||
exceptionDetails.service.id,
|
||||
);
|
||||
|
||||
if (repositories.length === 0) {
|
||||
await this.log(
|
||||
context,
|
||||
"No code repositories linked to this service",
|
||||
"error",
|
||||
);
|
||||
return this.createFailureResult(
|
||||
"No code repositories linked to this service via Service Catalog",
|
||||
{ isError: true },
|
||||
);
|
||||
}
|
||||
|
||||
await this.log(
|
||||
context,
|
||||
`Found ${repositories.length} linked code repository(ies)`,
|
||||
);
|
||||
|
||||
// Step 4: Create workspace for the task
|
||||
workspace = await WorkspaceManager.createWorkspace(
|
||||
context.taskId.toString(),
|
||||
);
|
||||
await this.log(context, `Created workspace: ${workspace.workspacePath}`);
|
||||
|
||||
// Step 5: Process each repository
|
||||
const pullRequestUrls: Array<string> = [];
|
||||
const errors: Array<string> = [];
|
||||
|
||||
for (const repo of repositories) {
|
||||
try {
|
||||
await this.log(
|
||||
context,
|
||||
`Processing repository: ${repo.organizationName}/${repo.repositoryName}`,
|
||||
);
|
||||
|
||||
const prUrl: string | null = await this.processRepository(
|
||||
context,
|
||||
repo,
|
||||
exceptionDetails,
|
||||
llmConfig,
|
||||
workspace,
|
||||
);
|
||||
|
||||
if (prUrl) {
|
||||
pullRequestUrls.push(prUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage: string =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
errors.push(
|
||||
`${repo.organizationName}/${repo.repositoryName}: ${errorMessage}`,
|
||||
);
|
||||
await this.log(
|
||||
context,
|
||||
`Failed to process repository ${repo.organizationName}/${repo.repositoryName}: ${errorMessage}`,
|
||||
"error",
|
||||
);
|
||||
// Continue with next repository
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Return result
|
||||
if (pullRequestUrls.length > 0) {
|
||||
await this.log(
|
||||
context,
|
||||
`Successfully created ${pullRequestUrls.length} pull request(s)`,
|
||||
);
|
||||
|
||||
const resultData: TaskResultData = {
|
||||
pullRequests: pullRequestUrls,
|
||||
};
|
||||
|
||||
if (errors.length > 0) {
|
||||
resultData.errors = errors;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Created ${pullRequestUrls.length} pull request(s)`,
|
||||
pullRequestsCreated: pullRequestUrls.length,
|
||||
pullRequestUrls,
|
||||
data: resultData,
|
||||
};
|
||||
}
|
||||
|
||||
// No PRs created - mark as error
|
||||
await this.log(
|
||||
context,
|
||||
"No fixes could be applied to any repository",
|
||||
"error",
|
||||
);
|
||||
return this.createFailureResult(
|
||||
errors.length > 0
|
||||
? `No fixes could be applied. Errors: ${errors.join("; ")}`
|
||||
: "No fixes could be applied to any repository",
|
||||
{ isError: true },
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage: string =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
await this.log(context, `Task failed: ${errorMessage}`, "error");
|
||||
// Mark as an actual error (not just "no action taken") so task gets Error status
|
||||
return this.createFailureResult(errorMessage, { isError: true });
|
||||
} finally {
|
||||
// Cleanup workspace
|
||||
if (workspace) {
|
||||
await this.log(context, "Cleaning up workspace...");
|
||||
await WorkspaceManager.deleteWorkspace(workspace.workspacePath);
|
||||
}
|
||||
|
||||
// Flush logs
|
||||
await context.logger.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Process a single repository
|
||||
private async processRepository(
|
||||
context: TaskContext<FixExceptionMetadata>,
|
||||
repo: CodeRepositoryInfo,
|
||||
exceptionDetails: ExceptionDetails,
|
||||
llmConfig: LLMConfig,
|
||||
workspace: WorkspaceInfo,
|
||||
): Promise<string | null> {
|
||||
// Get access token for the repository
|
||||
await this.log(
|
||||
context,
|
||||
`Getting access token for ${repo.organizationName}/${repo.repositoryName}...`,
|
||||
);
|
||||
|
||||
const tokenData: RepositoryToken =
|
||||
await context.backendAPI.getRepositoryToken(repo.id);
|
||||
|
||||
// Clone the repository
|
||||
await this.log(
|
||||
context,
|
||||
`Cloning repository ${repo.organizationName}/${repo.repositoryName}...`,
|
||||
);
|
||||
|
||||
const repoConfig: RepositoryConfig = {
|
||||
organizationName: tokenData.organizationName,
|
||||
repositoryName: tokenData.repositoryName,
|
||||
token: tokenData.token,
|
||||
repositoryUrl: tokenData.repositoryUrl,
|
||||
};
|
||||
|
||||
const repoManager: RepositoryManager = new RepositoryManager(
|
||||
context.logger,
|
||||
);
|
||||
const cloneResult: CloneResult = await repoManager.cloneRepository(
|
||||
repoConfig,
|
||||
workspace.workspacePath,
|
||||
);
|
||||
|
||||
// Create a fix branch
|
||||
const branchName: string = `oneuptime-fix-exception-${context.taskId.toString().substring(0, 8)}`;
|
||||
await this.log(context, `Creating branch: ${branchName}`);
|
||||
await repoManager.createBranch(cloneResult.repositoryPath, branchName);
|
||||
|
||||
// Build the prompt for the code agent
|
||||
const prompt: string = this.buildFixPrompt(
|
||||
exceptionDetails,
|
||||
repo.servicePathInRepository,
|
||||
);
|
||||
|
||||
// Initialize code agent
|
||||
await this.log(context, "Initializing code agent...");
|
||||
const agent: CodeAgent = CodeAgentFactory.createAgent(
|
||||
CodeAgentType.OpenCode,
|
||||
);
|
||||
const agentConfig: CodeAgentLLMConfig = {
|
||||
llmType: llmConfig.llmType,
|
||||
};
|
||||
|
||||
if (llmConfig.apiKey) {
|
||||
agentConfig.apiKey = llmConfig.apiKey;
|
||||
}
|
||||
|
||||
if (llmConfig.baseUrl) {
|
||||
agentConfig.baseUrl = llmConfig.baseUrl;
|
||||
}
|
||||
|
||||
if (llmConfig.modelName) {
|
||||
agentConfig.modelName = llmConfig.modelName;
|
||||
}
|
||||
|
||||
await agent.initialize(agentConfig, context.logger);
|
||||
|
||||
// Set up progress callback to log agent output
|
||||
agent.onProgress((event: CodeAgentProgressEvent) => {
|
||||
context.logger.logProcessOutput("CodeAgent", event.message);
|
||||
});
|
||||
|
||||
// Execute the code agent
|
||||
await this.log(context, "Running code agent to fix exception...");
|
||||
const codeAgentTask: CodeAgentTask = {
|
||||
workingDirectory: cloneResult.repositoryPath,
|
||||
prompt,
|
||||
timeoutMs: FixExceptionTaskHandler.CODE_AGENT_TIMEOUT_MS,
|
||||
};
|
||||
|
||||
if (repo.servicePathInRepository) {
|
||||
codeAgentTask.servicePath = repo.servicePathInRepository;
|
||||
}
|
||||
|
||||
const agentResult: CodeAgentResult = await agent.executeTask(codeAgentTask);
|
||||
|
||||
// Check if any changes were made
|
||||
if (!agentResult.success || agentResult.filesModified.length === 0) {
|
||||
await this.log(
|
||||
context,
|
||||
`Code agent did not make any changes: ${agentResult.error || agentResult.summary}`,
|
||||
"warning",
|
||||
);
|
||||
await agent.cleanup();
|
||||
return null;
|
||||
}
|
||||
|
||||
await this.log(
|
||||
context,
|
||||
`Code agent modified ${agentResult.filesModified.length} file(s)`,
|
||||
);
|
||||
|
||||
// Add all changes and commit
|
||||
await this.log(context, "Committing changes...");
|
||||
await repoManager.addAllChanges(cloneResult.repositoryPath);
|
||||
|
||||
const commitMessage: string = this.buildCommitMessage(exceptionDetails);
|
||||
await repoManager.commitChanges(cloneResult.repositoryPath, commitMessage);
|
||||
|
||||
// Push the branch
|
||||
await this.log(context, `Pushing branch ${branchName}...`);
|
||||
await repoManager.pushBranch(
|
||||
cloneResult.repositoryPath,
|
||||
branchName,
|
||||
repoConfig,
|
||||
);
|
||||
|
||||
// Create pull request
|
||||
await this.log(context, "Creating pull request...");
|
||||
const prCreator: PullRequestCreator = new PullRequestCreator(
|
||||
context.logger,
|
||||
);
|
||||
|
||||
const prTitle: string = PullRequestCreator.generatePRTitle(
|
||||
exceptionDetails.exception.message,
|
||||
);
|
||||
|
||||
const prBody: string = PullRequestCreator.generatePRBody({
|
||||
exceptionMessage: exceptionDetails.exception.message,
|
||||
exceptionType: exceptionDetails.exception.exceptionType,
|
||||
stackTrace: exceptionDetails.exception.stackTrace,
|
||||
serviceName: exceptionDetails.service?.name || "Unknown Service",
|
||||
summary: agentResult.summary,
|
||||
});
|
||||
|
||||
const prResult: PullRequestResult = await prCreator.createPullRequest({
|
||||
token: tokenData.token,
|
||||
organizationName: tokenData.organizationName,
|
||||
repositoryName: tokenData.repositoryName,
|
||||
baseBranch: repo.mainBranchName || "main",
|
||||
headBranch: branchName,
|
||||
title: prTitle,
|
||||
body: prBody,
|
||||
});
|
||||
|
||||
await this.log(context, `Pull request created: ${prResult.htmlUrl}`);
|
||||
|
||||
// Record the PR in the backend
|
||||
await context.backendAPI.recordPullRequest({
|
||||
taskId: context.taskId.toString(),
|
||||
codeRepositoryId: repo.id,
|
||||
pullRequestUrl: prResult.htmlUrl,
|
||||
pullRequestNumber: prResult.number,
|
||||
pullRequestId: prResult.id,
|
||||
title: prResult.title,
|
||||
description: prBody.substring(0, 1000),
|
||||
headRefName: branchName,
|
||||
baseRefName: repo.mainBranchName || "main",
|
||||
});
|
||||
|
||||
// Cleanup agent
|
||||
await agent.cleanup();
|
||||
|
||||
return prResult.htmlUrl;
|
||||
}
|
||||
|
||||
// Build the prompt for the code agent
|
||||
private buildFixPrompt(
|
||||
exceptionDetails: ExceptionDetails,
|
||||
servicePathInRepository: string | null,
|
||||
): string {
|
||||
let prompt: string = `You are a software engineer fixing a bug in a codebase.
|
||||
|
||||
## Exception Information
|
||||
|
||||
**Exception Type:** ${exceptionDetails.exception.exceptionType}
|
||||
|
||||
**Error Message:**
|
||||
${exceptionDetails.exception.message}
|
||||
|
||||
**Stack Trace:**
|
||||
\`\`\`
|
||||
${exceptionDetails.exception.stackTrace}
|
||||
\`\`\`
|
||||
|
||||
## Task
|
||||
|
||||
Please analyze the stack trace and fix the exception. Here are the steps to follow:
|
||||
|
||||
1. Identify the root cause of the exception from the stack trace
|
||||
2. Find the relevant source files in the codebase
|
||||
3. Implement a fix for the issue
|
||||
4. Make sure your fix handles edge cases appropriately
|
||||
5. The fix should be minimal and focused - only change what's necessary
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Do NOT add excessive error handling or logging unless necessary
|
||||
- Do NOT refactor unrelated code
|
||||
- Keep the fix simple and targeted
|
||||
- Preserve existing code style and patterns
|
||||
- If you cannot determine how to fix the issue, explain why
|
||||
|
||||
Please proceed with analyzing and fixing this exception.`;
|
||||
|
||||
if (servicePathInRepository) {
|
||||
prompt = `The service code is located in the \`${servicePathInRepository}\` directory.\n\n${prompt}`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
// Build commit message for the fix
|
||||
private buildCommitMessage(exceptionDetails: ExceptionDetails): string {
|
||||
const shortMessage: string = exceptionDetails.exception.message
|
||||
.replace(/\n/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.substring(0, 50);
|
||||
|
||||
return `fix: ${shortMessage}
|
||||
|
||||
This commit fixes an exception detected by OneUptime.
|
||||
|
||||
Exception Type: ${exceptionDetails.exception.exceptionType}
|
||||
Exception ID: ${exceptionDetails.exception.id}
|
||||
|
||||
Automatically generated by OneUptime AI Agent.`;
|
||||
}
|
||||
|
||||
// Validate metadata
|
||||
public validateMetadata(metadata: FixExceptionMetadata): boolean {
|
||||
return Boolean(metadata.exceptionId);
|
||||
}
|
||||
|
||||
// Get handler description
|
||||
public getDescription(): string {
|
||||
return "Analyzes exceptions detected by OneUptime and attempts to fix them by modifying the source code and creating a pull request.";
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Export all task handler related types and classes
|
||||
export {
|
||||
TaskHandler,
|
||||
TaskContext,
|
||||
TaskResult,
|
||||
TaskMetadata,
|
||||
TaskResultData,
|
||||
BaseTaskHandler,
|
||||
} from "./TaskHandlerInterface";
|
||||
|
||||
export {
|
||||
default as TaskHandlerRegistry,
|
||||
getTaskHandlerRegistry,
|
||||
} from "./TaskHandlerRegistry";
|
||||
|
||||
export { default as FixExceptionTaskHandler } from "./FixExceptionTaskHandler";
|
||||
@@ -1,161 +0,0 @@
|
||||
import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import TaskLogger from "../Utils/TaskLogger";
|
||||
import BackendAPI from "../Utils/BackendAPI";
|
||||
|
||||
// Base interface for task metadata - handlers should define their own specific metadata types
|
||||
export interface TaskMetadata {
|
||||
// All metadata must have at least these optional fields for extensibility
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// Base interface for task result data
|
||||
export interface TaskResultData {
|
||||
// Pull requests created (for Fix Exception tasks)
|
||||
pullRequests?: Array<string>;
|
||||
// Errors encountered during processing
|
||||
errors?: Array<string>;
|
||||
// Flag to indicate if this is an error result (not just "no action taken")
|
||||
isError?: boolean;
|
||||
// Additional data fields
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// Context provided to task handlers
|
||||
export interface TaskContext<TMetadata extends TaskMetadata = TaskMetadata> {
|
||||
// Task identification
|
||||
taskId: ObjectID;
|
||||
projectId: ObjectID;
|
||||
taskType: AIAgentTaskType;
|
||||
|
||||
// Task metadata (varies by task type)
|
||||
metadata: TMetadata;
|
||||
|
||||
// Utilities
|
||||
logger: TaskLogger;
|
||||
backendAPI: BackendAPI;
|
||||
|
||||
// Task timestamps
|
||||
createdAt: Date;
|
||||
startedAt: Date;
|
||||
}
|
||||
|
||||
// Result returned by task handlers
|
||||
export interface TaskResult {
|
||||
// Whether the task completed successfully
|
||||
success: boolean;
|
||||
|
||||
// Human-readable message describing the result
|
||||
message: string;
|
||||
|
||||
// Additional data about the result (optional)
|
||||
data?: TaskResultData;
|
||||
|
||||
// Number of PRs created (for Fix Exception tasks)
|
||||
pullRequestsCreated?: number;
|
||||
|
||||
// List of PR URLs created
|
||||
pullRequestUrls?: Array<string>;
|
||||
}
|
||||
|
||||
// Interface that all task handlers must implement
|
||||
export interface TaskHandler<TMetadata extends TaskMetadata = TaskMetadata> {
|
||||
// The type of task this handler processes
|
||||
readonly taskType: AIAgentTaskType;
|
||||
|
||||
// Human-readable name for the handler
|
||||
readonly name: string;
|
||||
|
||||
// Execute the task and return a result
|
||||
execute(context: TaskContext<TMetadata>): Promise<TaskResult>;
|
||||
|
||||
// Check if this handler can process a given task
|
||||
canHandle(taskType: AIAgentTaskType): boolean;
|
||||
|
||||
// Optional: Validate task metadata before execution
|
||||
validateMetadata?(metadata: TMetadata): boolean;
|
||||
|
||||
// Optional: Get a description of what this handler does
|
||||
getDescription?(): string;
|
||||
}
|
||||
|
||||
// Abstract base class that provides common functionality for task handlers
|
||||
export abstract class BaseTaskHandler<
|
||||
TMetadata extends TaskMetadata = TaskMetadata,
|
||||
> implements TaskHandler<TMetadata>
|
||||
{
|
||||
public abstract readonly taskType: AIAgentTaskType;
|
||||
public abstract readonly name: string;
|
||||
|
||||
public abstract execute(context: TaskContext<TMetadata>): Promise<TaskResult>;
|
||||
|
||||
public canHandle(taskType: AIAgentTaskType): boolean {
|
||||
return taskType === this.taskType;
|
||||
}
|
||||
|
||||
// Create a success result
|
||||
protected createSuccessResult(
|
||||
message: string,
|
||||
data?: TaskResultData,
|
||||
): TaskResult {
|
||||
const result: TaskResult = {
|
||||
success: true,
|
||||
message,
|
||||
};
|
||||
|
||||
if (data) {
|
||||
result.data = data;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create a failure result
|
||||
protected createFailureResult(
|
||||
message: string,
|
||||
data?: TaskResultData,
|
||||
): TaskResult {
|
||||
const result: TaskResult = {
|
||||
success: false,
|
||||
message,
|
||||
};
|
||||
|
||||
if (data) {
|
||||
result.data = data;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create a result for when no action was taken
|
||||
protected createNoActionResult(message: string): TaskResult {
|
||||
return {
|
||||
success: true,
|
||||
message,
|
||||
pullRequestsCreated: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Log to the task logger
|
||||
protected async log(
|
||||
context: TaskContext<TMetadata>,
|
||||
message: string,
|
||||
level: "info" | "debug" | "warning" | "error" = "info",
|
||||
): Promise<void> {
|
||||
switch (level) {
|
||||
case "debug":
|
||||
await context.logger.debug(message);
|
||||
break;
|
||||
case "warning":
|
||||
await context.logger.warning(message);
|
||||
break;
|
||||
case "error":
|
||||
await context.logger.error(message);
|
||||
break;
|
||||
case "info":
|
||||
default:
|
||||
await context.logger.info(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import { TaskHandler } from "./TaskHandlerInterface";
|
||||
import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
/*
|
||||
* Registry for task handlers
|
||||
* Allows dynamic registration and lookup of handlers by task type
|
||||
*/
|
||||
export default class TaskHandlerRegistry {
|
||||
private static instance: TaskHandlerRegistry | null = null;
|
||||
private handlers: Map<AIAgentTaskType, TaskHandler> = new Map();
|
||||
|
||||
// Private constructor for singleton pattern
|
||||
private constructor() {}
|
||||
|
||||
// Get the singleton instance
|
||||
public static getInstance(): TaskHandlerRegistry {
|
||||
if (!TaskHandlerRegistry.instance) {
|
||||
TaskHandlerRegistry.instance = new TaskHandlerRegistry();
|
||||
}
|
||||
return TaskHandlerRegistry.instance;
|
||||
}
|
||||
|
||||
// Reset the singleton (useful for testing)
|
||||
public static resetInstance(): void {
|
||||
TaskHandlerRegistry.instance = null;
|
||||
}
|
||||
|
||||
// Register a task handler
|
||||
public register(handler: TaskHandler): void {
|
||||
if (this.handlers.has(handler.taskType)) {
|
||||
logger.warn(
|
||||
`Overwriting existing handler for task type: ${handler.taskType}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.handlers.set(handler.taskType, handler);
|
||||
logger.debug(
|
||||
`Registered handler "${handler.name}" for task type: ${handler.taskType}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Register multiple handlers at once
|
||||
public registerAll(handlers: Array<TaskHandler>): void {
|
||||
for (const handler of handlers) {
|
||||
this.register(handler);
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister a handler
|
||||
public unregister(taskType: AIAgentTaskType): void {
|
||||
if (this.handlers.has(taskType)) {
|
||||
this.handlers.delete(taskType);
|
||||
logger.debug(`Unregistered handler for task type: ${taskType}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a handler for a specific task type
|
||||
public getHandler(taskType: AIAgentTaskType): TaskHandler | undefined {
|
||||
return this.handlers.get(taskType);
|
||||
}
|
||||
|
||||
// Check if a handler exists for a task type
|
||||
public hasHandler(taskType: AIAgentTaskType): boolean {
|
||||
return this.handlers.has(taskType);
|
||||
}
|
||||
|
||||
// Get all registered task types
|
||||
public getRegisteredTaskTypes(): Array<AIAgentTaskType> {
|
||||
return Array.from(this.handlers.keys());
|
||||
}
|
||||
|
||||
// Get all registered handlers
|
||||
public getAllHandlers(): Array<TaskHandler> {
|
||||
return Array.from(this.handlers.values());
|
||||
}
|
||||
|
||||
// Get the number of registered handlers
|
||||
public getHandlerCount(): number {
|
||||
return this.handlers.size;
|
||||
}
|
||||
|
||||
// Clear all handlers
|
||||
public clear(): void {
|
||||
this.handlers.clear();
|
||||
logger.debug("Cleared all task handlers");
|
||||
}
|
||||
}
|
||||
|
||||
// Export a convenience function to get the registry instance
|
||||
export function getTaskHandlerRegistry(): TaskHandlerRegistry {
|
||||
return TaskHandlerRegistry.getInstance();
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
|
||||
export default class AIAgentUtil {
|
||||
public static getAIAgentId(): ObjectID {
|
||||
const id: string | undefined =
|
||||
LocalCache.getString("AI_AGENT", "AI_AGENT_ID") ||
|
||||
process.env["AI_AGENT_ID"];
|
||||
|
||||
if (!id) {
|
||||
throw new BadDataException("AI Agent ID not found");
|
||||
}
|
||||
|
||||
return new ObjectID(id);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { AI_AGENT_KEY } from "../Config";
|
||||
import AIAgentUtil from "./AIAgent";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
export default class AIAgentAPIRequest {
|
||||
public static getDefaultRequestBody(): JSONObject {
|
||||
return {
|
||||
aiAgentKey: AI_AGENT_KEY,
|
||||
aiAgentId: AIAgentUtil.getAIAgentId().toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import AIAgentAPIRequest from "./AIAgentAPIRequest";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import LogSeverity from "Common/Types/Log/LogSeverity";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
export interface SendLogOptions {
|
||||
taskId: string;
|
||||
severity: LogSeverity;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export default class AIAgentTaskLog {
|
||||
private static createLogUrl: URL | null = null;
|
||||
|
||||
private static getCreateLogUrl(): URL {
|
||||
if (!this.createLogUrl) {
|
||||
this.createLogUrl = URL.fromString(ONEUPTIME_URL.toString()).addRoute(
|
||||
"/api/ai-agent-task-log/create-log",
|
||||
);
|
||||
}
|
||||
return this.createLogUrl;
|
||||
}
|
||||
|
||||
public static async sendLog(options: SendLogOptions): Promise<boolean> {
|
||||
try {
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: this.getCreateLogUrl(),
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: options.taskId,
|
||||
severity: options.severity,
|
||||
message: options.message,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
logger.error(`Failed to send log for task ${options.taskId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`Error sending log for task ${options.taskId}:`);
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static async sendTaskStartedLog(taskId: string): Promise<boolean> {
|
||||
return this.sendLog({
|
||||
taskId,
|
||||
severity: LogSeverity.Information,
|
||||
message: "Task execution started",
|
||||
});
|
||||
}
|
||||
|
||||
public static async sendTaskCompletedLog(taskId: string): Promise<boolean> {
|
||||
return this.sendLog({
|
||||
taskId,
|
||||
severity: LogSeverity.Information,
|
||||
message: "Task execution completed successfully",
|
||||
});
|
||||
}
|
||||
|
||||
public static async sendTaskErrorLog(
|
||||
taskId: string,
|
||||
errorMessage: string,
|
||||
): Promise<boolean> {
|
||||
return this.sendLog({
|
||||
taskId,
|
||||
severity: LogSeverity.Error,
|
||||
message: `Task execution failed: ${errorMessage}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,394 +0,0 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import AIAgentAPIRequest from "./AIAgentAPIRequest";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import LlmType from "Common/Types/LLM/LlmType";
|
||||
import AIAgentTaskStatus from "Common/Types/AI/AIAgentTaskStatus";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
// API Response types
|
||||
interface LLMConfigResponse {
|
||||
llmType: LlmType;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
modelName?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface ExceptionResponse {
|
||||
id: string;
|
||||
message: string;
|
||||
stackTrace: string;
|
||||
exceptionType: string;
|
||||
fingerprint: string;
|
||||
}
|
||||
|
||||
interface ServiceResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface ExceptionDetailsResponse {
|
||||
exception: ExceptionResponse;
|
||||
service: ServiceResponse | null;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface CodeRepositoryResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
repositoryHostedAt: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
mainBranchName: string;
|
||||
servicePathInRepository: string | null;
|
||||
gitHubAppInstallationId: string | null;
|
||||
}
|
||||
|
||||
interface CodeRepositoriesResponse {
|
||||
repositories: Array<CodeRepositoryResponse>;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface RepositoryTokenResponse {
|
||||
token: string;
|
||||
expiresAt: string;
|
||||
repositoryUrl: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface RecordPullRequestResponse {
|
||||
success: boolean;
|
||||
pullRequestId: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface UpdateTaskStatusResponse {
|
||||
success?: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// Exported types
|
||||
export interface LLMConfig {
|
||||
llmType: LlmType;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
modelName?: string;
|
||||
}
|
||||
|
||||
export interface ExceptionDetails {
|
||||
exception: {
|
||||
id: string;
|
||||
message: string;
|
||||
stackTrace: string;
|
||||
exceptionType: string;
|
||||
fingerprint: string;
|
||||
};
|
||||
service: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface CodeRepositoryInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
repositoryHostedAt: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
mainBranchName: string;
|
||||
servicePathInRepository: string | null;
|
||||
gitHubAppInstallationId: string | null;
|
||||
}
|
||||
|
||||
export interface RepositoryToken {
|
||||
token: string;
|
||||
expiresAt: Date;
|
||||
repositoryUrl: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
}
|
||||
|
||||
export interface RecordPullRequestOptions {
|
||||
taskId: string;
|
||||
codeRepositoryId: string;
|
||||
pullRequestUrl: string;
|
||||
pullRequestNumber?: number;
|
||||
pullRequestId?: number;
|
||||
title: string;
|
||||
description?: string;
|
||||
headRefName?: string;
|
||||
baseRefName?: string;
|
||||
}
|
||||
|
||||
export interface RecordPullRequestResult {
|
||||
success: boolean;
|
||||
pullRequestId: string;
|
||||
}
|
||||
|
||||
export default class BackendAPI {
|
||||
private baseUrl: URL;
|
||||
|
||||
public constructor() {
|
||||
this.baseUrl = URL.fromString(ONEUPTIME_URL.toString());
|
||||
}
|
||||
|
||||
// Get LLM configuration for a project
|
||||
public async getLLMConfig(projectId: string): Promise<LLMConfig> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/get-llm-config",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
projectId: projectId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: LLMConfigResponse =
|
||||
response.data as unknown as LLMConfigResponse;
|
||||
const errorMessage: string = data?.message || "Failed to get LLM config";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: LLMConfigResponse =
|
||||
response.data as unknown as LLMConfigResponse;
|
||||
|
||||
logger.debug(`Got LLM config for project ${projectId}: ${data.llmType}`);
|
||||
|
||||
const llmConfig: LLMConfig = {
|
||||
llmType: data.llmType,
|
||||
};
|
||||
|
||||
if (data.apiKey) {
|
||||
llmConfig.apiKey = data.apiKey;
|
||||
}
|
||||
|
||||
if (data.baseUrl) {
|
||||
llmConfig.baseUrl = data.baseUrl;
|
||||
}
|
||||
|
||||
if (data.modelName) {
|
||||
llmConfig.modelName = data.modelName;
|
||||
}
|
||||
|
||||
return llmConfig;
|
||||
}
|
||||
|
||||
// Get exception details with telemetry service info
|
||||
public async getExceptionDetails(
|
||||
exceptionId: string,
|
||||
): Promise<ExceptionDetails> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/get-exception-details",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
exceptionId: exceptionId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: ExceptionDetailsResponse =
|
||||
response.data as unknown as ExceptionDetailsResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to get exception details";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: ExceptionDetailsResponse =
|
||||
response.data as unknown as ExceptionDetailsResponse;
|
||||
|
||||
logger.debug(
|
||||
`Got exception details for ${exceptionId}: ${data.exception.message.substring(0, 100)}`,
|
||||
);
|
||||
|
||||
return {
|
||||
exception: {
|
||||
id: data.exception.id,
|
||||
message: data.exception.message,
|
||||
stackTrace: data.exception.stackTrace,
|
||||
exceptionType: data.exception.exceptionType,
|
||||
fingerprint: data.exception.fingerprint,
|
||||
},
|
||||
service: data.service
|
||||
? {
|
||||
id: data.service.id,
|
||||
name: data.service.name,
|
||||
description: data.service.description,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Get code repositories linked to a service
|
||||
public async getCodeRepositories(
|
||||
serviceId: string,
|
||||
): Promise<Array<CodeRepositoryInfo>> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/get-code-repositories",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
serviceId: serviceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: CodeRepositoriesResponse =
|
||||
response.data as unknown as CodeRepositoriesResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to get code repositories";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: CodeRepositoriesResponse =
|
||||
response.data as unknown as CodeRepositoriesResponse;
|
||||
|
||||
logger.debug(
|
||||
`Got ${data.repositories.length} code repositories for service ${serviceId}`,
|
||||
);
|
||||
|
||||
return data.repositories.map((repo: CodeRepositoryResponse) => {
|
||||
return {
|
||||
id: repo.id,
|
||||
name: repo.name,
|
||||
repositoryHostedAt: repo.repositoryHostedAt,
|
||||
organizationName: repo.organizationName,
|
||||
repositoryName: repo.repositoryName,
|
||||
mainBranchName: repo.mainBranchName,
|
||||
servicePathInRepository: repo.servicePathInRepository,
|
||||
gitHubAppInstallationId: repo.gitHubAppInstallationId,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Get access token for a code repository
|
||||
public async getRepositoryToken(
|
||||
codeRepositoryId: string,
|
||||
): Promise<RepositoryToken> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/get-repository-token",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
codeRepositoryId: codeRepositoryId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: RepositoryTokenResponse =
|
||||
response.data as unknown as RepositoryTokenResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to get repository token";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: RepositoryTokenResponse =
|
||||
response.data as unknown as RepositoryTokenResponse;
|
||||
|
||||
logger.debug(
|
||||
`Got access token for repository ${data.organizationName}/${data.repositoryName}`,
|
||||
);
|
||||
|
||||
return {
|
||||
token: data.token,
|
||||
expiresAt: new Date(data.expiresAt),
|
||||
repositoryUrl: data.repositoryUrl,
|
||||
organizationName: data.organizationName,
|
||||
repositoryName: data.repositoryName,
|
||||
};
|
||||
}
|
||||
|
||||
// Record a pull request created by the AI Agent
|
||||
public async recordPullRequest(
|
||||
options: RecordPullRequestOptions,
|
||||
): Promise<RecordPullRequestResult> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-data/record-pull-request",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: options.taskId,
|
||||
codeRepositoryId: options.codeRepositoryId,
|
||||
pullRequestUrl: options.pullRequestUrl,
|
||||
pullRequestNumber: options.pullRequestNumber,
|
||||
pullRequestId: options.pullRequestId,
|
||||
title: options.title,
|
||||
description: options.description,
|
||||
headRefName: options.headRefName,
|
||||
baseRefName: options.baseRefName,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: RecordPullRequestResponse =
|
||||
response.data as unknown as RecordPullRequestResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to record pull request";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data: RecordPullRequestResponse =
|
||||
response.data as unknown as RecordPullRequestResponse;
|
||||
|
||||
logger.debug(`Recorded pull request: ${options.pullRequestUrl}`);
|
||||
|
||||
return {
|
||||
success: data.success,
|
||||
pullRequestId: data.pullRequestId,
|
||||
};
|
||||
}
|
||||
|
||||
// Update task status (wrapper around existing endpoint)
|
||||
public async updateTaskStatus(
|
||||
taskId: string,
|
||||
status: AIAgentTaskStatus,
|
||||
statusMessage?: string,
|
||||
): Promise<void> {
|
||||
const url: URL = URL.fromURL(this.baseUrl).addRoute(
|
||||
"/api/ai-agent-task/update-task-status",
|
||||
);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: taskId,
|
||||
status: status,
|
||||
statusMessage: statusMessage,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const data: UpdateTaskStatusResponse =
|
||||
response.data as unknown as UpdateTaskStatusResponse;
|
||||
const errorMessage: string =
|
||||
data?.message || "Failed to update task status";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
logger.debug(`Updated task ${taskId} status to ${status}`);
|
||||
}
|
||||
}
|
||||
@@ -1,369 +0,0 @@
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject, JSONArray } from "Common/Types/JSON";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import Headers from "Common/Types/API/Headers";
|
||||
import TaskLogger from "./TaskLogger";
|
||||
|
||||
export interface PullRequestOptions {
|
||||
token: string;
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
baseBranch: string;
|
||||
headBranch: string;
|
||||
title: string;
|
||||
body: string;
|
||||
draft?: boolean;
|
||||
}
|
||||
|
||||
export interface PullRequestResult {
|
||||
id: number;
|
||||
number: number;
|
||||
url: string;
|
||||
htmlUrl: string;
|
||||
state: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default class PullRequestCreator {
|
||||
private static readonly GITHUB_API_BASE: string = "https://api.github.com";
|
||||
private static readonly GITHUB_API_VERSION: string = "2022-11-28";
|
||||
|
||||
private logger: TaskLogger | null = null;
|
||||
|
||||
public constructor(taskLogger?: TaskLogger) {
|
||||
if (taskLogger) {
|
||||
this.logger = taskLogger;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a pull request on GitHub
|
||||
public async createPullRequest(
|
||||
options: PullRequestOptions,
|
||||
): Promise<PullRequestResult> {
|
||||
await this.log(
|
||||
`Creating pull request: ${options.title} (${options.headBranch} -> ${options.baseBranch})`,
|
||||
);
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${options.organizationName}/${options.repositoryName}/pulls`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(options.token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: {
|
||||
title: options.title,
|
||||
body: options.body,
|
||||
head: options.headBranch,
|
||||
base: options.baseBranch,
|
||||
draft: options.draft || false,
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const errorData: JSONObject = response.data as JSONObject;
|
||||
const errorMessage: string =
|
||||
(errorData["message"] as string) || "Failed to create pull request";
|
||||
logger.error(`GitHub API error: ${errorMessage}`);
|
||||
throw new Error(`Failed to create pull request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const data: JSONObject = response.data as JSONObject;
|
||||
|
||||
const result: PullRequestResult = {
|
||||
id: data["id"] as number,
|
||||
number: data["number"] as number,
|
||||
url: data["url"] as string,
|
||||
htmlUrl: data["html_url"] as string,
|
||||
state: data["state"] as string,
|
||||
title: data["title"] as string,
|
||||
};
|
||||
|
||||
await this.log(`Pull request created: ${result.htmlUrl}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get an existing pull request by number
|
||||
public async getPullRequest(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
pullNumber: number,
|
||||
): Promise<PullRequestResult | null> {
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/pulls/${pullNumber}`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.get({
|
||||
url,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data: JSONObject = response.data as JSONObject;
|
||||
|
||||
return {
|
||||
id: data["id"] as number,
|
||||
number: data["number"] as number,
|
||||
url: data["url"] as string,
|
||||
htmlUrl: data["html_url"] as string,
|
||||
state: data["state"] as string,
|
||||
title: data["title"] as string,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if a pull request already exists for a branch
|
||||
public async findExistingPullRequest(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
headBranch: string,
|
||||
baseBranch: string,
|
||||
): Promise<PullRequestResult | null> {
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/pulls`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONArray> | HTTPErrorResponse = await API.get(
|
||||
{
|
||||
url,
|
||||
headers,
|
||||
params: {
|
||||
head: `${organizationName}:${headBranch}`,
|
||||
base: baseBranch,
|
||||
state: "open",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pulls: JSONArray = response.data as JSONArray;
|
||||
|
||||
if (pulls.length > 0) {
|
||||
const data: JSONObject = pulls[0] as JSONObject;
|
||||
return {
|
||||
id: data["id"] as number,
|
||||
number: data["number"] as number,
|
||||
url: data["url"] as string,
|
||||
htmlUrl: data["html_url"] as string,
|
||||
state: data["state"] as string,
|
||||
title: data["title"] as string,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update an existing pull request
|
||||
public async updatePullRequest(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
pullNumber: number,
|
||||
updates: { title?: string; body?: string; state?: "open" | "closed" },
|
||||
): Promise<PullRequestResult> {
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/pulls/${pullNumber}`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.patch({
|
||||
url,
|
||||
data: updates,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
const errorData: JSONObject = response.data as JSONObject;
|
||||
const errorMessage: string =
|
||||
(errorData["message"] as string) || "Failed to update pull request";
|
||||
throw new Error(`Failed to update pull request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const data: JSONObject = response.data as JSONObject;
|
||||
|
||||
return {
|
||||
id: data["id"] as number,
|
||||
number: data["number"] as number,
|
||||
url: data["url"] as string,
|
||||
htmlUrl: data["html_url"] as string,
|
||||
state: data["state"] as string,
|
||||
title: data["title"] as string,
|
||||
};
|
||||
}
|
||||
|
||||
// Add labels to a pull request
|
||||
public async addLabels(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
issueNumber: number,
|
||||
labels: Array<string>,
|
||||
): Promise<void> {
|
||||
await this.log(`Adding labels to PR #${issueNumber}: ${labels.join(", ")}`);
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/issues/${issueNumber}/labels`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: { labels },
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
logger.warn(`Failed to add labels to PR #${issueNumber}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add reviewers to a pull request
|
||||
public async requestReviewers(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
pullNumber: number,
|
||||
reviewers: Array<string>,
|
||||
teamReviewers?: Array<string>,
|
||||
): Promise<void> {
|
||||
await this.log(`Requesting reviewers for PR #${pullNumber}`);
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/pulls/${pullNumber}/requested_reviewers`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const data: JSONObject = {
|
||||
reviewers,
|
||||
};
|
||||
|
||||
if (teamReviewers && teamReviewers.length > 0) {
|
||||
data["team_reviewers"] = teamReviewers;
|
||||
}
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
logger.warn(`Failed to request reviewers for PR #${pullNumber}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a comment to a pull request
|
||||
public async addComment(
|
||||
token: string,
|
||||
organizationName: string,
|
||||
repositoryName: string,
|
||||
issueNumber: number,
|
||||
comment: string,
|
||||
): Promise<void> {
|
||||
await this.log(`Adding comment to PR #${issueNumber}`);
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
`${PullRequestCreator.GITHUB_API_BASE}/repos/${organizationName}/${repositoryName}/issues/${issueNumber}/comments`,
|
||||
);
|
||||
|
||||
const headers: Headers = this.getHeaders(token);
|
||||
|
||||
const response: HTTPResponse<JSONObject> = await API.post({
|
||||
url,
|
||||
data: { body: comment },
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
logger.warn(`Failed to add comment to PR #${issueNumber}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate PR body from exception details
|
||||
public static generatePRBody(data: {
|
||||
exceptionMessage: string;
|
||||
exceptionType: string;
|
||||
stackTrace: string;
|
||||
serviceName: string;
|
||||
summary: string;
|
||||
}): string {
|
||||
return `## Exception Fix
|
||||
|
||||
This pull request was automatically generated by OneUptime AI Agent to fix an exception.
|
||||
|
||||
### Exception Details
|
||||
|
||||
**Service:** ${data.serviceName}
|
||||
**Type:** ${data.exceptionType}
|
||||
**Message:** ${data.exceptionMessage}
|
||||
|
||||
### Stack Trace
|
||||
|
||||
\`\`\`
|
||||
${data.stackTrace.substring(0, 2000)}${data.stackTrace.length > 2000 ? "\n...(truncated)" : ""}
|
||||
\`\`\`
|
||||
|
||||
### Summary of Changes
|
||||
|
||||
${data.summary}
|
||||
|
||||
---
|
||||
|
||||
*This PR was automatically generated by [OneUptime AI Agent](https://oneuptime.com)*`;
|
||||
}
|
||||
|
||||
// Generate PR title from exception
|
||||
public static generatePRTitle(exceptionMessage: string): string {
|
||||
// Truncate and clean the message for use as a title
|
||||
const cleanMessage: string = exceptionMessage
|
||||
.replace(/\n/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
const maxLength: number = 70;
|
||||
if (cleanMessage.length <= maxLength) {
|
||||
return `fix: ${cleanMessage}`;
|
||||
}
|
||||
|
||||
return `fix: ${cleanMessage.substring(0, maxLength - 3)}...`;
|
||||
}
|
||||
|
||||
// Helper method to get GitHub API headers
|
||||
private getHeaders(token: string): Headers {
|
||||
return {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": PullRequestCreator.GITHUB_API_VERSION,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method for logging
|
||||
private async log(message: string): Promise<void> {
|
||||
if (this.logger) {
|
||||
await this.logger.info(message);
|
||||
} else {
|
||||
logger.debug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
import Execute from "Common/Server/Utils/Execute";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import path from "path";
|
||||
import TaskLogger from "./TaskLogger";
|
||||
|
||||
export interface CloneResult {
|
||||
workingDirectory: string;
|
||||
repositoryPath: string;
|
||||
}
|
||||
|
||||
export interface RepositoryConfig {
|
||||
organizationName: string;
|
||||
repositoryName: string;
|
||||
token: string;
|
||||
repositoryUrl?: string;
|
||||
}
|
||||
|
||||
export default class RepositoryManager {
|
||||
private logger: TaskLogger | null = null;
|
||||
|
||||
public constructor(taskLogger?: TaskLogger) {
|
||||
if (taskLogger) {
|
||||
this.logger = taskLogger;
|
||||
}
|
||||
}
|
||||
|
||||
// Clone a repository with token-based authentication
|
||||
public async cloneRepository(
|
||||
config: RepositoryConfig,
|
||||
workDir: string,
|
||||
): Promise<CloneResult> {
|
||||
await this.log(
|
||||
`Cloning repository ${config.organizationName}/${config.repositoryName}...`,
|
||||
);
|
||||
|
||||
// Build the authenticated URL
|
||||
const authUrl: string = this.buildAuthenticatedUrl(config);
|
||||
|
||||
// Ensure the working directory exists
|
||||
await LocalFile.makeDirectory(workDir);
|
||||
|
||||
// Clone the repository
|
||||
await this.runGitCommand(workDir, ["clone", authUrl]);
|
||||
|
||||
const repositoryPath: string = path.join(workDir, config.repositoryName);
|
||||
|
||||
await this.log(`Repository cloned to ${repositoryPath}`);
|
||||
|
||||
// Set git config for the repository
|
||||
await this.setGitConfig(repositoryPath);
|
||||
|
||||
return {
|
||||
workingDirectory: workDir,
|
||||
repositoryPath: repositoryPath,
|
||||
};
|
||||
}
|
||||
|
||||
// Build URL with embedded token for authentication
|
||||
private buildAuthenticatedUrl(config: RepositoryConfig): string {
|
||||
if (config.repositoryUrl) {
|
||||
// If URL is provided, inject token
|
||||
const url: URL = new URL(config.repositoryUrl);
|
||||
url.username = "x-access-token";
|
||||
url.password = config.token;
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
// Default to GitHub
|
||||
return `https://x-access-token:${config.token}@github.com/${config.organizationName}/${config.repositoryName}.git`;
|
||||
}
|
||||
|
||||
// Set git user config for commits
|
||||
private async setGitConfig(repoPath: string): Promise<void> {
|
||||
await this.runGitCommand(repoPath, [
|
||||
"config",
|
||||
"user.name",
|
||||
"OneUptime AI Agent",
|
||||
]);
|
||||
|
||||
await this.runGitCommand(repoPath, [
|
||||
"config",
|
||||
"user.email",
|
||||
"ai-agent@oneuptime.com",
|
||||
]);
|
||||
}
|
||||
|
||||
// Create a new branch
|
||||
public async createBranch(
|
||||
repoPath: string,
|
||||
branchName: string,
|
||||
): Promise<void> {
|
||||
await this.log(`Creating branch: ${branchName}`);
|
||||
|
||||
await this.runGitCommand(repoPath, ["checkout", "-b", branchName]);
|
||||
|
||||
await this.log(`Branch ${branchName} created and checked out`);
|
||||
}
|
||||
|
||||
// Checkout existing branch
|
||||
public async checkoutBranch(
|
||||
repoPath: string,
|
||||
branchName: string,
|
||||
): Promise<void> {
|
||||
await this.log(`Checking out branch: ${branchName}`);
|
||||
|
||||
await this.runGitCommand(repoPath, ["checkout", branchName]);
|
||||
}
|
||||
|
||||
// Create branch if doesn't exist, or checkout if it does
|
||||
public async createOrCheckoutBranch(
|
||||
repoPath: string,
|
||||
branchName: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Check if branch exists locally
|
||||
await this.runGitCommand(repoPath, ["rev-parse", "--verify", branchName]);
|
||||
await this.checkoutBranch(repoPath, branchName);
|
||||
} catch {
|
||||
// Branch doesn't exist, create it
|
||||
await this.createBranch(repoPath, branchName);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all changes to staging
|
||||
public async addAllChanges(repoPath: string): Promise<void> {
|
||||
await this.log("Adding all changes to git staging...");
|
||||
|
||||
await this.runGitCommand(repoPath, ["add", "-A"]);
|
||||
}
|
||||
|
||||
// Check if there are any changes to commit
|
||||
public async hasChanges(repoPath: string): Promise<boolean> {
|
||||
try {
|
||||
const status: string = await this.runGitCommand(repoPath, [
|
||||
"status",
|
||||
"--porcelain",
|
||||
]);
|
||||
return status.trim().length > 0;
|
||||
} catch (error) {
|
||||
logger.error("Error checking for changes:");
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get list of changed files
|
||||
public async getChangedFiles(repoPath: string): Promise<Array<string>> {
|
||||
const status: string = await this.runGitCommand(repoPath, [
|
||||
"status",
|
||||
"--porcelain",
|
||||
]);
|
||||
|
||||
if (!status.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return status
|
||||
.split("\n")
|
||||
.filter((line: string) => {
|
||||
return line.trim().length > 0;
|
||||
})
|
||||
.map((line: string) => {
|
||||
// Status output format is "XY filename" where XY is the status
|
||||
return line.substring(3).trim();
|
||||
});
|
||||
}
|
||||
|
||||
// Commit changes
|
||||
public async commitChanges(repoPath: string, message: string): Promise<void> {
|
||||
await this.log(`Committing changes: ${message.substring(0, 50)}...`);
|
||||
|
||||
await Execute.executeCommandFile({
|
||||
command: "git",
|
||||
args: ["commit", "-m", message],
|
||||
cwd: repoPath,
|
||||
});
|
||||
|
||||
await this.log("Changes committed successfully");
|
||||
}
|
||||
|
||||
// Push branch to remote
|
||||
public async pushBranch(
|
||||
repoPath: string,
|
||||
branchName: string,
|
||||
config: RepositoryConfig,
|
||||
): Promise<void> {
|
||||
await this.log(`Pushing branch ${branchName} to remote...`);
|
||||
|
||||
// Set the remote URL with authentication
|
||||
const authUrl: string = this.buildAuthenticatedUrl(config);
|
||||
|
||||
// Update the remote URL
|
||||
await this.runGitCommand(repoPath, [
|
||||
"remote",
|
||||
"set-url",
|
||||
"origin",
|
||||
authUrl,
|
||||
]);
|
||||
|
||||
// Push with tracking
|
||||
await this.runGitCommand(repoPath, ["push", "-u", "origin", branchName]);
|
||||
|
||||
await this.log(`Branch ${branchName} pushed to remote`);
|
||||
}
|
||||
|
||||
// Get the current branch name
|
||||
public async getCurrentBranch(repoPath: string): Promise<string> {
|
||||
const branch: string = await this.runGitCommand(repoPath, [
|
||||
"rev-parse",
|
||||
"--abbrev-ref",
|
||||
"HEAD",
|
||||
]);
|
||||
return branch.trim();
|
||||
}
|
||||
|
||||
// Get the current commit hash
|
||||
public async getCurrentCommitHash(repoPath: string): Promise<string> {
|
||||
const hash: string = await this.runGitCommand(repoPath, [
|
||||
"rev-parse",
|
||||
"HEAD",
|
||||
]);
|
||||
return hash.trim();
|
||||
}
|
||||
|
||||
// Pull latest changes from remote
|
||||
public async pullChanges(repoPath: string): Promise<void> {
|
||||
await this.log("Pulling latest changes from remote...");
|
||||
|
||||
await this.runGitCommand(repoPath, ["pull"]);
|
||||
}
|
||||
|
||||
// Discard all local changes
|
||||
public async discardChanges(repoPath: string): Promise<void> {
|
||||
await this.log("Discarding all local changes...");
|
||||
|
||||
await this.runGitCommand(repoPath, ["checkout", "."]);
|
||||
await this.runGitCommand(repoPath, ["clean", "-fd"]);
|
||||
}
|
||||
|
||||
// Clean up the repository directory
|
||||
public async cleanup(workDir: string): Promise<void> {
|
||||
await this.log(`Cleaning up workspace: ${workDir}`);
|
||||
|
||||
try {
|
||||
await LocalFile.deleteDirectory(workDir);
|
||||
await this.log("Workspace cleaned up successfully");
|
||||
} catch (error) {
|
||||
logger.error(`Error cleaning up workspace ${workDir}:`);
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get diff of current changes
|
||||
public async getDiff(repoPath: string): Promise<string> {
|
||||
try {
|
||||
const diff: string = await this.runGitCommand(repoPath, ["diff"]);
|
||||
return diff;
|
||||
} catch (error) {
|
||||
logger.error("Error getting diff:");
|
||||
logger.error(error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Get staged diff
|
||||
public async getStagedDiff(repoPath: string): Promise<string> {
|
||||
try {
|
||||
const diff: string = await this.runGitCommand(repoPath, [
|
||||
"diff",
|
||||
"--staged",
|
||||
]);
|
||||
return diff;
|
||||
} catch (error) {
|
||||
logger.error("Error getting staged diff:");
|
||||
logger.error(error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to run git commands
|
||||
private async runGitCommand(
|
||||
repoPath: string,
|
||||
args: Array<string>,
|
||||
): Promise<string> {
|
||||
const cwd: string = path.resolve(repoPath);
|
||||
|
||||
const logArgs: Array<string> = args.map((arg: string) => {
|
||||
// Mask tokens in URLs
|
||||
if (arg.includes("x-access-token:")) {
|
||||
return arg.replace(/x-access-token:[^@]+@/, "x-access-token:***@");
|
||||
}
|
||||
return arg.includes(" ") ? `"${arg}"` : arg;
|
||||
});
|
||||
|
||||
logger.debug(`Executing git command in ${cwd}: git ${logArgs.join(" ")}`);
|
||||
|
||||
return Execute.executeCommandFile({
|
||||
command: "git",
|
||||
args,
|
||||
cwd,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper method for logging
|
||||
private async log(message: string): Promise<void> {
|
||||
if (this.logger) {
|
||||
await this.logger.info(message);
|
||||
} else {
|
||||
logger.debug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
import { ONEUPTIME_URL } from "../Config";
|
||||
import AIAgentAPIRequest from "./AIAgentAPIRequest";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import LogSeverity from "Common/Types/Log/LogSeverity";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
|
||||
export interface TaskLoggerOptions {
|
||||
taskId: string;
|
||||
context?: string;
|
||||
batchSize?: number;
|
||||
flushIntervalMs?: number;
|
||||
}
|
||||
|
||||
interface LogEntry {
|
||||
severity: LogSeverity;
|
||||
message: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export default class TaskLogger {
|
||||
private taskId: string;
|
||||
private context: string | undefined;
|
||||
private logBuffer: Array<LogEntry> = [];
|
||||
private batchSize: number;
|
||||
private flushIntervalMs: number;
|
||||
private flushTimer: ReturnType<typeof setInterval> | null = null;
|
||||
private createLogUrl: URL | null = null;
|
||||
|
||||
public constructor(options: TaskLoggerOptions) {
|
||||
this.taskId = options.taskId;
|
||||
this.context = options.context;
|
||||
this.batchSize = options.batchSize || 10;
|
||||
this.flushIntervalMs = options.flushIntervalMs || 5000; // 5 seconds default
|
||||
|
||||
// Start periodic flush timer
|
||||
this.startFlushTimer();
|
||||
}
|
||||
|
||||
private getCreateLogUrl(): URL {
|
||||
if (!this.createLogUrl) {
|
||||
this.createLogUrl = URL.fromString(ONEUPTIME_URL.toString()).addRoute(
|
||||
"/api/ai-agent-task-log/create-log",
|
||||
);
|
||||
}
|
||||
return this.createLogUrl;
|
||||
}
|
||||
|
||||
private startFlushTimer(): void {
|
||||
this.flushTimer = setInterval(() => {
|
||||
this.flush().catch((err: Error) => {
|
||||
logger.error(`Error flushing logs: ${err.message}`);
|
||||
});
|
||||
}, this.flushIntervalMs);
|
||||
}
|
||||
|
||||
private stopFlushTimer(): void {
|
||||
if (this.flushTimer) {
|
||||
clearInterval(this.flushTimer);
|
||||
this.flushTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private formatMessage(
|
||||
severity: LogSeverity,
|
||||
message: string,
|
||||
timestamp: Date,
|
||||
): string {
|
||||
const timestampStr: string = OneUptimeDate.toDateTimeLocalString(timestamp);
|
||||
const severityStr: string = severity.toUpperCase().padEnd(7);
|
||||
const contextStr: string = this.context ? `[${this.context}] ` : "";
|
||||
|
||||
return `[${timestampStr}] [${severityStr}] ${contextStr}${message}`;
|
||||
}
|
||||
|
||||
private addToBuffer(severity: LogSeverity, message: string): void {
|
||||
const entry: LogEntry = {
|
||||
severity,
|
||||
message,
|
||||
timestamp: OneUptimeDate.getCurrentDate(),
|
||||
};
|
||||
|
||||
this.logBuffer.push(entry);
|
||||
|
||||
// Also log locally for debugging
|
||||
logger.debug(
|
||||
`[Task ${this.taskId}] ${this.formatMessage(entry.severity, entry.message, entry.timestamp)}`,
|
||||
);
|
||||
|
||||
// Auto-flush if buffer is full
|
||||
if (this.logBuffer.length >= this.batchSize) {
|
||||
this.flush().catch((err: Error) => {
|
||||
logger.error(`Error auto-flushing logs: ${err.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async sendLogToServer(
|
||||
severity: LogSeverity,
|
||||
message: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: this.getCreateLogUrl(),
|
||||
data: {
|
||||
...AIAgentAPIRequest.getDefaultRequestBody(),
|
||||
taskId: this.taskId,
|
||||
severity: severity,
|
||||
message: message,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
logger.error(`Failed to send log for task ${this.taskId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`Error sending log for task ${this.taskId}:`);
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Public logging methods
|
||||
public async debug(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Debug, message);
|
||||
}
|
||||
|
||||
public async info(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Information, message);
|
||||
}
|
||||
|
||||
public async warning(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Warning, message);
|
||||
}
|
||||
|
||||
public async error(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Error, message);
|
||||
// Immediately flush on errors
|
||||
await this.flush();
|
||||
}
|
||||
|
||||
public async trace(message: string): Promise<void> {
|
||||
this.addToBuffer(LogSeverity.Trace, message);
|
||||
}
|
||||
|
||||
// Log output from external processes like OpenCode
|
||||
public async logProcessOutput(
|
||||
processName: string,
|
||||
output: string,
|
||||
): Promise<void> {
|
||||
const lines: Array<string> = output.split("\n").filter((line: string) => {
|
||||
return line.trim().length > 0;
|
||||
});
|
||||
|
||||
for (const line of lines) {
|
||||
this.addToBuffer(LogSeverity.Information, `[${processName}] ${line}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a code block (useful for stack traces, code snippets, etc.)
|
||||
public async logCodeBlock(
|
||||
title: string,
|
||||
code: string,
|
||||
severity: LogSeverity = LogSeverity.Information,
|
||||
): Promise<void> {
|
||||
const formattedCode: string = `${title}:\n\`\`\`\n${code}\n\`\`\``;
|
||||
this.addToBuffer(severity, formattedCode);
|
||||
}
|
||||
|
||||
// Flush all buffered logs to the server
|
||||
public async flush(): Promise<void> {
|
||||
if (this.logBuffer.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all entries and clear buffer
|
||||
const entries: Array<LogEntry> = [...this.logBuffer];
|
||||
this.logBuffer = [];
|
||||
|
||||
// Send each log entry separately to preserve individual log lines
|
||||
for (const entry of entries) {
|
||||
const formattedMessage: string = this.formatMessage(
|
||||
entry.severity,
|
||||
entry.message,
|
||||
entry.timestamp,
|
||||
);
|
||||
await this.sendLogToServer(entry.severity, formattedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup method - call when task is done
|
||||
public async dispose(): Promise<void> {
|
||||
this.stopFlushTimer();
|
||||
await this.flush();
|
||||
}
|
||||
|
||||
// Helper methods for common log patterns
|
||||
public async logStepStart(stepName: string): Promise<void> {
|
||||
await this.info(`Starting: ${stepName}`);
|
||||
}
|
||||
|
||||
public async logStepComplete(stepName: string): Promise<void> {
|
||||
await this.info(`Completed: ${stepName}`);
|
||||
}
|
||||
|
||||
public async logStepFailed(stepName: string, error: string): Promise<void> {
|
||||
await this.error(`Failed: ${stepName} - ${error}`);
|
||||
}
|
||||
|
||||
// Create a child logger with additional context
|
||||
public createChildLogger(childContext: string): TaskLogger {
|
||||
const fullContext: string = this.context
|
||||
? `${this.context}:${childContext}`
|
||||
: childContext;
|
||||
|
||||
return new TaskLogger({
|
||||
taskId: this.taskId,
|
||||
context: fullContext,
|
||||
batchSize: this.batchSize,
|
||||
flushIntervalMs: this.flushIntervalMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
export interface WorkspaceInfo {
|
||||
workspacePath: string;
|
||||
taskId: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export default class WorkspaceManager {
|
||||
private static readonly BASE_TEMP_DIR: string = path.join(
|
||||
os.tmpdir(),
|
||||
"oneuptime-ai-agent",
|
||||
);
|
||||
|
||||
// Create a new workspace for a task
|
||||
public static async createWorkspace(taskId: string): Promise<WorkspaceInfo> {
|
||||
const timestamp: number = Date.now();
|
||||
const uniqueId: string = ObjectID.generate().toString().substring(0, 8);
|
||||
const workspaceName: string = `task-${taskId}-${timestamp}-${uniqueId}`;
|
||||
const workspacePath: string = path.join(this.BASE_TEMP_DIR, workspaceName);
|
||||
|
||||
logger.debug(`Creating workspace: ${workspacePath}`);
|
||||
|
||||
// Create the workspace directory
|
||||
await LocalFile.makeDirectory(workspacePath);
|
||||
|
||||
return {
|
||||
workspacePath,
|
||||
taskId,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
// Create a subdirectory within a workspace
|
||||
public static async createSubdirectory(
|
||||
workspacePath: string,
|
||||
subdirectoryName: string,
|
||||
): Promise<string> {
|
||||
const subdirectoryPath: string = path.join(workspacePath, subdirectoryName);
|
||||
await LocalFile.makeDirectory(subdirectoryPath);
|
||||
return subdirectoryPath;
|
||||
}
|
||||
|
||||
// Check if workspace exists
|
||||
public static async workspaceExists(workspacePath: string): Promise<boolean> {
|
||||
try {
|
||||
await LocalFile.readDirectory(workspacePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a workspace and all its contents
|
||||
public static async deleteWorkspace(workspacePath: string): Promise<void> {
|
||||
logger.debug(`Deleting workspace: ${workspacePath}`);
|
||||
|
||||
try {
|
||||
// Verify the path is within our temp directory to prevent accidental deletion
|
||||
const normalizedPath: string = path.normalize(workspacePath);
|
||||
const normalizedBase: string = path.normalize(this.BASE_TEMP_DIR);
|
||||
|
||||
if (!normalizedPath.startsWith(normalizedBase)) {
|
||||
throw new Error(
|
||||
`Security error: Cannot delete path outside workspace base: ${workspacePath}`,
|
||||
);
|
||||
}
|
||||
|
||||
await LocalFile.deleteDirectory(workspacePath);
|
||||
logger.debug(`Workspace deleted: ${workspacePath}`);
|
||||
} catch (error) {
|
||||
logger.error(`Error deleting workspace ${workspacePath}:`);
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Write a file to workspace
|
||||
public static async writeFile(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
content: string,
|
||||
): Promise<string> {
|
||||
const filePath: string = path.join(workspacePath, relativePath);
|
||||
|
||||
// Ensure parent directory exists
|
||||
const parentDir: string = path.dirname(filePath);
|
||||
await LocalFile.makeDirectory(parentDir);
|
||||
|
||||
await LocalFile.write(filePath, content);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// Read a file from workspace
|
||||
public static async readFile(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
): Promise<string> {
|
||||
const filePath: string = path.join(workspacePath, relativePath);
|
||||
return LocalFile.read(filePath);
|
||||
}
|
||||
|
||||
// Check if a file exists in workspace
|
||||
public static async fileExists(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const filePath: string = path.join(workspacePath, relativePath);
|
||||
await LocalFile.read(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a file from workspace
|
||||
public static async deleteFile(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
): Promise<void> {
|
||||
const filePath: string = path.join(workspacePath, relativePath);
|
||||
await LocalFile.deleteFile(filePath);
|
||||
}
|
||||
|
||||
// List files in workspace directory
|
||||
public static async listFiles(workspacePath: string): Promise<Array<string>> {
|
||||
const entries: Array<{ name: string; isDirectory(): boolean }> =
|
||||
await LocalFile.readDirectory(workspacePath);
|
||||
return entries.map((entry: { name: string }) => {
|
||||
return entry.name;
|
||||
});
|
||||
}
|
||||
|
||||
// Get the full path for a relative path in workspace
|
||||
public static getFullPath(
|
||||
workspacePath: string,
|
||||
relativePath: string,
|
||||
): string {
|
||||
return path.join(workspacePath, relativePath);
|
||||
}
|
||||
|
||||
// Clean up old workspaces (older than specified hours)
|
||||
public static async cleanupOldWorkspaces(
|
||||
maxAgeHours: number = 24,
|
||||
): Promise<number> {
|
||||
logger.debug(`Cleaning up workspaces older than ${maxAgeHours} hours`);
|
||||
|
||||
let cleanedCount: number = 0;
|
||||
|
||||
try {
|
||||
// Ensure base directory exists
|
||||
try {
|
||||
await LocalFile.readDirectory(this.BASE_TEMP_DIR);
|
||||
} catch {
|
||||
// Base directory doesn't exist, nothing to clean
|
||||
return 0;
|
||||
}
|
||||
|
||||
const entries: Array<{ name: string; isDirectory(): boolean }> =
|
||||
await LocalFile.readDirectory(this.BASE_TEMP_DIR);
|
||||
|
||||
const maxAge: number = maxAgeHours * 60 * 60 * 1000; // Convert to milliseconds
|
||||
const now: number = Date.now();
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const workspacePath: string = path.join(this.BASE_TEMP_DIR, entry.name);
|
||||
|
||||
/*
|
||||
* Try to extract timestamp from directory name
|
||||
* Format: task-{taskId}-{timestamp}-{uniqueId}
|
||||
*/
|
||||
const match: RegExpMatchArray | null = entry.name.match(
|
||||
/task-[^-]+-(\d+)-[^-]+/,
|
||||
);
|
||||
|
||||
if (match) {
|
||||
const timestamp: number = parseInt(match[1] || "0", 10);
|
||||
|
||||
if (now - timestamp > maxAge) {
|
||||
await this.deleteWorkspace(workspacePath);
|
||||
cleanedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error during workspace cleanup:");
|
||||
logger.error(error);
|
||||
}
|
||||
|
||||
logger.debug(`Cleaned up ${cleanedCount} old workspaces`);
|
||||
|
||||
return cleanedCount;
|
||||
}
|
||||
|
||||
// Initialize workspace manager (create base directory if needed)
|
||||
public static async initialize(): Promise<void> {
|
||||
try {
|
||||
await LocalFile.makeDirectory(this.BASE_TEMP_DIR);
|
||||
logger.debug(
|
||||
`Workspace base directory initialized: ${this.BASE_TEMP_DIR}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Error initializing workspace manager:");
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the base temp directory path
|
||||
public static getBaseTempDir(): string {
|
||||
return this.BASE_TEMP_DIR;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"watch": [
|
||||
"./",
|
||||
"../Common"
|
||||
],
|
||||
"ext": "ts,tsx",
|
||||
"ignore": ["./node_modules/**", "./public/**", "./bin/**", "./build/**"],
|
||||
"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"
|
||||
}
|
||||
4754
AIAgent/package-lock.json
generated
4754
AIAgent/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "@oneuptime/ai-agent",
|
||||
"version": "1.0.0",
|
||||
"description": "OneUptime AI Agent",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime"
|
||||
},
|
||||
"main": "index.js",
|
||||
"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": "jest --detectOpenHandles --passWithNoTests",
|
||||
"coverage": "jest --detectOpenHandles --coverage",
|
||||
"debug:test": "node --inspect node_modules/.bin/jest --runInBand ./Tests --detectOpenHandles"
|
||||
},
|
||||
"author": "OneUptime <hello@oneuptime.com> (https://oneuptime.com/)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
"ejs": "^3.1.10",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^17.0.31",
|
||||
"jest": "^28.1.0",
|
||||
"nodemon": "^2.0.20"
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"rootDir": "",
|
||||
"moduleResolution": "node",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"types": ["node", "jest"],
|
||||
"sourceMap": true,
|
||||
"outDir": "build/dist",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"query": {
|
||||
"age": {
|
||||
"_type": "EqualTo",
|
||||
value: 10
|
||||
}
|
||||
"name": "Hello",
|
||||
// other filters
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"query": {
|
||||
"labels": {
|
||||
"_type": "Includes",
|
||||
"value": [
|
||||
"aaa00000-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
"bbb00000-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,20 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
RUN npm config set fetch-retry-mintimeout 20000
|
||||
RUN npm config set fetch-retry-maxtimeout 60000
|
||||
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
|
||||
@@ -68,8 +65,6 @@ CMD [ "npm", "run", "dev" ]
|
||||
COPY ./APIReference /usr/src/app
|
||||
# Bundle app source
|
||||
RUN npm run compile
|
||||
# Set permission to write logs and cache in case container run as non root
|
||||
RUN chown -R 1000:1000 "/tmp/npm" && chmod -R 2777 "/tmp/npm"
|
||||
#Run the app
|
||||
CMD [ "npm", "start" ]
|
||||
{{ end }}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import AuthenticationServiceHandler from "./Service/Authentication";
|
||||
import DataTypeServiceHandler from "./Service/DataType";
|
||||
import DataTypeDetailServiceHandler from "./Service/DataTypeDetail";
|
||||
import ErrorServiceHandler from "./Service/Errors";
|
||||
import OpenAPIServiceHandler from "./Service/OpenAPI";
|
||||
import IntroductionServiceHandler from "./Service/Introduction";
|
||||
@@ -11,7 +10,6 @@ import PermissionServiceHandler from "./Service/Permissions";
|
||||
import StatusServiceHandler from "./Service/Status";
|
||||
import { StaticPath } from "./Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "./Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "./Utils/DataTypes";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import FeatureSet from "Common/Server/Types/FeatureSet";
|
||||
import Express, {
|
||||
@@ -26,9 +24,6 @@ const APIReferenceFeatureSet: FeatureSet = {
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
|
||||
const DataTypeDictionary: Dictionary<DataTypeDocumentation> =
|
||||
DataTypeUtil.getDataTypeDictionaryByPath();
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
// Serve static files for the API reference with a cache max age of 30 days
|
||||
@@ -77,8 +72,6 @@ const APIReferenceFeatureSet: FeatureSet = {
|
||||
return StatusServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "data-types") {
|
||||
return DataTypeServiceHandler.executeResponse(req, res);
|
||||
} else if (DataTypeDictionary[page]) {
|
||||
return DataTypeDetailServiceHandler.executeResponse(req, res);
|
||||
} else if (currentResource) {
|
||||
return ModelServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Retrieve resources documentation
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
@@ -19,7 +16,7 @@ export default class ServiceHandler {
|
||||
|
||||
// Extract page parameter from request
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Set default page title and description for the authentication page
|
||||
pageTitle = "Authentication";
|
||||
@@ -29,7 +26,6 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
pageData["selectCode"] = await LocalCache.getOrSetString(
|
||||
pageData.selectCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"select",
|
||||
async () => {
|
||||
@@ -25,7 +22,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["sortCode"] = await LocalCache.getOrSetString(
|
||||
pageData.sortCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"sort",
|
||||
async () => {
|
||||
@@ -33,7 +30,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["equalToCode"] = await LocalCache.getOrSetString(
|
||||
pageData.equalToCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to",
|
||||
async () => {
|
||||
@@ -41,7 +38,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["equalToOrNullCode"] = await LocalCache.getOrSetString(
|
||||
pageData.equalToOrNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to-or-null",
|
||||
async () => {
|
||||
@@ -51,7 +48,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["greaterThanCode"] = await LocalCache.getOrSetString(
|
||||
pageData.greaterThanCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than",
|
||||
async () => {
|
||||
@@ -61,7 +58,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["greaterThanOrEqualCode"] = await LocalCache.getOrSetString(
|
||||
pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than-or-equal",
|
||||
async () => {
|
||||
@@ -71,7 +68,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["lessThanCode"] = await LocalCache.getOrSetString(
|
||||
pageData.lessThanCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than",
|
||||
async () => {
|
||||
@@ -81,7 +78,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["lessThanOrEqualCode"] = await LocalCache.getOrSetString(
|
||||
pageData.lessThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
@@ -91,17 +88,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["includesCode"] = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"includes",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/Includes.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData["lessThanOrNullCode"] = await LocalCache.getOrSetString(
|
||||
pageData.lessThanOrNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
@@ -111,7 +98,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["greaterThanOrNullCode"] = await LocalCache.getOrSetString(
|
||||
pageData.greaterThanOrNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
@@ -121,7 +108,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["isNullCode"] = await LocalCache.getOrSetString(
|
||||
pageData.isNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"is-null",
|
||||
async () => {
|
||||
@@ -129,7 +116,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["notNullCode"] = await LocalCache.getOrSetString(
|
||||
pageData.notNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-null",
|
||||
async () => {
|
||||
@@ -137,7 +124,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["notEqualToCode"] = await LocalCache.getOrSetString(
|
||||
pageData.notEqualToCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-equals",
|
||||
async () => {
|
||||
@@ -155,7 +142,6 @@ export default class ServiceHandler {
|
||||
pageDescription:
|
||||
"Data Types that can be used to interact with OneUptime API",
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,10 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Fetch a list of resources used in the application
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
// Handles the HTTP response for a given request
|
||||
@@ -20,7 +17,7 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the 'page' parameter from the request
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Set the default page title and description
|
||||
pageTitle = "Errors";
|
||||
@@ -30,7 +27,6 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Get all resources and featured resources from ResourceUtil
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
const FeaturedResources: Array<ModelDocumentation> =
|
||||
ResourceUtil.getFeaturedResources();
|
||||
|
||||
@@ -23,10 +20,10 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the requested page from the URL parameters
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Set featured resources for the page
|
||||
pageData["featuredResources"] = FeaturedResources;
|
||||
pageData.featuredResources = FeaturedResources;
|
||||
|
||||
// Set page title and description
|
||||
pageTitle = "Introduction";
|
||||
@@ -36,7 +33,6 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import CodeExampleGenerator, {
|
||||
CodeExamples,
|
||||
} from "../Utils/CodeExampleGenerator";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import PageNotFoundServiceHandler from "./PageNotFound";
|
||||
import { AppApiRoute } from "Common/ServiceRoute";
|
||||
import BaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
||||
import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl";
|
||||
import {
|
||||
getTableColumns,
|
||||
TableColumnMetadata,
|
||||
} from "Common/Types/Database/TableColumn";
|
||||
import { getTableColumns } from "Common/Types/Database/TableColumn";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { JSONObject, JSONValue } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Permission, {
|
||||
PermissionHelper,
|
||||
@@ -24,305 +15,11 @@ import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
|
||||
interface ExampleObjects {
|
||||
simpleSelectExample: JSONObject;
|
||||
simpleQueryExample: JSONObject;
|
||||
simpleSortExample: JSONObject;
|
||||
simpleCreateExample: JSONObject;
|
||||
simpleUpdateExample: JSONObject;
|
||||
simpleResponseExample: JSONObject;
|
||||
simpleListResponseExample: Array<JSONObject>;
|
||||
}
|
||||
|
||||
interface ApiCodeExamples {
|
||||
list: CodeExamples;
|
||||
getItem: CodeExamples;
|
||||
count: CodeExamples;
|
||||
create: CodeExamples;
|
||||
update: CodeExamples;
|
||||
delete: CodeExamples;
|
||||
}
|
||||
|
||||
// Helper function to get a default example value based on column type
|
||||
function getDefaultExampleForType(
|
||||
columnType: string | undefined,
|
||||
columnTitle: string | undefined,
|
||||
): JSONValue {
|
||||
const typeStr: string = (columnType || "").toLowerCase();
|
||||
const title: string = (columnTitle || "").toLowerCase();
|
||||
|
||||
if (typeStr.includes("objectid") || typeStr.includes("id")) {
|
||||
return "550e8400-e29b-41d4-a716-446655440000";
|
||||
}
|
||||
if (typeStr.includes("boolean") || typeStr.includes("bool")) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
typeStr.includes("number") ||
|
||||
typeStr.includes("int") ||
|
||||
typeStr.includes("decimal")
|
||||
) {
|
||||
return 100;
|
||||
}
|
||||
if (typeStr.includes("date") || typeStr.includes("time")) {
|
||||
return "2024-01-15T10:30:00.000Z";
|
||||
}
|
||||
if (typeStr.includes("email")) {
|
||||
return "user@example.com";
|
||||
}
|
||||
if (typeStr.includes("phone")) {
|
||||
return "+1-555-123-4567";
|
||||
}
|
||||
if (typeStr.includes("url") || typeStr.includes("link")) {
|
||||
return "https://example.com";
|
||||
}
|
||||
if (typeStr.includes("color")) {
|
||||
return "#3498db";
|
||||
}
|
||||
if (
|
||||
typeStr.includes("markdown") ||
|
||||
typeStr.includes("longtext") ||
|
||||
typeStr.includes("description")
|
||||
) {
|
||||
return `Example ${title || "text"} content`;
|
||||
}
|
||||
if (typeStr.includes("json") || typeStr.includes("object")) {
|
||||
return { key: "value" };
|
||||
}
|
||||
if (typeStr.includes("array")) {
|
||||
return [];
|
||||
}
|
||||
// Default for text fields
|
||||
return `Example ${title || "value"}`;
|
||||
}
|
||||
|
||||
// Helper function to generate example objects from column metadata
|
||||
function generateExampleObjects(
|
||||
tableColumns: Dictionary<TableColumnMetadata>,
|
||||
exampleObjectID: string,
|
||||
): ExampleObjects {
|
||||
const simpleSelectExample: JSONObject = {};
|
||||
const simpleQueryExample: JSONObject = {};
|
||||
const simpleSortExample: JSONObject = {};
|
||||
const simpleCreateExample: JSONObject = {};
|
||||
const simpleUpdateExample: JSONObject = {};
|
||||
const simpleResponseExample: JSONObject = {
|
||||
_id: exampleObjectID,
|
||||
};
|
||||
|
||||
// Sort columns: prioritize fields with examples, then required, then alphabetically
|
||||
const sortedColumnKeys: Array<string> = Object.keys(tableColumns).sort(
|
||||
(a: string, b: string) => {
|
||||
const aHasExample: boolean = tableColumns[a]?.example !== undefined;
|
||||
const bHasExample: boolean = tableColumns[b]?.example !== undefined;
|
||||
const aRequired: boolean = tableColumns[a]?.required || false;
|
||||
const bRequired: boolean = tableColumns[b]?.required || false;
|
||||
|
||||
// Prioritize fields with examples
|
||||
if (aHasExample && !bHasExample) {
|
||||
return -1;
|
||||
}
|
||||
if (!aHasExample && bHasExample) {
|
||||
return 1;
|
||||
}
|
||||
// Then prioritize required fields
|
||||
if (aRequired && !bRequired) {
|
||||
return -1;
|
||||
}
|
||||
if (!aRequired && bRequired) {
|
||||
return 1;
|
||||
}
|
||||
return a.localeCompare(b);
|
||||
},
|
||||
);
|
||||
|
||||
let selectCount: number = 0;
|
||||
let createCount: number = 0;
|
||||
let updateCount: number = 0;
|
||||
|
||||
for (const key of sortedColumnKeys) {
|
||||
const column: TableColumnMetadata | undefined = tableColumns[key];
|
||||
if (!column) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const accessControl: ColumnAccessControl | undefined = (
|
||||
column as unknown as JSONObject
|
||||
)["permissions"] as ColumnAccessControl | undefined;
|
||||
|
||||
// Get the example value - use defined example or generate a default
|
||||
const exampleValue: JSONValue =
|
||||
column.example !== undefined
|
||||
? (column.example as JSONValue)
|
||||
: getDefaultExampleForType(column.type?.toString(), column.title);
|
||||
|
||||
/*
|
||||
* Add to select example (limit to 5 fields for readability)
|
||||
* Also add the field to response example so response matches select
|
||||
*/
|
||||
if (
|
||||
selectCount < 5 &&
|
||||
accessControl?.read &&
|
||||
accessControl.read.length > 0
|
||||
) {
|
||||
simpleSelectExample[key] = true;
|
||||
simpleResponseExample[key] = exampleValue;
|
||||
selectCount++;
|
||||
}
|
||||
|
||||
// Add to create example (only fields with create permission)
|
||||
if (
|
||||
createCount < 5 &&
|
||||
accessControl?.create &&
|
||||
accessControl.create.length > 0 &&
|
||||
!column.computed
|
||||
) {
|
||||
simpleCreateExample[key] = exampleValue;
|
||||
createCount++;
|
||||
}
|
||||
|
||||
// Add to update example (only fields with update permission)
|
||||
if (
|
||||
updateCount < 3 &&
|
||||
accessControl?.update &&
|
||||
accessControl.update.length > 0 &&
|
||||
!column.computed
|
||||
) {
|
||||
simpleUpdateExample[key] = exampleValue;
|
||||
updateCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a query example using the first string/text field with an example
|
||||
for (const key of sortedColumnKeys) {
|
||||
const column: TableColumnMetadata | undefined = tableColumns[key];
|
||||
if (column) {
|
||||
const exampleValue: JSONValue =
|
||||
column.example !== undefined
|
||||
? (column.example as JSONValue)
|
||||
: getDefaultExampleForType(column.type?.toString(), column.title);
|
||||
|
||||
if (
|
||||
typeof exampleValue === "string" &&
|
||||
column.type?.toString().toLowerCase().includes("text")
|
||||
) {
|
||||
simpleQueryExample[key] = exampleValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add sort example - sort by createdAt descending if available
|
||||
simpleSortExample["createdAt"] = -1;
|
||||
|
||||
// Generate list response with 3 sample items
|
||||
const simpleListResponseExample: Array<JSONObject> = [
|
||||
{ ...simpleResponseExample, _id: exampleObjectID },
|
||||
{
|
||||
...simpleResponseExample,
|
||||
_id: ObjectID.generate().toString(),
|
||||
},
|
||||
{
|
||||
...simpleResponseExample,
|
||||
_id: ObjectID.generate().toString(),
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
simpleSelectExample,
|
||||
simpleQueryExample,
|
||||
simpleSortExample,
|
||||
simpleCreateExample,
|
||||
simpleUpdateExample,
|
||||
simpleResponseExample,
|
||||
simpleListResponseExample,
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to generate code examples for all API operations
|
||||
function generateApiCodeExamples(
|
||||
apiPath: string,
|
||||
exampleObjects: ExampleObjects,
|
||||
exampleObjectID: string,
|
||||
): ApiCodeExamples {
|
||||
// List endpoint
|
||||
const listExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "POST",
|
||||
endpoint: `${apiPath}/get-list?skip=0&limit=10`,
|
||||
body: {
|
||||
select: exampleObjects.simpleSelectExample,
|
||||
query: exampleObjects.simpleQueryExample,
|
||||
sort: exampleObjects.simpleSortExample,
|
||||
},
|
||||
description: "List items with pagination",
|
||||
});
|
||||
|
||||
// Get item endpoint
|
||||
const getItemExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "POST",
|
||||
endpoint: `${apiPath}/${exampleObjectID}/get-item`,
|
||||
body: {
|
||||
select: exampleObjects.simpleSelectExample,
|
||||
},
|
||||
description: "Get a single item by ID",
|
||||
});
|
||||
|
||||
// Count endpoint
|
||||
const countExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "POST",
|
||||
endpoint: `${apiPath}/count`,
|
||||
body: {
|
||||
query: exampleObjects.simpleQueryExample,
|
||||
},
|
||||
description: "Count items matching a query",
|
||||
});
|
||||
|
||||
// Create endpoint
|
||||
const createExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "POST",
|
||||
endpoint: apiPath,
|
||||
body: {
|
||||
data: exampleObjects.simpleCreateExample,
|
||||
},
|
||||
description: "Create a new item",
|
||||
});
|
||||
|
||||
// Update endpoint
|
||||
const updateExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "PUT",
|
||||
endpoint: `${apiPath}/${exampleObjectID}`,
|
||||
body: {
|
||||
data: exampleObjects.simpleUpdateExample,
|
||||
},
|
||||
description: "Update an existing item",
|
||||
});
|
||||
|
||||
// Delete endpoint
|
||||
const deleteExamples: CodeExamples = CodeExampleGenerator.generate({
|
||||
method: "DELETE",
|
||||
endpoint: `${apiPath}/${exampleObjectID}`,
|
||||
description: "Delete an item by ID",
|
||||
});
|
||||
|
||||
return {
|
||||
list: listExamples,
|
||||
getItem: getItemExamples,
|
||||
count: countExamples,
|
||||
create: createExamples,
|
||||
update: updateExamples,
|
||||
delete: deleteExamples,
|
||||
};
|
||||
}
|
||||
|
||||
// Get all resources and resource dictionary
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
|
||||
// Dynamically built from DataTypes registry — no manual updates needed when new types are added
|
||||
const TypeToDocPath: Dictionary<string> = DataTypeUtil.getTypeToDocPathMap();
|
||||
|
||||
// Get all permission props
|
||||
const PermissionDictionary: Dictionary<PermissionProps> =
|
||||
PermissionHelper.getAllPermissionPropsAsDictionary();
|
||||
@@ -336,7 +33,7 @@ export default class ServiceHandler {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
let page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Check if page is provided
|
||||
if (!page) {
|
||||
@@ -359,9 +56,7 @@ export default class ServiceHandler {
|
||||
page = "model";
|
||||
|
||||
// Get table columns for current resource
|
||||
const tableColumns: Dictionary<TableColumnMetadata> = getTableColumns(
|
||||
currentResource.model,
|
||||
);
|
||||
const tableColumns: any = getTableColumns(currentResource.model);
|
||||
|
||||
// Filter out columns with no access
|
||||
for (const key in tableColumns) {
|
||||
@@ -382,14 +77,12 @@ export default class ServiceHandler {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tableColumns[key] && tableColumns[key]!.hideColumnInDocumentation) {
|
||||
if (tableColumns[key].hideColumnInDocumentation) {
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tableColumns[key]) {
|
||||
(tableColumns[key] as any).permissions = accessControl;
|
||||
}
|
||||
tableColumns[key].permissions = accessControl;
|
||||
}
|
||||
|
||||
// Remove unnecessary columns
|
||||
@@ -398,38 +91,12 @@ export default class ServiceHandler {
|
||||
delete tableColumns["deletedByUser"];
|
||||
delete tableColumns["version"];
|
||||
|
||||
// For columns with a modelType (Entity/EntityArray), resolve the related model's documentation path
|
||||
for (const key in tableColumns) {
|
||||
const column: TableColumnMetadata | undefined = tableColumns[key];
|
||||
if (column?.modelType) {
|
||||
try {
|
||||
const relatedModelInstance: BaseModel = new column.modelType();
|
||||
if (relatedModelInstance.enableDocumentation) {
|
||||
(column as any).modelDocumentationPath =
|
||||
relatedModelInstance.getAPIDocumentationPath();
|
||||
(column as any).modelName = relatedModelInstance.singularName;
|
||||
}
|
||||
} catch {
|
||||
// If model instantiation fails, skip linking
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve non-entity complex types to their documentation paths
|
||||
if (column?.type && !(column as any).modelDocumentationPath) {
|
||||
const typeStr: string = column.type.toString();
|
||||
const docPath: string | undefined = TypeToDocPath[typeStr];
|
||||
if (docPath) {
|
||||
(column as any).typeDocumentationPath = docPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set page data
|
||||
pageData["title"] = currentResource.model.singularName;
|
||||
pageData["description"] = currentResource.model.tableDescription;
|
||||
pageData["columns"] = tableColumns;
|
||||
pageData.title = currentResource.model.singularName;
|
||||
pageData.description = currentResource.model.tableDescription;
|
||||
pageData.columns = tableColumns;
|
||||
|
||||
pageData["tablePermissions"] = {
|
||||
pageData.tablePermissions = {
|
||||
read: currentResource.model.readRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
@@ -453,7 +120,7 @@ export default class ServiceHandler {
|
||||
};
|
||||
|
||||
// Cache the list request data
|
||||
pageData["listRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.listRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-request",
|
||||
async () => {
|
||||
@@ -463,7 +130,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the item request data
|
||||
pageData["itemRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.itemRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-request",
|
||||
async () => {
|
||||
@@ -473,7 +140,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the item response data
|
||||
pageData["itemResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.itemResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-response",
|
||||
async () => {
|
||||
@@ -485,7 +152,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the count request data
|
||||
pageData["countRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.countRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-request",
|
||||
async () => {
|
||||
@@ -497,7 +164,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Cache the count response data
|
||||
pageData["countResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.countResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-response",
|
||||
async () => {
|
||||
@@ -508,7 +175,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["updateRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.updateRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-request",
|
||||
async () => {
|
||||
@@ -519,7 +186,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["updateResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.updateResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-response",
|
||||
async () => {
|
||||
@@ -530,7 +197,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["createRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.createRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-request",
|
||||
async () => {
|
||||
@@ -541,7 +208,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["createResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.createResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-response",
|
||||
async () => {
|
||||
@@ -552,7 +219,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["deleteRequest"] = await LocalCache.getOrSetString(
|
||||
pageData.deleteRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-request",
|
||||
async () => {
|
||||
@@ -563,7 +230,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["deleteResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.deleteResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-response",
|
||||
async () => {
|
||||
@@ -575,7 +242,7 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Get list response from cache or set it if it's not available
|
||||
pageData["listResponse"] = await LocalCache.getOrSetString(
|
||||
pageData.listResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-response",
|
||||
async () => {
|
||||
@@ -587,38 +254,19 @@ export default class ServiceHandler {
|
||||
);
|
||||
|
||||
// Generate a unique ID for the example object
|
||||
const exampleObjectID: string = ObjectID.generate().toString();
|
||||
pageData["exampleObjectID"] = exampleObjectID;
|
||||
|
||||
// Generate dynamic example objects from column metadata
|
||||
const exampleObjects: ExampleObjects = generateExampleObjects(
|
||||
tableColumns,
|
||||
exampleObjectID,
|
||||
);
|
||||
pageData["exampleObjects"] = exampleObjects;
|
||||
pageData.exampleObjectID = ObjectID.generate();
|
||||
|
||||
// Construct the API path for the current resource
|
||||
const apiPath: string =
|
||||
pageData.apiPath =
|
||||
AppApiRoute.toString() + currentResource.model.crudApiPath?.toString();
|
||||
pageData["apiPath"] = apiPath;
|
||||
|
||||
// Generate code examples for all languages
|
||||
const codeExamples: ApiCodeExamples = generateApiCodeExamples(
|
||||
apiPath,
|
||||
exampleObjects,
|
||||
exampleObjectID,
|
||||
);
|
||||
pageData["codeExamples"] = codeExamples;
|
||||
|
||||
// Check if the current resource is a master admin API
|
||||
pageData["isMasterAdminApiDocs"] =
|
||||
currentResource.model.isMasterAdminApiDocs;
|
||||
pageData.isMasterAdminApiDocs = currentResource.model.isMasterAdminApiDocs;
|
||||
|
||||
// Render the index page with the required data
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -5,14 +5,11 @@ import {
|
||||
} from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
// Fetch a list of resources used in the application
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
// Handles the HTTP response for a given request
|
||||
@@ -25,7 +22,7 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the 'page' parameter from the request
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {
|
||||
const pageData: any = {
|
||||
hostUrl: new URL(HttpProtocol, Host).toString(),
|
||||
};
|
||||
|
||||
@@ -38,7 +35,6 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources(); // Get an array of model documentation resources
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
// This is a static method that handles the response
|
||||
@@ -23,7 +21,6 @@ export default class ServiceHandler {
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: "Page you're looking for is not found.", // The page description
|
||||
resources: Resources, // The array of model documentation resources
|
||||
dataTypes: DataTypes,
|
||||
pageData: {}, // An empty object to hold any additional page data
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources(); // Get all resources from ResourceUtil
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
@@ -18,14 +15,14 @@ export default class ServiceHandler {
|
||||
let pageTitle: string = ""; // Initialize page title
|
||||
let pageDescription: string = ""; // Initialize page description
|
||||
const page: string | undefined = req.params["page"]; // Get the page parameter from the request
|
||||
const pageData: Dictionary<unknown> = {}; // Initialize page data object
|
||||
const pageData: any = {}; // Initialize page data object
|
||||
|
||||
// Set page title and description
|
||||
pageTitle = "Pagination";
|
||||
pageDescription = "Learn how to paginate requests with OneUptime API";
|
||||
|
||||
// Get response and request code from LocalCache or LocalFile
|
||||
pageData["responseCode"] = await LocalCache.getOrSetString(
|
||||
pageData.responseCode = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"response",
|
||||
async () => {
|
||||
@@ -36,7 +33,7 @@ export default class ServiceHandler {
|
||||
},
|
||||
);
|
||||
|
||||
pageData["requestCode"] = await LocalCache.getOrSetString(
|
||||
pageData.requestCode = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"request",
|
||||
async () => {
|
||||
@@ -51,7 +48,6 @@ export default class ServiceHandler {
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page, // Pass the page parameter
|
||||
resources: Resources, // Pass all resources
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled, // Pass the page title
|
||||
pageDescription: pageDescription, // Pass the page description
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import {
|
||||
PermissionGroup,
|
||||
PermissionHelper,
|
||||
PermissionProps,
|
||||
} from "Common/Types/Permission";
|
||||
import { PermissionHelper, PermissionProps } from "Common/Types/Permission";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const DataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
@@ -24,46 +17,23 @@ export default class ServiceHandler {
|
||||
|
||||
// Get the requested page
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: Dictionary<unknown> = {};
|
||||
const pageData: any = {};
|
||||
|
||||
// Set page title and description
|
||||
pageTitle = "Permissions";
|
||||
pageDescription = "Learn how permissions work with OneUptime";
|
||||
|
||||
// Filter permissions to only include those assignable to tenants
|
||||
const tenantPermissions: Array<PermissionProps> =
|
||||
PermissionHelper.getAllPermissionProps().filter((i: PermissionProps) => {
|
||||
pageData.permissions = PermissionHelper.getAllPermissionProps().filter(
|
||||
(i: PermissionProps) => {
|
||||
return i.isAssignableToTenant;
|
||||
});
|
||||
|
||||
// Group permissions by PermissionGroup
|
||||
const permissionGroups: Array<{
|
||||
group: string;
|
||||
permissions: Array<PermissionProps>;
|
||||
}> = [];
|
||||
|
||||
for (const group of Object.values(PermissionGroup)) {
|
||||
const groupPermissions: Array<PermissionProps> = tenantPermissions.filter(
|
||||
(p: PermissionProps) => {
|
||||
return p.group === group;
|
||||
},
|
||||
);
|
||||
|
||||
if (groupPermissions.length > 0) {
|
||||
permissionGroups.push({
|
||||
group: group,
|
||||
permissions: groupPermissions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pageData["permissionGroups"] = permissionGroups;
|
||||
},
|
||||
);
|
||||
|
||||
// Render the page
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
dataTypes: DataTypes,
|
||||
pageTitle: pageTitle,
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: pageDescription,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import DataTypeUtil, { DataTypeDocumentation } from "../Utils/DataTypes";
|
||||
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
|
||||
|
||||
// Retrieve resources from ResourceUtil
|
||||
const resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const dataTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
@@ -23,7 +21,6 @@ export default class ServiceHandler {
|
||||
enableGoogleTagManager: IsBillingEnabled,
|
||||
pageDescription: "200 - Success",
|
||||
resources: resources, // Pass resources to the template
|
||||
dataTypes: dataTypes,
|
||||
pageData: {}, // Pass empty data to the template
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,771 +0,0 @@
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
export interface RequestPreview {
|
||||
headers: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface CodeExamples {
|
||||
requestPreview: RequestPreview;
|
||||
curl: string;
|
||||
javascript: string;
|
||||
typescript: string;
|
||||
python: string;
|
||||
go: string;
|
||||
java: string;
|
||||
csharp: string;
|
||||
php: string;
|
||||
ruby: string;
|
||||
rust: string;
|
||||
powershell: string;
|
||||
}
|
||||
|
||||
export interface ApiRequestParams {
|
||||
method: "GET" | "POST" | "PUT" | "DELETE";
|
||||
endpoint: string;
|
||||
body?: JSONObject;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export default class CodeExampleGenerator {
|
||||
private static readonly API_KEY_PLACEHOLDER: string = "YOUR_API_KEY";
|
||||
private static readonly BASE_URL: string = "https://oneuptime.com";
|
||||
|
||||
public static generate(params: ApiRequestParams): CodeExamples {
|
||||
return {
|
||||
requestPreview: this.generateRequestPreview(params),
|
||||
curl: this.generateCurl(params),
|
||||
javascript: this.generateJavaScript(params),
|
||||
typescript: this.generateTypeScript(params),
|
||||
python: this.generatePython(params),
|
||||
go: this.generateGo(params),
|
||||
java: this.generateJava(params),
|
||||
csharp: this.generateCSharp(params),
|
||||
php: this.generatePHP(params),
|
||||
ruby: this.generateRuby(params),
|
||||
rust: this.generateRust(params),
|
||||
powershell: this.generatePowerShell(params),
|
||||
};
|
||||
}
|
||||
|
||||
private static generateRequestPreview(
|
||||
params: ApiRequestParams,
|
||||
): RequestPreview {
|
||||
const { body } = params;
|
||||
|
||||
const headers: string = `Content-Type: application/json
|
||||
ApiKey: ${this.API_KEY_PLACEHOLDER}`;
|
||||
|
||||
const bodyStr: string =
|
||||
body && Object.keys(body).length > 0 ? JSON.stringify(body, null, 2) : "";
|
||||
|
||||
return {
|
||||
headers,
|
||||
body: bodyStr,
|
||||
};
|
||||
}
|
||||
|
||||
private static generateCurl(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let curlCmd: string = `curl -X ${method} "${url}"`;
|
||||
curlCmd += ` \\\n -H "Content-Type: application/json"`;
|
||||
curlCmd += ` \\\n -H "ApiKey: ${this.API_KEY_PLACEHOLDER}"`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 2)
|
||||
.split("\n")
|
||||
.map((line: string, index: number) => {
|
||||
return index === 0 ? line : ` ${line}`;
|
||||
})
|
||||
.join("\n");
|
||||
curlCmd += ` \\\n -d '${jsonBody}'`;
|
||||
}
|
||||
|
||||
return curlCmd;
|
||||
}
|
||||
|
||||
private static generateJavaScript(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `const axios = require('axios');
|
||||
|
||||
const response = await axios({
|
||||
method: '${method.toLowerCase()}',
|
||||
url: '${url}',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'ApiKey': '${this.API_KEY_PLACEHOLDER}'
|
||||
}`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 2)
|
||||
.split("\n")
|
||||
.map((line: string, index: number) => {
|
||||
return index === 0 ? line : ` ${line}`;
|
||||
})
|
||||
.join("\n");
|
||||
code += `,\n data: ${jsonBody}`;
|
||||
}
|
||||
|
||||
code += `
|
||||
});
|
||||
|
||||
console.log(response.data);`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateTypeScript(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `import axios, { AxiosResponse } from 'axios';
|
||||
|
||||
interface ApiResponse {
|
||||
// Define your response type here
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const response: AxiosResponse<ApiResponse> = await axios({
|
||||
method: '${method.toLowerCase()}',
|
||||
url: '${url}',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'ApiKey': '${this.API_KEY_PLACEHOLDER}'
|
||||
}`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 2)
|
||||
.split("\n")
|
||||
.map((line: string, index: number) => {
|
||||
return index === 0 ? line : ` ${line}`;
|
||||
})
|
||||
.join("\n");
|
||||
code += `,\n data: ${jsonBody}`;
|
||||
}
|
||||
|
||||
code += `
|
||||
});
|
||||
|
||||
console.log(response.data);`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generatePython(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `import requests
|
||||
|
||||
url = "${url}"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"ApiKey": "${this.API_KEY_PLACEHOLDER}"
|
||||
}`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const pythonBody: string = this.jsonToPython(body);
|
||||
code += `
|
||||
|
||||
payload = ${pythonBody}
|
||||
|
||||
response = requests.${method.toLowerCase()}(url, json=payload, headers=headers)`;
|
||||
} else {
|
||||
code += `
|
||||
|
||||
response = requests.${method.toLowerCase()}(url, headers=headers)`;
|
||||
}
|
||||
|
||||
code += `
|
||||
print(response.json())`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateGo(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
code += `
|
||||
payload := map[string]interface{}{`;
|
||||
const entries: Array<string> = Object.entries(body).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return ` "${key}": ${this.goValue(value)}`;
|
||||
},
|
||||
);
|
||||
code += `\n${entries.join(",\n")},
|
||||
}
|
||||
jsonData, _ := json.Marshal(payload)
|
||||
|
||||
req, _ := http.NewRequest("${method}", "${url}", bytes.NewBuffer(jsonData))`;
|
||||
} else {
|
||||
code += `
|
||||
req, _ := http.NewRequest("${method}", "${url}", nil)`;
|
||||
}
|
||||
|
||||
code += `
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("ApiKey", "${this.API_KEY_PLACEHOLDER}")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, _ := client.Do(req)
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
fmt.Println(string(body))
|
||||
}`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateJava(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class ApiRequest {
|
||||
public static void main(String[] args) throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 12).replace(
|
||||
/"/g,
|
||||
'\\"',
|
||||
);
|
||||
code += `
|
||||
String jsonBody = "${jsonBody.replace(/\n/g, "\\n")}";
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("${url}"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("ApiKey", "${this.API_KEY_PLACEHOLDER}")
|
||||
.method("${method}", HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||
.build();`;
|
||||
} else {
|
||||
code += `
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("${url}"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("ApiKey", "${this.API_KEY_PLACEHOLDER}")
|
||||
.method("${method}", HttpRequest.BodyPublishers.noBody())
|
||||
.build();`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
HttpResponse<String> response = client.send(request,
|
||||
HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
System.out.println(response.body());
|
||||
}
|
||||
}`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateCSharp(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
class Program
|
||||
{
|
||||
static async Task Main()
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
|
||||
client.DefaultRequestHeaders.Add("ApiKey", "${this.API_KEY_PLACEHOLDER}");
|
||||
`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const jsonBody: string = JSON.stringify(body, null, 8);
|
||||
code += `
|
||||
var json = @"${jsonBody.replace(/"/g, '""')}";
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await client.${this.csharpMethod(method)}Async(
|
||||
"${url}"${method !== "GET" && method !== "DELETE" ? ", content" : ""});`;
|
||||
} else {
|
||||
code += `
|
||||
var response = await client.${this.csharpMethod(method)}Async("${url}");`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
}`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generatePHP(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `<?php
|
||||
|
||||
$url = "${url}";
|
||||
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"ApiKey: ${this.API_KEY_PLACEHOLDER}"
|
||||
];
|
||||
`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
code += `
|
||||
$data = ${this.jsonToPhp(body)};
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "${method}");
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));`;
|
||||
} else {
|
||||
code += `
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "${method}");`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
print_r($result);
|
||||
?>`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateRuby(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `require 'net/http'
|
||||
require 'uri'
|
||||
require 'json'
|
||||
|
||||
uri = URI.parse("${url}")
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = true
|
||||
|
||||
request = Net::HTTP::${this.rubyMethodClass(method)}.new(uri.request_uri)
|
||||
request["Content-Type"] = "application/json"
|
||||
request["ApiKey"] = "${this.API_KEY_PLACEHOLDER}"`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const rubyBody: string = this.jsonToRuby(body);
|
||||
code += `
|
||||
|
||||
request.body = ${rubyBody}.to_json`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
response = http.request(request)
|
||||
puts JSON.parse(response.body)`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generateRust(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||
headers.insert("ApiKey", HeaderValue::from_static("${this.API_KEY_PLACEHOLDER}"));`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const rustBody: string = this.jsonToRust(body);
|
||||
code += `
|
||||
|
||||
let body = ${rustBody};
|
||||
|
||||
let response = client
|
||||
.${method.toLowerCase()}("${url}")
|
||||
.headers(headers)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?;`;
|
||||
} else {
|
||||
code += `
|
||||
|
||||
let response = client
|
||||
.${method.toLowerCase()}("${url}")
|
||||
.headers(headers)
|
||||
.send()
|
||||
.await?;`;
|
||||
}
|
||||
|
||||
code += `
|
||||
|
||||
let result: serde_json::Value = response.json().await?;
|
||||
println!("{:#?}", result);
|
||||
|
||||
Ok(())
|
||||
}`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static generatePowerShell(params: ApiRequestParams): string {
|
||||
const { method, endpoint, body } = params;
|
||||
const url: string = `${this.BASE_URL}${endpoint}`;
|
||||
|
||||
let code: string = `$headers = @{
|
||||
"Content-Type" = "application/json"
|
||||
"ApiKey" = "${this.API_KEY_PLACEHOLDER}"
|
||||
}`;
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
const psBody: string = this.jsonToPowerShell(body);
|
||||
code += `
|
||||
|
||||
$body = ${psBody} | ConvertTo-Json -Depth 10
|
||||
|
||||
$response = Invoke-RestMethod -Uri "${url}" -Method ${method} -Headers $headers -Body $body`;
|
||||
} else {
|
||||
code += `
|
||||
|
||||
$response = Invoke-RestMethod -Uri "${url}" -Method ${method} -Headers $headers`;
|
||||
}
|
||||
|
||||
code += `
|
||||
$response | ConvertTo-Json -Depth 10`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
// Helper methods for language-specific formatting
|
||||
|
||||
private static jsonToPython(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "[]";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.jsonToPython(item as JSONObject, indent + 1);
|
||||
});
|
||||
return `[\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}]`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${key}": ${this.pythonValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `{\n${entries.join(",\n")}\n${spaces}}`;
|
||||
}
|
||||
|
||||
return this.pythonValue(obj, indent);
|
||||
}
|
||||
|
||||
private static pythonValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "None";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "True" : "False";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return this.jsonToPython(value as JSONObject, indent);
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static goValue(value: unknown): string {
|
||||
if (value === null) {
|
||||
return "nil";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return `[]interface{}{${value
|
||||
.map((v: unknown) => {
|
||||
return this.goValue(v);
|
||||
})
|
||||
.join(", ")}}`;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const entries: Array<string> = Object.entries(
|
||||
value as Record<string, unknown>,
|
||||
).map(([k, v]: [string, unknown]) => {
|
||||
return `"${k}": ${this.goValue(v)}`;
|
||||
});
|
||||
return `map[string]interface{}{${entries.join(", ")}}`;
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static jsonToRuby(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "[]";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.jsonToRuby(item as JSONObject, indent + 1);
|
||||
});
|
||||
return `[\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}]`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${key}" => ${this.rubyValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `{\n${entries.join(",\n")}\n${spaces}}`;
|
||||
}
|
||||
|
||||
return this.rubyValue(obj, indent);
|
||||
}
|
||||
|
||||
private static rubyValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "nil";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return this.jsonToRuby(value as JSONObject, indent);
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static rubyMethodClass(method: string): string {
|
||||
const methodMap: Record<string, string> = {
|
||||
GET: "Get",
|
||||
POST: "Post",
|
||||
PUT: "Put",
|
||||
DELETE: "Delete",
|
||||
};
|
||||
return methodMap[method] || "Get";
|
||||
}
|
||||
|
||||
private static csharpMethod(method: string): string {
|
||||
const methodMap: Record<string, string> = {
|
||||
GET: "Get",
|
||||
POST: "Post",
|
||||
PUT: "Put",
|
||||
DELETE: "Delete",
|
||||
};
|
||||
return methodMap[method] || "Get";
|
||||
}
|
||||
|
||||
private static jsonToRust(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "json!([])";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.rustInnerValue(item, indent + 1);
|
||||
});
|
||||
return `json!([\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}])`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${key}": ${this.rustInnerValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `json!({\n${entries.join(",\n")}\n${spaces}})`;
|
||||
}
|
||||
|
||||
return `json!(${this.rustInnerValue(obj, indent)})`;
|
||||
}
|
||||
|
||||
private static rustInnerValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "null";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
const items: Array<string> = value.map((v: unknown) => {
|
||||
return this.rustInnerValue(v, indent + 1);
|
||||
});
|
||||
return `[\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}]`;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
const entries: Array<string> = Object.entries(
|
||||
value as Record<string, unknown>,
|
||||
).map(([k, v]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${k}": ${this.rustInnerValue(v, indent + 1)}`;
|
||||
});
|
||||
return `{\n${entries.join(",\n")}\n${spaces}}`;
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static jsonToPowerShell(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "@()";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.jsonToPowerShell(item as JSONObject, indent + 1);
|
||||
});
|
||||
return `@(\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces})`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}${key} = ${this.psValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `@{\n${entries.join("\n")}\n${spaces}}`;
|
||||
}
|
||||
|
||||
return this.psValue(obj, indent);
|
||||
}
|
||||
|
||||
private static psValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "$null";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "$true" : "$false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return this.jsonToPowerShell(value as JSONObject, indent);
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
private static jsonToPhp(obj: JSONObject, indent: number = 0): string {
|
||||
const spaces: string = " ".repeat(indent);
|
||||
const innerSpaces: string = " ".repeat(indent + 1);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) {
|
||||
return "[]";
|
||||
}
|
||||
const items: Array<string> = obj.map((item: unknown) => {
|
||||
return this.jsonToPhp(item as JSONObject, indent + 1);
|
||||
});
|
||||
return `[\n${innerSpaces}${items.join(`,\n${innerSpaces}`)}\n${spaces}]`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const entries: Array<string> = Object.entries(obj).map(
|
||||
([key, value]: [string, unknown]) => {
|
||||
return `${innerSpaces}"${key}" => ${this.phpValue(value, indent + 1)}`;
|
||||
},
|
||||
);
|
||||
return `[\n${entries.join(",\n")}\n${spaces}]`;
|
||||
}
|
||||
|
||||
return this.phpValue(obj, indent);
|
||||
}
|
||||
|
||||
private static phpValue(value: unknown, indent: number = 0): string {
|
||||
if (value === null) {
|
||||
return "null";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `"${value}"`;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return this.jsonToPhp(value as JSONObject, indent);
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
|
||||
export interface DataTypeDocumentation {
|
||||
name: string;
|
||||
path: string;
|
||||
description: string;
|
||||
/*
|
||||
* Additional column type display strings that should link to this data type page.
|
||||
* Used for cases where the TableColumnType enum value doesn't match the PascalCase name
|
||||
* (e.g., enum "Date" should link to the "DateTime" data type page).
|
||||
*/
|
||||
columnTypeAliases?: Array<string>;
|
||||
/*
|
||||
* Category for grouping in sidebar navigation.
|
||||
* Types with the same category are grouped under a collapsible heading.
|
||||
*/
|
||||
category?: string;
|
||||
}
|
||||
|
||||
export interface DataTypeCategory {
|
||||
name: string;
|
||||
types: Array<DataTypeDocumentation>;
|
||||
}
|
||||
|
||||
export default class DataTypeUtil {
|
||||
public static getDataTypes(): Array<DataTypeDocumentation> {
|
||||
return [
|
||||
{
|
||||
name: "ObjectID",
|
||||
path: "object-id",
|
||||
description:
|
||||
"A unique identifier for objects, typically a UUID string.",
|
||||
},
|
||||
{
|
||||
name: "Decimal",
|
||||
path: "decimal",
|
||||
description: "A decimal number type for precise numeric values.",
|
||||
},
|
||||
{
|
||||
name: "Name",
|
||||
path: "name",
|
||||
description: "A structured name type representing a text name value.",
|
||||
},
|
||||
{
|
||||
name: "EqualTo",
|
||||
path: "equal-to",
|
||||
description:
|
||||
"A query filter that matches objects where a field is equal to the specified value.",
|
||||
},
|
||||
{
|
||||
name: "EqualToOrNull",
|
||||
path: "equal-to-or-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is equal to the specified value or is null.",
|
||||
},
|
||||
{
|
||||
name: "MonitorSteps",
|
||||
path: "monitor-steps",
|
||||
description:
|
||||
"Complex nested object describing monitor check configuration including steps and default status.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStep",
|
||||
path: "monitor-step",
|
||||
description:
|
||||
"A single monitor step defining a check target, request configuration, and criteria for determining status.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "Recurring",
|
||||
path: "recurring",
|
||||
description:
|
||||
"Object describing a recurring interval schedule (e.g., every 5 minutes, daily).",
|
||||
},
|
||||
{
|
||||
name: "RestrictionTimes",
|
||||
path: "restriction-times",
|
||||
description:
|
||||
"Object describing on-call duty time restrictions (daily or weekly windows).",
|
||||
},
|
||||
{
|
||||
name: "MonitorCriteria",
|
||||
path: "monitor-criteria",
|
||||
description:
|
||||
"A collection of monitor criteria instances used to evaluate monitor check results.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "PositiveNumber",
|
||||
path: "positive-number",
|
||||
description: "A number type that must be greater than zero.",
|
||||
columnTypeAliases: ["Small Positive Number", "Big Positive Number"],
|
||||
},
|
||||
{
|
||||
name: "MonitorCriteriaInstance",
|
||||
path: "monitor-criteria-instance",
|
||||
description:
|
||||
"A single criteria rule defining conditions and the resulting monitor status when conditions are met.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "NotEqual",
|
||||
path: "not-equal",
|
||||
description:
|
||||
"A query filter that matches objects where a field is not equal to the specified value.",
|
||||
},
|
||||
{
|
||||
name: "Email",
|
||||
path: "email",
|
||||
description: "An email address type with built-in format validation.",
|
||||
},
|
||||
{
|
||||
name: "Phone",
|
||||
path: "phone",
|
||||
description: "A phone number type with built-in format validation.",
|
||||
},
|
||||
{
|
||||
name: "Color",
|
||||
path: "color",
|
||||
description:
|
||||
"A color value represented as a hex string (e.g., #3498db).",
|
||||
},
|
||||
{
|
||||
name: "Domain",
|
||||
path: "domain",
|
||||
description: "A domain name type (e.g., example.com).",
|
||||
},
|
||||
{
|
||||
name: "Version",
|
||||
path: "version",
|
||||
description: "A semantic version type (e.g., 1.0.0).",
|
||||
},
|
||||
{
|
||||
name: "IP",
|
||||
path: "ip",
|
||||
description:
|
||||
"An IP address type supporting both IPv4 and IPv6 formats.",
|
||||
},
|
||||
{
|
||||
name: "Route",
|
||||
path: "route",
|
||||
description: "A URL route/path segment type.",
|
||||
},
|
||||
{
|
||||
name: "URL",
|
||||
path: "url",
|
||||
description: "A full URL type with protocol, host, and path.",
|
||||
},
|
||||
{
|
||||
name: "Permission",
|
||||
path: "permission",
|
||||
description:
|
||||
"A string identifier representing an access control permission in OneUptime.",
|
||||
},
|
||||
{
|
||||
name: "Search",
|
||||
path: "search",
|
||||
description:
|
||||
"A query filter for text search that matches objects containing the specified string.",
|
||||
},
|
||||
{
|
||||
name: "GreaterThan",
|
||||
path: "greater-than",
|
||||
description:
|
||||
"A query filter that matches objects where a field is greater than the specified value.",
|
||||
},
|
||||
{
|
||||
name: "GreaterThanOrEqual",
|
||||
path: "greater-than-or-equal",
|
||||
description:
|
||||
"A query filter that matches objects where a field is greater than or equal to the specified value.",
|
||||
},
|
||||
{
|
||||
name: "GreaterThanOrNull",
|
||||
path: "greater-than-or-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is greater than the specified value or is null.",
|
||||
},
|
||||
{
|
||||
name: "LessThanOrNull",
|
||||
path: "less-than-or-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is less than the specified value or is null.",
|
||||
},
|
||||
{
|
||||
name: "LessThan",
|
||||
path: "less-than",
|
||||
description:
|
||||
"A query filter that matches objects where a field is less than the specified value.",
|
||||
},
|
||||
{
|
||||
name: "LessThanOrEqual",
|
||||
path: "less-than-or-equal",
|
||||
description:
|
||||
"A query filter that matches objects where a field is less than or equal to the specified value.",
|
||||
},
|
||||
{
|
||||
name: "Port",
|
||||
path: "port",
|
||||
description: "A network port number type (1-65535).",
|
||||
},
|
||||
{
|
||||
name: "Hostname",
|
||||
path: "hostname",
|
||||
description: "A hostname type (e.g., api.example.com).",
|
||||
},
|
||||
{
|
||||
name: "HashedString",
|
||||
path: "hashed-string",
|
||||
description:
|
||||
"A string that is stored in hashed form. Used for sensitive data like passwords and API keys.",
|
||||
},
|
||||
{
|
||||
name: "DateTime",
|
||||
path: "date-time",
|
||||
description:
|
||||
"An ISO 8601 date-time string (e.g., 2024-01-15T10:30:00.000Z).",
|
||||
columnTypeAliases: ["Date"],
|
||||
},
|
||||
{
|
||||
name: "Buffer",
|
||||
path: "buffer",
|
||||
description:
|
||||
"A binary data buffer, typically base64-encoded when serialized to JSON.",
|
||||
},
|
||||
{
|
||||
name: "InBetween",
|
||||
path: "in-between",
|
||||
description:
|
||||
"A query filter that matches objects where a field value is between two specified values (inclusive).",
|
||||
},
|
||||
{
|
||||
name: "NotNull",
|
||||
path: "not-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is not null.",
|
||||
},
|
||||
{
|
||||
name: "IsNull",
|
||||
path: "is-null",
|
||||
description:
|
||||
"A query filter that matches objects where a field is null.",
|
||||
},
|
||||
{
|
||||
name: "Includes",
|
||||
path: "includes",
|
||||
description:
|
||||
"A query filter that matches objects where a field value is included in the specified array of values.",
|
||||
},
|
||||
{
|
||||
name: "DashboardComponent",
|
||||
path: "dashboard-component",
|
||||
description:
|
||||
"A configuration object for a dashboard component including its type, layout, and settings.",
|
||||
},
|
||||
{
|
||||
name: "DashboardViewConfig",
|
||||
path: "dashboard-view-config",
|
||||
description:
|
||||
"A configuration object for a dashboard view including its components and layout.",
|
||||
},
|
||||
{
|
||||
name: "CriteriaFilter",
|
||||
path: "criteria-filter",
|
||||
description:
|
||||
"A single filter condition within a MonitorCriteriaInstance that defines what to check and how to compare it.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "CriteriaIncident",
|
||||
path: "criteria-incident",
|
||||
description:
|
||||
"Configuration for an incident that is automatically created when a MonitorCriteriaInstance's conditions are met.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "CriteriaAlert",
|
||||
path: "criteria-alert",
|
||||
description:
|
||||
"Configuration for an alert that is automatically created when a MonitorCriteriaInstance's conditions are met.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "CheckOn",
|
||||
path: "check-on",
|
||||
description:
|
||||
"Enum specifying what aspect of a monitor response to evaluate (e.g., response code, response time, body content).",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "FilterType",
|
||||
path: "filter-type",
|
||||
description:
|
||||
"Enum specifying the comparison operator used in a CriteriaFilter (e.g., Equal To, Greater Than, Contains).",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "FilterCondition",
|
||||
path: "filter-condition",
|
||||
description:
|
||||
"Enum specifying how multiple filters are combined: 'All' (AND) or 'Any' (OR).",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStepLogMonitor",
|
||||
path: "monitor-step-log-monitor",
|
||||
description:
|
||||
"Configuration for a Log monitor step, defining which logs to query and evaluate.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStepTraceMonitor",
|
||||
path: "monitor-step-trace-monitor",
|
||||
description:
|
||||
"Configuration for a Trace monitor step, defining which spans to query and evaluate.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStepMetricMonitor",
|
||||
path: "monitor-step-metric-monitor",
|
||||
description:
|
||||
"Configuration for a Metric monitor step, defining which metrics to query and evaluate.",
|
||||
category: "Monitor",
|
||||
},
|
||||
{
|
||||
name: "MonitorStepSnmpMonitor",
|
||||
path: "monitor-step-snmp-monitor",
|
||||
description:
|
||||
"Configuration for an SNMP monitor step, defining the SNMP device connection and OIDs to query.",
|
||||
category: "Monitor",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public static getDataTypesByCategory(): Array<DataTypeCategory> {
|
||||
const allTypes: Array<DataTypeDocumentation> = DataTypeUtil.getDataTypes();
|
||||
const uncategorized: Array<DataTypeDocumentation> = [];
|
||||
const categoryMap: Dictionary<Array<DataTypeDocumentation>> = {};
|
||||
const categoryOrder: Array<string> = [];
|
||||
|
||||
for (const dt of allTypes) {
|
||||
if (dt.category) {
|
||||
if (!categoryMap[dt.category]) {
|
||||
categoryMap[dt.category] = [];
|
||||
categoryOrder.push(dt.category);
|
||||
}
|
||||
categoryMap[dt.category]!.push(dt);
|
||||
} else {
|
||||
uncategorized.push(dt);
|
||||
}
|
||||
}
|
||||
|
||||
const result: Array<DataTypeCategory> = [];
|
||||
|
||||
// Add uncategorized types first under "General"
|
||||
if (uncategorized.length > 0) {
|
||||
result.push({ name: "General", types: uncategorized });
|
||||
}
|
||||
|
||||
// Add categorized groups
|
||||
for (const cat of categoryOrder) {
|
||||
if (categoryMap[cat]) {
|
||||
result.push({ name: cat, types: categoryMap[cat]! });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static getDataTypeDictionaryByPath(): Dictionary<DataTypeDocumentation> {
|
||||
const dict: Dictionary<DataTypeDocumentation> = {};
|
||||
|
||||
for (const dataType of DataTypeUtil.getDataTypes()) {
|
||||
dict[dataType.path] = dataType;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert PascalCase name to space-separated display string.
|
||||
* e.g., "ObjectID" → "Object ID", "MonitorSteps" → "Monitor Steps",
|
||||
* "HashedString" → "Hashed String", "IP" → "IP"
|
||||
*/
|
||||
private static pascalCaseToDisplayString(name: string): string {
|
||||
return name
|
||||
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
||||
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
|
||||
}
|
||||
|
||||
/*
|
||||
* Build a mapping from column type display strings to data type page paths.
|
||||
* Automatically derives both PascalCase and display-string variants from each
|
||||
* data type's name, so adding a new entry to getDataTypes() is all that's needed.
|
||||
*/
|
||||
public static getTypeToDocPathMap(): Dictionary<string> {
|
||||
const map: Dictionary<string> = {};
|
||||
|
||||
for (const dt of DataTypeUtil.getDataTypes()) {
|
||||
// Map PascalCase name: "ObjectID" → "object-id"
|
||||
map[dt.name] = dt.path;
|
||||
|
||||
// Map display string: "Object ID" → "object-id"
|
||||
const displayName: string = DataTypeUtil.pascalCaseToDisplayString(
|
||||
dt.name,
|
||||
);
|
||||
if (displayName !== dt.name) {
|
||||
map[displayName] = dt.path;
|
||||
}
|
||||
|
||||
// Map any explicit aliases (for edge cases like enum "Date" → "date-time")
|
||||
if (dt.columnTypeAliases) {
|
||||
for (const alias of dt.columnTypeAliases) {
|
||||
map[alias] = dt.path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,8 @@
|
||||
{
|
||||
"watch": ["./","../Common/Server", "../Common/Types", "../Common/Utils", "../Common/Models"],
|
||||
"ext": "ts,tsx",
|
||||
"ext": "ts,json,tsx,env,js,jsx,hbs",
|
||||
"ignore": [
|
||||
"./node_modules/**",
|
||||
"./public/**",
|
||||
"./bin/**",
|
||||
"./build/**",
|
||||
"greenlock.d/*"
|
||||
],
|
||||
"watchOptions": {"useFsEvents": false, "interval": 500},
|
||||
"env": {"TS_NODE_TRANSPILE_ONLY": "1", "TS_NODE_FILES": "false"},
|
||||
"exec": "node -r ts-node/register/transpile-only Index.ts"
|
||||
"exec": "node --inspect=0.0.0.0:9229 --require ts-node/register Index.ts"
|
||||
}
|
||||
457
APIReference/package-lock.json
generated
457
APIReference/package-lock.json
generated
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "@oneuptime/api-reference",
|
||||
"name": "@oneuptime/app",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@oneuptime/api-reference",
|
||||
"name": "@oneuptime/app",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
"ejs": "^3.1.9",
|
||||
"handlebars": "^4.7.8",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -25,56 +26,47 @@
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@asteasolutions/zod-to-openapi": "^7.3.2",
|
||||
"@bull-board/express": "^5.21.4",
|
||||
"@clickhouse/client": "^1.10.1",
|
||||
"@clickhouse/client": "^0.2.10",
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@hcaptcha/react-hcaptcha": "^1.14.0",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.206.0",
|
||||
"@opentelemetry/api-logs": "^0.52.1",
|
||||
"@opentelemetry/context-zone": "^1.25.1",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.207.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.207.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.207.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.207.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.52.1",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.52.1",
|
||||
"@opentelemetry/id-generator-aws-xray": "^1.2.2",
|
||||
"@opentelemetry/instrumentation": "^0.207.0",
|
||||
"@opentelemetry/instrumentation-fetch": "^0.207.0",
|
||||
"@opentelemetry/instrumentation-xml-http-request": "^0.207.0",
|
||||
"@opentelemetry/instrumentation": "^0.52.1",
|
||||
"@opentelemetry/instrumentation-fetch": "^0.52.1",
|
||||
"@opentelemetry/instrumentation-xml-http-request": "^0.52.1",
|
||||
"@opentelemetry/resources": "^1.25.1",
|
||||
"@opentelemetry/sdk-logs": "^0.207.0",
|
||||
"@opentelemetry/sdk-logs": "^0.52.1",
|
||||
"@opentelemetry/sdk-metrics": "^1.25.1",
|
||||
"@opentelemetry/sdk-node": "^0.207.0",
|
||||
"@opentelemetry/sdk-node": "^0.52.1",
|
||||
"@opentelemetry/sdk-trace-node": "^1.25.1",
|
||||
"@opentelemetry/sdk-trace-web": "^1.25.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.37.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.26.0",
|
||||
"@remixicon/react": "^4.2.0",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/web-push": "^3.6.4",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^1.12.0",
|
||||
"botbuilder": "^4.23.3",
|
||||
"bullmq": "^5.61.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"axios": "^1.7.2",
|
||||
"bullmq": "^5.3.3",
|
||||
"Common": "file:../Common",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"expo-server-sdk": "^3.15.0",
|
||||
"express": "^4.21.1",
|
||||
"express": "^4.19.2",
|
||||
"formik": "^2.4.6",
|
||||
"history": "^5.3.0",
|
||||
"ioredis": "^5.3.2",
|
||||
@@ -82,53 +74,48 @@
|
||||
"json5": "^2.2.3",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^12.0.2",
|
||||
"mermaid": "^11.12.2",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^7.0.7",
|
||||
"nodemailer": "^6.9.10",
|
||||
"otpauth": "^9.3.1",
|
||||
"pg": "^8.16.3",
|
||||
"playwright": "^1.56.0",
|
||||
"posthog-js": "^1.275.3",
|
||||
"pg": "^8.7.3",
|
||||
"posthog-js": "^1.139.6",
|
||||
"prop-types": "^15.8.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-big-calendar": "^1.19.4",
|
||||
"react-big-calendar": "^1.13.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.2.2",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-markdown": "^9.0.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-router-dom": "^6.24.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
"react-syntax-highlighter": "^16.0.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"recharts": "^2.12.7",
|
||||
"redis-semaphore": "^5.5.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"slackify-markdown": "^4.4.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"slugify": "^1.6.5",
|
||||
"socket.io": "^4.7.4",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"stripe": "^10.17.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.26",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"universal-cookie": "^7.2.1",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"use-async-effect": "^2.2.6",
|
||||
"uuid": "^8.3.2",
|
||||
"web-push": "^3.6.7",
|
||||
"zod": "^3.25.76"
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
@@ -142,6 +129,7 @@
|
||||
"@types/jest": "^28.1.4",
|
||||
"@types/json2csv": "^5.0.3",
|
||||
"@types/jsonwebtoken": "^8.5.9",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/node-cron": "^3.0.7",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
@@ -155,7 +143,6 @@
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-mock-extended": "^3.0.5",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"sass": "^1.89.2",
|
||||
"ts-jest": "^28.0.5"
|
||||
}
|
||||
},
|
||||
@@ -251,20 +238,89 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"version": "7.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
|
||||
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
"@babel/highlight": "^7.23.4",
|
||||
"chalk": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
|
||||
@@ -279,7 +335,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz",
|
||||
"integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.23.5",
|
||||
@@ -441,21 +496,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
||||
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -470,28 +523,109 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
|
||||
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz",
|
||||
"integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.4"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.6",
|
||||
"@babel/types": "^7.23.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
|
||||
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.23.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
|
||||
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.5"
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
|
||||
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -662,15 +796,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/parser": "^7.27.2",
|
||||
"@babel/types": "^7.27.1"
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -698,14 +831,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
|
||||
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
|
||||
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
"@babel/helper-string-parser": "^7.23.4",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1392,8 +1525,7 @@
|
||||
"node_modules/@types/node": {
|
||||
"version": "17.0.45",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
|
||||
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
|
||||
"peer": true
|
||||
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="
|
||||
},
|
||||
"node_modules/@types/prettier": {
|
||||
"version": "2.7.3",
|
||||
@@ -1623,23 +1755,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
"fill-range": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -1664,7 +1794,6 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001565",
|
||||
"electron-to-chromium": "^1.4.601",
|
||||
@@ -1870,11 +1999,10 @@
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@@ -1926,10 +2054,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
@@ -1944,10 +2071,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
@@ -2094,10 +2220,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -2114,11 +2239,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -2254,6 +2378,26 @@
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.6.1",
|
||||
"wordwrap": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
@@ -2413,7 +2557,6 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@@ -3111,15 +3254,13 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
@@ -3272,13 +3413,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"braces": "^3.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3305,6 +3445,14 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@@ -3317,6 +3465,11 @@
|
||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@@ -3559,11 +3712,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@@ -3809,7 +3961,6 @@
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3983,12 +4134,20 @@
|
||||
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
@@ -4012,7 +4171,6 @@
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
@@ -4085,6 +4243,18 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.17.4",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
|
||||
"integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"uglifyjs": "bin/uglifyjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undefsafe": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||
@@ -4170,6 +4340,11 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"name": "@oneuptime/api-reference",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime"
|
||||
},
|
||||
"main": "Index.ts",
|
||||
"scripts": {
|
||||
"start": "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<div class="text-center py-20">
|
||||
<div class="flex items-center justify-center w-16 h-16 rounded-2xl bg-indigo-100 mx-auto mb-6">
|
||||
<svg class="w-8 h-8 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-7xl font-bold text-slate-900 tracking-tight mb-4">404</h1>
|
||||
<p class="text-xl text-slate-600 mb-8">This page could not be found.</p>
|
||||
<a href="/reference" class="inline-flex items-center gap-2 rounded-lg bg-indigo-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 transition-colors">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
</svg>
|
||||
Back to API Reference
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
<main class="py-16">
|
||||
<article class="prose">
|
||||
<div>
|
||||
<div>
|
||||
|
||||
<h1 class="next-error-h1" style="display:inline-block;margin:0;margin-right:20px;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1>
|
||||
<div style="display:inline-block;text-align:left;line-height:49px;height:49px;vertical-align:middle">
|
||||
<h2 style="font-size:14px;font-weight:normal;line-height:49px;margin:0;padding:0">
|
||||
This page could not be found<!-- -->.
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
@@ -1,51 +1,39 @@
|
||||
<main class="py-12">
|
||||
<main class="py-16">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Authentication</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">You'll need to authenticate your requests to access any of the endpoints in the OneUptime API. In this guide, we'll look at how authentication works. OneUptime offers one way to authenticate your API requests - by using an API Key.</p>
|
||||
</div>
|
||||
<h1>Authentication</h1>
|
||||
<p class="lead">You'll need to authenticate your requests to access any of the endpoints in the OneUptime API. In
|
||||
this guide, we'll look at how authentication works. OneUptime offers one way to authenticate your API requests
|
||||
- by using an API Key.</p>
|
||||
|
||||
<h2 id="generate-api-key" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12">
|
||||
|
||||
<h2 id="basic-authentication" class="mb-5 scroll-mt-24 mt-24 font-bold text-lg">
|
||||
Generate an API Key
|
||||
</h2>
|
||||
<p class="text-slate-600 leading-relaxed">Please head over to <strong class="text-slate-800">Project Settings</strong> > <strong class="text-slate-800">API Keys</strong>. Create a new API Key. Please note: New API Keys have no permissions assigned to them, so you will have to assign a permission before you can use it.</p>
|
||||
<a class="mt-4 inline-flex gap-1 items-center font-medium text-indigo-600 hover:text-indigo-700 transition-colors" href="/reference/permissions">
|
||||
<p> Please head over to <b>Project Settings</b> > <b>API Keys</b>. Create a new API Key. Please note: New API Keys
|
||||
have no permissions assigned to them, so you will have to assign a permission before you can use it.</p>
|
||||
<a class="mt-5 inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/permissions">
|
||||
Read more about permissions
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-5 w-5">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<h2 id="project-id" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12">
|
||||
|
||||
<h2 id="basic-authentication" class="mb-5 scroll-mt-24 mt-24 font-bold text-lg">
|
||||
Project ID
|
||||
</h2>
|
||||
<p class="text-slate-600 leading-relaxed">Please head over to <strong class="text-slate-800">Project Settings</strong> > <strong class="text-slate-800">Project</strong>. You should see your Project ID there.</p>
|
||||
<p> Please head over to <b>Project Settings</b> > <b>Project</b>. You should see your Project ID there.</p>
|
||||
|
||||
<h2 id="auth-with-api-key" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12">
|
||||
<h2 id="basic-authentication">
|
||||
Authentication with API Key
|
||||
</h2>
|
||||
<p class="text-slate-600 leading-relaxed mb-6">You can use OneUptime API Key on Request Header when you're making a request. You can use <code class="inline-code">ApiKey</code> header with your API Key when you make a request.</p>
|
||||
|
||||
<p>You can use OneUptime API Key on Request Header when you're making a request. You can use <code
|
||||
class="rounded p-0.5 px-1 text-sm text-gray-500 bg-gray-100 border-2 border-gray-200">Authorization</code>
|
||||
header with your API Key when you make a request.</p>
|
||||
<%- include('../partials/code', {title: "Example request with API Key" , requestUrl: "" , requestType: "" , code: "curl --header \"ApiKey: {secret-api-key}\" https://oneuptime.com/api/\<path\>" }) -%>
|
||||
|
||||
<div class="mt-6 rounded-xl border border-amber-200 bg-amber-50 p-4">
|
||||
<div class="flex gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-amber-500" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-sm text-amber-800">Please don't commit your OneUptime API Key to GitHub, or on any other source control project. Please regenerate a new API Key if your API Key is committed by mistake.</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm">Please don't commit your OneUptime API Key to GitHub, or on any other source control
|
||||
project. Please regenerate a new API Key, if your API Key is committed by mistake.</p>
|
||||
</article>
|
||||
</main>
|
||||
</main>
|
||||
@@ -1,179 +0,0 @@
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Type Hierarchy Breadcrumb -->
|
||||
<% if (pageData.typeHierarchy && pageData.typeHierarchy.length > 0) { %>
|
||||
<nav class="mb-6">
|
||||
<ol class="flex items-center flex-wrap gap-1 text-sm">
|
||||
<% for (var h = 0; h < pageData.typeHierarchy.length; h++) { %>
|
||||
<% if (h > 0) { %>
|
||||
<li class="text-slate-400">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
|
||||
</li>
|
||||
<% } %>
|
||||
<% if (pageData.typeHierarchy[h].path && pageData.typeHierarchy[h].name !== pageData.title) { %>
|
||||
<li><a href="/reference/<%= pageData.typeHierarchy[h].path %>" class="text-indigo-600 hover:text-indigo-700 hover:underline font-medium"><%= pageData.typeHierarchy[h].name %></a></li>
|
||||
<% } else { %>
|
||||
<li class="text-slate-900 font-semibold"><%= pageData.typeHierarchy[h].name %></li>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</ol>
|
||||
</nav>
|
||||
<% } %>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-emerald-600 shadow-lg shadow-emerald-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-emerald-600 uppercase tracking-wider"><%= pageData.isEnum ? 'Enum' : 'Data Type' %></span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3"><%= pageData.title %></h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl"><%- pageData.description %></p>
|
||||
</div>
|
||||
|
||||
<!-- At a Glance Summary Box -->
|
||||
<div class="mb-10 rounded-xl border border-slate-200 bg-slate-50 p-5">
|
||||
<h4 class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3">At a Glance</h4>
|
||||
<div class="flex flex-wrap gap-x-8 gap-y-3 text-sm">
|
||||
<div>
|
||||
<span class="text-slate-500">Kind</span>
|
||||
<span class="ml-2 font-medium text-slate-900"><%= pageData.isEnum ? 'Enum' : 'Data Type' %></span>
|
||||
</div>
|
||||
<% if (pageData.isEnum && pageData.valueCount > 0) { %>
|
||||
<div>
|
||||
<span class="text-slate-500">Values</span>
|
||||
<span class="ml-2 font-medium text-slate-900"><%= pageData.valueCount %></span>
|
||||
</div>
|
||||
<% } else if (pageData.propertyCount > 0) { %>
|
||||
<div>
|
||||
<span class="text-slate-500">Properties</span>
|
||||
<span class="ml-2 font-medium text-slate-900"><%= pageData.propertyCount %></span>
|
||||
</div>
|
||||
<% } %>
|
||||
<div>
|
||||
<span class="text-slate-500">JSON Format</span>
|
||||
<code class="ml-2 text-xs font-mono bg-white px-2 py-0.5 rounded border border-slate-200 text-slate-700">{"_type": "<%= pageData.jsonWrapperType %>", "value": ...}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (pageData.isEnum) { %>
|
||||
<!-- Quick Navigation for Enum Values -->
|
||||
<% if (pageData.values.length >= 4) { %>
|
||||
<div class="mb-6">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<% for (var qv = 0; qv < pageData.values.length; qv++) { %>
|
||||
<a href="#value-<%= pageData.values[qv].value.toLowerCase().replace(/\s+/g, '-') %>" class="rounded-md bg-white px-2.5 py-1 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-200 hover:bg-slate-50 hover:text-slate-900 transition-colors no-underline"><%= pageData.values[qv].value %></a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<h3 id="values" class="text-base font-semibold text-slate-800 mb-4 scroll-mt-24">Possible Values</h3>
|
||||
<div class="my-6 rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 w-full list-none divide-y divide-slate-100 p-0">
|
||||
<% for (var v = 0; v < pageData.values.length; v++) { %>
|
||||
<li id="value-<%= pageData.values[v].value.toLowerCase().replace(/\s+/g, '-') %>" class="m-0 px-5 py-5 hover:bg-slate-50/50 transition-colors scroll-mt-24">
|
||||
<dl class="m-0 flex flex-wrap items-start gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Index</dt>
|
||||
<dd class="text-xs font-mono text-slate-300 w-6 pt-0.5 flex-shrink-0"><%= v + 1 %>.</dd>
|
||||
<dt class="sr-only">Value</dt>
|
||||
<dd><code class="model-inline-code"><%= pageData.values[v].value %></code></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none pl-9 text-sm text-slate-600"><%- pageData.values[v].description %></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
<% } else if (pageData.properties && pageData.properties.length > 0) { %>
|
||||
<!-- Quick Navigation for Properties -->
|
||||
<% if (pageData.properties.length >= 4) { %>
|
||||
<div class="mb-6">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<% for (var qp = 0; qp < pageData.properties.length; qp++) { %>
|
||||
<a href="#prop-<%= pageData.properties[qp].name %>" class="rounded-md bg-white px-2.5 py-1 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-200 hover:bg-slate-50 hover:text-slate-900 transition-colors no-underline"><%= pageData.properties[qp].name %></a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<h3 id="properties" class="text-base font-semibold text-slate-800 mb-4 scroll-mt-24">Properties</h3>
|
||||
<div class="my-6 rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 w-full list-none divide-y divide-slate-100 p-0">
|
||||
<% for (var p = 0; p < pageData.properties.length; p++) { %>
|
||||
<li id="prop-<%= pageData.properties[p].name %>" class="m-0 px-5 py-5 hover:bg-slate-50/50 transition-colors scroll-mt-24">
|
||||
<div class="mb-1.5">
|
||||
<code class="text-sm font-semibold text-slate-900"><%= pageData.properties[p].name %></code>
|
||||
<% if (pageData.properties[p].required) { %>
|
||||
<span class="ml-2 inline-flex items-center rounded-full bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-700 ring-1 ring-inset ring-amber-600/20">Required</span>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="inline-flex items-center rounded-md bg-slate-100 px-2 py-0.5 text-xs font-mono text-slate-600">
|
||||
<% if (pageData.properties[p].typeLinks && pageData.properties[p].typeLinks.length > 0) { %>
|
||||
<% var typeLinks = pageData.properties[p].typeLinks; %>
|
||||
<% for (var tl = 0; tl < typeLinks.length; tl++) { %>
|
||||
<% if (typeLinks[tl].path) { %>
|
||||
<a href="/reference/<%= typeLinks[tl].path %>" class="text-indigo-600 hover:text-indigo-700 hover:underline font-medium"><%= typeLinks[tl].label %></a>
|
||||
<% } else { %>
|
||||
<%= typeLinks[tl].label %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
<%= pageData.properties[p].type %>
|
||||
<% } %>
|
||||
</span>
|
||||
</div>
|
||||
<p class="m-0 text-sm text-slate-600 leading-relaxed"><%- pageData.properties[p].description %></p>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- JSON Example Section -->
|
||||
<div class="border-t border-slate-200 pt-8 mt-10">
|
||||
<h3 id="example" class="text-base font-semibold text-slate-800 mb-4 scroll-mt-24">JSON Example</h3>
|
||||
<%- include('../partials/code', { title: "JSON", code: pageData.jsonExample, requestType: "", requestUrl: "" }) %>
|
||||
</div>
|
||||
|
||||
<% if (pageData.title === "Permission") { %>
|
||||
<div class="mt-6 rounded-lg border border-indigo-100 bg-indigo-50 p-4">
|
||||
<p class="text-sm text-indigo-800 m-0">
|
||||
For a complete list of all available permissions and their descriptions, see the
|
||||
<a href="/reference/permissions" class="font-medium text-indigo-600 hover:text-indigo-700 hover:underline">Permissions guide</a>.
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- Related Types Section -->
|
||||
<% if (pageData.relatedTypes && pageData.relatedTypes.length > 0) { %>
|
||||
<div class="border-t border-slate-200 pt-8 mt-10">
|
||||
<h3 id="related-types" class="text-base font-semibold text-slate-800 mb-4 scroll-mt-24">Related Types</h3>
|
||||
<div class="my-6 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<% for (var r = 0; r < pageData.relatedTypes.length; r++) { %>
|
||||
<a href="/reference/<%= pageData.relatedTypes[r].path %>" class="group flex items-start gap-3 rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-sm transition-all no-underline">
|
||||
<div class="flex-shrink-0 mt-0.5">
|
||||
<div class="flex items-center justify-center w-8 h-8 rounded-lg bg-indigo-50 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-4 h-4 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors"><%= pageData.relatedTypes[r].name %></div>
|
||||
<div class="text-xs text-slate-500 mt-0.5"><%= pageData.relatedTypes[r].relationship %></div>
|
||||
<% if (pageData.relatedTypes[r].description) { %>
|
||||
<div class="text-xs text-slate-400 mt-1 line-clamp-2"><%= pageData.relatedTypes[r].description %></div>
|
||||
<% } %>
|
||||
</div>
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
@@ -1,22 +1,14 @@
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Data Types</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">In this guide, we will look at how to work with OneUptime Data Types when querying the OneUptime API.</p>
|
||||
</div>
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<h1>Data Types</h1>
|
||||
<p class="lead">In this guide, we will look at how to work with OneUptime Data Types when querying the OneUptime
|
||||
API. </p>
|
||||
|
||||
<h2 id="select" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12">
|
||||
Select
|
||||
</h2>
|
||||
<a href="#select" class="cursor-default">
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Select
|
||||
</h2>
|
||||
</a>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Select can be used to select fields from an Object in List or Get Item API. By default only ID's
|
||||
@@ -24,13 +16,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Select</dt>
|
||||
<dd><code class="inline-code">select</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Select</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Select</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Dictionary of fieldName - boolean as JSON Object. Here's an example. If the
|
||||
@@ -54,9 +46,11 @@
|
||||
|
||||
|
||||
|
||||
<h2 id="sort" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 pt-8 border-t border-slate-200">
|
||||
Sort
|
||||
</h2>
|
||||
<a href="#sort" class="cursor-default">
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Sort
|
||||
</h2>
|
||||
</a>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Sort can be used to sort list by fields in List API. By default objects are sorted by their createdAt
|
||||
@@ -65,13 +59,13 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Sort</dt>
|
||||
<dd><code class="inline-code">sort</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Sort</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Sort</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Dictionary of fieldName - 'DESC' / 'ASC' as JSON Object. Here's an example. If
|
||||
@@ -93,9 +87,11 @@
|
||||
</div>
|
||||
|
||||
|
||||
<h2 id="queries" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 pt-8 border-t border-slate-200">
|
||||
Query
|
||||
</h2>
|
||||
<a href="#queries" class="cursor-default">
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Query
|
||||
</h2>
|
||||
</a>
|
||||
|
||||
|
||||
<h3 id="example-using-cursors" class="scroll-mt-24">
|
||||
@@ -110,16 +106,16 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of an Equal To Query</p>
|
||||
<p>Here is an example of Equal To Query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -148,16 +144,16 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a Not Equal To Query</p>
|
||||
<p>Here is an example of Not Equal To Query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -186,16 +182,16 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of an is null query</p>
|
||||
<p>Here is an example of is null query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -224,16 +220,16 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of an is not null query</p>
|
||||
<p>Here is an example of is not null query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -261,16 +257,16 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a greater than query</p>
|
||||
<p>Here is an example of greater than query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -299,16 +295,16 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a greater than or equal query</p>
|
||||
<p>Here is an example of greater or equal than query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -337,16 +333,16 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a less than query</p>
|
||||
<p>Here is an example of less than query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -375,16 +371,16 @@
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a less than or equal query</p>
|
||||
<p>Here is an example of less or equal than query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -399,44 +395,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="example-using-cursors" class="scroll-mt-24">
|
||||
Inlcudes
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>
|
||||
Includes will get objects that match any of the values in the array. It is used to
|
||||
filter objects that have a field that matches any of the values in the array. For example, if you
|
||||
want to get all objects that have a label with ID `aaa00000-aaaa-aaaa-aaaa-aaaaaaaaaaaa` or
|
||||
`bbb00000-bbbb-bbbb-bbbb-bbbbbbbbbbbb`, you can use the `includes` query type.
|
||||
</p>
|
||||
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-slate-100 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Query</dt>
|
||||
<dd><code class="inline-code">query</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-slate-500 ">Query</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Here is an example of a less than or equal query</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<%- include('../partials/code', {title: "Example Not EqualTo Request Body" , requestUrl: "" , requestType: "" ,
|
||||
code: pageData.includesCode }) -%>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
@@ -1,77 +1,90 @@
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Errors</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">In this guide, we will talk about what happens when something goes wrong while you work with the API. Mistakes happen, and mostly they will be yours, not ours. Let's look at some status codes and error types you might encounter.</p>
|
||||
</div>
|
||||
|
||||
<p class="text-slate-600 leading-relaxed">You can tell if your request was successful by checking the status code when receiving an API response. If a response comes back unsuccessful, you can use the status code and error message to figure out what has gone wrong and do some rudimentary debugging (before contacting support).</p>
|
||||
|
||||
<div class="my-6 flex gap-3 rounded-xl border border-indigo-500/20 bg-indigo-50/50 p-4">
|
||||
<div class="flex-shrink-0">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" class="h-5 w-5 fill-indigo-500 stroke-white">
|
||||
<circle cx="8" cy="8" r="8" stroke-width="0"></circle>
|
||||
<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.75 7.75h1.5v3.5"></path>
|
||||
<circle cx="8" cy="4" r=".5" fill="none"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-sm text-indigo-900">Before reaching out to support with an error, please be aware that 99% of all reported errors are, in fact, user errors. Therefore, please carefully check your code before contacting OneUptime support.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="status-codes" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 pt-8 border-t border-slate-200">
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<h1 class="font-bold text-xl mb-5">Errors</h1>
|
||||
<p class="lead">In this guide, we will talk about what happens when something goes wrong while you work with the API. Mistakes happen, and mostly they will be yours, not ours. Let's look at some status codes and error types you might encounter.</p>
|
||||
<p>You can tell if your request was successful by checking the status code when receiving an API response. If a response comes back unsuccessful, you can use the status code and error message to figure out what has gone wrong and do some rudimentary debugging (before contacting support).</p>
|
||||
<div class="my-6 flex gap-2.5 rounded-2xl border border-emerald-500/20 bg-emerald-50/50 p-4 leading-6 text-emerald-900 ">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" class="mt-1 h-4 w-4 flex-none fill-emerald-500 stroke-white ">
|
||||
<circle cx="8" cy="8" r="8" stroke-width="0"></circle>
|
||||
<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.75 7.75h1.5v3.5"></path>
|
||||
<circle cx="8" cy="4" r=".5" fill="none"></circle>
|
||||
</svg>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Before reaching out to support with an error, please be aware that 99% of all
|
||||
reported errors are, in fact, user errors. Therefore, please carefully check
|
||||
your code before contacting OneUptime support.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="status-codes" class="mb-5 scroll-mt-24 mt-24 font-bold text-lg">
|
||||
Status codes
|
||||
</h2>
|
||||
<p class="text-slate-600 leading-relaxed mb-6">Here is a list of the different categories of status codes returned by the OneUptime API. Use these to understand if a request was successful.</p>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 list-none divide-y divide-slate-100 p-0">
|
||||
<li class="m-0 px-5 py-4">
|
||||
</h2>
|
||||
<p>Here is a list of the different categories of status codes returned by the OneUptime API. Use these to understand if a request was successful.</p>
|
||||
<div class="my-6">
|
||||
<ul role="list" class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">2xx</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>A 2xx status code indicates a successful response.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<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">Name</dt>
|
||||
<dd><code class="inline-code">4xx</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>A 4xx status code indicates a client error — this means it's a <em>you</em>
|
||||
problem.
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<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">
|
||||
<dd><code class="inline-flex items-center rounded-md bg-emerald-50 px-2 py-1 text-xs font-medium text-emerald-700 ring-1 ring-inset ring-emerald-600/20">2xx</code></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1">A 2xx status code indicates a successful response.</dd>
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">429</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p> Request limit exceeded. Request limits are 100 operations per second per project (this includes all the API keys in the project).
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-5 py-4">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-flex items-center rounded-md bg-amber-50 px-2 py-1 text-xs font-medium text-amber-700 ring-1 ring-inset ring-amber-600/20">4xx</code></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1">A 4xx status code indicates a client error - this means it's a <em>you</em> problem.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-5 py-4">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-flex items-center rounded-md bg-orange-50 px-2 py-1 text-xs font-medium text-orange-700 ring-1 ring-inset ring-orange-600/20">429</code></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1">Request limit exceeded. Request limits are 100 operations per second per project (this includes all the API keys in the project).</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-5 py-4">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/20">5xx</code></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1">A 5xx status code indicates a server error - you won't be seeing a lot of these.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="error-types" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 pt-8 border-t border-slate-200">
|
||||
Error Messages
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p class="text-slate-600 leading-relaxed">Whenever a request is unsuccessful, the OneUptime API will return an error response with an error message. You can use this information to understand better what has gone wrong and how to fix it. Most of the error messages are pretty helpful and actionable.</p>
|
||||
<p class="text-slate-600 leading-relaxed">Here is an example of an error message:</p>
|
||||
</div>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0 xl:sticky xl:top-24">
|
||||
<%- include('../partials/code', {title: "Example error response", requestUrl: "", requestType: "", code: "{ \"message\": \"Name is required\" }" }) -%>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
<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">Name</dt>
|
||||
<dd><code class="inline-code">5xx</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>A 5xx status code indicates a server error — you won't be seeing a lot of these.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="error-types" class="mb-5 scroll-mt-24 mt-24 font-bold text-lg">Error Messages</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Whenever a request is unsuccessful, the OneUptime API will return an error response with an error message. You can use this information to understand better what has gone wrong and how to fix it. Most of the error messages are pretty helpful and actionable.</p>
|
||||
<p>Here is an example of an error message: </p>
|
||||
|
||||
</div>
|
||||
<%- include('../partials/code', {title: "Example error response", requestUrl: "", requestType: "", code: "{ \"message\": \"Name is required\" }" }) -%>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
@@ -1,87 +1,198 @@
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="relative mb-12">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<div class="absolute inset-0 -z-10 mx-0 max-w-none overflow-hidden">
|
||||
<div
|
||||
class="absolute left-1/2 top-0 ml-[-38rem] h-[25rem] w-[81.25rem]">
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-r from-[#36b49f] to-[#DBFF75] opacity-40 [mask-image:radial-gradient(farthest-side_at_top,white,transparent)]">
|
||||
<svg aria-hidden="true"
|
||||
class="absolute inset-x-0 inset-y-[-50%] h-[200%] w-full skew-y-[-18deg] fill-black/40 stroke-black/50 mix-blend-overlay /5">
|
||||
<defs>
|
||||
<pattern id=":r6s:" width="72" height="56" patternUnits="userSpaceOnUse" x="-12"
|
||||
y="4">
|
||||
<path d="M.5 56V.5H72" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" stroke-width="0" fill="url(#:r6s:)"></rect>
|
||||
<svg x="-12" y="4" class="overflow-visible">
|
||||
<rect stroke-width="0" width="73" height="57" x="288" y="168"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="144" y="56"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="504" y="168"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="720" y="336"></rect>
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">REST API</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-4">API Reference</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">
|
||||
Use the OneUptime API to access any resource in your projects, create automated workflows,
|
||||
and seamlessly integrate with the tools and services your organization uses.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Guides Section -->
|
||||
<div class="my-12 xl:max-w-none">
|
||||
<h2 id="guides" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-6">
|
||||
Getting Started
|
||||
</h2>
|
||||
<div class="not-prose grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<a href="/reference/authentication" class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-50 text-indigo-600 mb-4 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors">Authentication</h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed">Learn how to authenticate your API requests.</p>
|
||||
</a>
|
||||
<a href="/reference/pagination" class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-50 text-indigo-600 mb-4 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors">Pagination</h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed">Understand how to work with paginated responses.</p>
|
||||
</a>
|
||||
<a href="/reference/errors" class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-50 text-indigo-600 mb-4 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors">Errors</h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed">Read about the different types of errors returned.</p>
|
||||
</a>
|
||||
<a href="/reference/permissions" class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-indigo-50 text-indigo-600 mb-4 group-hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors">Permissions</h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed">Learn how API Key Permissions work.</p>
|
||||
</a>
|
||||
<svg viewBox="0 0 1113 440" aria-hidden="true"
|
||||
class="absolute top-0 left-1/2 ml-[-19rem] w-[69.5625rem] fill-white blur-[26px] ">
|
||||
<path d="M.016 439.5s-9.5-300 434-300S882.516 20 882.516 20V0h230.004v439.5H.016Z">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Featured Resources Section -->
|
||||
<div class="my-12 xl:max-w-none">
|
||||
<h2 id="resources" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-6">
|
||||
Featured Resources
|
||||
<h1 class="font-bold text-xl">API Reference</h1>
|
||||
<p class="lead">Use the OneUptime API to access any resource in your projects, create automated
|
||||
workflows, and more
|
||||
and
|
||||
seamlessly integrate your project into the other tools and services you use in your
|
||||
organization.</p>
|
||||
|
||||
|
||||
<div class="my-16 xl:max-w-none">
|
||||
<h2 id="guides" class="scroll-mt-24 font-bold">
|
||||
<span class="group text-inherit no-underline hover:text-inherit">
|
||||
<div
|
||||
class="absolute mt-1 ml-[calc(-1*var(--width))] hidden w-[var(--width)] opacity-0 transition [--width:calc(2.625rem+0.5px+50%-min(50%,calc(theme(maxWidth.lg)+theme(spacing.8))))] group-hover:opacity-100 group-focus:opacity-100 md:block lg:z-50 2xl:[--width:theme(spacing.10)]">
|
||||
<div
|
||||
class="group/anchor block h-5 w-5 rounded-lg bg-zinc-50 ring-1 ring-inset ring-zinc-300 transition hover:ring-zinc-500 ">
|
||||
<svg viewBox="0 0 20 20" fill="none" stroke-linecap="round" aria-hidden="true"
|
||||
class="h-5 w-5 stroke-zinc-500 transition ">
|
||||
<path
|
||||
d="m6.5 11.5-.964-.964a3.535 3.535 0 1 1 5-5l.964.964m2 2 .964.964a3.536 3.536 0 0 1-5 5L8.5 13.5m0-5 3 3">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
Guides
|
||||
</span>
|
||||
</h2>
|
||||
<div class="not-prose grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<div
|
||||
class="not-prose mt-4 grid grid-cols-1 gap-8 border-t border-zinc-900/5 pt-10 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-zinc-900 ">Authentication</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 ">Learn how to authenticate your
|
||||
API
|
||||
requests.</p>
|
||||
<p class="mt-4">
|
||||
<a class="inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/authentication">
|
||||
Read more
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true"
|
||||
class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-zinc-900 ">Pagination</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 ">Understand how to work with
|
||||
paginated responses.</p>
|
||||
<p class="mt-4">
|
||||
<a class="inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/pagination">
|
||||
Read more
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true"
|
||||
class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-zinc-900 ">Errors</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 ">Read about the different types
|
||||
of
|
||||
errors returned by the API.</p>
|
||||
<p class="mt-4">
|
||||
<a class="inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/errors">
|
||||
Read more
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true"
|
||||
class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-zinc-900 ">Permissions</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 ">Learn how API Key Permissions
|
||||
work.</p>
|
||||
<p class="mt-4">
|
||||
<a class="inline-flex gap-0.5 justify-center overflow-hidden font-medium transition text-emerald-500 hover:text-emerald-600 "
|
||||
href="/reference/permissions">
|
||||
Read more
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true"
|
||||
class="mt-0.5 h-5 w-5 relative top-px -mr-1">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-16 xl:max-w-none">
|
||||
<h2 id="resources" class="scroll-mt-24 font-bold"><span
|
||||
class="group text-inherit no-underline hover:text-inherit">Featured Resources</span>
|
||||
</h2>
|
||||
<div
|
||||
class="not-prose mt-4 grid grid-cols-1 gap-8 border-t border-zinc-900/5 pt-10 sm:grid-cols-2 xl:grid-cols-4">
|
||||
|
||||
<% for(var i=0; i<pageData.featuredResources.length; i++) {%>
|
||||
<a href="/reference/<%= pageData.featuredResources[i].path -%>"
|
||||
class="group relative rounded-xl border border-slate-200 bg-white p-5 hover:border-indigo-300 hover:shadow-md hover:shadow-indigo-100/50 transition-all">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-slate-100 text-slate-600 mb-4 group-hover:bg-indigo-100 group-hover:text-indigo-600 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path>
|
||||
</svg>
|
||||
<div
|
||||
class="cursor-pointer group relative flex rounded-2xl bg-zinc-50 transition-shadow hover:shadow-md hover:shadow-zinc-900/5 ">
|
||||
<div class="pointer-events-none">
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl transition duration-300 [mask-image:linear-gradient(white,transparent)] group-hover:opacity-50">
|
||||
<svg aria-hidden="true"
|
||||
class="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/[0.02] stroke-black/5 /2.5">
|
||||
<defs>
|
||||
<pattern id=":r6t:" width="72" height="56" patternUnits="userSpaceOnUse"
|
||||
x="50%" y="16">
|
||||
<path d="M.5 56V.5H72" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" stroke-width="0" fill="url(#:r6t:)"></rect>
|
||||
<svg x="50%" y="16" class="overflow-visible">
|
||||
<rect stroke-width="0" width="73" height="57" x="0" y="56"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="72" y="168"></rect>
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[#D7EDEA] to-[#F4FBDF] opacity-0 transition duration-300 group-hover:opacity-100"
|
||||
data-projection-id="35"
|
||||
style="-webkit-mask-image: radial-gradient(180px at 0px 0px, white, transparent);">
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl opacity-0 mix-blend-overlay transition duration-300 group-hover:opacity-100"
|
||||
data-projection-id="36"
|
||||
style="-webkit-mask-image: radial-gradient(180px at 0px 0px, white, transparent);">
|
||||
<svg aria-hidden="true"
|
||||
class="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/50 stroke-black/70 /10">
|
||||
<defs>
|
||||
<pattern id=":r6u:" width="72" height="56" patternUnits="userSpaceOnUse"
|
||||
x="50%" y="16">
|
||||
<path d="M.5 56V.5H72" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" stroke-width="0" fill="url(#:r6u:)"></rect>
|
||||
<svg x="50%" y="16" class="overflow-visible">
|
||||
<rect stroke-width="0" width="73" height="57" x="0" y="56"></rect>
|
||||
<rect stroke-width="0" width="73" height="57" x="72" y="168"></rect>
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 group-hover:text-indigo-600 transition-colors"><%= pageData.featuredResources[i].name -%></h3>
|
||||
<p class="mt-1.5 text-sm text-slate-500 leading-relaxed"><%= pageData.featuredResources[i].description -%></p>
|
||||
</a>
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl ring-1 ring-inset ring-zinc-900/7.5 group-hover:ring-zinc-900/10 ">
|
||||
</div>
|
||||
<div class="relative rounded-2xl px-4 pt-16 pb-4">
|
||||
|
||||
<h3 class="mt-4 text-sm font-semibold leading-7 text-zinc-900 "><a
|
||||
href="/reference/<%= pageData.featuredResources[i].path -%>"><span
|
||||
class="absolute inset-0 rounded-2xl"></span> <%= pageData.featuredResources[i].name -%></a>
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-zinc-600 "><%= pageData.featuredResources[i].description -%></p>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
</main>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,84 +1,114 @@
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Pagination</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">In this guide, we will look at how to work with paginated responses when querying the OneUptime API. By default, all responses limit results to ten.</p>
|
||||
</div>
|
||||
|
||||
<p class="text-slate-600 leading-relaxed">When an API response returns a list of objects, no matter the amount, pagination is supported. In paginated responses, objects are nested in a <code class="inline-code">data</code> attribute. The API response also has <code class="inline-code">count</code> attribute that indicates total count in the list with that query. You can use the <code class="inline-code">limit</code> and <code class="inline-code">skip</code> query parameters to query pages.</p>
|
||||
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-6 mt-12">
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<h1>Pagination</h1>
|
||||
<p class="lead">In this guide, we will look at how to work with paginated responses when querying the OneUptime
|
||||
API. By default, all responses limit results to ten. </p>
|
||||
<p>When an API response returns a list of objects, no matter the amount, pagination is supported. In paginated
|
||||
responses, objects are nested in a <code class="inline-code">data</code> attribute. The API response also has
|
||||
<code class="inline-code">count</code> attribute that indicates total count in the list with that query. You
|
||||
can use the <code class="inline-code">limit</code> and <code class="inline-code">skip</code> query parameters
|
||||
to query pages.</p>
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Pagination Example
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p class="text-slate-600 leading-relaxed">In this example, we request the list of monitors. As a result, we get a list of three monitors and can tell by the <code class="inline-code">count</code> attribute that we have reached the end of the result set</p>
|
||||
|
||||
<h3 class="text-base font-semibold text-slate-800 mt-8 mb-3">Query Parameters</h3>
|
||||
<div class="rounded-lg border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 list-none divide-y divide-slate-100 p-0">
|
||||
<li class="m-0 px-4 py-3">
|
||||
<p>In this example, we request the list fo monitors. As a result, we get a list of three monitors and can
|
||||
tell by the <code class="inline-code">count</code> attribute that we have reached the end of the
|
||||
result set</p>
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Query Parameters
|
||||
</h2>
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">limit</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Number of items you need to fetch. More items will lead to slower responses. Max limit is 100.</dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Number of items you need to fetch. More items will lead to slower responses. Max limit is
|
||||
100.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-4 py-3">
|
||||
<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">Name</dt>
|
||||
<dd><code class="inline-code">skip</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Number of items to skip. This can be useful when you are paginating items.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3 class="text-base font-semibold text-slate-800 mt-8 mb-3">Response Body</h3>
|
||||
<div class="rounded-lg border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 list-none divide-y divide-slate-100 p-0">
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code">data</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">JSON Array</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">List of items fetched.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code">count</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Total number of items in the database</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code">limit</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Number of items you need to fetch. More items will lead to slower responses. Max limit is 100.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="m-0 px-4 py-3">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code">skip</code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500">Number</dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600">Number of items to skip. This can be useful when you are paginating items.</dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Number of items to skip. This can be useful when you are paginating items.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h2 id="example-using-cursors" class="scroll-mt-24">
|
||||
Response Body
|
||||
</h2>
|
||||
<div class="my-6">
|
||||
<ul role="list"
|
||||
class="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 ">
|
||||
<li class="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt class="sr-only">Name</dt>
|
||||
<dd><code class="inline-code">data</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">JSON Array</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>List of items fetched.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<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">Name</dt>
|
||||
<dd><code class="inline-code">count</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Total number of items in the database</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<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">Name</dt>
|
||||
<dd><code class="inline-code">limit</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Number of items you need to fetch. More items will lead to slower responses. Max limit is
|
||||
100.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<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">Name</dt>
|
||||
<dd><code class="inline-code">skip</code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 ">Number</dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p>Number of items to skip. This can be useful when you are paginating items.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0 xl:sticky xl:top-24">
|
||||
<%- include('../partials/code', {title: "Example Pagination Request", requestUrl: "/api/monitors/get-list?skip=0&limit=3", requestType: "POST", code: pageData.requestCode }) -%>
|
||||
</div>
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<%- include('../partials/code', {title: "Example Pagination Request", requestUrl: "/api/monitors/get-list?skip=0&limit=3", requestType: "GET", code: pageData.requestCode }) -%>
|
||||
<%- include('../partials/code', {title: "Example Pagination Response" , requestUrl: "", requestType: "", code: pageData.responseCode }) -%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,62 +1,38 @@
|
||||
<main class="py-12">
|
||||
<article class="prose">
|
||||
<!-- Hero Section -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-xl bg-indigo-600 shadow-lg shadow-indigo-500/30">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-indigo-600 uppercase tracking-wider">Guide</span>
|
||||
</div>
|
||||
<h1 class="font-bold text-3xl text-slate-900 tracking-tight mb-3">Permissions</h1>
|
||||
<p class="text-lg text-slate-600 leading-relaxed max-w-2xl">Your API Token needs permissions to create, update, read or delete any resource. If you do not have permissions to make a request a <code class="inline-code">4xx</code> status will be sent as response. You can manage permissions for your API Key in Project Settings > API Keys.</p>
|
||||
</div>
|
||||
<main class="py-16">
|
||||
<article class="prose ">
|
||||
<h1>Permissions</h1>
|
||||
<p class="lead"> Your API Token needs permissions to create, update, read or delete any resource. If you do not have permissions to make a request a <code class="inline-code">4xx</code> status will be sent as response. You can manage permissions for your API Key in Project Settings > API Keys. </p>
|
||||
|
||||
|
||||
<h2 id="consuming-webhooks" >
|
||||
Permissions List
|
||||
</h2>
|
||||
<p>Here is a list of all the permissions:</p>
|
||||
<div class="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
<div class="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<div class="my-6">
|
||||
<ul role="list" class="m-0 w-full">
|
||||
<% for(var i=0; i<pageData.permissions.length; i++) {%>
|
||||
<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">Name</dt>
|
||||
<dd><code class="inline-code"> <%= pageData.permissions[i].permission -%></code></dd>
|
||||
<dt class="sr-only">Type</dt>
|
||||
<dd class="font-mono text-xs text-zinc-400 "><%= pageData.permissions[i].title -%></dd>
|
||||
<dt class="sr-only">Description</dt>
|
||||
<dd class="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
<p><%= pageData.permissions[i].description -%></p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<% } %>
|
||||
|
||||
<!-- Quick Navigation -->
|
||||
<div class="my-6 flex gap-3 rounded-xl border border-indigo-500/20 bg-indigo-50/50 p-4">
|
||||
<div class="flex-shrink-0">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" class="h-5 w-5 fill-indigo-500 stroke-white">
|
||||
<circle cx="8" cy="8" r="8" stroke-width="0"></circle>
|
||||
<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.75 7.75h1.5v3.5"></path>
|
||||
<circle cx="8" cy="4" r=".5" fill="none"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-indigo-900 mb-2">Jump to a category</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<% for(var g=0; g<pageData.permissionGroups.length; g++) { %>
|
||||
<a href="#<%= pageData.permissionGroups[g].group.toLowerCase().replace(/ /g, '-') -%>"
|
||||
class="inline-flex items-center gap-1.5 rounded-md bg-white px-2.5 py-1 text-xs font-medium text-slate-700 ring-1 ring-inset ring-slate-200 hover:bg-indigo-50 hover:text-indigo-700 hover:ring-indigo-200 transition-colors">
|
||||
<%= pageData.permissionGroups[g].group %>
|
||||
<span class="text-slate-400">(<%= pageData.permissionGroups[g].permissions.length %>)</span>
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% for(var g=0; g<pageData.permissionGroups.length; g++) { %>
|
||||
<h2 id="<%= pageData.permissionGroups[g].group.toLowerCase().replace(/ /g, '-') -%>"
|
||||
class="scroll-mt-24 text-xl font-semibold text-slate-900 mb-4 mt-12 <%= g > 0 ? 'pt-8 border-t border-slate-200' : '' %>">
|
||||
<%= pageData.permissionGroups[g].group %>
|
||||
<span class="ml-2 align-middle inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-medium text-slate-600"><%= pageData.permissionGroups[g].permissions.length %></span>
|
||||
</h2>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<ul role="list" class="m-0 list-none divide-y divide-slate-100 p-0">
|
||||
<% for(var i=0; i<pageData.permissionGroups[g].permissions.length; i++) { %>
|
||||
<li class="m-0 px-5 py-4 hover:bg-slate-50/50 transition-colors">
|
||||
<dl class="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dd><code class="inline-code"><%= pageData.permissionGroups[g].permissions[i].permission -%></code></dd>
|
||||
<dd class="font-mono text-xs text-slate-500"><%= pageData.permissionGroups[g].permissions[i].title -%></dd>
|
||||
<dd class="w-full flex-none text-sm text-slate-600 mt-1"><%= pageData.permissionGroups[g].permissions[i].description -%></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
<% } %>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
@@ -1,14 +1,16 @@
|
||||
<html lang="en" class="h-full antialiased scroll-smooth" style="color-scheme: light;">
|
||||
<html lang="en" class="js-focus-visible ctshmsrlsm idc0_345">
|
||||
<%- include('../partials/head', {
|
||||
enableGoogleTagManager: typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false,
|
||||
}) -%>
|
||||
|
||||
<body onload="applyStyles()" class="flex min-h-full bg-white">
|
||||
<div id="__next" class="w-full">
|
||||
<body onload="applyStyles()" class="bg-white antialiased " data-new-gr-c-s-check-loaded="14.1095.0"
|
||||
data-gr-ext-installed="">
|
||||
<div id="__next">
|
||||
<div class="lg:ml-72 xl:ml-80">
|
||||
<%- include('../partials/nav') -%>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative px-4 pt-14 sm:px-6 lg:px-8 max-w-5xl w-full">
|
||||
<div class="relative px-4 pt-14 sm:px-6 lg:px-8 max-w-5xl ">
|
||||
<div>
|
||||
<div class="flex justify-center">
|
||||
<%- include('../main/'+page) -%>
|
||||
</div>
|
||||
@@ -16,6 +18,7 @@
|
||||
<%- include('../partials/footer') -%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
<%
|
||||
const uniqueId = 'code-tabs-' + Math.random().toString(36).substr(2, 9);
|
||||
const tabs = [
|
||||
{ id: 'preview', name: 'Request', icon: 'preview', isPreview: true },
|
||||
{ id: 'curl', name: 'cURL', icon: 'terminal' },
|
||||
{ id: 'javascript', name: 'JavaScript', icon: 'js' },
|
||||
{ id: 'typescript', name: 'TypeScript', icon: 'ts' },
|
||||
{ id: 'python', name: 'Python', icon: 'python' },
|
||||
{ id: 'go', name: 'Go', icon: 'go' },
|
||||
{ id: 'java', name: 'Java', icon: 'java' },
|
||||
{ id: 'csharp', name: 'C#', icon: 'csharp' },
|
||||
{ id: 'php', name: 'PHP', icon: 'php' },
|
||||
{ id: 'ruby', name: 'Ruby', icon: 'ruby' },
|
||||
{ id: 'rust', name: 'Rust', icon: 'rust' },
|
||||
{ id: 'powershell', name: 'PowerShell', icon: 'powershell' }
|
||||
];
|
||||
%>
|
||||
|
||||
<div class="code-tabs-container my-6 w-full max-w-full overflow-hidden" id="<%= uniqueId %>">
|
||||
<!-- Header with title and endpoint -->
|
||||
<div class="rounded-t-xl bg-slate-900 border border-slate-700/50 border-b-0">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 px-4 py-3 border-b border-slate-700/50">
|
||||
<h4 class="text-sm font-semibold text-slate-200 tracking-wide"><%= title %></h4>
|
||||
<div class="flex items-center gap-2">
|
||||
<% if(requestType === "GET"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-emerald-500/15 px-2.5 py-1 text-xs font-bold text-emerald-400 ring-1 ring-inset ring-emerald-500/30 uppercase tracking-wide">GET</span>
|
||||
<% } else if(requestType === "POST"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-500/15 px-2.5 py-1 text-xs font-bold text-indigo-400 ring-1 ring-inset ring-indigo-500/30 uppercase tracking-wide">POST</span>
|
||||
<% } else if(requestType === "DELETE"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-red-500/15 px-2.5 py-1 text-xs font-bold text-red-400 ring-1 ring-inset ring-red-500/30 uppercase tracking-wide">DELETE</span>
|
||||
<% } else if(requestType === "PUT"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-amber-500/15 px-2.5 py-1 text-xs font-bold text-amber-400 ring-1 ring-inset ring-amber-500/30 uppercase tracking-wide">PUT</span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if(requestUrl){ %>
|
||||
<div class="px-4 py-2.5 bg-slate-800/40 border-b border-slate-700/30">
|
||||
<code class="font-mono text-sm text-slate-300 break-all"><%= requestUrl %></code>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- Language Tabs -->
|
||||
<div class="relative">
|
||||
<div class="flex overflow-x-auto scrollbar-hide border-b border-slate-700/50" role="tablist">
|
||||
<% tabs.forEach((tab, index) => { %>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected="<%= index === 0 ? 'true' : 'false' %>"
|
||||
aria-controls="<%= uniqueId %>-panel-<%= tab.id %>"
|
||||
id="<%= uniqueId %>-tab-<%= tab.id %>"
|
||||
data-tab-id="<%= tab.id %>"
|
||||
onclick="switchCodeTab('<%= uniqueId %>', '<%= tab.id %>')"
|
||||
class="code-tab relative flex items-center gap-2 px-4 py-3 text-sm font-medium whitespace-nowrap transition-all duration-200 <%= index === 0 ? 'text-indigo-400 bg-slate-800/50' : 'text-slate-400 hover:text-slate-300 hover:bg-slate-800/30' %>"
|
||||
>
|
||||
<span class="lang-icon w-4 h-4 flex items-center justify-center">
|
||||
<% if(tab.icon === 'preview'){ %>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg>
|
||||
<% } else if(tab.icon === 'terminal'){ %>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
||||
<% } else if(tab.icon === 'js'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.405-.585-.585-.765-.63-.63-1.47-.93-2.835-.885l-.705.09c-.676.165-1.32.525-1.71 1.005-1.14 1.29-.81 3.54.57 4.47 1.365 1.035 3.369 1.26 3.629 2.235.225 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.63-1.755-1.455l-1.83 1.05c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.665 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z"/></svg>
|
||||
<% } else if(tab.icon === 'ts'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>
|
||||
<% } else if(tab.icon === 'python'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/></svg>
|
||||
<% } else if(tab.icon === 'go'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M1.811 10.231c-.047 0-.058-.023-.035-.059l.246-.315c.023-.035.081-.058.128-.058h4.172c.046 0 .058.035.035.07l-.199.303c-.023.036-.082.07-.117.07zM.047 11.306c-.047 0-.059-.023-.035-.058l.245-.316c.023-.035.082-.058.129-.058h5.328c.047 0 .07.035.058.07l-.093.28c-.012.047-.058.07-.105.07zm2.828 1.075c-.047 0-.059-.035-.035-.07l.163-.292c.023-.035.07-.07.117-.07h2.337c.047 0 .07.035.07.082l-.023.28c0 .047-.047.082-.082.082zm12.129-2.36c-.736.187-1.239.327-1.963.514c-.176.046-.187.058-.34-.117c-.174-.199-.303-.327-.548-.444c-.737-.362-1.45-.257-2.115.175c-.795.514-1.204 1.274-1.192 2.22c.011.935.654 1.706 1.577 1.835c.795.105 1.46-.175 1.987-.77c.105-.13.198-.27.315-.434H10.47c-.245 0-.304-.152-.222-.35c.152-.362.432-.97.596-1.274a.32.32 0 0 1 .292-.187h4.253c-.023.316-.023.631-.07.947a5 5 0 0 1-.958 2.29c-.841 1.11-1.94 1.8-3.33 1.986c-1.145.152-2.209-.07-3.143-.77c-.865-.655-1.356-1.52-1.484-2.595c-.152-1.274.222-2.419.993-3.424c.83-1.086 1.928-1.776 3.272-2.02c1.098-.2 2.15-.07 3.096.571c.62.41 1.063.97 1.356 1.648c.07.105.023.164-.117.2m3.868 6.461c-1.064-.024-2.034-.328-2.852-1.029a3.67 3.67 0 0 1-1.262-2.255c-.21-1.32.152-2.489.947-3.529c.853-1.122 1.881-1.706 3.272-1.95c1.192-.21 2.314-.095 3.33.595c.923.63 1.496 1.484 1.648 2.605c.198 1.578-.257 2.863-1.344 3.962c-.771.783-1.718 1.273-2.805 1.495c-.315.06-.63.07-.934.106m2.78-4.72c-.011-.153-.011-.27-.034-.387c-.21-1.157-1.274-1.81-2.384-1.554c-1.087.245-1.788.935-2.045 2.033c-.21.912.234 1.835 1.075 2.21c.643.28 1.285.244 1.905-.07c.923-.48 1.425-1.228 1.484-2.233z"/></svg>
|
||||
<% } else if(tab.icon === 'java'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M8.851 18.56s-.917.534.653.714c1.902.218 2.874.187 4.969-.211 0 0 .552.346 1.321.646-4.699 2.013-10.633-.118-6.943-1.149M8.276 15.933s-1.028.761.542.924c2.032.209 3.636.227 6.413-.308 0 0 .384.389.987.602-5.679 1.661-12.007.13-7.942-1.218M13.116 11.475c1.158 1.333-.304 2.533-.304 2.533s2.939-1.518 1.589-3.418c-1.261-1.772-2.228-2.652 3.007-5.688 0-.001-8.216 2.051-4.292 6.573M19.33 20.504s.679.559-.747.991c-2.712.822-11.288 1.069-13.669.033-.856-.373.75-.89 1.254-.998.527-.114.828-.093.828-.093-.953-.671-6.156 1.317-2.643 1.887 9.58 1.553 17.462-.7 14.977-1.82M9.292 13.21s-4.362 1.036-1.544 1.412c1.189.159 3.561.123 5.77-.062 1.806-.152 3.618-.477 3.618-.477s-.637.272-1.098.587c-4.429 1.165-12.986.623-10.522-.568 2.082-1.006 3.776-.892 3.776-.892M17.116 17.584c4.503-2.34 2.421-4.589.968-4.285-.355.074-.515.138-.515.138s.132-.207.385-.297c2.875-1.011 5.086 2.981-.928 4.562 0-.001.07-.062.09-.118M14.401 0s2.494 2.494-2.365 6.33c-3.896 3.077-.888 4.832-.001 6.836-2.274-2.053-3.943-3.858-2.824-5.539 1.644-2.469 6.197-3.665 5.19-7.627M9.734 23.924c4.322.277 10.959-.153 11.116-2.198 0 0-.302.775-3.572 1.391-3.688.694-8.239.613-10.937.168 0-.001.553.457 3.393.639"/></svg>
|
||||
<% } else if(tab.icon === 'csharp'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M11.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C2.56 15.77 2 14.16 2 12.21c.05-2.31.72-4.08 2-5.32C5.32 5.64 6.96 5 8.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32zm5.5-5.48h3.5v1.33H17v3.45l2 .1v1.27l-3.5-.1v-4.72H14v-1.33h3zm-1 0v1.33h-2v-1.33h2zm0 2.66v1.34h-2v-1.34h2zm0 2.67V17h-2v-1.18h2z"/></svg>
|
||||
<% } else if(tab.icon === 'php'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M7.01 10.207h-.944l-.515 2.648h.838c.556 0 .97-.105 1.242-.314.272-.21.455-.559.55-1.049.092-.47.05-.802-.124-.995-.175-.193-.523-.29-1.047-.29zM12 5.688C5.373 5.688 0 8.514 0 12s5.373 6.313 12 6.313S24 15.486 24 12c0-3.486-5.373-6.312-12-6.312zm-3.26 7.451c-.261.25-.575.438-.917.551-.336.108-.765.164-1.285.164H5.357l-.327 1.681H3.652l1.23-6.326h2.65c.797 0 1.378.209 1.744.628.366.418.476 1.002.33 1.752a2.836 2.836 0 01-.305.847c-.143.255-.33.49-.561.703zm4.024.715l.543-2.799c.063-.318.039-.536-.068-.651-.107-.116-.336-.174-.687-.174H11.46l-.704 3.625H9.388l1.23-6.327h1.367l-.327 1.682h1.218c.767 0 1.295.134 1.586.401s.378.7.263 1.299l-.572 2.944h-1.389zm7.597-2.465a2.782 2.782 0 01-.305.847c-.143.255-.33.49-.561.703a2.44 2.44 0 01-.917.551c-.336.108-.765.164-1.286.164h-1.18l-.327 1.682h-1.378l1.23-6.326h2.649c.797 0 1.378.209 1.744.628.366.417.477 1.001.331 1.751zm-2.595-1.382h-.943l-.516 2.648h.838c.557 0 .971-.105 1.242-.314.272-.21.455-.559.551-1.049.092-.47.049-.802-.125-.995s-.524-.29-1.047-.29z"/></svg>
|
||||
<% } else if(tab.icon === 'ruby'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M20.156.083c3.033.525 3.893 2.598 3.829 4.77L24 4.822 22.635 22.71 4.89 23.926h.016C3.433 23.864.15 23.729 0 19.139l1.645-3 2.819 6.586.503 1.172 2.805-9.144-.03.007.016-.03 9.255 2.956-1.396-5.431-.99-3.9 8.82-.569-.615-.51L16.5 2.114 20.159.073l-.003.01zM0 19.089v.026-.029.003zM5.13 5.073c3.561-3.533 8.157-5.621 9.922-3.84 1.762 1.777-.105 6.105-3.673 9.636-3.563 3.532-8.103 5.734-9.864 3.957-1.766-1.777.045-6.217 3.612-9.75l.003-.003z"/></svg>
|
||||
<% } else if(tab.icon === 'rust'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M23.687 11.709l-.995-.616a13.559 13.559 0 00-.028-.29l.855-.797a.344.344 0 00-.114-.571l-1.093-.409a8.392 8.392 0 00-.086-.282l.682-.947a.344.344 0 00-.199-.54l-1.135-.228a8.344 8.344 0 00-.14-.261l.48-1.066a.344.344 0 00-.276-.477l-1.149-.044a7.373 7.373 0 00-.192-.227l.258-1.151a.344.344 0 00-.348-.389l-1.136.14a6.4 6.4 0 00-.238-.18l.024-1.186a.344.344 0 00-.41-.281l-1.097.32a5.7 5.7 0 00-.275-.12l-.212-1.178a.344.344 0 00-.46-.157l-1.028.494a4.87 4.87 0 00-.3-.054l-.441-1.129a.344.344 0 00-.494-.023l-.935.657a3.787 3.787 0 00-.315.015l-.66-1.038a.344.344 0 00-.512.114l-.817.802a3.453 3.453 0 00-.318.084l-.86-.911a.344.344 0 00-.512.234l-.68.932a3.29 3.29 0 00-.304.152l-1.033-.744a.344.344 0 00-.493.342l-.524 1.04a3.29 3.29 0 00-.275.216l-1.168-.54a.344.344 0 00-.457.43l-.353 1.124a3.453 3.453 0 00-.233.275l-1.262-.3a.344.344 0 00-.404.498l-.168 1.178a3.787 3.787 0 00-.178.322l-1.315-.032a.344.344 0 00-.333.547l.025 1.2a4.87 4.87 0 00-.113.354l-1.325.241a.344.344 0 00-.248.576l.227 1.19a5.7 5.7 0 00-.04.373l-1.29.513a.344.344 0 00-.151.583l.422 1.147a6.4 6.4 0 00.038.38l-1.212.77a.344.344 0 00-.046.57l.605 1.072a7.373 7.373 0 00.116.371l-1.094 1.008a.344.344 0 00.06.538l.771.967a8.344 8.344 0 00.19.348l-.94 1.218a.344.344 0 00.163.486l.915.836a8.392 8.392 0 00.259.31l-.756 1.393a.344.344 0 00.258.415l1.03.68c.1.09.202.178.307.264l-.544 1.53a.344.344 0 00.342.327l1.115.504a13.559 13.559 0 00.173.12l-.312 1.63a.344.344 0 00.413.223l1.17.307a14.195 14.195 0 00.22.085l-.067 1.688a.344.344 0 00.469.106l1.193.094a14.718 14.718 0 00.253.044l.184 1.7a.344.344 0 00.508-.016l1.184-.128a14.94 14.94 0 00.273-.003l.432 1.667a.344.344 0 00.528-.136l1.142-.347a14.862 14.862 0 00.282-.053l.67 1.588a.344.344 0 00.53-.246l1.072-.56a14.49 14.49 0 00.278-.105l.893 1.465a.344.344 0 00.512-.343l.972-.76a13.844 13.844 0 00.262-.156l1.095 1.3a.344.344 0 00.475-.425l.85-.943c.08-.063.157-.127.234-.192l1.27 1.094a.344.344 0 00.422-.49l.706-1.105a12.656 12.656 0 00.197-.225l1.412.853a.344.344 0 00.354-.538l.544-1.24c.058-.084.115-.17.17-.255l1.521.583a.344.344 0 00.27-.567l.367-1.346c.047-.092.092-.185.136-.279l1.59.293a.344.344 0 00.176-.575l.18-1.418a9.903 9.903 0 00.094-.3l1.62-.008a.344.344 0 00.076-.56l-.013-1.455a9.245 9.245 0 00.045-.315l1.607-.307a.344.344 0 00-.028-.522l-.206-1.457c.005-.109.007-.218.007-.328l1.553-.595a.344.344 0 00-.13-.461zM12 16.874a4.874 4.874 0 110-9.748 4.874 4.874 0 010 9.748z"/></svg>
|
||||
<% } else if(tab.icon === 'powershell'){ %>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M23.181 2.974c.568 0 .923.463.792 1.035l-3.659 15.982c-.13.572-.697 1.035-1.265 1.035H.819c-.568 0-.923-.463-.792-1.035L3.686 4.009c.13-.572.697-1.035 1.265-1.035h18.23zm-8.375 12.15c0-.292-.123-.559-.322-.744l.004.004-5.444-4.678c-.162-.144-.373-.232-.605-.232-.506 0-.916.41-.916.916 0 .272.119.516.307.684l-.002-.002 4.453 3.832-4.453 3.832c-.188.166-.307.41-.307.684 0 .506.41.916.916.916.232 0 .443-.088.605-.232l5.444-4.678c.2-.186.323-.453.323-.746v-.556h-.003zm.62 2.406c0-.355.288-.644.644-.644h4.023c.355 0 .644.288.644.644 0 .355-.288.644-.644.644h-4.023a.644.644 0 01-.644-.644z"/></svg>
|
||||
<% } %>
|
||||
</span>
|
||||
<span><%= tab.name %></span>
|
||||
</button>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Code Panels -->
|
||||
<div class="rounded-b-xl bg-slate-900 border border-slate-700/50 border-t-0 overflow-hidden min-w-0">
|
||||
<% tabs.forEach((tab, index) => { %>
|
||||
<div
|
||||
role="tabpanel"
|
||||
id="<%= uniqueId %>-panel-<%= tab.id %>"
|
||||
aria-labelledby="<%= uniqueId %>-tab-<%= tab.id %>"
|
||||
class="code-panel relative min-w-0 w-full <%= index === 0 ? '' : 'hidden' %>"
|
||||
data-panel-id="<%= tab.id %>"
|
||||
>
|
||||
<% if(tab.isPreview) { %>
|
||||
<!-- Request Preview Panel -->
|
||||
<button
|
||||
class="copy-btn-tabs"
|
||||
onclick="copyPreviewFromPanel(this)"
|
||||
aria-label="Copy request"
|
||||
>
|
||||
<svg class="copy-icon w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<svg class="check-icon w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="copy-text">Copy</span>
|
||||
</button>
|
||||
<div class="code-content p-4 w-full max-w-full overflow-hidden" style="min-height: 200px;">
|
||||
<!-- Headers Section -->
|
||||
<div class="mb-4">
|
||||
<div class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Headers</div>
|
||||
<div class="bg-slate-800/50 rounded-lg p-3 border border-slate-700/50 overflow-hidden">
|
||||
<pre class="text-sm text-slate-300 font-mono whitespace-pre-wrap m-0 preview-headers overflow-x-auto"><%= codeExamples.requestPreview.headers %></pre>
|
||||
</div>
|
||||
</div>
|
||||
<% if(codeExamples.requestPreview.body) { %>
|
||||
<!-- Body Section -->
|
||||
<div>
|
||||
<div class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Body</div>
|
||||
<div class="bg-slate-800/50 rounded-lg p-3 border border-slate-700/50 overflow-hidden">
|
||||
<pre class="text-sm leading-relaxed m-0 overflow-x-auto"><code class="language-json preview-body"><%= codeExamples.requestPreview.body %></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<!-- Code Panel -->
|
||||
<button
|
||||
class="copy-btn-tabs"
|
||||
onclick="copyCodeFromPanel(this)"
|
||||
aria-label="Copy code"
|
||||
>
|
||||
<svg class="copy-icon w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<svg class="check-icon w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="copy-text">Copy</span>
|
||||
</button>
|
||||
<div class="code-content w-full max-w-full overflow-hidden" style="min-height: 200px;">
|
||||
<pre class="overflow-x-auto p-4 text-sm leading-relaxed m-0 w-full max-w-full"><code class="language-<%= tab.id === 'javascript' || tab.id === 'typescript' ? 'javascript' : (tab.id === 'python' ? 'python' : (tab.id === 'go' ? 'go' : (tab.id === 'ruby' ? 'ruby' : (tab.id === 'rust' ? 'rust' : (tab.id === 'powershell' ? 'powershell' : (tab.id === 'java' ? 'java' : (tab.id === 'csharp' ? 'csharp' : (tab.id === 'php' ? 'php' : 'bash')))))))) %>"><%= codeExamples[tab.id] %></code></pre>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function switchCodeTab(containerId, tabId) {
|
||||
// Update all code-tabs-containers on the page
|
||||
document.querySelectorAll('.code-tabs-container').forEach(container => {
|
||||
// Update tabs
|
||||
container.querySelectorAll('.code-tab').forEach(tab => {
|
||||
const isActive = tab.dataset.tabId === tabId;
|
||||
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
||||
|
||||
// Update tab styling
|
||||
if (isActive) {
|
||||
tab.classList.remove('text-slate-400', 'hover:text-slate-300', 'hover:bg-slate-800/30');
|
||||
tab.classList.add('text-indigo-400', 'bg-slate-800/50');
|
||||
} else {
|
||||
tab.classList.add('text-slate-400', 'hover:text-slate-300', 'hover:bg-slate-800/30');
|
||||
tab.classList.remove('text-indigo-400', 'bg-slate-800/50');
|
||||
}
|
||||
});
|
||||
|
||||
// Update panels
|
||||
container.querySelectorAll('.code-panel').forEach(panel => {
|
||||
if (panel.dataset.panelId === tabId) {
|
||||
panel.classList.remove('hidden');
|
||||
// Apply syntax highlighting to code blocks
|
||||
panel.querySelectorAll('pre code').forEach((block) => {
|
||||
if (window.hljs && !block.dataset.highlighted) {
|
||||
hljs.highlightElement(block);
|
||||
block.dataset.highlighted = 'true';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
panel.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Store preference in localStorage (only for non-preview tabs)
|
||||
if (tabId !== 'preview') {
|
||||
try {
|
||||
localStorage.setItem('preferred-code-lang', tabId);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
function copyCodeFromPanel(btn) {
|
||||
const panel = btn.closest('.code-panel');
|
||||
const code = panel.querySelector('code');
|
||||
if (code) {
|
||||
navigator.clipboard.writeText(code.textContent).then(() => {
|
||||
showCopySuccess(btn);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function copyPreviewFromPanel(btn) {
|
||||
const panel = btn.closest('.code-panel');
|
||||
const headers = panel.querySelector('.preview-headers');
|
||||
const body = panel.querySelector('.preview-body');
|
||||
|
||||
let textToCopy = '';
|
||||
if (headers) {
|
||||
textToCopy += headers.textContent;
|
||||
}
|
||||
if (body) {
|
||||
textToCopy += '\n\n' + body.textContent;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||
showCopySuccess(btn);
|
||||
});
|
||||
}
|
||||
|
||||
function showCopySuccess(btn) {
|
||||
const copyIcon = btn.querySelector('.copy-icon');
|
||||
const checkIcon = btn.querySelector('.check-icon');
|
||||
const copyText = btn.querySelector('.copy-text');
|
||||
|
||||
copyIcon.classList.add('hidden');
|
||||
checkIcon.classList.remove('hidden');
|
||||
copyText.textContent = 'Copied!';
|
||||
btn.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
copyIcon.classList.remove('hidden');
|
||||
checkIcon.classList.add('hidden');
|
||||
copyText.textContent = 'Copy';
|
||||
btn.classList.remove('copied');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Apply syntax highlighting and stored language preference on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Wait a bit for all scripts to load, then highlight all code blocks
|
||||
setTimeout(function() {
|
||||
if (window.hljs) {
|
||||
// Highlight ALL code blocks (including hidden ones)
|
||||
document.querySelectorAll('pre code').forEach((block) => {
|
||||
if (!block.classList.contains('hljs')) {
|
||||
hljs.highlightElement(block);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Apply stored preference if exists
|
||||
try {
|
||||
const preferred = localStorage.getItem('preferred-code-lang');
|
||||
if (preferred && preferred !== 'preview') {
|
||||
// Switch all containers to the preferred language
|
||||
document.querySelectorAll('.code-tabs-container').forEach(container => {
|
||||
const tab = container.querySelector(`[data-tab-id="${preferred}"]`);
|
||||
if (tab) {
|
||||
switchCodeTab(container.id, preferred);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
@@ -1,63 +1,33 @@
|
||||
<div class="not-prose my-6 overflow-hidden rounded-xl bg-slate-900 shadow-lg ring-1 ring-slate-800/50 code-block-wrapper">
|
||||
<div class="flex min-h-[calc(theme(spacing.11)+1px)] flex-wrap items-center gap-x-4 border-b border-slate-700/50 bg-slate-800/50 px-3 sm:px-4">
|
||||
<h4 class="mr-auto text-xs font-semibold text-slate-300 tracking-wide"><%= title -%></h4>
|
||||
<button class="copy-btn-response" onclick="copyCodeBlock(this)" aria-label="Copy code">
|
||||
<svg class="copy-icon w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<svg class="check-icon w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="copy-text">Copy</span>
|
||||
</button>
|
||||
<div class="not-prose my-6 overflow-hidden rounded-2xl bg-zinc-900 shadow-md ">
|
||||
<div class="flex min-h-[calc(theme(spacing.12)+1px)] flex-wrap items-start gap-x-4 border-b border-zinc-700 bg-zinc-800 px-4 ">
|
||||
<h4 class="mr-auto text-xs font-semibold text-white mt-5"><%= title -%></h4>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group ">
|
||||
|
||||
|
||||
<% if(requestType && requestUrl){ %>
|
||||
<div class="flex flex-wrap sm:flex-nowrap items-center gap-2 sm:gap-3 py-2 sm:py-0 sm:h-10 border-b border-slate-700/30 bg-slate-800/30 px-3 sm:px-4">
|
||||
|
||||
<div class="flex h-9 items-center gap-2 border-b-white/7.5 bg-zinc-900 bg-white/2.5 px-4 ">
|
||||
<% if( requestType === "GET"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-emerald-500/10 px-2 py-1 text-xs font-semibold text-emerald-400 ring-1 ring-inset ring-emerald-500/20 flex-shrink-0">GET</span>
|
||||
<% } %>
|
||||
<div class="dark flex"><span class="font-mono text-sm font-semibold leading-6 text-emerald-500 ">GET</span></div>
|
||||
<% } %>
|
||||
<% if( requestType === "POST"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-500/10 px-2 py-1 text-xs font-semibold text-indigo-400 ring-1 ring-inset ring-indigo-500/20 flex-shrink-0">POST</span>
|
||||
<% } %>
|
||||
<div class="dark flex"><span class="font-mono text-sm font-semibold leading-6 text-sky-500 ">POST</span></div>
|
||||
<% } %>
|
||||
<% if( requestType === "DELETE"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-red-500/10 px-2 py-1 text-xs font-semibold text-red-400 ring-1 ring-inset ring-red-500/20 flex-shrink-0">DELETE</span>
|
||||
<% } %>
|
||||
<div class="dark flex"><span class="font-mono text-sm font-semibold leading-6 text-red-500 ">DELETE</span></div>
|
||||
<% } %>
|
||||
<% if( requestType === "PUT"){ %>
|
||||
<span class="inline-flex items-center rounded-md bg-amber-500/10 px-2 py-1 text-xs font-semibold text-amber-400 ring-1 ring-inset ring-amber-500/20 flex-shrink-0">PUT</span>
|
||||
<% } %>
|
||||
<span class="font-mono text-xs sm:text-sm text-slate-400 break-all"><%= requestUrl -%></span>
|
||||
<div class="dark flex"><span class="font-mono text-sm font-semibold leading-6 text-amber-500 ">PUT</span></div>
|
||||
<% } %>
|
||||
<span class="h-0.5 w-0.5 rounded-full bg-zinc-500"></span><span class="font-mono text-sm text-zinc-300"><%= requestUrl -%></span>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% } %>
|
||||
<% if(code){ %>
|
||||
<div class="relative">
|
||||
<pre class="overflow-x-auto p-4 text-sm text-slate-300 leading-relaxed"><code class="language-json text-sm"><%= code -%></code></pre>
|
||||
<pre class="overflow-x-auto p-4 text-xs text-white"><code class="text-sm"><%= code -%></code></pre>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function copyCodeBlock(btn) {
|
||||
const codeBlock = btn.closest('.code-block-wrapper');
|
||||
const code = codeBlock.querySelector('code');
|
||||
if (code) {
|
||||
navigator.clipboard.writeText(code.textContent).then(() => {
|
||||
const copyIcon = btn.querySelector('.copy-icon');
|
||||
const checkIcon = btn.querySelector('.check-icon');
|
||||
const copyText = btn.querySelector('.copy-text');
|
||||
|
||||
copyIcon.classList.add('hidden');
|
||||
checkIcon.classList.remove('hidden');
|
||||
copyText.textContent = 'Copied!';
|
||||
btn.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
copyIcon.classList.remove('hidden');
|
||||
checkIcon.classList.add('hidden');
|
||||
copyText.textContent = 'Copy';
|
||||
btn.classList.remove('copied');
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
@@ -1,41 +1,31 @@
|
||||
<footer class="mx-auto w-full space-y-10 pb-16">
|
||||
<div class="mt-12 sm:mt-16 pt-8 border-t border-slate-200">
|
||||
<div class="rounded-xl bg-gradient-to-r from-slate-50 to-slate-100/50 p-4 sm:p-6 ring-1 ring-slate-200/50">
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex-shrink-0 rounded-lg bg-indigo-100 p-2.5">
|
||||
<svg class="h-5 w-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-900 mb-1">Open Source</h3>
|
||||
<p class="text-sm text-slate-600 leading-relaxed">
|
||||
OneUptime API documentation is under Apache 2.0 license. Contribute on GitHub.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<a class="group flex items-center justify-center w-9 h-9 rounded-lg bg-white ring-1 ring-slate-200 hover:ring-indigo-300 hover:bg-indigo-50 transition-all" href="https://twitter.com/OneUptimeHQ" target="_blank">
|
||||
<span class="sr-only">Follow us on Twitter</span>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"
|
||||
class="h-4 w-4 fill-slate-500 transition group-hover:fill-indigo-600">
|
||||
<path
|
||||
d="M16.712 6.652c.01.146.01.29.01.436 0 4.449-3.267 9.579-9.242 9.579v-.003a8.963 8.963 0 0 1-4.98-1.509 6.379 6.379 0 0 0 4.807-1.396c-1.39-.027-2.608-.966-3.035-2.337.487.097.99.077 1.467-.059-1.514-.316-2.606-1.696-2.606-3.3v-.041c.45.26.956.404 1.475.42C3.18 7.454 2.74 5.486 3.602 3.947c1.65 2.104 4.083 3.382 6.695 3.517a3.446 3.446 0 0 1 .94-3.217 3.172 3.172 0 0 1 4.596.148 6.38 6.38 0 0 0 2.063-.817 3.357 3.357 0 0 1-1.428 1.861 6.283 6.283 0 0 0 1.865-.53 6.735 6.735 0 0 1-1.62 1.744Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
<a class="group flex items-center justify-center w-9 h-9 rounded-lg bg-white ring-1 ring-slate-200 hover:ring-indigo-300 hover:bg-indigo-50 transition-all" href="https://github.com/oneuptime/oneuptime" target="_blank">
|
||||
<span class="sr-only">Follow us on GitHub</span>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"
|
||||
class="h-4 w-4 fill-slate-500 transition group-hover:fill-indigo-600">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="mx-auto max-w-2xl space-y-10 pb-16 lg:max-w-5xl">
|
||||
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center justify-between gap-5 border-t border-zinc-900/5 pt-8 sm:flex-row">
|
||||
<p class="text-xs text-zinc-600 ">
|
||||
OneUptime documentation is under MIT license. Please feel free to contribute and improve it on GitHub.
|
||||
</p>
|
||||
<div class="flex gap-4">
|
||||
<a class="group" href="https://twitter.com/OneUptimeHQ" target="_blank">
|
||||
<span class="sr-only">Follow us on Twitter</span>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"
|
||||
class="h-5 w-5 fill-zinc-700 transition group-hover:fill-zinc-900 :fill-zinc-500">
|
||||
<path
|
||||
d="M16.712 6.652c.01.146.01.29.01.436 0 4.449-3.267 9.579-9.242 9.579v-.003a8.963 8.963 0 0 1-4.98-1.509 6.379 6.379 0 0 0 4.807-1.396c-1.39-.027-2.608-.966-3.035-2.337.487.097.99.077 1.467-.059-1.514-.316-2.606-1.696-2.606-3.3v-.041c.45.26.956.404 1.475.42C3.18 7.454 2.74 5.486 3.602 3.947c1.65 2.104 4.083 3.382 6.695 3.517a3.446 3.446 0 0 1 .94-3.217 3.172 3.172 0 0 1 4.596.148 6.38 6.38 0 0 0 2.063-.817 3.357 3.357 0 0 1-1.428 1.861 6.283 6.283 0 0 0 1.865-.53 6.735 6.735 0 0 1-1.62 1.744Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
<a class="group" href="https://github.com/oneuptime/oneuptime" target="_blank">
|
||||
<span class="sr-only">Follow us on GitHub</span>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"
|
||||
class="h-5 w-5 fill-zinc-700 transition group-hover:fill-zinc-900 :fill-zinc-500">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</footer>
|
||||
@@ -4,377 +4,90 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
||||
rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<!-- Load additional language support -->
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/java.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/csharp.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/php.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/ruby.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/rust.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/powershell.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/json.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/typescript.min.js"></script>
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<script>
|
||||
function applyStyles() {
|
||||
applyStylesTo("h1", "font-bold text-2xl mb-6 text-slate-900 tracking-tight")
|
||||
applyStylesTo("h2", "mb-5 scroll-mt-24 mt-16 font-semibold text-xl text-slate-900 tracking-tight")
|
||||
applyStylesTo("h3", "mb-4 scroll-mt-24 mt-10 font-semibold text-base text-slate-800")
|
||||
applyStylesTo("p", "mb-5 text-slate-600 leading-7")
|
||||
applyStylesTo("link", "text-indigo-600 hover:text-indigo-700 font-medium transition-colors")
|
||||
applyStylesTo("model-inline-code", "rounded-md py-0.5 px-1.5 text-sm font-medium text-slate-700 bg-slate-100 border border-slate-200")
|
||||
applyStylesTo("inline-code", "rounded-md py-0.5 px-1.5 text-sm font-medium text-slate-700 bg-slate-100 border border-slate-200")
|
||||
applyStylesTo("h1", "font-bold text-2xl mb-5")
|
||||
applyStylesTo("h2", "mb-5 scroll-mt-24 mt-24 font-bold text-xl")
|
||||
applyStylesTo("h3", "mb-5 scroll-mt-24 mt-10 font-bold text-base")
|
||||
applyStylesTo("p", "mb-5")
|
||||
applyStylesTo("link", "text-emerald-500 hover:text-emerald-600")
|
||||
applyStylesTo("model-inline-code", "rounded p-0.5 px-1 text-sm text-gray-50 bg-gray-600 border-2 border-gray-600 shadow")
|
||||
applyStylesTo("inline-code", "rounded p-0.5 px-1 text-sm text-gray-500 bg-gray-100 border-2 border-gray-200")
|
||||
}
|
||||
|
||||
function applyStylesTo(tagOrClassName, classList) {
|
||||
|
||||
|
||||
let elements = document.getElementsByClassName(tagOrClassName);
|
||||
|
||||
if(elements.length === 0){
|
||||
elements = document.getElementsByTagName(tagOrClassName);
|
||||
}
|
||||
|
||||
for (var i = 0, all = elements.length; i < all; i++) {
|
||||
classList.split(" ").map((classItem)=> {
|
||||
elements[i].classList.add(classItem);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Better prose styling */
|
||||
.prose p {
|
||||
color: #475569;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.prose .lead {
|
||||
font-size: 1.125rem;
|
||||
color: #64748b;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.prose hr {
|
||||
border-color: #e2e8f0;
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.prose ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Syntax highlighting - comprehensive token styles */
|
||||
.hljs {
|
||||
background: transparent !important;
|
||||
color: unset;
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
/* Keywords: import, from, def, class, if, else, for, while, return, etc. */
|
||||
.hljs-keyword,
|
||||
.hljs-built_in,
|
||||
.hljs-type,
|
||||
.hljs-literal,
|
||||
.hljs-symbol {
|
||||
color: #c792ea !important;
|
||||
.hljs-string {
|
||||
color: #6ee7b7
|
||||
}
|
||||
|
||||
/* Strings */
|
||||
.hljs-string,
|
||||
.hljs-doctag,
|
||||
.hljs-regexp {
|
||||
color: #c3e88d !important;
|
||||
}
|
||||
|
||||
/* Numbers */
|
||||
.hljs-number {
|
||||
color: #f78c6c !important;
|
||||
color: #7dd3fc
|
||||
}
|
||||
|
||||
/* Comments */
|
||||
.hljs-comment {
|
||||
color: #676e95 !important;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Function/method names */
|
||||
.hljs-title,
|
||||
.hljs-title.function_,
|
||||
.hljs-title.class_ {
|
||||
color: #82aaff !important;
|
||||
}
|
||||
|
||||
/* Variables and parameters */
|
||||
.hljs-variable,
|
||||
.hljs-params,
|
||||
.hljs-attr {
|
||||
color: #f07178 !important;
|
||||
}
|
||||
|
||||
/* Properties and attributes */
|
||||
.hljs-property,
|
||||
.hljs-attribute {
|
||||
color: #ffcb6b !important;
|
||||
}
|
||||
|
||||
/* Punctuation */
|
||||
.hljs-punctuation {
|
||||
color: #89ddff !important;
|
||||
color: #e5e7eb
|
||||
}
|
||||
|
||||
/* Operators */
|
||||
.hljs-operator {
|
||||
color: #89ddff !important;
|
||||
.hljs-comment {
|
||||
color: #9ca3af
|
||||
}
|
||||
|
||||
/* Meta/preprocessor */
|
||||
.hljs-meta {
|
||||
color: #ffcb6b !important;
|
||||
.hljs-keyword{
|
||||
color: #7dd3fc;
|
||||
font-weight: unset;
|
||||
}
|
||||
|
||||
/* Section headers */
|
||||
.hljs-section {
|
||||
color: #82aaff !important;
|
||||
.hljs-attr{
|
||||
color: #fda4af;
|
||||
}
|
||||
|
||||
/* Names (function calls, etc) */
|
||||
.hljs-name {
|
||||
color: #f07178 !important;
|
||||
* {
|
||||
font-family: Inter;
|
||||
}
|
||||
|
||||
/* Selector tags in CSS */
|
||||
.hljs-selector-tag,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id {
|
||||
color: #c792ea !important;
|
||||
}
|
||||
|
||||
/* Template variables */
|
||||
.hljs-template-variable {
|
||||
color: #f07178 !important;
|
||||
}
|
||||
|
||||
/* Deletion/addition in diffs */
|
||||
.hljs-deletion {
|
||||
color: #f07178 !important;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #c3e88d !important;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #e2e8f0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #cbd5e1;
|
||||
}
|
||||
|
||||
/* Code block copy button */
|
||||
.code-block-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Response block copy button */
|
||||
.copy-btn-response {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #94a3b8;
|
||||
background: rgba(30, 41, 59, 0.6);
|
||||
border: 1px solid #334155;
|
||||
border-radius: 6px;
|
||||
bottom: 0;
|
||||
color: transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.copy-btn-response:hover {
|
||||
color: #f1f5f9;
|
||||
background: #334155;
|
||||
border-color: #475569;
|
||||
}
|
||||
.copy-btn-response.copied {
|
||||
color: #22c55e;
|
||||
border-color: #22c55e;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
/* Code tabs styling */
|
||||
.code-tabs-container {
|
||||
--tab-hover-bg: rgba(30, 41, 59, 0.5);
|
||||
}
|
||||
|
||||
.code-tab {
|
||||
position: relative;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.code-tab[aria-selected="true"] {
|
||||
border-bottom-color: #6366f1;
|
||||
}
|
||||
|
||||
.code-panel {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-btn-tabs {
|
||||
height: auto;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #94a3b8;
|
||||
background: rgba(30, 41, 59, 0.9);
|
||||
border: 1px solid #334155;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: all 0.15s ease;
|
||||
z-index: 10;
|
||||
backdrop-filter: blur(4px);
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.code-panel:hover .copy-btn-tabs {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.copy-btn-tabs:hover {
|
||||
color: #f1f5f9;
|
||||
background: #334155;
|
||||
border-color: #475569;
|
||||
}
|
||||
|
||||
.copy-btn-tabs.copied {
|
||||
color: #22c55e;
|
||||
border-color: #22c55e;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
/* Code content min-height for consistent sizing */
|
||||
.code-content {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
/* Fix code tabs width - prevent content from expanding container */
|
||||
.code-tabs-container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-tabs-container pre {
|
||||
width: 0;
|
||||
min-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-panel {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-content {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-content pre code {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for tabs but keep functionality */
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tab indicator animation */
|
||||
.tab-indicator {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* Code panel base styles */
|
||||
.code-panel pre {
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.code-panel code {
|
||||
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
/* Better focus states */
|
||||
a:focus-visible, button:focus-visible {
|
||||
outline: 2px solid #6366f1;
|
||||
outline-offset: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
a, button {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
/* Property list styling */
|
||||
.property-item {
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
.property-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Endpoint card styling */
|
||||
.endpoint-card {
|
||||
background: linear-gradient(to right, #fafafa, white);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Calendar picker */
|
||||
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
|
||||
input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
background: transparent;
|
||||
bottom: 0;
|
||||
@@ -388,6 +101,7 @@
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<% if(typeof enableGoogleTagManager !== 'undefined' ? enableGoogleTagManager : false){ %>
|
||||
|
||||
@@ -421,6 +135,7 @@
|
||||
<link rel="apple-touch-icon-precomposed" href="/img/ou-wb.svg">
|
||||
<link rel="icon" href="/img/ou-wb.svg">
|
||||
<link rel="image_src" type="image/png" href="/img/hou-wb.svg">
|
||||
<link rel="canonical" href="/">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<meta property="og:title" content="OneUptime - One Complete Observability platform.">
|
||||
<meta property="og:url" content="https://oneuptime.com">
|
||||
@@ -430,7 +145,7 @@
|
||||
<meta property="og:image" content="https://oneuptime.com/img/hou-wb.svg">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="theme-color" content="#6366f1">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="twitter:image" content="/img/ou-wb.svg">
|
||||
<meta name="twitter:site" content="@oneuptimeinc">
|
||||
<meta name="twitter:title" content="OneUptime - One Complete Observability platform.">
|
||||
|
||||
@@ -1,92 +1,9 @@
|
||||
<header class="contents lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex">
|
||||
<div
|
||||
class="contents lg:pointer-events-auto lg:block lg:w-72 lg:overflow-y-auto lg:border-r lg:border-slate-200/80 lg:px-6 lg:pt-6 lg:pb-8 xl:w-80 lg:bg-gradient-to-b lg:from-slate-50 lg:to-white">
|
||||
<div class="hidden lg:flex lg:items-center lg:mb-8">
|
||||
class="contents lg:pointer-events-auto lg:block lg:w-72 lg:overflow-y-auto lg:border-r lg:border-zinc-900/10 lg:px-6 lg:pt-4 lg:pb-8 xl:w-80">
|
||||
<div class="hidden lg:flex">
|
||||
<a aria-label="Home" href="/">
|
||||
<svg class="h-7" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 75" preserveAspectRatio="xMidYMid meet">
|
||||
<defs>
|
||||
<g></g>
|
||||
<clipPath id="1d83d73318">
|
||||
<path d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
<clipPath id="32c1ad7ad2">
|
||||
<path d="M 366.480469 51.03125 L 352.816406 51.03125 C 352.253906 51.03125 351.792969 50.574219 351.792969 50.007812 L 351.792969 36.347656 C 351.792969 35.785156 352.253906 35.324219 352.816406 35.324219 L 366.480469 35.324219 C 367.042969 35.324219 367.5 35.785156 367.5 36.347656 L 367.5 50.007812 C 367.5 50.574219 367.042969 51.03125 366.480469 51.03125 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#1d83d73318)">
|
||||
<g clip-path="url(#32c1ad7ad2)">
|
||||
<path fill="#7ed957" d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " fill-opacity="1" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(11.173064, 55.95717)">
|
||||
<g>
|
||||
<path d="M 1.5625 -21.828125 C 1.5625 -25.191406 2.054688 -28.320312 3.046875 -31.21875 C 4.046875 -34.164062 5.5 -36.71875 7.40625 -38.875 C 9.3125 -41.070312 11.632812 -42.773438 14.375 -43.984375 C 15.738281 -44.597656 17.195312 -45.0625 18.75 -45.375 C 20.3125 -45.6875 21.941406 -45.84375 23.640625 -45.84375 C 25.335938 -45.84375 26.960938 -45.6875 28.515625 -45.375 C 30.078125 -45.0625 31.539062 -44.578125 32.90625 -43.921875 C 34.269531 -43.296875 35.53125 -42.546875 36.6875 -41.671875 C 37.851562 -40.804688 38.914062 -39.8125 39.875 -38.6875 C 41.78125 -36.53125 43.25 -34.003906 44.28125 -31.109375 C 45.28125 -28.203125 45.78125 -25.109375 45.78125 -21.828125 C 45.78125 -18.554688 45.28125 -15.46875 44.28125 -12.5625 C 43.957031 -11.613281 43.578125 -10.679688 43.140625 -9.765625 C 42.703125 -8.847656 42.210938 -7.984375 41.671875 -7.171875 C 41.140625 -6.367188 40.5625 -5.597656 39.9375 -4.859375 C 38.976562 -3.734375 37.90625 -2.734375 36.71875 -1.859375 C 35.539062 -0.992188 34.289062 -0.25 32.96875 0.375 C 30.226562 1.65625 27.113281 2.296875 23.625 2.296875 C 20.144531 2.296875 17.039062 1.65625 14.3125 0.375 C 13.644531 0.0390625 13 -0.3125 12.375 -0.6875 C 11.757812 -1.0625 11.160156 -1.460938 10.578125 -1.890625 C 9.992188 -2.328125 9.429688 -2.796875 8.890625 -3.296875 C 8.359375 -3.796875 7.863281 -4.316406 7.40625 -4.859375 C 5.5 -7.054688 4.046875 -9.625 3.046875 -12.5625 C 2.054688 -15.46875 1.5625 -18.554688 1.5625 -21.828125 Z M 10.703125 -21.828125 C 10.703125 -20.796875 10.773438 -19.769531 10.921875 -18.75 C 11.066406 -17.738281 11.28125 -16.734375 11.5625 -15.734375 C 11.894531 -14.785156 12.269531 -13.882812 12.6875 -13.03125 C 13.101562 -12.175781 13.601562 -11.378906 14.1875 -10.640625 C 14.351562 -10.390625 14.535156 -10.160156 14.734375 -9.953125 C 14.941406 -9.742188 15.148438 -9.535156 15.359375 -9.328125 C 15.566406 -9.117188 15.785156 -8.910156 16.015625 -8.703125 C 16.242188 -8.492188 16.484375 -8.304688 16.734375 -8.140625 C 16.984375 -7.984375 17.234375 -7.820312 17.484375 -7.65625 C 17.734375 -7.488281 17.976562 -7.320312 18.21875 -7.15625 C 19.800781 -6.320312 21.609375 -5.90625 23.640625 -5.90625 C 24.378906 -5.90625 25.082031 -5.945312 25.75 -6.03125 C 26.414062 -6.113281 27.035156 -6.25 27.609375 -6.4375 C 28.191406 -6.625 28.734375 -6.84375 29.234375 -7.09375 C 30.847656 -7.875 32.195312 -8.953125 33.28125 -10.328125 C 33.65625 -10.785156 33.984375 -11.269531 34.265625 -11.78125 C 34.554688 -12.300781 34.828125 -12.863281 35.078125 -13.46875 C 35.328125 -14.070312 35.554688 -14.679688 35.765625 -15.296875 C 36.347656 -17.328125 36.640625 -19.503906 36.640625 -21.828125 C 36.640625 -23.898438 36.328125 -25.894531 35.703125 -27.8125 C 35.117188 -29.71875 34.25 -31.414062 33.09375 -32.90625 C 31.96875 -34.351562 30.617188 -35.515625 29.046875 -36.390625 C 27.472656 -37.222656 25.671875 -37.640625 23.640625 -37.640625 C 23.140625 -37.640625 22.640625 -37.617188 22.140625 -37.578125 C 21.640625 -37.535156 21.160156 -37.460938 20.703125 -37.359375 C 20.253906 -37.253906 19.820312 -37.128906 19.40625 -36.984375 C 18.988281 -36.835938 18.59375 -36.660156 18.21875 -36.453125 C 17.394531 -36.078125 16.648438 -35.617188 15.984375 -35.078125 C 15.316406 -34.546875 14.71875 -33.925781 14.1875 -33.21875 C 13.0625 -31.8125 12.1875 -30.148438 11.5625 -28.234375 C 10.988281 -26.242188 10.703125 -24.109375 10.703125 -21.828125 Z M 10.703125 -21.828125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(58.505968, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.046875 C 12.269531 -31.367188 12.601562 -31.65625 12.9375 -31.90625 C 13.269531 -32.15625 13.625 -32.382812 14 -32.59375 C 16.070312 -33.957031 18.5 -34.640625 21.28125 -34.640625 C 23.3125 -34.640625 25.113281 -34.316406 26.6875 -33.671875 C 28.257812 -33.035156 29.585938 -32.078125 30.671875 -30.796875 C 32.703125 -28.304688 33.71875 -25.070312 33.71875 -21.09375 L 33.71875 1.5625 L 25 1.5625 L 25 -20.46875 C 25 -21.164062 24.945312 -21.804688 24.84375 -22.390625 C 24.738281 -22.972656 24.59375 -23.488281 24.40625 -23.9375 C 24.226562 -24.394531 23.992188 -24.789062 23.703125 -25.125 C 23.453125 -25.457031 23.171875 -25.738281 22.859375 -25.96875 C 22.546875 -26.195312 22.179688 -26.363281 21.765625 -26.46875 C 21.359375 -26.570312 20.882812 -26.625 20.34375 -26.625 C 19.59375 -26.625 18.84375 -26.476562 18.09375 -26.1875 C 17.3125 -25.894531 16.546875 -25.476562 15.796875 -24.9375 C 15.054688 -24.363281 14.351562 -23.722656 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.046875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(95.327364, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(129.163241, 55.95717)">
|
||||
<g>
|
||||
<path d="M 31.28125 -45.09375 L 38.5625 -45.09375 L 38.5625 -16.796875 C 38.5625 -13.890625 38.148438 -11.253906 37.328125 -8.890625 C 36.492188 -6.523438 35.289062 -4.492188 33.71875 -2.796875 C 33.382812 -2.460938 33.039062 -2.148438 32.6875 -1.859375 C 32.332031 -1.578125 31.976562 -1.289062 31.625 -1 C 31.28125 -0.707031 30.910156 -0.445312 30.515625 -0.21875 C 30.117188 0.0078125 29.710938 0.226562 29.296875 0.4375 C 28.878906 0.644531 28.441406 0.832031 27.984375 1 C 25.828125 1.863281 23.425781 2.296875 20.78125 2.296875 C 15.257812 2.296875 10.925781 0.660156 7.78125 -2.609375 C 4.664062 -5.890625 3.109375 -10.535156 3.109375 -16.546875 L 3.109375 -45.09375 L 12.125 -45.09375 L 12.125 -17.484375 C 12.125 -13.617188 12.875 -10.691406 14.375 -8.703125 C 15.03125 -7.796875 15.875 -7.113281 16.90625 -6.65625 C 17.945312 -6.195312 19.238281 -5.96875 20.78125 -5.96875 C 23.800781 -5.96875 26.019531 -6.878906 27.4375 -8.703125 C 28.96875 -10.648438 29.734375 -13.578125 29.734375 -17.484375 L 29.734375 -45.09375 Z M 31.28125 -45.09375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(170.836098, 55.95717)">
|
||||
<g>
|
||||
<path d="M 3.671875 -33.90625 C 4.492188 -33.90625 5.257812 -33.84375 5.96875 -33.71875 C 6.675781 -33.59375 7.347656 -33.394531 7.984375 -33.125 C 8.628906 -32.851562 9.222656 -32.507812 9.765625 -32.09375 C 10.304688 -31.675781 10.765625 -31.179688 11.140625 -30.609375 C 11.835938 -31.304688 12.644531 -31.925781 13.5625 -32.46875 C 13.8125 -32.632812 14.0625 -32.789062 14.3125 -32.9375 C 14.5625 -33.082031 14.828125 -33.21875 15.109375 -33.34375 C 15.398438 -33.46875 15.691406 -33.582031 15.984375 -33.6875 C 16.273438 -33.789062 16.566406 -33.90625 16.859375 -34.03125 C 17.523438 -34.238281 18.207031 -34.390625 18.90625 -34.484375 C 19.613281 -34.585938 20.34375 -34.640625 21.09375 -34.640625 C 23.164062 -34.640625 25.164062 -34.222656 27.09375 -33.390625 C 29.019531 -32.566406 30.707031 -31.367188 32.15625 -29.796875 C 33.613281 -28.222656 34.753906 -26.316406 35.578125 -24.078125 C 35.867188 -23.328125 36.109375 -22.535156 36.296875 -21.703125 C 36.484375 -20.878906 36.628906 -20.03125 36.734375 -19.15625 C 36.835938 -18.289062 36.890625 -17.398438 36.890625 -16.484375 C 36.890625 -13.742188 36.472656 -11.210938 35.640625 -8.890625 C 35.222656 -7.773438 34.734375 -6.71875 34.171875 -5.71875 C 33.617188 -4.726562 32.96875 -3.816406 32.21875 -2.984375 C 31.96875 -2.648438 31.6875 -2.335938 31.375 -2.046875 C 31.070312 -1.765625 30.753906 -1.476562 30.421875 -1.1875 C 30.085938 -0.894531 29.753906 -0.625 29.421875 -0.375 C 29.085938 -0.125 28.734375 0.101562 28.359375 0.3125 C 27.992188 0.519531 27.625 0.707031 27.25 0.875 C 26.289062 1.375 25.300781 1.734375 24.28125 1.953125 C 23.269531 2.179688 22.25 2.296875 21.21875 2.296875 C 19.019531 2.296875 17.003906 1.863281 15.171875 1 C 14.515625 0.707031 13.894531 0.375 13.3125 0 L 13.3125 15.234375 L 4.609375 15.234375 L 4.609375 -24.078125 C 4.609375 -25.023438 4.441406 -25.644531 4.109375 -25.9375 C 3.734375 -26.269531 3.191406 -26.4375 2.484375 -26.4375 L 0.4375 -26.4375 L 1 -28.421875 L 2.171875 -32.78125 L 2.484375 -33.90625 Z M 28.046875 -16.296875 C 28.046875 -17.171875 27.984375 -17.976562 27.859375 -18.71875 C 27.742188 -19.46875 27.570312 -20.160156 27.34375 -20.796875 C 27.113281 -21.441406 26.851562 -22.035156 26.5625 -22.578125 C 26.269531 -23.117188 25.9375 -23.617188 25.5625 -24.078125 C 24.820312 -24.941406 23.960938 -25.59375 22.984375 -26.03125 C 22.003906 -26.46875 20.957031 -26.6875 19.84375 -26.6875 C 19.09375 -26.6875 18.382812 -26.59375 17.71875 -26.40625 C 17.0625 -26.21875 16.441406 -25.9375 15.859375 -25.5625 C 14.785156 -24.894531 13.9375 -24.191406 13.3125 -23.453125 L 13.3125 -8.703125 C 14.1875 -7.921875 15.140625 -7.257812 16.171875 -6.71875 C 17.335938 -6.09375 18.644531 -5.78125 20.09375 -5.78125 C 21.25 -5.78125 22.304688 -6.007812 23.265625 -6.46875 C 24.210938 -6.96875 25.039062 -7.648438 25.75 -8.515625 C 26.125 -8.972656 26.445312 -9.472656 26.71875 -10.015625 C 26.988281 -10.554688 27.226562 -11.15625 27.4375 -11.8125 C 27.644531 -12.476562 27.796875 -13.179688 27.890625 -13.921875 C 27.992188 -14.671875 28.046875 -15.460938 28.046875 -16.296875 Z M 28.046875 -16.296875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(208.963656, 55.95717)">
|
||||
<g>
|
||||
<path d="M 7.53125 -42.921875 L 13.375 -42.921875 L 13.375 -32.71875 L 22.328125 -32.71875 L 22.328125 -24.75 L 13.375 -24.75 L 13.375 -10.703125 C 13.375 -9.660156 13.476562 -8.8125 13.6875 -8.15625 C 13.894531 -7.53125 14.144531 -7.050781 14.4375 -6.71875 C 14.71875 -6.382812 15.066406 -6.15625 15.484375 -6.03125 C 15.734375 -5.945312 15.984375 -5.882812 16.234375 -5.84375 C 16.484375 -5.800781 16.734375 -5.78125 16.984375 -5.78125 C 17.847656 -5.78125 18.632812 -5.90625 19.34375 -6.15625 C 20.21875 -6.488281 21.023438 -6.863281 21.765625 -7.28125 L 23.390625 -8.15625 L 24.015625 -6.40625 L 25.5625 -1.921875 L 25.9375 -0.75 L 24.875 -0.125 C 23.96875 0.457031 22.75 1 21.21875 1.5 C 20.425781 1.78125 19.613281 1.984375 18.78125 2.109375 C 17.957031 2.234375 17.128906 2.296875 16.296875 2.296875 C 12.734375 2.296875 9.894531 1.113281 7.78125 -1.25 C 6.695312 -2.445312 5.90625 -3.875 5.40625 -5.53125 C 4.914062 -7.195312 4.671875 -9.066406 4.671875 -11.140625 L 4.671875 -24.75 L -0.9375 -24.75 L -0.9375 -32.71875 L 4.96875 -32.71875 L 5.96875 -41.546875 L 6.15625 -42.921875 Z M 7.53125 -42.921875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(233.656391, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.578125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 12.125 -33.90625 L 12.125 1.5625 Z M 7.78125 -36.328125 C 6.28125 -36.328125 4.988281 -36.863281 3.90625 -37.9375 C 2.832031 -39.019531 2.296875 -40.304688 2.296875 -41.796875 C 2.296875 -43.410156 2.800781 -44.726562 3.8125 -45.75 C 4.832031 -46.769531 6.15625 -47.28125 7.78125 -47.28125 C 8.519531 -47.28125 9.222656 -47.144531 9.890625 -46.875 C 10.554688 -46.601562 11.144531 -46.207031 11.65625 -45.6875 C 12.175781 -45.164062 12.570312 -44.570312 12.84375 -43.90625 C 13.113281 -43.25 13.25 -42.546875 13.25 -41.796875 C 13.25 -40.179688 12.738281 -38.863281 11.71875 -37.84375 C 10.707031 -36.832031 9.394531 -36.328125 7.78125 -36.328125 Z M 7.78125 -36.328125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(249.205965, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.109375 C 12.269531 -31.390625 12.59375 -31.65625 12.90625 -31.90625 C 13.21875 -32.15625 13.5625 -32.382812 13.9375 -32.59375 C 14.4375 -32.925781 14.953125 -33.226562 15.484375 -33.5 C 16.023438 -33.769531 16.59375 -33.984375 17.1875 -34.140625 C 17.789062 -34.304688 18.394531 -34.429688 19 -34.515625 C 19.601562 -34.597656 20.238281 -34.640625 20.90625 -34.640625 C 23.800781 -34.640625 26.203125 -33.914062 28.109375 -32.46875 C 29.273438 -31.59375 30.234375 -30.515625 30.984375 -29.234375 C 32.015625 -30.398438 33.113281 -31.4375 34.28125 -32.34375 C 34.6875 -32.632812 35.117188 -32.90625 35.578125 -33.15625 C 36.035156 -33.40625 36.507812 -33.625 37 -33.8125 C 37.5 -34 38.007812 -34.148438 38.53125 -34.265625 C 39.050781 -34.390625 39.578125 -34.484375 40.109375 -34.546875 C 40.648438 -34.609375 41.210938 -34.640625 41.796875 -34.640625 C 42.046875 -34.640625 42.285156 -34.640625 42.515625 -34.640625 C 42.742188 -34.640625 42.972656 -34.628906 43.203125 -34.609375 C 43.429688 -34.585938 43.65625 -34.554688 43.875 -34.515625 C 44.101562 -34.472656 44.320312 -34.429688 44.53125 -34.390625 C 44.738281 -34.359375 44.945312 -34.320312 45.15625 -34.28125 C 45.363281 -34.238281 45.570312 -34.1875 45.78125 -34.125 C 45.988281 -34.0625 46.195312 -33.988281 46.40625 -33.90625 C 46.613281 -33.820312 46.820312 -33.738281 47.03125 -33.65625 C 47.394531 -33.488281 47.753906 -33.300781 48.109375 -33.09375 C 48.460938 -32.882812 48.796875 -32.65625 49.109375 -32.40625 C 49.421875 -32.15625 49.722656 -31.894531 50.015625 -31.625 C 50.304688 -31.351562 50.570312 -31.054688 50.8125 -30.734375 C 51.851562 -29.523438 52.601562 -28.09375 53.0625 -26.4375 C 53.5625 -24.820312 53.8125 -23.039062 53.8125 -21.09375 L 53.8125 1.5625 L 45.09375 1.5625 L 45.09375 -20.65625 C 45.09375 -22.5625 44.679688 -24.070312 43.859375 -25.1875 C 43.109375 -26.144531 42.09375 -26.625 40.8125 -26.625 C 40.0625 -26.625 39.351562 -26.476562 38.6875 -26.1875 C 37.945312 -25.851562 37.242188 -25.414062 36.578125 -24.875 C 35.867188 -24.300781 35.179688 -23.640625 34.515625 -22.890625 C 34.273438 -22.554688 34.007812 -22.203125 33.71875 -21.828125 C 33.425781 -21.460938 33.175781 -21.113281 32.96875 -20.78125 L 32.96875 1.5625 L 24.265625 1.5625 L 24.265625 -20.65625 C 24.265625 -22.5625 23.847656 -24.070312 23.015625 -25.1875 C 22.265625 -26.144531 21.25 -26.625 19.96875 -26.625 C 19.257812 -26.625 18.554688 -26.476562 17.859375 -26.1875 C 17.523438 -26.0625 17.171875 -25.894531 16.796875 -25.6875 C 16.421875 -25.476562 16.066406 -25.226562 15.734375 -24.9375 C 15.609375 -24.851562 15.484375 -24.757812 15.359375 -24.65625 C 15.234375 -24.5625 15.117188 -24.460938 15.015625 -24.359375 C 14.910156 -24.253906 14.796875 -24.148438 14.671875 -24.046875 C 14.554688 -23.941406 14.445312 -23.835938 14.34375 -23.734375 C 14.238281 -23.628906 14.132812 -23.515625 14.03125 -23.390625 C 13.925781 -23.265625 13.8125 -23.140625 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.109375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(306.117405, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden">
|
||||
<a aria-label="Home" href="/">
|
||||
<svg class="h-6" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
<svg class="h-6 -ml-48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="500" zoomAndPan="magnify" viewBox="0 0 375 74.999997" height="100"
|
||||
preserveAspectRatio="xMidYMid meet" version="1.0">
|
||||
<defs>
|
||||
@@ -184,9 +101,9 @@
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="fixed inset-x-0 top-0 z-50 flex h-14 border-b border-slate-200 items-center justify-between gap-12 px-4 transition sm:px-6 lg:left-72 lg:z-30 lg:px-8 xl:left-80 backdrop-blur-sm lg:left-72 xl:left-80 bg-white/95 supports-[backdrop-filter]:bg-white/80"
|
||||
<div class="fixed inset-x-0 top-0 z-50 flex h-14 border-b-2 items-center justify-between gap-12 px-4 transition sm:px-6 lg:left-72 lg:z-30 lg:px-8 xl:left-80 backdrop-blur-sm lg:left-72 xl:left-80 bg-white/[var(--bg-opacity-light)] /[var(--bg-opacity-dark)]"
|
||||
style="--bg-opacity-light:0.5; ">
|
||||
<div class="absolute inset-x-0 top-full h-px transition bg-slate-900/5"></div>
|
||||
<div class="absolute inset-x-0 top-full h-px transition bg-zinc-900/7.5 "></div>
|
||||
<div class="hidden lg:block lg:max-w-md lg:flex-auto">
|
||||
<!-- <button type="button"
|
||||
class="hidden h-8 w-full items-center gap-2 rounded-full bg-white pl-2 pr-3 text-sm text-zinc-500 ring-1 ring-zinc-900/10 transition hover:ring-zinc-900/20 lg:flex focus:[&:not(:focus-visible)]:outline-none">
|
||||
@@ -199,117 +116,44 @@
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="flex items-center gap-5 lg:hidden">
|
||||
<button type="button" id="mobile-menu-toggle"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-lg transition hover:bg-slate-100"
|
||||
<button type="button"
|
||||
class="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 "
|
||||
aria-label="Toggle navigation">
|
||||
<svg id="menu-open-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"
|
||||
class="w-5 h-5 stroke-slate-700">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
<svg id="menu-close-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"
|
||||
class="w-5 h-5 stroke-slate-700 hidden">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
<svg viewBox="0 0 10 9" fill="none" stroke-linecap="round" aria-hidden="true"
|
||||
class="w-2.5 stroke-zinc-900 ">
|
||||
<path d="M.5 1h9M.5 8h9M.5 4.5h9"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<a aria-label="Home" href="/">
|
||||
<svg class="h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 75" preserveAspectRatio="xMidYMid meet">
|
||||
<defs>
|
||||
<g></g>
|
||||
<clipPath id="mobile-header-1d83d73318">
|
||||
<path d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
<clipPath id="mobile-header-32c1ad7ad2">
|
||||
<path d="M 366.480469 51.03125 L 352.816406 51.03125 C 352.253906 51.03125 351.792969 50.574219 351.792969 50.007812 L 351.792969 36.347656 C 351.792969 35.785156 352.253906 35.324219 352.816406 35.324219 L 366.480469 35.324219 C 367.042969 35.324219 367.5 35.785156 367.5 36.347656 L 367.5 50.007812 C 367.5 50.574219 367.042969 51.03125 366.480469 51.03125 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#mobile-header-1d83d73318)">
|
||||
<g clip-path="url(#mobile-header-32c1ad7ad2)">
|
||||
<path fill="#7ed957" d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " fill-opacity="1" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(11.173064, 55.95717)">
|
||||
<g>
|
||||
<path d="M 1.5625 -21.828125 C 1.5625 -25.191406 2.054688 -28.320312 3.046875 -31.21875 C 4.046875 -34.164062 5.5 -36.71875 7.40625 -38.875 C 9.3125 -41.070312 11.632812 -42.773438 14.375 -43.984375 C 15.738281 -44.597656 17.195312 -45.0625 18.75 -45.375 C 20.3125 -45.6875 21.941406 -45.84375 23.640625 -45.84375 C 25.335938 -45.84375 26.960938 -45.6875 28.515625 -45.375 C 30.078125 -45.0625 31.539062 -44.578125 32.90625 -43.921875 C 34.269531 -43.296875 35.53125 -42.546875 36.6875 -41.671875 C 37.851562 -40.804688 38.914062 -39.8125 39.875 -38.6875 C 41.78125 -36.53125 43.25 -34.003906 44.28125 -31.109375 C 45.28125 -28.203125 45.78125 -25.109375 45.78125 -21.828125 C 45.78125 -18.554688 45.28125 -15.46875 44.28125 -12.5625 C 43.957031 -11.613281 43.578125 -10.679688 43.140625 -9.765625 C 42.703125 -8.847656 42.210938 -7.984375 41.671875 -7.171875 C 41.140625 -6.367188 40.5625 -5.597656 39.9375 -4.859375 C 38.976562 -3.734375 37.90625 -2.734375 36.71875 -1.859375 C 35.539062 -0.992188 34.289062 -0.25 32.96875 0.375 C 30.226562 1.65625 27.113281 2.296875 23.625 2.296875 C 20.144531 2.296875 17.039062 1.65625 14.3125 0.375 C 13.644531 0.0390625 13 -0.3125 12.375 -0.6875 C 11.757812 -1.0625 11.160156 -1.460938 10.578125 -1.890625 C 9.992188 -2.328125 9.429688 -2.796875 8.890625 -3.296875 C 8.359375 -3.796875 7.863281 -4.316406 7.40625 -4.859375 C 5.5 -7.054688 4.046875 -9.625 3.046875 -12.5625 C 2.054688 -15.46875 1.5625 -18.554688 1.5625 -21.828125 Z M 10.703125 -21.828125 C 10.703125 -20.796875 10.773438 -19.769531 10.921875 -18.75 C 11.066406 -17.738281 11.28125 -16.734375 11.5625 -15.734375 C 11.894531 -14.785156 12.269531 -13.882812 12.6875 -13.03125 C 13.101562 -12.175781 13.601562 -11.378906 14.1875 -10.640625 C 14.351562 -10.390625 14.535156 -10.160156 14.734375 -9.953125 C 14.941406 -9.742188 15.148438 -9.535156 15.359375 -9.328125 C 15.566406 -9.117188 15.785156 -8.910156 16.015625 -8.703125 C 16.242188 -8.492188 16.484375 -8.304688 16.734375 -8.140625 C 16.984375 -7.984375 17.234375 -7.820312 17.484375 -7.65625 C 17.734375 -7.488281 17.976562 -7.320312 18.21875 -7.15625 C 19.800781 -6.320312 21.609375 -5.90625 23.640625 -5.90625 C 24.378906 -5.90625 25.082031 -5.945312 25.75 -6.03125 C 26.414062 -6.113281 27.035156 -6.25 27.609375 -6.4375 C 28.191406 -6.625 28.734375 -6.84375 29.234375 -7.09375 C 30.847656 -7.875 32.195312 -8.953125 33.28125 -10.328125 C 33.65625 -10.785156 33.984375 -11.269531 34.265625 -11.78125 C 34.554688 -12.300781 34.828125 -12.863281 35.078125 -13.46875 C 35.328125 -14.070312 35.554688 -14.679688 35.765625 -15.296875 C 36.347656 -17.328125 36.640625 -19.503906 36.640625 -21.828125 C 36.640625 -23.898438 36.328125 -25.894531 35.703125 -27.8125 C 35.117188 -29.71875 34.25 -31.414062 33.09375 -32.90625 C 31.96875 -34.351562 30.617188 -35.515625 29.046875 -36.390625 C 27.472656 -37.222656 25.671875 -37.640625 23.640625 -37.640625 C 23.140625 -37.640625 22.640625 -37.617188 22.140625 -37.578125 C 21.640625 -37.535156 21.160156 -37.460938 20.703125 -37.359375 C 20.253906 -37.253906 19.820312 -37.128906 19.40625 -36.984375 C 18.988281 -36.835938 18.59375 -36.660156 18.21875 -36.453125 C 17.394531 -36.078125 16.648438 -35.617188 15.984375 -35.078125 C 15.316406 -34.546875 14.71875 -33.925781 14.1875 -33.21875 C 13.0625 -31.8125 12.1875 -30.148438 11.5625 -28.234375 C 10.988281 -26.242188 10.703125 -24.109375 10.703125 -21.828125 Z M 10.703125 -21.828125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(58.505968, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.046875 C 12.269531 -31.367188 12.601562 -31.65625 12.9375 -31.90625 C 13.269531 -32.15625 13.625 -32.382812 14 -32.59375 C 16.070312 -33.957031 18.5 -34.640625 21.28125 -34.640625 C 23.3125 -34.640625 25.113281 -34.316406 26.6875 -33.671875 C 28.257812 -33.035156 29.585938 -32.078125 30.671875 -30.796875 C 32.703125 -28.304688 33.71875 -25.070312 33.71875 -21.09375 L 33.71875 1.5625 L 25 1.5625 L 25 -20.46875 C 25 -21.164062 24.945312 -21.804688 24.84375 -22.390625 C 24.738281 -22.972656 24.59375 -23.488281 24.40625 -23.9375 C 24.226562 -24.394531 23.992188 -24.789062 23.703125 -25.125 C 23.453125 -25.457031 23.171875 -25.738281 22.859375 -25.96875 C 22.546875 -26.195312 22.179688 -26.363281 21.765625 -26.46875 C 21.359375 -26.570312 20.882812 -26.625 20.34375 -26.625 C 19.59375 -26.625 18.84375 -26.476562 18.09375 -26.1875 C 17.3125 -25.894531 16.546875 -25.476562 15.796875 -24.9375 C 15.054688 -24.363281 14.351562 -23.722656 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.046875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(95.327364, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(129.163241, 55.95717)">
|
||||
<g>
|
||||
<path d="M 31.28125 -45.09375 L 38.5625 -45.09375 L 38.5625 -16.796875 C 38.5625 -13.890625 38.148438 -11.253906 37.328125 -8.890625 C 36.492188 -6.523438 35.289062 -4.492188 33.71875 -2.796875 C 33.382812 -2.460938 33.039062 -2.148438 32.6875 -1.859375 C 32.332031 -1.578125 31.976562 -1.289062 31.625 -1 C 31.28125 -0.707031 30.910156 -0.445312 30.515625 -0.21875 C 30.117188 0.0078125 29.710938 0.226562 29.296875 0.4375 C 28.878906 0.644531 28.441406 0.832031 27.984375 1 C 25.828125 1.863281 23.425781 2.296875 20.78125 2.296875 C 15.257812 2.296875 10.925781 0.660156 7.78125 -2.609375 C 4.664062 -5.890625 3.109375 -10.535156 3.109375 -16.546875 L 3.109375 -45.09375 L 12.125 -45.09375 L 12.125 -17.484375 C 12.125 -13.617188 12.875 -10.691406 14.375 -8.703125 C 15.03125 -7.796875 15.875 -7.113281 16.90625 -6.65625 C 17.945312 -6.195312 19.238281 -5.96875 20.78125 -5.96875 C 23.800781 -5.96875 26.019531 -6.878906 27.4375 -8.703125 C 28.96875 -10.648438 29.734375 -13.578125 29.734375 -17.484375 L 29.734375 -45.09375 Z M 31.28125 -45.09375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(170.836098, 55.95717)">
|
||||
<g>
|
||||
<path d="M 3.671875 -33.90625 C 4.492188 -33.90625 5.257812 -33.84375 5.96875 -33.71875 C 6.675781 -33.59375 7.347656 -33.394531 7.984375 -33.125 C 8.628906 -32.851562 9.222656 -32.507812 9.765625 -32.09375 C 10.304688 -31.675781 10.765625 -31.179688 11.140625 -30.609375 C 11.835938 -31.304688 12.644531 -31.925781 13.5625 -32.46875 C 13.8125 -32.632812 14.0625 -32.789062 14.3125 -32.9375 C 14.5625 -33.082031 14.828125 -33.21875 15.109375 -33.34375 C 15.398438 -33.46875 15.691406 -33.582031 15.984375 -33.6875 C 16.273438 -33.789062 16.566406 -33.90625 16.859375 -34.03125 C 17.523438 -34.238281 18.207031 -34.390625 18.90625 -34.484375 C 19.613281 -34.585938 20.34375 -34.640625 21.09375 -34.640625 C 23.164062 -34.640625 25.164062 -34.222656 27.09375 -33.390625 C 29.019531 -32.566406 30.707031 -31.367188 32.15625 -29.796875 C 33.613281 -28.222656 34.753906 -26.316406 35.578125 -24.078125 C 35.867188 -23.328125 36.109375 -22.535156 36.296875 -21.703125 C 36.484375 -20.878906 36.628906 -20.03125 36.734375 -19.15625 C 36.835938 -18.289062 36.890625 -17.398438 36.890625 -16.484375 C 36.890625 -13.742188 36.472656 -11.210938 35.640625 -8.890625 C 35.222656 -7.773438 34.734375 -6.71875 34.171875 -5.71875 C 33.617188 -4.726562 32.96875 -3.816406 32.21875 -2.984375 C 31.96875 -2.648438 31.6875 -2.335938 31.375 -2.046875 C 31.070312 -1.765625 30.753906 -1.476562 30.421875 -1.1875 C 30.085938 -0.894531 29.753906 -0.625 29.421875 -0.375 C 29.085938 -0.125 28.734375 0.101562 28.359375 0.3125 C 27.992188 0.519531 27.625 0.707031 27.25 0.875 C 26.289062 1.375 25.300781 1.734375 24.28125 1.953125 C 23.269531 2.179688 22.25 2.296875 21.21875 2.296875 C 19.019531 2.296875 17.003906 1.863281 15.171875 1 C 14.515625 0.707031 13.894531 0.375 13.3125 0 L 13.3125 15.234375 L 4.609375 15.234375 L 4.609375 -24.078125 C 4.609375 -25.023438 4.441406 -25.644531 4.109375 -25.9375 C 3.734375 -26.269531 3.191406 -26.4375 2.484375 -26.4375 L 0.4375 -26.4375 L 1 -28.421875 L 2.171875 -32.78125 L 2.484375 -33.90625 Z M 28.046875 -16.296875 C 28.046875 -17.171875 27.984375 -17.976562 27.859375 -18.71875 C 27.742188 -19.46875 27.570312 -20.160156 27.34375 -20.796875 C 27.113281 -21.441406 26.851562 -22.035156 26.5625 -22.578125 C 26.269531 -23.117188 25.9375 -23.617188 25.5625 -24.078125 C 24.820312 -24.941406 23.960938 -25.59375 22.984375 -26.03125 C 22.003906 -26.46875 20.957031 -26.6875 19.84375 -26.6875 C 19.09375 -26.6875 18.382812 -26.59375 17.71875 -26.40625 C 17.0625 -26.21875 16.441406 -25.9375 15.859375 -25.5625 C 14.785156 -24.894531 13.9375 -24.191406 13.3125 -23.453125 L 13.3125 -8.703125 C 14.1875 -7.921875 15.140625 -7.257812 16.171875 -6.71875 C 17.335938 -6.09375 18.644531 -5.78125 20.09375 -5.78125 C 21.25 -5.78125 22.304688 -6.007812 23.265625 -6.46875 C 24.210938 -6.96875 25.039062 -7.648438 25.75 -8.515625 C 26.125 -8.972656 26.445312 -9.472656 26.71875 -10.015625 C 26.988281 -10.554688 27.226562 -11.15625 27.4375 -11.8125 C 27.644531 -12.476562 27.796875 -13.179688 27.890625 -13.921875 C 27.992188 -14.671875 28.046875 -15.460938 28.046875 -16.296875 Z M 28.046875 -16.296875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(208.963656, 55.95717)">
|
||||
<g>
|
||||
<path d="M 7.53125 -42.921875 L 13.375 -42.921875 L 13.375 -32.71875 L 22.328125 -32.71875 L 22.328125 -24.75 L 13.375 -24.75 L 13.375 -10.703125 C 13.375 -9.660156 13.476562 -8.8125 13.6875 -8.15625 C 13.894531 -7.53125 14.144531 -7.050781 14.4375 -6.71875 C 14.71875 -6.382812 15.066406 -6.15625 15.484375 -6.03125 C 15.734375 -5.945312 15.984375 -5.882812 16.234375 -5.84375 C 16.484375 -5.800781 16.734375 -5.78125 16.984375 -5.78125 C 17.847656 -5.78125 18.632812 -5.90625 19.34375 -6.15625 C 20.21875 -6.488281 21.023438 -6.863281 21.765625 -7.28125 L 23.390625 -8.15625 L 24.015625 -6.40625 L 25.5625 -1.921875 L 25.9375 -0.75 L 24.875 -0.125 C 23.96875 0.457031 22.75 1 21.21875 1.5 C 20.425781 1.78125 19.613281 1.984375 18.78125 2.109375 C 17.957031 2.234375 17.128906 2.296875 16.296875 2.296875 C 12.734375 2.296875 9.894531 1.113281 7.78125 -1.25 C 6.695312 -2.445312 5.90625 -3.875 5.40625 -5.53125 C 4.914062 -7.195312 4.671875 -9.066406 4.671875 -11.140625 L 4.671875 -24.75 L -0.9375 -24.75 L -0.9375 -32.71875 L 4.96875 -32.71875 L 5.96875 -41.546875 L 6.15625 -42.921875 Z M 7.53125 -42.921875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(233.656391, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.578125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 12.125 -33.90625 L 12.125 1.5625 Z M 7.78125 -36.328125 C 6.28125 -36.328125 4.988281 -36.863281 3.90625 -37.9375 C 2.832031 -39.019531 2.296875 -40.304688 2.296875 -41.796875 C 2.296875 -43.410156 2.800781 -44.726562 3.8125 -45.75 C 4.832031 -46.769531 6.15625 -47.28125 7.78125 -47.28125 C 8.519531 -47.28125 9.222656 -47.144531 9.890625 -46.875 C 10.554688 -46.601562 11.144531 -46.207031 11.65625 -45.6875 C 12.175781 -45.164062 12.570312 -44.570312 12.84375 -43.90625 C 13.113281 -43.25 13.25 -42.546875 13.25 -41.796875 C 13.25 -40.179688 12.738281 -38.863281 11.71875 -37.84375 C 10.707031 -36.832031 9.394531 -36.328125 7.78125 -36.328125 Z M 7.78125 -36.328125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(249.205965, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.109375 C 12.269531 -31.390625 12.59375 -31.65625 12.90625 -31.90625 C 13.21875 -32.15625 13.5625 -32.382812 13.9375 -32.59375 C 14.4375 -32.925781 14.953125 -33.226562 15.484375 -33.5 C 16.023438 -33.769531 16.59375 -33.984375 17.1875 -34.140625 C 17.789062 -34.304688 18.394531 -34.429688 19 -34.515625 C 19.601562 -34.597656 20.238281 -34.640625 20.90625 -34.640625 C 23.800781 -34.640625 26.203125 -33.914062 28.109375 -32.46875 C 29.273438 -31.59375 30.234375 -30.515625 30.984375 -29.234375 C 32.015625 -30.398438 33.113281 -31.4375 34.28125 -32.34375 C 34.6875 -32.632812 35.117188 -32.90625 35.578125 -33.15625 C 36.035156 -33.40625 36.507812 -33.625 37 -33.8125 C 37.5 -34 38.007812 -34.148438 38.53125 -34.265625 C 39.050781 -34.390625 39.578125 -34.484375 40.109375 -34.546875 C 40.648438 -34.609375 41.210938 -34.640625 41.796875 -34.640625 C 42.046875 -34.640625 42.285156 -34.640625 42.515625 -34.640625 C 42.742188 -34.640625 42.972656 -34.628906 43.203125 -34.609375 C 43.429688 -34.585938 43.65625 -34.554688 43.875 -34.515625 C 44.101562 -34.472656 44.320312 -34.429688 44.53125 -34.390625 C 44.738281 -34.359375 44.945312 -34.320312 45.15625 -34.28125 C 45.363281 -34.238281 45.570312 -34.1875 45.78125 -34.125 C 45.988281 -34.0625 46.195312 -33.988281 46.40625 -33.90625 C 46.613281 -33.820312 46.820312 -33.738281 47.03125 -33.65625 C 47.394531 -33.488281 47.753906 -33.300781 48.109375 -33.09375 C 48.460938 -32.882812 48.796875 -32.65625 49.109375 -32.40625 C 49.421875 -32.15625 49.722656 -31.894531 50.015625 -31.625 C 50.304688 -31.351562 50.570312 -31.054688 50.8125 -30.734375 C 51.851562 -29.523438 52.601562 -28.09375 53.0625 -26.4375 C 53.5625 -24.820312 53.8125 -23.039062 53.8125 -21.09375 L 53.8125 1.5625 L 45.09375 1.5625 L 45.09375 -20.65625 C 45.09375 -22.5625 44.679688 -24.070312 43.859375 -25.1875 C 43.109375 -26.144531 42.09375 -26.625 40.8125 -26.625 C 40.0625 -26.625 39.351562 -26.476562 38.6875 -26.1875 C 37.945312 -25.851562 37.242188 -25.414062 36.578125 -24.875 C 35.867188 -24.300781 35.179688 -23.640625 34.515625 -22.890625 C 34.273438 -22.554688 34.007812 -22.203125 33.71875 -21.828125 C 33.425781 -21.460938 33.175781 -21.113281 32.96875 -20.78125 L 32.96875 1.5625 L 24.265625 1.5625 L 24.265625 -20.65625 C 24.265625 -22.5625 23.847656 -24.070312 23.015625 -25.1875 C 22.265625 -26.144531 21.25 -26.625 19.96875 -26.625 C 19.257812 -26.625 18.554688 -26.476562 17.859375 -26.1875 C 17.523438 -26.0625 17.171875 -25.894531 16.796875 -25.6875 C 16.421875 -25.476562 16.066406 -25.226562 15.734375 -24.9375 C 15.609375 -24.851562 15.484375 -24.757812 15.359375 -24.65625 C 15.234375 -24.5625 15.117188 -24.460938 15.015625 -24.359375 C 14.910156 -24.253906 14.796875 -24.148438 14.671875 -24.046875 C 14.554688 -23.941406 14.445312 -23.835938 14.34375 -23.734375 C 14.238281 -23.628906 14.132812 -23.515625 14.03125 -23.390625 C 13.925781 -23.265625 13.8125 -23.140625 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.109375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(306.117405, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<svg viewBox="0 0 99 24" aria-hidden="true" class="h-6">
|
||||
<path class="fill-emerald-400"
|
||||
d="M16 8a5 5 0 0 0-5-5H5a5 5 0 0 0-5 5v13.927a1 1 0 0 0 1.623.782l3.684-2.93a4 4 0 0 1 2.49-.87H11a5 5 0 0 0 5-5V8Z">
|
||||
</path>
|
||||
<path class="fill-zinc-900 "
|
||||
d="M26.538 18h2.654v-3.999h2.576c2.672 0 4.456-1.723 4.456-4.333V9.65c0-2.61-1.784-4.333-4.456-4.333h-5.23V18Zm4.58-10.582c1.52 0 2.416.8 2.416 2.241v.018c0 1.441-.896 2.25-2.417 2.25h-1.925V7.418h1.925ZM38.051 18h2.566v-5.414c0-1.371.923-2.206 2.382-2.206.396 0 .791.061 1.178.15V8.287a3.843 3.843 0 0 0-.958-.123c-1.257 0-2.136.615-2.443 1.661h-.159V8.323h-2.566V18Zm11.55.202c2.979 0 4.772-1.88 4.772-5.036v-.018c0-3.128-1.82-5.036-4.773-5.036-2.953 0-4.772 1.916-4.772 5.036v.018c0 3.146 1.793 5.036 4.772 5.036Zm0-2.013c-1.372 0-2.145-1.116-2.145-3.023v-.018c0-1.89.782-3.023 2.144-3.023 1.354 0 2.145 1.134 2.145 3.023v.018c0 1.907-.782 3.023-2.145 3.023Zm10.52 1.846c.492 0 .967-.053 1.283-.114v-1.907a6.057 6.057 0 0 1-.755.044c-.87 0-1.24-.387-1.24-1.257v-4.544h1.995V8.323H59.41V6.012h-2.592v2.311h-1.495v1.934h1.495v5.133c0 1.88.949 2.645 3.304 2.645Zm7.287.167c2.98 0 4.772-1.88 4.772-5.036v-.018c0-3.128-1.82-5.036-4.772-5.036-2.954 0-4.773 1.916-4.773 5.036v.018c0 3.146 1.793 5.036 4.773 5.036Zm0-2.013c-1.372 0-2.145-1.116-2.145-3.023v-.018c0-1.89.782-3.023 2.145-3.023 1.353 0 2.144 1.134 2.144 3.023v.018c0 1.907-.782 3.023-2.144 3.023Zm10.767 2.013c2.522 0 4.034-1.353 4.297-3.463l.01-.053h-2.374l-.017.036c-.229.966-.853 1.467-1.908 1.467-1.37 0-2.135-1.08-2.135-3.04v-.018c0-1.934.755-3.006 2.135-3.006 1.099 0 1.74.615 1.908 1.556l.008.017h2.391v-.026c-.228-2.162-1.749-3.56-4.315-3.56-3.033 0-4.738 1.837-4.738 5.019v.017c0 3.217 1.714 5.054 4.738 5.054Zm10.257 0c2.98 0 4.772-1.88 4.772-5.036v-.018c0-3.128-1.82-5.036-4.772-5.036-2.953 0-4.773 1.916-4.773 5.036v.018c0 3.146 1.793 5.036 4.773 5.036Zm0-2.013c-1.371 0-2.145-1.116-2.145-3.023v-.018c0-1.89.782-3.023 2.145-3.023 1.353 0 2.144 1.134 2.144 3.023v.018c0 1.907-.782 3.023-2.144 3.023ZM95.025 18h2.566V4.623h-2.566V18Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-5">
|
||||
<nav class="hidden md:block">
|
||||
<ul role="list" class="flex items-center gap-6">
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
<ul role="list" class="flex items-center gap-8">
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="/">Home</a></li>
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
target="_blank"
|
||||
href="https://join.slack.com/t/oneuptimesupport/shared_invite/zt-2pz5p1uhe-Fpmc7bv5ZE5xRMe7qJnwmA">Slack</a></li>
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
href="https://join.slack.com/t/oneuptimesupport/shared_invite/zt-2pz5p1uhe-Fpmc7bv5ZE5xRMe7qJnwmA">Chat
|
||||
with us on Slack</a></li>
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="/support">Support</a></li>
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
href="/reference/openapi" type="_blank">OpenAPI</a></li>
|
||||
<li><a class="text-sm font-medium text-slate-600 transition hover:text-slate-900"
|
||||
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="/reference/openapi" type="_blank">OpenAPI Spec</a></li>
|
||||
<li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="https://github.com/oneuptime/oneuptime">GitHub</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="hidden md:block md:h-5 md:w-px md:bg-slate-200"></div>
|
||||
<div class="hidden md:block md:h-5 md:w-px md:bg-zinc-900/10 md:"></div>
|
||||
<div class="flex gap-4">
|
||||
<div class="contents lg:hidden">
|
||||
<button type="button"
|
||||
@@ -340,372 +184,87 @@
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="hidden min-[416px]:contents"><a
|
||||
class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-indigo-600 py-1.5 px-4 text-white hover:bg-indigo-700 shadow-sm"
|
||||
class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 "
|
||||
href="/dashboard">Sign in</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="hidden lg:block">
|
||||
<div class="mb-6">
|
||||
<span class="inline-flex items-center rounded-full bg-indigo-50 px-3 py-1 text-xs font-semibold text-indigo-700 ring-1 ring-inset ring-indigo-600/20">
|
||||
API Reference
|
||||
</span>
|
||||
</div>
|
||||
<ul role="list" class="space-y-6">
|
||||
<nav class="hidden lg:mt-10 lg:block">
|
||||
<ul role="list">
|
||||
<li class="md:hidden"><a
|
||||
class="block py-1 text-sm text-slate-600 transition hover:text-slate-900"
|
||||
class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="/">API</a></li>
|
||||
<li class="md:hidden"><a
|
||||
class="block py-1 text-sm text-slate-600 transition hover:text-slate-900"
|
||||
class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="/#">Documentation</a></li>
|
||||
<li class="md:hidden"><a
|
||||
class="block py-1 text-sm text-slate-600 transition hover:text-slate-900"
|
||||
class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 "
|
||||
href="/#">Support</a></li>
|
||||
<li class="relative md:mt-0">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide">Guides</h6>
|
||||
<li class="relative mt-6 md:mt-0">
|
||||
<h6 class="text-sm font-semibold text-zinc-900 ">Guides</h6>
|
||||
<div class="relative mt-3 pl-2">
|
||||
<div class="absolute inset-y-0 left-2 w-px bg-slate-200"></div>
|
||||
<ul role="list" class="border-l border-transparent space-y-1">
|
||||
<div class="absolute inset-x-0 top-0 bg-zinc-800/2.5 will-change-transform "
|
||||
data-projection-id="32"
|
||||
style="height: 64px; top: 0px; border-radius: 8px; opacity: 1; transform: none; transform-origin: 50% 50% 0px;">
|
||||
</div>
|
||||
<div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 "
|
||||
style="transform: none; transform-origin: 50% 50% 0px;"></div>
|
||||
<div class="absolute left-2 h-6 w-px bg-emerald-500" data-projection-id="33"
|
||||
style="top: 4px; opacity: 1;"></div>
|
||||
<ul role="list" class="border-l border-transparent">
|
||||
<li class="relative">
|
||||
<a class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/introduction"><span
|
||||
<a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-900 "
|
||||
href="/reference/introduction" aria-current="page"><span
|
||||
class="truncate">Introduction</span></a>
|
||||
|
||||
</li>
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
|
||||
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
href="/reference/authentication"><span class="truncate">Authentication</span></a></li>
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
href="/reference/pagination"><span class="truncate">Pagination</span></a></li>
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
href="/reference/permissions"><span class="truncate">Permissions</span></a></li>
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
href="/reference/data-types"><span class="truncate">Data Types</span></a></li>
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
href="/reference/errors"><span class="truncate">Errors</span></a></li>
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
href="/reference/openapi"><span class="truncate">OpenAPI Spec</span></a></li>
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="relative mt-6">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide">Resources</h6>
|
||||
<h6 class="text-sm font-semibold text-zinc-900 "
|
||||
style="transform: none; transform-origin: 50% 50% 0px;">Resources</h6>
|
||||
<div class="relative mt-3 pl-2">
|
||||
<div class="absolute inset-y-0 left-2 w-px bg-slate-200"></div>
|
||||
<ul role="list" class="border-l border-transparent space-y-1">
|
||||
<div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 "
|
||||
style="transform: none; transform-origin: 50% 50% 0px;"></div>
|
||||
<ul role="list" class="border-l border-transparent">
|
||||
<% for(var i=0; i<resources.length; i++) {%>
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
<li class="relative" style="transform: none; transform-origin: 50% 50% 0px;"><a
|
||||
class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 "
|
||||
href="/reference/<%= resources[i].path -%>"><span class="truncate">
|
||||
<%= resources[i].name -%>
|
||||
</span></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<% if (typeof dataTypes !== 'undefined' && dataTypes && dataTypes.length > 0) { %>
|
||||
<%
|
||||
// Group data types by category
|
||||
var _dtGeneral = [];
|
||||
var _dtCategories = {};
|
||||
var _dtCatOrder = [];
|
||||
for (var _di = 0; _di < dataTypes.length; _di++) {
|
||||
var _dt = dataTypes[_di];
|
||||
if (_dt.category) {
|
||||
if (!_dtCategories[_dt.category]) {
|
||||
_dtCategories[_dt.category] = [];
|
||||
_dtCatOrder.push(_dt.category);
|
||||
}
|
||||
_dtCategories[_dt.category].push(_dt);
|
||||
} else {
|
||||
_dtGeneral.push(_dt);
|
||||
}
|
||||
}
|
||||
%>
|
||||
<li class="relative mt-6">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide">Data Types</h6>
|
||||
<div class="relative mt-3 pl-2">
|
||||
<div class="absolute inset-y-0 left-2 w-px bg-slate-200"></div>
|
||||
<ul role="list" class="border-l border-transparent space-y-1">
|
||||
<% for(var i=0; i<_dtGeneral.length; i++) {%>
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/<%= _dtGeneral[i].path -%>"><span class="truncate"><%= _dtGeneral[i].name -%></span></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% for (var _ci = 0; _ci < _dtCatOrder.length; _ci++) { %>
|
||||
<% var _catName = _dtCatOrder[_ci]; var _catTypes = _dtCategories[_catName]; %>
|
||||
<div class="mt-3 mb-1 pl-4">
|
||||
<span class="text-[10px] font-semibold text-slate-400 uppercase tracking-wider"><%= _catName %></span>
|
||||
</div>
|
||||
<ul role="list" class="border-l border-transparent space-y-1">
|
||||
<% for(var _ti=0; _ti<_catTypes.length; _ti++) {%>
|
||||
<li class="relative"><a
|
||||
class="nav-link flex justify-between gap-2 py-1.5 pr-3 text-sm transition pl-4 text-slate-600 hover:text-slate-900 -ml-px border-l-2 border-transparent hover:border-slate-300"
|
||||
href="/reference/<%= _catTypes[_ti].path -%>"><span class="truncate"><%= _catTypes[_ti].name -%></span></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% } %>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<% } %>
|
||||
<li class="sticky bottom-0 z-10 mt-6 min-[416px]:hidden"><a
|
||||
class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-indigo-600 py-1.5 px-4 text-white hover:bg-indigo-700 shadow-sm w-full"
|
||||
class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 :bg-emerald-400 w-full"
|
||||
href="/#">Sign in</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Mobile Navigation Overlay -->
|
||||
<div id="mobile-menu-overlay" class="fixed inset-0 z-50 bg-slate-900/50 backdrop-blur-sm hidden lg:hidden" aria-hidden="true"></div>
|
||||
|
||||
<!-- Mobile Navigation Panel -->
|
||||
<div id="mobile-menu-panel" class="fixed inset-y-0 left-0 z-50 w-80 max-w-[calc(100%-3rem)] bg-white shadow-2xl transform -translate-x-full transition-transform duration-300 ease-in-out lg:hidden overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<a aria-label="Home" href="/">
|
||||
<svg class="h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 75" preserveAspectRatio="xMidYMid meet">
|
||||
<defs>
|
||||
<g></g>
|
||||
<clipPath id="mobile-menu-1d83d73318">
|
||||
<path d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
<clipPath id="mobile-menu-32c1ad7ad2">
|
||||
<path d="M 366.480469 51.03125 L 352.816406 51.03125 C 352.253906 51.03125 351.792969 50.574219 351.792969 50.007812 L 351.792969 36.347656 C 351.792969 35.785156 352.253906 35.324219 352.816406 35.324219 L 366.480469 35.324219 C 367.042969 35.324219 367.5 35.785156 367.5 36.347656 L 367.5 50.007812 C 367.5 50.574219 367.042969 51.03125 366.480469 51.03125 " clip-rule="nonzero"></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#mobile-menu-1d83d73318)">
|
||||
<g clip-path="url(#mobile-menu-32c1ad7ad2)">
|
||||
<path fill="#7ed957" d="M 351.792969 35.324219 L 367.53125 35.324219 L 367.53125 51.0625 L 351.792969 51.0625 Z M 351.792969 35.324219 " fill-opacity="1" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(11.173064, 55.95717)">
|
||||
<g>
|
||||
<path d="M 1.5625 -21.828125 C 1.5625 -25.191406 2.054688 -28.320312 3.046875 -31.21875 C 4.046875 -34.164062 5.5 -36.71875 7.40625 -38.875 C 9.3125 -41.070312 11.632812 -42.773438 14.375 -43.984375 C 15.738281 -44.597656 17.195312 -45.0625 18.75 -45.375 C 20.3125 -45.6875 21.941406 -45.84375 23.640625 -45.84375 C 25.335938 -45.84375 26.960938 -45.6875 28.515625 -45.375 C 30.078125 -45.0625 31.539062 -44.578125 32.90625 -43.921875 C 34.269531 -43.296875 35.53125 -42.546875 36.6875 -41.671875 C 37.851562 -40.804688 38.914062 -39.8125 39.875 -38.6875 C 41.78125 -36.53125 43.25 -34.003906 44.28125 -31.109375 C 45.28125 -28.203125 45.78125 -25.109375 45.78125 -21.828125 C 45.78125 -18.554688 45.28125 -15.46875 44.28125 -12.5625 C 43.957031 -11.613281 43.578125 -10.679688 43.140625 -9.765625 C 42.703125 -8.847656 42.210938 -7.984375 41.671875 -7.171875 C 41.140625 -6.367188 40.5625 -5.597656 39.9375 -4.859375 C 38.976562 -3.734375 37.90625 -2.734375 36.71875 -1.859375 C 35.539062 -0.992188 34.289062 -0.25 32.96875 0.375 C 30.226562 1.65625 27.113281 2.296875 23.625 2.296875 C 20.144531 2.296875 17.039062 1.65625 14.3125 0.375 C 13.644531 0.0390625 13 -0.3125 12.375 -0.6875 C 11.757812 -1.0625 11.160156 -1.460938 10.578125 -1.890625 C 9.992188 -2.328125 9.429688 -2.796875 8.890625 -3.296875 C 8.359375 -3.796875 7.863281 -4.316406 7.40625 -4.859375 C 5.5 -7.054688 4.046875 -9.625 3.046875 -12.5625 C 2.054688 -15.46875 1.5625 -18.554688 1.5625 -21.828125 Z M 10.703125 -21.828125 C 10.703125 -20.796875 10.773438 -19.769531 10.921875 -18.75 C 11.066406 -17.738281 11.28125 -16.734375 11.5625 -15.734375 C 11.894531 -14.785156 12.269531 -13.882812 12.6875 -13.03125 C 13.101562 -12.175781 13.601562 -11.378906 14.1875 -10.640625 C 14.351562 -10.390625 14.535156 -10.160156 14.734375 -9.953125 C 14.941406 -9.742188 15.148438 -9.535156 15.359375 -9.328125 C 15.566406 -9.117188 15.785156 -8.910156 16.015625 -8.703125 C 16.242188 -8.492188 16.484375 -8.304688 16.734375 -8.140625 C 16.984375 -7.984375 17.234375 -7.820312 17.484375 -7.65625 C 17.734375 -7.488281 17.976562 -7.320312 18.21875 -7.15625 C 19.800781 -6.320312 21.609375 -5.90625 23.640625 -5.90625 C 24.378906 -5.90625 25.082031 -5.945312 25.75 -6.03125 C 26.414062 -6.113281 27.035156 -6.25 27.609375 -6.4375 C 28.191406 -6.625 28.734375 -6.84375 29.234375 -7.09375 C 30.847656 -7.875 32.195312 -8.953125 33.28125 -10.328125 C 33.65625 -10.785156 33.984375 -11.269531 34.265625 -11.78125 C 34.554688 -12.300781 34.828125 -12.863281 35.078125 -13.46875 C 35.328125 -14.070312 35.554688 -14.679688 35.765625 -15.296875 C 36.347656 -17.328125 36.640625 -19.503906 36.640625 -21.828125 C 36.640625 -23.898438 36.328125 -25.894531 35.703125 -27.8125 C 35.117188 -29.71875 34.25 -31.414062 33.09375 -32.90625 C 31.96875 -34.351562 30.617188 -35.515625 29.046875 -36.390625 C 27.472656 -37.222656 25.671875 -37.640625 23.640625 -37.640625 C 23.140625 -37.640625 22.640625 -37.617188 22.140625 -37.578125 C 21.640625 -37.535156 21.160156 -37.460938 20.703125 -37.359375 C 20.253906 -37.253906 19.820312 -37.128906 19.40625 -36.984375 C 18.988281 -36.835938 18.59375 -36.660156 18.21875 -36.453125 C 17.394531 -36.078125 16.648438 -35.617188 15.984375 -35.078125 C 15.316406 -34.546875 14.71875 -33.925781 14.1875 -33.21875 C 13.0625 -31.8125 12.1875 -30.148438 11.5625 -28.234375 C 10.988281 -26.242188 10.703125 -24.109375 10.703125 -21.828125 Z M 10.703125 -21.828125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(58.505968, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.046875 C 12.269531 -31.367188 12.601562 -31.65625 12.9375 -31.90625 C 13.269531 -32.15625 13.625 -32.382812 14 -32.59375 C 16.070312 -33.957031 18.5 -34.640625 21.28125 -34.640625 C 23.3125 -34.640625 25.113281 -34.316406 26.6875 -33.671875 C 28.257812 -33.035156 29.585938 -32.078125 30.671875 -30.796875 C 32.703125 -28.304688 33.71875 -25.070312 33.71875 -21.09375 L 33.71875 1.5625 L 25 1.5625 L 25 -20.46875 C 25 -21.164062 24.945312 -21.804688 24.84375 -22.390625 C 24.738281 -22.972656 24.59375 -23.488281 24.40625 -23.9375 C 24.226562 -24.394531 23.992188 -24.789062 23.703125 -25.125 C 23.453125 -25.457031 23.171875 -25.738281 22.859375 -25.96875 C 22.546875 -26.195312 22.179688 -26.363281 21.765625 -26.46875 C 21.359375 -26.570312 20.882812 -26.625 20.34375 -26.625 C 19.59375 -26.625 18.84375 -26.476562 18.09375 -26.1875 C 17.3125 -25.894531 16.546875 -25.476562 15.796875 -24.9375 C 15.054688 -24.363281 14.351562 -23.722656 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.046875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(95.327364, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(129.163241, 55.95717)">
|
||||
<g>
|
||||
<path d="M 31.28125 -45.09375 L 38.5625 -45.09375 L 38.5625 -16.796875 C 38.5625 -13.890625 38.148438 -11.253906 37.328125 -8.890625 C 36.492188 -6.523438 35.289062 -4.492188 33.71875 -2.796875 C 33.382812 -2.460938 33.039062 -2.148438 32.6875 -1.859375 C 32.332031 -1.578125 31.976562 -1.289062 31.625 -1 C 31.28125 -0.707031 30.910156 -0.445312 30.515625 -0.21875 C 30.117188 0.0078125 29.710938 0.226562 29.296875 0.4375 C 28.878906 0.644531 28.441406 0.832031 27.984375 1 C 25.828125 1.863281 23.425781 2.296875 20.78125 2.296875 C 15.257812 2.296875 10.925781 0.660156 7.78125 -2.609375 C 4.664062 -5.890625 3.109375 -10.535156 3.109375 -16.546875 L 3.109375 -45.09375 L 12.125 -45.09375 L 12.125 -17.484375 C 12.125 -13.617188 12.875 -10.691406 14.375 -8.703125 C 15.03125 -7.796875 15.875 -7.113281 16.90625 -6.65625 C 17.945312 -6.195312 19.238281 -5.96875 20.78125 -5.96875 C 23.800781 -5.96875 26.019531 -6.878906 27.4375 -8.703125 C 28.96875 -10.648438 29.734375 -13.578125 29.734375 -17.484375 L 29.734375 -45.09375 Z M 31.28125 -45.09375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(170.836098, 55.95717)">
|
||||
<g>
|
||||
<path d="M 3.671875 -33.90625 C 4.492188 -33.90625 5.257812 -33.84375 5.96875 -33.71875 C 6.675781 -33.59375 7.347656 -33.394531 7.984375 -33.125 C 8.628906 -32.851562 9.222656 -32.507812 9.765625 -32.09375 C 10.304688 -31.675781 10.765625 -31.179688 11.140625 -30.609375 C 11.835938 -31.304688 12.644531 -31.925781 13.5625 -32.46875 C 13.8125 -32.632812 14.0625 -32.789062 14.3125 -32.9375 C 14.5625 -33.082031 14.828125 -33.21875 15.109375 -33.34375 C 15.398438 -33.46875 15.691406 -33.582031 15.984375 -33.6875 C 16.273438 -33.789062 16.566406 -33.90625 16.859375 -34.03125 C 17.523438 -34.238281 18.207031 -34.390625 18.90625 -34.484375 C 19.613281 -34.585938 20.34375 -34.640625 21.09375 -34.640625 C 23.164062 -34.640625 25.164062 -34.222656 27.09375 -33.390625 C 29.019531 -32.566406 30.707031 -31.367188 32.15625 -29.796875 C 33.613281 -28.222656 34.753906 -26.316406 35.578125 -24.078125 C 35.867188 -23.328125 36.109375 -22.535156 36.296875 -21.703125 C 36.484375 -20.878906 36.628906 -20.03125 36.734375 -19.15625 C 36.835938 -18.289062 36.890625 -17.398438 36.890625 -16.484375 C 36.890625 -13.742188 36.472656 -11.210938 35.640625 -8.890625 C 35.222656 -7.773438 34.734375 -6.71875 34.171875 -5.71875 C 33.617188 -4.726562 32.96875 -3.816406 32.21875 -2.984375 C 31.96875 -2.648438 31.6875 -2.335938 31.375 -2.046875 C 31.070312 -1.765625 30.753906 -1.476562 30.421875 -1.1875 C 30.085938 -0.894531 29.753906 -0.625 29.421875 -0.375 C 29.085938 -0.125 28.734375 0.101562 28.359375 0.3125 C 27.992188 0.519531 27.625 0.707031 27.25 0.875 C 26.289062 1.375 25.300781 1.734375 24.28125 1.953125 C 23.269531 2.179688 22.25 2.296875 21.21875 2.296875 C 19.019531 2.296875 17.003906 1.863281 15.171875 1 C 14.515625 0.707031 13.894531 0.375 13.3125 0 L 13.3125 15.234375 L 4.609375 15.234375 L 4.609375 -24.078125 C 4.609375 -25.023438 4.441406 -25.644531 4.109375 -25.9375 C 3.734375 -26.269531 3.191406 -26.4375 2.484375 -26.4375 L 0.4375 -26.4375 L 1 -28.421875 L 2.171875 -32.78125 L 2.484375 -33.90625 Z M 28.046875 -16.296875 C 28.046875 -17.171875 27.984375 -17.976562 27.859375 -18.71875 C 27.742188 -19.46875 27.570312 -20.160156 27.34375 -20.796875 C 27.113281 -21.441406 26.851562 -22.035156 26.5625 -22.578125 C 26.269531 -23.117188 25.9375 -23.617188 25.5625 -24.078125 C 24.820312 -24.941406 23.960938 -25.59375 22.984375 -26.03125 C 22.003906 -26.46875 20.957031 -26.6875 19.84375 -26.6875 C 19.09375 -26.6875 18.382812 -26.59375 17.71875 -26.40625 C 17.0625 -26.21875 16.441406 -25.9375 15.859375 -25.5625 C 14.785156 -24.894531 13.9375 -24.191406 13.3125 -23.453125 L 13.3125 -8.703125 C 14.1875 -7.921875 15.140625 -7.257812 16.171875 -6.71875 C 17.335938 -6.09375 18.644531 -5.78125 20.09375 -5.78125 C 21.25 -5.78125 22.304688 -6.007812 23.265625 -6.46875 C 24.210938 -6.96875 25.039062 -7.648438 25.75 -8.515625 C 26.125 -8.972656 26.445312 -9.472656 26.71875 -10.015625 C 26.988281 -10.554688 27.226562 -11.15625 27.4375 -11.8125 C 27.644531 -12.476562 27.796875 -13.179688 27.890625 -13.921875 C 27.992188 -14.671875 28.046875 -15.460938 28.046875 -16.296875 Z M 28.046875 -16.296875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(208.963656, 55.95717)">
|
||||
<g>
|
||||
<path d="M 7.53125 -42.921875 L 13.375 -42.921875 L 13.375 -32.71875 L 22.328125 -32.71875 L 22.328125 -24.75 L 13.375 -24.75 L 13.375 -10.703125 C 13.375 -9.660156 13.476562 -8.8125 13.6875 -8.15625 C 13.894531 -7.53125 14.144531 -7.050781 14.4375 -6.71875 C 14.71875 -6.382812 15.066406 -6.15625 15.484375 -6.03125 C 15.734375 -5.945312 15.984375 -5.882812 16.234375 -5.84375 C 16.484375 -5.800781 16.734375 -5.78125 16.984375 -5.78125 C 17.847656 -5.78125 18.632812 -5.90625 19.34375 -6.15625 C 20.21875 -6.488281 21.023438 -6.863281 21.765625 -7.28125 L 23.390625 -8.15625 L 24.015625 -6.40625 L 25.5625 -1.921875 L 25.9375 -0.75 L 24.875 -0.125 C 23.96875 0.457031 22.75 1 21.21875 1.5 C 20.425781 1.78125 19.613281 1.984375 18.78125 2.109375 C 17.957031 2.234375 17.128906 2.296875 16.296875 2.296875 C 12.734375 2.296875 9.894531 1.113281 7.78125 -1.25 C 6.695312 -2.445312 5.90625 -3.875 5.40625 -5.53125 C 4.914062 -7.195312 4.671875 -9.066406 4.671875 -11.140625 L 4.671875 -24.75 L -0.9375 -24.75 L -0.9375 -32.71875 L 4.96875 -32.71875 L 5.96875 -41.546875 L 6.15625 -42.921875 Z M 7.53125 -42.921875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(233.656391, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.578125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 12.125 -33.90625 L 12.125 1.5625 Z M 7.78125 -36.328125 C 6.28125 -36.328125 4.988281 -36.863281 3.90625 -37.9375 C 2.832031 -39.019531 2.296875 -40.304688 2.296875 -41.796875 C 2.296875 -43.410156 2.800781 -44.726562 3.8125 -45.75 C 4.832031 -46.769531 6.15625 -47.28125 7.78125 -47.28125 C 8.519531 -47.28125 9.222656 -47.144531 9.890625 -46.875 C 10.554688 -46.601562 11.144531 -46.207031 11.65625 -45.6875 C 12.175781 -45.164062 12.570312 -44.570312 12.84375 -43.90625 C 13.113281 -43.25 13.25 -42.546875 13.25 -41.796875 C 13.25 -40.179688 12.738281 -38.863281 11.71875 -37.84375 C 10.707031 -36.832031 9.394531 -36.328125 7.78125 -36.328125 Z M 7.78125 -36.328125 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(249.205965, 55.95717)">
|
||||
<g>
|
||||
<path d="M 11.9375 -31.109375 C 12.269531 -31.390625 12.59375 -31.65625 12.90625 -31.90625 C 13.21875 -32.15625 13.5625 -32.382812 13.9375 -32.59375 C 14.4375 -32.925781 14.953125 -33.226562 15.484375 -33.5 C 16.023438 -33.769531 16.59375 -33.984375 17.1875 -34.140625 C 17.789062 -34.304688 18.394531 -34.429688 19 -34.515625 C 19.601562 -34.597656 20.238281 -34.640625 20.90625 -34.640625 C 23.800781 -34.640625 26.203125 -33.914062 28.109375 -32.46875 C 29.273438 -31.59375 30.234375 -30.515625 30.984375 -29.234375 C 32.015625 -30.398438 33.113281 -31.4375 34.28125 -32.34375 C 34.6875 -32.632812 35.117188 -32.90625 35.578125 -33.15625 C 36.035156 -33.40625 36.507812 -33.625 37 -33.8125 C 37.5 -34 38.007812 -34.148438 38.53125 -34.265625 C 39.050781 -34.390625 39.578125 -34.484375 40.109375 -34.546875 C 40.648438 -34.609375 41.210938 -34.640625 41.796875 -34.640625 C 42.046875 -34.640625 42.285156 -34.640625 42.515625 -34.640625 C 42.742188 -34.640625 42.972656 -34.628906 43.203125 -34.609375 C 43.429688 -34.585938 43.65625 -34.554688 43.875 -34.515625 C 44.101562 -34.472656 44.320312 -34.429688 44.53125 -34.390625 C 44.738281 -34.359375 44.945312 -34.320312 45.15625 -34.28125 C 45.363281 -34.238281 45.570312 -34.1875 45.78125 -34.125 C 45.988281 -34.0625 46.195312 -33.988281 46.40625 -33.90625 C 46.613281 -33.820312 46.820312 -33.738281 47.03125 -33.65625 C 47.394531 -33.488281 47.753906 -33.300781 48.109375 -33.09375 C 48.460938 -32.882812 48.796875 -32.65625 49.109375 -32.40625 C 49.421875 -32.15625 49.722656 -31.894531 50.015625 -31.625 C 50.304688 -31.351562 50.570312 -31.054688 50.8125 -30.734375 C 51.851562 -29.523438 52.601562 -28.09375 53.0625 -26.4375 C 53.5625 -24.820312 53.8125 -23.039062 53.8125 -21.09375 L 53.8125 1.5625 L 45.09375 1.5625 L 45.09375 -20.65625 C 45.09375 -22.5625 44.679688 -24.070312 43.859375 -25.1875 C 43.109375 -26.144531 42.09375 -26.625 40.8125 -26.625 C 40.0625 -26.625 39.351562 -26.476562 38.6875 -26.1875 C 37.945312 -25.851562 37.242188 -25.414062 36.578125 -24.875 C 35.867188 -24.300781 35.179688 -23.640625 34.515625 -22.890625 C 34.273438 -22.554688 34.007812 -22.203125 33.71875 -21.828125 C 33.425781 -21.460938 33.175781 -21.113281 32.96875 -20.78125 L 32.96875 1.5625 L 24.265625 1.5625 L 24.265625 -20.65625 C 24.265625 -22.5625 23.847656 -24.070312 23.015625 -25.1875 C 22.265625 -26.144531 21.25 -26.625 19.96875 -26.625 C 19.257812 -26.625 18.554688 -26.476562 17.859375 -26.1875 C 17.523438 -26.0625 17.171875 -25.894531 16.796875 -25.6875 C 16.421875 -25.476562 16.066406 -25.226562 15.734375 -24.9375 C 15.609375 -24.851562 15.484375 -24.757812 15.359375 -24.65625 C 15.234375 -24.5625 15.117188 -24.460938 15.015625 -24.359375 C 14.910156 -24.253906 14.796875 -24.148438 14.671875 -24.046875 C 14.554688 -23.941406 14.445312 -23.835938 14.34375 -23.734375 C 14.238281 -23.628906 14.132812 -23.515625 14.03125 -23.390625 C 13.925781 -23.265625 13.8125 -23.140625 13.6875 -23.015625 C 13.4375 -22.679688 13.164062 -22.335938 12.875 -21.984375 C 12.582031 -21.628906 12.332031 -21.289062 12.125 -20.96875 L 12.125 1.5625 L 3.421875 1.5625 L 3.421875 -33.90625 L 11.9375 -33.90625 Z M 11.9375 -31.109375 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#121212" fill-opacity="1">
|
||||
<g transform="translate(306.117405, 55.95717)">
|
||||
<g>
|
||||
<path d="M 10.390625 -12.3125 C 10.847656 -10.488281 11.632812 -9.019531 12.75 -7.90625 C 14.195312 -6.488281 16.144531 -5.78125 18.59375 -5.78125 C 19.175781 -5.78125 19.738281 -5.800781 20.28125 -5.84375 C 20.820312 -5.882812 21.335938 -5.945312 21.828125 -6.03125 C 22.328125 -6.113281 22.804688 -6.21875 23.265625 -6.34375 C 23.722656 -6.46875 24.175781 -6.601562 24.625 -6.75 C 25.082031 -6.894531 25.53125 -7.050781 25.96875 -7.21875 C 26.40625 -7.382812 26.851562 -7.570312 27.3125 -7.78125 L 28.921875 -8.515625 L 29.421875 -6.78125 L 30.734375 -1.984375 L 31.109375 -0.6875 L 29.921875 -0.125 C 28.304688 0.625 26.503906 1.207031 24.515625 1.625 C 23.515625 1.863281 22.460938 2.035156 21.359375 2.140625 C 20.265625 2.242188 19.117188 2.296875 17.921875 2.296875 C 15.390625 2.296875 13.085938 1.882812 11.015625 1.0625 C 8.941406 0.1875 7.175781 -1.054688 5.71875 -2.671875 C 4.269531 -4.242188 3.148438 -6.191406 2.359375 -8.515625 C 1.617188 -10.753906 1.25 -13.304688 1.25 -16.171875 C 1.25 -18.742188 1.617188 -21.148438 2.359375 -23.390625 C 2.773438 -24.503906 3.253906 -25.554688 3.796875 -26.546875 C 4.335938 -27.546875 4.957031 -28.460938 5.65625 -29.296875 C 7.070312 -30.953125 8.773438 -32.257812 10.765625 -33.21875 C 12.753906 -34.164062 14.972656 -34.640625 17.421875 -34.640625 C 18.242188 -34.640625 19.039062 -34.585938 19.8125 -34.484375 C 20.582031 -34.390625 21.328125 -34.25 22.046875 -34.0625 C 22.773438 -33.875 23.472656 -33.632812 24.140625 -33.34375 C 25.128906 -32.882812 26.023438 -32.351562 26.828125 -31.75 C 27.640625 -31.15625 28.378906 -30.460938 29.046875 -29.671875 C 30.335938 -28.140625 31.3125 -26.378906 31.96875 -24.390625 C 32.257812 -23.390625 32.488281 -22.359375 32.65625 -21.296875 C 32.820312 -20.242188 32.90625 -19.175781 32.90625 -18.09375 C 32.90625 -17.8125 32.90625 -17.523438 32.90625 -17.234375 C 32.90625 -16.941406 32.894531 -16.640625 32.875 -16.328125 C 32.851562 -16.015625 32.84375 -15.710938 32.84375 -15.421875 C 32.84375 -15.128906 32.832031 -14.847656 32.8125 -14.578125 C 32.789062 -14.316406 32.757812 -14.039062 32.71875 -13.75 L 32.59375 -12.3125 Z M 17.296875 -26.6875 C 15.304688 -26.6875 13.6875 -26.019531 12.4375 -24.6875 C 11.9375 -24.15625 11.5 -23.492188 11.125 -22.703125 C 10.757812 -21.910156 10.472656 -21 10.265625 -19.96875 L 24.5625 -19.96875 C 24.363281 -21.957031 23.742188 -23.507812 22.703125 -24.625 C 21.421875 -26 19.617188 -26.6875 17.296875 -26.6875 Z M 17.296875 -26.6875 "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
<button type="button" id="mobile-menu-close" class="flex h-8 w-8 items-center justify-center rounded-lg hover:bg-slate-100 transition">
|
||||
<svg class="w-5 h-5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<span class="inline-flex items-center rounded-full bg-indigo-50 px-3 py-1 text-xs font-semibold text-indigo-700 ring-1 ring-inset ring-indigo-600/20">
|
||||
API Reference
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<ul role="list" class="space-y-6">
|
||||
<li class="relative">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide mb-3">Guides</h6>
|
||||
<ul role="list" class="space-y-1 border-l border-slate-200 ml-2">
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/introduction">Introduction</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/authentication">Authentication</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/pagination">Pagination</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/permissions">Permissions</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/data-types">Data Types</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/errors">Errors</a></li>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition" href="/reference/openapi">OpenAPI Spec</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide mb-3">Resources</h6>
|
||||
<ul role="list" class="space-y-1 border-l border-slate-200 ml-2">
|
||||
<% for(var i=0; i<resources.length; i++) {%>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition truncate" href="/reference/<%= resources[i].path -%>"><%= resources[i].name -%></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</li>
|
||||
<% if (typeof dataTypes !== 'undefined' && dataTypes && dataTypes.length > 0) { %>
|
||||
<%
|
||||
// Group data types by category for mobile nav
|
||||
var _mGeneral = [];
|
||||
var _mCategories = {};
|
||||
var _mCatOrder = [];
|
||||
for (var _mi = 0; _mi < dataTypes.length; _mi++) {
|
||||
var _mdt = dataTypes[_mi];
|
||||
if (_mdt.category) {
|
||||
if (!_mCategories[_mdt.category]) {
|
||||
_mCategories[_mdt.category] = [];
|
||||
_mCatOrder.push(_mdt.category);
|
||||
}
|
||||
_mCategories[_mdt.category].push(_mdt);
|
||||
} else {
|
||||
_mGeneral.push(_mdt);
|
||||
}
|
||||
}
|
||||
%>
|
||||
<li class="relative">
|
||||
<h6 class="text-xs font-semibold text-slate-900 uppercase tracking-wide mb-3">Data Types</h6>
|
||||
<ul role="list" class="space-y-1 border-l border-slate-200 ml-2">
|
||||
<% for(var i=0; i<_mGeneral.length; i++) {%>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition truncate" href="/reference/<%= _mGeneral[i].path -%>"><%= _mGeneral[i].name -%></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% for (var _mci = 0; _mci < _mCatOrder.length; _mci++) { %>
|
||||
<% var _mCatName = _mCatOrder[_mci]; var _mCatTypes = _mCategories[_mCatName]; %>
|
||||
<div class="mt-3 mb-1 ml-4">
|
||||
<span class="text-[10px] font-semibold text-slate-400 uppercase tracking-wider"><%= _mCatName %></span>
|
||||
</div>
|
||||
<ul role="list" class="space-y-1 border-l border-slate-200 ml-2">
|
||||
<% for(var _mti=0; _mti<_mCatTypes.length; _mti++) {%>
|
||||
<li><a class="mobile-nav-link block py-2 pl-4 text-sm text-slate-600 hover:text-slate-900 border-l-2 border-transparent hover:border-slate-300 -ml-px transition truncate" href="/reference/<%= _mCatTypes[_mti].path -%>"><%= _mCatTypes[_mti].name -%></a></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% } %>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-slate-200">
|
||||
<a href="/dashboard" class="flex items-center justify-center w-full rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-indigo-700 transition shadow-sm">
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Highlight active navigation link
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const currentPath = window.location.pathname;
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
const mobileNavLinks = document.querySelectorAll('.mobile-nav-link');
|
||||
|
||||
navLinks.forEach(function(link) {
|
||||
const href = link.getAttribute('href');
|
||||
if (href === currentPath) {
|
||||
link.classList.remove('text-slate-600', 'border-transparent', 'hover:border-slate-300');
|
||||
link.classList.add('text-slate-900', 'font-medium', 'border-indigo-500');
|
||||
link.setAttribute('aria-current', 'page');
|
||||
}
|
||||
});
|
||||
|
||||
mobileNavLinks.forEach(function(link) {
|
||||
const href = link.getAttribute('href');
|
||||
if (href === currentPath) {
|
||||
link.classList.remove('text-slate-600', 'border-transparent', 'hover:border-slate-300');
|
||||
link.classList.add('text-slate-900', 'font-medium', 'border-indigo-500');
|
||||
link.setAttribute('aria-current', 'page');
|
||||
}
|
||||
});
|
||||
|
||||
// Mobile menu toggle functionality
|
||||
const menuToggle = document.getElementById('mobile-menu-toggle');
|
||||
const menuClose = document.getElementById('mobile-menu-close');
|
||||
const menuOverlay = document.getElementById('mobile-menu-overlay');
|
||||
const menuPanel = document.getElementById('mobile-menu-panel');
|
||||
const menuOpenIcon = document.getElementById('menu-open-icon');
|
||||
const menuCloseIcon = document.getElementById('menu-close-icon');
|
||||
|
||||
function openMenu() {
|
||||
menuOverlay.classList.remove('hidden');
|
||||
menuPanel.classList.remove('-translate-x-full');
|
||||
menuOpenIcon.classList.add('hidden');
|
||||
menuCloseIcon.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
menuOverlay.classList.add('hidden');
|
||||
menuPanel.classList.add('-translate-x-full');
|
||||
menuOpenIcon.classList.remove('hidden');
|
||||
menuCloseIcon.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (menuToggle) {
|
||||
menuToggle.addEventListener('click', function() {
|
||||
const isOpen = !menuPanel.classList.contains('-translate-x-full');
|
||||
if (isOpen) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (menuClose) {
|
||||
menuClose.addEventListener('click', closeMenu);
|
||||
}
|
||||
|
||||
if (menuOverlay) {
|
||||
menuOverlay.addEventListener('click', closeMenu);
|
||||
}
|
||||
|
||||
// Close menu on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && !menuPanel.classList.contains('-translate-x-full')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</header>
|
||||
@@ -3,23 +3,20 @@
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
|
||||
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
RUN npm config set fetch-retry-mintimeout 20000
|
||||
RUN npm config set fetch-retry-maxtimeout 60000
|
||||
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
|
||||
@@ -86,8 +83,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 }}
|
||||
|
||||
26
Accounts/index.d.ts
vendored
26
Accounts/index.d.ts
vendored
@@ -2,29 +2,3 @@ declare module "*.png";
|
||||
declare module "*.svg";
|
||||
declare module "*.jpg";
|
||||
declare module "*.gif";
|
||||
|
||||
declare module "react-syntax-highlighter/dist/esm/prism-light";
|
||||
declare module "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/javascript";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/typescript";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/jsx";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/tsx";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/python";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/bash";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/json";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/yaml";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/sql";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/go";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/java";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/css";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/markup";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/markdown";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/docker";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/rust";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/c";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/cpp";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/csharp";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/ruby";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/php";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/graphql";
|
||||
declare module "react-syntax-highlighter/dist/esm/languages/prism/http";
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
{
|
||||
"watch": ["./","../Common/UI", "../Common/Types", "../Common/Utils", "../Common/Models"],
|
||||
"ext": "ts,tsx",
|
||||
"ext": "ts,json,tsx,env,js,jsx,hbs",
|
||||
"ignore": [
|
||||
"./node_modules/**",
|
||||
"./public/**",
|
||||
"./bin/**",
|
||||
"./public/**",
|
||||
"./public/dist/**",
|
||||
"./build/*",
|
||||
|
||||
148
Accounts/package-lock.json
generated
148
Accounts/package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.30.2",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"use-async-effect": "^2.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -33,51 +33,43 @@
|
||||
"@bull-board/express": "^5.21.4",
|
||||
"@clickhouse/client": "^1.10.1",
|
||||
"@elastic/elasticsearch": "^8.12.1",
|
||||
"@hcaptcha/react-hcaptcha": "^1.14.0",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/api-logs": "^0.206.0",
|
||||
"@opentelemetry/api-logs": "^0.52.1",
|
||||
"@opentelemetry/context-zone": "^1.25.1",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.207.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.207.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.207.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.207.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.52.1",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.52.1",
|
||||
"@opentelemetry/id-generator-aws-xray": "^1.2.2",
|
||||
"@opentelemetry/instrumentation": "^0.207.0",
|
||||
"@opentelemetry/instrumentation-fetch": "^0.207.0",
|
||||
"@opentelemetry/instrumentation-xml-http-request": "^0.207.0",
|
||||
"@opentelemetry/instrumentation": "^0.52.1",
|
||||
"@opentelemetry/instrumentation-fetch": "^0.52.1",
|
||||
"@opentelemetry/instrumentation-xml-http-request": "^0.52.1",
|
||||
"@opentelemetry/resources": "^1.25.1",
|
||||
"@opentelemetry/sdk-logs": "^0.207.0",
|
||||
"@opentelemetry/sdk-logs": "^0.52.1",
|
||||
"@opentelemetry/sdk-metrics": "^1.25.1",
|
||||
"@opentelemetry/sdk-node": "^0.207.0",
|
||||
"@opentelemetry/sdk-node": "^0.52.1",
|
||||
"@opentelemetry/sdk-trace-node": "^1.25.1",
|
||||
"@opentelemetry/sdk-trace-web": "^1.25.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.37.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.26.0",
|
||||
"@remixicon/react": "^4.2.0",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/web-push": "^3.6.4",
|
||||
"acme-client": "^5.3.0",
|
||||
"airtable": "^0.12.2",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^1.12.0",
|
||||
"botbuilder": "^4.23.3",
|
||||
"bullmq": "^5.61.0",
|
||||
"axios": "^1.7.2",
|
||||
"bullmq": "^5.3.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.8.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.4.4",
|
||||
"ejs": "^3.1.10",
|
||||
"elkjs": "^0.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"expo-server-sdk": "^3.15.0",
|
||||
"express": "^4.21.1",
|
||||
"formik": "^2.4.6",
|
||||
"history": "^5.3.0",
|
||||
@@ -86,53 +78,51 @@
|
||||
"json5": "^2.2.3",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^12.0.2",
|
||||
"mermaid": "^11.12.2",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^7.0.7",
|
||||
"nodemailer": "^6.9.10",
|
||||
"otpauth": "^9.3.1",
|
||||
"pg": "^8.16.3",
|
||||
"playwright": "^1.56.0",
|
||||
"posthog-js": "^1.275.3",
|
||||
"pg": "^8.7.3",
|
||||
"playwright": "^1.50.0",
|
||||
"posthog-js": "^1.139.6",
|
||||
"prop-types": "^15.8.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-big-calendar": "^1.19.4",
|
||||
"react-big-calendar": "^1.13.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.2.2",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-markdown": "^9.0.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-router-dom": "^6.24.1",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
"react-syntax-highlighter": "^16.0.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"recharts": "^2.12.7",
|
||||
"redis-semaphore": "^5.5.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"slackify-markdown": "^4.4.0",
|
||||
"slugify": "^1.6.5",
|
||||
"socket.io": "^4.7.4",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"stripe": "^10.17.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"twilio": "^4.22.0",
|
||||
"typeorm": "^0.3.26",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm-extension": "^2.2.13",
|
||||
"universal-cookie": "^7.2.1",
|
||||
"use-async-effect": "^2.2.6",
|
||||
"uuid": "^8.3.2",
|
||||
"web-push": "^3.6.7",
|
||||
"zod": "^3.25.76"
|
||||
"zod": "^3.25.30"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
@@ -146,6 +136,7 @@
|
||||
"@types/jest": "^28.1.4",
|
||||
"@types/json2csv": "^5.0.3",
|
||||
"@types/jsonwebtoken": "^8.5.9",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/node-cron": "^3.0.7",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
@@ -352,9 +343,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
|
||||
"integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
||||
"integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -530,23 +521,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
"fill-range": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -660,11 +649,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
@@ -694,9 +682,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@@ -715,11 +703,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -806,7 +793,6 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@@ -971,12 +957,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.30.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
|
||||
"integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
|
||||
"version": "6.23.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
|
||||
"integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.23.2"
|
||||
"@remix-run/router": "1.16.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -986,13 +972,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.30.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
|
||||
"integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
|
||||
"version": "6.23.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz",
|
||||
"integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.23.2",
|
||||
"react-router": "6.30.3"
|
||||
"@remix-run/router": "1.16.1",
|
||||
"react-router": "6.23.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -1022,16 +1008,6 @@
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
@@ -1044,6 +1020,15 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier/node_modules/semver": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
|
||||
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -1061,7 +1046,6 @@
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"name": "@oneuptime/accounts",
|
||||
"version": "0.1.0",
|
||||
"private": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OneUptime/oneuptime"
|
||||
},
|
||||
"scripts": {
|
||||
"dev-build": "NODE_ENV=development node esbuild.config.js",
|
||||
"dev": "npx nodemon",
|
||||
@@ -37,7 +33,7 @@
|
||||
"ejs": "^3.1.10",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.30.2",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"use-async-effect": "^2.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow: /api/*
|
||||
Disallow:
|
||||
|
||||
@@ -2,13 +2,12 @@ import React from "react";
|
||||
|
||||
const ForbiddenPage: () => JSX.Element = () => {
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto text-center">
|
||||
<div className="text-6xl sm:text-7xl mb-4">🚫</div>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
<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 px-2 sm:px-0">
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
You do not have permission to access this page.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -14,35 +14,35 @@ const ForgotPassword: () => JSX.Element = () => {
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<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">
|
||||
<img
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Forgot your password
|
||||
</h2>
|
||||
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your email and the password reset link will be sent to
|
||||
you.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
We have emailed you the password reset link. Please do not forget to
|
||||
check spam.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto">
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
name="Forgot Password"
|
||||
@@ -81,8 +81,8 @@ const ForgotPassword: () => JSX.Element = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 sm:mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500 text-sm sm:text-base">
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Remember your password?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
|
||||
@@ -1,44 +1,28 @@
|
||||
import {
|
||||
LOGIN_API_URL,
|
||||
VERIFY_TOTP_AUTH_API_URL,
|
||||
GENERATE_WEBAUTHN_AUTH_OPTIONS_API_URL,
|
||||
VERIFY_WEBAUTHN_AUTH_API_URL,
|
||||
VERIFY_TWO_FACTOR_AUTH_API_URL,
|
||||
} from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
||||
import ModelForm, {
|
||||
FormType,
|
||||
ModelField,
|
||||
} from "Common/UI/Components/Forms/ModelForm";
|
||||
import { CustomElementProps } from "Common/UI/Components/Forms/Types/Field";
|
||||
import FormValues from "Common/UI/Components/Forms/Types/FormValues";
|
||||
import ModelForm, { FormType } from "Common/UI/Components/Forms/ModelForm";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "Common/UI/Components/Link/Link";
|
||||
import Captcha from "Common/UI/Components/Captcha/Captcha";
|
||||
import {
|
||||
DASHBOARD_URL,
|
||||
CAPTCHA_ENABLED,
|
||||
CAPTCHA_SITE_KEY,
|
||||
} from "Common/UI/Config";
|
||||
import { DASHBOARD_URL } from "Common/UI/Config";
|
||||
import OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import EditionLabel from "Common/UI/Components/EditionLabel/EditionLabel";
|
||||
import UiAnalytics from "Common/UI/Utils/Analytics";
|
||||
import LoginUtil from "Common/UI/Utils/Login";
|
||||
import UserTotpAuth from "Common/Models/DatabaseModels/UserTotpAuth";
|
||||
import UserWebAuthn from "Common/Models/DatabaseModels/UserWebAuthn";
|
||||
import UserTwoFactorAuth from "Common/Models/DatabaseModels/UserTwoFactorAuth";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import UserUtil from "Common/UI/Utils/User";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import React from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
import StaticModelList from "Common/UI/Components/ModelList/StaticModelList";
|
||||
import BasicForm from "Common/UI/Components/Forms/BasicForm";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import Base64 from "Common/Utils/Base64";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import ComponentLoader from "Common/UI/Components/ComponentLoader/ComponentLoader";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = LOGIN_API_URL;
|
||||
@@ -52,115 +36,19 @@ const LoginPage: () => JSX.Element = () => {
|
||||
const [showTwoFactorAuth, setShowTwoFactorAuth] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const [totpAuthList, setTotpAuthList] = React.useState<UserTotpAuth[]>([]);
|
||||
const [twoFactorAuthList, setTwoFactorAuthList] = React.useState<
|
||||
UserTwoFactorAuth[]
|
||||
>([]);
|
||||
|
||||
const [webAuthnList, setWebAuthnList] = React.useState<UserWebAuthn[]>([]);
|
||||
|
||||
const [selectedTotpAuth, setSelectedTotpAuth] = React.useState<
|
||||
UserTotpAuth | undefined
|
||||
const [selectedTwoFactorAuth, setSelectedTwoFactorAuth] = React.useState<
|
||||
UserTwoFactorAuth | undefined
|
||||
>(undefined);
|
||||
|
||||
const [selectedWebAuthn, setSelectedWebAuthn] = React.useState<
|
||||
UserWebAuthn | undefined
|
||||
>(undefined);
|
||||
|
||||
type TwoFactorMethod = {
|
||||
type: "totp" | "webauthn";
|
||||
item: UserTotpAuth | UserWebAuthn;
|
||||
};
|
||||
|
||||
const twoFactorMethods: TwoFactorMethod[] = [
|
||||
...totpAuthList.map((item: UserTotpAuth) => {
|
||||
return { type: "totp" as const, item };
|
||||
}),
|
||||
...webAuthnList.map((item: UserWebAuthn) => {
|
||||
return { type: "webauthn" as const, item };
|
||||
}),
|
||||
];
|
||||
|
||||
const [isTwoFactorAuthLoading, setIsTwoFactorAuthLoading] =
|
||||
React.useState<boolean>(false);
|
||||
const [twofactorAuthError, setTwoFactorAuthError] =
|
||||
React.useState<string>("");
|
||||
|
||||
const isCaptchaEnabled: boolean =
|
||||
CAPTCHA_ENABLED && Boolean(CAPTCHA_SITE_KEY);
|
||||
|
||||
const [shouldResetCaptcha, setShouldResetCaptcha] =
|
||||
React.useState<boolean>(false);
|
||||
const [captchaResetSignal, setCaptchaResetSignal] = React.useState<number>(0);
|
||||
|
||||
const handleCaptchaReset: () => void = React.useCallback(() => {
|
||||
setCaptchaResetSignal((current: number) => {
|
||||
return current + 1;
|
||||
});
|
||||
}, []);
|
||||
let loginFields: Array<ModelField<User>> = [
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues["email"]),
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: "Password",
|
||||
required: true,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
sideLink: {
|
||||
text: "Forgot password?",
|
||||
url: new Route("/accounts/forgot-password"),
|
||||
openLinkInNewTab: false,
|
||||
},
|
||||
dataTestId: "password",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
];
|
||||
|
||||
if (isCaptchaEnabled) {
|
||||
loginFields = loginFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
captchaToken: true,
|
||||
},
|
||||
overrideFieldKey: "captchaToken",
|
||||
fieldType: FormFieldSchemaType.CustomComponent,
|
||||
title: "Human Verification",
|
||||
description:
|
||||
"Complete the captcha challenge so we know you're not a bot.",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
getCustomElement: (
|
||||
_values: FormValues<User>,
|
||||
customProps: CustomElementProps,
|
||||
) => {
|
||||
return (
|
||||
<Captcha
|
||||
siteKey={CAPTCHA_SITE_KEY}
|
||||
resetSignal={captchaResetSignal}
|
||||
error={customProps.error}
|
||||
onTokenChange={(token: string) => {
|
||||
customProps.onChange?.(token);
|
||||
}}
|
||||
onBlur={customProps.onBlur}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (Navigation.getQueryStringByName("email")) {
|
||||
setInitialValues({
|
||||
@@ -169,96 +57,6 @@ const LoginPage: () => JSX.Element = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (selectedWebAuthn) {
|
||||
setIsTwoFactorAuthLoading(true);
|
||||
try {
|
||||
const result: HTTPResponse<JSONObject> = await API.post({
|
||||
url: GENERATE_WEBAUTHN_AUTH_OPTIONS_API_URL,
|
||||
data: {
|
||||
data: {
|
||||
email: initialValues["email"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result instanceof HTTPErrorResponse) {
|
||||
throw result;
|
||||
}
|
||||
|
||||
const data: any = result.data as any;
|
||||
|
||||
// Convert base64url strings back to Uint8Array
|
||||
data.options.challenge = Base64.base64UrlToUint8Array(
|
||||
data.options.challenge,
|
||||
);
|
||||
if (data.options.allowCredentials) {
|
||||
data.options.allowCredentials.forEach((cred: any) => {
|
||||
cred.id = Base64.base64UrlToUint8Array(cred.id);
|
||||
});
|
||||
}
|
||||
|
||||
// Use WebAuthn API
|
||||
const credential: PublicKeyCredential =
|
||||
(await navigator.credentials.get({
|
||||
publicKey: data.options,
|
||||
})) as PublicKeyCredential;
|
||||
|
||||
const assertionResponse: AuthenticatorAssertionResponse =
|
||||
credential.response as AuthenticatorAssertionResponse;
|
||||
|
||||
// Verify
|
||||
const verifyResult: HTTPResponse<JSONObject> = await API.post({
|
||||
url: VERIFY_WEBAUTHN_AUTH_API_URL,
|
||||
data: {
|
||||
data: {
|
||||
...initialValues,
|
||||
challenge: data.challenge,
|
||||
credential: {
|
||||
id: credential.id,
|
||||
rawId: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(credential.rawId),
|
||||
),
|
||||
response: {
|
||||
authenticatorData: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.authenticatorData),
|
||||
),
|
||||
clientDataJSON: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.clientDataJSON),
|
||||
),
|
||||
signature: Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.signature),
|
||||
),
|
||||
userHandle: assertionResponse.userHandle
|
||||
? Base64.uint8ArrayToBase64Url(
|
||||
new Uint8Array(assertionResponse.userHandle),
|
||||
)
|
||||
: null,
|
||||
},
|
||||
type: credential.type,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (verifyResult instanceof HTTPErrorResponse) {
|
||||
throw verifyResult;
|
||||
}
|
||||
|
||||
const user: User = User.fromJSON(
|
||||
verifyResult.data as JSONObject,
|
||||
User,
|
||||
) as User;
|
||||
const miscData: JSONObject = {};
|
||||
|
||||
login(user as User, miscData);
|
||||
} catch (error) {
|
||||
setTwoFactorAuthError(API.getFriendlyErrorMessage(error as Error));
|
||||
}
|
||||
setIsTwoFactorAuthLoading(false);
|
||||
}
|
||||
}, [selectedWebAuthn]);
|
||||
|
||||
type LoginFunction = (user: User, miscData: JSONObject) => void;
|
||||
|
||||
const login: LoginFunction = (user: User, miscData: JSONObject): void => {
|
||||
@@ -274,22 +72,19 @@ const LoginPage: () => JSX.Element = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<div className="mt-4 flex justify-center">
|
||||
<EditionLabel />
|
||||
</div>
|
||||
{!showTwoFactorAuth && (
|
||||
<>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
<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 px-2 sm:px-0">
|
||||
<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>
|
||||
@@ -298,10 +93,10 @@ const LoginPage: () => JSX.Element = () => {
|
||||
|
||||
{showTwoFactorAuth && (
|
||||
<>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
<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 px-2 sm:px-0">
|
||||
<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>
|
||||
@@ -309,71 +104,68 @@ const LoginPage: () => JSX.Element = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto">
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow sm:rounded-lg sm:py-8 sm:px-10 rounded-lg">
|
||||
<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={loginFields}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues["email"]),
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: "Password",
|
||||
required: true,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
sideLink: {
|
||||
text: "Forgot password?",
|
||||
url: new Route("/accounts/forgot-password"),
|
||||
openLinkInNewTab: false,
|
||||
},
|
||||
dataTestId: "password",
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Login"}
|
||||
onBeforeCreate={(data: User, miscDataProps: JSONObject) => {
|
||||
if (isCaptchaEnabled) {
|
||||
const captchaToken: string | undefined = (
|
||||
miscDataProps["captchaToken"] as string | undefined
|
||||
)
|
||||
?.toString()
|
||||
.trim();
|
||||
|
||||
if (!captchaToken) {
|
||||
throw new Error(
|
||||
"Please complete the captcha challenge before signing in.",
|
||||
);
|
||||
}
|
||||
|
||||
miscDataProps["captchaToken"] = captchaToken;
|
||||
setShouldResetCaptcha(true);
|
||||
}
|
||||
|
||||
onBeforeCreate={(data: User) => {
|
||||
setInitialValues(User.toJSON(data, User));
|
||||
return Promise.resolve(data);
|
||||
}}
|
||||
onLoadingChange={(loading: boolean) => {
|
||||
if (!isCaptchaEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loading && shouldResetCaptcha) {
|
||||
setShouldResetCaptcha(false);
|
||||
handleCaptchaReset();
|
||||
}
|
||||
}}
|
||||
onSuccess={(
|
||||
value: User | JSONObject,
|
||||
miscData: JSONObject | undefined,
|
||||
) => {
|
||||
if (
|
||||
miscData &&
|
||||
((((miscData as JSONObject)["totpAuthList"] as JSONArray)
|
||||
?.length || 0) > 0 ||
|
||||
(((miscData as JSONObject)["webAuthnList"] as JSONArray)
|
||||
?.length || 0) > 0)
|
||||
(miscData as JSONObject)["twoFactorAuth"] === true
|
||||
) {
|
||||
const totpAuthList: Array<UserTotpAuth> =
|
||||
UserTotpAuth.fromJSONArray(
|
||||
(miscData as JSONObject)["totpAuthList"] as JSONArray,
|
||||
UserTotpAuth,
|
||||
const twoFactorAuthList: Array<UserTwoFactorAuth> =
|
||||
UserTwoFactorAuth.fromJSONArray(
|
||||
(miscData as JSONObject)[
|
||||
"twoFactorAuthList"
|
||||
] as JSONArray,
|
||||
UserTwoFactorAuth,
|
||||
);
|
||||
const webAuthnList: Array<UserWebAuthn> =
|
||||
UserWebAuthn.fromJSONArray(
|
||||
(miscData as JSONObject)["webAuthnList"] as JSONArray,
|
||||
UserWebAuthn,
|
||||
);
|
||||
setTotpAuthList(totpAuthList);
|
||||
setWebAuthnList(webAuthnList);
|
||||
setTwoFactorAuthList(twoFactorAuthList);
|
||||
setShowTwoFactorAuth(true);
|
||||
return;
|
||||
}
|
||||
@@ -395,53 +187,19 @@ const LoginPage: () => JSX.Element = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{showTwoFactorAuth && !selectedTotpAuth && !selectedWebAuthn && (
|
||||
<div className="space-y-4">
|
||||
{twoFactorMethods.map(
|
||||
(method: TwoFactorMethod, index: number) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="cursor-pointer p-4 border border-gray-300 rounded-lg hover:bg-gray-50"
|
||||
onClick={() => {
|
||||
if (method.type === "totp") {
|
||||
setSelectedTotpAuth(method.item as UserTotpAuth);
|
||||
} else {
|
||||
setSelectedWebAuthn(method.item as UserWebAuthn);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="font-medium">
|
||||
{(method.item as any).name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{method.type === "totp"
|
||||
? "Authenticator App"
|
||||
: "Security Key"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
{showTwoFactorAuth && !selectedTwoFactorAuth && (
|
||||
<StaticModelList<UserTwoFactorAuth>
|
||||
titleField="name"
|
||||
descriptionField=""
|
||||
selectedItems={[]}
|
||||
list={twoFactorAuthList}
|
||||
onClick={(item: UserTwoFactorAuth) => {
|
||||
setSelectedTwoFactorAuth(item);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showTwoFactorAuth && selectedWebAuthn && (
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-medium mb-4">
|
||||
Authenticating with Security Key
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mb-4">
|
||||
Please follow the instructions on your security key device.
|
||||
</div>
|
||||
{isTwoFactorAuthLoading && <ComponentLoader />}
|
||||
{twofactorAuthError && (
|
||||
<ErrorMessage message={twofactorAuthError} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showTwoFactorAuth && selectedTotpAuth && (
|
||||
{showTwoFactorAuth && selectedTwoFactorAuth && (
|
||||
<BasicForm
|
||||
id="two-factor-auth-form"
|
||||
name="Two Factor Auth"
|
||||
@@ -467,17 +225,14 @@ const LoginPage: () => JSX.Element = () => {
|
||||
try {
|
||||
const code: string = data["code"] as string;
|
||||
const twoFactorAuthId: string =
|
||||
selectedTotpAuth!.id?.toString() as string;
|
||||
selectedTwoFactorAuth.id?.toString() as string;
|
||||
|
||||
const result: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.post({
|
||||
url: VERIFY_TOTP_AUTH_API_URL,
|
||||
await API.post(VERIFY_TWO_FACTOR_AUTH_API_URL, {
|
||||
data: {
|
||||
data: {
|
||||
...initialValues,
|
||||
code: code,
|
||||
twoFactorAuthId: twoFactorAuthId,
|
||||
},
|
||||
...initialValues,
|
||||
code: code,
|
||||
twoFactorAuthId: twoFactorAuthId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -505,9 +260,9 @@ const LoginPage: () => JSX.Element = () => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 sm:mt-10 text-center">
|
||||
{!selectedTotpAuth && !selectedWebAuthn && (
|
||||
<div className="text-muted mb-0 text-gray-500 text-sm sm:text-base">
|
||||
<div className="mt-10 text-center">
|
||||
{!selectedTwoFactorAuth && (
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/register")}
|
||||
@@ -517,12 +272,11 @@ const LoginPage: () => JSX.Element = () => {
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{selectedTotpAuth || selectedWebAuthn ? (
|
||||
{selectedTwoFactorAuth ? (
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
<Link
|
||||
onClick={() => {
|
||||
setSelectedTotpAuth(undefined);
|
||||
setSelectedWebAuthn(undefined);
|
||||
setSelectedTwoFactorAuth(undefined);
|
||||
}}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
|
||||
@@ -42,12 +42,12 @@ const LoginPage: () => JSX.Element = () => {
|
||||
try {
|
||||
// get sso config by email.
|
||||
const listResult: HTTPErrorResponse | HTTPResponse<JSONArray> =
|
||||
await API.get({
|
||||
url: URL.fromString(apiUrl.toString()).addQueryParam(
|
||||
await API.get(
|
||||
URL.fromString(apiUrl.toString()).addQueryParam(
|
||||
"email",
|
||||
email.toString(),
|
||||
),
|
||||
});
|
||||
);
|
||||
|
||||
if (listResult instanceof HTTPErrorResponse) {
|
||||
throw listResult;
|
||||
@@ -126,18 +126,18 @@ const LoginPage: () => JSX.Element = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md mx-auto px-4 sm:px-0">
|
||||
<div className="flex min-h-full flex-col justify-center py-8 sm:py-12">
|
||||
<div className="w-full">
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 sm:mt-10 text-center text-lg sm:text-xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-10 text-center text-xl tracking-tight text-gray-900">
|
||||
Select Project
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Select the project you want to login to.
|
||||
</p>
|
||||
</div>
|
||||
@@ -164,23 +164,23 @@ const LoginPage: () => JSX.Element = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Login with SSO
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Login with your SSO provider to access your account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto">
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<BasicForm
|
||||
modelType={User}
|
||||
id="login-form"
|
||||
@@ -217,8 +217,8 @@ const LoginPage: () => JSX.Element = () => {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 sm:mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500 text-sm sm:text-base">
|
||||
<div className="mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/register")}
|
||||
|
||||
@@ -2,13 +2,12 @@ import React from "react";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto text-center">
|
||||
<div className="text-6xl sm:text-7xl mb-4">🔍</div>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
<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">
|
||||
Page not found
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Page you are looking for does not exist.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -4,22 +4,12 @@ import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import ModelForm, {
|
||||
FormType,
|
||||
ModelField,
|
||||
} from "Common/UI/Components/Forms/ModelForm";
|
||||
import { CustomElementProps } from "Common/UI/Components/Forms/Types/Field";
|
||||
import ModelForm, { FormType } from "Common/UI/Components/Forms/ModelForm";
|
||||
import Fields from "Common/UI/Components/Forms/Types/Fields";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import FormValues from "Common/UI/Components/Forms/Types/FormValues";
|
||||
import Link from "Common/UI/Components/Link/Link";
|
||||
import PageLoader from "Common/UI/Components/Loader/PageLoader";
|
||||
import Captcha from "Common/UI/Components/Captcha/Captcha";
|
||||
import {
|
||||
BILLING_ENABLED,
|
||||
DASHBOARD_URL,
|
||||
CAPTCHA_ENABLED,
|
||||
CAPTCHA_SITE_KEY,
|
||||
} from "Common/UI/Config";
|
||||
import { BILLING_ENABLED, DASHBOARD_URL } from "Common/UI/Config";
|
||||
import OneUptimeLogo from "Common/UI/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import BaseAPI from "Common/UI/Utils/API/API";
|
||||
import UiAnalytics from "Common/UI/Utils/Analytics";
|
||||
@@ -32,7 +22,6 @@ import Reseller from "Common/Models/DatabaseModels/Reseller";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import React, { useState } from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
|
||||
const RegisterPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = SIGNUP_API_URL;
|
||||
@@ -47,19 +36,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
const isCaptchaEnabled: boolean =
|
||||
CAPTCHA_ENABLED && Boolean(CAPTCHA_SITE_KEY);
|
||||
|
||||
const [shouldResetCaptcha, setShouldResetCaptcha] =
|
||||
React.useState<boolean>(false);
|
||||
const [captchaResetSignal, setCaptchaResetSignal] = React.useState<number>(0);
|
||||
|
||||
const handleCaptchaReset: () => void = React.useCallback(() => {
|
||||
setCaptchaResetSignal((current: number) => {
|
||||
return current + 1;
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}
|
||||
@@ -117,7 +93,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
let formFields: Array<ModelField<User>> = [
|
||||
let formFields: Fields<User> = [
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
@@ -173,36 +149,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!BILLING_ENABLED) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
selfHostedCompanyName: true,
|
||||
},
|
||||
overrideFieldKey: "selfHostedCompanyName",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Acme, Inc.",
|
||||
required: false,
|
||||
title: "Company Name",
|
||||
dataTestId: "selfHostedCompanyName",
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
overrideField: {
|
||||
selfHostedPhoneNumber: true,
|
||||
},
|
||||
overrideFieldKey: "selfHostedPhoneNumber",
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: false,
|
||||
placeholder: "+11234567890",
|
||||
title: "Phone Number",
|
||||
dataTestId: "selfHostedPhoneNumber",
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
field: {
|
||||
@@ -237,58 +183,6 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
},
|
||||
]);
|
||||
|
||||
if (!IsBillingEnabled) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
notifySelfHosted: true,
|
||||
},
|
||||
overrideFieldKey: "notifySelfHosted",
|
||||
|
||||
fieldType: FormFieldSchemaType.Checkbox,
|
||||
required: false,
|
||||
defaultValue: true,
|
||||
title: "Notify me about security patches and new releases",
|
||||
dataTestId: "notifySelfHosted",
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
spanFullRow: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if (isCaptchaEnabled) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
captchaToken: true,
|
||||
},
|
||||
overrideFieldKey: "captchaToken",
|
||||
fieldType: FormFieldSchemaType.CustomComponent,
|
||||
title: "Human Verification",
|
||||
description:
|
||||
"Complete the captcha challenge so we know you're not a bot.",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
getCustomElement: (
|
||||
_values: FormValues<User>,
|
||||
customProps: CustomElementProps,
|
||||
) => {
|
||||
return (
|
||||
<Captcha
|
||||
siteKey={CAPTCHA_SITE_KEY}
|
||||
resetSignal={captchaResetSignal}
|
||||
error={customProps.error}
|
||||
onTokenChange={(token: string) => {
|
||||
customProps.onChange?.(token);
|
||||
}}
|
||||
onBlur={customProps.onBlur}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage message={error} />;
|
||||
}
|
||||
@@ -298,17 +192,17 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-6 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto lg:max-w-2xl">
|
||||
<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">
|
||||
<img
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Create your OneUptime account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
<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>
|
||||
@@ -317,8 +211,8 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto lg:max-w-2xl">
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
<div className="mt-8 lg:mx-auto lg:w-full lg:max-w-2xl">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
@@ -328,27 +222,7 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={formFields}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
onBeforeCreate={(
|
||||
item: User,
|
||||
miscDataProps: JSONObject,
|
||||
): Promise<User> => {
|
||||
if (isCaptchaEnabled) {
|
||||
const captchaToken: string | undefined = (
|
||||
miscDataProps["captchaToken"] as string | undefined
|
||||
)
|
||||
?.toString()
|
||||
.trim();
|
||||
|
||||
if (!captchaToken) {
|
||||
throw new Error(
|
||||
"Please complete the captcha challenge before signing up.",
|
||||
);
|
||||
}
|
||||
|
||||
miscDataProps["captchaToken"] = captchaToken;
|
||||
setShouldResetCaptcha(true);
|
||||
}
|
||||
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
const utmParams: Dictionary<string> = UserUtil.getUtmParams();
|
||||
|
||||
if (utmParams && Object.keys(utmParams).length > 0) {
|
||||
@@ -366,21 +240,10 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Sign Up"}
|
||||
onLoadingChange={(loading: boolean) => {
|
||||
if (!isCaptchaEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loading && shouldResetCaptcha) {
|
||||
setShouldResetCaptcha(false);
|
||||
handleCaptchaReset();
|
||||
}
|
||||
}}
|
||||
onSuccess={(value: User, miscData: JSONObject | undefined) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture("accounts/register");
|
||||
UiAnalytics.capture("sign_up");
|
||||
}
|
||||
|
||||
LoginUtil.login({
|
||||
@@ -390,8 +253,8 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 sm:mt-5 text-center text-gray-500">
|
||||
<p className="text-muted mb-0 text-sm sm:text-base">
|
||||
<div className="mt-5 text-center text-gray-500">
|
||||
<p className="text-muted mb-0">
|
||||
Already have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
|
||||
@@ -14,33 +14,33 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<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">
|
||||
<img
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-4 sm:mt-6 text-center text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Reset your password
|
||||
</h2>
|
||||
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your new password and we will have it updated.{" "}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600 px-2 sm:px-0">
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Your password has been updated. Please log in.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 sm:mt-8 w-full max-w-md mx-auto">
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
@@ -97,8 +97,8 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 sm:mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500 text-sm sm:text-base">
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Know your password?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
|
||||
@@ -58,53 +58,60 @@ const VerifyEmail: () => JSX.Element = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<div className="text-center mb-8 sm:mb-10">
|
||||
<img
|
||||
className="mx-auto h-10 w-auto sm:h-12"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
</div>
|
||||
<div className="auth-page">
|
||||
<div className="container-fluid p-0">
|
||||
<div className="row g-0">
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
|
||||
<div className="bg-white py-6 px-4 shadow-sm sm:shadow rounded-lg sm:py-8 sm:px-10">
|
||||
{!error && (
|
||||
<div className="text-center">
|
||||
<div className="text-5xl sm:text-6xl mb-4">✅</div>
|
||||
<h2 className="text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Your email is verified.
|
||||
</h2>
|
||||
<p className="text-gray-600 mt-3 text-sm sm:text-base px-2 sm:px-0">
|
||||
Thank you for verifying your email. You can now log in to
|
||||
OneUptime.
|
||||
</p>
|
||||
<div className="col-xxl-4 col-lg-4 col-md-6">
|
||||
<div className="auth-full-page-content d-flex p-sm-5 p-4">
|
||||
<div className="w-100">
|
||||
<div className="d-flex flex-column h-100">
|
||||
<div className="auth-content my-auto">
|
||||
<div
|
||||
className="mt-4 text-center flex justify-center"
|
||||
style={{ marginBottom: "40px" }}
|
||||
>
|
||||
<img
|
||||
style={{ height: "50px" }}
|
||||
src={`${OneUptimeLogo}`}
|
||||
/>
|
||||
</div>
|
||||
{!error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">Your email is verified.</h5>
|
||||
<p className="text-muted mt-2 mb-0">
|
||||
Thank you for verifying your email. You can now log in
|
||||
to OneUptime.{" "}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">Sorry, something went wrong!</h5>
|
||||
<p className="text-muted mt-2 mb-0">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0">
|
||||
Return to sign in?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="hover:underline text-primary fw-semibold"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="text-center">
|
||||
<div className="text-5xl sm:text-6xl mb-4">❌</div>
|
||||
<h2 className="text-xl sm:text-2xl tracking-tight text-gray-900">
|
||||
Sorry, something went wrong!
|
||||
</h2>
|
||||
<p className="text-gray-600 mt-3 text-sm sm:text-base px-2 sm:px-0">
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 sm:mt-8 text-center">
|
||||
<p className="text-gray-500 text-sm sm:text-base">
|
||||
Return to sign in?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user