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>
60 lines
2.7 KiB
Python
60 lines
2.7 KiB
Python
"""temper_overview — the Temper-Linie live-overview page-module for the SPRO gateway.
|
|
|
|
This repository is a **gateway page-module**, not a standalone server. It is
|
|
mounted into the SPRO Universal Gateway as a git submodule at
|
|
``<gateway>/modules/temper_overview/`` and discovered automatically by the kernel
|
|
module loader (``app.core.modules.load_modules``), which imports this package and
|
|
calls ``register(app)`` after the core routers are mounted.
|
|
|
|
It does two things:
|
|
|
|
* mounts the built Vue SPA (``dist/``) as static files under ``/module/temper_overview``;
|
|
* exposes a small **read-only, no-auth** JSON API under ``/module/temper_overview/api``
|
|
for the page to call (same origin → no CORS, no token).
|
|
|
|
The data is non-sensitive shop-floor data, so — like the kernel's ``/health`` and
|
|
``/heartbeat`` — the page API is deliberately unauthenticated. This is a per-page,
|
|
purpose-built read surface: never expose the generic ``POST /query/mariadb`` proxy
|
|
to the browser.
|
|
|
|
See ``agent_docs/vue-page-modules.md`` in the gateway repo for the full runbook,
|
|
and this repo's ``README.md`` for the design + build/deploy notes.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
MODULE_NAME = "temper_overview"
|
|
# MUST match vite.config.ts `base` (with a trailing slash on the Vite side).
|
|
MODULE_PREFIX = "/module/temper_overview"
|
|
|
|
# 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.
|
|
|
|
Called once by the kernel loader during app construction. Order matters: a
|
|
``StaticFiles`` mount captures every path under its prefix, so the ``/api``
|
|
routes are registered FIRST or the mount would shadow them.
|
|
"""
|
|
# 1) Data API first (imported lazily so package import stays cheap).
|
|
from .api import routes
|
|
|
|
app.include_router(routes.router)
|
|
|
|
# 2) Then the SPA. html=True serves dist/index.html at the prefix root.
|
|
# dist/ MUST exist (it is committed) — if it is missing this raises and the
|
|
# loader logs-and-skips the module (the kernel stays up but the page 404s).
|
|
dist = Path(__file__).resolve().parent / "dist"
|
|
app.mount(MODULE_PREFIX, StaticFiles(directory=dist, html=True), name=MODULE_NAME)
|