show url requests
This commit is contained in:
@@ -1331,6 +1331,10 @@ class LiveStatsTracker:
|
||||
self.blocked_paths: Dict[str, List[float]] = {} # Path -> [blocked timestamps]
|
||||
self.ip_404s: Dict[str, List[float]] = {} # IP -> [404 timestamps]
|
||||
|
||||
# IP Request Details (für IP-Detail Popup)
|
||||
self.ip_request_details: Dict[str, List[Dict]] = {} # IP -> [{ts, path, ua}, ...]
|
||||
self.max_request_details = 100 # Max Anzahl Details pro IP
|
||||
|
||||
# Human/Bot Counters
|
||||
self.human_requests: List[float] = []
|
||||
self.bot_requests: List[float] = []
|
||||
@@ -1353,7 +1357,13 @@ class LiveStatsTracker:
|
||||
self.human_requests = [t for t in self.human_requests if t > cutoff]
|
||||
self.bot_requests = [t for t in self.bot_requests if t > cutoff]
|
||||
|
||||
def record_request(self, ip: str, path: str, is_bot: bool, is_blocked: bool = False, is_404: bool = False):
|
||||
# Request Details cleanup
|
||||
for ip in list(self.ip_request_details.keys()):
|
||||
self.ip_request_details[ip] = [r for r in self.ip_request_details[ip] if r['ts'] > cutoff]
|
||||
if not self.ip_request_details[ip]:
|
||||
del self.ip_request_details[ip]
|
||||
|
||||
def record_request(self, ip: str, path: str, is_bot: bool, is_blocked: bool = False, is_404: bool = False, user_agent: str = ''):
|
||||
"""Zeichnet einen Request auf."""
|
||||
now = time.time()
|
||||
|
||||
@@ -1389,6 +1399,30 @@ class LiveStatsTracker:
|
||||
if ip not in self.ip_info:
|
||||
self.ip_info[ip] = get_ip_info(ip)
|
||||
|
||||
# Request Details speichern (für IP-Detail Popup)
|
||||
if ip not in self.ip_request_details:
|
||||
self.ip_request_details[ip] = []
|
||||
self.ip_request_details[ip].append({
|
||||
'ts': now,
|
||||
'path': path,
|
||||
'ua': user_agent,
|
||||
'blocked': is_blocked
|
||||
})
|
||||
# Limitieren auf max_request_details
|
||||
if len(self.ip_request_details[ip]) > self.max_request_details:
|
||||
self.ip_request_details[ip] = self.ip_request_details[ip][-self.max_request_details:]
|
||||
|
||||
def get_ip_requests(self, ip: str, limit: int = 0) -> List[Dict]:
|
||||
"""Gibt die Requests einer IP zurück. limit=0 für alle."""
|
||||
self.cleanup_old_data()
|
||||
if ip not in self.ip_request_details:
|
||||
return []
|
||||
# Neueste zuerst
|
||||
all_requests = list(reversed(self.ip_request_details[ip]))
|
||||
if limit > 0:
|
||||
return all_requests[:limit]
|
||||
return all_requests
|
||||
|
||||
def get_top_ips(self, limit: int = 10) -> List[Dict[str, Any]]:
|
||||
"""Gibt Top IPs mit Zusatzinfos zurück."""
|
||||
self.cleanup_old_data()
|
||||
@@ -3858,6 +3892,13 @@ class JTLWAFiAgent:
|
||||
if 'Path: ' in line:
|
||||
path = line.split('Path: ')[1].split(' |')[0].strip()
|
||||
|
||||
# User-Agent extrahieren
|
||||
user_agent = ''
|
||||
if 'UA: ' in line:
|
||||
user_agent = line.split('UA: ')[1].split(' |')[0].strip()
|
||||
elif 'User-Agent: ' in line:
|
||||
user_agent = line.split('User-Agent: ')[1].split(' |')[0].strip()
|
||||
|
||||
# Bot/Human erkennen
|
||||
is_bot = any(x in line for x in ['BOT: ', 'BOT:', 'BLOCKED_BOT:', 'MONITOR_BOT:', 'BANNED_BOT:'])
|
||||
|
||||
@@ -3868,7 +3909,7 @@ class JTLWAFiAgent:
|
||||
is_404 = '404' in line
|
||||
|
||||
if ip:
|
||||
tracker.record_request(ip, path, is_bot, is_blocked, is_404)
|
||||
tracker.record_request(ip, path, is_bot, is_blocked, is_404, user_agent)
|
||||
except Exception as e:
|
||||
logger.debug(f"LiveStats record error: {e}")
|
||||
|
||||
@@ -4490,9 +4531,10 @@ class JTLWAFiAgent:
|
||||
})
|
||||
|
||||
async def _handle_whois_command(self, data: Dict[str, Any]):
|
||||
"""Führt WHOIS-Lookup für eine IP durch und sendet Ergebnis."""
|
||||
"""Führt WHOIS-Lookup für eine IP durch und sendet Ergebnis mit Request-Historie."""
|
||||
command_id = data.get('command_id', 'unknown')
|
||||
ip = data.get('ip')
|
||||
shop = data.get('shop') # Optional: für Request-Historie
|
||||
|
||||
if not ip:
|
||||
await self._send_event('whois_result', {
|
||||
@@ -4506,7 +4548,23 @@ class JTLWAFiAgent:
|
||||
# WHOIS-Lookup durchführen (wird gecacht)
|
||||
result = whois_lookup(ip)
|
||||
|
||||
# Ergebnis senden (ohne raw für kleinere Payload)
|
||||
# Request-Historie für diese IP sammeln (aus allen Shops wenn kein Shop angegeben)
|
||||
requests = []
|
||||
if shop:
|
||||
tracker = get_shop_stats_tracker(shop)
|
||||
requests = tracker.get_ip_requests(ip) # Alle Requests
|
||||
else:
|
||||
# Aus allen aktiven Shops sammeln
|
||||
for s in list(_shop_stats_trackers.keys()):
|
||||
tracker = get_shop_stats_tracker(s)
|
||||
shop_requests = tracker.get_ip_requests(ip) # Alle Requests
|
||||
for r in shop_requests:
|
||||
r['shop'] = s
|
||||
requests.extend(shop_requests)
|
||||
# Nach Zeit sortieren (neueste zuerst)
|
||||
requests.sort(key=lambda x: x['ts'], reverse=True)
|
||||
|
||||
# Ergebnis senden
|
||||
await self._send_event('whois_result', {
|
||||
'command_id': command_id,
|
||||
'success': True,
|
||||
@@ -4516,10 +4574,11 @@ class JTLWAFiAgent:
|
||||
'asn': result.get('asn', ''),
|
||||
'country': result.get('country', ''),
|
||||
'abuse': result.get('abuse', ''),
|
||||
'range': result.get('range', '')
|
||||
'range': result.get('range', ''),
|
||||
'requests': requests
|
||||
})
|
||||
|
||||
logger.info(f"WHOIS für {ip}: {result.get('org', 'Unknown')} ({result.get('asn', 'N/A')})")
|
||||
logger.info(f"WHOIS für {ip}: {result.get('org', 'Unknown')} ({result.get('asn', 'N/A')}) - {len(requests)} Requests")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"WHOIS Fehler für {ip}: {e}")
|
||||
|
||||
@@ -1778,11 +1778,12 @@ def get_dashboard_html() -> str:
|
||||
function renderDetailTopIps(ips){if(!ips||!ips.length)return '<div style="color:var(--text-secondary);padding:8px">Keine Daten</div>';return ips.map(ip=>'<div class="ip-list-item"><div class="ip-info"><span class="ip-addr" style="cursor:pointer" onclick="showIpDetail(\\''+ip.ip+'\\')">'+ip.ip+'</span><span class="ip-meta">'+getCountryName(ip.country)+' | '+(ip.org||ip.asn||'-')+'</span></div><span class="ip-count">'+ip.count+'x</span><div class="ip-actions"><button class="btn btn-danger" onclick="detailQuickBan(\\''+ip.ip+'\\')">🚫</button><button class="btn btn-success" onclick="detailQuickWhitelist(\\''+ip.ip+'\\')">✓</button></div></div>').join('');}
|
||||
function detailQuickBan(ip){if(!currentDetailShop)return;const duration=prompt('Ban-Dauer auswählen:\\n\\n900 = 15 Minuten\\n3600 = 1 Stunde\\n21600 = 6 Stunden\\n86400 = 24 Stunden\\n604800 = 7 Tage\\n-1 = Permanent','3600');if(duration===null)return;ws.send(JSON.stringify({type:'command.ban',data:{shop:currentDetailShop,ip:ip,duration:parseInt(duration),reason:'Manual ban from dashboard'}}));toast('IP '+ip+' wird gebannt...','success');}
|
||||
function detailQuickWhitelist(ip){if(!currentDetailShop)return;const desc=prompt('Beschreibung (optional):','');if(desc===null)return;ws.send(JSON.stringify({type:'command.whitelist',data:{shop:currentDetailShop,ip:ip,description:desc}}));toast('IP '+ip+' wird gewhitelisted...','success');}
|
||||
let currentWhoisIp=null;
|
||||
function showIpDetail(ip){currentWhoisIp=ip;document.getElementById('ipDetailContent').innerHTML='<div style="text-align:center;padding:20px;color:var(--text-secondary)">Lade WHOIS-Daten für '+ip+'...</div>';document.getElementById('ipDetailModal').classList.add('open');ws.send(JSON.stringify({type:'command.whois',data:{ip:ip,command_id:'whois_'+Date.now()}}));}
|
||||
function renderWhoisResult(data){if(!data.success){document.getElementById('ipDetailContent').innerHTML='<div style="padding:16px;color:var(--danger)">Fehler: '+(data.error||'Unbekannter Fehler')+'</div>';return;}const ip=data.ip||currentWhoisIp;const html='<div style="padding:4px 0"><table style="width:100%;border-collapse:collapse"><tr><td style="padding:8px 0;color:var(--text-secondary);width:120px">IP-Adresse</td><td style="padding:8px 0;font-weight:bold;font-family:monospace">'+ip+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">Organisation</td><td style="padding:8px 0">'+(data.org||'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">Netname</td><td style="padding:8px 0">'+(data.netname||'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">ASN</td><td style="padding:8px 0">'+(data.asn||'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">Land</td><td style="padding:8px 0">'+(data.country?getCountryName(data.country)+' ('+data.country+')':'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">IP-Range</td><td style="padding:8px 0;font-family:monospace;font-size:12px">'+(data.range||'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">Abuse-Kontakt</td><td style="padding:8px 0">'+(data.abuse?'<a href="mailto:'+data.abuse+'" style="color:var(--accent)">'+data.abuse+'</a>':'-')+'</td></tr></table></div><div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--border);display:flex;gap:8px;flex-wrap:wrap">'+(currentDetailShop?'<button class="btn btn-danger" onclick="ipDetailBan(\\''+ip+'\\')">🚫 Bannen</button><button class="btn btn-success" onclick="ipDetailWhitelist(\\''+ip+'\\')">✓ Whitelist</button>':'')+'<a class="btn btn-secondary" href="https://www.whois.com/whois/'+ip+'" target="_blank">🔗 WHOIS.com</a></div>';document.getElementById('ipDetailContent').innerHTML=html;}
|
||||
function ipDetailBan(ip){if(!currentDetailShop){toast('Kein Shop ausgewählt','warning');return;}const duration=prompt('Ban-Dauer auswählen:\\n\\n900 = 15 Minuten\\n3600 = 1 Stunde\\n21600 = 6 Stunden\\n86400 = 24 Stunden\\n604800 = 7 Tage\\n-1 = Permanent','3600');if(duration===null)return;ws.send(JSON.stringify({type:'command.ban',data:{shop:currentDetailShop,ip:ip,duration:parseInt(duration),reason:'Manual ban from IP detail'}}));toast('IP '+ip+' wird gebannt...','success');closeModal('ipDetailModal');}
|
||||
function ipDetailWhitelist(ip){if(!currentDetailShop){toast('Kein Shop ausgewählt','warning');return;}const desc=prompt('Beschreibung (optional):','');if(desc===null)return;ws.send(JSON.stringify({type:'command.whitelist',data:{shop:currentDetailShop,ip:ip,description:desc}}));toast('IP '+ip+' wird gewhitelisted...','success');closeModal('ipDetailModal');}
|
||||
let currentWhoisIp=null,currentWhoisData=null,showAllRequests=false;
|
||||
function showIpDetail(ip){currentWhoisIp=ip;currentWhoisData=null;showAllRequests=false;document.getElementById('ipDetailContent').innerHTML='<div style="text-align:center;padding:20px;color:var(--text-secondary)">Lade WHOIS-Daten für '+ip+'...</div>';document.getElementById('ipDetailModal').classList.add('open');const shop=currentDetailShop||currentLogsShop||'';ws.send(JSON.stringify({type:'command.whois',data:{ip:ip,shop:shop,command_id:'whois_'+Date.now()}}));}
|
||||
function renderWhoisResult(data){if(!data.success){document.getElementById('ipDetailContent').innerHTML='<div style="padding:16px;color:var(--danger)">Fehler: '+(data.error||'Unbekannter Fehler')+'</div>';return;}currentWhoisData=data;renderWhoisContent();}
|
||||
function renderWhoisContent(){const data=currentWhoisData;if(!data)return;const ip=data.ip||currentWhoisIp;const allReqs=data.requests||[];const initialLimit=50;const showLimit=showAllRequests?allReqs.length:Math.min(initialLimit,allReqs.length);const reqs=allReqs.slice(0,showLimit);let reqsHtml='';if(allReqs.length>0){reqsHtml='<div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--border)"><div style="font-weight:600;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center"><span>📋 Requests ('+showLimit+'/'+allReqs.length+')</span>'+(allReqs.length>initialLimit&&!showAllRequests?'<button class="btn btn-secondary" style="padding:4px 12px;font-size:11px" onclick="showAllRequests=true;renderWhoisContent()">Alle '+allReqs.length+' anzeigen</button>':'')+'</div><div style="max-height:'+(showAllRequests?'500px':'300px')+';overflow-y:auto;font-size:12px" id="ipReqsList">';reqs.forEach(r=>{const t=new Date(r.ts*1000).toLocaleTimeString('de-DE');const blocked=r.blocked?'<span style="color:var(--danger)">[BLOCKED]</span> ':'';reqsHtml+='<div style="padding:6px 0;border-bottom:1px solid var(--border)"><div style="display:flex;justify-content:space-between;align-items:center"><span style="color:var(--text-secondary)">'+t+'</span>'+blocked+'</div><div style="font-family:monospace;word-break:break-all;margin:4px 0;color:var(--accent)">'+r.path+'</div>'+(r.ua?'<div style="color:var(--text-secondary);font-size:11px;word-break:break-all">'+r.ua+'</div>':'')+'</div>';});reqsHtml+='</div></div>';}else{reqsHtml='<div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--border);color:var(--text-secondary)">📋 Keine aktuellen Requests gefunden</div>';}const html='<div style="padding:4px 0"><table style="width:100%;border-collapse:collapse"><tr><td style="padding:8px 0;color:var(--text-secondary);width:120px">IP-Adresse</td><td style="padding:8px 0;font-weight:bold;font-family:monospace">'+ip+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">Organisation</td><td style="padding:8px 0">'+(data.org||'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">Netname</td><td style="padding:8px 0">'+(data.netname||'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">ASN</td><td style="padding:8px 0">'+(data.asn||'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">Land</td><td style="padding:8px 0">'+(data.country?getCountryName(data.country)+' ('+data.country+')':'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">IP-Range</td><td style="padding:8px 0;font-family:monospace;font-size:12px">'+(data.range||'-')+'</td></tr><tr><td style="padding:8px 0;color:var(--text-secondary)">Abuse-Kontakt</td><td style="padding:8px 0">'+(data.abuse?'<a href="mailto:'+data.abuse+'" style="color:var(--accent)">'+data.abuse+'</a>':'-')+'</td></tr></table></div><div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--border);display:flex;gap:8px;flex-wrap:wrap">'+(currentDetailShop||currentLogsShop?'<button class="btn btn-danger" onclick="ipDetailBan(\\''+ip+'\\')">🚫 Bannen</button><button class="btn btn-success" onclick="ipDetailWhitelist(\\''+ip+'\\')">✓ Whitelist</button>':'')+'<a class="btn btn-secondary" href="https://www.whois.com/whois/'+ip+'" target="_blank">🔗 WHOIS.com</a></div>'+reqsHtml;document.getElementById('ipDetailContent').innerHTML=html;}
|
||||
function ipDetailBan(ip){const shop=currentDetailShop||currentLogsShop;if(!shop){toast('Kein Shop ausgewählt','warning');return;}const duration=prompt('Ban-Dauer auswählen:\\n\\n900 = 15 Minuten\\n3600 = 1 Stunde\\n21600 = 6 Stunden\\n86400 = 24 Stunden\\n604800 = 7 Tage\\n-1 = Permanent','3600');if(duration===null)return;ws.send(JSON.stringify({type:'command.ban',data:{shop:shop,ip:ip,duration:parseInt(duration),reason:'Manual ban from IP detail'}}));toast('IP '+ip+' wird gebannt...','success');closeModal('ipDetailModal');}
|
||||
function ipDetailWhitelist(ip){const shop=currentDetailShop||currentLogsShop;if(!shop){toast('Kein Shop ausgewählt','warning');return;}const desc=prompt('Beschreibung (optional):','');if(desc===null)return;ws.send(JSON.stringify({type:'command.whitelist',data:{shop:shop,ip:ip,description:desc}}));toast('IP '+ip+' wird gewhitelisted...','success');closeModal('ipDetailModal');}
|
||||
function quickBan(ip){if(!currentLogsShop)return;const duration=prompt('Ban-Dauer auswählen:\\n\\n900 = 15 Minuten\\n3600 = 1 Stunde\\n21600 = 6 Stunden\\n86400 = 24 Stunden\\n604800 = 7 Tage\\n-1 = Permanent','3600');if(duration===null)return;ws.send(JSON.stringify({type:'command.ban',data:{shop:currentLogsShop,ip:ip,duration:parseInt(duration),reason:'Manual ban from dashboard'}}));toast('IP '+ip+' wird gebannt...','success');}
|
||||
function quickWhitelist(ip){if(!currentLogsShop)return;const desc=prompt('Beschreibung (optional):','');if(desc===null)return;ws.send(JSON.stringify({type:'command.whitelist',data:{shop:currentLogsShop,ip:ip,description:desc}}));toast('IP '+ip+' wird gewhitelisted...','success');}
|
||||
function addLogEntry(line){const c=document.getElementById('logsContent');if(c.querySelector('div[style*="color:#666"]'))c.innerHTML='';const e=document.createElement('div');e.className='log-entry'+(line.includes('BANNED')?' banned':'');const ipRegex=/\\b(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\b/g;e.innerHTML=line.replace(/</g,'<').replace(/>/g,'>').replace(ipRegex,'<span class="log-ip" style="cursor:pointer" onclick="showIpDetail(\\'$1\\')">$1</span>');c.insertBefore(e,c.firstChild);while(c.children.length>100)c.removeChild(c.lastChild);}
|
||||
|
||||
Reference in New Issue
Block a user