document.addEventListener('DOMContentLoaded', () => { const addForm = document.getElementById('add-form'); const instancesList = document.getElementById('instances-list'); const refreshBtn = document.getElementById('refresh-btn'); const portInput = document.getElementById('port'); const ufwCmd = document.getElementById('ufw-cmd'); const iptablesCmd = document.getElementById('iptables-cmd'); let config = { firewall_host_ip: null }; const updateFirewallCmds = () => { const port = portInput.value || '4646'; const hostIp = config.firewall_host_ip || window.location.hostname; ufwCmd.textContent = `sudo ufw allow from ${hostIp} to any port ${port} proto tcp`; iptablesCmd.textContent = `sudo iptables -I INPUT -p tcp -s ${hostIp} --dport ${port} -j ACCEPT`; }; const fetchConfig = async () => { try { const res = await fetch('/api/config'); if (res.ok) { config = await res.json(); updateFirewallCmds(); } } catch (e) { console.error('Failed to fetch config:', e); } }; portInput.addEventListener('input', updateFirewallCmds); // Initial fetch of config and instances fetchConfig(); updateFirewallCmds(); const fetchInstances = async () => { try { const res = await fetch('/api/instances'); if (res.status === 401) { return; // Let browser handle auth } const data = await res.json(); renderInstances(data); } catch (e) { console.error('Failed to fetch instances:', e); } }; const renderInstances = (instances) => { if (instances.length === 0) { instancesList.innerHTML = '
No instances configured yet.
'; return; } instancesList.innerHTML = instances.map(inst => { let statusClass = 'status-unknown'; if (inst.last_status === 'Sent') statusClass = 'status-sent'; else if (inst.last_status === 'Offline') statusClass = 'status-offline'; else if (inst.last_status.startsWith('Failed')) statusClass = 'status-offline'; const timeAgo = inst.last_checked ? Math.round((Date.now()/1000 - inst.last_checked)) + 's ago' : 'Never'; return `

${escapeHtml(inst.name)}

${escapeHtml(inst.ip)}:${inst.port} ${escapeHtml(inst.last_status)} Last checked: ${timeAgo}
`; }).join(''); }; addForm.addEventListener('submit', async (e) => { e.preventDefault(); const name = document.getElementById('name').value; const ip = document.getElementById('ip').value; const port = parseInt(document.getElementById('port').value, 10); const secret = document.getElementById('secret').value; try { const res = await fetch('/api/instances', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, ip, port, secret }) }); if (res.ok) { addForm.reset(); document.getElementById('port').value = "4646"; fetchInstances(); } else { alert('Failed to add instance'); } } catch (e) { console.error('Error adding instance', e); } }); refreshBtn.addEventListener('click', fetchInstances); window.deleteInstance = async (id) => { if (!confirm('Are you sure you want to delete this instance?')) return; try { const res = await fetch(`/api/instances/${id}`, { method: 'DELETE' }); if (res.ok) { fetchInstances(); } } catch (e) { console.error('Error deleting instance', e); } }; function escapeHtml(unsafe) { if (!unsafe) return ''; return unsafe.toString() .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } fetchInstances(); setInterval(fetchInstances, 5000); });