Declare CORS_ALLOW_ORIGINS=["*"]. The kernel loader reads this opt-in and emits Access-Control-Allow-Origin for this module's prefix only, so the SPA's Vite `crossorigin` assets load inside a sandboxed Grafana iframe (opaque origin) instead of being CORS-blocked (blank page). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
59 lines
2.7 KiB
Python
59 lines
2.7 KiB
Python
"""temper_rejects — the Ausschuss-Station page-module for the SPRO gateway.
|
|
|
|
A central, **login-free** reject-booking terminal for ALL presses' tempered
|
|
carts (Werk Cadolzburg). Mounted into the SPRO Universal Gateway as a git
|
|
submodule at ``<gateway>/modules/temper_rejects/`` and discovered by the kernel
|
|
module loader (``app.core.modules.load_modules``), which imports this package and
|
|
calls ``register(app)``.
|
|
|
|
It does two things:
|
|
|
|
* mounts the built Vue SPA (``dist/``) as static files under ``/module/temper_rejects``;
|
|
* exposes a small no-auth JSON API under ``/module/temper_rejects/api`` — reads
|
|
(tempered carts per ``ttId`` + reject reasons) AND one narrow write
|
|
(``POST /reject`` → one row in ``production.rejects``).
|
|
|
|
No login / device context by design — this is a central shop-floor terminal. The
|
|
existing device-scoped ``POST /press/rejects/send`` derives the press from the
|
|
device JWT, which is useless for a multi-press terminal; hence the un-scoped
|
|
variant here, which takes ``press`` + ``ttId`` from the request body. Non-sensitive
|
|
shop-floor data. Security posture (network/reverse-proxy vs. a static token) is an
|
|
open question — see this repo's README + ``.claude/memory/ausschuss-station.md``.
|
|
|
|
See ``agent_docs/vue-page-modules.md`` in the gateway repo for the module runbook.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
MODULE_NAME = "temper_rejects"
|
|
# MUST match vite.config.ts `base` (with a trailing slash on the Vite side).
|
|
MODULE_PREFIX = "/module/temper_rejects"
|
|
|
|
# CORS opt-in (read by the kernel loader, app.core.modules). This login-free board
|
|
# is embedded cross-origin in a Grafana text panel, which renders it inside a
|
|
# SANDBOXED iframe → the document gets an opaque origin (Origin: null). Vite tags the
|
|
# SPA's own JS/CSS `crossorigin`, so those assets are fetched in CORS mode and the
|
|
# browser blocks them (blank page) unless the gateway allows the read. "*" is safe:
|
|
# this is no-auth, read-only shop-floor data, and only "*" satisfies Origin: null.
|
|
CORS_ALLOW_ORIGINS = ["*"]
|
|
|
|
|
|
def register(app: FastAPI) -> None:
|
|
"""Mount the data API and the SPA onto the gateway app.
|
|
|
|
API routes are registered FIRST — a ``StaticFiles`` mount captures everything
|
|
under its prefix, so the ``/api`` routes must exist before the mount or they
|
|
get shadowed. ``dist/`` MUST exist (it is committed) or this raises and the
|
|
loader logs-and-skips the module (kernel stays up, page 404s).
|
|
"""
|
|
from .api import routes
|
|
|
|
app.include_router(routes.router)
|
|
|
|
dist = Path(__file__).resolve().parent / "dist"
|
|
app.mount(MODULE_PREFIX, StaticFiles(directory=dist, html=True), name=MODULE_NAME)
|