controller: snapshot history + layout_map + UI grid

snapshot_history.py — async periodic capture per instance:
  interval_sec / keep_last (FIFO eviction) / dir configurable
  GET /snapshots/{instance}?limit=N → list metadata
  GET /snapshots/{instance}/{filename} → image bytes
  Persisted в /var/lib/cuda-grid/snapshots/{instance}/<ts>.png

layout_map / layout_filter_target в InstanceCfg — для будущей runtime switch
архитектуры (через streamselect либо filter rework — выбор за Phase 7).
Текущий _set_layout dispatches к layout_filter_target c index из map.

UI:
  + Layout buttons (quad/single/main_plus_preview placeholder)
  + Snapshot history grid с thumbnails (loaded /snapshots/{inst}?limit=24)
  + "Reload history" button

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gx
2026-05-21 05:45:11 +01:00
parent d90c139dce
commit d7b3e34c6b
6 changed files with 228 additions and 9 deletions
+4 -3
View File
@@ -219,12 +219,12 @@ class CommandDispatcher:
# ─── Layout ────────────────────────────────────────────────────
async def _set_layout(self, inst: InstanceCfg, layout: str) -> None:
if layout not in PREDEFINED_LAYOUTS:
if layout not in inst.layout_map:
log.warning(
"dispatch.unknown_layout",
instance=inst.name,
layout=layout,
available=PREDEFINED_LAYOUTS,
available=list(inst.layout_map.keys()),
)
return
@@ -232,12 +232,13 @@ class CommandDispatcher:
client = self._client(inst)
try:
reply = await client.send_command(
inst.filter_target, "layout", layout
inst.layout_filter_target, "map", str(inst.layout_map[layout])
)
log.info(
"dispatch.layout_set",
instance=inst.name,
layout=layout,
index=inst.layout_map[layout],
ffmpeg_reply=reply,
)
except (TimeoutError, Exception) as e: