1287 lines
62 KiB
HTML
1287 lines
62 KiB
HTML
|
|
{% extends "base.html" %}
|
||
|
|
{% block title %}Threat Monitor - AUTARCH{% endblock %}
|
||
|
|
|
||
|
|
{% block content %}
|
||
|
|
<div class="page-header" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
|
||
|
|
<div>
|
||
|
|
<h1>Threat Monitor</h1>
|
||
|
|
<p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
|
||
|
|
Real-time threat detection, network monitoring, and active counter-attack.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<a href="{{ url_for('defense.index') }}" class="btn btn-sm" style="margin-left:auto">← Defense</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Tab Bar -->
|
||
|
|
<div class="tab-bar">
|
||
|
|
<button class="tab active" data-tab-group="tmon" data-tab="live" onclick="showTab('tmon','live')">Live Monitor</button>
|
||
|
|
<button class="tab" data-tab-group="tmon" data-tab="connections" onclick="showTab('tmon','connections')">Connections</button>
|
||
|
|
<button class="tab" data-tab-group="tmon" data-tab="netintel" onclick="showTab('tmon','netintel')">Network Intel</button>
|
||
|
|
<button class="tab" data-tab-group="tmon" data-tab="threats" onclick="showTab('tmon','threats')">Threats</button>
|
||
|
|
<button class="tab" data-tab-group="tmon" data-tab="capture" onclick="showTab('tmon','capture');capLoadInterfaces()">Packet Capture</button>
|
||
|
|
<button class="tab" data-tab-group="tmon" data-tab="ddos" onclick="showTab('tmon','ddos')">DDoS Mitigation</button>
|
||
|
|
<button class="tab" data-tab-group="tmon" data-tab="counter" onclick="showTab('tmon','counter')">Counter-Attack</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ==================== LIVE MONITOR TAB ==================== -->
|
||
|
|
<div class="tab-content active" data-tab-group="tmon" data-tab="live">
|
||
|
|
<div class="section">
|
||
|
|
<h2>Live Threat Dashboard</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-stream-toggle" class="btn btn-primary" onclick="toggleMonitorStream()">Start Live Monitor</button>
|
||
|
|
<span id="stream-status" style="font-size:0.8rem;color:var(--text-muted);margin-left:12px"></span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Threat Score + Stats -->
|
||
|
|
<div style="display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap;margin-top:16px">
|
||
|
|
<div class="score-display">
|
||
|
|
<div class="score-value" id="live-threat-score">--</div>
|
||
|
|
<div class="score-label" id="live-threat-level">Threat Level</div>
|
||
|
|
</div>
|
||
|
|
<div style="flex:1;min-width:250px">
|
||
|
|
<table class="data-table" style="font-size:0.85rem">
|
||
|
|
<tbody>
|
||
|
|
<tr class="tmon-stat-clickable" onclick="tmonPopup('connections')" title="Click to view live connections">
|
||
|
|
<td>Active Connections</td><td id="live-conn-count">--</td></tr>
|
||
|
|
<tr class="tmon-stat-clickable" onclick="tmonPopup('logins')" title="Click to view failed logins">
|
||
|
|
<td>Failed Logins (5m)</td><td id="live-fail-count">--</td></tr>
|
||
|
|
<tr class="tmon-stat-clickable" onclick="tmonPopup('processes')" title="Click to view suspicious processes">
|
||
|
|
<td>Suspicious Processes</td><td id="live-sus-count">--</td></tr>
|
||
|
|
<tr class="tmon-stat-clickable" onclick="tmonPopup('scans')" title="Click to view scan indicators">
|
||
|
|
<td>Scan Indicators</td><td id="live-scan-count">--</td></tr>
|
||
|
|
<tr class="tmon-stat-clickable" onclick="tmonPopup('bandwidth')" title="Click to view bandwidth details">
|
||
|
|
<td>Bandwidth (RX/TX)</td><td id="live-bandwidth">--</td></tr>
|
||
|
|
<tr class="tmon-stat-clickable" onclick="tmonPopup('arp')" title="Click to view ARP table">
|
||
|
|
<td>ARP Spoof Alerts</td><td id="live-arp-alerts">--</td></tr>
|
||
|
|
<tr class="tmon-stat-clickable" onclick="tmonPopup('ports')" title="Click to view listening ports">
|
||
|
|
<td>New Listening Ports</td><td id="live-new-ports">--</td></tr>
|
||
|
|
<tr class="tmon-stat-clickable" onclick="tmonPopup('connrate')" title="Click to view connection rate">
|
||
|
|
<td>Conn Rate (1m avg)</td><td id="live-conn-rate">--</td></tr>
|
||
|
|
<tr class="tmon-stat-clickable" onclick="tmonPopup('ddos')" title="Click to view DDoS status">
|
||
|
|
<td>DDoS Status</td><td id="live-ddos-status">--</td></tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<pre class="output-panel scrollable" id="live-log" style="max-height:200px;margin-top:12px;font-size:0.75rem"></pre>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ==================== CONNECTIONS TAB ==================== -->
|
||
|
|
<div class="tab-content" data-tab-group="tmon" data-tab="connections">
|
||
|
|
<div class="section">
|
||
|
|
<h2>Network Connections</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-load-conns" class="btn btn-primary" onclick="loadConnections()">Load Connections</button>
|
||
|
|
<span id="conn-total" style="font-size:0.8rem;color:var(--text-muted);margin-left:12px"></span>
|
||
|
|
</div>
|
||
|
|
<div style="overflow-x:auto;margin-top:12px">
|
||
|
|
<table class="data-table" style="font-size:0.8rem">
|
||
|
|
<thead><tr><th>Local</th><th>Remote</th><th>State</th><th>PID</th><th>Process</th><th>Action</th></tr></thead>
|
||
|
|
<tbody id="conn-table">
|
||
|
|
<tr><td colspan="6" class="empty-state">Click "Load Connections" to view active network connections.</td></tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ==================== NETWORK INTEL TAB ==================== -->
|
||
|
|
<div class="tab-content" data-tab-group="tmon" data-tab="netintel">
|
||
|
|
<div class="section">
|
||
|
|
<h2>Bandwidth Monitor</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-bw" class="btn btn-primary" onclick="niBandwidth()">Check Bandwidth</button>
|
||
|
|
</div>
|
||
|
|
<div id="ni-bandwidth" style="margin-top:12px"></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>ARP Spoof Detection</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-arp" class="btn btn-primary" onclick="niArpCheck()">Scan ARP Table</button>
|
||
|
|
</div>
|
||
|
|
<div id="ni-arp" style="margin-top:12px">
|
||
|
|
<p class="empty-state">Click "Scan ARP Table" to check for spoofing.</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>Listening Port Monitor</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-ports" class="btn btn-primary" onclick="niNewPorts()">Check New Ports</button>
|
||
|
|
</div>
|
||
|
|
<p style="font-size:0.75rem;color:var(--text-muted)">First click establishes a baseline. Subsequent clicks detect new listeners.</p>
|
||
|
|
<div id="ni-ports" style="margin-top:8px"></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>GeoIP Lookup</h2>
|
||
|
|
<div class="input-row" style="margin-bottom:8px">
|
||
|
|
<input type="text" id="ni-geoip-input" placeholder="IP address (e.g., 8.8.8.8)">
|
||
|
|
<button id="btn-geoip" class="btn btn-primary" onclick="niGeoip()">Lookup</button>
|
||
|
|
</div>
|
||
|
|
<div id="ni-geoip-result"></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>Connections + GeoIP</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-conns-geo" class="btn btn-primary" onclick="niConnsGeo()">Load with GeoIP</button>
|
||
|
|
<span style="font-size:0.75rem;color:var(--text-muted);margin-left:8px">May take a few seconds for API lookups</span>
|
||
|
|
</div>
|
||
|
|
<div style="overflow-x:auto;margin-top:12px">
|
||
|
|
<table class="data-table" style="font-size:0.8rem;display:none" id="ni-geo-table">
|
||
|
|
<thead><tr><th>Remote</th><th>Port</th><th>State</th><th>Country</th><th>ISP</th><th>Process</th></tr></thead>
|
||
|
|
<tbody id="ni-geo-tbody"></tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>Connection Rate</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-conn-rate" class="btn btn-primary" onclick="niConnRate()">Check Rate</button>
|
||
|
|
</div>
|
||
|
|
<div id="ni-conn-rate" style="margin-top:12px"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ==================== THREATS TAB ==================== -->
|
||
|
|
<div class="tab-content" data-tab-group="tmon" data-tab="threats">
|
||
|
|
<div class="section">
|
||
|
|
<h2>Threat Analysis</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-threat-report" class="btn btn-primary" onclick="loadThreatReport()">Generate Report</button>
|
||
|
|
</div>
|
||
|
|
<pre class="output-panel scrollable" id="threat-output" style="margin-top:12px">Click "Generate Report" for a full threat analysis.</pre>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ==================== PACKET CAPTURE TAB ==================== -->
|
||
|
|
<div class="tab-content" data-tab-group="tmon" data-tab="capture">
|
||
|
|
<div class="section">
|
||
|
|
<h2>Packet Capture</h2>
|
||
|
|
<p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
|
||
|
|
Live packet capture via Scapy. Requires root/admin privileges.
|
||
|
|
</p>
|
||
|
|
<div class="form-row" style="margin-bottom:12px">
|
||
|
|
<div class="form-group">
|
||
|
|
<label>Interface</label>
|
||
|
|
<select id="cap-interface">
|
||
|
|
<option value="">Loading...</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
<div class="form-group">
|
||
|
|
<label>BPF Filter</label>
|
||
|
|
<input type="text" id="cap-filter" placeholder="e.g., tcp port 80, udp, host 10.0.0.1">
|
||
|
|
</div>
|
||
|
|
<div class="form-group">
|
||
|
|
<label>Duration (sec)</label>
|
||
|
|
<input type="number" id="cap-duration" value="30" min="5" max="300" style="max-width:100px">
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-cap-start" class="btn btn-primary" onclick="capStart()">Start Capture</button>
|
||
|
|
<button id="btn-cap-stop" class="btn btn-danger" onclick="capStop()" style="display:none">Stop Capture</button>
|
||
|
|
<span id="cap-status" style="font-size:0.8rem;color:var(--text-muted);margin-left:12px"></span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Live Packets -->
|
||
|
|
<div id="cap-live-packets" class="output-panel scrollable" style="display:none;max-height:300px;font-size:0.78rem;margin-top:12px"></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>Capture Analysis</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button class="btn btn-small" onclick="capProtocols()">Protocol Distribution</button>
|
||
|
|
<button class="btn btn-small" onclick="capConversations()">Top Conversations</button>
|
||
|
|
</div>
|
||
|
|
<div id="cap-analysis" style="margin-top:12px"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ==================== DDOS MITIGATION TAB ==================== -->
|
||
|
|
<div class="tab-content" data-tab-group="tmon" data-tab="ddos">
|
||
|
|
<div class="section">
|
||
|
|
<h2>DDoS Detection</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-ddos-detect" class="btn btn-primary" onclick="ddosDetect()">Run Detection</button>
|
||
|
|
</div>
|
||
|
|
<div id="ddos-status" style="margin-top:12px"></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>Top Talkers</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button id="btn-ddos-talkers" class="btn btn-small" onclick="ddosTopTalkers()">Refresh</button>
|
||
|
|
</div>
|
||
|
|
<div style="overflow-x:auto;margin-top:8px">
|
||
|
|
<table class="data-table" style="font-size:0.8rem;display:none" id="ddos-talkers-table">
|
||
|
|
<thead><tr><th>IP</th><th>Connections</th><th>%</th><th>States</th><th>Action</th></tr></thead>
|
||
|
|
<tbody id="ddos-talkers-tbody"></tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
<div id="ddos-talkers-empty">
|
||
|
|
<p class="empty-state">Click "Refresh" to load top talkers.</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>SYN Flood Protection</h2>
|
||
|
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">
|
||
|
|
<span id="syn-status-text" style="font-size:0.85rem">Status: checking...</span>
|
||
|
|
<button id="btn-syn-enable" class="btn btn-small" onclick="ddosSynEnable()">Enable</button>
|
||
|
|
<button id="btn-syn-disable" class="btn btn-danger btn-small" onclick="ddosSynDisable()">Disable</button>
|
||
|
|
</div>
|
||
|
|
<p style="font-size:0.75rem;color:var(--text-muted)">Enables SYN cookies (Linux) or SynAttackProtect (Windows) to mitigate SYN floods.</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>Rate Limiting</h2>
|
||
|
|
<div class="input-row" style="margin-bottom:8px">
|
||
|
|
<input type="text" id="ddos-rl-ip" placeholder="IP address">
|
||
|
|
<input type="text" id="ddos-rl-rate" placeholder="25/min" value="25/min" style="max-width:120px">
|
||
|
|
<button class="btn btn-primary btn-small" onclick="ddosRateLimit()">Apply Rate Limit</button>
|
||
|
|
<button class="btn btn-small" onclick="ddosRateLimitRemove()">Remove</button>
|
||
|
|
</div>
|
||
|
|
<pre class="output-panel" id="ddos-rl-result" style="min-height:0"></pre>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>Auto-Mitigation</h2>
|
||
|
|
<div id="ddos-config-panel" style="margin-bottom:12px">
|
||
|
|
<label><input type="checkbox" id="ddos-cfg-enabled"> Enable auto-mitigation</label><br>
|
||
|
|
<label><input type="checkbox" id="ddos-cfg-block" checked> Auto-block top talkers</label><br>
|
||
|
|
<label><input type="checkbox" id="ddos-cfg-syn" checked> Auto-enable SYN cookies</label><br>
|
||
|
|
<div class="form-row" style="margin-top:8px">
|
||
|
|
<div class="form-group">
|
||
|
|
<label>Connection Threshold</label>
|
||
|
|
<input type="number" id="ddos-cfg-conn-thresh" value="100" min="10" style="max-width:100px">
|
||
|
|
</div>
|
||
|
|
<div class="form-group">
|
||
|
|
<label>SYN Threshold</label>
|
||
|
|
<input type="number" id="ddos-cfg-syn-thresh" value="50" min="10" style="max-width:100px">
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button class="btn btn-primary btn-small" onclick="ddosSaveConfig()">Save Config</button>
|
||
|
|
<button class="btn btn-danger btn-small" onclick="ddosAutoMitigate()">Run Auto-Mitigation Now</button>
|
||
|
|
</div>
|
||
|
|
<pre class="output-panel" id="ddos-auto-result" style="min-height:0;margin-top:8px"></pre>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="section">
|
||
|
|
<h2>Mitigation History</h2>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button class="btn btn-small" onclick="ddosHistory()">Refresh</button>
|
||
|
|
<button class="btn btn-danger btn-small" onclick="ddosClearHistory()">Clear</button>
|
||
|
|
</div>
|
||
|
|
<div id="ddos-history" style="margin-top:8px">
|
||
|
|
<p class="empty-state">Click "Refresh" to load history.</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ==================== COUNTER-ATTACK TAB ==================== -->
|
||
|
|
<div class="tab-content" data-tab-group="tmon" data-tab="counter">
|
||
|
|
<div class="section">
|
||
|
|
<h2>Counter-Attack</h2>
|
||
|
|
<p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
|
||
|
|
Active response tools. Use with caution — actions are immediate and may affect network connectivity.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<!-- Block IP -->
|
||
|
|
<div style="margin-bottom:16px">
|
||
|
|
<h4>Block IP Address</h4>
|
||
|
|
<div class="input-row">
|
||
|
|
<input type="text" id="counter-block-ip" placeholder="IP address">
|
||
|
|
<button class="btn btn-danger btn-small" onclick="counterBlockIP()">Block IP</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Kill Process -->
|
||
|
|
<div style="margin-bottom:16px">
|
||
|
|
<h4>Kill Process</h4>
|
||
|
|
<div class="input-row">
|
||
|
|
<input type="number" id="counter-kill-pid" placeholder="Process ID (PID)">
|
||
|
|
<button class="btn btn-danger btn-small" onclick="counterKillProcess()">Kill Process</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Block Port -->
|
||
|
|
<div style="margin-bottom:16px">
|
||
|
|
<h4>Block Port</h4>
|
||
|
|
<div class="input-row">
|
||
|
|
<input type="number" id="counter-block-port" placeholder="Port number">
|
||
|
|
<select id="counter-block-dir" style="max-width:120px">
|
||
|
|
<option value="in">Inbound</option>
|
||
|
|
<option value="out">Outbound</option>
|
||
|
|
</select>
|
||
|
|
<button class="btn btn-danger btn-small" onclick="counterBlockPort()">Block Port</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<pre class="output-panel" id="counter-result" style="min-height:0"></pre>
|
||
|
|
|
||
|
|
<!-- Blocklist -->
|
||
|
|
<h4 style="margin-top:24px">Persistent Blocklist</h4>
|
||
|
|
<div class="tool-actions">
|
||
|
|
<button class="btn btn-small" onclick="loadBlocklist()">Refresh Blocklist</button>
|
||
|
|
</div>
|
||
|
|
<div id="blocklist-container" style="margin-top:8px">
|
||
|
|
<p class="empty-state">Click "Refresh Blocklist" to load.</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ==================== DRILL-DOWN POPUP ==================== -->
|
||
|
|
<div id="tmon-overlay" class="tmon-overlay" onclick="if(event.target===this)tmonPopupClose()">
|
||
|
|
<div class="tmon-popup">
|
||
|
|
<div class="tmon-popup-header">
|
||
|
|
<h3 id="tmon-popup-title">Details</h3>
|
||
|
|
<button class="tmon-popup-close" onclick="tmonPopupClose()" title="Close">×</button>
|
||
|
|
</div>
|
||
|
|
<div class="tmon-popup-body" id="tmon-popup-body">
|
||
|
|
<p class="empty-state">Loading...</p>
|
||
|
|
</div>
|
||
|
|
<div class="tmon-popup-status" id="tmon-popup-status"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
/* ══════════════ LIVE MONITOR ══════════════ */
|
||
|
|
var _monitorSSE = null;
|
||
|
|
|
||
|
|
function toggleMonitorStream() {
|
||
|
|
var btn = document.getElementById('btn-stream-toggle');
|
||
|
|
if (_monitorSSE) {
|
||
|
|
_monitorSSE.close();
|
||
|
|
_monitorSSE = null;
|
||
|
|
btn.textContent = 'Start Live Monitor';
|
||
|
|
document.getElementById('stream-status').textContent = 'Stopped';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
btn.textContent = 'Stop Live Monitor';
|
||
|
|
document.getElementById('stream-status').textContent = 'Connecting...';
|
||
|
|
_monitorSSE = new EventSource('/defense/monitor/stream');
|
||
|
|
_monitorSSE.onmessage = function(e) {
|
||
|
|
try {
|
||
|
|
var d = JSON.parse(e.data);
|
||
|
|
// Handle initial heartbeat
|
||
|
|
if (d.type === 'heartbeat') {
|
||
|
|
document.getElementById('stream-status').textContent = 'Connected — gathering data...';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
document.getElementById('stream-status').textContent = 'Live — ' + d.timestamp;
|
||
|
|
|
||
|
|
// Threat score
|
||
|
|
var sc = d.threat_score || {};
|
||
|
|
var scoreEl = document.getElementById('live-threat-score');
|
||
|
|
scoreEl.textContent = sc.score;
|
||
|
|
scoreEl.style.color = sc.score >= 40 ? 'var(--danger)' : sc.score >= 15 ? 'var(--warning)' : 'var(--success)';
|
||
|
|
document.getElementById('live-threat-level').textContent = sc.level || 'Unknown';
|
||
|
|
|
||
|
|
// Core metrics
|
||
|
|
document.getElementById('live-conn-count').textContent = d.connection_count || 0;
|
||
|
|
document.getElementById('live-fail-count').textContent = d.failed_logins || 0;
|
||
|
|
document.getElementById('live-sus-count').textContent = d.suspicious_processes || 0;
|
||
|
|
document.getElementById('live-scan-count').textContent = d.scan_indicators || 0;
|
||
|
|
|
||
|
|
// Enhanced metrics
|
||
|
|
var bw = d.bandwidth || {};
|
||
|
|
document.getElementById('live-bandwidth').textContent =
|
||
|
|
(bw.rx_mbps || 0).toFixed(2) + ' / ' + (bw.tx_mbps || 0).toFixed(2) + ' MB/s';
|
||
|
|
|
||
|
|
document.getElementById('live-arp-alerts').textContent = d.arp_alerts || 0;
|
||
|
|
var npEl = document.getElementById('live-new-ports');
|
||
|
|
npEl.textContent = d.new_ports || 0;
|
||
|
|
if (d.new_ports > 0) npEl.style.color = 'var(--warning)';
|
||
|
|
|
||
|
|
var cr = d.connection_rate || {};
|
||
|
|
document.getElementById('live-conn-rate').textContent =
|
||
|
|
(cr.avg_rate_1m || 0) + ' (peak: ' + (cr.peak_rate || 0) + ')';
|
||
|
|
|
||
|
|
var ddos = d.ddos || {};
|
||
|
|
var ddosEl = document.getElementById('live-ddos-status');
|
||
|
|
if (ddos.under_attack) {
|
||
|
|
ddosEl.textContent = ddos.attack_type.toUpperCase();
|
||
|
|
ddosEl.style.color = 'var(--danger)';
|
||
|
|
ddosEl.style.fontWeight = 'bold';
|
||
|
|
} else {
|
||
|
|
ddosEl.textContent = 'Clear';
|
||
|
|
ddosEl.style.color = 'var(--success)';
|
||
|
|
ddosEl.style.fontWeight = 'normal';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log line
|
||
|
|
var log = document.getElementById('live-log');
|
||
|
|
var line = '[' + new Date().toLocaleTimeString() + '] Score:' + sc.score
|
||
|
|
+ ' Conns:' + d.connection_count + ' BW:' + (bw.rx_mbps||0).toFixed(1) + 'MB/s'
|
||
|
|
+ ' ARP:' + (d.arp_alerts||0) + ' DDoS:' + (ddos.under_attack ? ddos.attack_type : 'clear');
|
||
|
|
if (d.new_port_details) {
|
||
|
|
d.new_port_details.forEach(function(p) {
|
||
|
|
line += '\n NEW PORT: ' + p.port + ' (' + (p.process||'unknown') + ')';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
log.textContent = line + '\n' + (log.textContent || '').split('\n').slice(0, 80).join('\n');
|
||
|
|
} catch(ex) {}
|
||
|
|
};
|
||
|
|
_monitorSSE.onerror = function() {
|
||
|
|
document.getElementById('stream-status').textContent = 'Connection lost — retrying...';
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ══════════════ DRILL-DOWN POPUP ══════════════ */
|
||
|
|
var _popupRefresh = null; // interval for auto-refreshing popup data
|
||
|
|
|
||
|
|
function tmonPopupClose() {
|
||
|
|
document.getElementById('tmon-overlay').classList.remove('open');
|
||
|
|
if (_popupRefresh) { clearInterval(_popupRefresh); _popupRefresh = null; }
|
||
|
|
}
|
||
|
|
|
||
|
|
function tmonPopup(kind) {
|
||
|
|
var overlay = document.getElementById('tmon-overlay');
|
||
|
|
var title = document.getElementById('tmon-popup-title');
|
||
|
|
var body = document.getElementById('tmon-popup-body');
|
||
|
|
var status = document.getElementById('tmon-popup-status');
|
||
|
|
|
||
|
|
if (_popupRefresh) { clearInterval(_popupRefresh); _popupRefresh = null; }
|
||
|
|
body.innerHTML = '<p class="empty-state">Loading...</p>';
|
||
|
|
status.textContent = '';
|
||
|
|
overlay.classList.add('open');
|
||
|
|
|
||
|
|
var loaders = {
|
||
|
|
connections: { title: 'Active Connections', fn: _popupConnections, refresh: 5000 },
|
||
|
|
logins: { title: 'Failed Logins (Last 10 min)', fn: _popupLogins },
|
||
|
|
processes: { title: 'Suspicious Processes', fn: _popupProcesses },
|
||
|
|
scans: { title: 'Port Scan Indicators', fn: _popupScans },
|
||
|
|
bandwidth: { title: 'Bandwidth by Interface', fn: _popupBandwidth, refresh: 3000 },
|
||
|
|
arp: { title: 'ARP Table & Spoof Alerts', fn: _popupArp },
|
||
|
|
ports: { title: 'Listening Ports', fn: _popupPorts },
|
||
|
|
connrate: { title: 'Connection Rate', fn: _popupConnRate, refresh: 3000 },
|
||
|
|
ddos: { title: 'DDoS Detection Status', fn: _popupDdos, refresh: 5000 },
|
||
|
|
};
|
||
|
|
|
||
|
|
var cfg = loaders[kind];
|
||
|
|
if (!cfg) { body.innerHTML = '<p>Unknown view</p>'; return; }
|
||
|
|
|
||
|
|
title.textContent = cfg.title;
|
||
|
|
cfg.fn(body, status);
|
||
|
|
if (cfg.refresh) {
|
||
|
|
_popupRefresh = setInterval(function() {
|
||
|
|
if (!overlay.classList.contains('open')) { clearInterval(_popupRefresh); return; }
|
||
|
|
cfg.fn(body, status);
|
||
|
|
}, cfg.refresh);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Close on Escape key */
|
||
|
|
document.addEventListener('keydown', function(e) {
|
||
|
|
if (e.key === 'Escape') tmonPopupClose();
|
||
|
|
});
|
||
|
|
|
||
|
|
/* ── Connections popup (live, clickable rows) ── */
|
||
|
|
function _popupConnections(body, status) {
|
||
|
|
postJSON('/defense/monitor/connections', {}).then(function(data) {
|
||
|
|
var conns = data.connections || [];
|
||
|
|
status.textContent = conns.length + ' connections — ' + new Date().toLocaleTimeString();
|
||
|
|
if (!conns.length) { body.innerHTML = '<p class="empty-state">No active connections.</p>'; return; }
|
||
|
|
var html = '<table class="data-table" style="font-size:0.8rem">'
|
||
|
|
+ '<thead><tr><th>Local</th><th>Remote</th><th>State</th><th>PID</th><th>Process</th></tr></thead><tbody>';
|
||
|
|
conns.forEach(function(c, i) {
|
||
|
|
var ext = c.remote_addr && c.remote_addr !== '0.0.0.0' && c.remote_addr !== '::' && c.remote_addr !== '127.0.0.1' && c.remote_addr !== '::1';
|
||
|
|
var style = ext ? ' style="color:var(--warning)"' : '';
|
||
|
|
html += '<tr class="tmon-row-clickable"' + style + ' onclick="_popupConnDetail(this,' + i + ')" data-conn=\'' + escapeAttr(JSON.stringify(c)) + '\'>'
|
||
|
|
+ '<td>' + escapeHtml(c.local_addr + ':' + c.local_port) + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(c.remote_addr + ':' + c.remote_port) + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(c.state || '') + '</td>'
|
||
|
|
+ '<td>' + (c.pid || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(c.process || '') + '</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
body.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function escapeAttr(s) { return s.replace(/&/g,'&').replace(/'/g,''').replace(/"/g,'"').replace(/</g,'<'); }
|
||
|
|
|
||
|
|
function _popupConnDetail(row, idx) {
|
||
|
|
var c;
|
||
|
|
try { c = JSON.parse(row.getAttribute('data-conn')); } catch(e) { return; }
|
||
|
|
var body = document.getElementById('tmon-popup-body');
|
||
|
|
var status = document.getElementById('tmon-popup-status');
|
||
|
|
|
||
|
|
// Pause auto-refresh while viewing detail
|
||
|
|
if (_popupRefresh) { clearInterval(_popupRefresh); _popupRefresh = null; }
|
||
|
|
|
||
|
|
var ext = c.remote_addr && c.remote_addr !== '0.0.0.0' && c.remote_addr !== '::' && c.remote_addr !== '127.0.0.1' && c.remote_addr !== '::1';
|
||
|
|
|
||
|
|
var html = '<button class="tmon-back-btn" onclick="tmonPopup(\'connections\')">← Back to connections</button>';
|
||
|
|
html += '<div class="tmon-detail-card"><h4>Connection Details</h4><table>'
|
||
|
|
+ '<tr><td>Local Address</td><td>' + escapeHtml(c.local_addr) + '</td></tr>'
|
||
|
|
+ '<tr><td>Local Port</td><td>' + c.local_port + '</td></tr>'
|
||
|
|
+ '<tr><td>Remote Address</td><td>' + escapeHtml(c.remote_addr) + '</td></tr>'
|
||
|
|
+ '<tr><td>Remote Port</td><td>' + c.remote_port + '</td></tr>'
|
||
|
|
+ '<tr><td>State</td><td>' + escapeHtml(c.state || 'Unknown') + '</td></tr>'
|
||
|
|
+ '<tr><td>PID</td><td>' + (c.pid || '—') + '</td></tr>'
|
||
|
|
+ '<tr><td>Process</td><td>' + escapeHtml(c.process || '—') + '</td></tr>'
|
||
|
|
+ '</table></div>';
|
||
|
|
|
||
|
|
// Actions
|
||
|
|
html += '<div class="tmon-detail-card"><h4>Actions</h4><div class="tool-actions">';
|
||
|
|
if (ext) {
|
||
|
|
html += '<button class="btn btn-danger btn-small" onclick="counterBlockIP(\'' + escapeHtml(c.remote_addr) + '\');tmonPopupClose()">Block IP</button> ';
|
||
|
|
html += '<button class="btn btn-small" onclick="_popupGeoLookup(\'' + escapeHtml(c.remote_addr) + '\')">GeoIP Lookup</button> ';
|
||
|
|
}
|
||
|
|
if (c.pid) {
|
||
|
|
html += '<button class="btn btn-danger btn-small" onclick="if(confirm(\'Kill PID ' + c.pid + '?\')){counterKillPid(' + c.pid + ')}">Kill Process</button>';
|
||
|
|
}
|
||
|
|
html += '</div></div>';
|
||
|
|
|
||
|
|
// GeoIP placeholder
|
||
|
|
html += '<div id="popup-geo-result"></div>';
|
||
|
|
|
||
|
|
body.innerHTML = html;
|
||
|
|
status.textContent = 'Connection detail view';
|
||
|
|
}
|
||
|
|
|
||
|
|
function counterKillPid(pid) {
|
||
|
|
postJSON('/defense/monitor/kill-process', {pid: pid}).then(function(data) {
|
||
|
|
var el = document.getElementById('popup-geo-result');
|
||
|
|
if (el) el.innerHTML = '<div class="tmon-detail-card"><h4>Result</h4><p>' + escapeHtml(data.message || data.error) + '</p></div>';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function _popupGeoLookup(ip) {
|
||
|
|
var el = document.getElementById('popup-geo-result');
|
||
|
|
if (!el) return;
|
||
|
|
el.innerHTML = '<div class="tmon-detail-card"><h4>GeoIP — Loading...</h4></div>';
|
||
|
|
postJSON('/defense/monitor/geoip', {ip: ip}).then(function(data) {
|
||
|
|
if (data.error) { el.innerHTML = '<div class="tmon-detail-card"><h4>GeoIP</h4><p>' + escapeHtml(data.error) + '</p></div>'; return; }
|
||
|
|
el.innerHTML = '<div class="tmon-detail-card"><h4>GeoIP — ' + escapeHtml(ip) + '</h4><table>'
|
||
|
|
+ '<tr><td>Country</td><td>' + escapeHtml(data.country || '—') + ' (' + escapeHtml(data.country_code || '') + ')</td></tr>'
|
||
|
|
+ '<tr><td>City</td><td>' + escapeHtml(data.city || '—') + '</td></tr>'
|
||
|
|
+ '<tr><td>ISP</td><td>' + escapeHtml(data.isp || '—') + '</td></tr>'
|
||
|
|
+ '<tr><td>Org</td><td>' + escapeHtml(data.org || '—') + '</td></tr>'
|
||
|
|
+ '<tr><td>ASN</td><td>' + (data.asn || '—') + '</td></tr>'
|
||
|
|
+ '</table></div>';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── Failed Logins popup ── */
|
||
|
|
function _popupLogins(body, status) {
|
||
|
|
postJSON('/defense/monitor/threats', {}).then(function(data) {
|
||
|
|
var logins = data.recent_failed_logins || [];
|
||
|
|
status.textContent = logins.length + ' failed logins';
|
||
|
|
if (!logins.length) { body.innerHTML = '<p class="empty-state">No recent failed logins.</p>'; return; }
|
||
|
|
var html = '<table class="data-table" style="font-size:0.8rem">'
|
||
|
|
+ '<thead><tr><th>Time</th><th>IP Address</th><th>Username</th></tr></thead><tbody>';
|
||
|
|
logins.forEach(function(l) {
|
||
|
|
html += '<tr><td>' + escapeHtml(l.time || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(l.ip || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(l.user || '') + '</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
body.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── Suspicious Processes popup ── */
|
||
|
|
function _popupProcesses(body, status) {
|
||
|
|
postJSON('/defense/monitor/processes', {}).then(function(data) {
|
||
|
|
var procs = data.processes || [];
|
||
|
|
status.textContent = procs.length + ' suspicious processes';
|
||
|
|
if (!procs.length) { body.innerHTML = '<p class="empty-state" style="color:var(--success)">No suspicious processes detected.</p>'; return; }
|
||
|
|
var html = '<table class="data-table" style="font-size:0.8rem">'
|
||
|
|
+ '<thead><tr><th>PID</th><th>Name</th><th>Reason</th><th>Severity</th><th>Action</th></tr></thead><tbody>';
|
||
|
|
procs.forEach(function(p) {
|
||
|
|
html += '<tr style="color:var(--danger)"><td>' + (p.pid || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(p.name || '') + '</td>'
|
||
|
|
+ '<td style="font-size:0.75rem">' + escapeHtml(p.reason || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(p.severity || '') + '</td>'
|
||
|
|
+ '<td><button class="btn btn-danger btn-small" style="font-size:0.65rem;padding:2px 6px" '
|
||
|
|
+ 'onclick="if(confirm(\'Kill PID ' + p.pid + '?\')){counterKillPid(' + p.pid + ')}">Kill</button></td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
body.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── Scan Indicators popup ── */
|
||
|
|
function _popupScans(body, status) {
|
||
|
|
postJSON('/defense/monitor/threats', {}).then(function(data) {
|
||
|
|
var scans = data.scan_indicators || [];
|
||
|
|
status.textContent = scans.length + ' scan indicators';
|
||
|
|
if (!scans.length) { body.innerHTML = '<p class="empty-state" style="color:var(--success)">No port scan activity detected.</p>'; return; }
|
||
|
|
var html = '<table class="data-table" style="font-size:0.8rem">'
|
||
|
|
+ '<thead><tr><th>Type</th><th>Source IP</th><th>Detail</th><th>Severity</th><th>Action</th></tr></thead><tbody>';
|
||
|
|
scans.forEach(function(s) {
|
||
|
|
html += '<tr style="color:var(--danger)"><td>' + escapeHtml(s.type || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(s.ip || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(s.detail || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(s.severity || '') + '</td>'
|
||
|
|
+ '<td><button class="btn btn-danger btn-small" style="font-size:0.65rem;padding:2px 6px" '
|
||
|
|
+ 'onclick="counterBlockIP(\'' + escapeHtml(s.ip || '') + '\')">Block</button></td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
body.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── Bandwidth popup (live) ── */
|
||
|
|
function _popupBandwidth(body, status) {
|
||
|
|
postJSON('/defense/monitor/bandwidth', {}).then(function(data) {
|
||
|
|
var ifaces = data.interfaces || [];
|
||
|
|
status.textContent = ifaces.length + ' interfaces — ' + new Date().toLocaleTimeString();
|
||
|
|
if (!ifaces.length) { body.innerHTML = '<p class="empty-state">No interface data available.</p>'; return; }
|
||
|
|
var html = '<table class="data-table" style="font-size:0.8rem">'
|
||
|
|
+ '<thead><tr><th>Interface</th><th>RX Total</th><th>TX Total</th><th>RX Delta</th><th>TX Delta</th></tr></thead><tbody>';
|
||
|
|
ifaces.forEach(function(i) {
|
||
|
|
html += '<tr><td>' + escapeHtml(i.interface) + '</td>'
|
||
|
|
+ '<td>' + formatBytes(i.rx_bytes) + '</td>'
|
||
|
|
+ '<td>' + formatBytes(i.tx_bytes) + '</td>'
|
||
|
|
+ '<td style="color:var(--analyze)">' + formatBytes(i.rx_delta) + '</td>'
|
||
|
|
+ '<td style="color:var(--warning)">' + formatBytes(i.tx_delta) + '</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
body.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── ARP Table popup ── */
|
||
|
|
function _popupArp(body, status) {
|
||
|
|
postJSON('/defense/monitor/arp-check', {}).then(function(data) {
|
||
|
|
var alerts = data.alerts || [];
|
||
|
|
status.textContent = alerts.length ? alerts.length + ' spoof alerts!' : 'No spoofing detected';
|
||
|
|
if (!alerts.length) {
|
||
|
|
body.innerHTML = '<p class="empty-state" style="color:var(--success)">ARP table is clean — no spoofing detected.</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var html = '<table class="data-table" style="font-size:0.8rem">'
|
||
|
|
+ '<thead><tr><th>IP Address</th><th>MAC Addresses</th><th>Severity</th></tr></thead><tbody>';
|
||
|
|
alerts.forEach(function(a) {
|
||
|
|
html += '<tr style="color:var(--danger)"><td>' + escapeHtml(a.ip) + '</td>'
|
||
|
|
+ '<td>' + a.macs.map(escapeHtml).join('<br>') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(a.severity) + '</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
body.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── Listening Ports popup ── */
|
||
|
|
function _popupPorts(body, status) {
|
||
|
|
postJSON('/defense/monitor/new-ports', {}).then(function(data) {
|
||
|
|
var ports = data.new_ports || [];
|
||
|
|
status.textContent = ports.length ? ports.length + ' new ports since last check' : 'No new ports (baseline updated)';
|
||
|
|
if (!ports.length) {
|
||
|
|
body.innerHTML = '<p class="empty-state">No new listening ports since last check. Click again to re-check.</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var html = '<table class="data-table" style="font-size:0.8rem">'
|
||
|
|
+ '<thead><tr><th>Port</th><th>PID</th><th>Process</th><th>Action</th></tr></thead><tbody>';
|
||
|
|
ports.forEach(function(p) {
|
||
|
|
html += '<tr style="color:var(--warning)"><td>' + p.port + '</td>'
|
||
|
|
+ '<td>' + (p.pid || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(p.process || '') + '</td>'
|
||
|
|
+ '<td><button class="btn btn-danger btn-small" style="font-size:0.65rem;padding:2px 6px" '
|
||
|
|
+ 'onclick="counterBlockPort(\'' + p.port + '\',\'in\')">Block Port</button></td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
body.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function counterBlockPort(port, dir) {
|
||
|
|
postJSON('/defense/monitor/block-port', {port: parseInt(port), direction: dir}).then(function(data) {
|
||
|
|
var el = document.getElementById('popup-geo-result') || document.getElementById('tmon-popup-status');
|
||
|
|
if (el) el.textContent = data.message || data.error;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── Connection Rate popup (live) ── */
|
||
|
|
function _popupConnRate(body, status) {
|
||
|
|
postJSON('/defense/monitor/connection-rate', {}).then(function(data) {
|
||
|
|
status.textContent = 'Updated ' + new Date().toLocaleTimeString();
|
||
|
|
var html = '<div class="tmon-detail-card"><h4>Connection Rate Metrics</h4><table>'
|
||
|
|
+ '<tr><td>Current Connections</td><td style="font-size:1.1rem;font-weight:600">' + (data.current_rate || 0) + '</td></tr>'
|
||
|
|
+ '<tr><td>1-Minute Average</td><td>' + (data.avg_rate_1m || 0) + '</td></tr>'
|
||
|
|
+ '<tr><td>5-Minute Average</td><td>' + (data.avg_rate_5m || 0) + '</td></tr>'
|
||
|
|
+ '<tr><td>Peak (5m window)</td><td style="color:var(--warning)">' + (data.peak_rate || 0) + '</td></tr>'
|
||
|
|
+ '</table></div>';
|
||
|
|
body.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── DDoS Status popup (live) ── */
|
||
|
|
function _popupDdos(body, status) {
|
||
|
|
postJSON('/defense/monitor/ddos/detect', {}).then(function(data) {
|
||
|
|
var under = data.under_attack;
|
||
|
|
status.textContent = under ? 'ATTACK DETECTED' : 'No attack — ' + new Date().toLocaleTimeString();
|
||
|
|
|
||
|
|
var html = '<div class="tmon-detail-card"><h4>Detection Status</h4><table>'
|
||
|
|
+ '<tr><td>Status</td><td style="color:' + (under ? 'var(--danger);font-weight:bold' : 'var(--success)') + '">'
|
||
|
|
+ (under ? 'UNDER ATTACK' : 'Clear') + '</td></tr>'
|
||
|
|
+ '<tr><td>Attack Type</td><td>' + escapeHtml(data.attack_type || 'none') + '</td></tr>'
|
||
|
|
+ '<tr><td>Severity</td><td>' + escapeHtml(data.severity || 'LOW') + '</td></tr>'
|
||
|
|
+ '<tr><td>Total Connections</td><td>' + (data.total_connections || 0) + '</td></tr>'
|
||
|
|
+ '<tr><td>SYN/Half-Open</td><td>' + (data.syn_count || 0) + '</td></tr>'
|
||
|
|
+ '</table></div>';
|
||
|
|
|
||
|
|
if (data.indicators && data.indicators.length) {
|
||
|
|
html += '<div class="tmon-detail-card"><h4>Indicators</h4><ul style="padding-left:18px;font-size:0.82rem">';
|
||
|
|
data.indicators.forEach(function(ind) {
|
||
|
|
html += '<li style="color:var(--danger);margin-bottom:4px">' + escapeHtml(ind) + '</li>';
|
||
|
|
});
|
||
|
|
html += '</ul></div>';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (data.top_talkers && data.top_talkers.length) {
|
||
|
|
html += '<div class="tmon-detail-card"><h4>Top Talkers</h4>'
|
||
|
|
+ '<table class="data-table" style="font-size:0.8rem">'
|
||
|
|
+ '<thead><tr><th>IP</th><th>Connections</th><th>Action</th></tr></thead><tbody>';
|
||
|
|
data.top_talkers.slice(0, 10).forEach(function(t) {
|
||
|
|
html += '<tr><td>' + escapeHtml(t.ip) + '</td><td>' + t.connections + '</td>'
|
||
|
|
+ '<td><button class="btn btn-danger btn-small" style="font-size:0.65rem;padding:2px 6px" '
|
||
|
|
+ 'onclick="counterBlockIP(\'' + escapeHtml(t.ip) + '\')">Block</button></td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table></div>';
|
||
|
|
}
|
||
|
|
|
||
|
|
body.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ══════════════ CONNECTIONS ══════════════ */
|
||
|
|
function loadConnections() {
|
||
|
|
var btn = document.getElementById('btn-load-conns');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/connections', {}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
document.getElementById('conn-total').textContent = (data.total || 0) + ' connections';
|
||
|
|
var html = '';
|
||
|
|
(data.connections || []).forEach(function(c) {
|
||
|
|
var isExternal = c.remote_addr && c.remote_addr !== '0.0.0.0' && c.remote_addr !== '::' && c.remote_addr !== '127.0.0.1';
|
||
|
|
var rowClass = isExternal ? ' style="color:var(--warning)"' : '';
|
||
|
|
html += '<tr' + rowClass + '>'
|
||
|
|
+ '<td>' + escapeHtml(c.local_addr + ':' + c.local_port) + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(c.remote_addr + ':' + c.remote_port) + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(c.state || '') + '</td>'
|
||
|
|
+ '<td>' + (c.pid || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(c.process || '') + '</td>'
|
||
|
|
+ '<td>'
|
||
|
|
+ (isExternal ? '<button class="btn btn-danger btn-small" style="font-size:0.65rem;padding:2px 6px" onclick="counterBlockIP(\'' + escapeHtml(c.remote_addr) + '\')">Block</button>' : '')
|
||
|
|
+ '</td></tr>';
|
||
|
|
});
|
||
|
|
document.getElementById('conn-table').innerHTML = html || '<tr><td colspan="6">No connections found</td></tr>';
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ══════════════ NETWORK INTEL ══════════════ */
|
||
|
|
function niBandwidth() {
|
||
|
|
var btn = document.getElementById('btn-bw');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/bandwidth', {}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
var ifaces = data.interfaces || [];
|
||
|
|
if (!ifaces.length) {
|
||
|
|
document.getElementById('ni-bandwidth').innerHTML = '<p class="empty-state">No interface data</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var html = '<table class="data-table" style="font-size:0.82rem"><thead><tr>'
|
||
|
|
+ '<th>Interface</th><th>RX Total</th><th>TX Total</th><th>RX Delta</th><th>TX Delta</th></tr></thead><tbody>';
|
||
|
|
ifaces.forEach(function(i) {
|
||
|
|
html += '<tr><td>' + escapeHtml(i.interface) + '</td>'
|
||
|
|
+ '<td>' + formatBytes(i.rx_bytes) + '</td>'
|
||
|
|
+ '<td>' + formatBytes(i.tx_bytes) + '</td>'
|
||
|
|
+ '<td>' + formatBytes(i.rx_delta) + '</td>'
|
||
|
|
+ '<td>' + formatBytes(i.tx_delta) + '</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
document.getElementById('ni-bandwidth').innerHTML = html;
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
function niArpCheck() {
|
||
|
|
var btn = document.getElementById('btn-arp');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/arp-check', {}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
var alerts = data.alerts || [];
|
||
|
|
if (!alerts.length) {
|
||
|
|
document.getElementById('ni-arp').innerHTML = '<p style="color:var(--success)">No ARP spoofing detected.</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var html = '<table class="data-table" style="font-size:0.82rem"><thead><tr>'
|
||
|
|
+ '<th>IP</th><th>MACs</th><th>Severity</th></tr></thead><tbody>';
|
||
|
|
alerts.forEach(function(a) {
|
||
|
|
html += '<tr style="color:var(--danger)"><td>' + escapeHtml(a.ip) + '</td>'
|
||
|
|
+ '<td>' + a.macs.map(escapeHtml).join(', ') + '</td>'
|
||
|
|
+ '<td>' + a.severity + '</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
document.getElementById('ni-arp').innerHTML = html;
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
function niNewPorts() {
|
||
|
|
var btn = document.getElementById('btn-ports');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/new-ports', {}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
var ports = data.new_ports || [];
|
||
|
|
if (!ports.length) {
|
||
|
|
document.getElementById('ni-ports').innerHTML = '<p style="color:var(--success)">No new listening ports detected.</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var html = '<table class="data-table" style="font-size:0.82rem"><thead><tr>'
|
||
|
|
+ '<th>Port</th><th>PID</th><th>Process</th></tr></thead><tbody>';
|
||
|
|
ports.forEach(function(p) {
|
||
|
|
html += '<tr style="color:var(--warning)"><td>' + p.port + '</td>'
|
||
|
|
+ '<td>' + (p.pid || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(p.process || '') + '</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
document.getElementById('ni-ports').innerHTML = html;
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
function niGeoip() {
|
||
|
|
var ip = document.getElementById('ni-geoip-input').value.trim();
|
||
|
|
if (!ip) return;
|
||
|
|
var btn = document.getElementById('btn-geoip');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/geoip', {ip: ip}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
if (data.error) {
|
||
|
|
document.getElementById('ni-geoip-result').innerHTML = '<p style="color:var(--text-muted)">' + escapeHtml(data.error) + '</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var html = '<table class="data-table" style="font-size:0.82rem"><tbody>'
|
||
|
|
+ '<tr><td>IP</td><td>' + escapeHtml(data.ip) + '</td></tr>'
|
||
|
|
+ '<tr><td>Country</td><td>' + escapeHtml(data.country) + ' (' + escapeHtml(data.country_code) + ')</td></tr>'
|
||
|
|
+ '<tr><td>City</td><td>' + escapeHtml(data.city || '—') + '</td></tr>'
|
||
|
|
+ '<tr><td>ISP</td><td>' + escapeHtml(data.isp || '—') + '</td></tr>'
|
||
|
|
+ '<tr><td>Org</td><td>' + escapeHtml(data.org || '—') + '</td></tr>'
|
||
|
|
+ '<tr><td>ASN</td><td>' + (data.asn || '—') + '</td></tr>'
|
||
|
|
+ '</tbody></table>';
|
||
|
|
document.getElementById('ni-geoip-result').innerHTML = html;
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
function niConnsGeo() {
|
||
|
|
var btn = document.getElementById('btn-conns-geo');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/connections-geo', {}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
var conns = data.connections || [];
|
||
|
|
var ext = conns.filter(function(c) {
|
||
|
|
return c.remote_addr && c.remote_addr !== '0.0.0.0' && c.remote_addr !== '::' && c.remote_addr !== '127.0.0.1';
|
||
|
|
});
|
||
|
|
if (!ext.length) {
|
||
|
|
document.getElementById('ni-geo-table').style.display = 'none';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
document.getElementById('ni-geo-table').style.display = '';
|
||
|
|
var html = '';
|
||
|
|
ext.forEach(function(c) {
|
||
|
|
var geo = c.geo || {};
|
||
|
|
html += '<tr><td>' + escapeHtml(c.remote_addr) + '</td>'
|
||
|
|
+ '<td>' + c.remote_port + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(c.state || '') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(geo.country || '—') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(geo.isp || '—') + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(c.process || '') + '</td></tr>';
|
||
|
|
});
|
||
|
|
document.getElementById('ni-geo-tbody').innerHTML = html;
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
function niConnRate() {
|
||
|
|
var btn = document.getElementById('btn-conn-rate');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/connection-rate', {}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
var html = '<table class="data-table" style="font-size:0.82rem"><tbody>'
|
||
|
|
+ '<tr><td>Current Connections</td><td>' + (data.current_rate || 0) + '</td></tr>'
|
||
|
|
+ '<tr><td>1-Min Average</td><td>' + (data.avg_rate_1m || 0) + '</td></tr>'
|
||
|
|
+ '<tr><td>5-Min Average</td><td>' + (data.avg_rate_5m || 0) + '</td></tr>'
|
||
|
|
+ '<tr><td>Peak</td><td>' + (data.peak_rate || 0) + '</td></tr>'
|
||
|
|
+ '</tbody></table>';
|
||
|
|
document.getElementById('ni-conn-rate').innerHTML = html;
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
function formatBytes(b) {
|
||
|
|
if (b > 1073741824) return (b/1073741824).toFixed(2) + ' GB';
|
||
|
|
if (b > 1048576) return (b/1048576).toFixed(1) + ' MB';
|
||
|
|
if (b > 1024) return (b/1024).toFixed(0) + ' KB';
|
||
|
|
return b + ' B';
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ══════════════ THREATS ══════════════ */
|
||
|
|
function loadThreatReport() {
|
||
|
|
var btn = document.getElementById('btn-threat-report');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/threats', {}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
var lines = [];
|
||
|
|
var ts = data.threat_score || {};
|
||
|
|
lines.push('=== Threat Score: ' + ts.score + '/100 (' + ts.level + ') ===');
|
||
|
|
if (ts.details && ts.details.length) {
|
||
|
|
ts.details.forEach(function(d) { lines.push(' - ' + d); });
|
||
|
|
} else { lines.push(' No active threats detected.'); }
|
||
|
|
lines.push('\n=== Connection Summary ===');
|
||
|
|
lines.push('Active connections: ' + (data.connection_count || 0));
|
||
|
|
if (data.scan_indicators && data.scan_indicators.length) {
|
||
|
|
lines.push('\n=== Port Scan Indicators ===');
|
||
|
|
data.scan_indicators.forEach(function(s) { lines.push(' [' + s.severity + '] ' + s.detail); });
|
||
|
|
}
|
||
|
|
if (data.suspicious_processes && data.suspicious_processes.length) {
|
||
|
|
lines.push('\n=== Suspicious Processes ===');
|
||
|
|
data.suspicious_processes.forEach(function(p) { lines.push(' PID ' + p.pid + ' — ' + p.name + ': ' + p.reason); });
|
||
|
|
}
|
||
|
|
if (data.recent_failed_logins && data.recent_failed_logins.length) {
|
||
|
|
lines.push('\n=== Recent Failed Logins ===');
|
||
|
|
data.recent_failed_logins.forEach(function(l) { lines.push(' ' + l.time + ' — ' + l.ip + ' (' + l.user + ')'); });
|
||
|
|
}
|
||
|
|
if (data.blocklist && data.blocklist.length) {
|
||
|
|
lines.push('\n=== Blocklist (' + data.blocklist.length + ' IPs) ===');
|
||
|
|
data.blocklist.forEach(function(ip) { lines.push(' ' + ip); });
|
||
|
|
}
|
||
|
|
renderOutput('threat-output', lines.join('\n'));
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ══════════════ PACKET CAPTURE ══════════════ */
|
||
|
|
var _capSSE = null;
|
||
|
|
|
||
|
|
function capLoadInterfaces() {
|
||
|
|
fetchJSON('/defense/monitor/capture/interfaces').then(function(data) {
|
||
|
|
var sel = document.getElementById('cap-interface');
|
||
|
|
sel.innerHTML = '<option value="">Auto (default)</option>';
|
||
|
|
(data.interfaces || []).forEach(function(i) {
|
||
|
|
var name = i.name || i;
|
||
|
|
sel.innerHTML += '<option value="' + escapeHtml(name) + '">' + escapeHtml(name) + '</option>';
|
||
|
|
});
|
||
|
|
}).catch(function() {
|
||
|
|
document.getElementById('cap-interface').innerHTML = '<option value="">No interfaces (Scapy not available?)</option>';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function capStart() {
|
||
|
|
var iface = document.getElementById('cap-interface').value;
|
||
|
|
var filter = document.getElementById('cap-filter').value;
|
||
|
|
var duration = document.getElementById('cap-duration').value;
|
||
|
|
|
||
|
|
postJSON('/defense/monitor/capture/start', {
|
||
|
|
interface: iface, filter: filter, duration: parseInt(duration)
|
||
|
|
}).then(function(data) {
|
||
|
|
if (data.error) {
|
||
|
|
document.getElementById('cap-status').textContent = 'Error: ' + data.error;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
document.getElementById('btn-cap-start').style.display = 'none';
|
||
|
|
document.getElementById('btn-cap-stop').style.display = '';
|
||
|
|
document.getElementById('cap-status').textContent = 'Capturing...';
|
||
|
|
document.getElementById('cap-live-packets').style.display = 'block';
|
||
|
|
document.getElementById('cap-live-packets').textContent = '';
|
||
|
|
|
||
|
|
// Start SSE stream
|
||
|
|
_capSSE = new EventSource('/defense/monitor/capture/stream');
|
||
|
|
_capSSE.onmessage = function(e) {
|
||
|
|
try {
|
||
|
|
var d = JSON.parse(e.data);
|
||
|
|
var panel = document.getElementById('cap-live-packets');
|
||
|
|
if (d.type === 'packet') {
|
||
|
|
var line = (d.time||'') + ' ' + (d.src||'?') + ' → ' + (d.dst||'?')
|
||
|
|
+ ' ' + (d.protocol||'?') + ' len:' + (d.length||0)
|
||
|
|
+ (d.info ? ' ' + d.info : '');
|
||
|
|
panel.textContent += line + '\n';
|
||
|
|
panel.scrollTop = panel.scrollHeight;
|
||
|
|
} else if (d.type === 'stats') {
|
||
|
|
document.getElementById('cap-status').textContent = 'Capturing... ' + d.packet_count + ' packets';
|
||
|
|
} else if (d.type === 'done') {
|
||
|
|
capDone(d);
|
||
|
|
}
|
||
|
|
} catch(ex) {}
|
||
|
|
};
|
||
|
|
_capSSE.onerror = function() {
|
||
|
|
capDone({});
|
||
|
|
};
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function capStop() {
|
||
|
|
postJSON('/defense/monitor/capture/stop', {}).then(function() {
|
||
|
|
capDone({});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function capDone(stats) {
|
||
|
|
if (_capSSE) { _capSSE.close(); _capSSE = null; }
|
||
|
|
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' : '');
|
||
|
|
}
|
||
|
|
|
||
|
|
function capProtocols() {
|
||
|
|
postJSON('/defense/monitor/capture/protocols', {}).then(function(data) {
|
||
|
|
var protos = data.protocols || {};
|
||
|
|
var html = '<h4>Protocol Distribution</h4>';
|
||
|
|
if (protos.protocols && Object.keys(protos.protocols).length) {
|
||
|
|
html += '<table class="data-table" style="font-size:0.82rem"><thead><tr><th>Protocol</th><th>Count</th><th>%</th></tr></thead><tbody>';
|
||
|
|
var total = protos.total_packets || 1;
|
||
|
|
var sorted = Object.entries(protos.protocols).sort(function(a,b){return b[1]-a[1]});
|
||
|
|
sorted.forEach(function(p) {
|
||
|
|
html += '<tr><td>' + escapeHtml(p[0]) + '</td><td>' + p[1] + '</td><td>' + ((p[1]/total)*100).toFixed(1) + '%</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
} else {
|
||
|
|
html += '<p class="empty-state">No packet data. Run a capture first.</p>';
|
||
|
|
}
|
||
|
|
document.getElementById('cap-analysis').innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function capConversations() {
|
||
|
|
postJSON('/defense/monitor/capture/conversations', {}).then(function(data) {
|
||
|
|
var convos = data.conversations || [];
|
||
|
|
var html = '<h4>Top Conversations</h4>';
|
||
|
|
if (convos.length) {
|
||
|
|
html += '<table class="data-table" style="font-size:0.82rem"><thead><tr><th>Source</th><th>Destination</th><th>Packets</th><th>Bytes</th></tr></thead><tbody>';
|
||
|
|
convos.slice(0, 20).forEach(function(c) {
|
||
|
|
html += '<tr><td>' + escapeHtml(c.src||'') + '</td><td>' + escapeHtml(c.dst||'') + '</td>'
|
||
|
|
+ '<td>' + (c.packets||0) + '</td><td>' + formatBytes(c.bytes||0) + '</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
} else {
|
||
|
|
html += '<p class="empty-state">No conversation data. Run a capture first.</p>';
|
||
|
|
}
|
||
|
|
document.getElementById('cap-analysis').innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ══════════════ DDOS MITIGATION ══════════════ */
|
||
|
|
function ddosDetect() {
|
||
|
|
var btn = document.getElementById('btn-ddos-detect');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/ddos/detect', {}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
var html = '<div class="stats-grid" style="grid-template-columns:repeat(auto-fit,minmax(130px,1fr))">';
|
||
|
|
html += '<div class="stat-card"><div class="stat-label">Status</div><div class="stat-value small" style="color:'
|
||
|
|
+ (data.under_attack ? 'var(--danger)' : 'var(--success)') + '">'
|
||
|
|
+ (data.under_attack ? 'UNDER ATTACK' : 'Clear') + '</div></div>';
|
||
|
|
html += '<div class="stat-card"><div class="stat-label">Attack Type</div><div class="stat-value small">'
|
||
|
|
+ (data.attack_type || 'none') + '</div></div>';
|
||
|
|
html += '<div class="stat-card"><div class="stat-label">Severity</div><div class="stat-value small">'
|
||
|
|
+ (data.severity || 'LOW') + '</div></div>';
|
||
|
|
html += '<div class="stat-card"><div class="stat-label">Total Conns</div><div class="stat-value small">'
|
||
|
|
+ (data.total_connections || 0) + '</div></div>';
|
||
|
|
html += '<div class="stat-card"><div class="stat-label">SYN Count</div><div class="stat-value small">'
|
||
|
|
+ (data.syn_count || 0) + '</div></div>';
|
||
|
|
html += '</div>';
|
||
|
|
if (data.indicators && data.indicators.length) {
|
||
|
|
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;
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosTopTalkers() {
|
||
|
|
var btn = document.getElementById('btn-ddos-talkers');
|
||
|
|
setLoading(btn, true);
|
||
|
|
postJSON('/defense/monitor/ddos/top-talkers', {limit: 20}).then(function(data) {
|
||
|
|
setLoading(btn, false);
|
||
|
|
var talkers = data.talkers || [];
|
||
|
|
if (!talkers.length) {
|
||
|
|
document.getElementById('ddos-talkers-table').style.display = 'none';
|
||
|
|
document.getElementById('ddos-talkers-empty').innerHTML = '<p class="empty-state">No external connections.</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
document.getElementById('ddos-talkers-table').style.display = '';
|
||
|
|
document.getElementById('ddos-talkers-empty').innerHTML = '';
|
||
|
|
var html = '';
|
||
|
|
talkers.forEach(function(t) {
|
||
|
|
var states = Object.entries(t.state_breakdown || {}).map(function(s){return s[0]+':'+s[1]}).join(', ');
|
||
|
|
html += '<tr><td>' + escapeHtml(t.ip) + '</td>'
|
||
|
|
+ '<td>' + t.connections + '</td>'
|
||
|
|
+ '<td>' + t.percent + '%</td>'
|
||
|
|
+ '<td style="font-size:0.7rem">' + escapeHtml(states) + '</td>'
|
||
|
|
+ '<td>'
|
||
|
|
+ '<button class="btn btn-danger btn-small" style="font-size:0.65rem;padding:2px 6px;margin-right:2px" onclick="counterBlockIP(\'' + escapeHtml(t.ip) + '\')">Block</button>'
|
||
|
|
+ '<button class="btn btn-small" style="font-size:0.65rem;padding:2px 6px" onclick="document.getElementById(\'ddos-rl-ip\').value=\'' + escapeHtml(t.ip) + '\'">Rate Limit</button>'
|
||
|
|
+ '</td></tr>';
|
||
|
|
});
|
||
|
|
document.getElementById('ddos-talkers-tbody').innerHTML = html;
|
||
|
|
}).catch(function() { setLoading(btn, false); });
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosSynCheck() {
|
||
|
|
fetchJSON('/defense/monitor/ddos/syn-status').then(function(data) {
|
||
|
|
var el = document.getElementById('syn-status-text');
|
||
|
|
el.textContent = 'Status: ' + (data.enabled ? 'Enabled' : 'Disabled') + ' (' + data.platform + ')';
|
||
|
|
el.style.color = data.enabled ? 'var(--success)' : 'var(--text-muted)';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosSynEnable() {
|
||
|
|
postJSON('/defense/monitor/ddos/syn-enable', {}).then(function(data) {
|
||
|
|
renderOutput('ddos-rl-result', data.message || data.error);
|
||
|
|
ddosSynCheck();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosSynDisable() {
|
||
|
|
postJSON('/defense/monitor/ddos/syn-disable', {}).then(function(data) {
|
||
|
|
renderOutput('ddos-rl-result', data.message || data.error);
|
||
|
|
ddosSynCheck();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosRateLimit() {
|
||
|
|
var ip = document.getElementById('ddos-rl-ip').value.trim();
|
||
|
|
var rate = document.getElementById('ddos-rl-rate').value.trim() || '25/min';
|
||
|
|
if (!ip) return;
|
||
|
|
postJSON('/defense/monitor/ddos/rate-limit', {ip: ip, rate: rate}).then(function(data) {
|
||
|
|
renderOutput('ddos-rl-result', data.message || data.error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosRateLimitRemove() {
|
||
|
|
var ip = document.getElementById('ddos-rl-ip').value.trim();
|
||
|
|
if (!ip) return;
|
||
|
|
postJSON('/defense/monitor/ddos/rate-limit/remove', {ip: ip}).then(function(data) {
|
||
|
|
renderOutput('ddos-rl-result', data.message || data.error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosLoadConfig() {
|
||
|
|
fetchJSON('/defense/monitor/ddos/config').then(function(data) {
|
||
|
|
document.getElementById('ddos-cfg-enabled').checked = data.enabled || false;
|
||
|
|
document.getElementById('ddos-cfg-block').checked = data.auto_block_top_talkers !== false;
|
||
|
|
document.getElementById('ddos-cfg-syn').checked = data.auto_enable_syn_cookies !== false;
|
||
|
|
document.getElementById('ddos-cfg-conn-thresh').value = data.connection_threshold || 100;
|
||
|
|
document.getElementById('ddos-cfg-syn-thresh').value = data.syn_threshold || 50;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosSaveConfig() {
|
||
|
|
var config = {
|
||
|
|
enabled: document.getElementById('ddos-cfg-enabled').checked,
|
||
|
|
auto_block_top_talkers: document.getElementById('ddos-cfg-block').checked,
|
||
|
|
auto_enable_syn_cookies: document.getElementById('ddos-cfg-syn').checked,
|
||
|
|
connection_threshold: parseInt(document.getElementById('ddos-cfg-conn-thresh').value),
|
||
|
|
syn_threshold: parseInt(document.getElementById('ddos-cfg-syn-thresh').value),
|
||
|
|
};
|
||
|
|
postJSON('/defense/monitor/ddos/config', config).then(function(data) {
|
||
|
|
renderOutput('ddos-auto-result', 'Config saved');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosAutoMitigate() {
|
||
|
|
postJSON('/defense/monitor/ddos/auto-mitigate', {}).then(function(data) {
|
||
|
|
if (data.message) {
|
||
|
|
renderOutput('ddos-auto-result', data.message);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var lines = ['Auto-mitigation results:'];
|
||
|
|
(data.actions || []).forEach(function(a) {
|
||
|
|
lines.push(' ' + (a.success ? '[OK]' : '[FAIL]') + ' ' + a.action + ' — ' + a.target + ': ' + a.message);
|
||
|
|
});
|
||
|
|
if (!data.actions || !data.actions.length) lines.push(' No actions taken.');
|
||
|
|
renderOutput('ddos-auto-result', lines.join('\n'));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosHistory() {
|
||
|
|
fetchJSON('/defense/monitor/ddos/history').then(function(data) {
|
||
|
|
var history = data.history || [];
|
||
|
|
if (!history.length) {
|
||
|
|
document.getElementById('ddos-history').innerHTML = '<p class="empty-state">No mitigation history.</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var html = '<table class="data-table" style="font-size:0.8rem"><thead><tr>'
|
||
|
|
+ '<th>Time</th><th>Action</th><th>Target</th><th>Reason</th></tr></thead><tbody>';
|
||
|
|
history.slice(-30).reverse().forEach(function(h) {
|
||
|
|
html += '<tr><td>' + escapeHtml(h.timestamp.substring(0,19).replace('T',' ')) + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(h.action) + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(h.target) + '</td>'
|
||
|
|
+ '<td>' + escapeHtml(h.reason) + '</td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
document.getElementById('ddos-history').innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function ddosClearHistory() {
|
||
|
|
if (!confirm('Clear all mitigation history?')) return;
|
||
|
|
postJSON('/defense/monitor/ddos/history/clear', {}).then(function() {
|
||
|
|
document.getElementById('ddos-history').innerHTML = '<p class="empty-state">History cleared.</p>';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ══════════════ COUNTER-ATTACK ══════════════ */
|
||
|
|
function counterBlockIP(ip) {
|
||
|
|
if (!ip) ip = document.getElementById('counter-block-ip').value.trim();
|
||
|
|
if (!ip) return;
|
||
|
|
postJSON('/defense/monitor/block-ip', {ip: ip}).then(function(data) {
|
||
|
|
renderOutput('counter-result', data.message || data.error);
|
||
|
|
if (data.success) document.getElementById('counter-block-ip').value = '';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function counterKillProcess() {
|
||
|
|
var pid = document.getElementById('counter-kill-pid').value.trim();
|
||
|
|
if (!pid) return;
|
||
|
|
postJSON('/defense/monitor/kill-process', {pid: parseInt(pid)}).then(function(data) {
|
||
|
|
renderOutput('counter-result', data.message || data.error);
|
||
|
|
if (data.success) document.getElementById('counter-kill-pid').value = '';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function counterBlockPort() {
|
||
|
|
var port = document.getElementById('counter-block-port').value.trim();
|
||
|
|
var dir = document.getElementById('counter-block-dir').value;
|
||
|
|
if (!port) return;
|
||
|
|
postJSON('/defense/monitor/block-port', {port: parseInt(port), direction: dir}).then(function(data) {
|
||
|
|
renderOutput('counter-result', data.message || data.error);
|
||
|
|
if (data.success) document.getElementById('counter-block-port').value = '';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadBlocklist() {
|
||
|
|
fetchJSON('/defense/monitor/blocklist').then(function(data) {
|
||
|
|
var ips = data.blocked_ips || [];
|
||
|
|
var container = document.getElementById('blocklist-container');
|
||
|
|
if (!ips.length) {
|
||
|
|
container.innerHTML = '<p class="empty-state">Blocklist is empty.</p>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var html = '<table class="data-table" style="font-size:0.8rem"><thead><tr><th>IP Address</th><th>Action</th></tr></thead><tbody>';
|
||
|
|
ips.forEach(function(ip) {
|
||
|
|
html += '<tr><td>' + escapeHtml(ip) + '</td>'
|
||
|
|
+ '<td><button class="btn btn-small" style="font-size:0.65rem;padding:2px 6px" '
|
||
|
|
+ 'onclick="removeFromBlocklist(\'' + escapeHtml(ip) + '\')">Remove</button></td></tr>';
|
||
|
|
});
|
||
|
|
html += '</tbody></table>';
|
||
|
|
container.innerHTML = html;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function removeFromBlocklist(ip) {
|
||
|
|
postJSON('/defense/monitor/blocklist/remove', {ip: ip}).then(function(data) {
|
||
|
|
if (data.success) loadBlocklist();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ══════════════ INIT ══════════════ */
|
||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
||
|
|
ddosSynCheck();
|
||
|
|
ddosLoadConfig();
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
{% endblock %}
|