AUTARCH v1.9 — remote monitoring, SSH manager, daemon, vault, cleanup
- Add Remote Monitoring Station with PIAP device profile system - Add SSH/SSHD manager with fail2ban integration - Add privileged daemon architecture for safe root operations - Add encrypted vault, HAL memory, HAL auto-analyst - Add network security suite, module creator, codex training - Add start.sh launcher script and GTK3 desktop launcher - Remove Output/ build artifacts, installer files, loose docs - Update .gitignore for runtime data and build artifacts - Update README for v1.9 with new launch method, screenshots, and features Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -690,6 +690,9 @@ async function api(endpoint, body, panel) {
|
||||
});
|
||||
const data = await res.json();
|
||||
outJson(panel, data);
|
||||
if (typeof halAnalyze === 'function') {
|
||||
halAnalyze('Android Exploit: ' + endpoint, JSON.stringify(data, null, 2), 'android exploitation', 'android');
|
||||
}
|
||||
return data;
|
||||
} catch(e) {
|
||||
out(panel, 'Error: ' + escHtml(e.message));
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
<span style="font-weight:600;font-size:0.85rem;color:var(--text-secondary)">ADB Mode:</span>
|
||||
<button id="ap-mode-server" class="btn btn-sm active" onclick="apSetMode('server')">Server (Local ADB)</button>
|
||||
<button id="ap-mode-direct" class="btn btn-sm" onclick="apSetMode('direct')">Direct (WebUSB)</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="apAdbKillServer()" title="Kill ADB server process">Kill ADB</button>
|
||||
<button class="btn btn-sm" onclick="apAdbStartServer()" title="Restart ADB server">Start ADB</button>
|
||||
<span id="ap-adb-status" style="font-size:0.78rem;color:var(--text-muted)"></span>
|
||||
<span id="ap-direct-bar" style="display:none;align-items:center;gap:0.5rem">
|
||||
<span id="ap-device-label" style="font-size:0.85rem;color:var(--text-secondary)">Not connected</span>
|
||||
<button class="btn btn-sm" onclick="apDirectConnect()">Connect</button>
|
||||
@@ -387,6 +390,24 @@ let apLastScan = null;
|
||||
let apMode = 'server';
|
||||
|
||||
/* ── Mode Switching ── */
|
||||
function apAdbKillServer() {
|
||||
var s = document.getElementById('ap-adb-status');
|
||||
if (s) s.textContent = 'Killing...';
|
||||
fetch('/hardware/adb/kill-server', {method: 'POST'}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (s) s.textContent = d.ok ? 'Killed' : 'Error';
|
||||
setTimeout(function() { if (s) s.textContent = ''; }, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function apAdbStartServer() {
|
||||
var s = document.getElementById('ap-adb-status');
|
||||
if (s) s.textContent = 'Starting...';
|
||||
fetch('/hardware/adb/start-server', {method: 'POST'}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (s) s.textContent = d.ok ? 'Started' : 'Error';
|
||||
setTimeout(function() { if (s) s.textContent = ''; }, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function apSetMode(mode) {
|
||||
apMode = mode;
|
||||
localStorage.setItem('ap_connection_mode', mode);
|
||||
@@ -542,6 +563,7 @@ function apScan(type) {
|
||||
apSetStatus('Done');
|
||||
apLastScan = data;
|
||||
box.innerHTML = apRenderScan(type, data);
|
||||
halAnalyze('Shield', JSON.stringify(data, null, 2), 'android protection', 'android');
|
||||
}).catch(e => {
|
||||
apSetStatus('Error');
|
||||
box.innerHTML = '<span style="color:#e74c3c">Error: ' + e.message + '</span>';
|
||||
@@ -733,6 +755,7 @@ function apExportReport() {
|
||||
} else {
|
||||
box.innerHTML = '<span style="color:#e74c3c">Error: ' + (data.error || 'Unknown') + '</span>';
|
||||
}
|
||||
halAnalyze('Shield', JSON.stringify(data, null, 2), 'android protection', 'android');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -753,6 +776,7 @@ function apFindDangerous() {
|
||||
});
|
||||
} else h += '<p style="color:#27ae60">No apps with dangerous combos found.</p>';
|
||||
box.innerHTML = h;
|
||||
halAnalyze('Shield', JSON.stringify(data, null, 2), 'android protection', 'android');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -779,6 +803,7 @@ function apAnalyzePerms() {
|
||||
(p.denied||[]).forEach(pm => h += '<li><code>' + pm + '</code></li>');
|
||||
h += '</ul>';
|
||||
box.innerHTML = h;
|
||||
halAnalyze('Shield', JSON.stringify(data, null, 2), 'android protection', 'android');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -805,6 +830,7 @@ function apPermHeatmap() {
|
||||
h += '</tbody></table></div>';
|
||||
if (matrix.length > 50) h += '<p><small>Showing 50 of ' + matrix.length + ' apps</small></p>';
|
||||
box.innerHTML = h;
|
||||
halAnalyze('Shield', JSON.stringify(data, null, 2), 'android protection', 'android');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -826,6 +852,7 @@ function apFix(action) {
|
||||
} else {
|
||||
box.innerHTML = '<span style="color:#e74c3c">Error: ' + (data.error || JSON.stringify(data)) + '</span>';
|
||||
}
|
||||
halAnalyze('Shield', JSON.stringify(data, null, 2), 'android protection', 'android');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -839,6 +866,7 @@ function apFixProxy() {
|
||||
h += '<div>' + (r.ok ? '<span style="color:#27ae60">[OK]</span>' : '<span style="color:#e74c3c">[FAIL]</span>') + ' ' + r.setting + '</div>';
|
||||
});
|
||||
box.innerHTML = h;
|
||||
halAnalyze('Shield', JSON.stringify(data, null, 2), 'android protection', 'android');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -360,6 +360,7 @@ function autoRefreshStatus() {
|
||||
}
|
||||
}
|
||||
}
|
||||
halAnalyze('Autonomy', JSON.stringify(data, null, 2), 'threat monitoring', 'defense');
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -414,6 +415,7 @@ function autoLoadModelsDetail() {
|
||||
</div>`;
|
||||
}
|
||||
container.innerHTML = html;
|
||||
halAnalyze('Autonomy', JSON.stringify(data, null, 2), 'threat monitoring', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -424,6 +426,7 @@ function autoLoadRules() {
|
||||
fetch('/autonomy/rules').then(r => r.json()).then(data => {
|
||||
_rules = data.rules || [];
|
||||
renderRules();
|
||||
halAnalyze('Autonomy', JSON.stringify(data, null, 2), 'threat monitoring', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -684,6 +687,7 @@ function autoLoadActivity() {
|
||||
fetch('/autonomy/activity?limit=100').then(r => r.json()).then(data => {
|
||||
renderActivity(data.entries || []);
|
||||
document.getElementById('activity-count').textContent = (data.total||0) + ' entries';
|
||||
halAnalyze('Autonomy', JSON.stringify(data, null, 2), 'threat monitoring', 'defense');
|
||||
});
|
||||
// Start SSE
|
||||
if (!_activitySSE) {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<ul class="nav-links">
|
||||
<li><a href="{{ url_for('dashboard.index') }}" class="{% if request.endpoint == 'dashboard.index' %}active{% endif %}">Dashboard</a></li>
|
||||
<li><a href="{{ url_for('port_scanner.index') }}" class="{% if request.blueprint == 'port_scanner' %}active{% endif %}">🔍 Port Scanner</a></li>
|
||||
<li><a href="{{ url_for('targets.index') }}" class="{% if request.blueprint == 'targets' %}active{% endif %}">Targets</a></li>
|
||||
<li><a href="{{ url_for('targets.index') }}" class="{% if request.blueprint == 'targets' %}active{% endif %}">Investigations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
@@ -45,7 +45,6 @@
|
||||
<li><a href="{{ url_for('log_correlator.index') }}" class="{% if request.blueprint == 'log_correlator' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Log Correlator</a></li>
|
||||
<li><a href="{{ url_for('container_sec.index') }}" class="{% if request.blueprint == 'container_sec' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Container Sec</a></li>
|
||||
<li><a href="{{ url_for('email_sec.index') }}" class="{% if request.blueprint == 'email_sec' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Email Sec</a></li>
|
||||
<li><a href="{{ url_for('incident_resp.index') }}" class="{% if request.blueprint == 'incident_resp' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Incident Response</a></li>
|
||||
<li><a href="{{ url_for('offense.index') }}" class="{% if request.blueprint == 'offense' %}active{% endif %}">Offense</a></li>
|
||||
<li><a href="{{ url_for('loadtest.index') }}" class="{% if request.blueprint == 'loadtest' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Load Test</a></li>
|
||||
<li><a href="{{ url_for('phishmail.index') }}" class="{% if request.blueprint == 'phishmail' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Gone Fishing</a></li>
|
||||
@@ -53,15 +52,11 @@
|
||||
<li><a href="{{ url_for('hack_hijack.index') }}" class="{% if request.blueprint == 'hack_hijack' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Hack Hijack</a></li>
|
||||
<li><a href="{{ url_for('webapp_scanner.index') }}" class="{% if request.blueprint == 'webapp_scanner' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Web Scanner</a></li>
|
||||
<li><a href="{{ url_for('c2_framework.index') }}" class="{% if request.blueprint == 'c2_framework' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ C2 Framework</a></li>
|
||||
<li><a href="{{ url_for('wifi_audit.index') }}" class="{% if request.blueprint == 'wifi_audit' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ WiFi Audit</a></li>
|
||||
<li><a href="{{ url_for('deauth.index') }}" class="{% if request.blueprint == 'deauth' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Deauth Attack</a></li>
|
||||
<li><a href="{{ url_for('api_fuzzer.index') }}" class="{% if request.blueprint == 'api_fuzzer' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ API Fuzzer</a></li>
|
||||
<li><a href="{{ url_for('cloud_scan.index') }}" class="{% if request.blueprint == 'cloud_scan' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Cloud Scan</a></li>
|
||||
<li><a href="{{ url_for('vuln_scanner.index') }}" class="{% if request.blueprint == 'vuln_scanner' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Vuln Scanner</a></li>
|
||||
<li><a href="{{ url_for('exploit_dev.index') }}" class="{% if request.blueprint == 'exploit_dev' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Exploit Dev</a></li>
|
||||
<li><a href="{{ url_for('ad_audit.index') }}" class="{% if request.blueprint == 'ad_audit' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ AD Audit</a></li>
|
||||
<li><a href="{{ url_for('mitm_proxy.index') }}" class="{% if request.blueprint == 'mitm_proxy' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ MITM Proxy</a></li>
|
||||
<li><a href="{{ url_for('pineapple.index') }}" class="{% if request.blueprint == 'pineapple' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Pineapple</a></li>
|
||||
<li><a href="{{ url_for('counter.index') }}" class="{% if request.blueprint == 'counter' %}active{% endif %}">Counter</a></li>
|
||||
<li><a href="{{ url_for('steganography.index') }}" class="{% if request.blueprint == 'steganography' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Steganography</a></li>
|
||||
<li><a href="{{ url_for('anti_forensics.index') }}" class="{% if request.blueprint == 'anti_forensics' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Anti-Forensics</a></li>
|
||||
@@ -69,7 +64,6 @@
|
||||
<li><a href="{{ url_for('analyze.hash_detection') }}" class="{% if request.endpoint == 'analyze.hash_detection' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Hash Toolkit</a></li>
|
||||
<li><a href="{{ url_for('llm_trainer.index') }}" class="{% if request.blueprint == 'llm_trainer' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ LLM Trainer</a></li>
|
||||
<li><a href="{{ url_for('password_toolkit.index') }}" class="{% if request.blueprint == 'password_toolkit' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Password Toolkit</a></li>
|
||||
<li><a href="{{ url_for('net_mapper.index') }}" class="{% if request.blueprint == 'net_mapper' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Net Mapper</a></li>
|
||||
<li><a href="{{ url_for('report_engine.index') }}" class="{% if request.blueprint == 'report_engine' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Reports</a></li>
|
||||
<li><a href="{{ url_for('ble_scanner.index') }}" class="{% if request.blueprint == 'ble_scanner' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ BLE Scanner</a></li>
|
||||
<li><a href="{{ url_for('forensics.index') }}" class="{% if request.blueprint == 'forensics' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Forensics</a></li>
|
||||
@@ -82,11 +76,24 @@
|
||||
<li><a href="{{ url_for('simulate.legendary_creator') }}" class="{% if request.endpoint == 'simulate.legendary_creator' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Legendary Creator</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Network</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="{{ url_for('network.index') }}" class="{% if request.blueprint == 'network' %}active{% endif %}">🛡 Network Security</a></li>
|
||||
<li><a href="{{ url_for('wireshark.index') }}" class="{% if request.blueprint == 'wireshark' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Wireshark</a></li>
|
||||
<li><a href="{{ url_for('net_mapper.index') }}" class="{% if request.blueprint == 'net_mapper' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Net Mapper</a></li>
|
||||
<li><a href="{{ url_for('wifi_audit.index') }}" class="{% if request.blueprint == 'wifi_audit' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ WiFi Audit</a></li>
|
||||
<li><a href="{{ url_for('deauth.index') }}" class="{% if request.blueprint == 'deauth' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem;color:var(--danger,#f55)">└ Deauth</a></li>
|
||||
<li><a href="{{ url_for('pineapple.index') }}" class="{% if request.blueprint == 'pineapple' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem;color:var(--danger,#f55)">└ Evil Twin / Pineapple</a></li>
|
||||
<li><a href="{{ url_for('mitm_proxy.index') }}" class="{% if request.blueprint == 'mitm_proxy' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem;color:var(--danger,#f55)">└ MITM Proxy</a></li>
|
||||
<li><a href="{{ url_for('remote_monitor.index') }}" class="{% if request.blueprint == 'remote_monitor' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Remote Monitor</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Tools</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="{{ url_for('module_creator.index') }}" class="{% if request.blueprint == 'module_creator' %}active{% endif %}" style="color:var(--accent)">➕ Create Module</a></li>
|
||||
<li><a href="{{ url_for('encmodules.index') }}" class="{% if request.blueprint == 'encmodules' %}active{% endif %}" style="color:var(--danger,#f55)">🔒 Enc Modules</a></li>
|
||||
<li><a href="{{ url_for('wireshark.index') }}" class="{% if request.blueprint == 'wireshark' %}active{% endif %}">Wireshark</a></li>
|
||||
<li><a href="{{ url_for('hardware.index') }}" class="{% if request.blueprint == 'hardware' %}active{% endif %}">Hardware</a></li>
|
||||
<li><a href="{{ url_for('android_exploit.index') }}" class="{% if request.blueprint == 'android_exploit' %}active{% endif %}">Android Exploit</a></li>
|
||||
<li><a href="{{ url_for('sms_forge.index') }}" class="{% if request.blueprint == 'sms_forge' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ SMS Forge</a></li>
|
||||
@@ -102,6 +109,7 @@
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">System</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="{{ url_for('ssh_manager.index') }}" class="{% if request.blueprint == 'ssh_manager' %}active{% endif %}">SSH / SSHD</a></li>
|
||||
<li><a href="{{ url_for('upnp.index') }}" class="{% if request.blueprint == 'upnp' %}active{% endif %}">UPnP</a></li>
|
||||
<li><a href="{{ url_for('wireguard.index') }}" class="{% if request.blueprint == 'wireguard' %}active{% endif %}">WireGuard</a></li>
|
||||
<li><a href="{{ url_for('msf.index') }}" class="{% if request.blueprint == 'msf' %}active{% endif %}">MSF Console</a></li>
|
||||
@@ -109,6 +117,7 @@
|
||||
<li><a href="{{ url_for('dns_service.nameserver') }}" class="{% if request.endpoint == 'dns_service.nameserver' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Nameserver</a></li>
|
||||
<li><a href="{{ url_for('settings.index') }}" class="{% if request.blueprint == 'settings' and request.endpoint not in ('settings.llm_settings', 'settings.deps_index') %}active{% endif %}">Settings</a></li>
|
||||
<li><a href="{{ url_for('settings.llm_settings') }}" class="{% if request.endpoint == 'settings.llm_settings' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ LLM Config</a></li>
|
||||
<li><a href="{{ url_for('settings.mcp_settings') }}" class="{% if request.endpoint == 'settings.mcp_settings' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ MCP Server</a></li>
|
||||
<li><a href="{{ url_for('settings.deps_index') }}" class="{% if request.endpoint == 'settings.deps_index' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Dependencies</a></li>
|
||||
<li><a href="{{ url_for('dashboard.manual') }}" class="{% if request.endpoint == 'dashboard.manual' %}active{% endif %}">User Manual</a></li>
|
||||
<li><a href="{{ url_for('dashboard.manual_windows') }}" class="{% if request.endpoint == 'dashboard.manual_windows' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Windows Guide</a></li>
|
||||
@@ -175,12 +184,16 @@
|
||||
<span class="hal-mode-slider"></span>
|
||||
<span class="hal-mode-label" id="hal-mode-label">Chat</span>
|
||||
</label>
|
||||
<button onclick="halToggleFeedback()" id="hal-feedback-btn" class="hal-feedback-btn"
|
||||
title="Toggle auto-analysis feedback on tool results"
|
||||
style="font-size:0.6rem;padding:1px 5px;border-radius:3px;border:1px solid var(--accent);color:var(--accent);background:transparent;cursor:pointer">FB</button>
|
||||
<button onclick="halToggle()" class="hal-close" title="Close">✕</button>
|
||||
</div>
|
||||
<div id="hal-messages" class="hal-messages"></div>
|
||||
<div class="hal-footer">
|
||||
<input id="hal-input" type="text" placeholder="Ask Hal..." onkeypress="if(event.key==='Enter')halSend()">
|
||||
<button onclick="halSend()" class="btn btn-sm btn-primary">Send</button>
|
||||
<button onclick="halSend()" class="btn btn-sm btn-primary" id="hal-send-btn">Send</button>
|
||||
<button onclick="halStop()" class="btn btn-sm" id="hal-stop-btn" title="Stop generation" style="display:none;background:var(--danger,#ff3b30);color:#fff">Stop</button>
|
||||
<button onclick="halClear()" class="btn btn-sm" title="Clear history">↺</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -202,6 +202,7 @@ function bleScan() {
|
||||
document.getElementById('ble-scan-status').textContent = 'Found ' + bleDevices.length + ' device(s)';
|
||||
bleRenderDevices();
|
||||
bleUpdateDeviceSelector();
|
||||
halAnalyze('BLE Scanner', JSON.stringify(data, null, 2), 'bluetooth', 'network');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -263,6 +264,7 @@ function bleVulnScan() {
|
||||
});
|
||||
bleRenderDevices();
|
||||
}
|
||||
halAnalyze('BLE Scanner', JSON.stringify(data, null, 2), 'bluetooth', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -332,6 +334,7 @@ function bleConnect() {
|
||||
document.getElementById('ble-connect-status').textContent = 'Connected to ' + addr;
|
||||
document.getElementById('btn-ble-disconnect').style.display = '';
|
||||
bleRenderServices(data.services || []);
|
||||
halAnalyze('BLE Scanner', JSON.stringify(data, null, 2), 'bluetooth', 'network');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
|
||||
@@ -323,6 +323,7 @@ function csDockerAudit() {
|
||||
+ escapeHtml(f.detail || '') + '</td></tr>';
|
||||
});
|
||||
document.getElementById('cs-docker-audit-results').innerHTML = html || '<tr><td colspan="4">No findings.</td></tr>';
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -342,6 +343,7 @@ function csListContainers() {
|
||||
+ '</td></tr>';
|
||||
});
|
||||
document.getElementById('cs-containers-list').innerHTML = html || '<tr><td colspan="5" class="empty-state">No containers found.</td></tr>';
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -369,6 +371,7 @@ function csAuditContainer(id, name) {
|
||||
});
|
||||
document.getElementById('cs-container-audit-results').innerHTML = html || '<tr><td colspan="3">No findings.</td></tr>';
|
||||
document.getElementById('cs-container-audit-section').scrollIntoView({behavior: 'smooth'});
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -401,6 +404,7 @@ function csEscapeCheck(id, name) {
|
||||
}
|
||||
document.getElementById('cs-escape-results').innerHTML = html;
|
||||
document.getElementById('cs-escape-section').scrollIntoView({behavior: 'smooth'});
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -448,6 +452,7 @@ function csK8sPods() {
|
||||
+ '</tr>';
|
||||
});
|
||||
document.getElementById('cs-k8s-pods-list').innerHTML = html || '<tr><td colspan="6" class="empty-state">No pods found in this namespace.</td></tr>';
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -476,6 +481,7 @@ function csK8sPodAudit(podName) {
|
||||
});
|
||||
document.getElementById('cs-k8s-pod-audit-results').innerHTML = html || '<tr><td colspan="3">No findings.</td></tr>';
|
||||
sec.scrollIntoView({behavior: 'smooth'});
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -501,6 +507,7 @@ function csK8sRBAC() {
|
||||
});
|
||||
html += '</div>';
|
||||
el.innerHTML = html;
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -526,6 +533,7 @@ function csK8sSecrets() {
|
||||
});
|
||||
html += '</div>';
|
||||
el.innerHTML = html;
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -552,6 +560,7 @@ function csK8sNetPolicies() {
|
||||
}
|
||||
html += '</div>';
|
||||
el.innerHTML = html;
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -596,6 +605,7 @@ function csScanImage() {
|
||||
+ '</tr>';
|
||||
});
|
||||
document.getElementById('cs-scan-results').innerHTML = html;
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -616,6 +626,7 @@ function csListImages() {
|
||||
+ '</tr>';
|
||||
});
|
||||
document.getElementById('cs-images-list').innerHTML = html || '<tr><td colspan="5" class="empty-state">No local images found.</td></tr>';
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -656,6 +667,7 @@ function csLintDockerfile() {
|
||||
+ '</tr>';
|
||||
});
|
||||
document.getElementById('cs-lint-results').innerHTML = html;
|
||||
halAnalyze('Container Security', JSON.stringify(data, null, 2), 'docker/k8s', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ function linuxRunAudit() {
|
||||
+ (c.passed ? 'PASS' : 'FAIL') + '</span></td><td>' + escapeHtml(c.details || '') + '</td></tr>';
|
||||
});
|
||||
document.getElementById('audit-results').innerHTML = html || '<tr><td colspan="3">No results</td></tr>';
|
||||
halAnalyze('Defense: Security Audit', JSON.stringify(data, null, 2), 'linux security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -110,12 +111,14 @@ function linuxRunCheck(name) {
|
||||
return (c.passed ? '[PASS] ' : '[FAIL] ') + c.name + (c.details ? ' — ' + c.details : '');
|
||||
});
|
||||
if (el) el.textContent = lines.join('\n') || 'No results';
|
||||
halAnalyze('Defense: ' + name + ' (single check only, do NOT run a full audit)', JSON.stringify(data, null, 2), 'single check: ' + name, 'defense');
|
||||
}).catch(function() { if (el) el.textContent = 'Request failed'; });
|
||||
}
|
||||
|
||||
function linuxLoadFwRules() {
|
||||
fetchJSON('/defense/linux/firewall/rules').then(function(data) {
|
||||
renderOutput('fw-rules', data.rules || 'Could not load rules');
|
||||
halAnalyze('Defense: Firewall Rules', JSON.stringify(data, null, 2), 'linux security audit', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,6 +128,7 @@ function linuxBlockIP() {
|
||||
postJSON('/defense/linux/firewall/block', {ip: ip}).then(function(data) {
|
||||
renderOutput('fw-result', data.message || data.error);
|
||||
if (data.success) { document.getElementById('block-ip').value = ''; linuxLoadFwRules(); }
|
||||
halAnalyze('Defense: Block IP', JSON.stringify(data, null, 2), 'linux security audit', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -134,6 +138,7 @@ function linuxUnblockIP() {
|
||||
postJSON('/defense/linux/firewall/unblock', {ip: ip}).then(function(data) {
|
||||
renderOutput('fw-result', data.message || data.error);
|
||||
if (data.success) linuxLoadFwRules();
|
||||
halAnalyze('Defense: Unblock IP', JSON.stringify(data, null, 2), 'linux security audit', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -157,6 +162,7 @@ function linuxAnalyzeLogs() {
|
||||
});
|
||||
}
|
||||
renderOutput('log-output', lines.join('\n') || 'No findings.');
|
||||
halAnalyze('Defense: Log Analysis', JSON.stringify(data, null, 2), 'linux security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -776,6 +776,7 @@ function loadConnections() {
|
||||
+ '</td></tr>';
|
||||
});
|
||||
document.getElementById('conn-table').innerHTML = html || '<tr><td colspan="6">No connections found</td></tr>';
|
||||
halAnalyze('Defense Monitor: Connections', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -801,6 +802,7 @@ function niBandwidth() {
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
document.getElementById('ni-bandwidth').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: Bandwidth', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -823,6 +825,7 @@ function niArpCheck() {
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
document.getElementById('ni-arp').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: ARP Check', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -845,6 +848,7 @@ function niNewPorts() {
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
document.getElementById('ni-ports').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: New Ports', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -868,6 +872,7 @@ function niGeoip() {
|
||||
+ '<tr><td>ASN</td><td>' + (data.asn || '—') + '</td></tr>'
|
||||
+ '</tbody></table>';
|
||||
document.getElementById('ni-geoip-result').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: GeoIP Lookup', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -896,6 +901,7 @@ function niConnsGeo() {
|
||||
+ '<td>' + escapeHtml(c.process || '') + '</td></tr>';
|
||||
});
|
||||
document.getElementById('ni-geo-tbody').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: Connections GeoIP', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -911,6 +917,7 @@ function niConnRate() {
|
||||
+ '<tr><td>Peak</td><td>' + (data.peak_rate || 0) + '</td></tr>'
|
||||
+ '</tbody></table>';
|
||||
document.getElementById('ni-conn-rate').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: Connection Rate', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -952,6 +959,7 @@ function loadThreatReport() {
|
||||
data.blocklist.forEach(function(ip) { lines.push(' ' + ip); });
|
||||
}
|
||||
renderOutput('threat-output', lines.join('\n'));
|
||||
halAnalyze('Defense Monitor: Threat Report', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -1025,6 +1033,11 @@ function capDone(stats) {
|
||||
document.getElementById('btn-cap-start').style.display = '';
|
||||
document.getElementById('btn-cap-stop').style.display = 'none';
|
||||
document.getElementById('cap-status').textContent = 'Capture complete' + (stats.packet_count ? ' — ' + stats.packet_count + ' packets' : '');
|
||||
// Send captured packet output to HAL for analysis
|
||||
var packetPanel = document.getElementById('cap-live-packets');
|
||||
if (packetPanel && packetPanel.textContent.trim()) {
|
||||
halAnalyze('Threat Monitor: Packet Capture', packetPanel.textContent, 'packet capture analysis', 'network');
|
||||
}
|
||||
}
|
||||
|
||||
function capProtocols() {
|
||||
@@ -1043,6 +1056,7 @@ function capProtocols() {
|
||||
html += '<p class="empty-state">No packet data. Run a capture first.</p>';
|
||||
}
|
||||
document.getElementById('cap-analysis').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: Protocol Distribution', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1061,6 +1075,7 @@ function capConversations() {
|
||||
html += '<p class="empty-state">No conversation data. Run a capture first.</p>';
|
||||
}
|
||||
document.getElementById('cap-analysis').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: Top Conversations', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1087,6 +1102,7 @@ function ddosDetect() {
|
||||
html += '<pre class="output-panel" style="margin-top:8px;font-size:0.78rem">' + data.indicators.map(escapeHtml).join('\n') + '</pre>';
|
||||
}
|
||||
document.getElementById('ddos-status').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: DDoS Detection', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -1116,6 +1132,7 @@ function ddosTopTalkers() {
|
||||
+ '</td></tr>';
|
||||
});
|
||||
document.getElementById('ddos-talkers-tbody').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: Top Talkers', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -1213,6 +1230,7 @@ function ddosHistory() {
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
document.getElementById('ddos-history').innerHTML = html;
|
||||
halAnalyze('Defense Monitor: Mitigation History', JSON.stringify(data, null, 2), 'security audit', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,31 @@
|
||||
<a href="{{ url_for('defense.index') }}" class="btn btn-sm" style="margin-left:auto">← Defense</a>
|
||||
</div>
|
||||
|
||||
<!-- OS Mismatch Warning -->
|
||||
<div id="os-mismatch-banner" style="display:none;padding:0.75rem 1rem;border:2px solid var(--danger,#ff3b30);border-radius:var(--radius);background:rgba(255,59,48,0.08);margin-bottom:1rem">
|
||||
<strong style="color:var(--danger,#ff3b30)">Wrong platform detected!</strong>
|
||||
<span style="font-size:0.85rem;color:var(--text-secondary)">
|
||||
This system is running <strong id="os-detected">Linux</strong>, not Windows.
|
||||
These scans will not work correctly.
|
||||
<a href="{{ url_for('defense.linux_index') }}" style="color:var(--accent);font-weight:bold">Switch to Linux Defense</a>
|
||||
</span>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
fetch('/defense/', {headers: {'Accept': 'application/json'}})
|
||||
.then(function(r) { return r.json(); })
|
||||
.catch(function() { return null; })
|
||||
.then(function(d) {
|
||||
if (d && d.platform && d.platform !== 'Windows') {
|
||||
var banner = document.getElementById('os-mismatch-banner');
|
||||
var det = document.getElementById('os-detected');
|
||||
if (banner) banner.style.display = '';
|
||||
if (det) det.textContent = d.platform + ' (' + (d.os_version || '') + ')';
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<div class="tab-bar">
|
||||
<button class="tab active" data-tab-group="windef" data-tab="audit" onclick="showTab('windef','audit')">Security Audit</button>
|
||||
@@ -159,6 +184,7 @@ function winRunAudit() {
|
||||
+ (c.passed ? 'PASS' : 'FAIL') + '</span></td><td>' + escapeHtml(c.details || '') + '</td></tr>';
|
||||
});
|
||||
document.getElementById('win-audit-results').innerHTML = html || '<tr><td colspan="3">No results</td></tr>';
|
||||
halAnalyze('Windows Defense', JSON.stringify(data, null, 2), 'windows security', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -171,6 +197,7 @@ function winRunCheck(name) {
|
||||
return (c.passed ? '[PASS] ' : '[FAIL] ') + c.name + (c.details ? ' — ' + c.details : '');
|
||||
});
|
||||
if (el) el.textContent = lines.join('\n') || 'No results';
|
||||
halAnalyze('Windows Defense', JSON.stringify(data, null, 2), 'windows security', 'defense');
|
||||
}).catch(function() { if (el) el.textContent = 'Request failed'; });
|
||||
}
|
||||
|
||||
@@ -218,6 +245,7 @@ function winAnalyzeLogs() {
|
||||
});
|
||||
}
|
||||
renderOutput('win-log-output', lines.join('\n') || 'No findings.');
|
||||
halAnalyze('Windows Defense', JSON.stringify(data, null, 2), 'windows security', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); renderOutput('win-log-output', 'Request failed'); });
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -735,6 +735,7 @@ function checkDNS() {
|
||||
el.innerHTML = 'Status: <span style="color:var(--text-muted);font-weight:600">STOPPED</span>';
|
||||
document.getElementById('dns-metrics').textContent = '';
|
||||
}
|
||||
halAnalyze('DNS Service', JSON.stringify(d, null, 2), 'dns query', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -780,6 +781,7 @@ function loadZones() {
|
||||
s.innerHTML = opts;
|
||||
if (_currentZone) s.value = _currentZone;
|
||||
});
|
||||
halAnalyze('DNS Service', JSON.stringify(d, null, 2), 'dns query', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -834,6 +836,7 @@ function loadRecords() {
|
||||
fetch(`/dns/zones/${zone}/records`).then(r => r.json()).then(d => {
|
||||
_allRecords = (d.ok && d.records) ? d.records : [];
|
||||
filterRecords();
|
||||
halAnalyze('DNS Service', JSON.stringify(d, null, 2), 'dns query', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -945,6 +948,7 @@ function exportZone() {
|
||||
if (!d.ok) { alert(d.error); return; }
|
||||
document.getElementById('exp-output').style.display = '';
|
||||
document.getElementById('exp-text').value = d.zone_file || d.data || JSON.stringify(d, null, 2);
|
||||
halAnalyze('DNS Service', JSON.stringify(d, null, 2), 'dns query', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1140,6 +1144,7 @@ function loadConfig() {
|
||||
// Hosts
|
||||
document.getElementById('cfg-hosts-file').value = c.hosts_file || '';
|
||||
document.getElementById('cfg-hosts-autoload').checked = c.hosts_auto_load === true;
|
||||
halAnalyze('DNS Service', JSON.stringify(d, null, 2), 'dns query', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1208,6 +1213,7 @@ function ezScanNetwork() {
|
||||
ezRenderHosts();
|
||||
|
||||
document.getElementById('ez-scan-status').innerHTML = `<span style="color:#4ade80">Found ${_ezHosts.length} hosts on ${d.subnet || 'network'}</span>`;
|
||||
halAnalyze('DNS Service', JSON.stringify(d, null, 2), 'dns query', 'network');
|
||||
}).catch(e => {
|
||||
document.getElementById('ez-scan-status').innerHTML = `<span style="color:var(--danger)">Scan failed: ${e.message}</span>`;
|
||||
});
|
||||
|
||||
@@ -470,6 +470,7 @@ function esDomainAnalyze(){
|
||||
}
|
||||
renderFindings('es-mx-findings',d.mx.findings);
|
||||
|
||||
halAnalyze('Email Security', JSON.stringify(d, null, 2), 'email analysis', 'defense');
|
||||
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
|
||||
}
|
||||
|
||||
@@ -499,6 +500,7 @@ function esBlCheck(){
|
||||
rows+='<tr><td>'+esc(r.blacklist)+'</td><td>'+st+'</td><td style="font-size:0.82rem">'+esc(r.details)+'</td></tr>';
|
||||
});
|
||||
document.getElementById('es-bl-rows').innerHTML=rows;
|
||||
halAnalyze('Email Security', JSON.stringify(d, null, 2), 'email analysis', 'defense');
|
||||
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
|
||||
}
|
||||
|
||||
@@ -579,6 +581,7 @@ function esAnalyzeHeaders(){
|
||||
} else {
|
||||
findSect.style.display='none';
|
||||
}
|
||||
halAnalyze('Email Security', JSON.stringify(d, null, 2), 'email analysis', 'defense');
|
||||
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
|
||||
}
|
||||
|
||||
@@ -628,6 +631,7 @@ function esDetectPhishing(){
|
||||
});
|
||||
}
|
||||
document.getElementById('es-phish-urls').innerHTML=uHtml;
|
||||
halAnalyze('Email Security', JSON.stringify(d, null, 2), 'email analysis', 'defense');
|
||||
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
|
||||
}
|
||||
|
||||
@@ -651,6 +655,7 @@ function esAbuseReport(){
|
||||
if(d.error){alert(d.error);return;}
|
||||
document.getElementById('es-abuse-output').style.display='';
|
||||
document.getElementById('es-abuse-text').textContent=d.report_text;
|
||||
halAnalyze('Email Security', JSON.stringify(d, null, 2), 'email analysis', 'defense');
|
||||
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
|
||||
}
|
||||
function esAbuseCopy(){
|
||||
@@ -708,6 +713,7 @@ function esMbSearch(){
|
||||
rows='<tr><td colspan="5" class="empty-state">No messages found.</td></tr>';
|
||||
}
|
||||
document.getElementById('es-mb-rows').innerHTML=rows;
|
||||
halAnalyze('Email Security', JSON.stringify(d, null, 2), 'email analysis', 'defense');
|
||||
}).catch(function(e){setLoading(btn,false);document.getElementById('es-mb-status').innerHTML='<span style="color:var(--danger)">'+esc(String(e))+'</span>';});
|
||||
}
|
||||
|
||||
@@ -740,6 +746,7 @@ function esMbView(msgId){
|
||||
attRows='<tr><td colspan="3" class="empty-state">No attachments</td></tr>';
|
||||
}
|
||||
document.getElementById('es-viewer-att-rows').innerHTML=attRows;
|
||||
halAnalyze('Email Security', JSON.stringify(d, null, 2), 'email analysis', 'defense');
|
||||
}).catch(function(e){document.getElementById('es-viewer-headers-text').textContent='Error: '+e;});
|
||||
}
|
||||
|
||||
|
||||
@@ -258,6 +258,7 @@ function forHashFile() {
|
||||
document.getElementById('for-hash-sha1').textContent = data.sha1 || '—';
|
||||
document.getElementById('for-hash-sha256').textContent = data.sha256 || '—';
|
||||
forLogCustody('hash', path, 'Computed hashes', data.sha256 || '');
|
||||
halAnalyze('Digital Forensics', JSON.stringify(data, null, 2), 'forensics', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -283,6 +284,7 @@ function forVerifyHash() {
|
||||
el.style.color = 'var(--danger)';
|
||||
}
|
||||
forLogCustody('verify', path, data.match ? 'Hash verified' : 'Hash mismatch', expected);
|
||||
halAnalyze('Digital Forensics', JSON.stringify(data, null, 2), 'forensics', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -310,6 +312,7 @@ function forCreateImage() {
|
||||
if (data.duration) lines.push('Duration: ' + data.duration + 's');
|
||||
log.textContent = lines.join('\n') || 'Done.';
|
||||
forLogCustody('image', output, 'Disk image created from ' + source, data.hash || '');
|
||||
halAnalyze('Digital Forensics', JSON.stringify(data, null, 2), 'forensics', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -341,6 +344,7 @@ function forCarve() {
|
||||
document.getElementById('for-carve-status').textContent = 'Carved ' + forCarvedFiles.length + ' file(s)';
|
||||
forRenderCarved();
|
||||
forLogCustody('carve', source, 'Carved ' + forCarvedFiles.length + ' files (' + types.join(',') + ')', '');
|
||||
halAnalyze('Digital Forensics', JSON.stringify(data, null, 2), 'forensics', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -401,6 +405,7 @@ function forBuildTimeline() {
|
||||
document.getElementById('for-timeline-status').textContent = 'Timeline built — ' + forTimelineEvents.length + ' event(s)';
|
||||
forRenderTimeline();
|
||||
forLogCustody('timeline', path, 'Built timeline with ' + forTimelineEvents.length + ' events', '');
|
||||
halAnalyze('Digital Forensics', JSON.stringify(data, null, 2), 'forensics', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,9 @@
|
||||
<h2>ADB Devices</h2>
|
||||
<div class="tool-actions" id="hw-adb-refresh-bar">
|
||||
<button class="btn btn-primary btn-small" onclick="hwRefreshAdbDevices()">Refresh</button>
|
||||
<button class="btn btn-danger btn-small" onclick="hwAdbKillServer()">Kill ADB Server</button>
|
||||
<button class="btn btn-small" onclick="hwAdbStartServer()">Start ADB Server</button>
|
||||
<span id="hw-adb-server-status" style="font-size:0.8rem;color:var(--text-muted)"></span>
|
||||
</div>
|
||||
<div id="hw-adb-devices"></div>
|
||||
</div>
|
||||
|
||||
@@ -401,6 +401,7 @@ function irCreate() {
|
||||
document.getElementById('ir-name').value = '';
|
||||
document.getElementById('ir-desc').value = '';
|
||||
irLoadList();
|
||||
halAnalyze('Incident Response', JSON.stringify(data, null, 2), 'incident response', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -432,6 +433,7 @@ function irLoadList() {
|
||||
body.innerHTML = '<tr><td colspan="7" style="text-align:center;color:var(--text-muted)">No incidents found</td></tr>';
|
||||
}
|
||||
irPopulateSelectors();
|
||||
halAnalyze('Incident Response', JSON.stringify(data, null, 2), 'incident response', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -451,6 +453,7 @@ function irSelect(id) {
|
||||
document.getElementById('ir-detail-desc').textContent = inc.description || '';
|
||||
document.getElementById('ir-update-status').value = inc.status === 'closed' ? 'resolved' : inc.status;
|
||||
irLoadPlaybook(id, inc);
|
||||
halAnalyze('Incident Response', JSON.stringify(inc, null, 2), 'incident response', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -636,6 +639,7 @@ function swSweep() {
|
||||
if (!swLastMatches.length) {
|
||||
body.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--text-muted)">No matches found - system appears clean</td></tr>';
|
||||
}
|
||||
halAnalyze('Incident Response', JSON.stringify(data, null, 2), 'incident response', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -789,6 +793,7 @@ function tlGenReport() {
|
||||
|
||||
document.getElementById('tl-report-content').innerHTML = html;
|
||||
sec.scrollIntoView({behavior: 'smooth'});
|
||||
halAnalyze('Incident Response', JSON.stringify(rpt, null, 2), 'incident response', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ function createCapLink() {
|
||||
document.getElementById('cap-res-article').value = base + d.article_path;
|
||||
document.getElementById('cap-result').style.display = '';
|
||||
loadCapLinks();
|
||||
halAnalyze('IP Capture', JSON.stringify(d, null, 2), 'ip tracking', 'network');
|
||||
} else {
|
||||
alert(d.error);
|
||||
}
|
||||
@@ -167,6 +168,7 @@ function loadCapLinks() {
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
halAnalyze('IP Capture', JSON.stringify(d, null, 2), 'ip tracking', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -218,6 +220,7 @@ function loadCaptures() {
|
||||
<td style="padding:5px;font-size:0.75rem">${c.accept_language || ''}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
halAnalyze('IP Capture', JSON.stringify(d, null, 2), 'ip tracking', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<p style="font-size:0.8rem;color:var(--text-secondary);margin-top:0.5rem">
|
||||
Configured backend: <strong style="color:var(--accent)">{{ llm_backend }}</strong>
|
||||
— select a tab, fill in settings, and click <em>Save & Activate</em>, then <em>Load Model</em> to initialise.
|
||||
— select a tab, fill in settings, and click <em>Save & Activate</em>, then <em>Initialize LLM</em> to initialise.
|
||||
</p>
|
||||
|
||||
<!-- Load / Status bar -->
|
||||
@@ -32,10 +32,10 @@
|
||||
<div id="llm-status-dot" style="width:10px;height:10px;border-radius:50%;
|
||||
background:var(--text-muted);flex-shrink:0" title="Not loaded"></div>
|
||||
<span id="llm-status-text" style="font-size:0.83rem;color:var(--text-secondary);flex:1">
|
||||
Not loaded — click <strong>Load Model</strong> to initialise the current backend.
|
||||
Not loaded — click <strong>Initialize LLM</strong> to initialise the current backend.
|
||||
</span>
|
||||
<button id="btn-llm-load" class="btn btn-primary btn-sm" onclick="loadLLM()">
|
||||
Load Model
|
||||
Initialize LLM
|
||||
</button>
|
||||
<button class="btn btn-sm" onclick="debugOpen()"
|
||||
title="Open debug console to see detailed load output">
|
||||
@@ -367,62 +367,71 @@
|
||||
<div class="section">
|
||||
<h2>Claude API</h2>
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary)">
|
||||
Requires an <a href="https://console.anthropic.com" target="_blank" rel="noopener">Anthropic account</a>.
|
||||
Get your API key from the console.
|
||||
Requires an <a href="https://console.anthropic.com" target="_blank" rel="noopener">Anthropic API</a> key.
|
||||
AUTARCH calls Claude directly for chat, agent, and analysis tasks.
|
||||
</p>
|
||||
<form method="POST" action="{{ url_for('settings.update_llm') }}" class="settings-form">
|
||||
<input type="hidden" name="backend" value="claude">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="claude-key">API Key</label>
|
||||
<input type="password" id="claude-key" name="api_key" value="{{ claude.api_key }}" placeholder="sk-ant-api03-...">
|
||||
<small>Stored in autarch_settings.conf — keep it safe.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-key">API Key</label>
|
||||
<input type="password" id="claude-key" value="{{ claude.api_key }}" placeholder="sk-ant-api03-...">
|
||||
<small>Stored in autarch_settings.conf — keep it safe.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="claude-model">Model</label>
|
||||
<select id="claude-model" name="model">
|
||||
<optgroup label="Claude 4.6">
|
||||
<option value="claude-opus-4-6" {% if claude.model == 'claude-opus-4-6' %}selected{% endif %}>claude-opus-4-6 (most capable)</option>
|
||||
<option value="claude-sonnet-4-6" {% if claude.model == 'claude-sonnet-4-6' %}selected{% endif %}>claude-sonnet-4-6 (balanced)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Claude 4.5">
|
||||
<option value="claude-opus-4-5" {% if claude.model == 'claude-opus-4-5' %}selected{% endif %}>claude-opus-4-5</option>
|
||||
<option value="claude-sonnet-4-5" {% if claude.model == 'claude-sonnet-4-5' %}selected{% endif %}>claude-sonnet-4-5</option>
|
||||
<option value="claude-haiku-4-5-20251001" {% if claude.model == 'claude-haiku-4-5-20251001' %}selected{% endif %}>claude-haiku-4-5 (fastest)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Claude 3.5 / 3">
|
||||
<option value="claude-3-5-sonnet-20241022" {% if claude.model == 'claude-3-5-sonnet-20241022' %}selected{% endif %}>claude-3-5-sonnet-20241022</option>
|
||||
<option value="claude-3-5-haiku-20241022" {% if claude.model == 'claude-3-5-haiku-20241022' %}selected{% endif %}>claude-3-5-haiku-20241022</option>
|
||||
<option value="claude-3-opus-20240229" {% if claude.model == 'claude-3-opus-20240229' %}selected{% endif %}>claude-3-opus-20240229</option>
|
||||
</optgroup>
|
||||
<div class="form-group">
|
||||
<label for="claude-model">Model</label>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center">
|
||||
<select id="claude-model" style="flex:1">
|
||||
<option value="{{ claude.model }}" selected>{{ claude.model }}</option>
|
||||
</select>
|
||||
<button class="btn btn-sm" onclick="claudeFetchModels()" id="btn-claude-refresh" title="Fetch available models from API">Refresh</button>
|
||||
</div>
|
||||
<small id="claude-model-hint">Click <strong>Refresh</strong> to fetch available models from the API.</small>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="claude-max-tok">Max Tokens</label>
|
||||
<input type="number" id="claude-max-tok" name="max_tokens" value="{{ claude.max_tokens }}" min="1" max="200000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-temp">Temperature</label>
|
||||
<input type="number" id="claude-temp" name="temperature" value="{{ claude.temperature }}" step="0.05" min="0" max="1">
|
||||
<small>0–1. Claude default is 1.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-top-p">Top-P</label>
|
||||
<input type="number" id="claude-top-p" name="top_p" value="{{ claude.get('top_p', 1.0) }}" step="0.05" min="0" max="1">
|
||||
<small>Use with lower temp.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-top-k">Top-K</label>
|
||||
<input type="number" id="claude-top-k" name="top_k" value="{{ claude.get('top_k', 0) }}" min="0">
|
||||
<small>0 = disabled.</small>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="claude-max-tok">Max Tokens</label>
|
||||
<input type="number" id="claude-max-tok" value="{{ claude.max_tokens }}" min="1" max="200000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-temp">Temperature</label>
|
||||
<input type="number" id="claude-temp" value="{{ claude.temperature }}" step="0.05" min="0" max="1">
|
||||
<small>0–1. Claude default is 1.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-top-p">Top-P</label>
|
||||
<input type="number" id="claude-top-p" value="{{ claude.get('top_p', 1.0) }}" step="0.05" min="0" max="1">
|
||||
<small>Use with lower temp.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="claude-top-k">Top-K</label>
|
||||
<input type="number" id="claude-top-k" value="{{ claude.get('top_k', 0) }}" min="0">
|
||||
<small>0 = disabled.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save & Activate Claude</button>
|
||||
</form>
|
||||
<!-- Action buttons + inline status -->
|
||||
<div style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;margin-top:0.5rem">
|
||||
<button class="btn btn-primary" onclick="claudeActivate()" id="btn-claude-activate">
|
||||
Activate Claude
|
||||
</button>
|
||||
<button class="btn btn-sm" onclick="claudeSave()" id="btn-claude-save">
|
||||
Save Settings
|
||||
</button>
|
||||
<button class="btn btn-sm" onclick="claudeReload()" id="btn-claude-reload" title="Re-test API key and reconnect">
|
||||
Reload
|
||||
</button>
|
||||
<div id="claude-status-dot" style="width:10px;height:10px;border-radius:50%;
|
||||
background:{% if llm_backend == 'claude' %}var(--success, #34c759){% else %}var(--text-muted){% endif %};flex-shrink:0"></div>
|
||||
<span id="claude-status-text" style="font-size:0.83rem;color:var(--text-secondary)">
|
||||
{% if llm_backend == 'claude' %}Active — {{ claude.model }}{% else %}Not active{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.35rem">
|
||||
<strong>Save</strong> stores settings + API key to encrypted vault.
|
||||
<strong>Reload</strong> re-tests the connection without changing settings.
|
||||
<strong>Activate</strong> saves + loads Claude as the active backend.
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- end tab-claude -->
|
||||
|
||||
@@ -605,9 +614,159 @@
|
||||
</div>
|
||||
</div><!-- end tab-huggingface -->
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════════ -->
|
||||
<!-- AGENTS SECTION -->
|
||||
<!-- ══════════════════════════════════════════════════════════════════ -->
|
||||
<div class="section" style="margin-top:2rem">
|
||||
<h2>Agent Configuration</h2>
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.75rem">
|
||||
Configure the AI agent backends used by Agent Hal and Autonomy.
|
||||
Agents use the THOUGHT/ACTION/PARAMS loop to accomplish tasks with tools.
|
||||
</p>
|
||||
|
||||
<!-- Agent sub-tabs -->
|
||||
<div class="tab-bar" id="agent-tab-bar">
|
||||
<button class="tab active" onclick="agentTab('local')">Local Agent</button>
|
||||
<button class="tab" onclick="agentTab('claude')">Claude Agent</button>
|
||||
<button class="tab" onclick="agentTab('openai')">OpenAI Agent</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Local Agent Sub-Tab ──────────────────────────────────────── -->
|
||||
<div id="agent-tab-local" class="agent-tab-panel">
|
||||
<div style="padding:1rem 0">
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Uses the currently loaded LLM backend (configured above) for agent operations.
|
||||
Best for offline or privacy-sensitive work.
|
||||
</p>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="agent-local-steps">Max Steps</label>
|
||||
<input type="number" id="agent-local-steps" value="{{ agents.local_max_steps }}" min="1" max="100">
|
||||
<small>Maximum tool-use steps per task.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-local-verbose">
|
||||
<input type="checkbox" id="agent-local-verbose" {% if agents.local_verbose %}checked{% endif %}
|
||||
style="margin-right:0.4rem">
|
||||
Verbose Output
|
||||
</label>
|
||||
<small>Show step-by-step agent reasoning.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;margin-top:0.5rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="agentSave('local')">Save Local Agent Settings</button>
|
||||
<span id="agent-local-status" style="font-size:0.82rem;color:var(--text-secondary)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Claude Agent Sub-Tab ─────────────────────────────────────── -->
|
||||
<div id="agent-tab-claude" class="agent-tab-panel hidden">
|
||||
<div style="padding:1rem 0">
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Uses the Anthropic Claude API with native tool use for agent operations.
|
||||
Requires a Claude API key (configured in the Claude tab above).
|
||||
Supports extended thinking and structured tool calls.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="agent-claude-model">Agent Model</label>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center">
|
||||
<select id="agent-claude-model" style="flex:1">
|
||||
<option value="{{ agents.claude_model }}" selected>{{ agents.claude_model }}</option>
|
||||
</select>
|
||||
<button class="btn btn-sm" onclick="agentClaudeFetchModels()" id="btn-agent-claude-refresh">Refresh</button>
|
||||
</div>
|
||||
<small id="agent-claude-model-hint">Click Refresh to fetch models from the API. Uses the API key from the Claude tab.</small>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="agent-claude-tokens">Max Tokens</label>
|
||||
<input type="number" id="agent-claude-tokens" value="{{ agents.claude_max_tokens }}" min="1" max="200000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-claude-steps">Max Steps</label>
|
||||
<input type="number" id="agent-claude-steps" value="{{ agents.claude_max_steps }}" min="1" max="200">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-claude-enabled">
|
||||
<input type="checkbox" id="agent-claude-enabled" {% if agents.claude_enabled %}checked{% endif %}
|
||||
style="margin-right:0.4rem">
|
||||
Enable Claude Agent
|
||||
</label>
|
||||
<small>Allow agent tasks to use Claude.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;margin-top:0.5rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="agentSave('claude')">Save Claude Agent Settings</button>
|
||||
<span id="agent-claude-status" style="font-size:0.82rem;color:var(--text-secondary)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── OpenAI Agent Sub-Tab ─────────────────────────────────────── -->
|
||||
<div id="agent-tab-openai" class="agent-tab-panel hidden">
|
||||
<div style="padding:1rem 0">
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Uses the OpenAI API (or any compatible endpoint: Ollama, vLLM, LiteLLM) with function calling for agent operations.
|
||||
Requires an API key configured in the OpenAI tab above.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-model">Agent Model</label>
|
||||
<input type="text" id="agent-openai-model" value="{{ agents.openai_model }}" placeholder="gpt-4o"
|
||||
list="agent-openai-model-list">
|
||||
<datalist id="agent-openai-model-list">
|
||||
<option value="gpt-4o">
|
||||
<option value="gpt-4o-mini">
|
||||
<option value="gpt-4.1">
|
||||
<option value="gpt-4.1-mini">
|
||||
<option value="gpt-4.1-nano">
|
||||
<option value="o3">
|
||||
<option value="o4-mini">
|
||||
</datalist>
|
||||
<small>Model ID. For local servers (Ollama, etc.) use the name you pulled.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-base-url">Base URL</label>
|
||||
<input type="text" id="agent-openai-base-url" value="{{ agents.openai_base_url }}" placeholder="https://api.openai.com/v1">
|
||||
<small>Change for Ollama (<code>http://localhost:11434/v1</code>), vLLM, LiteLLM, etc.</small>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-tokens">Max Tokens</label>
|
||||
<input type="number" id="agent-openai-tokens" value="{{ agents.openai_max_tokens }}" min="1" max="200000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-steps">Max Steps</label>
|
||||
<input type="number" id="agent-openai-steps" value="{{ agents.openai_max_steps }}" min="1" max="200">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="agent-openai-enabled">
|
||||
<input type="checkbox" id="agent-openai-enabled" {% if agents.openai_enabled %}checked{% endif %}
|
||||
style="margin-right:0.4rem">
|
||||
Enable OpenAI Agent
|
||||
</label>
|
||||
<small>Allow agent tasks to use OpenAI.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;margin-top:0.5rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="agentSave('openai')">Save OpenAI Agent Settings</button>
|
||||
<span id="agent-openai-status" style="font-size:0.82rem;color:var(--text-secondary)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active agent backend indicator -->
|
||||
<div style="margin-top:0.75rem;padding:0.5rem 0.75rem;border-radius:var(--radius);border:1px solid var(--border);
|
||||
background:var(--bg-card);font-size:0.82rem;color:var(--text-secondary)">
|
||||
Active agent backend: <strong id="agent-active-backend" style="color:var(--accent)">{{ agents.backend }}</strong>
|
||||
— The agent will use this backend when processing tasks.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hidden { display: none !important; }
|
||||
.form-group small { display:block; font-size:0.72rem; color:var(--text-muted); margin-top:0.2rem; }
|
||||
.agent-tab-panel.hidden { display: none !important; }
|
||||
|
||||
/* ── GPU Preset Cards ───────────────────────────────────────────────────── */
|
||||
.gpu-presets {
|
||||
@@ -667,7 +826,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// ── Load Model ────────────────────────────────────────────────────────────────
|
||||
// ── Initialize LLM ────────────────────────────────────────────────────────────────
|
||||
function loadLLM() {
|
||||
var btn = document.getElementById('btn-llm-load');
|
||||
var dot = document.getElementById('llm-status-dot');
|
||||
@@ -683,7 +842,7 @@ function loadLLM() {
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Load Model';
|
||||
btn.textContent = 'Initialize LLM';
|
||||
if (d.ok) {
|
||||
dot.style.background = 'var(--success, #34c759)';
|
||||
dot.title = 'Loaded';
|
||||
@@ -700,7 +859,7 @@ function loadLLM() {
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Load Model';
|
||||
btn.textContent = 'Initialize LLM';
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
dot.title = 'Error';
|
||||
text.textContent = 'Request failed: ' + e.message;
|
||||
@@ -866,5 +1025,277 @@ function hfVerifyToken() {
|
||||
})
|
||||
.catch(function(e) { btn.textContent = 'Verify Token'; btn.disabled = false; alert('Request failed: ' + e.message); });
|
||||
}
|
||||
|
||||
// ── Claude: Activate (save + load in one click) ──────────────────────────────
|
||||
function claudeSave() {
|
||||
var btn = document.getElementById('btn-claude-save');
|
||||
var text = document.getElementById('claude-status-text');
|
||||
btn.disabled = true; btn.textContent = 'Saving…';
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('backend', 'claude');
|
||||
fd.append('api_key', document.getElementById('claude-key').value);
|
||||
fd.append('model', document.getElementById('claude-model').value);
|
||||
fd.append('max_tokens', document.getElementById('claude-max-tok').value);
|
||||
fd.append('temperature', document.getElementById('claude-temp').value);
|
||||
|
||||
fetch('/settings/llm', {method: 'POST', body: fd, redirect: 'manual'})
|
||||
.then(function() {
|
||||
btn.disabled = false; btn.textContent = 'Save Settings';
|
||||
text.innerHTML = '<span style="color:var(--success,#34c759)">✓ Settings saved to encrypted vault</span>';
|
||||
setTimeout(function() { text.textContent = ''; }, 3000);
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false; btn.textContent = 'Save Settings';
|
||||
text.textContent = 'Save failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
function claudeReload() {
|
||||
var btn = document.getElementById('btn-claude-reload');
|
||||
var dot = document.getElementById('claude-status-dot');
|
||||
var text = document.getElementById('claude-status-text');
|
||||
btn.disabled = true; btn.textContent = 'Reloading…';
|
||||
dot.style.background = '#f59e0b';
|
||||
text.innerHTML = '<em>Re-testing Claude connection…</em>';
|
||||
|
||||
fetch('/settings/llm/load', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false; btn.textContent = 'Reload';
|
||||
if (d.ok) {
|
||||
dot.style.background = 'var(--success, #34c759)';
|
||||
text.innerHTML = '✓ <strong style="color:var(--success,#34c759)">Connected</strong> — ' + escapeHtml(d.model_name);
|
||||
} else {
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
text.innerHTML = '✕ ' + escapeHtml(d.error || 'Connection failed');
|
||||
}
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false; btn.textContent = 'Reload';
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
text.textContent = 'Failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
function claudeActivate() {
|
||||
var btn = document.getElementById('btn-claude-activate');
|
||||
var dot = document.getElementById('claude-status-dot');
|
||||
var text = document.getElementById('claude-status-text');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Saving…';
|
||||
dot.style.background = '#f59e0b';
|
||||
text.textContent = 'Saving settings…';
|
||||
|
||||
// Build form data from the Claude tab fields
|
||||
var fd = new FormData();
|
||||
fd.append('backend', 'claude');
|
||||
fd.append('api_key', document.getElementById('claude-key').value);
|
||||
fd.append('model', document.getElementById('claude-model').value);
|
||||
fd.append('max_tokens', document.getElementById('claude-max-tok').value);
|
||||
fd.append('temperature', document.getElementById('claude-temp').value);
|
||||
|
||||
// Step 1: Save settings via the existing update_llm route (no redirect — use fetch)
|
||||
fetch('/settings/llm', {method: 'POST', body: fd, redirect: 'manual'})
|
||||
.then(function() {
|
||||
// Step 2: Now load the backend
|
||||
btn.textContent = 'Loading…';
|
||||
text.innerHTML = '<em>Initialising Claude — please wait…</em>';
|
||||
return fetch('/settings/llm/load', {method: 'POST'});
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Activate Claude';
|
||||
if (d.ok) {
|
||||
dot.style.background = 'var(--success, #34c759)';
|
||||
text.innerHTML = '✓ <strong style="color:var(--success,#34c759)">Claude active</strong> — '
|
||||
+ escapeHtml(d.model_name);
|
||||
// Also update the top-level status bar
|
||||
var topDot = document.getElementById('llm-status-dot');
|
||||
var topText = document.getElementById('llm-status-text');
|
||||
if (topDot) { topDot.style.background = 'var(--success, #34c759)'; topDot.title = 'Loaded'; }
|
||||
if (topText) { topText.innerHTML = '✓ <strong style="color:var(--success,#34c759)">claude</strong> ready — ' + escapeHtml(d.model_name); }
|
||||
} else {
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
text.innerHTML = '✕ <strong style="color:var(--danger,#ff3b30)">Load failed:</strong> '
|
||||
+ escapeHtml(d.error || 'Unknown error')
|
||||
+ ' — <em>check Debug Log for details</em>';
|
||||
}
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Activate Claude';
|
||||
dot.style.background = 'var(--danger, #ff3b30)';
|
||||
text.textContent = 'Request failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Agent tab switching ───────────────────────────────────────────────────────
|
||||
function agentTab(name) {
|
||||
// Hide all agent panels
|
||||
var panels = document.querySelectorAll('.agent-tab-panel');
|
||||
for (var i = 0; i < panels.length; i++) panels[i].classList.add('hidden');
|
||||
// Deactivate all agent tabs
|
||||
var tabs = document.querySelectorAll('#agent-tab-bar .tab');
|
||||
for (var i = 0; i < tabs.length; i++) tabs[i].classList.remove('active');
|
||||
// Show selected
|
||||
var panel = document.getElementById('agent-tab-' + name);
|
||||
if (panel) panel.classList.remove('hidden');
|
||||
// Activate tab button (find by text match)
|
||||
var labels = {local: 'Local Agent', claude: 'Claude Agent', openai: 'OpenAI Agent'};
|
||||
for (var i = 0; i < tabs.length; i++) {
|
||||
if (tabs[i].textContent.trim() === labels[name]) tabs[i].classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Agent save ───────────────────────────────────────────────────────────────
|
||||
function agentSave(tab) {
|
||||
var status = document.getElementById('agent-' + tab + '-status');
|
||||
var data = {backend: tab};
|
||||
|
||||
if (tab === 'local') {
|
||||
data.local_max_steps = parseInt(document.getElementById('agent-local-steps').value) || 20;
|
||||
data.local_verbose = document.getElementById('agent-local-verbose').checked;
|
||||
} else if (tab === 'claude') {
|
||||
data.claude_enabled = document.getElementById('agent-claude-enabled').checked;
|
||||
data.claude_model = document.getElementById('agent-claude-model').value;
|
||||
data.claude_max_tokens = parseInt(document.getElementById('agent-claude-tokens').value) || 16384;
|
||||
data.claude_max_steps = parseInt(document.getElementById('agent-claude-steps').value) || 30;
|
||||
} else if (tab === 'openai') {
|
||||
data.openai_enabled = document.getElementById('agent-openai-enabled').checked;
|
||||
data.openai_model = document.getElementById('agent-openai-model').value;
|
||||
data.openai_base_url = document.getElementById('agent-openai-base-url').value;
|
||||
data.openai_max_tokens = parseInt(document.getElementById('agent-openai-tokens').value) || 16384;
|
||||
data.openai_max_steps = parseInt(document.getElementById('agent-openai-steps').value) || 30;
|
||||
}
|
||||
|
||||
status.textContent = 'Saving…';
|
||||
fetch('/settings/agents/save', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.ok) {
|
||||
status.innerHTML = '<span style="color:var(--success,#34c759)">✓ Saved</span>';
|
||||
document.getElementById('agent-active-backend').textContent = tab;
|
||||
} else {
|
||||
status.innerHTML = '<span style="color:var(--danger,#ff3b30)">✕ ' + escapeHtml(d.error || 'Error') + '</span>';
|
||||
}
|
||||
setTimeout(function() { status.textContent = ''; }, 4000);
|
||||
})
|
||||
.catch(function(e) {
|
||||
status.textContent = 'Failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Agent Claude: Fetch models (reuses the same API endpoint) ────────────────
|
||||
function agentClaudeFetchModels() {
|
||||
var btn = document.getElementById('btn-agent-claude-refresh');
|
||||
var sel = document.getElementById('agent-claude-model');
|
||||
var hint = document.getElementById('agent-claude-model-hint');
|
||||
var curVal = sel.value;
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Fetching…';
|
||||
hint.textContent = 'Querying Anthropic API…';
|
||||
|
||||
fetch('/settings/llm/claude-models', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Refresh';
|
||||
if (!d.ok) {
|
||||
hint.textContent = 'Error: ' + (d.error || 'Unknown');
|
||||
return;
|
||||
}
|
||||
sel.innerHTML = '';
|
||||
var models = d.models || [];
|
||||
if (models.length === 0) {
|
||||
hint.textContent = 'No models returned.';
|
||||
var opt = document.createElement('option');
|
||||
opt.value = curVal; opt.textContent = curVal; opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
return;
|
||||
}
|
||||
var foundCurrent = false;
|
||||
for (var i = 0; i < models.length; i++) {
|
||||
var m = models[i];
|
||||
var opt = document.createElement('option');
|
||||
opt.value = m.id;
|
||||
opt.textContent = m.name || m.id;
|
||||
if (m.id === curVal) { opt.selected = true; foundCurrent = true; }
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
if (!foundCurrent && curVal) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = curVal; opt.textContent = curVal + ' (current)'; opt.selected = true;
|
||||
sel.insertBefore(opt, sel.firstChild);
|
||||
}
|
||||
hint.textContent = models.length + ' models available.';
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Refresh';
|
||||
hint.textContent = 'Failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Claude: Fetch models from API ────────────────────────────────────────────
|
||||
function claudeFetchModels() {
|
||||
var btn = document.getElementById('btn-claude-refresh');
|
||||
var sel = document.getElementById('claude-model');
|
||||
var hint = document.getElementById('claude-model-hint');
|
||||
var curVal = sel.value;
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Fetching…';
|
||||
hint.textContent = 'Querying Anthropic API…';
|
||||
|
||||
fetch('/settings/llm/claude-models', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Refresh';
|
||||
if (!d.ok) {
|
||||
hint.textContent = 'Error: ' + (d.error || 'Unknown error');
|
||||
return;
|
||||
}
|
||||
// Rebuild the dropdown with live models
|
||||
sel.innerHTML = '';
|
||||
var models = d.models || [];
|
||||
if (models.length === 0) {
|
||||
hint.textContent = 'No models returned — check API key permissions.';
|
||||
var opt = document.createElement('option');
|
||||
opt.value = curVal; opt.textContent = curVal; opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
return;
|
||||
}
|
||||
var foundCurrent = false;
|
||||
for (var i = 0; i < models.length; i++) {
|
||||
var m = models[i];
|
||||
var opt = document.createElement('option');
|
||||
opt.value = m.id;
|
||||
opt.textContent = m.name || m.id;
|
||||
if (m.id === curVal) { opt.selected = true; foundCurrent = true; }
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
// Keep current value selected even if not in list
|
||||
if (!foundCurrent && curVal) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = curVal; opt.textContent = curVal + ' (current)'; opt.selected = true;
|
||||
sel.insertBefore(opt, sel.firstChild);
|
||||
}
|
||||
hint.textContent = models.length + ' models available.';
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Refresh';
|
||||
hint.textContent = 'Request failed: ' + e.message;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -241,6 +241,7 @@ function logIngestFile() {
|
||||
setLoading(btn, false);
|
||||
renderOutput('log-ingest-file-output', data.message || data.error || 'Done');
|
||||
if (data.success) logLoadSources();
|
||||
halAnalyze('Log Correlator', JSON.stringify(data, null, 2), 'log correlation', 'log_analysis');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -253,6 +254,7 @@ function logIngestPaste() {
|
||||
setLoading(btn, false);
|
||||
renderOutput('log-ingest-paste-output', data.message || data.error || 'Done');
|
||||
if (data.success) logLoadSources();
|
||||
halAnalyze('Log Correlator', JSON.stringify(data, null, 2), 'log correlation', 'log_analysis');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -294,6 +296,7 @@ function logSearch() {
|
||||
+ '<td style="font-family:monospace;font-size:0.8rem">' + esc(r.entry) + '</td></tr>';
|
||||
});
|
||||
tb.innerHTML = html;
|
||||
halAnalyze('Log Correlator', JSON.stringify(data, null, 2), 'log correlation', 'log_analysis');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -338,6 +341,7 @@ function logFilterAlerts(severity, btnEl) {
|
||||
+ '<td style="font-family:monospace;font-size:0.8rem;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc(a.entry || '') + '</td></tr>';
|
||||
});
|
||||
tb.innerHTML = html;
|
||||
halAnalyze('Log Correlator', JSON.stringify(data, null, 2), 'log correlation', 'log_analysis');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -400,6 +404,7 @@ function logAddRule() {
|
||||
document.getElementById('log-rule-name').value = '';
|
||||
document.getElementById('log-rule-pattern').value = '';
|
||||
}
|
||||
halAnalyze('Log Correlator', JSON.stringify(data, null, 2), 'log correlation', 'log_analysis');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -459,6 +464,7 @@ function logLoadStats() {
|
||||
barsEl.innerHTML = barsHtml;
|
||||
labelsEl.innerHTML = labelsHtml;
|
||||
}
|
||||
halAnalyze('Log Correlator', JSON.stringify(data, null, 2), 'log correlation', 'log_analysis');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -184,6 +184,7 @@ function sandboxSubmit() {
|
||||
setLoading(btn, false);
|
||||
renderOutput('sandbox-submit-output', data.message || data.error || 'Submitted');
|
||||
if (data.success) { sandboxLoadSamples(); sandboxRefreshSelect(); }
|
||||
halAnalyze('Malware Sandbox', JSON.stringify(data, null, 2), 'malware analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); renderOutput('sandbox-submit-output', 'Upload failed'); });
|
||||
} else if (pathInput) {
|
||||
setLoading(btn, true);
|
||||
@@ -191,6 +192,7 @@ function sandboxSubmit() {
|
||||
setLoading(btn, false);
|
||||
renderOutput('sandbox-submit-output', data.message || data.error || 'Submitted');
|
||||
if (data.success) { sandboxLoadSamples(); sandboxRefreshSelect(); }
|
||||
halAnalyze('Malware Sandbox', JSON.stringify(data, null, 2), 'malware analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
} else {
|
||||
renderOutput('sandbox-submit-output', 'Select a file or enter a path.');
|
||||
@@ -266,6 +268,7 @@ function sandboxStatic() {
|
||||
|
||||
var strings = (data.strings || []).join('\n');
|
||||
renderOutput('sandbox-strings', strings || 'No interesting strings found.');
|
||||
halAnalyze('Malware Sandbox', JSON.stringify(data, null, 2), 'malware analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -328,6 +331,7 @@ function sandboxRenderDynamic(data) {
|
||||
renderOutput('sandbox-syscalls', (data.syscalls || []).join('\n') || 'No syscalls captured.');
|
||||
renderOutput('sandbox-files', (data.files_accessed || []).join('\n') || 'No file access recorded.');
|
||||
renderOutput('sandbox-network', (data.network_calls || []).join('\n') || 'No network activity recorded.');
|
||||
halAnalyze('Malware Sandbox', JSON.stringify(data, null, 2), 'malware analysis', 'analyze');
|
||||
}
|
||||
|
||||
/* ── Reports ── */
|
||||
@@ -395,6 +399,7 @@ function sandboxViewReport(reportId) {
|
||||
|
||||
html += '</div>';
|
||||
document.getElementById('sandbox-report-content').innerHTML = html;
|
||||
halAnalyze('Malware Sandbox', JSON.stringify(data, null, 2), 'malware analysis', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
427
web/templates/mcp_settings.html
Normal file
427
web/templates/mcp_settings.html
Normal file
@@ -0,0 +1,427 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}MCP Server - AUTARCH{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
|
||||
<h1>MCP Server</h1>
|
||||
<a href="{{ url_for('settings.index') }}" class="btn btn-sm" style="margin-left:auto">← Back to Settings</a>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Model Context Protocol</h2>
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
The <a href="https://modelcontextprotocol.io/docs/getting-started/intro" target="_blank" rel="noopener">Model Context Protocol (MCP)</a>
|
||||
lets AI assistants like Claude Desktop, Claude Code, and other MCP-compatible clients use AUTARCH's security tools directly.
|
||||
When connected, Claude can run nmap scans, look up IPs, capture packets, manage devices, and more — all through natural conversation.
|
||||
</p>
|
||||
|
||||
<!-- ── Section 1: Server Control ── -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1.25rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Server Control</h3>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;margin-bottom:0.75rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="mcpStart()" id="btn-mcp-start">Start MCP Server</button>
|
||||
<button class="btn btn-sm" onclick="mcpStop()" id="btn-mcp-stop">Stop</button>
|
||||
<div id="mcp-status-dot" style="width:10px;height:10px;border-radius:50%;background:var(--text-muted);flex-shrink:0"></div>
|
||||
<span id="mcp-status-text" style="font-size:0.82rem;color:var(--text-secondary)">Checking...</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:1.5rem;flex-wrap:wrap;font-size:0.82rem;margin-bottom:0.5rem">
|
||||
<label style="display:flex;align-items:center;gap:0.4rem;cursor:pointer">
|
||||
<input type="checkbox" id="mcp-auto-start" {{ 'checked' if mcp.auto_start == 'true' }}>
|
||||
Auto-start on launch
|
||||
</label>
|
||||
<span style="color:var(--text-muted)">Transport: <strong id="mcp-transport-display">{{ mcp.transport }}</strong></span>
|
||||
</div>
|
||||
<p style="font-size:0.75rem;color:var(--text-muted);margin:0">
|
||||
SSE endpoint: <code>http://{{ request.host.split(':')[0] }}:{{ mcp.port }}/sse</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ── Section 2: Transport & Network ── -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1.25rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Transport & Network Settings</h3>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:0.75rem;font-size:0.82rem">
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">Transport</label>
|
||||
<select id="mcp-transport" style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem">
|
||||
<option value="sse" {{ 'selected' if mcp.transport == 'sse' }}>SSE (HTTP)</option>
|
||||
<option value="stdio" {{ 'selected' if mcp.transport == 'stdio' }}>stdio (CLI only)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">Host</label>
|
||||
<input type="text" id="mcp-host" value="{{ mcp.host }}" placeholder="0.0.0.0"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">Port</label>
|
||||
<input type="number" id="mcp-port" value="{{ mcp.port }}" placeholder="8081"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">CORS Origins</label>
|
||||
<input type="text" id="mcp-cors-origins" value="{{ mcp.cors_origins }}" placeholder="*"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Section 3: Security ── -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1.25rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Security</h3>
|
||||
<div style="display:flex;flex-direction:column;gap:0.75rem;font-size:0.82rem">
|
||||
<label style="display:flex;align-items:center;gap:0.4rem;cursor:pointer">
|
||||
<input type="checkbox" id="mcp-auth-enabled" {{ 'checked' if mcp.auth_enabled == 'true' }}>
|
||||
Enable authentication
|
||||
</label>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">Auth Token</label>
|
||||
<div style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap">
|
||||
<input type="password" id="mcp-auth-token" value="{{ mcp.auth_token }}" readonly
|
||||
style="flex:1;min-width:200px;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;font-family:monospace">
|
||||
<button class="btn btn-sm" onclick="mcpToggleToken()" id="btn-toggle-token" style="font-size:0.75rem">Show</button>
|
||||
<button class="btn btn-sm" onclick="mcpGenerateToken()" style="font-size:0.75rem">Generate New Token</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem">
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">Rate Limit</label>
|
||||
<input type="text" id="mcp-rate-limit" value="{{ mcp.rate_limit }}" placeholder="100/hour"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:0.4rem;cursor:pointer">
|
||||
<input type="checkbox" id="mcp-mask-errors" {{ 'checked' if mcp.mask_errors == 'true' }}>
|
||||
Mask error details in responses
|
||||
</label>
|
||||
<div style="border-top:1px solid var(--border);padding-top:0.75rem;margin-top:0.25rem">
|
||||
<label style="display:flex;align-items:center;gap:0.4rem;cursor:pointer;margin-bottom:0.5rem">
|
||||
<input type="checkbox" id="mcp-ssl-enabled" {{ 'checked' if mcp.ssl_enabled == 'true' }}>
|
||||
Enable SSL / TLS
|
||||
</label>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:0.75rem">
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">SSL Certificate Path</label>
|
||||
<input type="text" id="mcp-ssl-cert" value="{{ mcp.ssl_cert }}" placeholder="/path/to/cert.pem"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">SSL Key Path</label>
|
||||
<input type="text" id="mcp-ssl-key" value="{{ mcp.ssl_key }}" placeholder="/path/to/key.pem"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Section 4: Tool Management ── -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1.25rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Tool Management</h3>
|
||||
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:0.75rem">
|
||||
Enable or disable individual MCP tools. Disabled tools will not be exposed to connected clients.
|
||||
</p>
|
||||
<div id="mcp-tools-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:0.5rem;font-size:0.82rem">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Section 5: Tool Timeouts ── -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1.25rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Tool Timeouts</h3>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem;font-size:0.82rem">
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">nmap timeout (s)</label>
|
||||
<input type="number" id="mcp-nmap-timeout" value="{{ mcp.nmap_timeout }}" placeholder="120"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">tcpdump timeout (s)</label>
|
||||
<input type="number" id="mcp-tcpdump-timeout" value="{{ mcp.tcpdump_timeout }}" placeholder="30"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">whois timeout (s)</label>
|
||||
<input type="number" id="mcp-whois-timeout" value="{{ mcp.whois_timeout }}" placeholder="15"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">DNS timeout (s)</label>
|
||||
<input type="number" id="mcp-dns-timeout" value="{{ mcp.dns_timeout }}" placeholder="10"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">GeoIP timeout (s)</label>
|
||||
<input type="number" id="mcp-geoip-timeout" value="{{ mcp.geoip_timeout }}" placeholder="10"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">GeoIP Endpoint URL</label>
|
||||
<input type="text" id="mcp-geoip-endpoint" value="{{ mcp.geoip_endpoint }}" placeholder="http://ip-api.com/json/"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Section 6: Advanced ── -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1.25rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Advanced</h3>
|
||||
<div style="display:flex;flex-direction:column;gap:0.75rem;font-size:0.82rem">
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem">
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">Log Level</label>
|
||||
<select id="mcp-log-level" style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem">
|
||||
<option value="DEBUG" {{ 'selected' if mcp.log_level == 'DEBUG' }}>DEBUG</option>
|
||||
<option value="INFO" {{ 'selected' if mcp.log_level == 'INFO' }}>INFO</option>
|
||||
<option value="WARNING" {{ 'selected' if mcp.log_level == 'WARNING' }}>WARNING</option>
|
||||
<option value="ERROR" {{ 'selected' if mcp.log_level == 'ERROR' }}>ERROR</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">Request Timeout (s)</label>
|
||||
<input type="number" id="mcp-request-timeout" value="{{ mcp.request_timeout }}" placeholder="30"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">Max Message Size (bytes)</label>
|
||||
<input type="number" id="mcp-max-message-size" value="{{ mcp.max_message_size }}" placeholder="1048576"
|
||||
style="width:100%;padding:0.35rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;box-sizing:border-box">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;color:var(--text-secondary);margin-bottom:0.25rem">Server Instructions</label>
|
||||
<textarea id="mcp-instructions" rows="4" placeholder="Instructions sent to MCP clients describing this server's capabilities..."
|
||||
style="width:100%;padding:0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-main);color:inherit;font-size:0.82rem;font-family:inherit;resize:vertical;box-sizing:border-box">{{ mcp.instructions }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Section 7: Integration ── -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1.25rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Integration</h3>
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.5rem">
|
||||
<strong>Claude Desktop / Claude Code config:</strong> Add this to your
|
||||
<code>claude_desktop_config.json</code> or <code>.claude/settings.json</code>:
|
||||
</p>
|
||||
<div style="position:relative;margin-bottom:1rem">
|
||||
<pre id="mcp-config-block" style="background:var(--bg-main);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:0.75rem;font-size:0.78rem;overflow-x:auto;margin:0">Loading...</pre>
|
||||
<button class="btn btn-sm" onclick="mcpCopyConfig()" id="btn-mcp-copy"
|
||||
style="position:absolute;top:0.4rem;right:0.4rem;font-size:0.7rem">Copy</button>
|
||||
</div>
|
||||
<p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.5rem"><strong>CLI Commands:</strong></p>
|
||||
<pre style="background:var(--bg-main);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:0.75rem;font-size:0.78rem;overflow-x:auto;margin:0"><span style="color:var(--text-muted)"># stdio mode (Claude Desktop / Claude Code)</span>
|
||||
python autarch.py --mcp stdio
|
||||
|
||||
<span style="color:var(--text-muted)"># SSE mode (remote / web clients)</span>
|
||||
python autarch.py --mcp sse --mcp-port {{ mcp.port }}</pre>
|
||||
</div>
|
||||
|
||||
<!-- ── Save Button ── -->
|
||||
<div style="position:sticky;bottom:0;padding:0.75rem 0;background:var(--bg-main);border-top:1px solid var(--border);display:flex;align-items:center;gap:1rem;z-index:10">
|
||||
<button class="btn btn-primary" onclick="mcpSave()" id="btn-mcp-save" style="font-size:0.9rem;padding:0.5rem 1.5rem">
|
||||
Save MCP Settings
|
||||
</button>
|
||||
<span id="mcp-save-status" style="font-size:0.82rem;color:var(--text-muted)"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ── Tool definitions ─────────────────────────────────────────────────────────
|
||||
var MCP_TOOLS = [
|
||||
{name: 'nmap_scan', desc: 'Run an nmap network scan'},
|
||||
{name: 'geoip_lookup', desc: 'GeoIP information for an IP'},
|
||||
{name: 'dns_lookup', desc: 'DNS record queries'},
|
||||
{name: 'whois_lookup', desc: 'WHOIS lookup for domain/IP'},
|
||||
{name: 'packet_capture', desc: 'Capture network packets'},
|
||||
{name: 'wireguard_status', desc: 'WireGuard VPN status'},
|
||||
{name: 'upnp_status', desc: 'UPnP port mapping status'},
|
||||
{name: 'system_info', desc: 'System information'},
|
||||
{name: 'llm_chat', desc: 'Chat with configured LLM'},
|
||||
{name: 'android_devices', desc: 'List Android devices via ADB'},
|
||||
{name: 'config_get', desc: 'Read AUTARCH configuration'}
|
||||
];
|
||||
|
||||
var disabledToolsRaw = {{ mcp.disabled_tools | tojson }};
|
||||
|
||||
function escapeHtml(s) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = s;
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function buildToolGrid() {
|
||||
var disabled = (disabledToolsRaw || '').split(',').map(function(s) { return s.trim(); }).filter(Boolean);
|
||||
var grid = document.getElementById('mcp-tools-grid');
|
||||
grid.innerHTML = '';
|
||||
for (var i = 0; i < MCP_TOOLS.length; i++) {
|
||||
var t = MCP_TOOLS[i];
|
||||
var isEnabled = disabled.indexOf(t.name) === -1;
|
||||
var el = document.createElement('label');
|
||||
el.style.cssText = 'display:flex;align-items:flex-start;gap:0.5rem;padding:0.5rem 0.6rem;border-radius:4px;border:1px solid var(--border);background:var(--bg-main);cursor:pointer';
|
||||
el.innerHTML = '<input type="checkbox" class="mcp-tool-cb" data-tool="' + t.name + '" ' + (isEnabled ? 'checked' : '') + ' style="margin-top:0.15rem">'
|
||||
+ '<div><strong style="color:var(--accent)">' + escapeHtml(t.name) + '</strong>'
|
||||
+ '<div style="font-size:0.72rem;color:var(--text-muted);margin-top:0.1rem">' + escapeHtml(t.desc) + '</div></div>';
|
||||
grid.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Server Controls ──────────────────────────────────────────────────────────
|
||||
function mcpStart() {
|
||||
var btn = document.getElementById('btn-mcp-start');
|
||||
btn.disabled = true; btn.textContent = 'Starting...';
|
||||
fetch('/settings/mcp/start', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false; btn.textContent = 'Start MCP Server';
|
||||
mcpRefreshStatus();
|
||||
})
|
||||
.catch(function(e) { btn.disabled = false; btn.textContent = 'Start MCP Server'; alert(e.message); });
|
||||
}
|
||||
|
||||
function mcpStop() {
|
||||
var btn = document.getElementById('btn-mcp-stop');
|
||||
btn.disabled = true; btn.textContent = 'Stopping...';
|
||||
fetch('/settings/mcp/stop', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false; btn.textContent = 'Stop';
|
||||
mcpRefreshStatus();
|
||||
})
|
||||
.catch(function(e) { btn.disabled = false; btn.textContent = 'Stop'; });
|
||||
}
|
||||
|
||||
function mcpRefreshStatus() {
|
||||
fetch('/settings/mcp/status', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
var dot = document.getElementById('mcp-status-dot');
|
||||
var text = document.getElementById('mcp-status-text');
|
||||
if (d.ok && d.status && d.status.running) {
|
||||
dot.style.background = 'var(--success, #34c759)';
|
||||
text.innerHTML = '✓ Running (PID ' + d.status.pid + ')';
|
||||
} else {
|
||||
dot.style.background = 'var(--text-muted)';
|
||||
text.textContent = 'Stopped';
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
document.getElementById('mcp-status-dot').style.background = 'var(--text-muted)';
|
||||
document.getElementById('mcp-status-text').textContent = 'Error checking status';
|
||||
});
|
||||
}
|
||||
|
||||
// ── Save ─────────────────────────────────────────────────────────────────────
|
||||
function mcpSave() {
|
||||
var btn = document.getElementById('btn-mcp-save');
|
||||
var status = document.getElementById('mcp-save-status');
|
||||
btn.disabled = true; btn.textContent = 'Saving...';
|
||||
status.textContent = '';
|
||||
|
||||
// Collect disabled tools
|
||||
var disabledTools = [];
|
||||
var cbs = document.querySelectorAll('.mcp-tool-cb');
|
||||
for (var i = 0; i < cbs.length; i++) {
|
||||
if (!cbs[i].checked) disabledTools.push(cbs[i].getAttribute('data-tool'));
|
||||
}
|
||||
|
||||
var payload = {
|
||||
enabled: true,
|
||||
auto_start: document.getElementById('mcp-auto-start').checked,
|
||||
transport: document.getElementById('mcp-transport').value,
|
||||
host: document.getElementById('mcp-host').value,
|
||||
port: document.getElementById('mcp-port').value,
|
||||
cors_origins: document.getElementById('mcp-cors-origins').value,
|
||||
auth_enabled: document.getElementById('mcp-auth-enabled').checked,
|
||||
auth_token: document.getElementById('mcp-auth-token').value,
|
||||
rate_limit: document.getElementById('mcp-rate-limit').value,
|
||||
mask_errors: document.getElementById('mcp-mask-errors').checked,
|
||||
ssl_enabled: document.getElementById('mcp-ssl-enabled').checked,
|
||||
ssl_cert: document.getElementById('mcp-ssl-cert').value,
|
||||
ssl_key: document.getElementById('mcp-ssl-key').value,
|
||||
log_level: document.getElementById('mcp-log-level').value,
|
||||
instructions: document.getElementById('mcp-instructions').value,
|
||||
request_timeout: document.getElementById('mcp-request-timeout').value,
|
||||
max_message_size: document.getElementById('mcp-max-message-size').value,
|
||||
disabled_tools: disabledTools.join(','),
|
||||
nmap_timeout: document.getElementById('mcp-nmap-timeout').value,
|
||||
tcpdump_timeout: document.getElementById('mcp-tcpdump-timeout').value,
|
||||
whois_timeout: document.getElementById('mcp-whois-timeout').value,
|
||||
dns_timeout: document.getElementById('mcp-dns-timeout').value,
|
||||
geoip_timeout: document.getElementById('mcp-geoip-timeout').value,
|
||||
geoip_endpoint: document.getElementById('mcp-geoip-endpoint').value
|
||||
};
|
||||
|
||||
fetch('/settings/mcp/save', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false; btn.textContent = 'Save MCP Settings';
|
||||
if (d.ok) {
|
||||
status.style.color = 'var(--success, #34c759)';
|
||||
status.textContent = 'Settings saved successfully.';
|
||||
} else {
|
||||
status.style.color = 'var(--danger, #ff3b30)';
|
||||
status.textContent = 'Error: ' + (d.error || 'Unknown error');
|
||||
}
|
||||
setTimeout(function() { status.textContent = ''; }, 4000);
|
||||
})
|
||||
.catch(function(e) {
|
||||
btn.disabled = false; btn.textContent = 'Save MCP Settings';
|
||||
status.style.color = 'var(--danger, #ff3b30)';
|
||||
status.textContent = 'Error: ' + e.message;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Token helpers ────────────────────────────────────────────────────────────
|
||||
function mcpGenerateToken() {
|
||||
fetch('/settings/mcp/generate-token', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.ok && d.token) {
|
||||
var input = document.getElementById('mcp-auth-token');
|
||||
input.value = d.token;
|
||||
input.type = 'text';
|
||||
document.getElementById('btn-toggle-token').textContent = 'Hide';
|
||||
}
|
||||
})
|
||||
.catch(function(e) { alert('Failed to generate token: ' + e.message); });
|
||||
}
|
||||
|
||||
function mcpToggleToken() {
|
||||
var input = document.getElementById('mcp-auth-token');
|
||||
var btn = document.getElementById('btn-toggle-token');
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
btn.textContent = 'Hide';
|
||||
} else {
|
||||
input.type = 'password';
|
||||
btn.textContent = 'Show';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Config copy ──────────────────────────────────────────────────────────────
|
||||
function mcpCopyConfig() {
|
||||
var block = document.getElementById('mcp-config-block');
|
||||
navigator.clipboard.writeText(block.textContent).then(function() {
|
||||
var btn = document.getElementById('btn-mcp-copy');
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(function() { btn.textContent = 'Copy'; }, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Page load ────────────────────────────────────────────────────────────────
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
buildToolGrid();
|
||||
mcpRefreshStatus();
|
||||
// Fetch config snippet
|
||||
fetch('/settings/mcp/config', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.ok) document.getElementById('mcp-config-block').textContent = d.config;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
398
web/templates/module_creator.html
Normal file
398
web/templates/module_creator.html
Normal file
@@ -0,0 +1,398 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Module Creator - AUTARCH{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Module Creator</h1>
|
||||
<p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
|
||||
Create, edit, validate, and manage AUTARCH modules
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#module-code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.82rem;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 0.75rem;
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
resize: vertical;
|
||||
tab-size: 4;
|
||||
line-height: 1.5;
|
||||
}
|
||||
#module-code:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.mc-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 340px;
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
@media (max-width: 960px) {
|
||||
.mc-layout { grid-template-columns: 1fr; }
|
||||
}
|
||||
.mc-editor, .mc-list {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 1rem;
|
||||
}
|
||||
.mc-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.6rem 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.mc-form-grid .form-group { margin-bottom: 0; }
|
||||
.mc-form-grid label {
|
||||
display: block;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.mc-form-grid input, .mc-form-grid select {
|
||||
width: 100%;
|
||||
padding: 0.4rem 0.6rem;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
.mc-btn-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.mc-btn-row .btn { font-size: 0.82rem; padding: 0.4rem 0.9rem; }
|
||||
.mc-status {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
border-radius: var(--radius);
|
||||
font-size: 0.8rem;
|
||||
display: none;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.mc-status.success { display: block; background: rgba(34,197,94,0.12); color: #4ade80; border: 1px solid rgba(34,197,94,0.25); }
|
||||
.mc-status.error { display: block; background: rgba(239,68,68,0.12); color: #f87171; border: 1px solid rgba(239,68,68,0.25); }
|
||||
.mc-status.info { display: block; background: rgba(99,102,241,0.12); color: var(--accent-hover); border: 1px solid rgba(99,102,241,0.25); }
|
||||
.mc-list h2 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.mc-modules-scroll {
|
||||
max-height: 620px;
|
||||
overflow-y: auto;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
.mc-cat-group { margin-bottom: 0.75rem; }
|
||||
.mc-cat-group h3 {
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 0.3rem;
|
||||
padding-bottom: 0.2rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.mc-mod-item {
|
||||
padding: 0.4rem 0.5rem;
|
||||
border-radius: calc(var(--radius) - 2px);
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.mc-mod-item:hover { background: var(--bg-input); }
|
||||
.mc-mod-item .mod-name { font-weight: 600; color: var(--text-primary); }
|
||||
.mc-mod-item .mod-desc {
|
||||
font-size: 0.72rem;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.mc-mod-item .mod-meta {
|
||||
font-size: 0.68rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mc-layout">
|
||||
<!-- Left column: editor -->
|
||||
<div class="mc-editor">
|
||||
<div class="mc-form-grid">
|
||||
<div class="form-group">
|
||||
<label for="mod-name">Module Name</label>
|
||||
<input type="text" id="mod-name" placeholder="my_module" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mod-category">Category</label>
|
||||
<select id="mod-category">
|
||||
<option value="defense">defense</option>
|
||||
<option value="offense">offense</option>
|
||||
<option value="counter">counter</option>
|
||||
<option value="analyze">analyze</option>
|
||||
<option value="osint">osint</option>
|
||||
<option value="simulate">simulate</option>
|
||||
<option value="core">core</option>
|
||||
<option value="hardware">hardware</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mod-desc">Description</label>
|
||||
<input type="text" id="mod-desc" placeholder="Short description of the module">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mod-author">Author</label>
|
||||
<input type="text" id="mod-author" value="darkHal">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:0.5rem">
|
||||
<button class="btn btn-sm" onclick="loadTemplate()" style="font-size:0.78rem">Load Template</button>
|
||||
</div>
|
||||
|
||||
<textarea id="module-code" rows="25" placeholder="# Module code will appear here... # Click 'Load Template' or select a module from the list." spellcheck="false"></textarea>
|
||||
|
||||
<div class="mc-btn-row">
|
||||
<button class="btn" onclick="validateCode()">Validate</button>
|
||||
<button class="btn" onclick="createModule()" style="background:var(--accent);color:#fff">Create Module</button>
|
||||
<button class="btn" onclick="saveModule()">Save Changes</button>
|
||||
</div>
|
||||
|
||||
<div id="mc-status" class="mc-status"></div>
|
||||
</div>
|
||||
|
||||
<!-- Right column: module list -->
|
||||
<div class="mc-list">
|
||||
<h2>
|
||||
Modules
|
||||
<button class="btn btn-sm" onclick="refreshModuleList()" style="font-size:0.72rem;padding:0.25rem 0.6rem">Refresh</button>
|
||||
</h2>
|
||||
<div id="module-list" class="mc-modules-scroll">
|
||||
<p style="color:var(--text-muted);font-size:0.8rem">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API = '/module-creator';
|
||||
const statusEl = document.getElementById('mc-status');
|
||||
const codeEl = document.getElementById('module-code');
|
||||
let currentEditName = null;
|
||||
|
||||
function showStatus(msg, type) {
|
||||
statusEl.className = 'mc-status ' + type;
|
||||
statusEl.textContent = msg;
|
||||
}
|
||||
|
||||
function clearStatus() {
|
||||
statusEl.className = 'mc-status';
|
||||
statusEl.textContent = '';
|
||||
}
|
||||
|
||||
/* Tab key support in textarea */
|
||||
codeEl.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
const start = this.selectionStart;
|
||||
const end = this.selectionEnd;
|
||||
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
|
||||
this.selectionStart = this.selectionEnd = start + 4;
|
||||
}
|
||||
});
|
||||
|
||||
/* Load template skeleton for selected category */
|
||||
function loadTemplate() {
|
||||
const cat = document.getElementById('mod-category').value;
|
||||
fetch(API + '/templates')
|
||||
.then(r => r.json())
|
||||
.then(templates => {
|
||||
const t = templates.find(x => x.category === cat);
|
||||
if (t) {
|
||||
codeEl.value = t.code;
|
||||
if (!document.getElementById('mod-desc').value) {
|
||||
document.getElementById('mod-desc').value = t.description;
|
||||
}
|
||||
currentEditName = null;
|
||||
showStatus('Template loaded for category: ' + cat, 'info');
|
||||
}
|
||||
})
|
||||
.catch(err => showStatus('Failed to load templates: ' + err, 'error'));
|
||||
}
|
||||
|
||||
/* Validate code syntax */
|
||||
function validateCode() {
|
||||
const code = codeEl.value.trim();
|
||||
if (!code) { showStatus('No code to validate', 'error'); return; }
|
||||
fetch(API + '/validate', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({code: code})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.valid) {
|
||||
showStatus('Validation passed. ' + (data.warnings || []).join('; '), 'success');
|
||||
} else {
|
||||
showStatus('Validation failed:\n' + (data.errors || []).join('\n'), 'error');
|
||||
}
|
||||
})
|
||||
.catch(err => showStatus('Validation request failed: ' + err, 'error'));
|
||||
}
|
||||
|
||||
/* Create new module */
|
||||
function createModule() {
|
||||
const name = document.getElementById('mod-name').value.trim();
|
||||
const category = document.getElementById('mod-category').value;
|
||||
const description = document.getElementById('mod-desc').value.trim();
|
||||
const author = document.getElementById('mod-author').value.trim() || 'darkHal';
|
||||
const code = codeEl.value;
|
||||
|
||||
if (!name) { showStatus('Module name is required', 'error'); return; }
|
||||
if (!code.trim()) { showStatus('Module code is empty', 'error'); return; }
|
||||
|
||||
fetch(API + '/create', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name, category, description, author, code})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showStatus(data.message, 'success');
|
||||
currentEditName = name;
|
||||
refreshModuleList();
|
||||
} else {
|
||||
showStatus(data.error || 'Creation failed', 'error');
|
||||
}
|
||||
})
|
||||
.catch(err => showStatus('Request failed: ' + err, 'error'));
|
||||
}
|
||||
|
||||
/* Save changes to existing module */
|
||||
function saveModule() {
|
||||
const name = currentEditName || document.getElementById('mod-name').value.trim();
|
||||
const code = codeEl.value;
|
||||
|
||||
if (!name) { showStatus('No module selected to save. Enter a name or click a module from the list.', 'error'); return; }
|
||||
if (!code.trim()) { showStatus('Module code is empty', 'error'); return; }
|
||||
|
||||
fetch(API + '/save', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name, code})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showStatus(data.message, 'success');
|
||||
refreshModuleList();
|
||||
} else {
|
||||
showStatus(data.error || 'Save failed', 'error');
|
||||
}
|
||||
})
|
||||
.catch(err => showStatus('Request failed: ' + err, 'error'));
|
||||
}
|
||||
|
||||
/* Load a module into the editor */
|
||||
function loadModule(name) {
|
||||
fetch(API + '/preview', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name: name})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
codeEl.value = data.code;
|
||||
currentEditName = name;
|
||||
const m = data.metadata || {};
|
||||
document.getElementById('mod-name').value = name;
|
||||
if (m.category) document.getElementById('mod-category').value = m.category;
|
||||
if (m.description) document.getElementById('mod-desc').value = m.description;
|
||||
if (m.author) document.getElementById('mod-author').value = m.author;
|
||||
showStatus('Loaded module: ' + name, 'info');
|
||||
} else {
|
||||
showStatus(data.error || 'Failed to load module', 'error');
|
||||
}
|
||||
})
|
||||
.catch(err => showStatus('Failed to load module: ' + err, 'error'));
|
||||
}
|
||||
|
||||
/* Refresh the module list */
|
||||
function refreshModuleList() {
|
||||
const container = document.getElementById('module-list');
|
||||
container.innerHTML = '<p style="color:var(--text-muted);font-size:0.8rem">Loading...</p>';
|
||||
|
||||
fetch(API + '/list')
|
||||
.then(r => r.json())
|
||||
.then(modules => {
|
||||
if (!modules.length) {
|
||||
container.innerHTML = '<p style="color:var(--text-muted);font-size:0.8rem">No modules found.</p>';
|
||||
return;
|
||||
}
|
||||
/* Group by category */
|
||||
const groups = {};
|
||||
modules.forEach(m => {
|
||||
const cat = m.category || 'unknown';
|
||||
if (!groups[cat]) groups[cat] = [];
|
||||
groups[cat].push(m);
|
||||
});
|
||||
|
||||
let html = '';
|
||||
const catOrder = ['defense','offense','counter','analyze','osint','simulate','core','hardware','unknown'];
|
||||
catOrder.forEach(cat => {
|
||||
if (!groups[cat]) return;
|
||||
html += '<div class="mc-cat-group">';
|
||||
html += '<h3>' + cat + ' (' + groups[cat].length + ')</h3>';
|
||||
groups[cat].forEach(m => {
|
||||
const desc = m.description ? m.description.substring(0, 60) : '';
|
||||
const ver = m.version ? 'v' + m.version : '';
|
||||
html += '<div class="mc-mod-item" onclick="loadModule(\'' + m.name.replace(/'/g, "\\'") + '\')">';
|
||||
html += '<div class="mod-name">' + m.name + '</div>';
|
||||
if (desc) html += '<div class="mod-desc">' + desc + '</div>';
|
||||
html += '<div class="mod-meta">' + [ver, m.last_modified].filter(Boolean).join(' · ') + '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
/* Any categories not in catOrder */
|
||||
Object.keys(groups).forEach(cat => {
|
||||
if (catOrder.includes(cat)) return;
|
||||
html += '<div class="mc-cat-group">';
|
||||
html += '<h3>' + cat + ' (' + groups[cat].length + ')</h3>';
|
||||
groups[cat].forEach(m => {
|
||||
const desc = m.description ? m.description.substring(0, 60) : '';
|
||||
const ver = m.version ? 'v' + m.version : '';
|
||||
html += '<div class="mc-mod-item" onclick="loadModule(\'' + m.name.replace(/'/g, "\\'") + '\')">';
|
||||
html += '<div class="mod-name">' + m.name + '</div>';
|
||||
if (desc) html += '<div class="mod-desc">' + desc + '</div>';
|
||||
html += '<div class="mod-meta">' + [ver, m.last_modified].filter(Boolean).join(' · ') + '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
})
|
||||
.catch(err => {
|
||||
container.innerHTML = '<p style="color:var(--danger);font-size:0.8rem">Failed to load modules</p>';
|
||||
});
|
||||
}
|
||||
|
||||
/* Initial load */
|
||||
refreshModuleList();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -112,6 +112,7 @@ function startDiscover(){
|
||||
document.getElementById('disc-status').innerHTML='';
|
||||
currentHosts=s.hosts||[];
|
||||
renderHosts(currentHosts);
|
||||
halAnalyze('Net Mapper', JSON.stringify(s, null, 2), 'network topology', 'network');
|
||||
});
|
||||
},2000);
|
||||
}).catch(e=>showDiscError(e.message));
|
||||
@@ -160,6 +161,7 @@ function showTopology(){
|
||||
if(!d.ok) return;
|
||||
renderTopology(d);
|
||||
switchTab('map');
|
||||
halAnalyze('Net Mapper', JSON.stringify(d, null, 2), 'network topology', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -231,6 +233,7 @@ function loadScans(){
|
||||
s1.innerHTML+=`<option value="${esc(s.file)}">${esc(s.name)} (${s.host_count})</option>`;
|
||||
s2.innerHTML+=`<option value="${esc(s.file)}">${esc(s.name)} (${s.host_count})</option>`;
|
||||
});
|
||||
halAnalyze('Net Mapper', JSON.stringify(d, null, 2), 'network topology', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -240,6 +243,7 @@ function loadSavedScan(file){
|
||||
currentHosts=d.scan.hosts||[];
|
||||
renderHosts(currentHosts);
|
||||
switchTab('discover');
|
||||
halAnalyze('Net Mapper', JSON.stringify(d, null, 2), 'network topology', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -256,6 +260,7 @@ function diffScans(){
|
||||
html+=`<div style="color:var(--danger)"><strong>- Removed (${d.removed_hosts.length}):</strong> ${d.removed_hosts.join(', ')||'none'}</div>`;
|
||||
html+=`<div style="color:var(--text-muted)">Unchanged: ${d.unchanged_hosts.length}</div></div>`;
|
||||
document.getElementById('diff-results').innerHTML=html;
|
||||
halAnalyze('Net Mapper', JSON.stringify(d, null, 2), 'network topology', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
895
web/templates/network.html
Normal file
895
web/templates/network.html
Normal file
@@ -0,0 +1,895 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Network Security - AUTARCH{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Network Security</h1>
|
||||
<p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
|
||||
Connection analysis, intrusion detection, rogue device scanning, and real-time monitoring.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<div class="tab-bar" id="network-tab-bar">
|
||||
<button class="tab active" onclick="networkTab('connections')">Connections</button>
|
||||
<button class="tab" onclick="networkTab('ids')">Intrusion Detection</button>
|
||||
<button class="tab" onclick="networkTab('rogue')">Rogue Devices</button>
|
||||
<button class="tab" onclick="networkTab('monitor')">Monitor</button>
|
||||
<button class="tab" onclick="networkTab('wifi')">WiFi Scanner</button>
|
||||
<button class="tab" onclick="networkTab('attacks')">Attack Detection</button>
|
||||
<button class="tab" onclick="networkTab('arpspoof')">ARP Spoof</button>
|
||||
<button class="tab" onclick="networkTab('ssid')">SSID Map</button>
|
||||
</div>
|
||||
|
||||
<!-- ==================== CONNECTIONS TAB ==================== -->
|
||||
<div class="network-tab-panel" id="network-tab-connections">
|
||||
|
||||
<div class="section">
|
||||
<h2>Active Connections</h2>
|
||||
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin-bottom:1rem">
|
||||
<button class="btn btn-primary" onclick="scanConnections()">Scan Connections</button>
|
||||
<button class="btn" onclick="scanArpTable()">ARP Table</button>
|
||||
<button class="btn" onclick="scanInterfaces()">Interfaces</button>
|
||||
</div>
|
||||
<div id="conn-status" style="margin-bottom:0.5rem;font-size:0.85rem;color:var(--text-secondary)"></div>
|
||||
<div id="conn-results" style="overflow-x:auto"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ==================== INTRUSION DETECTION TAB ==================== -->
|
||||
<div class="network-tab-panel hidden" id="network-tab-ids">
|
||||
|
||||
<div class="section">
|
||||
<h2>Intrusion Detection System</h2>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem">
|
||||
<button class="btn btn-primary" onclick="runIdsScan()">Run IDS Scan</button>
|
||||
<span id="ids-overall" style="font-size:0.85rem"></span>
|
||||
</div>
|
||||
<div id="ids-results"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ==================== ROGUE DEVICES TAB ==================== -->
|
||||
<div class="network-tab-panel hidden" id="network-tab-rogue">
|
||||
|
||||
<div class="section">
|
||||
<h2>Rogue Device Detection</h2>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem">
|
||||
<button class="btn btn-primary" onclick="scanRogueDevices()">Scan for Rogues</button>
|
||||
<span id="rogue-summary" style="font-size:0.85rem;color:var(--text-secondary)"></span>
|
||||
</div>
|
||||
|
||||
<div id="rogue-new" style="margin-bottom:1rem"></div>
|
||||
|
||||
<h3 style="margin-top:1rem">Known Devices</h3>
|
||||
<div id="rogue-known" style="overflow-x:auto"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ==================== MONITOR TAB ==================== -->
|
||||
<div class="network-tab-panel hidden" id="network-tab-monitor">
|
||||
|
||||
<div class="section">
|
||||
<h2>Real-Time Connection Monitor</h2>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem">
|
||||
<button class="btn btn-primary" id="monitor-start-btn" onclick="startMonitor()">Start Monitor</button>
|
||||
<button class="btn btn-danger" id="monitor-stop-btn" onclick="stopMonitor()" disabled>Stop Monitor</button>
|
||||
<span id="monitor-status" style="font-size:0.85rem;color:var(--text-secondary)">Stopped</span>
|
||||
</div>
|
||||
<div id="monitor-feed" style="overflow-x:auto;max-height:500px;overflow-y:auto">
|
||||
<table class="data-table" style="font-size:0.8rem">
|
||||
<thead><tr><th>Time</th><th>Protocol</th><th>Local</th><th>Remote</th><th>Process</th></tr></thead>
|
||||
<tbody id="monitor-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- WiFi Scanner Tab -->
|
||||
<div class="network-tab-panel hidden" id="network-tab-wifi">
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">WiFi Network Scanner</h3>
|
||||
<p style="font-size:0.78rem;color:var(--text-muted);margin-bottom:0.75rem">
|
||||
Scan for nearby WiFi networks. Shows SSIDs, BSSIDs, channels, signal strength, and security.
|
||||
Requires a wireless interface.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.5rem;margin-bottom:0.75rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="wifiScan()">Scan WiFi Networks</button>
|
||||
</div>
|
||||
<div id="wifi-scan-status" style="font-size:0.82rem;color:var(--text-muted);margin-bottom:0.5rem"></div>
|
||||
<div id="wifi-scan-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attack Detection Tab -->
|
||||
<div class="network-tab-panel hidden" id="network-tab-attacks">
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">WiFi Attack Detection</h3>
|
||||
<p style="font-size:0.78rem;color:var(--text-muted);margin-bottom:0.75rem">
|
||||
Scan for active attacks against your network: deauth floods, evil twin APs,
|
||||
WiFi Pineapple rogue APs, MITM/ARP poisoning, and SSL stripping.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:0.75rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="detectAttacks()" id="btn-detect-attacks">Run Attack Detection</button>
|
||||
<span id="attack-detect-status" style="font-size:0.82rem;color:var(--text-muted)"></span>
|
||||
</div>
|
||||
<div id="attack-results"></div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Pentesting Tools</h3>
|
||||
<p style="font-size:0.78rem;color:var(--text-muted);margin-bottom:0.75rem">
|
||||
Launch offensive WiFi tools for authorized penetration testing.
|
||||
</p>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.5rem">
|
||||
<a href="/deauth" class="btn btn-sm" style="text-align:center;text-decoration:none;border:1px solid var(--danger,#f55);color:var(--danger,#f55)">Deauth Attack</a>
|
||||
<a href="/pineapple" class="btn btn-sm" style="text-align:center;text-decoration:none;border:1px solid var(--danger,#f55);color:var(--danger,#f55)">Pineapple / Evil Twin</a>
|
||||
<a href="/mitm-proxy" class="btn btn-sm" style="text-align:center;text-decoration:none;border:1px solid var(--danger,#f55);color:var(--danger,#f55)">MITM Proxy</a>
|
||||
<a href="/wifi-audit" class="btn btn-sm" style="text-align:center;text-decoration:none;border:1px solid var(--danger,#f55);color:var(--danger,#f55)">WiFi Audit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ARP Spoof Detection & Remediation Tab -->
|
||||
<div class="network-tab-panel hidden" id="network-tab-arpspoof">
|
||||
|
||||
<!-- Detection -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">ARP Spoof Detection</h3>
|
||||
<p style="font-size:0.78rem;color:var(--text-muted);margin-bottom:0.75rem">
|
||||
Scans your ARP table for poisoning indicators: IPs with multiple MACs, gateway MAC changes,
|
||||
and suspicious broadcast entries. Compares against your saved baseline.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:0.75rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="arpSpoofScan()" id="btn-arp-scan">Scan for ARP Spoofing</button>
|
||||
<button class="btn btn-sm" onclick="arpSaveBaseline()" id="btn-arp-baseline">Save Current as Baseline</button>
|
||||
<span id="arp-scan-status" style="font-size:0.82rem;color:var(--text-muted)"></span>
|
||||
</div>
|
||||
<div id="arp-scan-results"></div>
|
||||
</div>
|
||||
|
||||
<!-- ARP Table -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Current ARP Table</h3>
|
||||
<div id="arp-table-display"></div>
|
||||
</div>
|
||||
|
||||
<!-- Remediation Tools -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">Remediation Tools</h3>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:0.75rem;font-size:0.82rem">
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.65rem;background:var(--bg-main)">
|
||||
<strong>Flush & Set Static ARP</strong>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin:0.3rem 0">
|
||||
Remove the poisoned entry and lock in the correct MAC for an IP.
|
||||
</div>
|
||||
<div style="display:flex;gap:0.4rem;margin-top:0.4rem">
|
||||
<input type="text" id="arp-fix-ip" placeholder="IP (e.g. 192.168.1.1)" style="flex:1;padding:0.3rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-card);color:inherit;font-size:0.78rem">
|
||||
<input type="text" id="arp-fix-mac" placeholder="MAC (e.g. aa:bb:cc:dd:ee:ff)" style="flex:1;padding:0.3rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-card);color:inherit;font-size:0.78rem">
|
||||
</div>
|
||||
<button class="btn btn-sm" onclick="arpFixStatic()" style="margin-top:0.4rem">Flush & Set Static</button>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.65rem;background:var(--bg-main)">
|
||||
<strong>Enable Kernel ARP Protection</strong>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin:0.3rem 0">
|
||||
Set <code>arp_announce=2</code>, <code>arp_ignore=1</code>, and <code>rp_filter=1</code>
|
||||
to make the kernel reject suspicious ARP replies.
|
||||
</div>
|
||||
<button class="btn btn-sm" onclick="arpEnableProtection()" style="margin-top:0.4rem">Enable Protection</button>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.65rem;background:var(--bg-main)">
|
||||
<strong>Flush Specific Entry</strong>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin:0.3rem 0">
|
||||
Remove a single IP from the ARP cache so it re-learns the correct MAC.
|
||||
</div>
|
||||
<div style="display:flex;gap:0.4rem;margin-top:0.4rem">
|
||||
<input type="text" id="arp-flush-ip" placeholder="IP to flush" style="flex:1;padding:0.3rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-card);color:inherit;font-size:0.78rem">
|
||||
<button class="btn btn-sm" onclick="arpFlushEntry()">Flush</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="arp-fix-results" style="margin-top:0.75rem"></div>
|
||||
</div>
|
||||
|
||||
<!-- How to Fix Guide -->
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">How ARP Spoofing Works & How to Fix It</h3>
|
||||
<div style="font-size:0.8rem;color:var(--text-secondary);line-height:1.65">
|
||||
<p><strong>What is ARP Spoofing?</strong><br>
|
||||
An attacker sends fake ARP (Address Resolution Protocol) replies to associate their MAC address
|
||||
with the IP of another device (usually the gateway). This causes your traffic to route through
|
||||
the attacker's machine instead of directly to the router — enabling eavesdropping, credential
|
||||
theft, and session hijacking.</p>
|
||||
|
||||
<p style="margin-top:0.6rem"><strong>Signs you're being spoofed:</strong></p>
|
||||
<ul style="padding-left:1.2rem;margin:0.3rem 0">
|
||||
<li>An IP address (especially the gateway) shows multiple MAC addresses</li>
|
||||
<li>Your gateway's MAC address changed from what it was before</li>
|
||||
<li>Internet is slow or connections drop intermittently</li>
|
||||
<li>HTTPS certificate warnings appearing on sites that worked before</li>
|
||||
</ul>
|
||||
|
||||
<p style="margin-top:0.6rem"><strong>Immediate fix (Linux):</strong></p>
|
||||
<pre style="background:var(--bg-main);border:1px solid var(--border);border-radius:var(--radius);padding:0.5rem;font-size:0.75rem;overflow-x:auto;margin:0.3rem 0"><span style="color:var(--text-muted)"># 1. Find your gateway IP and its REAL MAC (check your router's label)</span>
|
||||
ip route show default
|
||||
<span style="color:var(--text-muted)"># 2. Flush the poisoned entry</span>
|
||||
sudo ip neigh flush 192.168.1.1
|
||||
<span style="color:var(--text-muted)"># 3. Set a static ARP entry (replace with your router's real MAC)</span>
|
||||
sudo arp -s 192.168.1.1 aa:bb:cc:dd:ee:ff
|
||||
<span style="color:var(--text-muted)"># 4. Enable kernel-level ARP protection</span>
|
||||
sudo sysctl -w net.ipv4.conf.all.arp_announce=2
|
||||
sudo sysctl -w net.ipv4.conf.all.arp_ignore=1
|
||||
sudo sysctl -w net.ipv4.conf.all.rp_filter=1</pre>
|
||||
|
||||
<p style="margin-top:0.6rem"><strong>Permanent fix:</strong></p>
|
||||
<ul style="padding-left:1.2rem;margin:0.3rem 0">
|
||||
<li>Add the sysctl settings to <code>/etc/sysctl.conf</code> so they persist across reboots</li>
|
||||
<li>Use <strong>Dynamic ARP Inspection (DAI)</strong> on managed switches</li>
|
||||
<li>Use <strong>802.1X port authentication</strong> on your network</li>
|
||||
<li>Use a VPN — encrypted traffic can't be read even if intercepted</li>
|
||||
<li>Install <strong>arpwatch</strong>: <code>sudo apt install arpwatch</code> — monitors ARP changes 24/7</li>
|
||||
</ul>
|
||||
|
||||
<p style="margin-top:0.6rem"><strong>Find the attacker:</strong></p>
|
||||
<ul style="padding-left:1.2rem;margin:0.3rem 0">
|
||||
<li>Note the attacker's MAC address from the scan results</li>
|
||||
<li>Use the <strong>Intruder Trace</strong> section below with the attacker's IP</li>
|
||||
<li>Check your router's DHCP lease table to identify the device</li>
|
||||
<li>Run <code>nmap -sn 192.168.1.0/24</code> to find all devices and their MACs</li>
|
||||
<li>Block the attacker: <code>sudo iptables -A INPUT -m mac --mac-source XX:XX:XX:XX:XX:XX -j DROP</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSID Map Tab -->
|
||||
<div class="network-tab-panel hidden" id="network-tab-ssid">
|
||||
<div style="border:1px solid var(--border);background:var(--bg-card);border-radius:var(--radius);padding:0.85rem 1rem;margin-bottom:1rem">
|
||||
<h3 style="font-size:0.95rem;margin-bottom:0.5rem">SSID Scanner & Mapper</h3>
|
||||
<p style="font-size:0.78rem;color:var(--text-muted);margin-bottom:0.75rem">
|
||||
Map all WiFi networks in range. Groups access points by SSID, showing all BSSIDs,
|
||||
channels, signal strength, and security for each network. Useful for identifying
|
||||
multi-AP deployments and spotting rogues.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:0.75rem">
|
||||
<button class="btn btn-primary btn-sm" onclick="ssidMap()" id="btn-ssid-map">Build SSID Map</button>
|
||||
<span id="ssid-map-status" style="font-size:0.82rem;color:var(--text-muted)"></span>
|
||||
</div>
|
||||
<div id="ssid-map-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== INTRUDER TRACE (always visible) ==================== -->
|
||||
<div class="section" style="margin-top:2rem;border-top:1px solid var(--border);padding-top:1.5rem">
|
||||
<h2>Intruder Trace</h2>
|
||||
<p style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Trace an IP address: reverse DNS, GeoIP, whois, open ports, associated processes, connection history.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap">
|
||||
<input type="text" id="trace-ip" class="form-control" placeholder="Enter IP address" style="max-width:250px">
|
||||
<button class="btn btn-primary" onclick="traceIntruder()">Trace</button>
|
||||
</div>
|
||||
<div id="trace-status" style="margin-top:0.5rem;font-size:0.85rem;color:var(--text-secondary)"></div>
|
||||
<div id="trace-results" style="margin-top:1rem"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hidden { display: none !important; }
|
||||
.ids-group {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.ids-group h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.severity-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.severity-critical { background: rgba(239,68,68,0.2); color: #ef4444; }
|
||||
.severity-warning { background: rgba(234,179,8,0.2); color: #eab308; }
|
||||
.severity-clean { background: rgba(34,197,94,0.2); color: #22c55e; }
|
||||
.rogue-new-device {
|
||||
background: rgba(239,68,68,0.1);
|
||||
border: 1px solid rgba(239,68,68,0.3);
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.trace-section {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.trace-section h4 { margin: 0 0 0.5rem 0; color: var(--accent); }
|
||||
.trace-section pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-secondary);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/* ── Tab switching ── */
|
||||
function networkTab(name) {
|
||||
var panels = document.querySelectorAll('.network-tab-panel');
|
||||
for (var i = 0; i < panels.length; i++) panels[i].classList.add('hidden');
|
||||
var tabs = document.querySelectorAll('#network-tab-bar .tab');
|
||||
for (var i = 0; i < tabs.length; i++) tabs[i].classList.remove('active');
|
||||
document.getElementById('network-tab-' + name).classList.remove('hidden');
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
/* ── Helpers ── */
|
||||
function postJSON(url, body) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: body ? JSON.stringify(body) : '{}'
|
||||
}).then(function(r) { return r.json(); });
|
||||
}
|
||||
|
||||
function escHtml(s) {
|
||||
if (!s) return '';
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
/* ── Connections ── */
|
||||
function scanConnections() {
|
||||
var el = document.getElementById('conn-results');
|
||||
var st = document.getElementById('conn-status');
|
||||
st.textContent = 'Scanning connections...';
|
||||
el.innerHTML = '';
|
||||
postJSON('/network/connections').then(function(d) {
|
||||
if (!d.ok) { st.textContent = 'Error: ' + (d.error || 'unknown'); return; }
|
||||
st.textContent = d.count + ' connection(s) found';
|
||||
var html = '<table class="data-table" style="font-size:0.8rem"><thead><tr>' +
|
||||
'<th>Protocol</th><th>Local</th><th>Remote</th><th>State</th><th>Process</th></tr></thead><tbody>';
|
||||
for (var i = 0; i < d.connections.length; i++) {
|
||||
var c = d.connections[i];
|
||||
html += '<tr><td>' + escHtml(c.protocol) + '</td><td>' + escHtml(c.local) +
|
||||
'</td><td>' + escHtml(c.remote) + '</td><td>' + escHtml(c.state) +
|
||||
'</td><td>' + escHtml(c.process) + '</td></tr>';
|
||||
}
|
||||
html += '</tbody></table>';
|
||||
el.innerHTML = html;
|
||||
halAnalyze('Network: Connection Scan', JSON.stringify(d, null, 2), 'network scan', 'network');
|
||||
}).catch(function(e) { st.textContent = 'Error: ' + e; });
|
||||
}
|
||||
|
||||
function scanArpTable() {
|
||||
var el = document.getElementById('conn-results');
|
||||
var st = document.getElementById('conn-status');
|
||||
st.textContent = 'Fetching ARP table...';
|
||||
el.innerHTML = '';
|
||||
postJSON('/network/arp-table').then(function(d) {
|
||||
if (!d.ok) { st.textContent = 'Error: ' + (d.error || 'unknown'); return; }
|
||||
st.textContent = d.count + ' ARP entries';
|
||||
var html = '<table class="data-table" style="font-size:0.8rem"><thead><tr>' +
|
||||
'<th>IP</th><th>MAC</th><th>Interface</th><th>State</th></tr></thead><tbody>';
|
||||
for (var i = 0; i < d.entries.length; i++) {
|
||||
var e = d.entries[i];
|
||||
html += '<tr><td>' + escHtml(e.ip) + '</td><td>' + escHtml(e.mac) +
|
||||
'</td><td>' + escHtml(e.dev) + '</td><td>' + escHtml(e.state) + '</td></tr>';
|
||||
}
|
||||
html += '</tbody></table>';
|
||||
el.innerHTML = html;
|
||||
}).catch(function(e) { st.textContent = 'Error: ' + e; });
|
||||
}
|
||||
|
||||
function scanInterfaces() {
|
||||
var el = document.getElementById('conn-results');
|
||||
var st = document.getElementById('conn-status');
|
||||
st.textContent = 'Listing interfaces...';
|
||||
el.innerHTML = '';
|
||||
postJSON('/network/interfaces').then(function(d) {
|
||||
if (!d.ok) { st.textContent = 'Error: ' + (d.error || 'unknown'); return; }
|
||||
st.textContent = d.interfaces.length + ' interface(s)';
|
||||
var html = '<table class="data-table" style="font-size:0.8rem"><thead><tr>' +
|
||||
'<th>Name</th><th>State</th><th>MAC</th><th>MTU</th><th>Addresses</th></tr></thead><tbody>';
|
||||
for (var i = 0; i < d.interfaces.length; i++) {
|
||||
var ifc = d.interfaces[i];
|
||||
var addrs = '';
|
||||
if (ifc.addresses) {
|
||||
for (var j = 0; j < ifc.addresses.length; j++) {
|
||||
if (j > 0) addrs += '<br>';
|
||||
addrs += escHtml(ifc.addresses[j].address || '');
|
||||
}
|
||||
}
|
||||
html += '<tr><td><strong>' + escHtml(ifc.name) + '</strong></td><td>' + escHtml(ifc.state) +
|
||||
'</td><td>' + escHtml(ifc.mac) + '</td><td>' + escHtml(ifc.mtu) +
|
||||
'</td><td>' + addrs + '</td></tr>';
|
||||
}
|
||||
html += '</tbody></table>';
|
||||
el.innerHTML = html;
|
||||
}).catch(function(e) { st.textContent = 'Error: ' + e; });
|
||||
}
|
||||
|
||||
/* ── IDS ── */
|
||||
function severityBadge(sev) {
|
||||
return '<span class="severity-badge severity-' + sev + '">' + sev + '</span>';
|
||||
}
|
||||
|
||||
function runIdsScan() {
|
||||
var el = document.getElementById('ids-results');
|
||||
var ov = document.getElementById('ids-overall');
|
||||
ov.innerHTML = 'Scanning...';
|
||||
el.innerHTML = '';
|
||||
postJSON('/network/ids/scan').then(function(d) {
|
||||
if (!d.ok) { ov.textContent = 'Scan failed'; return; }
|
||||
var r = d.results;
|
||||
ov.innerHTML = 'Overall: ' + severityBadge(r.overall);
|
||||
|
||||
var groups = [
|
||||
{key: 'arp_spoof', title: 'ARP Spoof Detection'},
|
||||
{key: 'promiscuous', title: 'Promiscuous Mode'},
|
||||
{key: 'dhcp', title: 'Unauthorized DHCP'},
|
||||
{key: 'suspicious_conns', title: 'Suspicious Connections'},
|
||||
{key: 'raw_sockets', title: 'Raw Socket Processes'}
|
||||
];
|
||||
|
||||
var html = '';
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
var g = groups[i];
|
||||
var data = r[g.key];
|
||||
html += '<div class="ids-group">';
|
||||
html += '<h4>' + escHtml(g.title) + ' ' + severityBadge(data.severity) + '</h4>';
|
||||
html += '<p style="font-size:0.85rem;color:var(--text-secondary);margin:0 0 0.5rem 0">' + escHtml(data.details) + '</p>';
|
||||
if (data.alerts && data.alerts.length > 0) {
|
||||
html += '<ul style="margin:0;padding-left:1.2rem;font-size:0.8rem">';
|
||||
for (var j = 0; j < data.alerts.length; j++) {
|
||||
var sev = data.severity === 'critical' ? 'color:#ef4444' : 'color:#eab308';
|
||||
html += '<li style="' + sev + ';margin-bottom:0.25rem">' + escHtml(data.alerts[j].message) + '</li>';
|
||||
}
|
||||
html += '</ul>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
el.innerHTML = html;
|
||||
halAnalyze('Network: IDS Scan', JSON.stringify(d, null, 2), 'network scan', 'network');
|
||||
}).catch(function(e) { ov.textContent = 'Error: ' + e; });
|
||||
}
|
||||
|
||||
/* ── Rogue Devices ── */
|
||||
function scanRogueDevices() {
|
||||
var newEl = document.getElementById('rogue-new');
|
||||
var knownEl = document.getElementById('rogue-known');
|
||||
var sumEl = document.getElementById('rogue-summary');
|
||||
sumEl.textContent = 'Scanning...';
|
||||
newEl.innerHTML = '';
|
||||
knownEl.innerHTML = '';
|
||||
postJSON('/network/rogue-detect').then(function(d) {
|
||||
if (!d.ok) { sumEl.textContent = 'Error: ' + (d.error || 'unknown'); return; }
|
||||
var s = d.summary;
|
||||
sumEl.textContent = s.total + ' devices found, ' + s.known + ' known, ' + s.new + ' new, ' + s.spoofed + ' spoofed';
|
||||
|
||||
// New/unauthorized devices
|
||||
if (d.new_devices.length > 0) {
|
||||
var html = '<h3 style="color:#ef4444;margin-bottom:0.5rem">New / Unknown Devices</h3>';
|
||||
for (var i = 0; i < d.new_devices.length; i++) {
|
||||
var dev = d.new_devices[i];
|
||||
html += '<div class="rogue-new-device">';
|
||||
html += '<div><strong>' + escHtml(dev.ip) + '</strong> — ' + escHtml(dev.mac) + '</div>';
|
||||
html += '<div style="display:flex;gap:0.5rem">';
|
||||
html += '<button class="btn btn-sm" onclick="trustDevice(\'' + escHtml(dev.ip) + '\',\'' + escHtml(dev.mac) + '\')">Trust</button>';
|
||||
html += '<button class="btn btn-danger btn-sm" onclick="blockDevice(\'' + escHtml(dev.ip) + '\')">Block</button>';
|
||||
html += '</div></div>';
|
||||
}
|
||||
newEl.innerHTML = html;
|
||||
}
|
||||
|
||||
// Spoofed alerts
|
||||
if (d.spoofed.length > 0) {
|
||||
var shtml = '<h3 style="color:#ef4444;margin:1rem 0 0.5rem 0">Spoofed MAC Alerts</h3>';
|
||||
for (var i = 0; i < d.spoofed.length; i++) {
|
||||
shtml += '<div class="rogue-new-device">' + escHtml(d.spoofed[i].message) + '</div>';
|
||||
}
|
||||
newEl.innerHTML += shtml;
|
||||
}
|
||||
|
||||
// Known devices table
|
||||
var known = d.known_devices;
|
||||
var keys = Object.keys(known);
|
||||
if (keys.length > 0) {
|
||||
var khtml = '<table class="data-table" style="font-size:0.8rem"><thead><tr>' +
|
||||
'<th>IP</th><th>MAC</th><th>Trusted</th><th>First Seen</th><th>Last Seen</th></tr></thead><tbody>';
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var k = keys[i];
|
||||
var kd = known[k];
|
||||
khtml += '<tr><td>' + escHtml(k) + '</td><td>' + escHtml(kd.mac) +
|
||||
'</td><td>' + (kd.trusted ? 'Yes' : 'No') +
|
||||
'</td><td>' + escHtml(kd.first_seen || '') +
|
||||
'</td><td>' + escHtml(kd.last_seen || '') + '</td></tr>';
|
||||
}
|
||||
khtml += '</tbody></table>';
|
||||
knownEl.innerHTML = khtml;
|
||||
} else {
|
||||
knownEl.innerHTML = '<p style="color:var(--text-secondary);font-size:0.85rem">No known devices yet. Scan and trust devices to build your baseline.</p>';
|
||||
}
|
||||
halAnalyze('Network: Rogue Device Scan', JSON.stringify(d, null, 2), 'network scan', 'network');
|
||||
}).catch(function(e) { sumEl.textContent = 'Error: ' + e; });
|
||||
}
|
||||
|
||||
function trustDevice(ip, mac) {
|
||||
postJSON('/network/rogue-detect/trust', {ip: ip, mac: mac}).then(function(d) {
|
||||
if (d.ok) scanRogueDevices();
|
||||
else alert('Error: ' + (d.error || 'unknown'));
|
||||
});
|
||||
}
|
||||
|
||||
function blockDevice(ip) {
|
||||
if (!confirm('Block IP ' + ip + '?')) return;
|
||||
postJSON('/network/block-ip', {ip: ip, action: 'block'}).then(function(d) {
|
||||
alert(d.message || d.error || 'Done');
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Monitor ── */
|
||||
var monitorSource = null;
|
||||
|
||||
function startMonitor() {
|
||||
document.getElementById('monitor-body').innerHTML = '';
|
||||
postJSON('/network/monitor/start').then(function(d) {
|
||||
if (!d.ok) { alert(d.error || 'Failed'); return; }
|
||||
document.getElementById('monitor-start-btn').disabled = true;
|
||||
document.getElementById('monitor-stop-btn').disabled = false;
|
||||
document.getElementById('monitor-status').textContent = 'Running...';
|
||||
document.getElementById('monitor-status').style.color = '#22c55e';
|
||||
|
||||
monitorSource = new EventSource('/network/monitor/feed');
|
||||
monitorSource.onmessage = function(ev) {
|
||||
try {
|
||||
var data = JSON.parse(ev.data);
|
||||
if (data.done) { stopMonitor(); return; }
|
||||
var body = document.getElementById('monitor-body');
|
||||
var row = document.createElement('tr');
|
||||
var ts = data.timestamp ? data.timestamp.split('T')[1].split('.')[0] : '';
|
||||
row.innerHTML = '<td>' + escHtml(ts) + '</td><td>' + escHtml(data.protocol) +
|
||||
'</td><td>' + escHtml(data.local) + '</td><td>' + escHtml(data.remote) +
|
||||
'</td><td>' + escHtml(data.process) + '</td>';
|
||||
body.insertBefore(row, body.firstChild);
|
||||
// Cap displayed rows
|
||||
while (body.children.length > 200) body.removeChild(body.lastChild);
|
||||
} catch(e) {}
|
||||
};
|
||||
monitorSource.onerror = function() {
|
||||
document.getElementById('monitor-status').textContent = 'Connection lost';
|
||||
document.getElementById('monitor-status').style.color = '#ef4444';
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function stopMonitor() {
|
||||
postJSON('/network/monitor/stop').then(function() {
|
||||
document.getElementById('monitor-start-btn').disabled = false;
|
||||
document.getElementById('monitor-stop-btn').disabled = true;
|
||||
document.getElementById('monitor-status').textContent = 'Stopped';
|
||||
document.getElementById('monitor-status').style.color = 'var(--text-secondary)';
|
||||
});
|
||||
if (monitorSource) { monitorSource.close(); monitorSource = null; }
|
||||
}
|
||||
|
||||
/* ── Intruder Trace ── */
|
||||
function traceIntruder() {
|
||||
var ip = document.getElementById('trace-ip').value.trim();
|
||||
if (!ip) { alert('Enter an IP address'); return; }
|
||||
var st = document.getElementById('trace-status');
|
||||
var el = document.getElementById('trace-results');
|
||||
st.textContent = 'Tracing ' + ip + '... this may take up to 30 seconds.';
|
||||
el.innerHTML = '';
|
||||
postJSON('/network/intruder-trace', {ip: ip}).then(function(d) {
|
||||
if (!d.ok) { st.textContent = 'Error: ' + (d.error || 'unknown'); return; }
|
||||
st.textContent = 'Trace complete for ' + ip;
|
||||
var t = d.trace;
|
||||
var sections = [
|
||||
{title: 'Reverse DNS', data: t.reverse_dns},
|
||||
{title: 'GeoIP', data: t.geoip},
|
||||
{title: 'Whois', data: t.whois},
|
||||
{title: 'Open Ports', data: t.open_ports},
|
||||
{title: 'Associated Processes', data: t.processes},
|
||||
{title: 'Connection History', data: t.connection_history}
|
||||
];
|
||||
var html = '';
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
html += '<div class="trace-section"><h4>' + escHtml(sections[i].title) + '</h4>';
|
||||
html += '<pre>' + escHtml(sections[i].data) + '</pre></div>';
|
||||
}
|
||||
el.innerHTML = html;
|
||||
}).catch(function(e) { st.textContent = 'Error: ' + e; });
|
||||
}
|
||||
|
||||
// ── WiFi Scanner ─────────────────────────────────────────────────────────────
|
||||
function wifiScan() {
|
||||
var status = document.getElementById('wifi-scan-status');
|
||||
var results = document.getElementById('wifi-scan-results');
|
||||
status.textContent = 'Scanning WiFi networks…';
|
||||
results.innerHTML = '';
|
||||
|
||||
fetch('/network/wifi/scan', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (!d.ok) { status.textContent = 'Error: ' + (d.error || 'Unknown'); return; }
|
||||
status.textContent = d.count + ' network(s) found';
|
||||
if (!d.networks || !d.networks.length) { results.innerHTML = '<p style="color:var(--text-muted)">No networks found.</p>'; return; }
|
||||
|
||||
var html = '<table class="data-table" style="font-size:0.82rem"><thead><tr>'
|
||||
+ '<th>SSID</th><th>BSSID</th><th>Channel</th><th>Signal</th><th>Security</th><th>Mode</th>'
|
||||
+ '</tr></thead><tbody>';
|
||||
for (var i = 0; i < d.networks.length; i++) {
|
||||
var n = d.networks[i];
|
||||
var sig = parseInt(n.signal) || 0;
|
||||
var sigColor = sig > 70 ? 'var(--success,#34c759)' : sig > 40 ? '#f59e0b' : 'var(--danger,#ff3b30)';
|
||||
html += '<tr><td><strong>' + escHtml(n.ssid) + '</strong></td>'
|
||||
+ '<td style="font-family:monospace;font-size:0.75rem">' + escHtml(n.bssid) + '</td>'
|
||||
+ '<td>' + escHtml(n.channel) + '</td>'
|
||||
+ '<td style="color:' + sigColor + '">' + escHtml(n.signal) + '%</td>'
|
||||
+ '<td>' + escHtml(n.security) + '</td>'
|
||||
+ '<td>' + escHtml(n.mode || '') + '</td></tr>';
|
||||
}
|
||||
html += '</tbody></table>';
|
||||
results.innerHTML = html;
|
||||
halAnalyze('Network: WiFi Scan', JSON.stringify(d, null, 2), 'network scan', 'network');
|
||||
})
|
||||
.catch(function(e) { status.textContent = 'Request failed: ' + e.message; });
|
||||
}
|
||||
|
||||
// ── Attack Detection ─────────────────────────────────────────────────────────
|
||||
function detectAttacks() {
|
||||
var btn = document.getElementById('btn-detect-attacks');
|
||||
var status = document.getElementById('attack-detect-status');
|
||||
var results = document.getElementById('attack-results');
|
||||
btn.disabled = true; btn.textContent = 'Scanning…';
|
||||
status.textContent = 'Running 5 attack detection checks…';
|
||||
results.innerHTML = '';
|
||||
|
||||
fetch('/network/wifi/detect-attacks', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false; btn.textContent = 'Run Attack Detection';
|
||||
if (!d.ok) { status.textContent = 'Error: ' + (d.error || 'Unknown'); return; }
|
||||
|
||||
var sev = d.severity || {};
|
||||
status.innerHTML = '<span style="color:var(--danger,#ff3b30)">' + (sev.critical || 0) + ' critical</span>'
|
||||
+ ' · <span style="color:#f59e0b">' + (sev.warning || 0) + ' warning</span>'
|
||||
+ ' · <span style="color:var(--success,#34c759)">' + (sev.clean || 0) + ' clean</span>';
|
||||
|
||||
var html = '';
|
||||
var findings = d.findings || [];
|
||||
for (var i = 0; i < findings.length; i++) {
|
||||
var f = findings[i];
|
||||
var color = f.severity === 'critical' ? 'var(--danger,#ff3b30)' : f.severity === 'warning' ? '#f59e0b' : 'var(--success,#34c759)';
|
||||
var icon = f.severity === 'critical' ? '⚠' : f.severity === 'warning' ? '⚠' : '✓';
|
||||
html += '<div style="border:1px solid ' + color + ';border-radius:var(--radius);padding:0.65rem 0.85rem;margin-bottom:0.5rem;background:var(--bg-card)">'
|
||||
+ '<div style="display:flex;justify-content:space-between;align-items:center">'
|
||||
+ '<strong style="color:' + color + '">' + icon + ' ' + escHtml(f.check) + '</strong>'
|
||||
+ '<span style="font-size:0.72rem;padding:2px 8px;border-radius:3px;border:1px solid ' + color + ';color:' + color + '">' + f.severity + '</span>'
|
||||
+ '</div>'
|
||||
+ '<div style="font-size:0.82rem;color:var(--text-secondary);margin-top:0.3rem">' + escHtml(f.description) + '</div>';
|
||||
if (f.details && f.details.length) {
|
||||
html += '<ul style="font-size:0.75rem;color:var(--text-muted);margin:0.4rem 0 0 1rem;padding:0">';
|
||||
for (var j = 0; j < f.details.length; j++) {
|
||||
html += '<li>' + escHtml(f.details[j]) + '</li>';
|
||||
}
|
||||
html += '</ul>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
results.innerHTML = html;
|
||||
halAnalyze('Network: Attack Detection', JSON.stringify(d, null, 2), 'network scan', 'network');
|
||||
})
|
||||
.catch(function(e) { btn.disabled = false; btn.textContent = 'Run Attack Detection'; status.textContent = 'Failed: ' + e.message; });
|
||||
}
|
||||
|
||||
// ── SSID Map ─────────────────────────────────────────────────────────────────
|
||||
function ssidMap() {
|
||||
var btn = document.getElementById('btn-ssid-map');
|
||||
var status = document.getElementById('ssid-map-status');
|
||||
var results = document.getElementById('ssid-map-results');
|
||||
btn.disabled = true; btn.textContent = 'Mapping…';
|
||||
status.textContent = 'Building SSID map…';
|
||||
results.innerHTML = '';
|
||||
|
||||
fetch('/network/wifi/ssid-map', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false; btn.textContent = 'Build SSID Map';
|
||||
if (!d.ok) { status.textContent = 'Error: ' + (d.error || 'Unknown'); return; }
|
||||
status.textContent = d.total_ssids + ' SSIDs, ' + d.total_aps + ' access points';
|
||||
|
||||
var html = '';
|
||||
var ssids = d.ssids || [];
|
||||
for (var i = 0; i < ssids.length; i++) {
|
||||
var s = ssids[i];
|
||||
var aps = s.aps || [];
|
||||
html += '<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.65rem 0.85rem;margin-bottom:0.5rem;background:var(--bg-card)">'
|
||||
+ '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.4rem">'
|
||||
+ '<strong style="color:var(--accent)">' + escHtml(s.ssid) + '</strong>'
|
||||
+ '<span style="font-size:0.72rem;color:var(--text-muted)">' + aps.length + ' AP(s) · ' + escHtml(s.security || '') + '</span>'
|
||||
+ '</div>';
|
||||
|
||||
if (aps.length > 0) {
|
||||
html += '<table style="width:100%;font-size:0.75rem;border-collapse:collapse">'
|
||||
+ '<tr style="color:var(--text-muted)"><td>BSSID</td><td>Ch</td><td>Signal</td><td>Security</td></tr>';
|
||||
for (var j = 0; j < aps.length; j++) {
|
||||
var a = aps[j];
|
||||
var sig = parseInt(a.signal) || 0;
|
||||
var sigColor = sig > 70 ? 'var(--success,#34c759)' : sig > 40 ? '#f59e0b' : 'var(--danger,#ff3b30)';
|
||||
html += '<tr><td style="font-family:monospace">' + escHtml(a.bssid) + '</td>'
|
||||
+ '<td>' + escHtml(a.channel) + '</td>'
|
||||
+ '<td style="color:' + sigColor + '">' + sig + '%</td>'
|
||||
+ '<td>' + escHtml(a.security || '') + '</td></tr>';
|
||||
}
|
||||
html += '</table>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
results.innerHTML = html || '<p style="color:var(--text-muted)">No SSIDs found.</p>';
|
||||
halAnalyze('Network: SSID Map', JSON.stringify(d, null, 2), 'network scan', 'network');
|
||||
})
|
||||
.catch(function(e) { btn.disabled = false; btn.textContent = 'Build SSID Map'; status.textContent = 'Failed: ' + e.message; });
|
||||
}
|
||||
|
||||
// ── ARP Spoof Detection & Remediation ────────────────────────────────────────
|
||||
function arpSpoofScan() {
|
||||
var btn = document.getElementById('btn-arp-scan');
|
||||
var status = document.getElementById('arp-scan-status');
|
||||
var results = document.getElementById('arp-scan-results');
|
||||
var tableDisplay = document.getElementById('arp-table-display');
|
||||
btn.disabled = true; btn.textContent = 'Scanning...';
|
||||
status.textContent = 'Checking ARP table and gateway...';
|
||||
results.innerHTML = ''; tableDisplay.innerHTML = '';
|
||||
|
||||
fetch('/network/arp-spoof/scan', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false; btn.textContent = 'Scan for ARP Spoofing';
|
||||
if (!d.ok) { status.textContent = 'Error: ' + (d.error || 'Unknown'); return; }
|
||||
|
||||
// Gateway info
|
||||
var gw = d.gateway || {};
|
||||
var gwHtml = '<div style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.5rem">'
|
||||
+ 'Gateway: <strong>' + escHtml(gw.ip || '?') + '</strong>'
|
||||
+ ' · MAC: <code>' + escHtml(gw.mac || '?') + '</code>'
|
||||
+ ' · Interface: ' + escHtml(gw.interface || '?')
|
||||
+ ' · Baseline: ' + (d.has_baseline ? '<span style="color:var(--success,#34c759)">saved</span>' : '<span style="color:#f59e0b">not set</span>')
|
||||
+ '</div>';
|
||||
|
||||
// Severity summary
|
||||
var sevColors = {critical: 'var(--danger,#ff3b30)', warning: '#f59e0b', clean: 'var(--success,#34c759)'};
|
||||
status.innerHTML = '<span style="color:' + (sevColors[d.severity] || 'var(--text-muted)') + ';font-weight:700">'
|
||||
+ d.severity.toUpperCase() + '</span> — ' + (d.findings || []).length + ' finding(s)';
|
||||
|
||||
// Findings
|
||||
var html = gwHtml;
|
||||
var findings = d.findings || [];
|
||||
if (findings.length === 0) {
|
||||
html += '<div style="padding:0.5rem 0.75rem;border:1px solid var(--success,#34c759);border-radius:var(--radius);color:var(--success,#34c759);font-size:0.82rem">'
|
||||
+ '✓ No ARP spoofing detected. Your ARP table looks clean.</div>';
|
||||
}
|
||||
for (var i = 0; i < findings.length; i++) {
|
||||
var f = findings[i];
|
||||
var fc = f.severity === 'critical' ? 'var(--danger,#ff3b30)' : '#f59e0b';
|
||||
html += '<div style="border:1px solid ' + fc + ';border-radius:var(--radius);padding:0.6rem 0.8rem;margin-bottom:0.5rem;background:var(--bg-card)">'
|
||||
+ '<strong style="color:' + fc + '">⚠ ' + escHtml(f.message) + '</strong>'
|
||||
+ '<div style="font-size:0.78rem;color:var(--text-secondary);margin-top:0.25rem">' + escHtml(f.detail || '') + '</div>';
|
||||
if (f.fix) {
|
||||
html += '<div style="font-size:0.75rem;margin-top:0.3rem"><strong>Fix:</strong> <code>' + escHtml(f.fix) + '</code></div>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
results.innerHTML = html;
|
||||
|
||||
// ARP table
|
||||
var entries = d.arp_table || [];
|
||||
if (entries.length) {
|
||||
var thtml = '<table class="data-table" style="font-size:0.8rem"><thead><tr><th>IP</th><th>MAC</th><th>State</th><th>Action</th></tr></thead><tbody>';
|
||||
for (var j = 0; j < entries.length; j++) {
|
||||
var e = entries[j];
|
||||
thtml += '<tr><td>' + escHtml(e.ip) + '</td><td style="font-family:monospace">' + escHtml(e.mac) + '</td>'
|
||||
+ '<td>' + escHtml(e.state) + '</td>'
|
||||
+ '<td><button class="btn btn-sm" style="font-size:0.65rem;padding:1px 6px" '
|
||||
+ 'onclick="arpFlushOne(\'' + escHtml(e.ip) + '\')">Flush</button></td></tr>';
|
||||
}
|
||||
thtml += '</tbody></table>';
|
||||
tableDisplay.innerHTML = thtml;
|
||||
}
|
||||
|
||||
halAnalyze('Network: ARP Spoof Scan', JSON.stringify(d, null, 2), 'ARP spoofing detection', 'network');
|
||||
})
|
||||
.catch(function(e) { btn.disabled = false; btn.textContent = 'Scan for ARP Spoofing'; status.textContent = 'Failed: ' + e.message; });
|
||||
}
|
||||
|
||||
function arpSaveBaseline() {
|
||||
var btn = document.getElementById('btn-arp-baseline');
|
||||
btn.disabled = true; btn.textContent = 'Saving...';
|
||||
fetch('/network/arp-spoof/save-baseline', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
btn.disabled = false; btn.textContent = 'Save Current as Baseline';
|
||||
if (d.ok) {
|
||||
alert('Baseline saved! Gateway MAC: ' + (d.gateway_mac || 'unknown') + ', ' + d.entries + ' entries stored.');
|
||||
} else {
|
||||
alert('Error saving baseline');
|
||||
}
|
||||
})
|
||||
.catch(function(e) { btn.disabled = false; btn.textContent = 'Save Current as Baseline'; });
|
||||
}
|
||||
|
||||
function arpFixStatic() {
|
||||
var ip = document.getElementById('arp-fix-ip').value.trim();
|
||||
var mac = document.getElementById('arp-fix-mac').value.trim();
|
||||
if (!ip || !mac) { alert('Enter both IP and MAC'); return; }
|
||||
_arpFix('flush_and_static', {ip: ip, mac: mac});
|
||||
}
|
||||
|
||||
function arpEnableProtection() {
|
||||
_arpFix('enable_arp_protection', {});
|
||||
}
|
||||
|
||||
function arpFlushEntry() {
|
||||
var ip = document.getElementById('arp-flush-ip').value.trim();
|
||||
if (!ip) { alert('Enter an IP'); return; }
|
||||
_arpFix('flush_entry', {ip: ip});
|
||||
}
|
||||
|
||||
function arpFlushOne(ip) {
|
||||
_arpFix('flush_entry', {ip: ip});
|
||||
}
|
||||
|
||||
function _arpFix(action, extra) {
|
||||
var el = document.getElementById('arp-fix-results');
|
||||
el.innerHTML = '<span style="color:var(--text-muted)">Applying fix...</span>';
|
||||
var payload = Object.assign({action: action}, extra);
|
||||
fetch('/network/arp-spoof/fix', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (!d.ok) { el.innerHTML = '<span style="color:var(--danger,#ff3b30)">Error: ' + escHtml(d.error || 'Unknown') + '</span>'; return; }
|
||||
var html = '';
|
||||
var results = d.results || [];
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
var r = results[i];
|
||||
var color = r.ok ? 'var(--success,#34c759)' : 'var(--danger,#ff3b30)';
|
||||
html += '<div style="font-size:0.78rem;margin-bottom:0.3rem">'
|
||||
+ '<span style="color:' + color + '">' + (r.ok ? '✓' : '✕') + '</span> '
|
||||
+ '<code>' + escHtml(r.cmd) + '</code>';
|
||||
if (r.output) html += ' <span style="color:var(--text-muted)">' + escHtml(r.output.trim()) + '</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
el.innerHTML = html || '<span style="color:var(--success,#34c759)">Done</span>';
|
||||
})
|
||||
.catch(function(e) { el.innerHTML = '<span style="color:var(--danger,#ff3b30)">Failed: ' + e.message + '</span>'; });
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -226,6 +226,7 @@ function identifyHash(){
|
||||
${r.types.length?r.types.map(t=>`<span class="conf-${t.confidence}">${t.confidence.toUpperCase()}</span> ${t.name} `).join(' | '):'<span style="color:var(--text-muted)">Unknown</span>'}
|
||||
</div>`).join('');
|
||||
}
|
||||
halAnalyze('Password Toolkit', JSON.stringify(d, null, 2), 'password analysis', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -237,6 +238,7 @@ function hashString(){
|
||||
document.getElementById('hash-result').innerHTML=d.ok?
|
||||
`<strong>${d.algorithm}:</strong> ${d.hash} <span class="copy-btn" onclick="navigator.clipboard.writeText('${d.hash}')">[copy]</span>`
|
||||
:`Error: ${d.error}`;
|
||||
halAnalyze('Password Toolkit', JSON.stringify(d, null, 2), 'password analysis', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -268,6 +270,7 @@ function startCrack(){
|
||||
} else {
|
||||
div.innerHTML=`<div style="color:var(--text-muted)">${esc(d.message||d.error||'No result')}</div>`;
|
||||
}
|
||||
halAnalyze('Password Toolkit', JSON.stringify(d, null, 2), 'password analysis', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -298,6 +301,7 @@ function generatePw(){
|
||||
<span style="font-size:0.75rem;color:var(--text-muted)">${p.entropy} bits — ${p.strength}</span>
|
||||
<div class="strength-bar str-${p.strength}" style="width:100px"><div class="strength-fill"></div></div>
|
||||
</div>`).join('');
|
||||
halAnalyze('Password Toolkit', JSON.stringify(d, null, 2), 'password analysis', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +237,7 @@ function startScan() {
|
||||
document.getElementById('ps-cancel-btn').style.display = '';
|
||||
appendLine('info', `► Scan started | job=${d.job_id} | ${d.port_count} ports queued`);
|
||||
openStream(d.job_id);
|
||||
halAnalyze('Port Scanner', JSON.stringify(d, null, 2), 'port scan', 'network');
|
||||
}).catch(e => appendLine('error', '✗ ' + e.message));
|
||||
}
|
||||
|
||||
|
||||
722
web/templates/remote_monitor.html
Normal file
722
web/templates/remote_monitor.html
Normal file
@@ -0,0 +1,722 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Remote Monitor Station - AUTARCH{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Remote Monitoring Station</h1>
|
||||
<p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
|
||||
Connect and control remote WiFi monitoring devices via .piap profiles.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Device Selector -->
|
||||
<div class="section" style="margin-bottom:1rem">
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap">
|
||||
<select id="piap-select" onchange="rmLoadPiap()" style="min-width:200px;padding:0.4rem">
|
||||
<option value="">— Select Device —</option>
|
||||
{% for p in piap_files %}
|
||||
<option value="{{ p.filename }}">{{ p.name }}</option>
|
||||
{% endfor %}
|
||||
<option value="__new__">+ Create New</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" id="rm-connect-btn" onclick="rmConnect()" disabled>Connect</button>
|
||||
<button class="btn" id="rm-disconnect-btn" onclick="rmDisconnect()" disabled style="display:none">Disconnect</button>
|
||||
<span id="rm-conn-status" style="font-size:0.85rem;color:var(--text-secondary)"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device Panel (hidden until loaded) -->
|
||||
<div id="rm-device-panel" style="display:none">
|
||||
|
||||
<!-- Device Info Bar -->
|
||||
<div class="section" style="margin-bottom:1rem;padding:0.5rem 1rem;display:flex;gap:2rem;flex-wrap:wrap;font-size:0.85rem" id="rm-device-info-bar">
|
||||
<span><strong>Model:</strong> <span id="rm-model"></span></span>
|
||||
<span><strong>OS:</strong> <span id="rm-os"></span></span>
|
||||
<span><strong>Chipset:</strong> <span id="rm-chipset"></span></span>
|
||||
<span><strong>Host:</strong> <span id="rm-host"></span></span>
|
||||
</div>
|
||||
|
||||
<!-- Sub-tab bar (built dynamically from .piap) -->
|
||||
<div class="tab-bar" id="rm-tab-bar">
|
||||
<button class="tab active" onclick="rmTab('radios')">Radios</button>
|
||||
<!-- Additional tabs built from features -->
|
||||
</div>
|
||||
|
||||
<!-- ==================== RADIOS TAB ==================== -->
|
||||
<div class="rm-tab-panel" id="rm-tab-radios">
|
||||
<div id="rm-radios-container"></div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== CAPTURE TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-capture">
|
||||
<div class="section">
|
||||
<h2>Packet Capture</h2>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem;flex-wrap:wrap">
|
||||
<label>Radio:</label>
|
||||
<select id="rm-cap-radio" style="padding:0.4rem"></select>
|
||||
<button class="btn btn-primary" onclick="rmCaptureStart()">Start Capture</button>
|
||||
<button class="btn btn-danger" onclick="rmCaptureStop()">Stop Capture</button>
|
||||
</div>
|
||||
<div id="rm-capture-status" style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:0.5rem"></div>
|
||||
<pre id="rm-capture-output" style="max-height:400px;overflow:auto;background:var(--bg-secondary);padding:1rem;border-radius:4px;font-size:0.8rem"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== SCAN TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-scan">
|
||||
<div class="section">
|
||||
<h2>WiFi Scan</h2>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem;flex-wrap:wrap">
|
||||
<label>Radio:</label>
|
||||
<select id="rm-scan-radio" style="padding:0.4rem"></select>
|
||||
<button class="btn btn-primary" onclick="rmWifiScan()">Scan</button>
|
||||
</div>
|
||||
<div id="rm-scan-status" style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:0.5rem"></div>
|
||||
<pre id="rm-scan-output" style="max-height:400px;overflow:auto;background:var(--bg-secondary);padding:1rem;border-radius:4px;font-size:0.8rem"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== DEAUTH TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-deauth">
|
||||
<div class="section">
|
||||
<h2>Deauthentication</h2>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem;flex-wrap:wrap">
|
||||
<label>Radio:</label>
|
||||
<select id="rm-deauth-radio" style="padding:0.4rem"></select>
|
||||
<label>BSSID:</label>
|
||||
<input type="text" id="rm-deauth-bssid" placeholder="AA:BB:CC:DD:EE:FF" style="padding:0.4rem;width:180px">
|
||||
<label>Count:</label>
|
||||
<input type="number" id="rm-deauth-count" value="10" min="1" max="1000" style="padding:0.4rem;width:60px">
|
||||
<button class="btn btn-danger" onclick="rmDeauth()">Send Deauth</button>
|
||||
</div>
|
||||
<pre id="rm-deauth-output" style="max-height:300px;overflow:auto;background:var(--bg-secondary);padding:1rem;border-radius:4px;font-size:0.8rem"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== CHANNEL HOP TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-chanhop">
|
||||
<div class="section">
|
||||
<h2>Channel Hopper</h2>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem;flex-wrap:wrap">
|
||||
<label>Radio:</label>
|
||||
<select id="rm-hop-radio" style="padding:0.4rem"></select>
|
||||
<label>Dwell (ms):</label>
|
||||
<input type="number" id="rm-hop-dwell" value="500" min="100" max="5000" step="100" style="padding:0.4rem;width:80px">
|
||||
<button class="btn btn-primary" onclick="rmHopStart()">Start Hopping</button>
|
||||
<button class="btn btn-danger" onclick="rmHopStop()">Stop</button>
|
||||
</div>
|
||||
<div style="margin-bottom:0.5rem">
|
||||
<label>Channels (comma separated, leave blank for all):</label>
|
||||
<input type="text" id="rm-hop-channels" placeholder="1,6,11 or blank for all" style="width:100%;padding:0.4rem;margin-top:0.25rem">
|
||||
</div>
|
||||
<pre id="rm-hop-output" style="max-height:200px;overflow:auto;background:var(--bg-secondary);padding:0.5rem;border-radius:4px;font-size:0.8rem"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== AIRODUMP TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-airodump">
|
||||
<div class="section">
|
||||
<h2>Airodump-ng</h2>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem;flex-wrap:wrap">
|
||||
<label>Radio:</label>
|
||||
<select id="rm-airo-radio" style="padding:0.4rem"></select>
|
||||
<label>Channel:</label>
|
||||
<input type="text" id="rm-airo-channel" placeholder="all or specific" value="" style="padding:0.4rem;width:80px">
|
||||
<label>Band:</label>
|
||||
<select id="rm-airo-band" style="padding:0.4rem">
|
||||
<option value="">All</option>
|
||||
<option value="--band a">5GHz</option>
|
||||
<option value="--band bg">2.4GHz</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="rmAirodumpStart()">Start</button>
|
||||
<button class="btn btn-danger" onclick="rmAirodumpStop()">Stop</button>
|
||||
<button class="btn" onclick="rmAirodumpRefresh()">Refresh</button>
|
||||
</div>
|
||||
<div style="margin-bottom:0.5rem;font-size:0.85rem;color:var(--text-secondary)" id="rm-airo-status"></div>
|
||||
<h3>Access Points</h3>
|
||||
<div id="rm-airo-aps" style="overflow-x:auto;margin-bottom:1rem"></div>
|
||||
<h3>Clients</h3>
|
||||
<div id="rm-airo-clients" style="overflow-x:auto"></div>
|
||||
<pre id="rm-airo-raw" style="max-height:300px;overflow:auto;background:var(--bg-secondary);padding:0.5rem;border-radius:4px;font-size:0.8rem;display:none"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== HANDSHAKE TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-handshake">
|
||||
<div class="section">
|
||||
<h2>Handshake Capture</h2>
|
||||
<p style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Capture WPA/WPA2 handshakes for offline cracking. Optionally deauth clients to force reconnection.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem;flex-wrap:wrap">
|
||||
<label>Radio:</label>
|
||||
<select id="rm-hs-radio" style="padding:0.4rem"></select>
|
||||
<label>Target BSSID:</label>
|
||||
<input type="text" id="rm-hs-bssid" placeholder="AA:BB:CC:DD:EE:FF" style="padding:0.4rem;width:180px">
|
||||
<label>Channel:</label>
|
||||
<input type="text" id="rm-hs-channel" placeholder="6" style="padding:0.4rem;width:50px">
|
||||
</div>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem;flex-wrap:wrap">
|
||||
<button class="btn btn-primary" onclick="rmHsCapture()">Start Capture</button>
|
||||
<button class="btn btn-danger" onclick="rmHsDeauth()">Deauth + Capture</button>
|
||||
<button class="btn" onclick="rmHsStop()">Stop</button>
|
||||
<button class="btn" onclick="rmHsPull()">Pull Capture File</button>
|
||||
<label><input type="checkbox" id="rm-hs-auto-deauth" checked> Auto-deauth clients</label>
|
||||
</div>
|
||||
<div id="rm-hs-status" style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:0.5rem"></div>
|
||||
<pre id="rm-hs-output" style="max-height:300px;overflow:auto;background:var(--bg-secondary);padding:0.5rem;border-radius:4px;font-size:0.8rem"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== RECON TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-recon">
|
||||
<div class="section">
|
||||
<h2>Wireless Recon</h2>
|
||||
<p style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Passive reconnaissance — discover APs, clients, probe requests, and hidden networks.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem;flex-wrap:wrap">
|
||||
<label>Radio:</label>
|
||||
<select id="rm-recon-radio" style="padding:0.4rem"></select>
|
||||
<label>Duration (sec):</label>
|
||||
<input type="number" id="rm-recon-duration" value="30" min="5" max="300" style="padding:0.4rem;width:60px">
|
||||
<button class="btn btn-primary" onclick="rmReconStart()">Start Recon</button>
|
||||
<button class="btn btn-danger" onclick="rmReconStop()">Stop</button>
|
||||
</div>
|
||||
<div id="rm-recon-status" style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:0.5rem"></div>
|
||||
<h3>Discovered Networks</h3>
|
||||
<div id="rm-recon-networks" style="overflow-x:auto;margin-bottom:1rem"></div>
|
||||
<h3>Probe Requests</h3>
|
||||
<div id="rm-recon-probes" style="overflow-x:auto;margin-bottom:1rem"></div>
|
||||
<h3>Raw Output</h3>
|
||||
<pre id="rm-recon-raw" style="max-height:300px;overflow:auto;background:var(--bg-secondary);padding:0.5rem;border-radius:4px;font-size:0.8rem"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== INJECTION TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-injection">
|
||||
<div class="section">
|
||||
<h2>Frame Injection</h2>
|
||||
<p style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:1rem">
|
||||
Test frame injection capability and send custom frames.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;margin-bottom:1rem;flex-wrap:wrap">
|
||||
<label>Radio:</label>
|
||||
<select id="rm-inj-radio" style="padding:0.4rem"></select>
|
||||
<button class="btn btn-primary" onclick="rmInjTest()">Test Injection</button>
|
||||
</div>
|
||||
<div style="margin-bottom:1rem">
|
||||
<label>Custom aireplay-ng command:</label>
|
||||
<div style="display:flex;gap:0.5rem;margin-top:0.25rem">
|
||||
<input type="text" id="rm-inj-cmd" placeholder="--fakeauth 0 -a AA:BB:CC:DD:EE:FF" style="flex:1;padding:0.4rem">
|
||||
<button class="btn" onclick="rmInjCustom()">Run</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre id="rm-inj-output" style="max-height:300px;overflow:auto;background:var(--bg-secondary);padding:0.5rem;border-radius:4px;font-size:0.8rem"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== TERMINAL TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-terminal">
|
||||
<div class="section">
|
||||
<h2>Remote Terminal</h2>
|
||||
<div style="display:flex;gap:0.5rem;margin-bottom:1rem">
|
||||
<input type="text" id="rm-cmd-input" placeholder="Command..." style="flex:1;padding:0.4rem" onkeypress="if(event.key==='Enter')rmExec()">
|
||||
<button class="btn btn-primary" onclick="rmExec()">Run</button>
|
||||
</div>
|
||||
<pre id="rm-terminal-output" style="max-height:500px;overflow:auto;background:var(--bg-secondary);padding:1rem;border-radius:4px;font-size:0.8rem"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== INFO TAB ==================== -->
|
||||
<div class="rm-tab-panel hidden" id="rm-tab-info">
|
||||
<div class="section">
|
||||
<h2>Device Info</h2>
|
||||
<button class="btn" onclick="rmGetInfo()" style="margin-bottom:1rem">Refresh</button>
|
||||
<div id="rm-info-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Create New Dialog -->
|
||||
<div id="rm-new-dialog" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:999;display:none;align-items:center;justify-content:center">
|
||||
<div style="background:var(--bg-primary);padding:2rem;border-radius:8px;max-width:500px;width:90%">
|
||||
<h2 style="margin-top:0">Create New Device Profile</h2>
|
||||
<p>To add a new device:</p>
|
||||
<ol>
|
||||
<li>Navigate to <code>data/piap/</code></li>
|
||||
<li>Copy <code>template.piap</code> to a new file</li>
|
||||
<li>Edit the file with your device's information</li>
|
||||
<li>Fill in all fields marked <code>CHANGEME</code></li>
|
||||
<li>Save and refresh this page</li>
|
||||
</ol>
|
||||
<p style="font-size:0.85rem;color:var(--text-secondary)">
|
||||
Template location: <code>/data/piap/template.piap</code>
|
||||
</p>
|
||||
<button class="btn" onclick="document.getElementById('rm-new-dialog').style.display='none'">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let rmPiap = null;
|
||||
let rmConnected = false;
|
||||
|
||||
function rmTab(name) {
|
||||
document.querySelectorAll('.rm-tab-panel').forEach(p => p.classList.add('hidden'));
|
||||
document.querySelectorAll('#rm-tab-bar .tab').forEach(t => t.classList.remove('active'));
|
||||
const panel = document.getElementById('rm-tab-' + name);
|
||||
if (panel) panel.classList.remove('hidden');
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
function rmLoadPiap() {
|
||||
const sel = document.getElementById('piap-select');
|
||||
const filename = sel.value;
|
||||
|
||||
if (filename === '__new__') {
|
||||
document.getElementById('rm-new-dialog').style.display = 'flex';
|
||||
sel.value = '';
|
||||
return;
|
||||
}
|
||||
if (!filename) {
|
||||
document.getElementById('rm-device-panel').style.display = 'none';
|
||||
document.getElementById('rm-connect-btn').disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/remote-monitor/api/load', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({filename: filename})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.ok) { alert(data.error); return; }
|
||||
rmPiap = data.data;
|
||||
rmBuildUI();
|
||||
document.getElementById('rm-connect-btn').disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function rmBuildUI() {
|
||||
const d = rmPiap.device;
|
||||
document.getElementById('rm-model').textContent = d.model || '';
|
||||
document.getElementById('rm-os').textContent = d.os || '';
|
||||
document.getElementById('rm-chipset').textContent = d.wifi_chipset || d.chipset || '';
|
||||
document.getElementById('rm-host').textContent = rmPiap.connection.host || '';
|
||||
|
||||
// Build tab bar
|
||||
const tabBar = document.getElementById('rm-tab-bar');
|
||||
tabBar.innerHTML = '<button class="tab active" onclick="rmTab(\'radios\')">Radios</button>';
|
||||
const features = rmPiap.features;
|
||||
if (features.capture === 'true') tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'capture\')">Capture</button>';
|
||||
if (features.wifi_scan === 'true') tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'scan\')">WiFi Scan</button>';
|
||||
tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'recon\')">Recon</button>';
|
||||
if (features.aircrack === 'true') tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'airodump\')">Airodump</button>';
|
||||
tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'handshake\')">Handshake</button>';
|
||||
if (features.deauth === 'true') tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'deauth\')">Deauth</button>';
|
||||
if (features.channel_hop === 'true') tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'chanhop\')">Channel Hop</button>';
|
||||
tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'injection\')">Injection</button>';
|
||||
tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'terminal\')">Terminal</button>';
|
||||
tabBar.innerHTML += '<button class="tab" onclick="rmTab(\'info\')">Info</button>';
|
||||
|
||||
// Build radio panels
|
||||
const container = document.getElementById('rm-radios-container');
|
||||
container.innerHTML = '';
|
||||
rmPiap.radios.forEach((radio, idx) => {
|
||||
const channels = (radio.channel_list || []).map(c =>
|
||||
`<option value="${c}" ${c === radio.default_channel ? 'selected' : ''}>${c}</option>`
|
||||
).join('');
|
||||
const modes = (radio.mode_list || []).map(m =>
|
||||
`<span style="background:var(--bg-tertiary);padding:2px 8px;border-radius:3px;font-size:0.8rem">${m}</span>`
|
||||
).join(' ');
|
||||
|
||||
container.innerHTML += `
|
||||
<div class="section" style="margin-bottom:1rem">
|
||||
<h2>${radio.name || 'Radio ' + idx}</h2>
|
||||
<div style="display:flex;gap:1rem;flex-wrap:wrap;align-items:center;margin-bottom:0.5rem">
|
||||
<span><strong>PHY:</strong> ${radio.phy}</span>
|
||||
<span><strong>Band:</strong> ${radio.band || ''}</span>
|
||||
<span><strong>Modes:</strong> ${modes}</span>
|
||||
<span><strong>Injection:</strong> ${radio.injection === 'true' ? 'Yes' : 'No'}</span>
|
||||
<span><strong>Radiotap:</strong> ${radio.radiotap === 'true' ? 'Yes' : 'No'}</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;margin-bottom:0.5rem">
|
||||
<label>Channel:</label>
|
||||
<select id="rm-radio-${idx}-ch" style="padding:0.4rem">${channels}</select>
|
||||
<button class="btn btn-primary" onclick="rmMonitorOn(${idx})">Monitor ON</button>
|
||||
<button class="btn btn-danger" onclick="rmMonitorOff(${idx})">Monitor OFF</button>
|
||||
<button class="btn" onclick="rmSetChannel(${idx})">Set Channel</button>
|
||||
<button class="btn" onclick="rmRadioStatus(${idx})">Status</button>
|
||||
</div>
|
||||
<pre id="rm-radio-${idx}-output" style="max-height:200px;overflow:auto;background:var(--bg-secondary);padding:0.5rem;border-radius:4px;font-size:0.8rem"></pre>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
// Populate radio selects in feature tabs
|
||||
const radioOpts = rmPiap.radios.map((r, i) =>
|
||||
`<option value="${i}">${r.name || 'Radio ' + i}</option>`
|
||||
).join('');
|
||||
['rm-cap-radio', 'rm-scan-radio', 'rm-deauth-radio', 'rm-hop-radio', 'rm-airo-radio', 'rm-hs-radio', 'rm-recon-radio', 'rm-inj-radio'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.innerHTML = radioOpts;
|
||||
});
|
||||
|
||||
document.getElementById('rm-device-panel').style.display = 'block';
|
||||
}
|
||||
|
||||
function _rmPost(url, extra) {
|
||||
const body = {connection: rmPiap.connection, features: rmPiap.features, ...extra};
|
||||
return fetch('/remote-monitor/api/' + url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body)
|
||||
}).then(r => r.json());
|
||||
}
|
||||
|
||||
function rmConnect() {
|
||||
document.getElementById('rm-conn-status').textContent = 'Connecting...';
|
||||
_rmPost('connect', {}).then(data => {
|
||||
if (data.ok) {
|
||||
rmConnected = true;
|
||||
document.getElementById('rm-conn-status').innerHTML = '<span style="color:#4caf50">Connected</span>';
|
||||
document.getElementById('rm-connect-btn').style.display = 'none';
|
||||
document.getElementById('rm-disconnect-btn').style.display = '';
|
||||
document.getElementById('rm-disconnect-btn').disabled = false;
|
||||
} else {
|
||||
document.getElementById('rm-conn-status').innerHTML = '<span style="color:#f44336">Failed: ' + (data.stderr || 'unknown') + '</span>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function rmDisconnect() {
|
||||
rmConnected = false;
|
||||
document.getElementById('rm-conn-status').textContent = 'Disconnected';
|
||||
document.getElementById('rm-connect-btn').style.display = '';
|
||||
document.getElementById('rm-connect-btn').disabled = false;
|
||||
document.getElementById('rm-disconnect-btn').style.display = 'none';
|
||||
}
|
||||
|
||||
function rmMonitorOn(idx) {
|
||||
const channel = document.getElementById('rm-radio-' + idx + '-ch').value;
|
||||
const out = document.getElementById('rm-radio-' + idx + '-output');
|
||||
out.textContent = 'Enabling monitor mode...';
|
||||
_rmPost('radio/monitor-on', {radio: rmPiap.radios[idx], channel: channel}).then(data => {
|
||||
out.textContent = data.ok ? 'Monitor mode enabled on channel ' + channel : 'Error: ' + (data.stderr || data.stdout);
|
||||
});
|
||||
}
|
||||
|
||||
function rmMonitorOff(idx) {
|
||||
const out = document.getElementById('rm-radio-' + idx + '-output');
|
||||
out.textContent = 'Disabling monitor mode...';
|
||||
_rmPost('radio/monitor-off', {radio: rmPiap.radios[idx]}).then(data => {
|
||||
out.textContent = data.ok ? 'Monitor mode disabled' : 'Error: ' + (data.stderr || data.stdout);
|
||||
});
|
||||
}
|
||||
|
||||
function rmSetChannel(idx) {
|
||||
const channel = document.getElementById('rm-radio-' + idx + '-ch').value;
|
||||
const out = document.getElementById('rm-radio-' + idx + '-output');
|
||||
_rmPost('radio/set-channel', {radio: rmPiap.radios[idx], channel: channel}).then(data => {
|
||||
out.textContent = data.ok ? 'Channel set to ' + channel : 'Error: ' + (data.stderr || data.stdout);
|
||||
});
|
||||
}
|
||||
|
||||
function rmRadioStatus(idx) {
|
||||
const out = document.getElementById('rm-radio-' + idx + '-output');
|
||||
out.textContent = 'Getting status...';
|
||||
_rmPost('radio/status', {radio: rmPiap.radios[idx]}).then(data => {
|
||||
out.textContent = data.stdout || data.stderr || 'No output';
|
||||
});
|
||||
}
|
||||
|
||||
function rmCaptureStart() {
|
||||
const idx = document.getElementById('rm-cap-radio').value;
|
||||
document.getElementById('rm-capture-status').textContent = 'Starting capture...';
|
||||
_rmPost('capture/start', {radio: rmPiap.radios[idx]}).then(data => {
|
||||
document.getElementById('rm-capture-status').textContent = data.ok ? 'Capture running' : 'Error: ' + (data.stderr || '');
|
||||
});
|
||||
}
|
||||
|
||||
function rmCaptureStop() {
|
||||
document.getElementById('rm-capture-status').textContent = 'Stopping...';
|
||||
_rmPost('capture/stop', {}).then(data => {
|
||||
document.getElementById('rm-capture-status').textContent = 'Capture stopped';
|
||||
});
|
||||
}
|
||||
|
||||
function rmWifiScan() {
|
||||
const idx = document.getElementById('rm-scan-radio').value;
|
||||
const out = document.getElementById('rm-scan-output');
|
||||
document.getElementById('rm-scan-status').textContent = 'Scanning...';
|
||||
out.textContent = '';
|
||||
_rmPost('scan', {radio: rmPiap.radios[idx]}).then(data => {
|
||||
document.getElementById('rm-scan-status').textContent = data.ok ? 'Scan complete' : 'Error';
|
||||
out.textContent = data.stdout || data.stderr || 'No results';
|
||||
});
|
||||
}
|
||||
|
||||
function rmDeauth() {
|
||||
const idx = document.getElementById('rm-deauth-radio').value;
|
||||
const bssid = document.getElementById('rm-deauth-bssid').value;
|
||||
const count = document.getElementById('rm-deauth-count').value;
|
||||
if (!bssid) { alert('Enter a BSSID'); return; }
|
||||
_rmPost('deauth', {radio: rmPiap.radios[idx], bssid: bssid, count: count}).then(data => {
|
||||
document.getElementById('rm-deauth-output').textContent = data.stdout || data.stderr || 'Sent';
|
||||
});
|
||||
}
|
||||
|
||||
function rmExec() {
|
||||
const cmd = document.getElementById('rm-cmd-input').value;
|
||||
if (!cmd) return;
|
||||
const out = document.getElementById('rm-terminal-output');
|
||||
out.textContent += '$ ' + cmd + '\n';
|
||||
_rmPost('exec', {cmd: cmd}).then(data => {
|
||||
out.textContent += (data.stdout || data.stderr || '') + '\n';
|
||||
out.scrollTop = out.scrollHeight;
|
||||
});
|
||||
document.getElementById('rm-cmd-input').value = '';
|
||||
}
|
||||
|
||||
function rmGetInfo() {
|
||||
const out = document.getElementById('rm-info-output');
|
||||
out.innerHTML = '<em>Loading...</em>';
|
||||
_rmPost('info', {info: rmPiap.info}).then(data => {
|
||||
if (!data.ok) { out.textContent = 'Error'; return; }
|
||||
let html = '';
|
||||
for (const [key, val] of Object.entries(data.info)) {
|
||||
html += `<div class="section" style="margin-bottom:0.5rem"><h3>${key}</h3><pre style="background:var(--bg-secondary);padding:0.5rem;border-radius:4px;font-size:0.8rem">${val}</pre></div>`;
|
||||
}
|
||||
out.innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Channel Hopper ──────────────────────────────────────────────────────
|
||||
let rmHopRunning = false;
|
||||
function rmHopStart() {
|
||||
const idx = document.getElementById('rm-hop-radio').value;
|
||||
const radio = rmPiap.radios[idx];
|
||||
const dwell = document.getElementById('rm-hop-dwell').value;
|
||||
const customCh = document.getElementById('rm-hop-channels').value.trim();
|
||||
const channels = customCh || radio.channels;
|
||||
const out = document.getElementById('rm-hop-output');
|
||||
out.textContent = 'Starting channel hopper...';
|
||||
|
||||
const cmd = `sh -c 'while true; do for ch in ${channels.replace(/,/g, ' ')}; do iw dev ${radio.monitor_interface} set channel $ch 2>/dev/null && echo "Channel: $ch"; sleep ${dwell / 1000}; done; done' &`;
|
||||
_rmPost('exec', {cmd: cmd, radio: radio}).then(data => {
|
||||
rmHopRunning = true;
|
||||
out.textContent = 'Hopping across channels: ' + channels;
|
||||
});
|
||||
}
|
||||
|
||||
function rmHopStop() {
|
||||
_rmPost('exec', {cmd: "pkill -f 'while true; do for ch'"}).then(data => {
|
||||
rmHopRunning = false;
|
||||
document.getElementById('rm-hop-output').textContent = 'Stopped';
|
||||
});
|
||||
}
|
||||
|
||||
// ── Airodump ────────────────────────────────────────────────────────────
|
||||
function rmAirodumpStart() {
|
||||
const idx = document.getElementById('rm-airo-radio').value;
|
||||
const radio = rmPiap.radios[idx];
|
||||
const channel = document.getElementById('rm-airo-channel').value.trim();
|
||||
const band = document.getElementById('rm-airo-band').value;
|
||||
document.getElementById('rm-airo-status').textContent = 'Starting airodump-ng...';
|
||||
|
||||
let cmd = `airodump-ng ${radio.monitor_interface} --write /tmp/airo --output-format csv -w /tmp/airo --write-interval 2`;
|
||||
if (channel) cmd += ` -c ${channel}`;
|
||||
if (band) cmd += ` ${band}`;
|
||||
cmd += ' &';
|
||||
|
||||
_rmPost('exec', {cmd: cmd, radio: radio}).then(data => {
|
||||
document.getElementById('rm-airo-status').textContent = 'Airodump running. Click Refresh to see results.';
|
||||
});
|
||||
}
|
||||
|
||||
function rmAirodumpStop() {
|
||||
_rmPost('exec', {cmd: 'killall airodump-ng 2>/dev/null'}).then(data => {
|
||||
document.getElementById('rm-airo-status').textContent = 'Stopped';
|
||||
});
|
||||
}
|
||||
|
||||
function rmAirodumpRefresh() {
|
||||
_rmPost('exec', {cmd: 'cat /tmp/airo-01.csv 2>/dev/null || echo "No data yet"'}).then(data => {
|
||||
const raw = data.stdout || '';
|
||||
document.getElementById('rm-airo-raw').textContent = raw;
|
||||
document.getElementById('rm-airo-raw').style.display = 'block';
|
||||
rmParseAirodump(raw);
|
||||
});
|
||||
}
|
||||
|
||||
function rmParseAirodump(csv) {
|
||||
const lines = csv.split('\n');
|
||||
let aps = [], clients = [], section = 'ap';
|
||||
|
||||
lines.forEach(line => {
|
||||
if (line.includes('Station MAC')) { section = 'client'; return; }
|
||||
if (line.trim() === '' || line.startsWith('BSSID') || line.startsWith('\r')) return;
|
||||
const cols = line.split(',').map(c => c.trim());
|
||||
if (section === 'ap' && cols.length >= 14) {
|
||||
aps.push({bssid: cols[0], power: cols[8], channel: cols[3], enc: cols[5], essid: cols[13]});
|
||||
} else if (section === 'client' && cols.length >= 6) {
|
||||
clients.push({mac: cols[0], power: cols[3], bssid: cols[5], probes: cols[6] || ''});
|
||||
}
|
||||
});
|
||||
|
||||
let apHtml = '<table style="width:100%;font-size:0.8rem"><tr><th>BSSID</th><th>Ch</th><th>Pwr</th><th>Enc</th><th>ESSID</th></tr>';
|
||||
aps.forEach(a => { apHtml += `<tr><td>${a.bssid}</td><td>${a.channel}</td><td>${a.power}</td><td>${a.enc}</td><td>${a.essid}</td></tr>`; });
|
||||
apHtml += '</table>';
|
||||
document.getElementById('rm-airo-aps').innerHTML = aps.length ? apHtml : '<em>No APs found</em>';
|
||||
|
||||
let clHtml = '<table style="width:100%;font-size:0.8rem"><tr><th>MAC</th><th>Pwr</th><th>BSSID</th><th>Probes</th></tr>';
|
||||
clients.forEach(c => { clHtml += `<tr><td>${c.mac}</td><td>${c.power}</td><td>${c.bssid}</td><td>${c.probes}</td></tr>`; });
|
||||
clHtml += '</table>';
|
||||
document.getElementById('rm-airo-clients').innerHTML = clients.length ? clHtml : '<em>No clients found</em>';
|
||||
}
|
||||
|
||||
// ── Handshake Capture ───────────────────────────────────────────────────
|
||||
function rmHsCapture() {
|
||||
const idx = document.getElementById('rm-hs-radio').value;
|
||||
const radio = rmPiap.radios[idx];
|
||||
const bssid = document.getElementById('rm-hs-bssid').value;
|
||||
const channel = document.getElementById('rm-hs-channel').value;
|
||||
if (!bssid) { alert('Enter target BSSID'); return; }
|
||||
|
||||
const out = document.getElementById('rm-hs-output');
|
||||
document.getElementById('rm-hs-status').textContent = 'Starting handshake capture...';
|
||||
|
||||
let cmd = `iw dev ${radio.monitor_interface} set channel ${channel} 2>/dev/null; airodump-ng ${radio.monitor_interface} --bssid ${bssid} -c ${channel} --write /tmp/handshake --output-format pcap -w /tmp/handshake &`;
|
||||
_rmPost('exec', {cmd: cmd, radio: radio}).then(data => {
|
||||
document.getElementById('rm-hs-status').textContent = 'Capturing on channel ' + channel + ' for ' + bssid;
|
||||
out.textContent = 'Waiting for handshake...\n';
|
||||
|
||||
if (document.getElementById('rm-hs-auto-deauth').checked) {
|
||||
setTimeout(() => rmHsDeauth(), 3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function rmHsDeauth() {
|
||||
const idx = document.getElementById('rm-hs-radio').value;
|
||||
const radio = rmPiap.radios[idx];
|
||||
const bssid = document.getElementById('rm-hs-bssid').value;
|
||||
if (!bssid) { alert('Enter target BSSID'); return; }
|
||||
|
||||
document.getElementById('rm-hs-status').textContent = 'Sending deauth to force reconnection...';
|
||||
const cmd = `aireplay-ng --deauth 5 -a ${bssid} ${radio.monitor_interface}`;
|
||||
_rmPost('exec', {cmd: cmd, radio: radio}).then(data => {
|
||||
document.getElementById('rm-hs-output').textContent += (data.stdout || data.stderr || '') + '\n';
|
||||
});
|
||||
}
|
||||
|
||||
function rmHsStop() {
|
||||
_rmPost('exec', {cmd: 'killall airodump-ng 2>/dev/null; killall aireplay-ng 2>/dev/null'}).then(data => {
|
||||
document.getElementById('rm-hs-status').textContent = 'Stopped';
|
||||
});
|
||||
}
|
||||
|
||||
function rmHsPull() {
|
||||
document.getElementById('rm-hs-status').textContent = 'Checking for capture files...';
|
||||
_rmPost('exec', {cmd: 'ls -la /tmp/handshake* 2>/dev/null'}).then(data => {
|
||||
document.getElementById('rm-hs-output').textContent = data.stdout || 'No capture files found';
|
||||
});
|
||||
}
|
||||
|
||||
// ── Wireless Recon ──────────────────────────────────────────────────────
|
||||
function rmReconStart() {
|
||||
const idx = document.getElementById('rm-recon-radio').value;
|
||||
const radio = rmPiap.radios[idx];
|
||||
const duration = document.getElementById('rm-recon-duration').value;
|
||||
document.getElementById('rm-recon-status').textContent = 'Running passive recon for ' + duration + 's...';
|
||||
|
||||
const cmd = `timeout ${duration} tcpdump -i ${radio.monitor_interface} -e -c 500 2>&1`;
|
||||
_rmPost('exec', {cmd: cmd, radio: radio}).then(data => {
|
||||
const raw = data.stdout || '';
|
||||
document.getElementById('rm-recon-raw').textContent = raw;
|
||||
document.getElementById('rm-recon-status').textContent = 'Recon complete';
|
||||
rmParseRecon(raw);
|
||||
});
|
||||
}
|
||||
|
||||
function rmReconStop() {
|
||||
_rmPost('exec', {cmd: 'killall tcpdump 2>/dev/null'}).then(data => {
|
||||
document.getElementById('rm-recon-status').textContent = 'Stopped';
|
||||
});
|
||||
}
|
||||
|
||||
function rmParseRecon(raw) {
|
||||
const lines = raw.split('\n');
|
||||
const networks = {};
|
||||
const probes = {};
|
||||
|
||||
lines.forEach(line => {
|
||||
// Extract beacons
|
||||
const beaconMatch = line.match(/Beacon \(([^)]*)\)/);
|
||||
const bssidMatch = line.match(/BSSID:([0-9a-f:]+)/i);
|
||||
const sigMatch = line.match(/(-?\d+)dBm/);
|
||||
if (beaconMatch && bssidMatch) {
|
||||
const ssid = beaconMatch[1] || '(hidden)';
|
||||
const bssid = bssidMatch[1];
|
||||
networks[bssid] = {ssid: ssid, bssid: bssid, signal: sigMatch ? sigMatch[1] : '', count: (networks[bssid]?.count || 0) + 1};
|
||||
}
|
||||
|
||||
// Extract probe requests
|
||||
const probeMatch = line.match(/Probe Request \(([^)]*)\)/);
|
||||
const saMatch = line.match(/SA:([0-9a-f:]+)/i);
|
||||
if (probeMatch && saMatch) {
|
||||
const ssid = probeMatch[1] || '(broadcast)';
|
||||
const sa = saMatch[1];
|
||||
const key = sa + ':' + ssid;
|
||||
probes[key] = {mac: sa, ssid: ssid, count: (probes[key]?.count || 0) + 1};
|
||||
}
|
||||
});
|
||||
|
||||
let netHtml = '<table style="width:100%;font-size:0.8rem"><tr><th>SSID</th><th>BSSID</th><th>Signal</th><th>Beacons</th></tr>';
|
||||
Object.values(networks).sort((a, b) => b.count - a.count).forEach(n => {
|
||||
netHtml += `<tr><td>${n.ssid}</td><td>${n.bssid}</td><td>${n.signal}dBm</td><td>${n.count}</td></tr>`;
|
||||
});
|
||||
netHtml += '</table>';
|
||||
document.getElementById('rm-recon-networks').innerHTML = Object.keys(networks).length ? netHtml : '<em>No networks found</em>';
|
||||
|
||||
let probeHtml = '<table style="width:100%;font-size:0.8rem"><tr><th>Client MAC</th><th>Probing For</th><th>Count</th></tr>';
|
||||
Object.values(probes).sort((a, b) => b.count - a.count).forEach(p => {
|
||||
probeHtml += `<tr><td>${p.mac}</td><td>${p.ssid}</td><td>${p.count}</td></tr>`;
|
||||
});
|
||||
probeHtml += '</table>';
|
||||
document.getElementById('rm-recon-probes').innerHTML = Object.keys(probes).length ? probeHtml : '<em>No probe requests found</em>';
|
||||
}
|
||||
|
||||
// ── Frame Injection ─────────────────────────────────────────────────────
|
||||
function rmInjTest() {
|
||||
const idx = document.getElementById('rm-inj-radio').value;
|
||||
const radio = rmPiap.radios[idx];
|
||||
const out = document.getElementById('rm-inj-output');
|
||||
out.textContent = 'Testing injection capability...';
|
||||
const cmd = `aireplay-ng --test ${radio.monitor_interface} 2>&1`;
|
||||
_rmPost('exec', {cmd: cmd, radio: radio}).then(data => {
|
||||
out.textContent = data.stdout || data.stderr || 'No output';
|
||||
});
|
||||
}
|
||||
|
||||
function rmInjCustom() {
|
||||
const idx = document.getElementById('rm-inj-radio').value;
|
||||
const radio = rmPiap.radios[idx];
|
||||
const args = document.getElementById('rm-inj-cmd').value;
|
||||
const out = document.getElementById('rm-inj-output');
|
||||
const cmd = `aireplay-ng ${args} ${radio.monitor_interface} 2>&1`;
|
||||
_rmPost('exec', {cmd: cmd, radio: radio}).then(data => {
|
||||
out.textContent += (data.stdout || data.stderr || '') + '\n';
|
||||
out.scrollTop = out.scrollHeight;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.rm-tab-panel { margin-top: 1rem; }
|
||||
.rm-tab-panel table { border-collapse: collapse; }
|
||||
.rm-tab-panel th, .rm-tab-panel td { padding: 4px 8px; border-bottom: 1px solid var(--border-color, #333); text-align: left; }
|
||||
.rm-tab-panel th { color: var(--text-secondary); font-weight: 600; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -123,6 +123,7 @@ function loadReports(){
|
||||
<button class="btn btn-sm" style="color:var(--danger)" onclick="event.stopPropagation();deleteReport('${r.id}')">Delete</button>
|
||||
</div>
|
||||
</div></div>`).join('');
|
||||
halAnalyze('Report Engine', JSON.stringify(d, null, 2), 'pentest report', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -154,6 +155,7 @@ function openReport(id){
|
||||
document.getElementById('ed-status').value=r.status||'draft';
|
||||
renderFindings(r.findings||[]);
|
||||
switchTab('editor');
|
||||
halAnalyze('Report Engine', JSON.stringify(d, null, 2), 'pentest report', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -236,6 +238,7 @@ function loadTemplates(){
|
||||
<p style="font-size:0.8rem;margin:0.3rem 0;color:var(--text-secondary)">${esc(t.description)}</p>
|
||||
<div style="font-size:0.75rem;color:var(--text-muted)">${(t.references||[]).join(', ')}</div>
|
||||
</div>`).join('');
|
||||
halAnalyze('Report Engine', JSON.stringify(d, null, 2), 'pentest report', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -435,6 +435,7 @@ function reAnalyze() {
|
||||
_reStrings = r.strings_preview || [];
|
||||
document.getElementById('a-strings-count').textContent = '(' + (r.strings_count||0).toLocaleString() + ' total, showing first ' + _reStrings.length + ')';
|
||||
reFilterStrings();
|
||||
halAnalyze('Reverse Engineering', JSON.stringify(r, null, 2), 'binary analysis', 'analyze');
|
||||
}).catch(function(e) {
|
||||
setLoading(btn, false);
|
||||
alert('Analysis failed: ' + e);
|
||||
@@ -492,6 +493,7 @@ function reCompare() {
|
||||
lines.push('MD5 file1: ' + r.file1.hashes.md5);
|
||||
lines.push('MD5 file2: ' + r.file2.hashes.md5);
|
||||
out.textContent = lines.join('\n');
|
||||
halAnalyze('Reverse Engineering', JSON.stringify(r, null, 2), 'binary analysis', 'analyze');
|
||||
}).catch(function(e) {
|
||||
setLoading(btn, false);
|
||||
out.style.display = '';
|
||||
@@ -548,6 +550,7 @@ function reDisassemble() {
|
||||
row += '</tr>';
|
||||
tbody.innerHTML += row;
|
||||
});
|
||||
halAnalyze('Reverse Engineering', JSON.stringify(r, null, 2), 'binary analysis', 'analyze');
|
||||
}).catch(function(e) {
|
||||
setLoading(btn, false);
|
||||
alert('Disassembly failed: ' + e);
|
||||
@@ -642,6 +645,7 @@ function reYaraScan() {
|
||||
html += '</div>';
|
||||
});
|
||||
container.innerHTML = html;
|
||||
halAnalyze('Reverse Engineering', JSON.stringify(r, null, 2), 'binary analysis', 'analyze');
|
||||
}).catch(function(e) {
|
||||
setLoading(btn, false);
|
||||
alert('YARA scan failed: ' + e);
|
||||
@@ -680,6 +684,7 @@ function reHexDump() {
|
||||
});
|
||||
pre.innerHTML = html;
|
||||
document.getElementById('hex-info').textContent = 'Offset: 0x' + _hexCurrentOffset.toString(16) + ' | Showing: ' + r.length + ' bytes | File: ' + r.file_size.toLocaleString() + ' bytes total';
|
||||
halAnalyze('Reverse Engineering', JSON.stringify(r, null, 2), 'binary analysis', 'analyze');
|
||||
}).catch(function(e) {
|
||||
setLoading(btn, false);
|
||||
alert('Hex dump failed: ' + e);
|
||||
@@ -720,6 +725,7 @@ function reHexSearch() {
|
||||
html += '</div>';
|
||||
});
|
||||
list.innerHTML = html;
|
||||
halAnalyze('Reverse Engineering', JSON.stringify(r, null, 2), 'binary analysis', 'analyze');
|
||||
}).catch(function(e) {
|
||||
setLoading(btn, false);
|
||||
alert('Hex search failed: ' + e);
|
||||
|
||||
@@ -162,6 +162,7 @@ function rfidLFSearch() {
|
||||
if (data.error) { renderOutput('rfid-scan-output', 'Error: ' + data.error); return; }
|
||||
renderOutput('rfid-scan-output', data.output || 'No card detected.');
|
||||
if (data.card) rfidUpdateLastCard(data.card);
|
||||
halAnalyze('RFID/NFC', JSON.stringify(data, null, 2), 'rfid', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -173,6 +174,7 @@ function rfidHFSearch() {
|
||||
if (data.error) { renderOutput('rfid-scan-output', 'Error: ' + data.error); return; }
|
||||
renderOutput('rfid-scan-output', data.output || 'No card detected.');
|
||||
if (data.card) rfidUpdateLastCard(data.card);
|
||||
halAnalyze('RFID/NFC', JSON.stringify(data, null, 2), 'rfid', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -184,6 +186,7 @@ function rfidNFCScan() {
|
||||
if (data.error) { renderOutput('rfid-scan-output', 'Error: ' + data.error); return; }
|
||||
renderOutput('rfid-scan-output', data.output || 'No NFC tag detected.');
|
||||
if (data.card) rfidUpdateLastCard(data.card);
|
||||
halAnalyze('RFID/NFC', JSON.stringify(data, null, 2), 'rfid', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -203,6 +206,7 @@ function rfidEMClone() {
|
||||
postJSON('/rfid/clone/em410x', {card_id: id}).then(function(data) {
|
||||
setLoading(btn, false);
|
||||
renderOutput('rfid-em-output', data.message || data.error || 'Done');
|
||||
halAnalyze('RFID/NFC', JSON.stringify(data, null, 2), 'rfid', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -212,6 +216,7 @@ function rfidMFDump() {
|
||||
postJSON('/rfid/dump/mifare', {}).then(function(data) {
|
||||
setLoading(btn, false);
|
||||
renderOutput('rfid-mf-dump-output', data.output || data.error || 'No output');
|
||||
halAnalyze('RFID/NFC', JSON.stringify(data, null, 2), 'rfid', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -223,6 +228,7 @@ function rfidMFClone() {
|
||||
postJSON('/rfid/clone/mifare', {dump_path: path}).then(function(data) {
|
||||
setLoading(btn, false);
|
||||
renderOutput('rfid-mf-clone-output', data.message || data.error || 'Done');
|
||||
halAnalyze('RFID/NFC', JSON.stringify(data, null, 2), 'rfid', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
|
||||
@@ -452,6 +452,7 @@ function sdrDetectDevices() {
|
||||
});
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -492,6 +493,7 @@ function sdrScanSpectrum() {
|
||||
document.getElementById('sdr-scan-info').textContent = startMhz + ' - ' + endMhz + ' MHz | ' + sdrSpectrumData.length + ' pts | ' + dev;
|
||||
sdrDrawSpectrum(sdrSpectrumData, startMhz, endMhz);
|
||||
document.getElementById('sdr-spectrum-section').style.display = '';
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
}).catch(function() {
|
||||
setLoading(btn, false);
|
||||
document.getElementById('sdr-scan-status').textContent = 'Scan request failed.';
|
||||
@@ -667,6 +669,7 @@ function sdrStartCapture() {
|
||||
setTimeout(function() {
|
||||
sdrCaptureFinished();
|
||||
}, (body.duration + 2) * 1000);
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -739,6 +742,7 @@ function sdrLoadRecordings() {
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
container.innerHTML = html;
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -785,6 +789,7 @@ function sdrReplay() {
|
||||
} else {
|
||||
document.getElementById('sdr-replay-output').textContent = data.message || 'Replay complete.';
|
||||
}
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
}).catch(function() {
|
||||
setLoading(btn, false);
|
||||
document.getElementById('sdr-replay-output').textContent = 'Replay request failed.';
|
||||
@@ -812,6 +817,7 @@ function sdrDemodulate() {
|
||||
+ '\nSamples: ' + (data.samples || 0)
|
||||
+ '\nMode: ' + (data.mode || mode.toUpperCase());
|
||||
}
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
}).catch(function() {
|
||||
setLoading(btn, false);
|
||||
document.getElementById('sdr-demod-output').textContent = 'Demod request failed.';
|
||||
@@ -849,6 +855,7 @@ function sdrAnalyze() {
|
||||
+ '<tr><td>Device</td><td>' + escapeHtml(data.device || 'Unknown') + '</td></tr>'
|
||||
+ '</tbody></table>';
|
||||
container.innerHTML = html;
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
}).catch(function() {
|
||||
setLoading(btn, false);
|
||||
document.getElementById('sdr-analysis-results').innerHTML = '<div class="empty-state">Analysis request failed.</div>';
|
||||
@@ -873,6 +880,7 @@ function sdrAdsbStart() {
|
||||
// Start polling
|
||||
sdrAdsbInterval = setInterval(sdrAdsbRefresh, 3000);
|
||||
sdrAdsbRefresh();
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -918,6 +926,7 @@ function sdrAdsbRefresh() {
|
||||
+ '</tr>';
|
||||
});
|
||||
tbody.innerHTML = html;
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -970,6 +979,7 @@ function sdrGpsDetect() {
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
}).catch(function() {
|
||||
setLoading(btn, false);
|
||||
document.getElementById('sdr-gps-results').innerHTML = '<div class="empty-state">GPS detection request failed.</div>';
|
||||
@@ -1010,6 +1020,7 @@ function sdrDroneStart() {
|
||||
sdrDroneCheckStatus();
|
||||
}, (dur + 5) * 1000);
|
||||
}
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -1103,6 +1114,7 @@ function sdrDroneRefresh() {
|
||||
table.classList.add('drone-alert-flash');
|
||||
setTimeout(function() { table.classList.remove('drone-alert-flash'); }, 1500);
|
||||
}
|
||||
halAnalyze('SDR/RF Tools', JSON.stringify(data, null, 2), 'rf analysis', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
1045
web/templates/ssh_manager.html
Normal file
1045
web/templates/ssh_manager.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -250,6 +250,7 @@ function stegoHide() {
|
||||
if (data.bytes_hidden) lines.push('Bytes embedded: ' + data.bytes_hidden);
|
||||
if (data.capacity_used) lines.push('Capacity used: ' + data.capacity_used + '%');
|
||||
renderOutput('hide-output-result', lines.join('\n'));
|
||||
halAnalyze('Steganography', JSON.stringify(data, null, 2), 'stego analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -268,6 +269,7 @@ function stegoCapacity() {
|
||||
lines.push('Max characters: ~' + (data.max_chars || '--'));
|
||||
if (data.dimensions) lines.push('Dimensions: ' + data.dimensions);
|
||||
renderOutput('hide-output-result', lines.join('\n'));
|
||||
halAnalyze('Steganography', JSON.stringify(data, null, 2), 'stego analysis', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -279,6 +281,7 @@ function wsEncode() {
|
||||
postJSON('/stego/whitespace/encode', {cover: cover, hidden: hidden}).then(function(data) {
|
||||
if (data.error) { alert('Error: ' + data.error); return; }
|
||||
document.getElementById('ws-result').value = data.encoded || '';
|
||||
halAnalyze('Steganography', JSON.stringify(data, null, 2), 'stego analysis', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -289,6 +292,7 @@ function wsDecode() {
|
||||
if (data.error) { alert('Error: ' + data.error); return; }
|
||||
document.getElementById('ws-hidden').value = data.decoded || '';
|
||||
document.getElementById('ws-result').value = 'Decoded: ' + (data.decoded || '(empty)');
|
||||
halAnalyze('Steganography', JSON.stringify(data, null, 2), 'stego analysis', 'analyze');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -323,6 +327,7 @@ function stegoExtract() {
|
||||
} else {
|
||||
renderOutput('extract-result', data.text || data.message || 'No hidden data found.');
|
||||
}
|
||||
halAnalyze('Steganography', JSON.stringify(data, null, 2), 'stego analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -396,6 +401,7 @@ function stegoDetect() {
|
||||
} else {
|
||||
iContainer.innerHTML = '<div class="empty-state">No specific indicators found.</div>';
|
||||
}
|
||||
halAnalyze('Steganography', JSON.stringify(data, null, 2), 'stego analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -425,6 +431,7 @@ function stegoBatchScan() {
|
||||
lines.push(flag + ' ' + r.filename + ' (confidence: ' + (r.confidence || 0) + '%)');
|
||||
});
|
||||
renderOutput('batch-output', lines.join('\n'));
|
||||
halAnalyze('Steganography', JSON.stringify(data, null, 2), 'stego analysis', 'analyze');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -42,9 +42,261 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System (non-Python) Dependencies -->
|
||||
<div class="section">
|
||||
<h2>System Dependencies</h2>
|
||||
<p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
|
||||
These tools must be installed separately — AUTARCH cannot install them for you.
|
||||
</p>
|
||||
<div id="sys-deps-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:0.75rem;font-size:0.82rem">
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>Nmap</strong>
|
||||
<span id="dep-nmap" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Network scanner used by OSINT, port scanning, and IDS modules.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install nmap</code><br>
|
||||
<strong>Windows:</strong> <a href="https://nmap.org/download.html" target="_blank" rel="noopener">nmap.org/download</a><br>
|
||||
<strong>macOS:</strong> <code>brew install nmap</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>Wireshark / tshark</strong>
|
||||
<span id="dep-tshark" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Packet capture and protocol analysis.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install tshark wireshark</code><br>
|
||||
<strong>Windows:</strong> <a href="https://www.wireshark.org/download.html" target="_blank" rel="noopener">wireshark.org/download</a> (includes Npcap)<br>
|
||||
<strong>macOS:</strong> <code>brew install wireshark</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>tcpdump</strong>
|
||||
<span id="dep-tcpdump" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Low-level packet capture used by MCP tools and network module.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install tcpdump</code><br>
|
||||
<strong>Windows:</strong> Included with Npcap/Wireshark<br>
|
||||
<strong>macOS:</strong> Pre-installed
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>Metasploit Framework</strong>
|
||||
<span id="dep-msfconsole" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Penetration testing framework for offense modules.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <a href="https://docs.metasploit.com/docs/using-metasploit/getting-started/nightly-installers.html" target="_blank" rel="noopener">Metasploit nightly installer</a><br>
|
||||
<strong>Windows:</strong> <a href="https://www.metasploit.com/download" target="_blank" rel="noopener">metasploit.com/download</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>WireGuard</strong>
|
||||
<span id="dep-wg" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">VPN tunnel management.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install wireguard wireguard-tools</code><br>
|
||||
<strong>Windows:</strong> <a href="https://www.wireguard.com/install/" target="_blank" rel="noopener">wireguard.com/install</a><br>
|
||||
<strong>macOS:</strong> <code>brew install wireguard-tools</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>Node.js / npm</strong>
|
||||
<span id="dep-node" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Required for building WebUSB hardware bundles (ADB, Fastboot, ESP32).</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>All platforms:</strong> <a href="https://nodejs.org/en/download" target="_blank" rel="noopener">nodejs.org/download</a><br>
|
||||
<strong>Linux:</strong> <code>sudo apt install nodejs npm</code><br>
|
||||
<strong>macOS:</strong> <code>brew install node</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>Go</strong>
|
||||
<span id="dep-go" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Required for the DNS server and Setec Manager services.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>All platforms:</strong> <a href="https://go.dev/dl/" target="_blank" rel="noopener">go.dev/dl</a><br>
|
||||
<strong>Linux:</strong> <code>sudo apt install golang-go</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>ADB / Fastboot</strong>
|
||||
<span id="dep-adb" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Android device management. Bundled in android/ for Linux ARM64.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>All platforms:</strong> <a href="https://developer.android.com/tools/releases/platform-tools" target="_blank" rel="noopener">Android Platform Tools</a><br>
|
||||
<strong>Linux:</strong> <code>sudo apt install android-tools-adb</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>miniupnpc</strong>
|
||||
<span id="dep-upnpc" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">UPnP port forwarding client.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install miniupnpc</code><br>
|
||||
<strong>macOS:</strong> <code>brew install miniupnpc</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>whois</strong>
|
||||
<span id="dep-whois" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Domain/IP registration lookups for OSINT and MCP.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install whois</code><br>
|
||||
<strong>macOS:</strong> Pre-installed
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>aircrack-ng</strong>
|
||||
<span id="dep-aircrack" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">WiFi auditing, deauth attacks, handshake capture.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install aircrack-ng</code><br>
|
||||
<strong>Website:</strong> <a href="https://www.aircrack-ng.org/" target="_blank" rel="noopener">aircrack-ng.org</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>mdk3 / mdk4</strong>
|
||||
<span id="dep-mdk4" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Advanced WiFi deauthentication and beacon flooding. Used by the Deauth Attack module.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install mdk4</code><br>
|
||||
<strong>Note:</strong> mdk3 is legacy; mdk4 is the maintained fork.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>sslstrip</strong>
|
||||
<span id="dep-sslstrip" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">HTTPS downgrade tool for MITM testing. Used by the Pineapple module.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>pip install sslstrip</code> or <code>sudo apt install sslstrip</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>Monitor Mode WiFi Adapter</strong>
|
||||
<span id="dep-monitor" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">—</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Required for WiFi Audit, Deauth, and Pineapple. The built-in Pi WiFi does NOT support monitor mode. You need a USB adapter.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Recommended:</strong> Alfa AWUS036ACH (dual-band, widely supported)<br>
|
||||
<strong>Budget:</strong> Alfa AWUS036NHA (2.4GHz only, ~$20)<br>
|
||||
<strong>Check yours:</strong> <code>iw phy | grep -A5 "Supported interface modes"</code> — look for "monitor"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>PyTorch (GPU)</strong>
|
||||
<span id="dep-torch" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Required for local Transformers models and LoRA training. Install the correct version for your GPU.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>All platforms:</strong> <a href="https://pytorch.org/get-started/locally/" target="_blank" rel="noopener">pytorch.org/get-started</a><br>
|
||||
<strong>CPU only:</strong> <code>pip install torch --index-url https://download.pytorch.org/whl/cpu</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>iw / wireless-tools</strong>
|
||||
<span id="dep-iw" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">WiFi interface management for scanning, monitor mode, and channel control.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install iw wireless-tools</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>NetworkManager (nmcli)</strong>
|
||||
<span id="dep-nmcli" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">WiFi scanning and connection management. Used by Network Security WiFi Scanner.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install network-manager</code> (usually pre-installed)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>hostapd</strong>
|
||||
<span id="dep-hostapd" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Access point daemon for Pineapple/Evil Twin and rogue AP pentesting.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install hostapd</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>dnsmasq</strong>
|
||||
<span id="dep-dnsmasq" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">DHCP/DNS server for captive portals and rogue AP pentesting.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install dnsmasq</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;background:var(--bg-card)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
||||
<strong>nftables / iptables</strong>
|
||||
<span id="dep-nft" style="font-size:0.72rem;padding:2px 6px;border-radius:3px;background:var(--bg-main)">checking…</span>
|
||||
</div>
|
||||
<div style="color:var(--text-muted);font-size:0.75rem;margin-bottom:0.4rem">Firewall and packet filtering. Used by Network Security IP blocking and NAT.</div>
|
||||
<div style="font-size:0.72rem">
|
||||
<strong>Linux:</strong> <code>sudo apt install nftables</code> (usually pre-installed)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button class="btn btn-sm" onclick="checkSystemDeps()" style="margin-top:0.75rem" id="btn-sys-check">Check System Tools</button>
|
||||
</div>
|
||||
|
||||
<!-- Quick Install -->
|
||||
<div class="section">
|
||||
<h2>Install Packages</h2>
|
||||
<h2>Python Packages</h2>
|
||||
<p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
|
||||
Install individual packages or preset groups.
|
||||
</p>
|
||||
@@ -165,6 +417,37 @@ function depsInstallOne() {
|
||||
document.getElementById('install-pkg').value = '';
|
||||
}
|
||||
|
||||
function checkSystemDeps() {
|
||||
var btn = document.getElementById('btn-sys-check');
|
||||
if (btn) { btn.disabled = true; btn.textContent = 'Checking…'; }
|
||||
fetch('/settings/deps/system-check', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Check System Tools'; }
|
||||
if (!d.ok) return;
|
||||
var tools = d.tools || {};
|
||||
for (var name in tools) {
|
||||
var el = document.getElementById('dep-' + name);
|
||||
if (!el) continue;
|
||||
var info = tools[name];
|
||||
if (info.found) {
|
||||
el.textContent = info.version || 'installed';
|
||||
el.style.color = 'var(--success, #34c759)';
|
||||
el.style.borderColor = 'var(--success, #34c759)';
|
||||
el.style.border = '1px solid var(--success, #34c759)';
|
||||
} else {
|
||||
el.textContent = 'not found';
|
||||
el.style.color = 'var(--danger, #ff3b30)';
|
||||
el.style.border = '1px solid var(--danger, #ff3b30)';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function() { if (btn) { btn.disabled = false; btn.textContent = 'Check System Tools'; } });
|
||||
}
|
||||
|
||||
// Auto-check system deps on page load
|
||||
document.addEventListener('DOMContentLoaded', function() { checkSystemDeps(); });
|
||||
|
||||
function depsInstallGroup(group) {
|
||||
var pkgs = _depsGroupPkgs[group];
|
||||
if (!pkgs || !pkgs.length) return;
|
||||
|
||||
@@ -1,89 +1,103 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Targets - AUTARCH{% endblock %}
|
||||
{% block title %}Investigations - AUTARCH{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
|
||||
<div>
|
||||
<h1>Targets</h1>
|
||||
<h1>Investigations</h1>
|
||||
<p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
|
||||
Manage scope — IPs, CIDRs, domains, and URLs for your engagement.
|
||||
Investigation profiles for IPs, domains, threat actors, and incidents. Build detailed profiles with GeoIP, traceroute, DNS, WHOIS, and custom fields.
|
||||
</p>
|
||||
</div>
|
||||
<div style="margin-left:auto;display:flex;gap:0.5rem;flex-wrap:wrap">
|
||||
<button class="btn btn-sm btn-primary" onclick="toggleAddForm()">+ Add Target</button>
|
||||
<button class="btn btn-sm" onclick="exportTargets()">Export JSON</button>
|
||||
<label class="btn btn-sm" style="cursor:pointer" title="Import targets from JSON file">
|
||||
Import JSON
|
||||
<input type="file" accept=".json" style="display:none" onchange="importTargets(this)">
|
||||
</label>
|
||||
<button class="btn btn-sm btn-primary" onclick="toggleAddForm()">+ New Investigation</button>
|
||||
<button class="btn btn-sm" onclick="exportTargets()">Export</button>
|
||||
<label class="btn btn-sm" style="cursor:pointer">Import<input type="file" accept=".json" style="display:none" onchange="importTargets(this)"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Target Form -->
|
||||
<!-- Add Investigation Form -->
|
||||
<div id="add-form" class="section" style="display:none">
|
||||
<h2>Add Target</h2>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem 1rem">
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label for="add-host">Host / IP / CIDR <span style="color:var(--danger)">*</span></label>
|
||||
<input type="text" id="add-host" placeholder="192.168.1.1 or example.com">
|
||||
<h2>New Investigation Profile</h2>
|
||||
|
||||
<!-- Basic Info -->
|
||||
<fieldset style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;margin-bottom:0.75rem">
|
||||
<legend style="font-size:0.85rem;font-weight:600;color:var(--accent);padding:0 0.5rem">Basic Info</legend>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem">
|
||||
<div class="form-group" style="margin:0"><label>Host / IP <span style="color:var(--danger)">*</span></label><input type="text" id="add-host" placeholder="192.168.1.1 or example.com"></div>
|
||||
<div class="form-group" style="margin:0"><label>Name / Label</label><input type="text" id="add-name" placeholder="Investigation name"></div>
|
||||
<div class="form-group" style="margin:0"><label>Type</label><select id="add-type"><option value="ip">IP</option><option value="cidr">CIDR</option><option value="domain">Domain</option><option value="url">URL</option><option value="email">Email</option><option value="actor">Threat Actor</option><option value="incident">Incident</option></select></div>
|
||||
<div class="form-group" style="margin:0"><label>Status</label><select id="add-status"><option value="active">Active</option><option value="pending">Pending</option><option value="completed">Completed</option><option value="escalated">Escalated</option><option value="closed">Closed</option></select></div>
|
||||
<div class="form-group" style="margin:0"><label>Threat Level</label><select id="add-threat"><option value="unknown">Unknown</option><option value="low">Low</option><option value="medium">Medium</option><option value="high">High</option><option value="critical">Critical</option></select></div>
|
||||
<div class="form-group" style="margin:0"><label>OS</label><select id="add-os"><option>Unknown</option><option>Linux</option><option>Windows</option><option>macOS</option><option>Android</option><option>iOS</option><option>Network Device</option></select></div>
|
||||
<div class="form-group" style="margin:0"><label>Source</label><input type="text" id="add-source" placeholder="fail2ban, IDS, manual"></div>
|
||||
<div class="form-group" style="margin:0"><label>Tags</label><input type="text" id="add-tags" placeholder="brute-force, ssh, external"></div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label for="add-name">Name / Label</label>
|
||||
<input type="text" id="add-name" placeholder="Corp Web Server">
|
||||
</fieldset>
|
||||
|
||||
<!-- Network Info -->
|
||||
<fieldset style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;margin-bottom:0.75rem">
|
||||
<legend style="font-size:0.85rem;font-weight:600;color:var(--accent);padding:0 0.5rem">Network</legend>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem">
|
||||
<div class="form-group" style="margin:0"><label>IPv4</label><input type="text" id="add-ipv4" placeholder="192.168.1.1"></div>
|
||||
<div class="form-group" style="margin:0"><label>IPv6</label><input type="text" id="add-ipv6" placeholder="::1"></div>
|
||||
<div class="form-group" style="margin:0"><label>Domain</label><input type="text" id="add-domain" placeholder="example.com"></div>
|
||||
<div class="form-group" style="margin:0"><label>Reverse DNS</label><input type="text" id="add-rdns" placeholder="host.isp.net"></div>
|
||||
<div class="form-group" style="margin:0"><label>MAC Address</label><input type="text" id="add-mac" placeholder="aa:bb:cc:dd:ee:ff"></div>
|
||||
<div class="form-group" style="margin:0"><label>Hostname</label><input type="text" id="add-hostname" placeholder="server01"></div>
|
||||
<div class="form-group" style="margin:0"><label>Ports / Services</label><input type="text" id="add-ports" placeholder="22/ssh, 80/http, 443/https"></div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label for="add-type">Type</label>
|
||||
<select id="add-type">
|
||||
<option value="ip">IP Address</option>
|
||||
<option value="cidr">CIDR / Range</option>
|
||||
<option value="domain">Domain</option>
|
||||
<option value="url">URL</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<!-- GeoIP -->
|
||||
<fieldset style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;margin-bottom:0.75rem">
|
||||
<legend style="font-size:0.85rem;font-weight:600;color:var(--accent);padding:0 0.5rem">GeoIP</legend>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem">
|
||||
<div class="form-group" style="margin:0"><label>Country</label><input type="text" id="add-geo-country" placeholder="US"></div>
|
||||
<div class="form-group" style="margin:0"><label>City</label><input type="text" id="add-geo-city" placeholder="New York"></div>
|
||||
<div class="form-group" style="margin:0"><label>ISP</label><input type="text" id="add-geo-isp" placeholder="Comcast"></div>
|
||||
<div class="form-group" style="margin:0"><label>ASN</label><input type="text" id="add-geo-asn" placeholder="AS7922"></div>
|
||||
<div class="form-group" style="margin:0"><label>Coordinates</label><input type="text" id="add-geo-coords" placeholder="40.7128,-74.0060"></div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label for="add-os">OS</label>
|
||||
<select id="add-os">
|
||||
<option>Unknown</option>
|
||||
<option>Linux</option>
|
||||
<option>Windows</option>
|
||||
<option>macOS</option>
|
||||
<option>Android</option>
|
||||
<option>iOS</option>
|
||||
<option>Network Device</option>
|
||||
<option>Other</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<!-- Intel -->
|
||||
<fieldset style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;margin-bottom:0.75rem">
|
||||
<legend style="font-size:0.85rem;font-weight:600;color:var(--accent);padding:0 0.5rem">Intelligence</legend>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:0.75rem">
|
||||
<div class="form-group" style="margin:0"><label>DNS Records</label><textarea id="add-dns" rows="2" placeholder="A: 1.2.3.4 MX: mail.example.com" style="font-family:monospace;font-size:0.78rem"></textarea></div>
|
||||
<div class="form-group" style="margin:0"><label>Email(s)</label><input type="text" id="add-email" placeholder="admin@example.com, abuse@isp.net"></div>
|
||||
<div class="form-group" style="margin:0"><label>Username(s)</label><input type="text" id="add-usernames" placeholder="root, admin, attacker123"></div>
|
||||
<div class="form-group" style="margin:0"><label>Vulnerabilities</label><textarea id="add-vulns" rows="2" placeholder="CVE-2024-1234, open SSH" style="font-family:monospace;font-size:0.78rem"></textarea></div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label for="add-status">Status</label>
|
||||
<select id="add-status">
|
||||
<option value="active">Active</option>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="out-of-scope">Out of Scope</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label for="add-ports">Known Ports</label>
|
||||
<input type="text" id="add-ports" placeholder="22,80,443,8080">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.75rem 1rem;margin-top:0.75rem">
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label for="add-tags">Tags (comma-separated)</label>
|
||||
<input type="text" id="add-tags" placeholder="web, internal, critical">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label for="add-notes">Notes</label>
|
||||
<input type="text" id="add-notes" placeholder="Brief notes about this target">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.5rem;margin-top:1rem">
|
||||
<button class="btn btn-primary" onclick="addTarget()">Add Target</button>
|
||||
<div class="form-group" style="margin:0.5rem 0 0"><label>Traceroute</label><textarea id="add-traceroute" rows="3" placeholder="Paste traceroute output" style="font-family:monospace;font-size:0.78rem;width:100%"></textarea></div>
|
||||
<div class="form-group" style="margin:0.5rem 0 0"><label>WHOIS</label><textarea id="add-whois" rows="3" placeholder="Paste WHOIS output" style="font-family:monospace;font-size:0.78rem;width:100%"></textarea></div>
|
||||
<div class="form-group" style="margin:0.5rem 0 0"><label>Notes</label><textarea id="add-notes" rows="3" placeholder="Investigation notes, observations, timeline..." style="font-size:0.82rem;width:100%"></textarea></div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Custom Fields -->
|
||||
<fieldset style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;margin-bottom:0.75rem">
|
||||
<legend style="font-size:0.85rem;font-weight:600;color:var(--accent);padding:0 0.5rem">Custom Fields</legend>
|
||||
<div id="custom-fields-container"></div>
|
||||
<button class="btn btn-sm" onclick="addCustomField()" style="margin-top:0.5rem">+ Add Field</button>
|
||||
</fieldset>
|
||||
|
||||
<div style="display:flex;gap:0.5rem;margin-top:0.75rem">
|
||||
<button class="btn btn-primary" onclick="addTarget()">Create Investigation</button>
|
||||
<button class="btn btn-sm" onclick="toggleAddForm()">Cancel</button>
|
||||
<span id="add-status-msg" style="font-size:0.82rem;color:var(--text-secondary);align-self:center"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Investigation Reports -->
|
||||
<div class="section">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.75rem">
|
||||
<h2 style="margin:0">Investigation Reports</h2>
|
||||
<button class="btn btn-sm" onclick="loadIRs()">Refresh</button>
|
||||
</div>
|
||||
<div id="ir-list" style="margin-bottom:0.5rem"><p style="color:var(--text-muted)">Loading reports...</p></div>
|
||||
</div>
|
||||
|
||||
<!-- Filter / Search -->
|
||||
<div class="section" style="padding:0.6rem 1rem">
|
||||
<div style="display:flex;gap:0.75rem;align-items:center;flex-wrap:wrap">
|
||||
@@ -272,24 +286,74 @@ function toggleAddForm() {
|
||||
}
|
||||
|
||||
// ── Add target ────────────────────────────────────────────────────────────────
|
||||
var _customFieldCount = 0;
|
||||
function addCustomField() {
|
||||
_customFieldCount++;
|
||||
var container = document.getElementById('custom-fields-container');
|
||||
var row = document.createElement('div');
|
||||
row.style.cssText = 'display:flex;gap:0.5rem;align-items:flex-start;margin-bottom:0.5rem;flex-wrap:wrap';
|
||||
row.innerHTML = '<input type="text" class="cf-name" placeholder="Field name" style="width:150px;padding:0.3rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-input);color:inherit;font-size:0.8rem">'
|
||||
+ '<select class="cf-type" style="width:130px;padding:0.3rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-input);color:inherit;font-size:0.8rem">'
|
||||
+ '<option value="text">Text</option><option value="ipv4">IPv4</option><option value="ipv6">IPv6</option>'
|
||||
+ '<option value="domain">Domain</option><option value="dns">DNS Record</option>'
|
||||
+ '<option value="email">Email</option><option value="username">Username</option>'
|
||||
+ '<option value="url">URL</option><option value="mac">MAC Address</option>'
|
||||
+ '<option value="hash">Hash</option><option value="misc">Misc (4000 char)</option></select>'
|
||||
+ '<input type="text" class="cf-value" placeholder="Value" style="flex:1;min-width:200px;padding:0.3rem 0.5rem;border-radius:var(--radius);border:1px solid var(--border);background:var(--bg-input);color:inherit;font-size:0.8rem">'
|
||||
+ '<button class="btn btn-sm" onclick="this.parentElement.remove()" style="padding:2px 8px;color:var(--danger)">X</button>';
|
||||
container.appendChild(row);
|
||||
}
|
||||
|
||||
function _getVal(id) { var e = document.getElementById(id); return e ? (e.value||'').trim() : ''; }
|
||||
|
||||
function addTarget() {
|
||||
var host = document.getElementById('add-host').value.trim();
|
||||
var host = _getVal('add-host');
|
||||
if (!host) { document.getElementById('add-host').focus(); return; }
|
||||
var msg = document.getElementById('add-status-msg');
|
||||
msg.textContent = 'Saving…';
|
||||
|
||||
// Collect custom fields
|
||||
var customFields = [];
|
||||
document.querySelectorAll('#custom-fields-container > div').forEach(function(row) {
|
||||
var name = row.querySelector('.cf-name').value.trim();
|
||||
var type = row.querySelector('.cf-type').value;
|
||||
var value = row.querySelector('.cf-value').value.trim();
|
||||
if (name && value) customFields.push({name: name, type: type, value: value});
|
||||
});
|
||||
|
||||
fetch('/targets/add', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
host: host,
|
||||
name: document.getElementById('add-name').value,
|
||||
type: document.getElementById('add-type').value,
|
||||
os: document.getElementById('add-os').value,
|
||||
status: document.getElementById('add-status').value,
|
||||
ports: document.getElementById('add-ports').value,
|
||||
tags: document.getElementById('add-tags').value,
|
||||
notes: document.getElementById('add-notes').value,
|
||||
host: host,
|
||||
name: _getVal('add-name'),
|
||||
type: _getVal('add-type'),
|
||||
os: _getVal('add-os'),
|
||||
status: _getVal('add-status'),
|
||||
threat_level: _getVal('add-threat'),
|
||||
source: _getVal('add-source'),
|
||||
ports: _getVal('add-ports'),
|
||||
tags: _getVal('add-tags'),
|
||||
notes: _getVal('add-notes'),
|
||||
ipv4: _getVal('add-ipv4'),
|
||||
ipv6: _getVal('add-ipv6'),
|
||||
domain: _getVal('add-domain'),
|
||||
rdns: _getVal('add-rdns'),
|
||||
mac_address: _getVal('add-mac'),
|
||||
hostname: _getVal('add-hostname'),
|
||||
services: _getVal('add-ports'),
|
||||
geo_country: _getVal('add-geo-country'),
|
||||
geo_city: _getVal('add-geo-city'),
|
||||
geo_isp: _getVal('add-geo-isp'),
|
||||
geo_asn: _getVal('add-geo-asn'),
|
||||
geo_coords: _getVal('add-geo-coords'),
|
||||
dns_records: _getVal('add-dns'),
|
||||
email: _getVal('add-email'),
|
||||
usernames: _getVal('add-usernames'),
|
||||
vulns: _getVal('add-vulns'),
|
||||
traceroute: _getVal('add-traceroute'),
|
||||
whois: _getVal('add-whois'),
|
||||
custom_fields: customFields,
|
||||
})
|
||||
}).then(function(r){ return r.json(); })
|
||||
.then(function(d) {
|
||||
@@ -434,6 +498,90 @@ document.addEventListener('DOMContentLoaded', function(){
|
||||
document.getElementById('add-notes').addEventListener('keypress', function(e){
|
||||
if (e.key === 'Enter') addTarget();
|
||||
});
|
||||
loadIRs();
|
||||
});
|
||||
|
||||
function loadIRs() {
|
||||
fetch('/targets/ir').then(function(r){ return r.json(); }).then(function(d) {
|
||||
var el = document.getElementById('ir-list');
|
||||
if (!d.ok || !d.reports || !d.reports.length) {
|
||||
el.innerHTML = '<p style="color:var(--text-muted);font-size:0.85rem">No investigation reports yet. Use HAL\'s "Report Only" or "Let HAL Fix It + IR" buttons after a scan.</p>';
|
||||
return;
|
||||
}
|
||||
var html = '<table class="data-table" style="font-size:0.82rem;width:100%"><thead><tr>'
|
||||
+ '<th>ID</th><th>Title</th><th>Risk</th><th>Status</th><th>Source</th><th>Created</th><th>Actions</th>'
|
||||
+ '</tr></thead><tbody>';
|
||||
d.reports.forEach(function(r) {
|
||||
var riskColors = {critical:'#ff3b30',high:'#ff6b35',medium:'#f59e0b',low:'#8ec07c',clean:'#34c759',unknown:'#888'};
|
||||
var rc = riskColors[r.risk_level] || '#888';
|
||||
var halBadge = r.created_by_hal ? ' <span style="background:var(--accent);color:#000;font-size:0.6rem;padding:1px 4px;border-radius:2px;font-weight:700">HAL</span>' : '';
|
||||
var statusColor = r.status === 'closed' ? 'var(--text-muted)' : r.status === 'open' ? 'var(--accent)' : '#f59e0b';
|
||||
var created = r.created_at ? new Date(r.created_at).toLocaleString() : '';
|
||||
html += '<tr>'
|
||||
+ '<td><strong style="font-family:monospace;color:var(--accent)">' + escapeHtml(r.id) + '</strong>' + halBadge + '</td>'
|
||||
+ '<td>' + escapeHtml(r.title || '') + '</td>'
|
||||
+ '<td><span style="color:' + rc + ';font-weight:600;text-transform:uppercase;font-size:0.75rem">' + escapeHtml(r.risk_level || 'unknown') + '</span></td>'
|
||||
+ '<td><span style="color:' + statusColor + '">' + escapeHtml(r.status || '') + '</span></td>'
|
||||
+ '<td style="font-size:0.75rem">' + escapeHtml(r.source || '') + '</td>'
|
||||
+ '<td style="font-size:0.75rem">' + escapeHtml(created) + '</td>'
|
||||
+ '<td style="white-space:nowrap">'
|
||||
+ '<button class="btn btn-sm" style="font-size:0.65rem;padding:1px 6px;margin-right:3px" onclick="viewIR(\'' + escapeHtml(r.id) + '\')">View</button>'
|
||||
+ '<button class="btn btn-sm" style="font-size:0.65rem;padding:1px 6px;margin-right:3px" onclick="loadIRToHal(\'' + escapeHtml(r.id) + '\')">Send to HAL</button>'
|
||||
+ '<button class="btn btn-sm" style="font-size:0.65rem;padding:1px 6px;color:var(--danger);border-color:var(--danger)" onclick="deleteIR(\'' + escapeHtml(r.id) + '\')">Delete</button>'
|
||||
+ '</td></tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
el.innerHTML = html;
|
||||
}).catch(function() {
|
||||
document.getElementById('ir-list').innerHTML = '<p style="color:var(--danger)">Failed to load reports.</p>';
|
||||
});
|
||||
}
|
||||
|
||||
function viewIR(irId) {
|
||||
fetch('/targets/ir/' + irId).then(function(r){ return r.json(); }).then(function(d) {
|
||||
if (!d.ok) { alert('IR not found'); return; }
|
||||
var ir = d.ir;
|
||||
var w = window.open('', '_blank', 'width=700,height=600');
|
||||
w.document.write('<html><head><title>' + ir.id + '</title>'
|
||||
+ '<style>body{background:#1a1a2e;color:#e0e0e0;font-family:system-ui;padding:20px;font-size:14px} '
|
||||
+ 'h1{color:#00ff41;font-size:1.2rem} h2{color:#5ac8fa;font-size:1rem;margin-top:1rem} '
|
||||
+ 'pre{background:#12122a;padding:10px;border-radius:6px;white-space:pre-wrap;font-size:0.82rem;border:1px solid #333} '
|
||||
+ '.badge{display:inline-block;padding:2px 8px;border-radius:3px;font-size:0.75rem;font-weight:700}</style></head><body>');
|
||||
w.document.write('<h1>' + ir.id + (ir.created_by_hal ? ' <span class="badge" style="background:#00ff41;color:#000">HAL</span>' : '') + '</h1>');
|
||||
w.document.write('<div><strong>Title:</strong> ' + (ir.title||'') + '</div>');
|
||||
w.document.write('<div><strong>Status:</strong> ' + (ir.status||'') + ' · <strong>Risk:</strong> ' + (ir.risk_level||'') + ' · <strong>Source:</strong> ' + (ir.source||'') + '</div>');
|
||||
w.document.write('<div><strong>Created:</strong> ' + (ir.created_at||'') + '</div>');
|
||||
if (ir.ip) w.document.write('<div><strong>IP:</strong> ' + ir.ip + '</div>');
|
||||
if (ir.analysis) { w.document.write('<h2>Analysis</h2><pre>' + ir.analysis.replace(/</g,'<') + '</pre>'); }
|
||||
if (ir.fix_attempted) {
|
||||
w.document.write('<h2>Fix Attempted</h2><pre>' + (ir.fix_results||'').replace(/</g,'<') + '</pre>');
|
||||
}
|
||||
if (ir.notes) { w.document.write('<h2>Notes</h2><pre>' + ir.notes.replace(/</g,'<') + '</pre>'); }
|
||||
w.document.write('</body></html>');
|
||||
w.document.close();
|
||||
});
|
||||
}
|
||||
|
||||
function loadIRToHal(irId) {
|
||||
fetch('/targets/ir/' + irId + '/load-to-hal', {method:'POST'}).then(function(r){return r.json()}).then(function(d) {
|
||||
if (d.ok) {
|
||||
// Open HAL panel and show the loaded IR
|
||||
var panel = document.getElementById('hal-panel');
|
||||
if (panel) panel.style.display = 'flex';
|
||||
halAppendStyled('status', 'Loaded IR ' + d.ir.id + ' into HAL memory');
|
||||
halAppendStyled('bot', 'IR ' + d.ir.id + ': ' + (d.ir.title||'') + '\nRisk: ' + (d.ir.risk_level||'unknown') + '\nStatus: ' + (d.ir.status||'') + '\n\nAnalysis loaded. Ask me to continue working on this investigation.');
|
||||
halScroll();
|
||||
} else {
|
||||
alert('Failed to load IR: ' + (d.error||''));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteIR(irId) {
|
||||
if (!confirm('Delete investigation report ' + irId + '?')) return;
|
||||
fetch('/targets/ir/' + irId + '/delete', {method:'POST'}).then(function(r){return r.json()}).then(function(d) {
|
||||
loadIRs();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -350,6 +350,7 @@ function iocAdd() {
|
||||
document.getElementById('ioc-tags').value = '';
|
||||
document.getElementById('ioc-desc').value = '';
|
||||
iocLoad();
|
||||
halAnalyze('Threat Intel', JSON.stringify(data, null, 2), 'threat intelligence', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -357,6 +358,7 @@ function iocLoad() {
|
||||
fetchJSON('/threat-intel/iocs').then(function(data) {
|
||||
_iocCache = data.iocs || [];
|
||||
iocRender(_iocCache);
|
||||
halAnalyze('Threat Intel', JSON.stringify(data, null, 2), 'threat intelligence', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -536,6 +538,7 @@ function repLookup() {
|
||||
lines.push('');
|
||||
}
|
||||
renderOutput('rep-output', lines.join('\n'));
|
||||
halAnalyze('Threat Intel', JSON.stringify(data, null, 2), 'threat intelligence', 'defense');
|
||||
}).catch(function() { setLoading(btn, false); });
|
||||
}
|
||||
|
||||
@@ -547,6 +550,7 @@ function blGenerate() {
|
||||
}).then(function(data) {
|
||||
if (data.error) { renderOutput('bl-output', 'Error: ' + data.error); return; }
|
||||
renderOutput('bl-output', data.blocklist || 'No IOCs match the selected criteria.');
|
||||
halAnalyze('Threat Intel', JSON.stringify(data, null, 2), 'threat intelligence', 'defense');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -291,6 +291,7 @@ function wgServerStatus() {
|
||||
} else {
|
||||
wgResult('wg-dashboard-results', wgOk('Server status refreshed.'));
|
||||
}
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,6 +328,7 @@ function wgRefreshPeers() {
|
||||
document.getElementById('wg-online-count').textContent = online;
|
||||
wgPopulateIpDropdowns();
|
||||
wgRenderPeersTable();
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -382,6 +384,7 @@ function wgRefreshClients() {
|
||||
wgClients = data.clients || [];
|
||||
wgPopulateIpDropdowns();
|
||||
wgRenderClientsTable();
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -432,6 +435,7 @@ function wgCreateClient() {
|
||||
wgResult('wg-clients-results', wgOk('Created: ' + c.name + ' (' + c.assigned_ip + ')'));
|
||||
document.getElementById('wg-new-name').value = '';
|
||||
wgRefreshClients();
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
} else {
|
||||
wgResult('wg-clients-results', wgErr(data.error || 'Failed'));
|
||||
}
|
||||
@@ -466,6 +470,7 @@ function wgViewClient(id) {
|
||||
h += '</div>';
|
||||
h += '<div id="wg-config-box" style="display:none;margin-top:10px"></div>';
|
||||
document.getElementById('wg-detail-content').innerHTML = h;
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -532,6 +537,7 @@ function wgAdbAutoConnect() {
|
||||
});
|
||||
wgResult('wg-adb-results', h);
|
||||
wgAdbDevices();
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -549,6 +555,7 @@ function wgAdbDevices() {
|
||||
});
|
||||
h += '</tbody></table>';
|
||||
el.innerHTML = h;
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -561,6 +568,7 @@ function wgUsbipStatus() {
|
||||
txt.textContent = data.modules_loaded ? 'Loaded' : 'Not loaded';
|
||||
document.getElementById('wg-usbip-imports').textContent = data.active_imports || '0';
|
||||
wgUsbipPorts();
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -596,6 +604,7 @@ function wgUsbipListRemote() {
|
||||
});
|
||||
h += '</tbody></table>';
|
||||
el.innerHTML = h;
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -632,6 +641,7 @@ function wgUsbipPorts() {
|
||||
});
|
||||
h += '</tbody></table>';
|
||||
el.innerHTML = h;
|
||||
halAnalyze('WireGuard', JSON.stringify(data, null, 2), 'vpn status', 'network');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user