controller: GET /layouts/{instance} + UI fetch dynamic layout list

UI loadLayouts() теперь fetches /layouts/{inst} — берёт actual layout_map
из config'а (не hardcoded), показывает только existing layouts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gx
2026-05-21 06:04:48 +01:00
parent d7b3e34c6b
commit c9c5b93ef8
2 changed files with 20 additions and 12 deletions
@@ -85,6 +85,15 @@ def create_app(
await dispatcher.handle(instance, "layout.set", req.layout)
return {"ok": True, "instance": instance, "layout": req.layout}
@app.get("/layouts/{instance}")
async def layouts_for_instance(instance: str) -> dict[str, Any]:
inst = _check_instance(instance)
return {
"instance": instance,
"layouts": list(inst.layout_map.keys()),
"current": await state.get_layout(instance),
}
# ─── Overlays ──────────────────────────────────────────────────
@app.post("/overlay/{instance}/add")
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<title>cuda-grid control</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<script src="/ui/lib/hls.js"></script>
<style>
:root { --bg:#1a1a1f; --fg:#e6e6e6; --accent:#4af; --muted:#888; --good:#4f8; --warn:#fa4; --err:#f44; }
* { box-sizing:border-box; }
@@ -171,13 +171,12 @@ async function api(method, path, body=null, okMsg='ok') {
// ── Layout buttons ────────────────────────────────────
async function loadLayouts() {
const r = await fetch('/state');
const r = await fetch(`/layouts/${INSTANCE}`);
if (!r.ok) return;
// Hardcode для simplicity — реальный list в controller config layout_map
const layouts = ['quad', 'single', 'main_plus_preview'];
const d = await r.json();
const box = document.getElementById('layout-buttons');
box.innerHTML = '';
layouts.forEach(name => {
d.layouts.forEach(name => {
const b = document.createElement('button');
b.textContent = name;
b.onclick = () => api('POST', `/layout/${INSTANCE}/set`, {layout:name}, '→ '+name);
@@ -304,13 +303,13 @@ async function refreshState() {
}
// ── Init ──────────────────────────────────────────────
ovTypeChanged();
initVideo();
loadLayouts();
loadAudio();
loadHistory();
refreshState();
setInterval(refreshState, 2000);
// Defensive init — async (non-video) FIRST so кнопки работают независимо от HLS.
try { ovTypeChanged(); } catch(e) { console.error('ovTypeChanged', e); }
try { loadLayouts(); } catch(e) { console.error('loadLayouts', e); }
try { loadAudio(); } catch(e) { console.error('loadAudio', e); }
try { loadHistory(); } catch(e) { console.error('loadHistory', e); }
try { refreshState(); setInterval(refreshState, 2000); } catch(e) { console.error('refreshState', e); }
try { initVideo(); } catch(e) { console.error('initVideo', e); setStatus('video init failed (HLS?)', true); }
</script>
</body>
</html>