Embed per-run plots and flight animation in the web dashboard

Clicking a config in the dashboard now shows its velocity/field/current
plots (rendered on the fly by /api/run/<id>/plot/<kind>.png) plus an
on-demand GIF of the slug flying through the coils
(/api/run/<id>/anim.gif). Matplotlib rendering is serialized behind a
lock since the Agg pyplot state machine isn't thread-safe and the server
is threaded. Verified end to end: endpoints return real PNG/GIF bytes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-07-07 00:57:02 +05:00
parent 950be5fcf2
commit 1ca807dcbe
4 changed files with 181 additions and 2 deletions

View File

@@ -75,3 +75,39 @@ def test_page_is_self_contained():
# никаких внешних ресурсов (CSP-безопасно): ни http-ссылок в src/href, ни CDN
assert "src=\"http" not in PAGE_HTML
assert "href=\"http" not in PAGE_HTML
def test_render_plot_png_returns_image_bytes(tmp_path):
from gausse.web.render import render_plot_png
from gausse.web.stats import overview
db = _make_db(tmp_path)
o = overview(db, None)
assert o["top"], "нужен реализуемый прогон для графика"
run_id = o["top"][0]["run_id"]
for kind in ("velocity", "field", "current"):
png = render_plot_png(db, run_id, kind)
assert png is not None and png[:8] == b"\x89PNG\r\n\x1a\n", f"{kind} не PNG"
def test_render_plot_unknown_kind_returns_none(tmp_path):
from gausse.web.render import render_plot_png
db = _make_db(tmp_path)
o = _overview_top_id(db)
assert render_plot_png(db, o, "nonsense") is None
def _overview_top_id(db):
from gausse.web.stats import overview
return overview(db, None)["top"][0]["run_id"]
def test_render_animation_gif_returns_gif_bytes(tmp_path):
from gausse.web.render import render_animation_gif
db = _make_db(tmp_path)
run_id = _overview_top_id(db)
gif = render_animation_gif(db, run_id)
assert gif is not None and gif[:6] in (b"GIF87a", b"GIF89a")