/* ESPILON C2 — Command send + poll mixin for Alpine.js */ function commander() { return { cmdPending: {}, cmdHistory: [], async sendCommand(deviceIds, command, argv = []) { const payload = { device_ids: deviceIds, command, argv }; const res = await fetch('/api/commands', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await res.json(); if (data.error) throw new Error(data.error); for (const r of (data.results || [])) { if (r.status === 'ok' && r.request_id) { this.cmdPending[r.request_id] = { device_id: r.device_id, command, status: 'pending', output: [] }; this._pollResult(r.request_id); } this.cmdHistory.unshift({ ...r, command, argv, time: new Date().toISOString() }); } if (this.cmdHistory.length > 200) this.cmdHistory.length = 200; return data; }, _pollResult(requestId) { let attempts = 0; const poll = async () => { attempts++; try { const res = await fetch('/api/commands/' + encodeURIComponent(requestId)); const data = await res.json(); const entry = this.cmdPending[requestId]; if (entry) { entry.output = data.output || []; if (data.status === 'completed' || data.status === 'error' || attempts >= 60) { entry.status = data.status || 'completed'; clearInterval(iv); } } } catch (e) { if (attempts >= 60) clearInterval(iv); } }; const iv = setInterval(poll, 500); setTimeout(poll, 200); }, getPendingOutput(requestId) { const entry = this.cmdPending[requestId]; return entry ? entry.output : []; }, isPending(requestId) { const entry = this.cmdPending[requestId]; return entry && entry.status === 'pending'; } }; }