Full-stack disaster response coordination platform for Operation Agile Saber 2026. Field personnel submit damage and UXO (Unexploded Ordnance) reports, manage evacuation manifests, and track personnel status — all offline-first, syncing automatically to a PostgreSQL backend when connectivity resumes. EOC operators monitor a real-time dashboard with WebSocket-powered live updates.
| Dashboard | Accountability | Manifests |
|---|---|---|
![]() |
![]() |
![]() |
| Asset Tracking | Medical Tracking | Repatriation |
|---|---|---|
![]() |
| Aircraft Maintenance | Data Import | Chat |
|---|---|---|
![]() |
![]() |
![]() |
| Login | 2FA | Menu |
|---|---|---|
![]() |
![]() |
![]() |
| Field Reports | Map View | UXO Hazard |
|---|---|---|
![]() |
![]() |
![]() |
| Chat | Offline Queue | MOPP Friendly |
|---|---|---|
![]() |
![]() |
![]() |
| Dependency | Minimum Version | Notes |
|---|---|---|
| Node.js | 18.x | 22.x used in Docker builds |
| npm | 9.x | Bundled with Node 18 |
| PostgreSQL | 16.x | Required for backend; skip if using Docker |
| Docker | 24.x | Required for containerized deployment |
| Docker Compose | 2.x | Required for containerized deployment |
A modern browser with IndexedDB, Geolocation API, and Service Worker support is required (Chrome 90+, Firefox 88+, Safari 15+).
git clone https://github.com/AGILE-SABER-2026/Team-8-Repository.git
cd Team-8-Repository
# Configure environment files
cp .env.example .env
cp backend/.env.example backend/.env
# IMPORTANT: Edit backend/.env and set a strong JWT_SECRET before running
# Build and start all three services (db, backend, frontend)
docker-compose up -d --build
# Initialize the database (first time only)
docker exec agile-saber-backend npm run setupAccess: Frontend → http://localhost:8080 | Backend API → http://localhost:3001/api
npm install
cp .env.example .env
npm run devAccess: http://localhost:5174
The app works fully offline using IndexedDB. Sync and real-time features require the backend to be running.
Terminal 1 — Backend:
cd backend
npm install
cp .env.example .env
# Edit .env: set DB_USER, DB_PASSWORD, DB_NAME to match your local PostgreSQL
# First time only — create the database role and database
psql postgres -c "CREATE ROLE postgres WITH SUPERUSER LOGIN PASSWORD 'postgres';"
createdb agile_saber
# Run migrations + seed demo users + seed exercise data
npm run setup
npm run devTerminal 2 — Frontend:
npm install
cp .env.example .env
npm run devAccess: Frontend → http://localhost:5174 | Backend → http://localhost:3001
| Username | Password | Role | Access Level |
|---|---|---|---|
demo |
demo |
Field | Basic reporting |
jsmith |
Saber2026! |
Field | Field operations |
eoc_ops |
EOC2026! |
EOC | Dashboard + coordination |
admin |
Admin2026! |
Admin | Full access |
2FA PIN (demo): 123456
npm run dev # Start Vite dev server with HMR
npm run build # Production build → dist/
npm run lint # ESLint (zero warnings — any warning is a failure)
npm run preview # Serve the production build locallycd backend
npm run dev # Start Express with nodemon (auto-reload)
npm run start # Start Express without auto-reload
npm run migrate # Apply schema.sql to PostgreSQL
npm run seed # Seed demo users
npm run seed-exercise # Seed exercise scenario data
npm run setup # migrate + seed + seed-exercise (first-time init)# Connect to PostgreSQL inside Docker
docker exec -it agile-saber-db psql -U postgres agile_saber
docker exec agile-saber-db pg_dump -U postgres agile_saber > backup.sql
docker exec -i agile-saber-db psql -U postgres agile_saber < backup.sqlTeam-8-Repository/
├── src/ # React frontend (Vite)
│ ├── auth/ # AuthContext (mock users + JWT fallback), useAuth
│ ├── components/ # AppHeader (drawer nav), BottomNav (tab nav)
│ ├── config/ # Region centers (Guam, Saipan, Tinian, Rota, CNMI)
│ ├── data/ # day1_reports.js — 31KB seed data for demo
│ ├── db/ # Dexie v4 schema (IndexedDB)
│ ├── hooks/ # useSync — monitors navigator.onLine, drains queue
│ ├── pages/ # One file per route (11 pages)
│ ├── services/ # api.js (REST + WebSocket), offlineQueue.js, store.js
│ └── App.jsx # Routes + protected route guards
├── backend/ # Express API server
│ ├── src/
│ │ ├── config/ # database.js, migrate.js, seed.js, schema.sql
│ │ ├── middleware/ # auth.js (authenticateToken, requireRole)
│ │ └── routes/ # auth.js, reports.js, manifests.js, repatriations.js
│ └── Dockerfile
├── docker-compose.yml # Orchestrates db + backend + frontend
├── Dockerfile # Frontend: Node 22 build → Nginx unprivileged
└── nginx.conf # Gzip, 1-year asset cache, SPA fallback
- Submit report →
DamageReport.jsxwrites to Dexie withsynced: falseand queues aCREATE_REPORTaction. - Go online →
useSync.jsdetectsnavigator.onLine, drainsofflineQueue.js, POSTs to/api/reports, marks recordssynced: true. - EOC Dashboard → Uses Dexie
liveQuery()for local reactivity; also receives WebSocket pushes from the backend for real-time cross-client updates. - MapView → Reads Dexie locally, renders severity-coded Leaflet markers on OSM tiles cached offline by Workbox (500 tiles, 30-day TTL).
- Auth → JWT issued by backend, stored in
sessionStorage. If backend is unreachable,AuthContext.jsxfalls back to hardcoded mock users so the app remains functional offline.
Offline-first with Dexie/IndexedDB — Connectivity in field operations is unreliable. Every write is local-first; the backend is treated as an optional sync target, not a dependency.
WebSocket for real-time coordination — The backend broadcasts mutations (new reports, person status changes) to all connected clients except the sender, enabling EOC operators to see field updates without polling.
JWT with no refresh token — Tokens expire in 7 days. There is no silent refresh flow; this is a known limitation.
Nginx in Docker — The frontend container uses nginxinc/nginx-unprivileged (rootless). The SPA fallback (try_files $uri $uri/ /index.html) ensures React Router routes work on direct load.
Severity as integers 1–5 — 1 = Critical, 5 = Monitoring. This integer scale is used in the DB schema, map color coding, and EOC dashboard; all must stay in sync.
No TypeScript — The project uses JSX. @types/react is present only for IDE autocomplete.
<ticket-number>-<short-description>
# Examples:
6-add-login-function-to-the-landing-page
22-websocket-reconnect-backoff
- Branch off
main. - Run lint before pushing:
npm run lint
- Verify the production build completes:
npm run build
- Open a PR targeting
main.
No CI pipelines exist — reviewers verify lint and build manually. There is no test suite.
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_API_URL |
No | http://localhost:3001/api |
Backend REST base URL |
VITE_WS_URL |
No | ws://localhost:3001 |
Backend WebSocket endpoint |
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
Yes* | — | Full PostgreSQL connection string |
DB_HOST |
Yes* | localhost |
PostgreSQL host |
DB_PORT |
Yes* | 5432 |
PostgreSQL port |
DB_NAME |
Yes* | agile_saber |
Database name |
DB_USER |
Yes* | postgres |
Database user |
DB_PASSWORD |
Yes | postgres |
Database password |
JWT_SECRET |
Yes | (insecure default) | Must be changed before production |
JWT_EXPIRES_IN |
No | 7d |
Token lifetime |
PORT |
No | 3001 |
Express listen port |
NODE_ENV |
No | development |
production disables error stack traces |
ALLOWED_ORIGINS |
No | localhost variants | CORS whitelist (comma-separated) |
UPLOAD_DIR |
No | ./uploads |
Local file upload path (multer, not yet wired) |
MAX_FILE_SIZE |
No | 10485760 (10MB) |
Max upload size in bytes |
AWS_REGION |
No | — | S3 region (future photo upload) |
AWS_ACCESS_KEY_ID |
No | — | S3 credentials (future) |
AWS_SECRET_ACCESS_KEY |
No | — | S3 credentials (future) |
S3_BUCKET |
No | — | S3 bucket name (future) |
* Either DATABASE_URL or the individual DB_* variables are required.
# Copy and configure both env files first
cp .env.example .env
cp backend/.env.example backend/.env
# IMPORTANT: Set a strong JWT_SECRET in backend/.env before deploying
docker-compose up -d --build
# First-time database init
docker exec agile-saber-backend npm run setup
# Check health
curl http://localhost:3001/healthServices: frontend on :8080, backend API on :3001, PostgreSQL on :5432.
For production details (security checklist, cloud platforms, scaling), see DEPLOYMENT.md.
npm run build
# Deploy dist/ to any static host (S3, Netlify, Vercel, Cloudflare Pages)
# The host must redirect all 404s to index.html for React Router to workThe app is installable on iOS and Android via the browser's "Add to Home Screen" prompt. HTTPS is required for Service Worker registration on mobile. No separate native build process exists.
| Issue | Details |
|---|---|
| No JWT refresh | Tokens expire after 7 days with no silent refresh. Users are silently deauthenticated post-expiry. |
| Mock 2FA | The 2FA PIN 123456 is hardcoded in AuthContext.jsx. Not production-safe. |
| No photo size enforcement | Photos stored as base64 in IndexedDB. Large images can exhaust browser storage quotas on mobile. |
| Multer installed but unused | backend/package.json includes multer; no file upload endpoint exists yet. |
| Dexie schema migrations | Adding/removing indexed fields in src/db/index.js requires a version bump or existing users get a startup crash. |
| No CI/CD | No automated pipelines. Lint and build must be run manually pre-merge. |
| Rate limiter in dev | Backend limits 100 req/15 min per IP; rapid dev scripts or browser devtools can trigger it unexpectedly. |
| WebSocket unauthenticated | WebSocket connection carries no auth — any client on the network can connect. |
Developed for Operation Agile Saber 2026 — Team 8 (OCHO). See LICENSE.















