The default 2-worker gunicorn could only serve 2 concurrent tablet requests,
queueing the rest, and the rate limiter saw every tablet as the same Nginx
container IP, so 20 users would have collectively burned through the
100 req/min general bucket.
- gunicorn: 5 workers x 4 gthread, --forwarded-allow-ips=*, access log
- uvicorn: 4 workers, --proxy-headers, --forwarded-allow-ips=*
- RateLimitMiddleware: resolve real client IP from
X-Forwarded-For -> X-Real-IP -> request.client.host
- Bump rate_limit_general 100 -> 300 req/min/IP (per tablet now)
- Flask: ProxyFix(x_for=1, x_proto=1, x_host=1) so request.remote_addr
is the tablet IP, not the Nginx IP
- APIClient: forward X-Forwarded-For + X-Real-IP to FastAPI for both
JSON and multipart/files calls; safe no-op outside request context
- 12 new tests (7 server + 5 client) covering header precedence,
forwarding behavior and ProxyFix install
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add file proxy route in maker blueprint (X-API-Key auth for browser requests)
- Persist file_path/annotations_json to DB via RecipeCreate/RecipeUpdate schemas
- Fix canvas sizing using grandparent container instead of Fabric.js wrapper div
- Defer canvas init with requestAnimationFrame for x-show timing
- Add PDF.js support in annotation-editor and annotation-viewer
- Fix annotations_json double-serialization (parse string to object before send)
- Handle FastAPI 422 validation error arrays in api_client and JS error display
- Update template URLs to use /maker/api/files/ proxy path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implementazione completa del frontend Flask:
- Layout base.html con TailwindCSS CDN, dark/light theme, flash messages
- Navbar responsive role-based (Maker, MeasurementTec, Metrologist, Admin)
- Login page professionale con form + API integration
- Profilo utente: nome, lingua, tema, badge ruoli
- Sistema tema dark/light: CSS variables + Alpine.js store + localStorage
- i18n completo IT/EN: Flask-Babel (.po) + alpinejs-i18n (JSON)
- API Client riscritto: error handling normalizzato, no crash su 4xx/5xx
- CSRF protection con Flask-WTF su tutti i form
- Logo aziendale dinamico da system_settings
- Asset SVG: tmflow-logo.svg + tmflow-icon.svg (favicon)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>