diff --git a/.env.dev b/.env.dev index 2d1caf7..28147e2 100644 --- a/.env.dev +++ b/.env.dev @@ -1,2 +1,2 @@ -REACT_APP_API_ENDPOINT=http://dashboard-be.devv:5000 +REACT_APP_API_ENDPOINT=http://dashboard-be.devv/api REACT_APP_DASHBOARD_NAME=MQP Local \ No newline at end of file diff --git a/.env.production b/.env.production index a866e7e..c3e41be 100644 --- a/.env.production +++ b/.env.production @@ -1,2 +1,2 @@ -REACT_APP_API_ENDPOINT=https://portal.quantum.lrz.de:5000 +REACT_APP_API_ENDPOINT=https://portal.quantum.lrz.de/api REACT_APP_DASHBOARD_NAME=Munich Quantum Portal \ No newline at end of file diff --git a/.env.stage b/.env.stage index 2edf6d0..5cd7666 100644 --- a/.env.stage +++ b/.env.stage @@ -1,2 +1,2 @@ -REACT_APP_API_ENDPOINT=https://portal-stg.quantum.lrz.de:5000 +REACT_APP_API_ENDPOINT=https://portal-stg.quantum.lrz.de/api REACT_APP_DASHBOARD_NAME=MQP Stage \ No newline at end of file diff --git a/.env.test b/.env.test index 9e2585e..39151e0 100644 --- a/.env.test +++ b/.env.test @@ -1,2 +1,2 @@ -REACT_APP_API_ENDPOINT=https://portal-test.quantum.lrz.de:5000 +REACT_APP_API_ENDPOINT=https://portal-test.quantum.lrz.de/api REACT_APP_DASHBOARD_NAME=MQP Test \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..2f29dcb --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,39 @@ +name: Documentation + +on: + push: + branches: + - develop + paths: + - 'docs/**' + - 'mkdocs.yml' + pull_request: + paths: + - 'docs/**' + - 'mkdocs.yml' + +jobs: + build-docs: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Install dependencies + run: uv pip install --system -r docs/requirements.txt + + - name: Build docs + run: mkdocs build --strict + + - name: Deploy to GitHub Pages + if: github.ref == 'refs/heads/main' + run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index 9a3fb67..03213f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. -package-lock.json - - # dependencies /node_modules /.pnp @@ -27,7 +23,6 @@ build/ npm-debug.log* yarn-debug.log* yarn-error.log* -package-lock.json # assets *.mp4 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b65d021 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# ── Stage 1: build the React app ────────────────────────────────────────── +FROM node:20-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +COPY . . + +ARG BUILD_ENV=production +RUN npx env-cmd -f .env.${BUILD_ENV} npm run build:${BUILD_ENV} + +# ── Stage 2: serve with nginx ────────────────────────────────────────────── +FROM nginx:alpine + +COPY --from=builder /app/build /usr/share/nginx/html +COPY nginx/default.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b700fc3 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +ENV ?= production + +build: + BUILD_ENV=$(ENV) docker compose build + +up: + docker compose up -d + +deploy: build up + +down: + docker compose down + +logs: + docker compose logs -f react-app +# Usage: +# make deploy ENV=staging +# make deploy ENV=production \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1c55ef6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + mqp-app: + build: + context: . + dockerfile: Dockerfile + args: + BUILD_ENV: production # passed to Dockerfile at build time + container_name: mqp-app + restart: unless-stopped + networks: + - mqp-web + +networks: + mqp-web: + external: true \ No newline at end of file diff --git a/docs/user_guide/deployment.md b/docs/user_guide/deployment.md new file mode 100644 index 0000000..b852fb2 --- /dev/null +++ b/docs/user_guide/deployment.md @@ -0,0 +1,167 @@ +# Deployment + +This document describes how to build and deploy the frontend application using Docker and Docker Compose. It also covers how the app connects to the backend infrastructure at runtime. + +## Overview + +The frontend is a React (craco) application served by an nginx container. The build process is fully containerised — no local Node.js installation is required to deploy. + +At runtime, the app container sits on a shared Docker network alongside the backend services. It is not exposed directly to the internet; an infrastructure-level nginx reverse proxy handles all inbound traffic and routes requests to the appropriate container by hostname. + +``` +Internet + │ + ▼ +nginx (infrastructure) ← binds :80 / :443, TLS termination + ├── app.example.com → react-app container (this repo) + └── grafana.example.com → grafana container (backend repo) + +All containers share a Docker bridge network: web +``` + +``` +Infrastructure repo App repo +────────────────────────────── ──────────────────────────────── + nginx container react-app container + - binds :80, :443 - inner nginx serves SPA + - routes by hostname - listens on :80 internally + - TLS termination - NO published ports + - proxies → react-app:80 - only on shared network + - proxies → grafana:3000 + │ │ + └──────── shared Docker network ─────────┘ +``` + +## Prerequisites + +- Docker and Docker Compose installed on the target machine +- The shared Docker network `mqp-web` must already exist (created by the infrastructure stack) +- A clone of this repository on the target machine + +## Repository structure + +``` +MQP-Dashboard-Frontend/ +├── ... +├── src/ +├── public/ +├── nginx/ +│ └── default.conf # inner nginx config — serves the SPA +├── Dockerfile # multi-stage: build → serve +├── docker-compose.yml +├── Makefile +├── .env.test +├── .env.staging +└── .env.production +``` + +## Environment configuration + +Build-time environment variables are managed via per-environment `.env.*` files and injected during the React build step using `env-cmd`. The target environment is selected by passing `BUILD_ENV` at build time. + +| File | Environment | +|---|---| +| `.env.test` | Test | +| `.env.staging` | Staging | +| `.env.production` | Production | + +!!! warning + Never commit secrets or API keys to these files. Use environment-specific secret management for sensitive values. + +## Building and deploying + +Deployment is managed via `make` targets. All targets accept an optional `ENV` variable (defaults to `production`). + +```bash +# Deploy to production (default) +make deploy + +# Deploy to staging +make deploy ENV=staging + +# Deploy to test +make deploy ENV=test +``` + +### Available Makefile targets + +| Target | Description | +|---|---| +| `make build` | Build the Docker image for the target environment | +| `make up` | Start the container (image must already be built) | +| `make deploy` | Build and start in one step | +| `make down` | Stop and remove the container | +| `make logs` | Tail container logs | + +### First-time setup + +On a fresh machine, ensure the shared network exists before bringing up the app: + +```bash +docker network create mqp-web +``` + +This network is owned by the infrastructure stack. If the infrastructure stack is already running, the network will already exist. + +## How the Docker image is built + +The `Dockerfile` uses a multi-stage build: + +1. **Stage 1 — build**: installs Node dependencies and runs `env-cmd` with the target `.env.*` file to produce a static build in `/app/build` +2. **Stage 2 — serve**: copies the static build into an nginx Alpine image + +The result is a small, self-contained image with no Node.js runtime. + +```dockerfile +# Stage 1: build +FROM node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm install && npm ci +COPY . . +ARG BUILD_ENV=production +RUN npx env-cmd -f .env.${BUILD_ENV} npm run build:${BUILD_ENV} + +# Stage 2: serve +FROM nginx:alpine +COPY --from=builder /app/build /usr/share/nginx/html +COPY nginx/default.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +``` + +## Inner nginx configuration + +The `nginx/default.conf` in this repository is the **inner** nginx config — it runs inside the container and is responsible only for serving the SPA correctly. It is not the infrastructure-level reverse proxy. + +Its sole responsibilities are: + +- Serving static files from `/usr/share/nginx/html` +- Redirecting all unknown paths to `index.html` so client-side routing works +- Exposing a `/health` endpoint for container health checks + +It has no knowledge of domain names, TLS, or upstream services. Those concerns belong to the infrastructure layer. + +## Connection to the backend + +The app container joins the shared `mqp-web` Docker network at startup. It does not publish any ports to the host directly. + +The infrastructure nginx reverse proxy — maintained in the backend repository — routes `mqp.example.com` traffic to this container by its Docker container name (`mqp-app`). No changes to this repository are required when the infrastructure routing configuration changes. + +### Grafana embedding + +Certain pages embed Grafana dashboard panels via `