// Remote Panel Logic let remoteDataLoaded = false; function toggleRemotePanel() { const panel = document.getElementById('remotePanel'); const backdrop = document.getElementById('remoteBackdrop'); if (panel.classList.contains('translate-x-full')) { // Open panel.classList.remove('translate-x-full'); backdrop.classList.remove('hidden'); setTimeout(() => backdrop.classList.remove('opacity-0'), 10); if (!remoteDataLoaded) { loadRemoteList(); } } else { // Close panel.classList.add('translate-x-full'); backdrop.classList.add('opacity-0'); setTimeout(() => backdrop.classList.add('hidden'), 300); } } function loadRemoteList() { const container = document.getElementById('remoteListContainer'); container.innerHTML = '

Loading remote IDs...
'; fetch('/api/agents/remote-list') .then(res => res.text()) .then(text => { let res; try { res = JSON.parse(text); } catch (e) { console.error("JSON Parse Error:", e, "Response Text:", text); container.innerHTML = '

Parse error. Server returned:
' + text.substring(0, 200).replace(/
'; return; } try { if (res.status === 'success') { remoteDataLoaded = true; renderRemoteList(res.data); } else { container.innerHTML = '

' + (res.message || 'Failed to load data.') + '
'; } } catch (e) { console.error("Render Error:", e); container.innerHTML = '

UI Error: ' + e.message + '
'; } }) .catch(err => { console.error("Fetch Error:", err); container.innerHTML = '

Connection error: ' + err.message + '
'; }); } function toggleRemoteGroup(listId, iconId) { const list = document.getElementById(listId); const icon = document.getElementById(iconId); if (list.classList.contains('hidden')) { list.classList.remove('hidden'); icon.classList.remove('fa-chevron-down'); icon.classList.add('fa-chevron-up'); } else { list.classList.add('hidden'); icon.classList.remove('fa-chevron-up'); icon.classList.add('fa-chevron-down'); } } function renderRemoteList(data) { const container = document.getElementById('remoteListContainer'); container.innerHTML = ''; if (data.length === 0) { container.innerHTML = '
No agents found.
'; return; } // Sort alphabetically by name data.sort((a, b) => a.name.localeCompare(b.name)); const onlineAgents = data.filter(a => a.status === 'active'); const offlineAgents = data.filter(a => a.status !== 'active'); // Build HTML for Online Group (Expanded by default) const onlineHtml = `
${onlineAgents.map(ag => createRemoteItemHtml(ag, true)).join('')}
`; // Build HTML for Offline Group (Collapsed by default) const offlineHtml = `
`; container.innerHTML = onlineHtml + offlineHtml; filterRemoteList(); } function createRemoteItemHtml(ag, isOnline) { const dotColor = isOnline ? 'bg-emerald-500 shadow-[0_0_5px_rgba(16,185,129,0.5)]' : 'bg-slate-300'; let displayId = ag.remote_id ? ag.remote_id.toString() : '-'; let programIcon = ''; if (ag.remote_type === 'teamviewer') { programIcon = ''; } else if (ag.remote_type === 'anydesk') { programIcon = ''; } return `
${programIcon}
${ag.name}
${displayId}
`; } function filterRemoteList() { const input = document.getElementById('remoteSearch').value.toLowerCase(); const hideNoRemote = document.getElementById('hideNoRemote').checked; const items = document.querySelectorAll('.remote-item'); let onlineVisible = 0; let offlineVisible = 0; items.forEach(item => { const name = item.querySelector('.name-field').innerText.toLowerCase(); const id = item.querySelector('.id-field').innerText.trim().toLowerCase(); const isOnline = item.parentElement.id === 'online-list'; const hasNoId = (id === '-' || id === ''); const matchSearch = name.includes(input) || id.includes(input) || input.replace(/\s/g, '') === id.replace(/\s/g, ''); if (matchSearch) { if (hideNoRemote && hasNoId) { item.style.display = 'none'; } else { item.style.display = 'flex'; if (isOnline) onlineVisible++; else offlineVisible++; } } else { item.style.display = 'none'; } }); document.getElementById('online-count').innerText = onlineVisible; document.getElementById('offline-count').innerText = offlineVisible; } $(document).ready(function() { $('#agentsTable').DataTable({ pageLength: 10, language: { search: "_INPUT_", searchPlaceholder: "Search agents..." }, dom: '<"flex flex-col md:flex-row justify-between items-center mb-4"lf>rt<"flex flex-col md:flex-row justify-between items-center mt-4"ip>' }); }); window.viewSpecs = function(agentId, agentName) { Swal.fire({ title: ' - ' + agentName, html: '
', showConfirmButton: false, showCloseButton: true, width: '800px', customClass: { popup: 'rounded-xl shadow-2xl' } }); $.ajax({ url: `/api/agents/${agentId}/details`, method: 'GET', success: function(res) { if (res.status === 'success') { const tmpl = document.getElementById('specsTemplate').innerHTML; Swal.update({ html: tmpl }); const modal = Swal.getHtmlContainer(); const hw = (res.hardware && res.hardware.length > 0) ? res.hardware[0] : {}; // CPU if (hw.cpu && hw.cpu.name) { const cores = hw.cpu.cores ? ` (${hw.cpu.cores} Cores)` : ''; modal.querySelector('#spec-cpu').innerText = hw.cpu.name + cores; } else { modal.querySelector('#spec-cpu').innerText = '-'; } // RAM if (hw.ram && hw.ram.total) { const totalMB = hw.ram.total / 1024; const gb = (totalMB / 1024).toFixed(2); let usageHtml = `${gb} GB`; if (hw.ram.free !== undefined) { const freeMB = hw.ram.free / 1024; const usageMB = Math.max(0, totalMB - freeMB); const pct = Math.round((usageMB / totalMB) * 100); const alertThresh = ; const colorClass = pct >= alertThresh ? 'text-rose-600 font-bold' : (pct >= alertThresh - 15 ? 'text-orange-500' : 'text-emerald-600'); usageHtml = `${gb} GB ${pct}% Used`; } modal.querySelector('#spec-ram').innerHTML = usageHtml; } else { modal.querySelector('#spec-ram').innerText = '-'; } // Disk const diskContainer = modal.querySelector('#spec-disk'); let diskTotal = 0, diskFree = 0, diskUsed = 0; if (res.disk_info) { let diskData = res.disk_info; if (typeof diskData === 'string') { try { diskData = JSON.parse(diskData); } catch (e) {} } if (diskData && !Array.isArray(diskData) && typeof diskData === 'object') { diskData = [diskData]; } if (diskData && Array.isArray(diskData)) { diskData = diskData.filter(disk => { let d = String(disk.DriveLetter || disk.Drive || disk.DeviceID || disk.Name || ''); return d.toUpperCase().includes('C'); }); if (diskData.length > 0) { let dInfo = diskData[0]; diskTotal = parseFloat(dInfo.Total_GB || dInfo.TotalSpace || dInfo.SizeGB || (dInfo.Size ? dInfo.Size / 1073741824 : 0)); diskFree = parseFloat(dInfo.Free_GB || dInfo.FreeGB || (dInfo.FreeSpace ? dInfo.FreeSpace / 1073741824 : 0)); // If parsed values are extraordinarily large (e.g., FreeSpace was just a number in bytes but not named FreeSpace) // or if TotalSpace was in bytes if (dInfo.TotalSpace && dInfo.TotalSpace > 100000) diskTotal = diskTotal / 1073741824; if (dInfo.FreeSpace && dInfo.FreeSpace > 100000) diskFree = diskFree / 1073741824; diskUsed = parseFloat(dInfo.Used_GB || (diskTotal - diskFree)); diskUsed = Math.max(0, diskUsed); } } } if (diskTotal > 0) { const pct = Math.round((diskUsed / diskTotal) * 100); const alertThresh = ; let colorClass = 'bg-emerald-500'; if (pct >= alertThresh) colorClass = 'bg-rose-500'; else if (pct >= alertThresh - 15) colorClass = 'bg-orange-500'; diskContainer.innerHTML = `
Capacity: ${diskTotal.toFixed(2)} GB ${diskUsed.toFixed(2)} GB Used (${diskFree.toFixed(2)} GB Free)
`; } else { diskContainer.innerHTML = '
No disk information available
'; } // Remote Access const remoteContainer = modal.querySelector('#spec-remote'); remoteContainer.innerHTML = ''; let tvInfo = null; if (res.tv_info) { let t = Array.isArray(res.tv_info) ? res.tv_info[0] : res.tv_info; tvInfo = (t && t.TeamViewerID && t.TeamViewerID !== 'Not Found') ? t.TeamViewerID : null; } let adInfo = null; if (res.ad_info) { let a = Array.isArray(res.ad_info) ? res.ad_info[0] : res.ad_info; adInfo = (a && a.AnyDeskID && a.AnyDeskID !== 'Not Found') ? a.AnyDeskID : null; } if (tvInfo || adInfo) { if (tvInfo) { let tvId = tvInfo.toString(); if (tvId.match(/^\d{9,10}$/)) { tvId = tvId.replace(/(\d{1,2})(\d{3})(\d{3})(\d{3})?/, "$1 $2 $3 $4").trim(); } remoteContainer.innerHTML += `
TeamViewer
${tvId}
`; } if (adInfo) { let adId = adInfo.toString(); if (adId.match(/^\d{9,10}$/)) { adId = adId.replace(/(\d{3})(\d{3})(\d{3})?/, "$1 $2 $3").trim(); } remoteContainer.innerHTML += `
AnyDesk
${adId}
`; } } else { remoteContainer.innerHTML = '
No remote access ID found
'; } // Network const netContainer = modal.querySelector('#spec-network'); netContainer.innerHTML = ''; let networkArray = []; if (res.netiface && Array.isArray(res.netiface)) { res.netiface.forEach(iface => { let ips = []; if (res.netaddr && Array.isArray(res.netaddr)) { res.netaddr.forEach(addr => { if (addr.iface === iface.name && addr.address) { ips.push(addr.address); } }); } networkArray.push({ name: iface.name, mac: iface.mac, state: iface.state, ips: ips }); }); } if (networkArray.length > 0) { let validIfaces = networkArray.filter(n => n.mac && n.mac !== '00:00:00:00:00:00' && n.ips && n.ips.length > 0); validIfaces.forEach(iface => { let stateText = iface.state ? iface.state.toLowerCase() : 'unknown'; if (stateText === 'unknown') stateText = (iface.ips.length > 0 ? 'up' : 'down'); iface._stateText = stateText; let isExtra = false; let n = iface.name.toLowerCase(); const extraWords = ['virtual', 'vmware', 'vbox', 'vethernet', 'bluetooth', 'pseudo', 'vpn', 'loopback', 'docker', 'br-', 'tailscale', 'zerotier', 'hamachi', 'wg', 'local area connection']; for (let word of extraWords) { if (n.includes(word)) { isExtra = true; break; } } iface._isExtra = isExtra; }); validIfaces.sort((a, b) => { if (a._stateText === 'up' && b._stateText !== 'up') return -1; if (a._stateText !== 'up' && b._stateText === 'up') return 1; if (!a._isExtra && b._isExtra) return -1; if (a._isExtra && !b._isExtra) return 1; return 0; }); const toggleCb = document.getElementById('toggle-extra-net'); if (toggleCb) toggleCb.checked = false; if (validIfaces.length > 0) { validIfaces.forEach(iface => { let stateBadge = ''; if (iface._stateText === 'up') { stateBadge = 'Active'; } else { stateBadge = 'Offline'; } const div = document.createElement('div'); div.className = `bg-slate-50 p-2 rounded border border-slate-200 ${iface._isExtra ? 'iface-extra hidden' : ''}`; div.innerHTML = `
${iface.name.length > 20 ? iface.name.substring(0,20)+'...' : iface.name}
${stateBadge}
IP: ${iface.ips.join(', ')}
: ${iface.mac || 'N/A'}
`; netContainer.appendChild(div); }); let visibleCount = validIfaces.filter(i => !i._isExtra).length; if (visibleCount === 0) { const placeholder = document.createElement('div'); placeholder.className = 'text-slate-400 text-center py-2 col-span-full iface-placeholder'; placeholder.innerHTML = 'No primary interfaces found. Click "Show All" to view others.'; netContainer.appendChild(placeholder); if (toggleCb) { toggleCb.addEventListener('change', function() { placeholder.style.display = this.checked ? 'none' : 'block'; }); } } } else { netContainer.innerHTML = '
No active LAN/WiFi interfaces found
'; } } else { netContainer.innerHTML = '
No network data available
'; } } else { Swal.update({ icon: 'error', title: 'Error', html: res.message || '', showConfirmButton: true }); } }, error: function() { Swal.update({ icon: 'error', title: 'Error', html: '', showConfirmButton: true }); } }); };