Next.js control plane for QuantWarden.
This repo handles:
- authentication and organization UI
- asset management, overview, and explorer
- manual scan creation
- scheduled scan creation
- live scan activity over SSE
Long-running scan execution does not run inside Vercel. It is handled by the dedicated worker container documented in worker/README.md.
Production setup:
Vercel- runs this Next.js app
- users click manual scans and create schedules here
- the app writes scan batches and schedules into Neon
- the app subscribes to scan activity from the database through SSE
Neon Postgres- stores assets, batches, scan items, schedules, and progress state
Azure VM- runs backend scan services such as
openssl-api,nmap-api, andsubfinder-api - runs the QuantWarden scan worker container
- runs backend scan services such as
The app and worker coordinate through:
- shared Neon database state
- a signed wake endpoint for instant manual scans
From your local machine:
git clone <your-repo-url>
cd quantwarden-ui
npm installRun the app locally:
npm run devFor local app development, use your normal root .env or .env.local.
For the deployed app on Vercel, set:
SCAN_WORKER_WAKE_URL=https://your-vm-or-proxy.example.com/internal/wake
SCAN_WORKER_WAKE_SECRET=replace-with-the-same-secret-used-by-the-worker
SCAN_WORKER_WAKE_TIMEOUT_MS=1500Meaning:
SCAN_WORKER_WAKE_URL- app calls this after a manual batch is created
SCAN_WORKER_WAKE_SECRET- bearer token shared with the worker
SCAN_WORKER_WAKE_TIMEOUT_MS- short timeout for the wake request; the batch still succeeds if wake fails
On the VM, in the repo root, create:
cp .env.worker.example .env.workerThen fill in .env.worker.
Recommended values:
DATABASE_URL=postgres://username:password@host/database?sslmode=require
OPENSSL_API_URL=http://openssl-api:8020
NMAP_API_URL=http://nmap-api:8010
SCAN_WORKER_PORT=8088
SCAN_WORKER_HEALTH_PORT=8089
SCAN_WORKER_WAKE_SECRET=replace-with-the-same-secret-used-by-the-app
OPENSSL_API_TIMEOUT_SECONDS=3
OPENSSL_API_REQUEST_TIMEOUT_MS=15000
OPENSSL_API_PROBE_BATCH_SIZE=10
SCAN_WORKER_ACTIVE_EXECUTOR_TICK_MS=1500
SCAN_WORKER_ACTIVE_SCHEDULER_TICK_MS=10000
SCAN_WORKER_IDLE_EXECUTOR_TICK_MS=1800000
SCAN_WORKER_IDLE_SCHEDULER_TICK_MS=1800000
SCAN_WORKER_ACTIVE_GRACE_MS=60000
SCAN_WORKER_ACTIVE_ORG_LIMIT=100Behavior of those defaults:
- manual scans start quickly when the org is idle
- scheduled scans may wait up to about 30 minutes
- active scans still get frequent progress updates
The worker runs best as Docker on the same VM as your backend scan services.
Fresh VM setup:
git clone <your-repo-url>
cd quantwarden-ui
git checkout <branch-name>
cp .env.worker.example .env.worker
# edit .env.worker
docker compose -f worker/docker-compose.worker.yml up -d --buildIf the repo already exists on the VM:
cd quantwarden-ui
git fetch origin
git checkout <branch-name>
git pull origin <branch-name>
docker compose -f worker/docker-compose.worker.yml down
docker compose -f worker/docker-compose.worker.yml up -d --buildWorker health:
curl http://127.0.0.1:8089/healthz
curl http://<vm-ip>:8089/healthzManual wake test:
curl -i -X POST "http://<vm-ip>:8088/internal/wake" \
-H "Authorization: Bearer <SCAN_WORKER_WAKE_SECRET>" \
-H "Content-Type: application/json" \
-d '{"reason":"manual_test","orgId":"test-org"}'Container status:
docker compose -f worker/docker-compose.worker.yml ps
docker compose -f worker/docker-compose.worker.yml logs -fFrom the repo root on the VM:
git fetch origin
git checkout <branch-name>
git pull origin <branch-name>
docker compose -f worker/docker-compose.worker.yml down
docker compose -f worker/docker-compose.worker.yml up -d --buildIf only the env file changed:
docker compose -f worker/docker-compose.worker.yml down
docker compose -f worker/docker-compose.worker.yml up -dIf you want a clean rebuild:
docker compose -f worker/docker-compose.worker.yml down
docker compose -f worker/docker-compose.worker.yml build --no-cache
docker compose -f worker/docker-compose.worker.yml up -dThe worker lifecycle, ports, control endpoints, and VM operational commands are documented in: