The management app (also called Basic Management App or BMA) serves as an interface for researchers to submit and reviewers to approve study proposals.
It's responsible for:
- Researcher creates/modifies/deletes study
- Reviewer is notified when a study proposal is submitted
- Researcher is notified when study is approved
- Reviewer reviews/approves/denies study
- Reports study status in response to enclave's SetupApp requests
- Node.js version 20.x or higher
- PostgreSQL
Either:
- Option 1: Node.js + Docker and Docker Compose (installs PostgreSQL in container)
- Option 2: Node.js + PostgreSQL installed locally
You can use Docker compose to run the app and a PostgreSQL database:
docker compose upOpen http://localhost:4000 with your browser to access the app.
For developing locally without docker compose, you will need to:
-
Install PostgreSQL and add a
.envfile that contains a valid DATABASE_URL to access it. Homebrew instructions -
Install SeaweedFS:
brew install seaweedfsand provision it using:- Start server:
./bin/local-seaweedfs - Create bucket (only needed once):
./bin/local-createbucket
- Start server:
-
Install dependencies:
corepack enable pnpm install -
Run the development server:
pnpm run dev
- dashboard is located at:
/dashboard - Reviewers can access the review dashboard at:
/<org slug>/dashboard - There are two admin types and screens:
- An organization admin is a member of an organization who can invite other users to that organization. Their admin screen is located at:
/admin/team/<org slug>. From there they can administer the users in their organization. - An SI Staff admin is a user who belongs to the
safe-insightsorganization (defined asCLERK_ADMIN_ORG_SLUGin the codebase). The screen at/admin/safeinsightsallows administrating Organizations. SI Staff administrators are super-admins and can also visit the organization admin screens noted above.
- An organization admin is a member of an organization who can invite other users to that organization. Their admin screen is located at:
The application uses a type-safe routing system located in src/lib/routes that provides compile-time and runtime validation for routes and parameters using Zod schemas. All routes must be defined in the Routes object in src/lib/routes and accessed using it's type safe methods as shown below:
Usage example:
import { Routes, useTypedParams } from '@/lib/routes'
// Build type-safe routes
const route = Routes.studyView({ orgSlug: 'acme', studyId: '123' })
// Use type-safe params in components
const params = useTypedParams(Routes.studyView.schema)
// params.orgSlug and params.studyId are guaranteed to be validTo develop locally, you'll need to create your own SI Staff admin account:
-
Copy the
.env.samplefile to.env, replacing the XXX strings with values obtained from your teammates. This will set up the app with a sandbox Clerk backend. Your.envfile MUST have validCLERK_SECRET_KEYandDATABASE_URLvalues (get values from teammates) -
Run the admin user creation script:
pnpm run create-admin-user
Or if using Docker (with
docker compose uprunning):docker compose exec mgmnt-app pnpm run create-admin-user -
Follow the interactive prompts to enter your email, password, and name
-
Sign in at http://localhost:4000 with your new credentials
Note: This creates an SI Staff admin with full access to administer all organizations.
docker compose build- Rebuild the docker image (needed after packages are installed)docker compose exec mgmnt-app ./bin/migrate-dev-db- Run migrations (needs runningdocker compose upat same time)docker volume rm management-app_pgdata- Delete the database, allowing it to be migrated freshlydocker compose down -v- Gentler "reset switch" that stops and removes containers, networks, volumes, but keeps imagesdocker compose down -v --rmi all- Full "reset switch" for DB errors (stops and removes Docker containers, networks, volumes, and all images)docker system prune -aordocker builder prune- Clear your docker cache in case of emergency
pnpm exec kysely migrate:make your_migration_name- Creates a migration file, we should usesnake_casefor migration namespnpm run db:migrate- Run database migrationspnpm exec kysely migrate:down -1- Rollback the last applied migration
When running the application using docker compose up, a DBGate instance is also started. DBGate provides a web-based interface for visualizing and interacting with the PostgreSQL database used by the application.
You can access DBGate in your browser at: http://localhost:3000
The connection details for the development database (mgmnt_dev) are pre-configured.
API routes are protected by an authorization header containing a JWT Bearer which is signed with an RSA private key held by the organization. The public key is stored in the organization admin panel.
To generate a public/private key pair you can run:
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:4096
openssl rsa -pubout -in private_key.pem -out public_key.pemWe use Ladle to develop and preview UI in isolation β design-system primitives, page
layouts, modals, and auth/MFA flows β without running the full app, a session, or a database. Stories live
next to their components as *.stories.tsx; the Ladle config and the Vite shims that let app components
render outside Next.js live under .ladle/.
pnpm ladle # starts the explorer (default http://localhost:61000) with hot reloadTo hand someone a self-contained copy they can open with no server and no install:
pnpm ladle:build:standalone # β .ladle/dist-standalone/index.htmlThis produces a single (~2 MB) index.html with all JS/CSS inlined and no code-split chunks, so it opens
from a plain file:// double-click. A normal Ladle build can't do this β it emits ES-module scripts and
lazy-loaded chunks that browsers block over file://. Zip it to share:
( cd .ladle/dist-standalone && zip -r ../dist-standalone.zip index.html favicon-*.svg touch-icon-*.png manifest-*.webmanifest )
# β .ladle/dist-standalone.zip β recipient unzips and double-clicks index.htmlThe brand font (Open Sans) still loads from Google Fonts, so fully offline it falls back to a system sans-serif; everything else is embedded.
pnpm ladle:build # β .ladle/dist/ (code-split; serve over HTTP, e.g. `pnpm dlx serve .ladle/dist`)Every push to main builds the explorer and publishes it via the
Ladle Pages workflow β browse it at
https://safeinsights.github.io/management-app/. The build runs
ladle:build:pages, which passes the Pages base path through --base so assets
resolve under the project subpath. Publishing happens only from main so the
Pages deploy credentials are never exposed to PR-controlled code (see OTTER-545).
One-time setup (maintainer, in repo Settings β Pages): set Source to GitHub Actions. After that the workflow deploys on its own.
pnpm run test:unitTo run playwright tests locally, you'll need to install playwright:
pnpm installpnpm exec playwright install
And then run playwright: pnpm exec playwright test --ui to view status, or run pnpm exec playwright test --headed to interact with browser as it runs.
If there are failures, a trace file will be stored under the ./test-results directory. For instance to view a failure with the researcher creating a study, you can run: pnpm exec playwright show-trace ./test-results/researcher-create-study-app-researcher-creates-a-study-chromium/trace.zip
If there are playwright failures on GitHub actions, the trace file will be stored under the "Artifacts" section of the code run. You can download the trace.zip and then run pnpm exec playwright show-trace <path to download> to view the failure details.
To run trivy locally, you'll need to install it. For mac that would be e.g. brew install trivy
And then run it to get report back on your terminal
trivy fs --config trivy.yaml --format table .The new way to deploy is from the IaC repo, run:
./bin/deploy-lambda -p <AWS CLI profile> -c <management app dir> -a managementAppThere are a few CLI applications to debug the API end endpoints:
pnpm exec tsx bin/debug/fetch-runnable.ts -u http://localhost:4000 -o openstax -k <path to private key>
pnpm exec tsx bin/debug/set-status.ts -u http://localhost:4000 -o openstax -k <path to private key> -s <status: JOB-PROVISIONING | JOB-RUNNING | JOB-ERRORED> -j <uuid of job>
pnpm exec tsx bin/debug/upload-results.ts -u http://localhost:4000 -o openstax -k <path to private key> -j <uuid of job> -r <path to file to upload as results> -l <path of file to upload as logs>
pnpm exec tsx bin/debug/keys.ts -u http://localhost:4000 -o openstax -k <path to private key> -j <uuid of job>The scripts will use default values tailored for local development:
- origin will default to http://localhost:4000
- organization to
openstax - key to
tests/support/private_key.pem(local devopenstaxdefaults to using the public key pair of this)
Examples:
- view runnable jobs details (useful for obtaining job uuids):
pnpm exec tsx bin/debug/fetch-runnable.ts - set a job as running:
pnpm exec tsx bin/debug/set-status.ts -s JOB-RUNNING -j <job uuid> - upload results:
pnpm exec tsx bin/debug/set-status.ts -f tests/assets/results-with-pii.csv -j <job uuid>
Currently, it is possible to upload results and then set status back to RUNNING to force the run to re-appear in the runnable api results and repeatedly upload files. While useful for testing, do not depend on that behavior: it's likely we'll not allow it in later versions.
- phosphor icons
- mantine
- Next.js Documentation - learn about Next.js features and API.
- Learn Next.js - an interactive Next.js tutorial.
- How to package and deploy a Lambda function as a container image
- Tutorial: Create a serverless Hello World application
This application is released under the GNU Affero General Public License v3.0. It bundles third-party components covered by separate licenses, including LGPL-3.0-or-later code (libvips, transitively via sharp). See THIRD_PARTY_LICENSES.md for the complete list, source-availability information, and notes on how to exercise LGPL rights against the distributed image.