Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ trail.
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | System diagram, background-job pipeline (Monitoring → Incident → Notification → Analytics), middleware stack, code layout |
| [`docs/ER_DIAGRAM.md`](docs/ER_DIAGRAM.md) | Full database schema as a Mermaid ER diagram, with design-decision notes |
| [`docs/API.md`](docs/API.md) | Full REST API reference, grouped by resource |
| [`docs/FRONTEND.md`](docs/FRONTEND.md) | Next.js frontend: stack, structure, auth model, local dev, and Vercel deployment |
| [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) | Environment variables, Docker setup, migrations, worker startup, health checks, troubleshooting |
| [`docs/INTERVIEW_NOTES.md`](docs/INTERVIEW_NOTES.md) | "Why X?" design-decision write-ups (Postgres, Redis, Celery, multi-tenancy, RBAC, thresholds, caching, audit logs) |
| [`docs/RESUME_BULLETS.md`](docs/RESUME_BULLETS.md) | Resume-ready bullet points describing this project |
Expand Down Expand Up @@ -95,6 +96,7 @@ diagrams and component-by-component breakdown, and
| Infrastructure | Docker, multi-stage Dockerfile, Docker Compose (db/redis/api/worker/beat) |
| Testing | Pytest, pytest-asyncio, respx, 95% coverage gate |
| CI/CD | GitHub Actions (lint, type check, test+coverage, Docker build, security scan), branch protection |
| Frontend | Next.js 15 (App Router), React 19, TypeScript, custom CSS design system, deployed separately on Vercel |

## Local Setup

Expand Down Expand Up @@ -159,6 +161,18 @@ createdb sentinel_test # or via psql/pgAdmin
pytest -v
```

## Frontend

The [`frontend/`](frontend/) directory contains a Next.js (App Router) +
React 19 + TypeScript single-page app that consumes this API —
authentication, workspaces (with member roles and invite codes), monitors
(CRUD + detail with check history and metrics), a dashboard, incident
management, alert rules, the notification delivery log, audit logs, and
per-workspace API key management. It's a separate deployable, typically
hosted on Vercel while the API above runs elsewhere. See
[`docs/FRONTEND.md`](docs/FRONTEND.md) for the stack, local dev
(`npm run dev`), environment variables, and deployment + CORS setup.

## API Documentation

Once the app is running:
Expand Down
131 changes: 131 additions & 0 deletions docs/FRONTEND.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Frontend

The Sentinel frontend is a [Next.js 15](https://nextjs.org/) (App Router) +
[React 19](https://react.dev/) + TypeScript single-page application located in
[`frontend/`](../frontend). It is a thin client over the FastAPI backend: all
state lives on the server, and the frontend is a typed REST client with a
custom dark-themed UI.

## Stack

| Layer | Choice |
| ----------- | ------------------------------------------------------------------ |
| Framework | Next.js 15 (App Router, `output: "standalone"`) |
| UI library | React 19 |
| Language | TypeScript 5.7 |
| Styling | Hand-written CSS design system (`src/app/globals.css`) — no Tailwind/CSS framework |
| Data access | Plain `fetch` wrapper (`src/lib/api.ts`) — no React Query/SWR |
| Auth | JWT access + refresh tokens stored in `localStorage` |
| Charts | Inline SVG/canvas in page components — no charting library |

No Tailwind CSS, React Query, Zod, react-hook-form, or Recharts are used. The
goal was a small, dependency-light bundle with a single shared stylesheet.

## Project structure

```
frontend/
├── src/
│ ├── app/
│ │ ├── layout.tsx # Root layout, global metadata/viewport
│ │ ├── globals.css # Design system (glass-card, btn, badge, etc.)
│ │ ├── page.tsx # "/" — redirects to /workspaces or /login
│ │ ├── icon.svg # Favicon
│ │ ├── not-found.tsx # Custom 404
│ │ ├── error.tsx # Client error boundary
│ │ ├── login/page.tsx # "/login"
│ │ ├── register/page.tsx # "/register"
│ │ └── workspaces/
│ │ ├── layout.tsx # Auth guard + sidebar/app shell
│ │ ├── page.tsx # "/workspaces" — list/create/join workspaces
│ │ └── [id]/
│ │ ├── page.tsx # Dashboard (metrics, status overview)
│ │ ├── monitors/
│ │ │ ├── page.tsx # Monitor list + create/edit modal
│ │ │ └── [monitorId]/page.tsx # Monitor detail (checks, metrics, incidents)
│ │ ├── incidents/page.tsx # Incident list + detail/resolve
│ │ ├── alerts/page.tsx # Alert rule management
│ │ ├── notifications/page.tsx # Notification delivery log
│ │ ├── audit-logs/page.tsx # Workspace audit log
│ │ └── settings/page.tsx # General / Members / API Keys tabs
│ ├── components/
│ │ └── MonitorModal.tsx # Create/edit monitor form (modal)
│ └── lib/
│ ├── api.ts # Typed fetch client + all DTOs
│ └── utils.ts # Formatting helpers (dates, durations, badges)
├── next.config.ts
├── tsconfig.json
├── .env.example
└── .gitignore
```

Routes use the dynamic segment `[id]` for the workspace ID (not
`[workspaceId]`), matched against `useParams()` in `workspaces/layout.tsx`.

## Authentication model

There is no React context/provider for auth. Each protected layout/page:

1. Reads `access_token` from `localStorage` on mount.
2. If missing, redirects to `/login` via `router.replace`.
3. Otherwise calls `apiGetMe()` to fetch the current user and render the page.

`src/lib/api.ts` exports `setTokens`, `clearTokens`, and an `apiFetch<T>`
wrapper that:

- Attaches `Authorization: Bearer <access_token>` to every request.
- On a `401` response, calls `/auth/refresh` with the stored refresh token,
retries the original request once, and otherwise clears tokens and
redirects to `/login`.
- Normalizes FastAPI's `{"detail": ...}` error bodies (string or pydantic
validation array) into a single `ApiError` with a human-readable message.

## Design system

All shared styles live in `src/app/globals.css`. Key building blocks:

- **Layout**: `.auth-page` / `.auth-card` (login/register/404/error), `.app-layout` / `.sidebar` / `.main-content` (authenticated shell)
- **Cards & grids**: `.glass-card`, `.metrics-grid`, `.workspace-grid`
- **Forms**: `.input-field`, `.select-field`, `.btn` / `.btn-primary` / `.btn-secondary` / `.btn-ghost` / `.btn-danger`
- **Status badges**: `.badge` plus variants — `badge-up`, `badge-down`, `badge-pending`, `badge-success`, `badge-failure`, `badge-open`, `badge-investigating`, `badge-resolved`, `badge-critical`, `badge-major`, `badge-minor`, `badge-neutral`
- **Modals**: `.modal-overlay` / `.modal-content`
- **Misc**: `.tabs` / `.tab`, `.empty-state`, `.skeleton`, `.stagger-children`, `.loading-page`

The accent color is a purple/indigo gradient
(`--accent-gradient: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a78bfa 100%)`)
on a near-black background (`--bg-primary: #0a0e1a`).

## Environment variables

| Variable | Description | Default (`.env.example`) |
| ----------------------- | ----------------------------------------------------- | ---------------------------------------- |
| `NEXT_PUBLIC_API_URL` | Base URL of the Sentinel API, including `/api/v1` | `http://localhost:8000/api/v1` |

Copy `frontend/.env.example` to `frontend/.env.local` and adjust for your
environment. `.env.local` is gitignored.

## Local development

```bash
cd frontend
cp .env.example .env.local # adjust NEXT_PUBLIC_API_URL if needed
npm install
npm run dev
```

The app runs at `http://localhost:3000` and expects the backend (see the
[root README](../README.md)) running at the URL configured in
`NEXT_PUBLIC_API_URL` (default `http://localhost:8000`), with
`BACKEND_CORS_ORIGINS` including `http://localhost:3000`.

## Production build & deployment

```bash
npm run build
npm start
```

`next.config.ts` sets `output: "standalone"` for containerized deployments.
For a Vercel deployment, set `NEXT_PUBLIC_API_URL` to the deployed backend's
URL (e.g. a Render service, see [`render.yaml`](../render.yaml)) and ensure
the backend's `BACKEND_CORS_ORIGINS` includes the Vercel deployment URL.
7 changes: 7 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Base URL of the Sentinel API (including the /api/v1 prefix).
#
# Local development (backend running via `uvicorn` on port 8000):
NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1

# Production (e.g. backend deployed on Render):
# NEXT_PUBLIC_API_URL=https://your-api.onrender.com/api/v1
3 changes: 3 additions & 0 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
31 changes: 31 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# See https://nextjs.org/docs/app/building-your-application/configuring/eslint

# dependencies
/node_modules

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# env files
.env*
!.env.example

# typescript
*.tsbuildinfo
next-env.d.ts
13 changes: 13 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Sentinel Frontend

Next.js (App Router) + React 19 + TypeScript frontend for the
[Sentinel](../README.md) monitoring and incident management platform.

```bash
cp .env.example .env.local # set NEXT_PUBLIC_API_URL if not using the default
npm install
npm run dev
```

See [`../docs/FRONTEND.md`](../docs/FRONTEND.md) for the stack, structure,
auth model, environment variables, and deployment instructions.
10 changes: 10 additions & 0 deletions frontend/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { NextConfig } from "next";
import { resolve } from "path";

const nextConfig: NextConfig = {
output: "standalone",
outputFileTracingRoot: resolve(__dirname),
reactStrictMode: true,
};

export default nextConfig;
Loading