| 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
|
|
|
|
|
| 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 = '''
|
| <!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>
|
| '''
|
|
|
|
|
| @app.route('/')
|
| def dashboard():
|
| return render_template_string(HTML_TEMPLATE, geoip_available=geoip_available)
|
|
|
| @app.route('/api/stats')
|
| 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)
|
| })
|
|
|
| @app.route('/api/traffic_stats')
|
| 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
|
| })
|
|
|
| @app.route('/api/threats')
|
| 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])
|
|
|
| @app.route('/api/blocked_ips')
|
| def api_blocked_ips():
|
| blocked = db.get_blocked_ips()
|
| return jsonify([{'ip': b[0], 'time': b[1], 'reason': b[2]} for b in blocked])
|
|
|
| @app.route('/api/block', methods=['POST'])
|
| 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
|
|
|
| @app.route('/api/unblock', methods=['POST'])
|
| 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
|
|
|
| @app.route('/api/download_blocked_ips_pdf')
|
| 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')
|
|
|
| @app.route('/api/download_all_ips_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')
|
|
|
| @app.route('/api/download_pcap')
|
| 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
|
|
|
| @app.route('/api/geoip_status')
|
| def geoip_status():
|
| import main
|
| return jsonify({'enabled': getattr(main, 'geoip_enabled', False)})
|
|
|
| @app.route('/api/geoip_toggle', methods=['POST'])
|
| def geoip_toggle():
|
| import main
|
| data = request.get_json()
|
| main.geoip_enabled = data.get('enabled', False)
|
| return jsonify({'success': True})
|
|
|
| @app.route('/static/<path:filename>')
|
| 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)
|
|
|
|
|
| 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) |