fix(ui): actionable 413/504/401 toasts + VPS nginx deploy template

Production QA reported "Submit failed 413" on /generation and plain
"Upload failed" (no detail) on resource upload. Root cause is the
outer reverse-proxy nginx on the VPS — still on default
`client_max_body_size 1m` — rejecting requests before they reach
Odoo. The inner docker-nginx and Odoo limits were already raised in
earlier commits; only the VPS proxy was left unpatched.

UI side
- Add `describeApiError(err, fallback, context)` helper in
  lib/api-client.ts. Maps common HTTP codes to human-readable,
  actionable toast descriptions (413 → "ask ops to raise nginx
  client_max_body_size", 504 → "server took too long, retry", 502
  → "Odoo is restarting", 401 → "sign in again", 4xx/5xx → server
  error body + status code).
- Use it in GenerationPage submit, ResourceManager upload,
  TeacherLibrary upload, CustomExamCreate save/publish. Replaces
  four slightly-different ad-hoc mappings with one source of truth.

Deploy side
- Add deploy/nginx-vps.conf.example: full TLS reverse-proxy template
  with the body/timeout knobs that must match Odoo's limit_request
  (128 MB) and limit_time_real (900 s).
- Add docs/DEPLOY_413_504_FIX.md: step-by-step remediation guide for
  the team lead covering the 3 layers that can block large bodies
  (outer nginx, Cloudflare, Odoo worker) and how to verify each.

Made-with: Cursor
This commit is contained in:
Yamen Ahmad
2026-04-21 09:09:23 +04:00
parent d34180e107
commit 75ee0f1fe0
7 changed files with 279 additions and 29 deletions

105
docs/DEPLOY_413_504_FIX.md Normal file
View File

@@ -0,0 +1,105 @@
# Why "Submit failed 413" and "Upload failed" still happen in production
QA keeps reporting that the following two actions fail on the deployed VPS
while working locally:
- **Resource Manager → Upload**: toast shows "Upload failed" (no details).
- **Generation → Submit module as exam for approval**: toast shows
"Submit failed — 413".
Both are the same root cause: the **outer nginx reverse proxy** on the VPS
(the one terminating TLS in front of the docker stack) is still using its
default `client_max_body_size 1m` and short proxy timeouts. We already
raised the inner limits in:
- `odoo.conf` / `odoo-docker.conf``limit_request 134217728` (128 MB) +
`limit_time_real 900s`
- `frontend/Dockerfile``client_max_body_size 128m` +
`proxy_read_timeout 900s` + `proxy_request_buffering off`
But the **outer** nginx wasn't changed, so it still rejects the request
(413) or times the proxied connection (504) before traffic ever reaches
Odoo. Because the refusal happens at the proxy, nothing shows up in the
Odoo logs — only in `/var/log/nginx/*.error.log`.
## Fix on the VPS (one-time, 2 minutes)
1. SSH to the VPS and back up the existing config:
```bash
sudo cp /etc/nginx/sites-available/encoach /etc/nginx/sites-available/encoach.bak
```
2. Copy the template from the repo and edit the `server_name` + TLS
cert paths to match the real hostnames:
```bash
sudo cp deploy/nginx-vps.conf.example /etc/nginx/sites-available/encoach
sudo ln -sf /etc/nginx/sites-available/encoach /etc/nginx/sites-enabled/encoach
sudo nginx -t
sudo systemctl reload nginx
```
The critical lines added are:
```nginx
client_max_body_size 128m; # matches Odoo limit_request
client_body_timeout 900s;
proxy_read_timeout 900s; # matches Odoo limit_time_real
proxy_send_timeout 900s;
proxy_request_buffering off; # stream uploads, don't buffer 100% on disk
```
3. Smoke-test:
```bash
# Upload a ~30 MB PDF through /api/resources — should return 200, not 413.
# Submit a module from /generation — should return 201, not 413.
tail -f /var/log/nginx/encoach.error.log # watch for "client intended to send too large body"
tail -f /var/log/odoo/odoo-server.log # watch for the actual request hitting Odoo
```
## If the 413 persists after the nginx change
The body is getting rejected by something upstream. Check in order:
1. **Cloudflare / WAF** — Cloudflare's default free-plan body cap is 100 MB
for authenticated endpoints and as low as 1 MB on some enterprise
rules. In the dashboard → Rules → Transform / Firewall, look for
`request.body.size` restrictions.
2. **Odoo worker (`limit_request`)** — verify
`grep limit_request /etc/odoo/odoo.conf` shows at least `134217728`.
If the config file was baked into the docker image and never rebuilt,
`docker compose build backend && docker compose up -d backend`.
3. **`ModSecurity` / CRL** — some VPS providers ship ModSecurity enabled
by default (`SecRequestBodyLimit`, default 13 MB).
## If the 504 persists
504 = the upstream didn't respond in time.
- Confirm `proxy_read_timeout 900s` is actually in the running config
(`nginx -T | grep proxy_read_timeout`).
- Confirm `limit_time_real` in `odoo.conf` is at least 900 (default is
120; a single AI generation with 4 modules can exceed 3 minutes).
- For first-time submissions, Odoo may need to warm up its AI clients.
Retry once after a minute.
## What the UI now tells the user
The frontend toast has been upgraded (see `lib/api-client.ts::describeApiError`).
Users will now see, for example:
- **413**: _"The module is too large for the server to accept (HTTP 413).
Try a smaller file / fewer tasks, or ask an admin to raise the nginx
client_max_body_size and Odoo limit_request."_
- **504**: _"The server took too long to respond (HTTP 504). The module
may still be processing — wait a minute and reload before retrying."_
- **502**: _"The backend is unreachable (HTTP 502). Odoo may be
restarting — try again in a moment."_
- **401**: _"Your session expired — please sign in again."_
This applies to:
- Resource upload (admin + teacher library)
- Exam generation submit
- Custom-exam publish / save draft