(function () { const STORAGE_KEY = 'agent_notify_token'; const POLL_MS = 3000; const elHost = document.getElementById('filter-host'); const elAgent = document.getElementById('filter-agent'); const elStatus = document.getElementById('filter-status'); const elCwd = document.getElementById('filter-cwd'); const elSessions = document.getElementById('sessions'); const elError = document.getElementById('error'); const btnToken = document.getElementById('btn-token'); let expanded = new Set(); let pollTimer = null; function getToken() { return sessionStorage.getItem(STORAGE_KEY) || ''; } function promptToken() { const current = getToken(); const t = window.prompt('API token (Bearer):', current); if (t === null) return false; const trimmed = t.trim(); if (!trimmed) { sessionStorage.removeItem(STORAGE_KEY); return false; } sessionStorage.setItem(STORAGE_KEY, trimmed); return true; } function ensureToken() { if (getToken()) return true; return promptToken(); } function showError(msg) { if (!msg) { elError.textContent = ''; elError.classList.add('hidden'); return; } elError.textContent = msg; elError.classList.remove('hidden'); } function basename(path) { if (!path) return ''; const i = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); return i >= 0 ? path.slice(i + 1) : path; } function formatMachine(row) { const ips = (row.ips || []).join(', '); return row.hostname + (ips ? ' (' + ips + ')' : ''); } function formatTime(iso) { if (!iso) return ''; try { const d = new Date(iso); return isNaN(d.getTime()) ? iso : d.toLocaleString(); } catch { return iso; } } function escapeHtml(s) { const div = document.createElement('div'); div.textContent = s; return div.innerHTML; } async function apiGet(path) { const token = getToken(); if (!token) throw new Error('No token'); const res = await fetch(path, { headers: { Authorization: 'Bearer ' + token }, }); if (res.status === 401) { sessionStorage.removeItem(STORAGE_KEY); throw new Error('Unauthorized — check token'); } if (!res.ok) { throw new Error(res.status + ' ' + res.statusText); } return res.json(); } function queryParams() { const q = new URLSearchParams(); if (elHost.value) q.set('host', elHost.value); if (elAgent.value) q.set('agent', elAgent.value); if (elStatus.value) q.set('status', elStatus.value); if (elCwd.value) q.set('cwd', elCwd.value); const s = q.toString(); return s ? '?' + s : ''; } function fillSelect(select, values, keepValue) { const current = keepValue ? select.value : ''; while (select.options.length > 1) select.remove(1); for (const v of values || []) { const opt = document.createElement('option'); opt.value = v; opt.textContent = v; select.appendChild(opt); } if (current && [...select.options].some((o) => o.value === current)) { select.value = current; } } async function loadMeta() { const meta = await apiGet('/api/v1/meta'); fillSelect(elHost, meta.hosts, true); fillSelect(elAgent, meta.agents, true); fillSelect(elCwd, meta.cwds, true); } async function loadStatus() { const rows = await apiGet('/api/v1/status' + queryParams()); renderTable(Array.isArray(rows) ? rows : []); } function renderTable(rows) { if (rows.length === 0) { elSessions.innerHTML = 'No sessions'; return; } const parts = []; for (const row of rows) { const key = row.session_key || row.hostname + row.cwd + row.agent; const offline = row.status === 'offline'; const cls = offline ? ' class="offline"' : ''; const open = expanded.has(key); parts.push( '' + '' + escapeHtml(formatMachine(row)) + '' + '' + escapeHtml(basename(row.cwd)) + '' + '' + escapeHtml(row.agent || '') + '' + '' + escapeHtml(row.status || '') + '' + '' + escapeHtml(formatTime(row.updated_at)) + '' + '' + '' ); if (open) { parts.push( '' + '
last_user
' + '
' +
            escapeHtml(row.last_user || '(empty)') +
            '
' + '
last_agent
' + '
' +
            escapeHtml(row.last_agent || '(empty)') +
            '
' + '' ); } } elSessions.innerHTML = parts.join(''); } async function refresh() { if (!ensureToken()) { showError('Token required'); return; } try { showError(''); await Promise.all([loadMeta(), loadStatus()]); } catch (e) { showError(e.message || String(e)); if (String(e.message || '').includes('Unauthorized')) { if (promptToken()) refresh(); } } } function startPoll() { stopPoll(); pollTimer = setInterval(refresh, POLL_MS); } function stopPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } } [elHost, elAgent, elStatus, elCwd].forEach((el) => { el.addEventListener('change', refresh); }); btnToken.addEventListener('click', () => { promptToken(); refresh(); }); elSessions.addEventListener('click', (e) => { const btn = e.target.closest('.btn-expand'); if (!btn) return; const key = btn.getAttribute('data-key'); if (expanded.has(key)) expanded.delete(key); else expanded.add(key); refresh(); }); if (!getToken()) promptToken(); refresh(); startPoll(); })();