Download dashboard/app.py from theghostcmd/abcj: direct link, hf CLI and curl.
- Browser
- Download file 26.3 kB
-
https://huggingface.co/theghostcmd/abcj/resolve/main/dashboard/app.py
- Command line
-
hf download hf://theghostcmd/abcj/dashboard/app.py
-
curl -L -o app.py https://huggingface.co/theghostcmd/abcj/resolve/main/dashboard/app.py
26.3 kB
| from flask import Flask, render_template_string, jsonify, request, send_file | |
| import sys | |
| import os | |
| import io | |
| from datetime import datetime | |
| from collections import Counter | |
| from reportlab.lib.pagesizes import letter | |
| from reportlab.pdfgen import canvas | |
| from reportlab.lib.utils import ImageReader | |
| from reportlab.lib.units import inch | |
| from scapy.utils import wrpcap | |
| import tempfile | |
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from database.db import Database | |
| from response.block_ip import block_ip_windows, unblock_ip_windows | |
| from geoip.blocker import GeoIPBlocker | |
| app = Flask(__name__) | |
| db = None | |
| pcap_sniffer = None | |
| geoip = None | |
| geoip_available = False | |
| # ---------- PDF Helpers ---------- | |
| def add_page_number(c, page_num): | |
| c.saveState() | |
| c.setFont("Helvetica", 8) | |
| c.drawString(letter[0] - 80, 30, f"Page {page_num}") | |
| c.restoreState() | |
| def draw_header(c, title, subtitle=""): | |
| c.setFont("Helvetica-Bold", 16) | |
| c.drawString(50, letter[1] - 50, title) | |
| c.setFont("Helvetica", 10) | |
| c.drawString(50, letter[1] - 70, subtitle) | |
| c.line(50, letter[1] - 80, letter[0] - 50, letter[1] - 80) | |
| def draw_watermark(c, logo_path): | |
| if os.path.exists(logo_path): | |
| try: | |
| img = ImageReader(logo_path) | |
| c.saveState() | |
| c.setFillAlpha(0.2) | |
| c.drawImage(img, letter[0]/2 - 1.5*inch, letter[1]/2 - 1.5*inch, | |
| width=3*inch, height=3*inch, mask='auto', preserveAspectRatio=True) | |
| c.restoreState() | |
| except: | |
| pass | |
| def generate_blocked_ips_pdf(): | |
| blocked = db.get_blocked_ips() | |
| buffer = io.BytesIO() | |
| c = canvas.Canvas(buffer, pagesize=letter) | |
| logo_path = os.path.join(os.path.dirname(__file__), 'static', 'logo.png') | |
| page_num = 1 | |
| draw_watermark(c, logo_path) | |
| draw_header(c, "MayOne Security Framework", "Blocked IP Addresses Report") | |
| y = letter[1] - 110 | |
| c.drawString(50, y, f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| y -= 20 | |
| c.drawString(50, y, f"Total Blocked IPs: {len(blocked)}") | |
| y -= 40 | |
| c.setFont("Helvetica-Bold", 10) | |
| c.drawString(50, y, "IP Address") | |
| c.drawString(160, y, "Blocked Since") | |
| c.drawString(300, y, "Reason") | |
| c.setFont("Helvetica", 10) | |
| y -= 20 | |
| for ip, block_time, reason in blocked: | |
| if y < 80: | |
| draw_watermark(c, logo_path) | |
| add_page_number(c, page_num) | |
| c.showPage() | |
| page_num += 1 | |
| draw_header(c, "MayOne Security Framework (cont.)", "Blocked IPs") | |
| y = letter[1] - 110 | |
| c.setFont("Helvetica-Bold", 10) | |
| c.drawString(50, y, "IP Address") | |
| c.drawString(160, y, "Blocked Since") | |
| c.drawString(300, y, "Reason") | |
| c.setFont("Helvetica", 10) | |
| y -= 20 | |
| reason_short = reason[:60] + "..." if len(reason) > 60 else reason | |
| c.drawString(50, y, ip) | |
| c.drawString(160, y, block_time[:19]) | |
| c.drawString(300, y, reason_short) | |
| y -= 20 | |
| add_page_number(c, page_num) | |
| c.save() | |
| buffer.seek(0) | |
| return buffer | |
| def generate_all_ips_pdf(): | |
| events = db.get_recent_events(limit=1000) | |
| buffer = io.BytesIO() | |
| c = canvas.Canvas(buffer, pagesize=letter) | |
| logo_path = os.path.join(os.path.dirname(__file__), 'static', 'logo.png') | |
| page_num = 1 | |
| draw_watermark(c, logo_path) | |
| draw_header(c, "MayOne Security Framework", "Complete IP Traffic Log") | |
| y = letter[1] - 110 | |
| c.drawString(50, y, f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| y -= 20 | |
| c.drawString(50, y, f"Total Events: {len(events)}") | |
| y -= 40 | |
| headers = ["Timestamp", "Src IP", "Dst IP", "Proto", "Port", "Size", "Threat", "Risk"] | |
| col_widths = [100, 80, 80, 40, 40, 50, 70, 40] | |
| c.setFont("Helvetica-Bold", 8) | |
| x = 50 | |
| for i, h in enumerate(headers): | |
| c.drawString(x, y, h) | |
| x += col_widths[i] | |
| c.setFont("Helvetica", 8) | |
| y -= 15 | |
| for ev in events: | |
| if y < 80: | |
| draw_watermark(c, logo_path) | |
| add_page_number(c, page_num) | |
| c.showPage() | |
| page_num += 1 | |
| draw_header(c, "MayOne Security Framework (cont.)", "IP Traffic Log") | |
| y = letter[1] - 110 | |
| c.setFont("Helvetica-Bold", 8) | |
| x = 50 | |
| for i, h in enumerate(headers): | |
| c.drawString(x, y, h) | |
| x += col_widths[i] | |
| c.setFont("Helvetica", 8) | |
| y -= 15 | |
| ts = ev[1][:19] if len(ev[1]) > 19 else ev[1] | |
| src = ev[2][:15] | |
| dst = ev[3][:15] | |
| proto = ev[4][:4] | |
| port = str(ev[5]) if ev[5] else "" | |
| size = str(ev[6]) | |
| threat = ev[7] or "Normal" | |
| risk = str(ev[8]) if ev[8] else "0" | |
| x = 50 | |
| c.drawString(x, y, ts); x += col_widths[0] | |
| c.drawString(x, y, src); x += col_widths[1] | |
| c.drawString(x, y, dst); x += col_widths[2] | |
| c.drawString(x, y, proto); x += col_widths[3] | |
| c.drawString(x, y, port); x += col_widths[4] | |
| c.drawString(x, y, size); x += col_widths[5] | |
| c.drawString(x, y, threat); x += col_widths[6] | |
| c.drawString(x, y, risk) | |
| y -= 12 | |
| add_page_number(c, page_num) | |
| c.save() | |
| buffer.seek(0) | |
| return buffer | |
| # ---------- HTML Template (with GeoIP warning and conditional toggle) ---------- | |
| HTML_TEMPLATE = ''' | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>MayOne Security Framework</title> | |
| <meta charset="UTF-8"> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> | |
| <style> | |
| * { box-sizing: border-box; } | |
| body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; background: #0a0f1e; color: #eef4ff; padding: 20px; } | |
| .container { max-width: 1400px; margin: 0 auto; } | |
| .header { display: flex; align-items: center; gap: 20px; margin-bottom: 30px; border-bottom: 2px solid #2a3f6e; padding-bottom: 15px; } | |
| .logo { height: 60px; width: auto; } | |
| h1 { margin: 0; font-size: 2rem; background: linear-gradient(135deg, #ff6b6b, #4ecdc4); -webkit-background-clip: text; background-clip: text; color: transparent; } | |
| .subtitle { color: #8e9aaf; margin-top: 5px; } | |
| .card { background: #141b2b; border-radius: 16px; padding: 20px; margin-bottom: 25px; box-shadow: 0 8px 20px rgba(0,0,0,0.3); border: 1px solid #2a3f6e; } | |
| .card h2 { margin-top: 0; color: #4ecdc4; font-size: 1.5rem; } | |
| .stats-grid { display: flex; gap: 20px; flex-wrap: wrap; } | |
| .stat-box { background: #0f172a; padding: 15px 25px; border-radius: 12px; flex: 1; min-width: 150px; text-align: center; border-left: 4px solid #4ecdc4; } | |
| .stat-number { font-size: 2rem; font-weight: bold; color: #ffd966; } | |
| .chart-container { display: flex; gap: 20px; flex-wrap: wrap; } | |
| .chart-box { flex: 1; min-width: 250px; background: #0f172a; border-radius: 12px; padding: 15px; } | |
| canvas { max-height: 300px; width: 100%; } | |
| table { width: 100%; border-collapse: collapse; } | |
| th, td { text-align: left; padding: 12px; border-bottom: 1px solid #2a3f6e; } | |
| th { background: #1e2a3a; color: #4ecdc4; } | |
| .critical { color: #ff6b6b; font-weight: bold; } | |
| .high { color: #ffa64d; } | |
| .medium { color: #ffd966; } | |
| .form-group { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; } | |
| .form-field { flex: 1; } | |
| label { display: block; font-size: 0.8rem; margin-bottom: 5px; color: #8e9aaf; } | |
| input, textarea { width: 100%; padding: 10px; background: #0f172a; border: 1px solid #2a3f6e; border-radius: 8px; color: #eef4ff; font-size: 0.9rem; } | |
| button { background: #4ecdc4; color: #0a0f1e; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-weight: bold; transition: 0.2s; } | |
| button.danger { background: #ff6b6b; } | |
| button.secondary { background: #2a3f6e; color: white; } | |
| button:hover { opacity: 0.85; transform: translateY(-2px); } | |
| .alert { padding: 10px; border-radius: 8px; margin-bottom: 15px; display: none; } | |
| .alert-success { background: #2e7d64; color: white; } | |
| .alert-error { background: #b91c1c; color: white; } | |
| .toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; flex-wrap: wrap; gap: 10px; } | |
| .warning { background: #ffaa44; color: #0a0f1e; padding: 10px 15px; border-radius: 8px; margin-bottom: 20px; font-weight: bold; } | |
| footer { text-align: center; margin-top: 30px; font-size: 0.8rem; color: #5a6e8a; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <img src="/static/logo.png" alt="MayOne Logo" class="logo" onerror="this.style.display='none'"> | |
| <div> | |
| <h1>🛡️ MayOne Security Framework</h1> | |
| <div class="subtitle">AI‑powered Intrusion Detection & Response</div> | |
| </div> | |
| </div> | |
| <div id="alertBox" class="alert"></div> | |
| <div id="geoipWarning" class="warning" style="display: none;"> | |
| ⚠️ GeoIP database not found. Download GeoLite2-Country.mmdb and place it in the 'geoip/' folder to enable country lookup and automatic blocking. | |
| </div> | |
| <div class="card"> | |
| <h2>📊 Live Statistics</h2> | |
| <div class="stats-grid"> | |
| <div class="stat-box"><div class="stat-number" id="totalEvents">0</div><div>Total Events</div></div> | |
| <div class="stat-box"><div class="stat-number" id="uniqueSrc">0</div><div>Unique Sources</div></div> | |
| <div class="stat-box"><div class="stat-number" id="blockedCount">0</div><div>Blocked IPs</div></div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>📡 Traffic Analysis</h2> | |
| <div class="chart-container"> | |
| <div class="chart-box"><h3>Protocol Distribution</h3><canvas id="protocolChart"></canvas></div> | |
| <div class="chart-box"><h3>Top 5 Ports</h3><canvas id="portChart"></canvas></div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>🚨 Recent Threats</h2> | |
| <div style="overflow-x: auto;"> | |
| <table id="threatsTable"> | |
| <thead> | |
| <tr> | |
| <th>Time</th> | |
| <th>Source IP</th> | |
| <th>Country</th> | |
| <th>Protocol</th> | |
| <th>Dest Port</th> | |
| <th>Threat Type</th> | |
| <th>Risk</th> | |
| <th>Action</th> | |
| </tr> | |
| </thead> | |
| <tbody id="threatsBody"> | |
| <tr><td colspan="8">Loading...</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <div class="toolbar"> | |
| <h2>🚫 Blocked IPs & Manual Control</h2> | |
| <div style="display: flex; gap: 15px; align-items: center;"> | |
| <label id="geoipLabel" style="display: inline-flex; align-items: center; gap: 8px; background: #1e2a3a; padding: 5px 12px; border-radius: 20px;"> | |
| 🌍 GeoIP Blocking | |
| <input type="checkbox" id="geoipToggle" style="width: 18px; height: 18px; margin: 0;"> | |
| </label> | |
| <button id="downloadBlockedPdfBtn" class="secondary">📄 Blocked IPs (PDF)</button> | |
| <button id="downloadAllPdfBtn" class="secondary">📊 All IP Logs (PDF)</button> | |
| <button id="downloadPcapBtn" class="secondary">📦 Download PCAP</button> | |
| </div> | |
| </div> | |
| <div class="form-group" style="margin-bottom: 25px;"> | |
| <div class="form-field"><label>IP Address to Block</label><input type="text" id="blockIp" placeholder="e.g., 203.0.113.5"></div> | |
| <div class="form-field"><label>Reason (optional)</label><input type="text" id="blockReason" placeholder="Manual block"></div> | |
| <button id="blockBtn">➕ Block IP</button> | |
| </div> | |
| <div style="overflow-x: auto;"> | |
| <table id="blockedTable"> | |
| <thead> | |
| <tr><th>IP Address</th><th>Blocked Since</th><th>Reason</th><th>Action</th></tr> | |
| </thead> | |
| <tbody id="blockedBody"> | |
| <tr><td colspan="4">Loading...</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <footer>MayOne Security Framework — Real‑time monitoring | Auto‑refresh every 3s</footer> | |
| </div> | |
| <script> | |
| let protocolChart, portChart; | |
| let geoipAvailable = {{ geoip_available|tojson }}; | |
| if (!geoipAvailable) { | |
| document.getElementById('geoipWarning').style.display = 'block'; | |
| document.getElementById('geoipToggle').disabled = true; | |
| document.getElementById('geoipLabel').style.opacity = '0.6'; | |
| } | |
| function showAlert(message, type) { | |
| const alertDiv = document.getElementById('alertBox'); | |
| alertDiv.textContent = message; | |
| alertDiv.className = `alert alert-${type}`; | |
| alertDiv.style.display = 'block'; | |
| setTimeout(() => { alertDiv.style.display = 'none'; }, 4000); | |
| } | |
| async function fetchStats() { | |
| try { | |
| const res = await fetch('/api/stats'); | |
| const data = await res.json(); | |
| document.getElementById('totalEvents').innerText = data.total_events; | |
| document.getElementById('uniqueSrc').innerText = data.unique_src; | |
| document.getElementById('blockedCount').innerText = data.blocked_count; | |
| } catch(e) { console.error('Stats error', e); } | |
| } | |
| async function fetchTrafficStats() { | |
| try { | |
| const res = await fetch('/api/traffic_stats'); | |
| const data = await res.json(); | |
| if (protocolChart) protocolChart.destroy(); | |
| const protoCtx = document.getElementById('protocolChart').getContext('2d'); | |
| protocolChart = new Chart(protoCtx, { | |
| type: 'pie', | |
| data: { labels: Object.keys(data.protocols), datasets: [{ data: Object.values(data.protocols), backgroundColor: ['#4ecdc4', '#ff6b6b', '#ffd966', '#a78bfa'] }] }, | |
| options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { labels: { color: '#eef4ff' } } } } | |
| }); | |
| if (portChart) portChart.destroy(); | |
| const portCtx = document.getElementById('portChart').getContext('2d'); | |
| const portLabels = Object.keys(data.top_ports); | |
| const portValues = Object.values(data.top_ports); | |
| portChart = new Chart(portCtx, { | |
| type: 'bar', | |
| data: { labels: portLabels, datasets: [{ label: 'Packets', data: portValues, backgroundColor: '#4ecdc4' }] }, | |
| options: { responsive: true, maintainAspectRatio: true, scales: { y: { beginAtZero: true, ticks: { color: '#eef4ff' } }, x: { ticks: { color: '#eef4ff' } } }, plugins: { legend: { labels: { color: '#eef4ff' } } } } | |
| }); | |
| } catch(e) { console.error('Traffic stats error', e); } | |
| } | |
| async function fetchThreats() { | |
| try { | |
| const res = await fetch('/api/threats'); | |
| const threats = await res.json(); | |
| const tbody = document.getElementById('threatsBody'); | |
| if(threats.length === 0) { tbody.innerHTML = '<tr><td colspan="8">No threats detected</td></tr>'; return; } | |
| tbody.innerHTML = threats.map(t => ` | |
| <tr> | |
| <td>${t.time.slice(0,19)}</td> | |
| <td>${t.src_ip}</td> | |
| <td>${t.country || '-'}</td> | |
| <td>${t.protocol || '-'}</td> | |
| <td>${t.port || '-'}</td> | |
| <td>${t.threat_type || 'ANOMALY'}</td> | |
| <td class="${t.risk >= 80 ? 'critical' : (t.risk >= 60 ? 'high' : (t.risk >= 30 ? 'medium' : ''))}">${t.risk}</td> | |
| <td>${t.action || '-'}</td> | |
| </tr> | |
| `).join(''); | |
| } catch(e) { console.error('Threats error', e); } | |
| } | |
| async function fetchBlockedIPs() { | |
| try { | |
| const res = await fetch('/api/blocked_ips'); | |
| const blocked = await res.json(); | |
| const tbody = document.getElementById('blockedBody'); | |
| if(blocked.length === 0) { tbody.innerHTML = '<tr><td colspan="4">No IPs blocked</td></tr>'; return; } | |
| tbody.innerHTML = blocked.map(b => ` | |
| <tr> | |
| <td>${b.ip}</td> | |
| <td>${b.time.slice(0,19)}</td> | |
| <td>${b.reason}</td> | |
| <td><button class="danger" onclick="unblockIP('${b.ip}')">Unblock</button></td> | |
| </tr> | |
| `).join(''); | |
| } catch(e) { console.error('Blocked IPs error', e); } | |
| } | |
| async function fetchGeoIPStatus() { | |
| if (!geoipAvailable) return; | |
| try { | |
| const res = await fetch('/api/geoip_status'); | |
| const data = await res.json(); | |
| document.getElementById('geoipToggle').checked = data.enabled; | |
| } catch(e) { console.error('GeoIP status error', e); } | |
| } | |
| async function blockIP() { | |
| const ip = document.getElementById('blockIp').value.trim(); | |
| if(!ip) { showAlert('Please enter an IP address', 'error'); return; } | |
| const reason = document.getElementById('blockReason').value.trim() || 'Manual block from dashboard'; | |
| try { | |
| const res = await fetch('/api/block', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ip: ip, reason: reason}) }); | |
| const data = await res.json(); | |
| if(data.success) { | |
| showAlert(`Blocked ${ip} successfully`, 'success'); | |
| document.getElementById('blockIp').value = ''; | |
| document.getElementById('blockReason').value = ''; | |
| fetchBlockedIPs(); fetchStats(); | |
| } else { showAlert(`Failed: ${data.error}`, 'error'); } | |
| } catch(e) { showAlert('Network error', 'error'); } | |
| } | |
| window.unblockIP = async function(ip) { | |
| if(!confirm(`Unblock ${ip}?`)) return; | |
| try { | |
| const res = await fetch('/api/unblock', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ip: ip}) }); | |
| const data = await res.json(); | |
| if(data.success) { | |
| showAlert(`Unblocked ${ip}`, 'success'); | |
| fetchBlockedIPs(); fetchStats(); | |
| } else { showAlert(`Failed: ${data.error}`, 'error'); } | |
| } catch(e) { showAlert('Network error', 'error'); } | |
| }; | |
| document.getElementById('downloadBlockedPdfBtn').addEventListener('click', () => window.location.href = '/api/download_blocked_ips_pdf'); | |
| document.getElementById('downloadAllPdfBtn').addEventListener('click', () => window.location.href = '/api/download_all_ips_pdf'); | |
| document.getElementById('downloadPcapBtn').addEventListener('click', () => window.location.href = '/api/download_pcap'); | |
| if (geoipAvailable) { | |
| document.getElementById('geoipToggle').addEventListener('change', async (e) => { | |
| const enabled = e.target.checked; | |
| try { | |
| const res = await fetch('/api/geoip_toggle', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({enabled: enabled}) }); | |
| const data = await res.json(); | |
| if(data.success) showAlert(`GeoIP blocking ${enabled ? 'enabled' : 'disabled'}`, 'success'); | |
| else showAlert('Failed to toggle GeoIP', 'error'); | |
| } catch(e) { showAlert('Network error', 'error'); } | |
| }); | |
| } | |
| function refreshAll() { | |
| fetchStats(); | |
| fetchTrafficStats(); | |
| fetchThreats(); | |
| fetchBlockedIPs(); | |
| if (geoipAvailable) fetchGeoIPStatus(); | |
| } | |
| setInterval(refreshAll, 3000); | |
| refreshAll(); | |
| document.getElementById('blockBtn').addEventListener('click', blockIP); | |
| </script> | |
| </body> | |
| </html> | |
| ''' | |
| # ---------- Flask Routes ---------- | |
| def dashboard(): | |
| return render_template_string(HTML_TEMPLATE, geoip_available=geoip_available) | |
| def api_stats(): | |
| total_events = db.get_total_event_count() | |
| conn, cursor = db._get_connection() | |
| cursor.execute('SELECT DISTINCT src_ip FROM events') | |
| unique_src = len(cursor.fetchall()) | |
| blocked = db.get_blocked_ips() | |
| return jsonify({ | |
| 'total_events': total_events, | |
| 'unique_src': unique_src, | |
| 'blocked_count': len(blocked) | |
| }) | |
| def api_traffic_stats(): | |
| events = db.get_recent_events(limit=2000) | |
| protocol_counter = Counter() | |
| port_counter = Counter() | |
| for ev in events: | |
| proto = ev[4] | |
| port = ev[5] | |
| protocol_counter[proto] += 1 | |
| if port: | |
| port_counter[port] += 1 | |
| top_ports = dict(port_counter.most_common(5)) | |
| return jsonify({ | |
| 'protocols': dict(protocol_counter), | |
| 'top_ports': top_ports | |
| }) | |
| def api_threats(): | |
| events = db.get_recent_events(500) | |
| threats = [] | |
| for e in events: | |
| if e[7] or e[8] > 0: | |
| src_ip = e[2] | |
| country = None | |
| if geoip and geoip.reader: | |
| country = geoip.get_country_code(src_ip) | |
| threats.append({ | |
| 'time': e[1], | |
| 'src_ip': src_ip, | |
| 'country': country, | |
| 'protocol': e[4], | |
| 'port': e[5] if e[5] else None, | |
| 'threat_type': e[7] or 'MONITORED', | |
| 'risk': e[8], | |
| 'action': e[9] | |
| }) | |
| return jsonify(threats[:100]) | |
| def api_blocked_ips(): | |
| blocked = db.get_blocked_ips() | |
| return jsonify([{'ip': b[0], 'time': b[1], 'reason': b[2]} for b in blocked]) | |
| def api_block(): | |
| data = request.get_json() | |
| ip = data.get('ip', '').strip() | |
| reason = data.get('reason', 'Manual block from dashboard') | |
| if not ip: | |
| return jsonify({'success': False, 'error': 'IP required'}), 400 | |
| success = block_ip_windows(ip, reason) | |
| if success: | |
| db.insert_blocked_ip(ip, reason) | |
| return jsonify({'success': True}) | |
| else: | |
| return jsonify({'success': False, 'error': 'Firewall rule failed'}), 500 | |
| def api_unblock(): | |
| data = request.get_json() | |
| ip = data.get('ip', '').strip() | |
| if not ip: | |
| return jsonify({'success': False, 'error': 'IP required'}), 400 | |
| success = unblock_ip_windows(ip) | |
| if success: | |
| conn, cursor = db._get_connection() | |
| cursor.execute('DELETE FROM blocked_ips WHERE ip = ?', (ip,)) | |
| conn.commit() | |
| return jsonify({'success': True}) | |
| else: | |
| return jsonify({'success': False, 'error': 'Unblock failed'}), 500 | |
| def download_blocked_ips_pdf(): | |
| pdf_buffer = generate_blocked_ips_pdf() | |
| return send_file(pdf_buffer, as_attachment=True, download_name='blocked_ips_log.pdf', mimetype='application/pdf') | |
| def download_all_ips_pdf(): | |
| pdf_buffer = generate_all_ips_pdf() | |
| return send_file(pdf_buffer, as_attachment=True, download_name='all_ip_traffic_log.pdf', mimetype='application/pdf') | |
| def download_pcap(): | |
| if not pcap_sniffer: | |
| return jsonify({'error': 'PCAP capture not available'}), 500 | |
| packets = pcap_sniffer.get_pcap_buffer() | |
| if not packets: | |
| return jsonify({'error': 'No packets captured yet'}), 404 | |
| try: | |
| with tempfile.NamedTemporaryFile(delete=False, suffix='.pcap') as tmp: | |
| wrpcap(tmp.name, packets) | |
| tmp_path = tmp.name | |
| with open(tmp_path, 'rb') as f: | |
| pcap_data = f.read() | |
| os.unlink(tmp_path) | |
| return send_file(io.BytesIO(pcap_data), as_attachment=True, download_name='capture.pcap', mimetype='application/vnd.tcpdump.pcap') | |
| except Exception as e: | |
| app.logger.error(f"PCAP export failed: {e}") | |
| return jsonify({'error': str(e)}), 500 | |
| def geoip_status(): | |
| import main | |
| return jsonify({'enabled': getattr(main, 'geoip_enabled', False)}) | |
| def geoip_toggle(): | |
| import main | |
| data = request.get_json() | |
| main.geoip_enabled = data.get('enabled', False) | |
| return jsonify({'success': True}) | |
| def serve_static(filename): | |
| from flask import send_from_directory | |
| static_dir = os.path.join(os.path.dirname(__file__), 'static') | |
| return send_from_directory(static_dir, filename) | |
| # ---------- Startup ---------- | |
| def run_dashboard(host='127.0.0.1', port=5000, sniffer=None): | |
| global db, pcap_sniffer, geoip, geoip_available | |
| db = Database() | |
| pcap_sniffer = sniffer | |
| geoip = GeoIPBlocker() | |
| geoip_available = geoip.reader is not None | |
| if not geoip_available: | |
| print("[Dashboard] GeoIP database not found. Country lookup and blocking disabled.") | |
| static_folder = os.path.join(os.path.dirname(__file__), 'static') | |
| os.makedirs(static_folder, exist_ok=True) | |
| logo_path = os.path.join(static_folder, 'logo.png') | |
| if not os.path.exists(logo_path): | |
| print("[Dashboard] No logo.png found in dashboard/static/. Please add your logo for watermark.") | |
| app.run(host=host, port=port, debug=False, use_reloader=False) |