// cockpit/fester-module/targets.js // // Target registry + WebSocket subscription. // Exposes window.FesterTargets with onUpdate() / update() / reset() // so that live_dag.html can subscribe to a stable API even when // loaded outside of Cockpit (e.g. via the mock dev server). (function () { 'use strict'; // ---------- State ---------- const state = { targets: [], nodes: [], // DAG nodes edges: [], // DAG edges byNodeId: {}, }; const subs = new Set(); function emit() { subs.forEach(cb => { try { cb(state); } catch (e) { console.error('targets subscriber error', e); } }); } // ---------- Public API ---------- const FesterTargets = { // Subscribe to snapshot updates. // cb(snapshot) — snapshot = { targets, nodes, edges, byNodeId } // Returns an unsubscribe function. onUpdate(cb) { subs.add(cb); try { cb(state); } catch (e) { console.error(e); } return () => subs.delete(cb); }, // Patch the snapshot. // patch = { targets?, nodes?, edges? } // nodes are upserted by id; edges are deduped by from+to. update(patch) { if (patch.targets) { state.targets = patch.targets; } if (patch.nodes) { patch.nodes.forEach(n => { const existing = state.byNodeId[n.id]; state.byNodeId[n.id] = existing ? Object.assign(existing, n) : n; }); state.nodes = Object.values(state.byNodeId); } if (patch.edges) { const seen = new Set(state.edges.map(e => e.from + '→' + e.to)); patch.edges.forEach(e => { const k = e.from + '→' + e.to; if (!seen.has(k)) { state.edges.push(e); seen.add(k); } }); } emit(); }, reset() { state.nodes = []; state.edges = []; state.byNodeId = {}; emit(); }, get() { return state; }, }; window.FesterTargets = FesterTargets; // ---------- Target list (REST) ---------- async function fetchTargets() { try { const res = await fetch('/api/targets'); const targets = await res.json(); FesterTargets.update({ targets }); renderTargets(); } catch (err) { console.error('Failed to fetch targets:', err); } } function renderTargets() { let sidebar = document.getElementById('sidebar'); if (!sidebar) return; let panel = document.getElementById('targets-panel'); if (!panel) { panel = document.createElement('div'); panel.id = 'targets-panel'; panel.className = 'panel'; sidebar.appendChild(panel); } panel.innerHTML = '