jtl-wafi-dashboard.py aktualisiert
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
JTL-WAFi Dashboard v2.2.0 - WebSocket Real-Time Dashboard
|
||||
JTL-WAFi Dashboard v2.5.0 - WebSocket Real-Time Dashboard
|
||||
|
||||
ÄNDERUNG: Keine SQLite mehr für Echtzeit-Daten!
|
||||
ÄNDERUNG v2.5: Country Rate-Limiting ersetzt GeoIP-Blocking!
|
||||
- Bot Rate-Limiting: Bots nach Rate-Limit bannen
|
||||
- Country Rate-Limiting: Länder nach Rate-Limit bannen
|
||||
- Unlimitierte Länder: Definierte Länder werden nicht limitiert
|
||||
- Monitor-Only Modus: Nur überwachen ohne zu blocken
|
||||
- Alle Agent/Shop-Daten im Memory
|
||||
- DB nur für: Passwort, Tokens, Sessions
|
||||
- Kein Locking, keine Timeouts
|
||||
|
||||
v2.2.0: In-Memory Storage für maximale Performance
|
||||
v2.5.0: Country Rate-Limiting Feature
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -31,7 +34,7 @@ import uvicorn
|
||||
# =============================================================================
|
||||
# VERSION & CONFIG
|
||||
# =============================================================================
|
||||
VERSION = "2.4.0"
|
||||
VERSION = "2.5.0"
|
||||
|
||||
DATA_DIR = "/var/lib/jtl-wafi"
|
||||
SSL_DIR = "/var/lib/jtl-wafi/ssl"
|
||||
@@ -83,32 +86,48 @@ class AgentData:
|
||||
|
||||
@dataclass
|
||||
class ShopData:
|
||||
"""Shop-Daten v2.5 mit Country-Rate-Limiting Support."""
|
||||
domain: str
|
||||
agent_id: str
|
||||
agent_hostname: str = ""
|
||||
status: str = "inactive"
|
||||
mode: str = ""
|
||||
geo_region: str = ""
|
||||
rate_limit: int = 0
|
||||
ban_duration: int = 0
|
||||
bot_monitor_only: bool = False
|
||||
link11: bool = False
|
||||
link11_ip: str = ""
|
||||
activated: str = ""
|
||||
runtime_minutes: float = 0.0
|
||||
# Stats
|
||||
|
||||
# v2.5: Neue Konfiguration
|
||||
bot_mode: bool = False
|
||||
bot_rate_limit: int = 0
|
||||
bot_ban_duration: int = 0
|
||||
country_mode: bool = False
|
||||
country_rate_limit: int = 0
|
||||
country_ban_duration: int = 0
|
||||
unlimited_countries: List[str] = field(default_factory=list)
|
||||
monitor_only: bool = False
|
||||
|
||||
# Stats (v2.5 erweitert)
|
||||
log_entries: int = 0
|
||||
total_bans: int = 0
|
||||
active_bans: int = 0
|
||||
bot_bans: int = 0
|
||||
country_bans: int = 0
|
||||
active_bot_bans: int = 0
|
||||
active_country_bans: int = 0
|
||||
banned_bots: List[str] = field(default_factory=list)
|
||||
banned_countries: List[str] = field(default_factory=list)
|
||||
req_per_min: float = 0.0
|
||||
unique_ips: int = 0
|
||||
unique_bots: int = 0
|
||||
unique_countries: int = 0
|
||||
top_bots: Dict[str, int] = field(default_factory=dict)
|
||||
top_ips: Dict[str, int] = field(default_factory=dict)
|
||||
# History für Graph - jetzt pro Bot
|
||||
top_countries: Dict[str, int] = field(default_factory=dict)
|
||||
human_requests: int = 0
|
||||
bot_requests: int = 0
|
||||
|
||||
# History für Graph
|
||||
history: deque = field(default_factory=lambda: deque(maxlen=HISTORY_MAX_POINTS))
|
||||
bot_history: Dict[str, deque] = field(default_factory=dict) # bot_name -> deque of {timestamp, count}
|
||||
bot_history: Dict[str, deque] = field(default_factory=dict)
|
||||
country_history: Dict[str, deque] = field(default_factory=dict)
|
||||
|
||||
|
||||
class DataStore:
|
||||
@@ -242,6 +261,7 @@ class DataStore:
|
||||
|
||||
# === Shops ===
|
||||
def update_shop(self, agent_id: str, agent_hostname: str, shop_data: Dict) -> ShopData:
|
||||
"""Aktualisiert Shop-Daten vom Agent (v2.5)."""
|
||||
domain = shop_data.get('domain')
|
||||
|
||||
if domain not in self.shops:
|
||||
@@ -251,52 +271,75 @@ class DataStore:
|
||||
shop.agent_id = agent_id
|
||||
shop.agent_hostname = agent_hostname
|
||||
shop.status = shop_data.get('status', 'inactive')
|
||||
shop.mode = shop_data.get('mode', '')
|
||||
shop.geo_region = shop_data.get('geo_region', '')
|
||||
shop.rate_limit = shop_data.get('rate_limit', 0)
|
||||
shop.ban_duration = shop_data.get('ban_duration', 0)
|
||||
shop.bot_monitor_only = shop_data.get('bot_monitor_only', False)
|
||||
shop.link11 = bool(shop_data.get('link11'))
|
||||
shop.link11_ip = shop_data.get('link11_ip', '')
|
||||
shop.activated = shop_data.get('activated', '')
|
||||
shop.runtime_minutes = shop_data.get('runtime_minutes', 0)
|
||||
|
||||
# Stats
|
||||
# v2.5 Konfiguration
|
||||
shop.bot_mode = shop_data.get('bot_mode', False)
|
||||
shop.bot_rate_limit = shop_data.get('bot_rate_limit') or 0
|
||||
shop.bot_ban_duration = shop_data.get('bot_ban_duration') or 0
|
||||
shop.country_mode = shop_data.get('country_mode', False)
|
||||
shop.country_rate_limit = shop_data.get('country_rate_limit') or 0
|
||||
shop.country_ban_duration = shop_data.get('country_ban_duration') or 0
|
||||
shop.unlimited_countries = shop_data.get('unlimited_countries', [])
|
||||
shop.monitor_only = shop_data.get('monitor_only', False)
|
||||
|
||||
# Stats (v2.5)
|
||||
stats = shop_data.get('stats', {})
|
||||
if stats:
|
||||
shop.log_entries = stats.get('log_entries', 0)
|
||||
shop.total_bans = stats.get('total_bans', 0)
|
||||
shop.active_bans = stats.get('active_bans', 0)
|
||||
shop.bot_bans = stats.get('bot_bans', 0)
|
||||
shop.country_bans = stats.get('country_bans', 0)
|
||||
shop.active_bot_bans = stats.get('active_bot_bans', 0)
|
||||
shop.active_country_bans = stats.get('active_country_bans', 0)
|
||||
shop.banned_bots = stats.get('banned_bots', [])
|
||||
shop.banned_countries = stats.get('banned_countries', [])
|
||||
shop.req_per_min = stats.get('req_per_min', 0)
|
||||
shop.unique_ips = stats.get('unique_ips', 0)
|
||||
shop.unique_bots = stats.get('unique_bots', 0)
|
||||
shop.unique_countries = stats.get('unique_countries', 0)
|
||||
shop.top_bots = stats.get('top_bots', {})
|
||||
shop.top_ips = stats.get('top_ips', {})
|
||||
shop.top_countries = stats.get('top_countries', {})
|
||||
shop.human_requests = stats.get('human_requests', 0)
|
||||
shop.bot_requests = stats.get('bot_requests', 0)
|
||||
|
||||
# History für Graph
|
||||
shop.history.append({
|
||||
'timestamp': utc_now_str(),
|
||||
'req_per_min': shop.req_per_min,
|
||||
'active_bans': shop.active_bans
|
||||
'active_bot_bans': shop.active_bot_bans,
|
||||
'active_country_bans': shop.active_country_bans,
|
||||
'human_requests': shop.human_requests,
|
||||
'bot_requests': shop.bot_requests
|
||||
})
|
||||
|
||||
return shop
|
||||
|
||||
def update_shop_stats(self, domain: str, stats: Dict):
|
||||
"""Aktualisiert Shop-Statistiken (v2.5)."""
|
||||
if domain not in self.shops:
|
||||
return
|
||||
|
||||
shop = self.shops[domain]
|
||||
shop.log_entries = stats.get('log_entries', shop.log_entries)
|
||||
shop.total_bans = stats.get('total_bans', shop.total_bans)
|
||||
shop.active_bans = stats.get('active_bans', shop.active_bans)
|
||||
shop.bot_bans = stats.get('bot_bans', shop.bot_bans)
|
||||
shop.country_bans = stats.get('country_bans', shop.country_bans)
|
||||
shop.active_bot_bans = stats.get('active_bot_bans', shop.active_bot_bans)
|
||||
shop.active_country_bans = stats.get('active_country_bans', shop.active_country_bans)
|
||||
shop.banned_bots = stats.get('banned_bots', shop.banned_bots)
|
||||
shop.banned_countries = stats.get('banned_countries', shop.banned_countries)
|
||||
shop.req_per_min = stats.get('req_per_min', shop.req_per_min)
|
||||
shop.unique_ips = stats.get('unique_ips', shop.unique_ips)
|
||||
shop.unique_bots = stats.get('unique_bots', shop.unique_bots)
|
||||
shop.unique_countries = stats.get('unique_countries', shop.unique_countries)
|
||||
shop.top_bots = stats.get('top_bots', shop.top_bots)
|
||||
shop.top_ips = stats.get('top_ips', shop.top_ips)
|
||||
shop.top_countries = stats.get('top_countries', shop.top_countries)
|
||||
shop.human_requests = stats.get('human_requests', shop.human_requests)
|
||||
shop.bot_requests = stats.get('bot_requests', shop.bot_requests)
|
||||
|
||||
timestamp = utc_now_str()
|
||||
|
||||
@@ -304,7 +347,10 @@ class DataStore:
|
||||
shop.history.append({
|
||||
'timestamp': timestamp,
|
||||
'req_per_min': shop.req_per_min,
|
||||
'active_bans': shop.active_bans
|
||||
'active_bot_bans': shop.active_bot_bans,
|
||||
'active_country_bans': shop.active_country_bans,
|
||||
'human_requests': shop.human_requests,
|
||||
'bot_requests': shop.bot_requests
|
||||
})
|
||||
|
||||
# Bot-History aktualisieren
|
||||
@@ -317,10 +363,21 @@ class DataStore:
|
||||
'count': count
|
||||
})
|
||||
|
||||
# Country-History aktualisieren
|
||||
top_countries = stats.get('top_countries', {})
|
||||
for country, count in top_countries.items():
|
||||
if country not in shop.country_history:
|
||||
shop.country_history[country] = deque(maxlen=HISTORY_MAX_POINTS)
|
||||
shop.country_history[country].append({
|
||||
'timestamp': timestamp,
|
||||
'count': count
|
||||
})
|
||||
|
||||
def get_shop(self, domain: str) -> Optional[ShopData]:
|
||||
return self.shops.get(domain)
|
||||
|
||||
def get_all_shops(self) -> List[Dict]:
|
||||
"""Gibt alle Shops mit v2.5 Struktur zurück."""
|
||||
result = []
|
||||
for shop in self.shops.values():
|
||||
result.append({
|
||||
@@ -328,46 +385,65 @@ class DataStore:
|
||||
'agent_id': shop.agent_id,
|
||||
'agent_hostname': shop.agent_hostname,
|
||||
'status': shop.status,
|
||||
'mode': shop.mode,
|
||||
'geo_region': shop.geo_region,
|
||||
'rate_limit': shop.rate_limit,
|
||||
'ban_duration': shop.ban_duration,
|
||||
'bot_monitor_only': shop.bot_monitor_only,
|
||||
'link11': shop.link11,
|
||||
'link11_ip': shop.link11_ip,
|
||||
'activated': shop.activated,
|
||||
'runtime_minutes': shop.runtime_minutes,
|
||||
# v2.5 Konfiguration
|
||||
'bot_mode': shop.bot_mode,
|
||||
'bot_rate_limit': shop.bot_rate_limit,
|
||||
'bot_ban_duration': shop.bot_ban_duration,
|
||||
'country_mode': shop.country_mode,
|
||||
'country_rate_limit': shop.country_rate_limit,
|
||||
'country_ban_duration': shop.country_ban_duration,
|
||||
'unlimited_countries': shop.unlimited_countries,
|
||||
'monitor_only': shop.monitor_only,
|
||||
# Stats
|
||||
'stats': {
|
||||
'log_entries': shop.log_entries,
|
||||
'total_bans': shop.total_bans,
|
||||
'active_bans': shop.active_bans,
|
||||
'bot_bans': shop.bot_bans,
|
||||
'country_bans': shop.country_bans,
|
||||
'active_bot_bans': shop.active_bot_bans,
|
||||
'active_country_bans': shop.active_country_bans,
|
||||
'banned_bots': shop.banned_bots,
|
||||
'banned_countries': shop.banned_countries,
|
||||
'req_per_min': shop.req_per_min,
|
||||
'unique_ips': shop.unique_ips,
|
||||
'unique_bots': shop.unique_bots,
|
||||
'unique_countries': shop.unique_countries,
|
||||
'top_bots': shop.top_bots,
|
||||
'top_ips': shop.top_ips
|
||||
'top_ips': shop.top_ips,
|
||||
'top_countries': shop.top_countries,
|
||||
'human_requests': shop.human_requests,
|
||||
'bot_requests': shop.bot_requests
|
||||
}
|
||||
})
|
||||
return result
|
||||
|
||||
def get_shop_history(self, domain: str) -> Dict:
|
||||
"""Gibt Shop-History inkl. Country-History zurück."""
|
||||
shop = self.shops.get(domain)
|
||||
if not shop:
|
||||
return {'history': [], 'bot_history': {}}
|
||||
return {'history': [], 'bot_history': {}, 'country_history': {}}
|
||||
|
||||
# Bot-History in JSON-serialisierbares Format
|
||||
bot_history = {}
|
||||
for bot_name, history in shop.bot_history.items():
|
||||
bot_history[bot_name] = list(history)
|
||||
|
||||
# Country-History in JSON-serialisierbares Format
|
||||
country_history = {}
|
||||
for country, history in shop.country_history.items():
|
||||
country_history[country] = list(history)
|
||||
|
||||
return {
|
||||
'history': list(shop.history),
|
||||
'bot_history': bot_history
|
||||
'bot_history': bot_history,
|
||||
'country_history': country_history
|
||||
}
|
||||
|
||||
def get_top_shops(self, limit: int = 10, sort_by: str = 'req_per_min') -> List[Dict]:
|
||||
"""Gibt Top Shops sortiert nach req_per_min oder active_bans zurück."""
|
||||
"""Gibt Top Shops sortiert nach verschiedenen Kriterien zurück."""
|
||||
shops_list = []
|
||||
for shop in self.shops.values():
|
||||
shops_list.append({
|
||||
@@ -375,13 +451,17 @@ class DataStore:
|
||||
'agent_hostname': shop.agent_hostname,
|
||||
'status': shop.status,
|
||||
'req_per_min': shop.req_per_min,
|
||||
'active_bans': shop.active_bans,
|
||||
'link11': shop.link11
|
||||
'active_bot_bans': shop.active_bot_bans,
|
||||
'active_country_bans': shop.active_country_bans,
|
||||
'link11': shop.link11,
|
||||
'bot_mode': shop.bot_mode,
|
||||
'country_mode': shop.country_mode,
|
||||
'monitor_only': shop.monitor_only
|
||||
})
|
||||
|
||||
# Sortieren
|
||||
if sort_by == 'active_bans':
|
||||
shops_list.sort(key=lambda x: x['active_bans'], reverse=True)
|
||||
shops_list.sort(key=lambda x: x['active_bot_bans'] + x['active_country_bans'], reverse=True)
|
||||
else:
|
||||
shops_list.sort(key=lambda x: x['req_per_min'], reverse=True)
|
||||
|
||||
@@ -400,7 +480,8 @@ class DataStore:
|
||||
shops_direct = shops_total - shops_link11
|
||||
|
||||
req_per_min = sum(s.req_per_min for s in self.shops.values())
|
||||
active_bans = sum(s.active_bans for s in self.shops.values())
|
||||
active_bot_bans = sum(s.active_bot_bans for s in self.shops.values())
|
||||
active_country_bans = sum(s.active_country_bans for s in self.shops.values())
|
||||
|
||||
return {
|
||||
'agents_online': agents_online,
|
||||
@@ -410,7 +491,9 @@ class DataStore:
|
||||
'shops_link11': shops_link11,
|
||||
'shops_direct': shops_direct,
|
||||
'req_per_min': round(req_per_min, 1),
|
||||
'active_bans': active_bans
|
||||
'active_bot_bans': active_bot_bans,
|
||||
'active_country_bans': active_country_bans,
|
||||
'active_bans': active_bot_bans + active_country_bans
|
||||
}
|
||||
|
||||
|
||||
@@ -864,18 +947,29 @@ async def approve_agent(agent_id: str, request: Request):
|
||||
async def activate_shop(
|
||||
request: Request,
|
||||
domain: str = Form(...),
|
||||
mode: str = Form(...),
|
||||
geo_region: str = Form("dach"),
|
||||
rate_limit: int = Form(30),
|
||||
ban_duration: int = Form(300),
|
||||
bot_monitor_only: str = Form("false")
|
||||
bot_mode: str = Form("true"),
|
||||
bot_rate_limit: int = Form(30),
|
||||
bot_ban_duration: int = Form(300),
|
||||
country_mode: str = Form("false"),
|
||||
country_rate_limit: int = Form(100),
|
||||
country_ban_duration: int = Form(600),
|
||||
unlimited_countries: str = Form(""),
|
||||
monitor_only: str = Form("false")
|
||||
):
|
||||
"""Aktiviert Blocking für einen Shop (v2.5)."""
|
||||
user = await get_current_user(request)
|
||||
if not user:
|
||||
raise HTTPException(401)
|
||||
|
||||
# String "true"/"false" zu Boolean konvertieren
|
||||
is_monitor_only = bot_monitor_only.lower() in ('true', '1', 'yes', 'on')
|
||||
# String zu Boolean konvertieren
|
||||
is_bot_mode = bot_mode.lower() in ('true', '1', 'yes', 'on')
|
||||
is_country_mode = country_mode.lower() in ('true', '1', 'yes', 'on')
|
||||
is_monitor_only = monitor_only.lower() in ('true', '1', 'yes', 'on')
|
||||
|
||||
# unlimited_countries als Liste parsen
|
||||
countries_list = []
|
||||
if unlimited_countries:
|
||||
countries_list = [c.strip().lower() for c in unlimited_countries.split(',') if c.strip()]
|
||||
|
||||
agent_id = manager.get_agent_for_shop(domain)
|
||||
if not agent_id or not manager.is_agent_connected(agent_id):
|
||||
@@ -887,11 +981,14 @@ async def activate_shop(
|
||||
'data': {
|
||||
'command_id': command_id,
|
||||
'shop': domain,
|
||||
'mode': mode,
|
||||
'geo_region': geo_region if mode == 'geoip' else None,
|
||||
'rate_limit': rate_limit if mode == 'bot' and not is_monitor_only else None,
|
||||
'ban_duration': ban_duration if mode == 'bot' and not is_monitor_only else None,
|
||||
'bot_monitor_only': is_monitor_only if mode == 'bot' else False
|
||||
'bot_mode': is_bot_mode and not is_monitor_only,
|
||||
'bot_rate_limit': bot_rate_limit if is_bot_mode else None,
|
||||
'bot_ban_duration': bot_ban_duration if is_bot_mode else None,
|
||||
'country_mode': is_country_mode and not is_monitor_only,
|
||||
'country_rate_limit': country_rate_limit if is_country_mode else None,
|
||||
'country_ban_duration': country_ban_duration if is_country_mode else None,
|
||||
'unlimited_countries': countries_list if is_country_mode else [],
|
||||
'monitor_only': is_monitor_only
|
||||
}
|
||||
})
|
||||
|
||||
@@ -920,19 +1017,30 @@ async def deactivate_shop(request: Request, domain: str = Form(...)):
|
||||
@app.post("/api/shops/bulk-activate")
|
||||
async def bulk_activate(
|
||||
request: Request,
|
||||
mode: str = Form(...),
|
||||
geo_region: str = Form("dach"),
|
||||
rate_limit: int = Form(30),
|
||||
ban_duration: int = Form(300),
|
||||
bot_monitor_only: str = Form("false"),
|
||||
bot_mode: str = Form("true"),
|
||||
bot_rate_limit: int = Form(30),
|
||||
bot_ban_duration: int = Form(300),
|
||||
country_mode: str = Form("false"),
|
||||
country_rate_limit: int = Form(100),
|
||||
country_ban_duration: int = Form(600),
|
||||
unlimited_countries: str = Form(""),
|
||||
monitor_only: str = Form("false"),
|
||||
filter_type: str = Form("all")
|
||||
):
|
||||
"""Bulk-Aktivierung für mehrere Shops (v2.5)."""
|
||||
user = await get_current_user(request)
|
||||
if not user:
|
||||
raise HTTPException(401)
|
||||
|
||||
# String "true"/"false"/"on" zu Boolean konvertieren
|
||||
is_monitor_only = bot_monitor_only.lower() in ('true', '1', 'yes', 'on')
|
||||
# String zu Boolean konvertieren
|
||||
is_bot_mode = bot_mode.lower() in ('true', '1', 'yes', 'on')
|
||||
is_country_mode = country_mode.lower() in ('true', '1', 'yes', 'on')
|
||||
is_monitor_only = monitor_only.lower() in ('true', '1', 'yes', 'on')
|
||||
|
||||
# unlimited_countries als Liste parsen
|
||||
countries_list = []
|
||||
if unlimited_countries:
|
||||
countries_list = [c.strip().lower() for c in unlimited_countries.split(',') if c.strip()]
|
||||
|
||||
activated = 0
|
||||
shops = store.get_all_shops()
|
||||
@@ -956,11 +1064,14 @@ async def bulk_activate(
|
||||
'data': {
|
||||
'command_id': command_id,
|
||||
'shop': shop['domain'],
|
||||
'mode': mode,
|
||||
'geo_region': geo_region if mode == 'geoip' else None,
|
||||
'rate_limit': rate_limit if mode == 'bot' and not is_monitor_only else None,
|
||||
'ban_duration': ban_duration if mode == 'bot' and not is_monitor_only else None,
|
||||
'bot_monitor_only': is_monitor_only if mode == 'bot' else False
|
||||
'bot_mode': is_bot_mode and not is_monitor_only,
|
||||
'bot_rate_limit': bot_rate_limit if is_bot_mode else None,
|
||||
'bot_ban_duration': bot_ban_duration if is_bot_mode else None,
|
||||
'country_mode': is_country_mode and not is_monitor_only,
|
||||
'country_rate_limit': country_rate_limit if is_country_mode else None,
|
||||
'country_ban_duration': country_ban_duration if is_country_mode else None,
|
||||
'unlimited_countries': countries_list if is_country_mode else [],
|
||||
'monitor_only': is_monitor_only
|
||||
}
|
||||
})
|
||||
activated += 1
|
||||
@@ -1120,7 +1231,7 @@ def get_dashboard_html() -> str:
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JTL-WAFi Dashboard v2.3</title>
|
||||
<title>JTL-WAFi Dashboard v2.5</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
:root {
|
||||
@@ -1244,7 +1355,7 @@ def get_dashboard_html() -> str:
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="logo">🌍 JTL-<span>WAFi</span> <small style="font-size:11px;opacity:0.5">v2.3</small></div>
|
||||
<div class="logo">🌍 JTL-<span>WAFi</span> <small style="font-size:11px;opacity:0.5">v2.5</small></div>
|
||||
<div class="header-right">
|
||||
<div class="clock" id="clock">--:--:--</div>
|
||||
<div class="connection-status"><div class="status-dot" id="wsStatus"></div><span id="wsStatusText">Verbinde...</span></div>
|
||||
@@ -1286,12 +1397,12 @@ def get_dashboard_html() -> str:
|
||||
</div>
|
||||
</main>
|
||||
<div class="logs-panel" id="logsPanel"><div class="logs-header"><span>📜 Live Logs: <span id="logsShop">-</span></span><button class="btn btn-secondary" onclick="closeLogs()">✕</button></div><div class="logs-content" id="logsContent"></div></div>
|
||||
<div class="modal-overlay" id="activateModal"><div class="modal"><h3 class="modal-title">🚀 Shop aktivieren</h3><form id="activateForm"><input type="hidden" name="domain" id="activateDomain"><div class="form-group"><label>Domain</label><input type="text" id="activateDomainDisplay" readonly></div><div class="form-group"><label>Modus</label><select name="mode" id="activateMode" onchange="toggleModeOptions()"><option value="bot">🤖 Bot Rate-Limiting</option><option value="geoip">🛡️ JTL-WAFi-Blocking</option></select></div><div class="form-group" id="geoRegionGroup" style="display:none"><label>Region</label><select name="geo_region"><option value="dach">🇩🇪🇦🇹🇨🇭 DACH</option><option value="eurozone">🇪🇺 Eurozone</option></select></div><div class="form-group" id="monitorOnlyGroup"><label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="bot_monitor_only" id="monitorOnlyCheck" onchange="toggleMonitorOnly()"> 🔍 Nur überwachen (kein Blocking)</label><small style="color:var(--text-secondary)">Bots werden erkannt und protokolliert, aber nicht blockiert</small></div><div class="form-group" id="rateLimitGroup"><label>Rate-Limit (Req/min)</label><input type="number" name="rate_limit" value="30" min="1"></div><div class="form-group" id="banDurationGroup"><label>Ban-Dauer (Sekunden)</label><input type="number" name="ban_duration" value="300" min="60"><small>300=5min</small></div><div class="modal-actions"><button type="button" class="btn btn-secondary" onclick="closeModal('activateModal')">Abbrechen</button><button type="submit" class="btn btn-primary">Aktivieren</button></div></form></div></div>
|
||||
<div class="modal-overlay" id="bulkActivateModal"><div class="modal"><h3 class="modal-title">⚡ Massenaktivierung</h3><form id="bulkActivateForm"><div class="form-group"><label>Modus</label><select name="mode" id="bulkActivateMode" onchange="toggleBulkModeOptions()"><option value="bot">🤖 Bot Rate-Limiting</option><option value="geoip">🛡️ JTL-WAFi-Blocking</option></select></div><div class="form-group" id="bulkGeoRegionGroup" style="display:none"><label>Region</label><select name="geo_region"><option value="dach">🇩🇪🇦🇹🇨🇭 DACH</option><option value="eurozone">🇪🇺 Eurozone</option></select></div><div class="form-group" id="bulkMonitorOnlyGroup"><label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="bot_monitor_only" id="bulkMonitorOnlyCheck" onchange="toggleBulkMonitorOnly()"> 🔍 Nur überwachen</label></div><div class="form-group" id="bulkRateLimitGroup"><label>Rate-Limit (Req/min)</label><input type="number" name="rate_limit" value="30" min="1"></div><div class="form-group" id="bulkBanDurationGroup"><label>Ban-Dauer (Sek)</label><input type="number" name="ban_duration" value="300" min="60"><small>300=5min, 3600=1h</small></div><div class="form-group"><label>Filter</label><select name="filter_type"><option value="all">Alle inaktiven</option><option value="direct">Nur Direkte ⚡</option><option value="link11">Nur Link11 🛡️</option></select></div><div class="modal-actions"><button type="button" class="btn btn-secondary" onclick="closeModal('bulkActivateModal')">Abbrechen</button><button type="submit" class="btn btn-success">▶️ Aktivieren</button></div></form></div></div>
|
||||
<div class="modal-overlay" id="activateModal"><div class="modal"><h3 class="modal-title">🚀 Shop aktivieren (v2.5)</h3><form id="activateForm"><input type="hidden" name="domain" id="activateDomain"><div class="form-group"><label>Domain</label><input type="text" id="activateDomainDisplay" readonly></div><div class="form-group" id="monitorOnlyGroup"><label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="monitor_only" id="monitorOnlyCheck" onchange="toggleMonitorOnly()"> 🔍 Nur überwachen (kein Blocking)</label><small style="color:var(--text-secondary)">Alle Anfragen werden protokolliert, aber nicht blockiert</small></div><div id="blockingOptions"><div style="border:1px solid var(--border);border-radius:8px;padding:16px;margin-bottom:16px"><div class="form-group"><label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="bot_mode" id="botModeCheck" checked onchange="toggleBotOptions()"> 🤖 Bot Rate-Limiting</label></div><div id="botOptionsGroup"><div class="form-group"><label>Bot Rate-Limit (Req/min pro Bot)</label><input type="number" name="bot_rate_limit" value="30" min="1"></div><div class="form-group"><label>Bot Ban-Dauer (Sekunden)</label><input type="number" name="bot_ban_duration" value="300" min="60"><small>300=5min</small></div></div></div><div style="border:1px solid var(--border);border-radius:8px;padding:16px"><div class="form-group"><label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="country_mode" id="countryModeCheck" onchange="toggleCountryOptions()"> 🌍 Country Rate-Limiting</label></div><div id="countryOptionsGroup" style="display:none"><div class="form-group"><label>Country Rate-Limit (Req/min pro Land)</label><input type="number" name="country_rate_limit" value="100" min="1"></div><div class="form-group"><label>Country Ban-Dauer (Sekunden)</label><input type="number" name="country_ban_duration" value="600" min="60"><small>600=10min</small></div><div class="form-group"><label>Unlimitierte Länder</label><input type="text" name="unlimited_countries" value="de,at,ch" placeholder="de,at,ch,us,gb"><small>Kommagetrennte ISO-Codes (z.B. de,at,ch). Diese Länder werden nicht limitiert.</small></div><div class="form-group"><label>Presets:</label><button type="button" class="btn btn-secondary" onclick="setCountries('dach')">🇩🇪🇦🇹🇨🇭 DACH</button> <button type="button" class="btn btn-secondary" onclick="setCountries('eu_plus')">🇪🇺 EU+</button></div></div></div></div><div class="modal-actions"><button type="button" class="btn btn-secondary" onclick="closeModal('activateModal')">Abbrechen</button><button type="submit" class="btn btn-primary">Aktivieren</button></div></form></div></div>
|
||||
<div class="modal-overlay" id="bulkActivateModal"><div class="modal"><h3 class="modal-title">⚡ Massenaktivierung (v2.5)</h3><form id="bulkActivateForm"><div class="form-group" id="bulkMonitorOnlyGroup"><label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="monitor_only" id="bulkMonitorOnlyCheck" onchange="toggleBulkMonitorOnly()"> 🔍 Nur überwachen (kein Blocking)</label></div><div id="bulkBlockingOptions"><div style="border:1px solid var(--border);border-radius:8px;padding:16px;margin-bottom:16px"><div class="form-group"><label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="bot_mode" id="bulkBotModeCheck" checked onchange="toggleBulkBotOptions()"> 🤖 Bot Rate-Limiting</label></div><div id="bulkBotOptionsGroup"><div class="form-group"><label>Bot Rate-Limit (Req/min)</label><input type="number" name="bot_rate_limit" value="30" min="1"></div><div class="form-group"><label>Bot Ban-Dauer (Sek)</label><input type="number" name="bot_ban_duration" value="300" min="60"><small>300=5min</small></div></div></div><div style="border:1px solid var(--border);border-radius:8px;padding:16px"><div class="form-group"><label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="country_mode" id="bulkCountryModeCheck" onchange="toggleBulkCountryOptions()"> 🌍 Country Rate-Limiting</label></div><div id="bulkCountryOptionsGroup" style="display:none"><div class="form-group"><label>Country Rate-Limit (Req/min)</label><input type="number" name="country_rate_limit" value="100" min="1"></div><div class="form-group"><label>Country Ban-Dauer (Sek)</label><input type="number" name="country_ban_duration" value="600" min="60"><small>600=10min</small></div><div class="form-group"><label>Unlimitierte Länder</label><input type="text" name="unlimited_countries" value="de,at,ch" placeholder="de,at,ch"><small>Kommagetrennte ISO-Codes</small></div><div class="form-group"><label>Presets:</label><button type="button" class="btn btn-secondary" onclick="setBulkCountries('dach')">🇩🇪🇦🇹🇨🇭 DACH</button> <button type="button" class="btn btn-secondary" onclick="setBulkCountries('eu_plus')">🇪🇺 EU+</button></div></div></div></div><div class="form-group" style="margin-top:16px"><label>Filter</label><select name="filter_type"><option value="all">Alle inaktiven</option><option value="direct">Nur Direkte ⚡</option><option value="link11">Nur Link11 🛡️</option></select></div><div class="modal-actions"><button type="button" class="btn btn-secondary" onclick="closeModal('bulkActivateModal')">Abbrechen</button><button type="submit" class="btn btn-success">▶️ Aktivieren</button></div></form></div></div>
|
||||
<div class="modal-overlay" id="bulkDeactivateModal"><div class="modal"><h3 class="modal-title">⏹️ Massendeaktivierung</h3><form id="bulkDeactivateForm"><div class="form-group"><label>Filter</label><select name="filter_type"><option value="all">Alle aktiven</option><option value="direct">Nur Direkte ⚡</option><option value="link11">Nur Link11 🛡️</option></select></div><div class="modal-actions"><button type="button" class="btn btn-secondary" onclick="closeModal('bulkDeactivateModal')">Abbrechen</button><button type="submit" class="btn btn-danger">⏹️ Deaktivieren</button></div></form></div></div>
|
||||
<div class="modal-overlay" id="passwordModal"><div class="modal"><h3 class="modal-title">🔑 Passwort ändern</h3><form id="passwordForm"><div class="form-group"><label>Aktuelles Passwort</label><input type="password" name="current" required></div><div class="form-group"><label>Neues Passwort</label><input type="password" name="new_pw" required minlength="8"></div><div class="form-group"><label>Bestätigen</label><input type="password" name="confirm" required></div><div class="modal-actions"><button type="button" class="btn btn-secondary" onclick="closeModal('passwordModal')">Abbrechen</button><button type="submit" class="btn btn-primary">Speichern</button></div></form></div></div>
|
||||
<div class="modal-overlay" id="allShopsModal"><div class="modal xlarge"><h3 class="modal-title"><span>📊 Alle Shops</span><button class="btn btn-secondary" onclick="closeModal('allShopsModal')">✕</button></h3><div style="margin-bottom:16px"><button class="btn" id="sortByReq" onclick="sortAllShops('req_per_min')">Nach Requests</button> <button class="btn btn-secondary" id="sortByBans" onclick="sortAllShops('active_bans')">Nach Bans</button></div><table><thead><tr><th>#</th><th>Domain</th><th>Server</th><th>Status</th><th>Req/min</th><th>Bans</th><th>Typ</th></tr></thead><tbody id="allShopsTable"></tbody></table></div></div>
|
||||
<div class="modal-overlay" id="detailModal"><div class="modal large"><h3 class="modal-title"><a href="#" id="detailDomainLink" target="_blank" style="color:var(--accent);text-decoration:none" onmouseover="this.style.textDecoration='underline'" onmouseout="this.style.textDecoration='none'"><span id="detailDomain">-</span> 🔗</a><button class="btn btn-secondary" onclick="closeModal('detailModal')">✕</button></h3><div style="color:var(--text-secondary);margin:-8px 0 16px 0;font-size:13px">Server: <span id="detailServer">-</span></div><div class="detail-grid"><div class="detail-card"><div class="detail-card-label">Status</div><div class="detail-card-value" id="detailStatus">-</div></div><div class="detail-card"><div class="detail-card-label">Modus</div><div class="detail-card-value" id="detailMode">-</div></div><div class="detail-card"><div class="detail-card-label">Region</div><div class="detail-card-value" id="detailRegion">-</div></div><div class="detail-card"><div class="detail-card-label">Rate-Limit</div><div class="detail-card-value" id="detailRateLimit">-</div></div><div class="detail-card"><div class="detail-card-label">Ban-Dauer</div><div class="detail-card-value" id="detailBanDuration">-</div></div><div class="detail-card"><div class="detail-card-label">Laufzeit</div><div class="detail-card-value" id="detailRuntime">-</div></div></div><div class="detail-grid"><div class="detail-card"><div class="detail-card-label">Req/min</div><div class="detail-card-value" id="detailReqMin">0</div></div><div class="detail-card"><div class="detail-card-label">Aktive Bans</div><div class="detail-card-value" id="detailActiveBans">0</div></div><div class="detail-card"><div class="detail-card-label">Total Bans</div><div class="detail-card-value" id="detailTotalBans">0</div></div></div><div class="detail-section" id="detailActions"><div class="detail-section-title">⚙️ Steuerung</div><div style="display:flex;flex-wrap:wrap;gap:8px;padding:12px;background:var(--card-bg);border-radius:8px;align-items:center"><button class="btn btn-danger" id="detailBtnDeactivate" onclick="detailDeactivate()">⏹️ Deaktivieren</button><div style="border-left:1px solid var(--border);margin:0 8px;height:24px"></div><span style="color:var(--text-secondary);font-size:13px">Wechseln zu:</span><button class="btn" onclick="detailSwitchMode('bot-monitor')">🔍 Monitor</button><button class="btn" onclick="toggleRateLimitForm()">🤖 Rate-Limit ▾</button><button class="btn" onclick="detailSwitchMode('geoip-dach')">🇩🇪 DACH</button><button class="btn" onclick="detailSwitchMode('geoip-eur')">🇪🇺 Europa</button></div><div id="rateLimitFormArea" style="display:none;margin-top:12px;padding:12px;background:var(--card-bg);border-radius:8px;border:1px solid var(--border)"><div style="display:flex;gap:16px;align-items:flex-end;flex-wrap:wrap"><div><label style="display:block;font-size:12px;color:var(--text-secondary);margin-bottom:4px">Rate-Limit (Req/min)</label><input type="number" id="detailRateLimitInput" value="30" min="1" max="1000" style="width:100px;padding:8px;border-radius:4px;border:1px solid var(--border);background:var(--bg);color:var(--text)"></div><div><label style="display:block;font-size:12px;color:var(--text-secondary);margin-bottom:4px">Ban-Dauer</label><select id="detailBanDurationInput" style="padding:8px;border-radius:4px;border:1px solid var(--border);background:var(--bg);color:var(--text)"><option value="60">1 Minute</option><option value="300" selected>5 Minuten</option><option value="600">10 Minuten</option><option value="1800">30 Minuten</option><option value="3600">1 Stunde</option><option value="86400">24 Stunden</option></select></div><button class="btn btn-primary" onclick="applyRateLimit()">✓ Anwenden</button><button class="btn btn-secondary" onclick="toggleRateLimitForm()">Abbrechen</button></div></div></div><div class="detail-section"><div class="detail-section-title">📈 Bot-Aktivität über Zeit</div><div class="chart-container"><canvas id="requestChart"></canvas></div><div class="chart-legend" id="chartLegend"></div></div><div class="detail-section"><div class="detail-section-title">🤖 Top Bots</div><div class="bot-list" id="detailTopBots"></div></div><div class="detail-section"><div class="detail-section-title">🚫 Aktuell gebannt</div><div class="bot-list" id="detailBannedBots"></div></div></div></div>
|
||||
<div class="modal-overlay" id="detailModal"><div class="modal large"><h3 class="modal-title"><a href="#" id="detailDomainLink" target="_blank" style="color:var(--accent);text-decoration:none" onmouseover="this.style.textDecoration='underline'" onmouseout="this.style.textDecoration='none'"><span id="detailDomain">-</span> 🔗</a><button class="btn btn-secondary" onclick="closeModal('detailModal')">✕</button></h3><div style="color:var(--text-secondary);margin:-8px 0 16px 0;font-size:13px">Server: <span id="detailServer">-</span></div><div class="detail-grid"><div class="detail-card"><div class="detail-card-label">Status</div><div class="detail-card-value" id="detailStatus">-</div></div><div class="detail-card"><div class="detail-card-label">Modus</div><div class="detail-card-value" id="detailMode">-</div></div><div class="detail-card"><div class="detail-card-label">Unlimitiert</div><div class="detail-card-value" id="detailRegion">-</div></div><div class="detail-card"><div class="detail-card-label">Rate-Limit</div><div class="detail-card-value" id="detailRateLimit">-</div></div><div class="detail-card"><div class="detail-card-label">Ban-Dauer</div><div class="detail-card-value" id="detailBanDuration">-</div></div><div class="detail-card"><div class="detail-card-label">Laufzeit</div><div class="detail-card-value" id="detailRuntime">-</div></div></div><div class="detail-grid"><div class="detail-card"><div class="detail-card-label">Req/min</div><div class="detail-card-value" id="detailReqMin">0</div></div><div class="detail-card"><div class="detail-card-label">Aktive Bans</div><div class="detail-card-value" id="detailActiveBans">0</div></div><div class="detail-card"><div class="detail-card-label">Total Bans</div><div class="detail-card-value" id="detailTotalBans">0</div></div></div><div class="detail-section" id="detailActions"><div class="detail-section-title">⚙️ Steuerung</div><div style="display:flex;flex-wrap:wrap;gap:8px;padding:12px;background:var(--card-bg);border-radius:8px;align-items:center"><button class="btn btn-danger" id="detailBtnDeactivate" onclick="detailDeactivate()">⏹️ Deaktivieren</button><div style="border-left:1px solid var(--border);margin:0 8px;height:24px"></div><span style="color:var(--text-secondary);font-size:13px">Wechseln zu:</span><button class="btn" onclick="detailSwitchMode('bot-monitor')">🔍 Monitor</button><button class="btn" onclick="toggleRateLimitForm()">🤖 Bot-Limit ▾</button><button class="btn" onclick="detailSwitchMode('country-dach')">🇩🇪🇦🇹🇨🇭 DACH</button><button class="btn" onclick="detailSwitchMode('country-eu')">🇪🇺 EU+</button></div><div id="rateLimitFormArea" style="display:none;margin-top:12px;padding:12px;background:var(--card-bg);border-radius:8px;border:1px solid var(--border)"><div style="display:flex;gap:16px;align-items:flex-end;flex-wrap:wrap"><div><label style="display:block;font-size:12px;color:var(--text-secondary);margin-bottom:4px">Rate-Limit (Req/min)</label><input type="number" id="detailRateLimitInput" value="30" min="1" max="1000" style="width:100px;padding:8px;border-radius:4px;border:1px solid var(--border);background:var(--bg);color:var(--text)"></div><div><label style="display:block;font-size:12px;color:var(--text-secondary);margin-bottom:4px">Ban-Dauer</label><select id="detailBanDurationInput" style="padding:8px;border-radius:4px;border:1px solid var(--border);background:var(--bg);color:var(--text)"><option value="60">1 Minute</option><option value="300" selected>5 Minuten</option><option value="600">10 Minuten</option><option value="1800">30 Minuten</option><option value="3600">1 Stunde</option><option value="86400">24 Stunden</option></select></div><button class="btn btn-primary" onclick="applyRateLimit()">✓ Anwenden</button><button class="btn btn-secondary" onclick="toggleRateLimitForm()">Abbrechen</button></div></div></div><div class="detail-section"><div class="detail-section-title">📈 Bot-Aktivität über Zeit</div><div class="chart-container"><canvas id="requestChart"></canvas></div><div class="chart-legend" id="chartLegend"></div></div><div class="detail-section"><div class="detail-section-title">🤖 Top Bots</div><div class="bot-list" id="detailTopBots"></div></div><div class="detail-section"><div class="detail-section-title">🚫 Aktuell gebannt</div><div class="bot-list" id="detailBannedBots"></div></div></div></div>
|
||||
<div class="toast-container" id="toastContainer"></div>
|
||||
<script>
|
||||
let ws=null,agents={},shops={},currentLogsShop=null,currentSortBy='req_per_min',currentDetailShop=null;
|
||||
@@ -1309,29 +1420,33 @@ def get_dashboard_html() -> str:
|
||||
function handleMessage(msg){switch(msg.type){case'initial_state':case'refresh':agents={};msg.data.agents.forEach(a=>agents[a.id]=a);shops={};msg.data.shops.forEach(s=>shops[s.domain]=s);updateStats(msg.data.stats);renderAgents();renderShops();renderTopShops();break;case'agent.online':case'agent.update':case'agent.pending':const a=msg.data;if(!agents[a.agent_id])agents[a.agent_id]={};Object.assign(agents[a.agent_id],{id:a.agent_id,hostname:a.hostname,status:a.status||'online',approved:a.approved,shops_total:a.shops_summary?.total||0,shops_active:a.shops_summary?.active||0,load_1m:a.system?.load_1m,memory_percent:a.system?.memory_percent,last_seen:new Date().toISOString()});renderAgents();break;case'agent.offline':if(agents[msg.data.agent_id]){agents[msg.data.agent_id].status='offline';renderAgents();}break;case'agent.approved':if(agents[msg.data.agent_id]){agents[msg.data.agent_id].status='online';agents[msg.data.agent_id].approved=true;renderAgents();toast('Agent freigegeben','success');}break;case'shop.full_update':msg.data.shops.forEach(s=>{shops[s.domain]={...s,agent_id:msg.data.agent_id,agent_hostname:msg.data.hostname};});renderShops();renderTopShops();refreshStats();break;case'shop.stats':if(shops[msg.data.domain]){shops[msg.data.domain].stats=msg.data.stats;renderShops();renderTopShops();refreshStats();}break;case'shop_history':updateBotChart(msg.data);break;case'top_shops':case'all_shops_sorted':renderAllShopsTable(msg.data.shops,msg.data.sort_by);break;case'log.entry':if(msg.data.shop===currentLogsShop)addLogEntry(msg.data.line);break;case'bot.banned':toast('🚫 '+msg.data.bot_name+' gebannt','warning');break;case'command.result':toast(msg.data.message,msg.data.status==='success'?'success':'error');break;}}
|
||||
function renderAgents(){const t=document.getElementById('agentsTable'),l=getSortedAgents();document.getElementById('agentCount').textContent=l.length+' Agents';t.innerHTML=l.map(a=>'<tr><td><span class="status-badge status-'+(a.status||'offline')+'">'+(a.status==='online'?'🟢':a.status==='pending'?'🟡':'🔴')+' '+(a.status==='pending'?'Warte':a.status==='online'?'Online':'Offline')+'</span></td><td><strong>'+a.hostname+'</strong></td><td>'+(a.shops_active||0)+'/'+(a.shops_total||0)+'</td><td>'+(a.load_1m!=null?a.load_1m.toFixed(2):'-')+'</td><td>'+(a.memory_percent!=null?a.memory_percent.toFixed(1)+'%':'-')+'</td><td>'+(a.last_seen?formatTime(a.last_seen):'-')+'</td><td>'+(a.status==='pending'?'<button class="btn btn-primary" onclick="approveAgent(\\''+a.id+'\\')">✓</button>':'')+'</td></tr>').join('');}
|
||||
function renderShops(){const all=Object.values(shops);let l11=all.filter(s=>s.link11),dir=all.filter(s=>!s.link11);l11=getSortedShops(l11,'link11');dir=getSortedShops(dir,'direct');document.getElementById('link11Count').textContent=l11.length+' Shops';document.getElementById('directCount').textContent=dir.length+' Shops';document.getElementById('shopsLink11Table').innerHTML=renderShopRows(l11);document.getElementById('shopsDirectTable').innerHTML=renderShopRows(dir);}
|
||||
function renderShopRows(l){return l.map(s=>'<tr><td><span class="status-badge status-'+(s.status||'inactive')+'">'+(s.status==='active'?'✅':'⭕')+'</span></td><td><span class="domain-link" onclick="openDetailModal(\\''+s.domain+'\\')">'+s.domain+'</span></td><td>'+(s.agent_hostname||'-')+'</td><td>'+(s.mode?(s.bot_monitor_only?'🔍':(s.mode==='bot'?'🤖':'🌍'))+(s.mode==='geoip'&&s.geo_region?' ('+s.geo_region.toUpperCase()+')':'')+(s.bot_monitor_only?' Monitor':''):'-')+'</td><td>'+((s.stats?.req_per_min||0).toFixed(1))+'</td><td>'+(s.bot_monitor_only?'-':(s.stats?.active_bans||0))+'</td><td>'+formatRuntime(s.runtime_minutes)+'</td><td class="actions"><a href="https://'+s.domain+'" target="_blank" class="btn-icon">🔗</a>'+(s.status==='active'?'<button class="btn-icon" onclick="openLogs(\\''+s.domain+'\\')">📜</button><button class="btn btn-danger" onclick="deactivateShop(\\''+s.domain+'\\')">Stop</button>':'<button class="btn btn-primary" onclick="openActivateModal(\\''+s.domain+'\\')">Start</button>')+'</td></tr>').join('');}
|
||||
function renderTopShops(){const l=Object.values(shops).filter(s=>s.status==='active').sort((a,b)=>(b.stats?.req_per_min||0)-(a.stats?.req_per_min||0)).slice(0,10);document.getElementById('topShopsList').innerHTML=l.map(s=>'<div class="top-shop-item" onclick="openDetailModal(\\''+s.domain+'\\')"><div class="top-shop-domain">'+s.domain+'</div><div class="top-shop-stats"><span class="top-shop-req">'+((s.stats?.req_per_min||0).toFixed(1))+' req/m</span><span class="top-shop-bans">'+(s.stats?.active_bans||0)+' bans</span></div></div>').join('')||'<div style="color:var(--text-secondary)">Keine aktiven Shops</div>';}
|
||||
function renderShopRows(l){return l.map(s=>{const modeStr=s.monitor_only?'🔍 Monitor':((s.bot_mode?'🤖':'')+(s.country_mode?' 🌍':''))||'-';const bansStr=s.monitor_only?'-':((s.stats?.active_bot_bans||0)+(s.stats?.active_country_bans||0));return '<tr><td><span class="status-badge status-'+(s.status||'inactive')+'">'+(s.status==='active'?'✅':'⭕')+'</span></td><td><span class="domain-link" onclick="openDetailModal(\\''+s.domain+'\\')">'+s.domain+'</span></td><td>'+(s.agent_hostname||'-')+'</td><td>'+modeStr+'</td><td>'+((s.stats?.req_per_min||0).toFixed(1))+'</td><td>'+bansStr+'</td><td>'+formatRuntime(s.runtime_minutes)+'</td><td class="actions"><a href="https://'+s.domain+'" target="_blank" class="btn-icon">🔗</a>'+(s.status==='active'?'<button class="btn-icon" onclick="openLogs(\\''+s.domain+'\\')">📜</button><button class="btn btn-danger" onclick="deactivateShop(\\''+s.domain+'\\')">Stop</button>':'<button class="btn btn-primary" onclick="openActivateModal(\\''+s.domain+'\\')">Start</button>')+'</td></tr>';}).join('');}
|
||||
function renderTopShops(){const l=Object.values(shops).filter(s=>s.status==='active').sort((a,b)=>(b.stats?.req_per_min||0)-(a.stats?.req_per_min||0)).slice(0,10);document.getElementById('topShopsList').innerHTML=l.map(s=>{const bans=(s.stats?.active_bot_bans||0)+(s.stats?.active_country_bans||0);return '<div class="top-shop-item" onclick="openDetailModal(\\''+s.domain+'\\')"><div class="top-shop-domain">'+s.domain+'</div><div class="top-shop-stats"><span class="top-shop-req">'+((s.stats?.req_per_min||0).toFixed(1))+' req/m</span><span class="top-shop-bans">'+bans+' bans</span></div></div>';}).join('')||'<div style="color:var(--text-secondary)">Keine aktiven Shops</div>';}
|
||||
function updateStats(s){document.getElementById('statAgents').textContent=s.agents_online||0;document.getElementById('statShops').textContent=s.shops_active||0;document.getElementById('statLink11').textContent=s.shops_link11||0;document.getElementById('statDirect').textContent=s.shops_direct||0;document.getElementById('statReqMin').textContent=(s.req_per_min||0).toFixed(1);document.getElementById('statBans').textContent=s.active_bans||0;}
|
||||
function refreshStats(){const l=Object.values(shops),a=l.filter(s=>s.status==='active');document.getElementById('statShops').textContent=a.length;document.getElementById('statLink11').textContent=l.filter(s=>s.link11).length;document.getElementById('statDirect').textContent=l.filter(s=>!s.link11).length;document.getElementById('statReqMin').textContent=a.reduce((sum,s)=>sum+(s.stats?.req_per_min||0),0).toFixed(1);document.getElementById('statBans').textContent=a.reduce((sum,s)=>sum+(s.stats?.active_bans||0),0);}
|
||||
function refreshStats(){const l=Object.values(shops),a=l.filter(s=>s.status==='active');document.getElementById('statShops').textContent=a.length;document.getElementById('statLink11').textContent=l.filter(s=>s.link11).length;document.getElementById('statDirect').textContent=l.filter(s=>!s.link11).length;document.getElementById('statReqMin').textContent=a.reduce((sum,s)=>sum+(s.stats?.req_per_min||0),0).toFixed(1);document.getElementById('statBans').textContent=a.reduce((sum,s)=>sum+((s.stats?.active_bot_bans||0)+(s.stats?.active_country_bans||0)),0);}
|
||||
async function approveAgent(id){await fetch('/api/agents/'+id+'/approve',{method:'POST'});}
|
||||
function openActivateModal(d){document.getElementById('activateDomain').value=d;document.getElementById('activateDomainDisplay').value=d;document.getElementById('activateModal').classList.add('open');toggleModeOptions();}
|
||||
function openActivateModal(d){document.getElementById('activateDomain').value=d;document.getElementById('activateDomainDisplay').value=d;document.getElementById('monitorOnlyCheck').checked=false;document.getElementById('botModeCheck').checked=true;document.getElementById('countryModeCheck').checked=false;toggleMonitorOnly();toggleBotOptions();toggleCountryOptions();document.getElementById('activateModal').classList.add('open');}
|
||||
function closeModal(id){document.getElementById(id).classList.remove('open');}
|
||||
function toggleModeOptions(){const m=document.getElementById('activateMode').value;const isBot=m==='bot';document.getElementById('geoRegionGroup').style.display=m==='geoip'?'block':'none';document.getElementById('monitorOnlyGroup').style.display=isBot?'block':'none';document.getElementById('rateLimitGroup').style.display=isBot&&!document.getElementById('monitorOnlyCheck').checked?'block':'none';document.getElementById('banDurationGroup').style.display=isBot&&!document.getElementById('monitorOnlyCheck').checked?'block':'none';}
|
||||
function toggleMonitorOnly(){const isMonitor=document.getElementById('monitorOnlyCheck').checked;document.getElementById('rateLimitGroup').style.display=isMonitor?'none':'block';document.getElementById('banDurationGroup').style.display=isMonitor?'none':'block';}
|
||||
function toggleMonitorOnly(){const isMonitor=document.getElementById('monitorOnlyCheck').checked;document.getElementById('blockingOptions').style.display=isMonitor?'none':'block';}
|
||||
function toggleBotOptions(){const isBot=document.getElementById('botModeCheck').checked;document.getElementById('botOptionsGroup').style.display=isBot?'block':'none';}
|
||||
function toggleCountryOptions(){const isCountry=document.getElementById('countryModeCheck').checked;document.getElementById('countryOptionsGroup').style.display=isCountry?'block':'none';}
|
||||
function setCountries(preset){const input=document.querySelector('#activateForm input[name="unlimited_countries"]');if(preset==='dach')input.value='de,at,ch';else if(preset==='eu_plus')input.value='de,at,ch,be,cy,ee,es,fi,fr,gb,gr,hr,ie,it,lt,lu,lv,mt,nl,pt,si,sk';}
|
||||
document.getElementById('activateForm').onsubmit=async e=>{e.preventDefault();await fetch('/api/shops/activate',{method:'POST',body:new FormData(e.target)});closeModal('activateModal');};
|
||||
async function deactivateShop(d){if(!confirm(d+' deaktivieren?'))return;const fd=new FormData();fd.append('domain',d);await fetch('/api/shops/deactivate',{method:'POST',body:fd});}
|
||||
function openBulkActivateModal(){document.getElementById('bulkActivateModal').classList.add('open');toggleBulkModeOptions();}
|
||||
function openBulkActivateModal(){document.getElementById('bulkMonitorOnlyCheck').checked=false;document.getElementById('bulkBotModeCheck').checked=true;document.getElementById('bulkCountryModeCheck').checked=false;toggleBulkMonitorOnly();toggleBulkBotOptions();toggleBulkCountryOptions();document.getElementById('bulkActivateModal').classList.add('open');}
|
||||
function openBulkDeactivateModal(){document.getElementById('bulkDeactivateModal').classList.add('open');}
|
||||
function toggleBulkModeOptions(){const m=document.getElementById('bulkActivateMode').value;const isBot=m==='bot';document.getElementById('bulkGeoRegionGroup').style.display=m==='geoip'?'block':'none';document.getElementById('bulkMonitorOnlyGroup').style.display=isBot?'block':'none';document.getElementById('bulkRateLimitGroup').style.display=isBot&&!document.getElementById('bulkMonitorOnlyCheck').checked?'block':'none';document.getElementById('bulkBanDurationGroup').style.display=isBot&&!document.getElementById('bulkMonitorOnlyCheck').checked?'block':'none';}
|
||||
function toggleBulkMonitorOnly(){const isMonitor=document.getElementById('bulkMonitorOnlyCheck').checked;document.getElementById('bulkRateLimitGroup').style.display=isMonitor?'none':'block';document.getElementById('bulkBanDurationGroup').style.display=isMonitor?'none':'block';}
|
||||
function toggleBulkMonitorOnly(){const isMonitor=document.getElementById('bulkMonitorOnlyCheck').checked;document.getElementById('bulkBlockingOptions').style.display=isMonitor?'none':'block';}
|
||||
function toggleBulkBotOptions(){const isBot=document.getElementById('bulkBotModeCheck').checked;document.getElementById('bulkBotOptionsGroup').style.display=isBot?'block':'none';}
|
||||
function toggleBulkCountryOptions(){const isCountry=document.getElementById('bulkCountryModeCheck').checked;document.getElementById('bulkCountryOptionsGroup').style.display=isCountry?'block':'none';}
|
||||
function setBulkCountries(preset){const input=document.querySelector('#bulkActivateForm input[name="unlimited_countries"]');if(preset==='dach')input.value='de,at,ch';else if(preset==='eu_plus')input.value='de,at,ch,be,cy,ee,es,fi,fr,gb,gr,hr,ie,it,lt,lu,lv,mt,nl,pt,si,sk';}
|
||||
document.getElementById('bulkActivateForm').onsubmit=async e=>{e.preventDefault();if(!confirm('Shops aktivieren?'))return;closeModal('bulkActivateModal');toast('Aktivierung...','info');const r=await fetch('/api/shops/bulk-activate',{method:'POST',body:new FormData(e.target)});const d=await r.json();if(d.success)toast(d.activated+' aktiviert','success');};
|
||||
document.getElementById('bulkDeactivateForm').onsubmit=async e=>{e.preventDefault();if(!confirm('Shops deaktivieren?'))return;closeModal('bulkDeactivateModal');toast('Deaktivierung...','info');const r=await fetch('/api/shops/bulk-deactivate',{method:'POST',body:new FormData(e.target)});const d=await r.json();if(d.success)toast(d.deactivated+' deaktiviert','success');};
|
||||
function openPasswordModal(){document.getElementById('passwordModal').classList.add('open');}
|
||||
document.getElementById('passwordForm').onsubmit=async e=>{e.preventDefault();const r=await fetch('/api/change-password',{method:'POST',body:new FormData(e.target)});const d=await r.json();if(d.success){toast('Passwort geändert','success');closeModal('passwordModal');e.target.reset();}else toast(d.error,'error');};
|
||||
function openAllShopsModal(){document.getElementById('allShopsModal').classList.add('open');sortAllShops('req_per_min');}
|
||||
function sortAllShops(by){currentSortBy=by;document.getElementById('sortByReq').className='btn'+(by==='req_per_min'?' btn-primary':' btn-secondary');document.getElementById('sortByBans').className='btn'+(by==='active_bans'?' btn-primary':' btn-secondary');if(ws&&ws.readyState===1)ws.send(JSON.stringify({type:'get_all_shops_sorted',data:{sort_by:by}}));}
|
||||
function renderAllShopsTable(l,by){document.getElementById('allShopsTable').innerHTML=l.map((s,i)=>'<tr onclick="openDetailModal(\\''+s.domain+'\\')" style="cursor:pointer"><td>'+(i+1)+'</td><td><strong>'+s.domain+'</strong></td><td>'+(s.agent_hostname||'-')+'</td><td><span class="status-badge status-'+(s.status||'inactive')+'">'+(s.status==='active'?'✅':'⭕')+'</span></td><td style="'+(by==='req_per_min'?'color:var(--accent);font-weight:600':'')+'">'+(s.req_per_min||0).toFixed(1)+'</td><td style="'+(by==='active_bans'?'color:var(--warning);font-weight:600':'')+'">'+(s.active_bans||0)+'</td><td>'+(s.link11?'🛡️':'⚡')+'</td></tr>').join('');}
|
||||
function openDetailModal(d){currentDetailShop=d;const s=shops[d];if(!s)return;document.getElementById('detailDomain').textContent=d;document.getElementById('detailDomainLink').href='https://'+d;document.getElementById('detailServer').textContent=s.agent_hostname||'-';document.getElementById('detailStatus').textContent=s.status==='active'?'✅ Aktiv':'⭕ Inaktiv';document.getElementById('detailMode').textContent=s.bot_monitor_only?'🔍 Monitor':s.mode==='bot'?'🤖 Bot':s.mode==='geoip'?'🛡️ JTL-WAFi':'-';document.getElementById('detailRegion').textContent=s.mode==='geoip'&&s.geo_region?s.geo_region.toUpperCase():(s.mode==='bot'?'NONE':'-');document.getElementById('detailRateLimit').textContent=s.bot_monitor_only?'- (Monitor)':s.rate_limit?s.rate_limit+'/min':'-';document.getElementById('detailBanDuration').textContent=s.bot_monitor_only?'- (Monitor)':s.ban_duration?(s.ban_duration>=60?Math.round(s.ban_duration/60)+' min':s.ban_duration+'s'):'-';document.getElementById('detailRuntime').textContent=formatRuntime(s.runtime_minutes);const st=s.stats||{};document.getElementById('detailReqMin').textContent=(st.req_per_min||0).toFixed(1);document.getElementById('detailActiveBans').textContent=s.bot_monitor_only?'-':st.active_bans||0;document.getElementById('detailTotalBans').textContent=s.bot_monitor_only?'-':st.total_bans||0;document.getElementById('detailTopBots').innerHTML=Object.entries(st.top_bots||{}).sort((a,b)=>b[1]-a[1]).map(([n,c])=>'<div class="bot-item"><span>'+n+'</span><span>'+c+'</span></div>').join('')||'<div style="color:var(--text-secondary);padding:8px">Keine Daten</div>';document.getElementById('detailBannedBots').innerHTML=s.bot_monitor_only?'<div style="color:var(--text-secondary);padding:8px">Monitor-Only (keine Bans)</div>':(st.banned_bots||[]).map(n=>'<div class="bot-item"><span>🚫 '+n+'</span></div>').join('')||'<div style="color:var(--text-secondary);padding:8px">Keine Bans</div>';document.getElementById('detailBtnDeactivate').style.display=s.status==='active'?'inline-block':'none';document.getElementById('rateLimitFormArea').style.display='none';document.getElementById('detailRateLimitInput').value=s.rate_limit||30;const bd=s.ban_duration||300;document.getElementById('detailBanDurationInput').value=[60,300,600,1800,3600,86400].includes(bd)?bd:300;document.getElementById('chartLegend').innerHTML='';const cv=document.getElementById('requestChart');cv.getContext('2d').clearRect(0,0,cv.width,cv.height);if(ws&&ws.readyState===1)ws.send(JSON.stringify({type:'get_shop_history',data:{domain:d}}));document.getElementById('detailModal').classList.add('open');}
|
||||
function renderAllShopsTable(l,by){document.getElementById('allShopsTable').innerHTML=l.map((s,i)=>{const bans=(s.active_bot_bans||0)+(s.active_country_bans||0);return '<tr onclick="openDetailModal(\\''+s.domain+'\\')" style="cursor:pointer"><td>'+(i+1)+'</td><td><strong>'+s.domain+'</strong></td><td>'+(s.agent_hostname||'-')+'</td><td><span class="status-badge status-'+(s.status||'inactive')+'">'+(s.status==='active'?'✅':'⭕')+'</span></td><td style="'+(by==='req_per_min'?'color:var(--accent);font-weight:600':'')+'">'+(s.req_per_min||0).toFixed(1)+'</td><td style="'+(by==='active_bans'?'color:var(--warning);font-weight:600':'')+'">'+bans+'</td><td>'+(s.link11?'🛡️':'⚡')+'</td></tr>';}).join('');}
|
||||
function openDetailModal(d){currentDetailShop=d;const s=shops[d];if(!s)return;document.getElementById('detailDomain').textContent=d;document.getElementById('detailDomainLink').href='https://'+d;document.getElementById('detailServer').textContent=s.agent_hostname||'-';document.getElementById('detailStatus').textContent=s.status==='active'?'✅ Aktiv':'⭕ Inaktiv';const modeStr=s.monitor_only?'🔍 Monitor':((s.bot_mode?'🤖 Bot':'')+(s.country_mode?' 🌍 Country':''))||'-';document.getElementById('detailMode').textContent=modeStr;const unlimitedStr=s.unlimited_countries&&s.unlimited_countries.length>0?s.unlimited_countries.map(c=>c.toUpperCase()).join(', '):(s.country_mode?'(keine)':'-');document.getElementById('detailRegion').textContent=unlimitedStr;const rlStr=s.monitor_only?'- (Monitor)':(s.bot_mode&&s.bot_rate_limit?'Bot:'+s.bot_rate_limit+'/min':'')+(s.country_mode&&s.country_rate_limit?(s.bot_mode?' | ':'')+'Country:'+s.country_rate_limit+'/min':'')||'-';document.getElementById('detailRateLimit').textContent=rlStr;const bdStr=s.monitor_only?'- (Monitor)':(s.bot_mode&&s.bot_ban_duration?'Bot:'+(s.bot_ban_duration>=60?Math.round(s.bot_ban_duration/60)+'m':s.bot_ban_duration+'s'):'')+(s.country_mode&&s.country_ban_duration?(s.bot_mode?' | ':'')+'Country:'+(s.country_ban_duration>=60?Math.round(s.country_ban_duration/60)+'m':s.country_ban_duration+'s'):'')||'-';document.getElementById('detailBanDuration').textContent=bdStr;document.getElementById('detailRuntime').textContent=formatRuntime(s.runtime_minutes);const st=s.stats||{};document.getElementById('detailReqMin').textContent=(st.req_per_min||0).toFixed(1);const activeBans=s.monitor_only?'-':((st.active_bot_bans||0)+(st.active_country_bans||0));document.getElementById('detailActiveBans').textContent=activeBans;const totalBans=s.monitor_only?'-':((st.bot_bans||0)+(st.country_bans||0));document.getElementById('detailTotalBans').textContent=totalBans;document.getElementById('detailTopBots').innerHTML=Object.entries(st.top_bots||{}).sort((a,b)=>b[1]-a[1]).map(([n,c])=>'<div class="bot-item"><span>'+n+'</span><span>'+c+'</span></div>').join('')||'<div style="color:var(--text-secondary);padding:8px">Keine Daten</div>';const bannedList=(st.banned_bots||[]).concat(st.banned_countries||[]);document.getElementById('detailBannedBots').innerHTML=s.monitor_only?'<div style="color:var(--text-secondary);padding:8px">Monitor-Only (keine Bans)</div>':bannedList.map(n=>'<div class="bot-item"><span>🚫 '+n+'</span></div>').join('')||'<div style="color:var(--text-secondary);padding:8px">Keine Bans</div>';document.getElementById('detailBtnDeactivate').style.display=s.status==='active'?'inline-block':'none';document.getElementById('rateLimitFormArea').style.display='none';document.getElementById('detailRateLimitInput').value=s.bot_rate_limit||30;const bd=s.bot_ban_duration||300;document.getElementById('detailBanDurationInput').value=[60,300,600,1800,3600,86400].includes(bd)?bd:300;document.getElementById('chartLegend').innerHTML='';const cv=document.getElementById('requestChart');cv.getContext('2d').clearRect(0,0,cv.width,cv.height);if(ws&&ws.readyState===1)ws.send(JSON.stringify({type:'get_shop_history',data:{domain:d}}));document.getElementById('detailModal').classList.add('open');}
|
||||
function updateBotChart(data){const cv=document.getElementById('requestChart'),ctx=cv.getContext('2d'),ct=cv.parentElement,w=ct.clientWidth-32,h=230;cv.width=w;cv.height=h;ctx.clearRect(0,0,w,h);const bh=data.bot_history||{},bn=Object.keys(bh).slice(0,10);if(bn.length===0){ctx.fillStyle='#a0a0b0';ctx.font='14px sans-serif';ctx.fillText('Noch keine Bot-Daten',w/2-60,h/2);return;}let ts=new Set();bn.forEach(b=>bh[b].forEach(p=>ts.add(p.timestamp)));ts=[...ts].sort();if(ts.length<2){ctx.fillStyle='#a0a0b0';ctx.font='14px sans-serif';ctx.fillText('Warte auf Daten...',w/2-50,h/2);return;}let mx=1;bn.forEach(b=>bh[b].forEach(p=>{if(p.count>mx)mx=p.count;}));const pd={t:20,r:20,b:40,l:50},cW=w-pd.l-pd.r,cH=h-pd.t-pd.b;ctx.strokeStyle='rgba(255,255,255,0.1)';ctx.lineWidth=1;for(let i=0;i<=4;i++){const y=pd.t+(cH/4)*i;ctx.beginPath();ctx.moveTo(pd.l,y);ctx.lineTo(w-pd.r,y);ctx.stroke();ctx.fillStyle='#a0a0b0';ctx.font='10px sans-serif';ctx.fillText(Math.round(mx-(mx/4)*i),5,y+4);}const step=Math.max(1,Math.floor(ts.length/6));ctx.fillStyle='#a0a0b0';ctx.font='10px sans-serif';for(let i=0;i<ts.length;i+=step){const t=ts[i].split(' ')[1]?.substring(0,5)||ts[i],x=pd.l+(cW/(ts.length-1))*i;ctx.fillText(t,x-15,h-10);}const lg=document.getElementById('chartLegend');lg.innerHTML='';bn.forEach((bot,idx)=>{const c=BOT_COLORS[idx%BOT_COLORS.length],pts=bh[bot];ctx.strokeStyle=c;ctx.lineWidth=2;ctx.beginPath();pts.forEach((p,i)=>{const ti=ts.indexOf(p.timestamp),x=pd.l+(cW/(ts.length-1))*ti,y=pd.t+cH-(p.count/mx)*cH;i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);});ctx.stroke();lg.innerHTML+='<div class="legend-item"><div class="legend-color" style="background:'+c+'"></div><span>'+bot+'</span></div>';});}
|
||||
function openLogs(d){currentLogsShop=d;document.getElementById('logsShop').textContent=d;document.getElementById('logsContent').innerHTML='<div style="color:#666">Warte auf Logs...</div>';document.getElementById('logsPanel').classList.add('open');if(ws&&ws.readyState===1)ws.send(JSON.stringify({type:'log.subscribe',data:{shop:d}}));}
|
||||
function closeLogs(){if(currentLogsShop&&ws&&ws.readyState===1)ws.send(JSON.stringify({type:'log.unsubscribe',data:{shop:currentLogsShop}}));currentLogsShop=null;document.getElementById('logsPanel').classList.remove('open');}
|
||||
@@ -1341,8 +1456,8 @@ def get_dashboard_html() -> str:
|
||||
function toast(msg,type='info'){const c=document.getElementById('toastContainer'),t=document.createElement('div');t.className='toast '+type;t.innerHTML='<span>'+msg+'</span>';c.appendChild(t);setTimeout(()=>t.remove(),4000);}
|
||||
async function detailDeactivate(){if(!currentDetailShop)return;if(!confirm('Shop '+currentDetailShop+' deaktivieren?'))return;const fd=new FormData();fd.append('domain',currentDetailShop);toast('Deaktiviere...','info');await fetch('/api/shops/deactivate',{method:'POST',body:fd});closeModal('detailModal');}
|
||||
function toggleRateLimitForm(){const area=document.getElementById('rateLimitFormArea');area.style.display=area.style.display==='none'?'block':'none';}
|
||||
async function applyRateLimit(){if(!currentDetailShop)return;const rateLimit=document.getElementById('detailRateLimitInput').value;const banDuration=document.getElementById('detailBanDurationInput').value;if(!confirm('Rate-Limit aktivieren: '+rateLimit+' Req/min?'))return;const s=shops[currentDetailShop];toast('Wechsle zu Rate-Limit...','info');if(s&&s.status==='active'){const dfd=new FormData();dfd.append('domain',currentDetailShop);await fetch('/api/shops/deactivate',{method:'POST',body:dfd});await new Promise(r=>setTimeout(r,500));}const fd=new FormData();fd.append('domain',currentDetailShop);fd.append('mode','bot');fd.append('rate_limit',rateLimit);fd.append('ban_duration',banDuration);await fetch('/api/shops/activate',{method:'POST',body:fd});closeModal('detailModal');}
|
||||
async function detailSwitchMode(mode){if(!currentDetailShop)return;const s=shops[currentDetailShop];const modeNames={'bot-monitor':'🔍 Monitor','geoip-dach':'🇩🇪 DACH','geoip-eur':'🇪🇺 Europa'};if(!confirm('Modus wechseln zu '+modeNames[mode]+'?'))return;toast('Wechsle Modus...','info');if(s&&s.status==='active'){const dfd=new FormData();dfd.append('domain',currentDetailShop);await fetch('/api/shops/deactivate',{method:'POST',body:dfd});await new Promise(r=>setTimeout(r,500));}const fd=new FormData();fd.append('domain',currentDetailShop);if(mode==='bot-monitor'){fd.append('mode','bot');fd.append('bot_monitor_only','true');}else if(mode==='geoip-dach'){fd.append('mode','geoip');fd.append('geo_region','dach');}else if(mode==='geoip-eur'){fd.append('mode','geoip');fd.append('geo_region','eurozone');}await fetch('/api/shops/activate',{method:'POST',body:fd});closeModal('detailModal');}
|
||||
async function applyRateLimit(){if(!currentDetailShop)return;const rateLimit=document.getElementById('detailRateLimitInput').value;const banDuration=document.getElementById('detailBanDurationInput').value;if(!confirm('Rate-Limit aktivieren: '+rateLimit+' Req/min?'))return;const s=shops[currentDetailShop];toast('Wechsle zu Rate-Limit...','info');if(s&&s.status==='active'){const dfd=new FormData();dfd.append('domain',currentDetailShop);await fetch('/api/shops/deactivate',{method:'POST',body:dfd});await new Promise(r=>setTimeout(r,500));}const fd=new FormData();fd.append('domain',currentDetailShop);fd.append('bot_mode','true');fd.append('bot_rate_limit',rateLimit);fd.append('bot_ban_duration',banDuration);fd.append('country_mode','false');fd.append('monitor_only','false');await fetch('/api/shops/activate',{method:'POST',body:fd});closeModal('detailModal');}
|
||||
async function detailSwitchMode(mode){if(!currentDetailShop)return;const s=shops[currentDetailShop];const modeNames={'bot-monitor':'🔍 Monitor','country-dach':'🇩🇪 DACH','country-eu':'🇪🇺 EU+'};if(!confirm('Modus wechseln zu '+modeNames[mode]+'?'))return;toast('Wechsle Modus...','info');if(s&&s.status==='active'){const dfd=new FormData();dfd.append('domain',currentDetailShop);await fetch('/api/shops/deactivate',{method:'POST',body:dfd});await new Promise(r=>setTimeout(r,500));}const fd=new FormData();fd.append('domain',currentDetailShop);if(mode==='bot-monitor'){fd.append('monitor_only','true');}else if(mode==='country-dach'){fd.append('bot_mode','true');fd.append('bot_rate_limit','30');fd.append('bot_ban_duration','300');fd.append('country_mode','true');fd.append('country_rate_limit','100');fd.append('country_ban_duration','600');fd.append('unlimited_countries','de,at,ch');}else if(mode==='country-eu'){fd.append('bot_mode','true');fd.append('bot_rate_limit','30');fd.append('bot_ban_duration','300');fd.append('country_mode','true');fd.append('country_rate_limit','100');fd.append('country_ban_duration','600');fd.append('unlimited_countries','de,at,ch,be,cy,ee,es,fi,fr,gb,gr,hr,ie,it,lt,lu,lv,mt,nl,pt,si,sk');}await fetch('/api/shops/activate',{method:'POST',body:fd});closeModal('detailModal');}
|
||||
connect();
|
||||
</script>
|
||||
</body>
|
||||
@@ -1351,7 +1466,7 @@ def get_dashboard_html() -> str:
|
||||
|
||||
def create_systemd_service():
|
||||
service = """[Unit]
|
||||
Description=JTL-WAFi Dashboard v2.3 (WebSocket)
|
||||
Description=JTL-WAFi Dashboard v2.5 (WebSocket)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
|
||||
Reference in New Issue
Block a user