// 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('= $baseUrl ?>/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 = `
${offlineAgents.map(ag => createRemoteItemHtml(ag, false)).join('')}
`;
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: '= __('Hardware Specs') ?> - ' + agentName,
html: '
',
showConfirmButton: false,
showCloseButton: true,
width: '800px',
customClass: { popup: 'rounded-xl shadow-2xl' }
});
$.ajax({
url: `= $baseUrl ?>/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 = = $_ENV['ALERT_RAM_PERCENT'] ?? 85 ?>;
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 = = $_ENV['ALERT_DISK_PERCENT'] ?? 85 ?>;
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 += `
`;
}
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 += `
`;
}
} 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(', ')}
= __('MAC Address') ?>:
${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 || '= __('Failed to load specifications.') ?>',
showConfirmButton: true
});
}
},
error: function() {
Swal.update({
icon: 'error',
title: 'Error',
html: '= __('Failed to connect to the server.') ?>',
showConfirmButton: true
});
}
});
};