/* atxpi — браузерный клиент (без сборки, vanilla JS). */ 'use strict'; const TOKEN = 'atxpi_token'; let term = null, ws = null, termOpen = false; function getToken() { return localStorage.getItem(TOKEN) || sessionStorage.getItem(TOKEN) || ''; } function setToken(t) { localStorage.setItem(TOKEN, t); } function hdr() { return { 'Authorization': 'Bearer ' + getToken() }; } /* ── API ─────────────────────────────────────────────────────────── */ async function api(path, opts = {}) { const headers = Object.assign({}, hdr(), opts.headers || {}); const r = await fetch(path, Object.assign({}, opts, { headers })); if (r.status === 401) { showLogin(); throw new Error('unauthorized'); } return r.json(); } async function refresh() { try { const s = await api('/api/status'); document.getElementById('powerState').textContent = s.power === 'on' ? '🟢 ON' : '🔴 OFF'; document.getElementById('status').textContent = `driver=${s.driver} · serial=${s.serial_port}@${s.serial_baud}` + (s.mode === 'console' ? ' · режим: только консоль' : ''); const pc = document.getElementById('powerControls'); if (pc) pc.style.display = (s.mode === 'console') ? 'none' : ''; } catch (e) { /* уже обработано в api() */ } } async function doPower(action) { try { await api('/api/power/' + action, { method: 'POST' }); await refresh(); } catch (e) { console.error(e); } } /* ── логин ───────────────────────────────────────────────────────── */ async function login() { const t = document.getElementById('tok').value.trim(); const r = await fetch('/api/status', { headers: { 'Authorization': 'Bearer ' + t } }); if (r.ok) { setToken(t); showApp(); } else { document.getElementById('loginMsg').textContent = 'Неверный токен'; } } function showLogin() { document.getElementById('login').classList.remove('hidden'); document.getElementById('app').classList.add('hidden'); } function showApp() { document.getElementById('login').classList.add('hidden'); document.getElementById('app').classList.remove('hidden'); refresh(); } /* ── консоль (xterm.js + WebSocket) ──────────────────────────────── */ function toggleConsole() { if (termOpen) { disconnectConsole(); return; } term = new Terminal({ cursorBlink: true, fontSize: 13, convertEol: true, theme: { background: '#0b0f14', foreground: '#d7d7d7', cursor: '#4e8cff' }, }); term.open(document.getElementById('term')); const scheme = (location.protocol === 'https:') ? 'wss' : 'ws'; ws = new WebSocket( scheme + '://' + location.host + '/ws/console?token=' + encodeURIComponent(getToken()) ); ws.binaryType = 'arraybuffer'; ws.onopen = () => { termOpen = true; document.getElementById('consoleState').textContent = 'подключена'; term.writeln('\x1b[32m(консоль подключена)\x1b[0m'); }; ws.onmessage = (ev) => { if (typeof ev.data === 'string') term.write(ev.data); else term.write(new Uint8Array(ev.data)); }; ws.onclose = () => { disconnectConsole(true); }; term.onData((d) => { if (ws && ws.readyState === 1) ws.send(d); }); } function disconnectConsole(byRemote = false) { if (ws) { try { ws.close(); } catch (e) {} ws = null; } if (term) { try { term.dispose(); } catch (e) {} term = null; } termOpen = false; document.getElementById('consoleState').textContent = 'не подключена'; if (byRemote) { /* уже закрыто сервером */ } } /* ── init ────────────────────────────────────────────────────────── */ if (getToken()) showApp(); else showLogin();