(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 = '
' +
escapeHtml(row.last_user || '(empty)') +
'' +
'' +
escapeHtml(row.last_agent || '(empty)') +
'' +
'