jtl-wafi-dashboard.py aktualisiert

This commit is contained in:
2025-12-22 14:11:54 +01:00
parent fd9dc5d79c
commit 0a62fabacd

View File

@@ -113,7 +113,7 @@ class ShopData:
class DataStore: class DataStore:
"""In-Memory Datenspeicher - Thread-safe durch asyncio.""" """In-Memory Datenspeicher - Thread-safe durch asyncio."""
def __init__(self): def __init__(self):
self.agents: Dict[str, AgentData] = {} self.agents: Dict[str, AgentData] = {}
self.shops: Dict[str, ShopData] = {} self.shops: Dict[str, ShopData] = {}
@@ -121,11 +121,11 @@ class DataStore:
self._password_hash: Optional[str] = None self._password_hash: Optional[str] = None
self._tokens: Dict[str, str] = {} # agent_id -> token self._tokens: Dict[str, str] = {} # agent_id -> token
self._load_persistent_data() self._load_persistent_data()
def _load_persistent_data(self): def _load_persistent_data(self):
"""Lädt persistente Daten (Passwort, Tokens).""" """Lädt persistente Daten (Passwort, Tokens)."""
os.makedirs(DATA_DIR, exist_ok=True) os.makedirs(DATA_DIR, exist_ok=True)
# Config laden (Passwort) # Config laden (Passwort)
if os.path.exists(CONFIG_FILE): if os.path.exists(CONFIG_FILE):
try: try:
@@ -134,7 +134,7 @@ class DataStore:
self._password_hash = config.get('password_hash') self._password_hash = config.get('password_hash')
except: except:
pass pass
# Tokens laden # Tokens laden
if os.path.exists(TOKENS_FILE): if os.path.exists(TOKENS_FILE):
try: try:
@@ -142,37 +142,37 @@ class DataStore:
self._tokens = json.load(f) self._tokens = json.load(f)
except: except:
pass pass
def _save_config(self): def _save_config(self):
"""Speichert Config.""" """Speichert Config."""
with open(CONFIG_FILE, 'w') as f: with open(CONFIG_FILE, 'w') as f:
json.dump({'password_hash': self._password_hash}, f) json.dump({'password_hash': self._password_hash}, f)
def _save_tokens(self): def _save_tokens(self):
"""Speichert Tokens.""" """Speichert Tokens."""
with open(TOKENS_FILE, 'w') as f: with open(TOKENS_FILE, 'w') as f:
json.dump(self._tokens, f) json.dump(self._tokens, f)
# === Password === # === Password ===
def get_password_hash(self) -> Optional[str]: def get_password_hash(self) -> Optional[str]:
return self._password_hash return self._password_hash
def set_password(self, password: str): def set_password(self, password: str):
self._password_hash = hashlib.sha256(password.encode()).hexdigest() self._password_hash = hashlib.sha256(password.encode()).hexdigest()
self._save_config() self._save_config()
def verify_password(self, password: str) -> bool: def verify_password(self, password: str) -> bool:
if not self._password_hash: if not self._password_hash:
return False return False
return hashlib.sha256(password.encode()).hexdigest() == self._password_hash return hashlib.sha256(password.encode()).hexdigest() == self._password_hash
# === Sessions === # === Sessions ===
def create_session(self, username: str) -> str: def create_session(self, username: str) -> str:
token = secrets.token_hex(32) token = secrets.token_hex(32)
expires = (utc_now() + timedelta(hours=24)).isoformat() expires = (utc_now() + timedelta(hours=24)).isoformat()
self.sessions[token] = {'username': username, 'expires': expires} self.sessions[token] = {'username': username, 'expires': expires}
return token return token
def verify_session(self, token: str) -> Optional[str]: def verify_session(self, token: str) -> Optional[str]:
if not token or token not in self.sessions: if not token or token not in self.sessions:
return None return None
@@ -182,18 +182,18 @@ class DataStore:
del self.sessions[token] del self.sessions[token]
return None return None
return session['username'] return session['username']
def delete_session(self, token: str): def delete_session(self, token: str):
self.sessions.pop(token, None) self.sessions.pop(token, None)
# === Agent Tokens === # === Agent Tokens ===
def get_agent_token(self, agent_id: str) -> Optional[str]: def get_agent_token(self, agent_id: str) -> Optional[str]:
return self._tokens.get(agent_id) return self._tokens.get(agent_id)
def set_agent_token(self, agent_id: str, token: str): def set_agent_token(self, agent_id: str, token: str):
self._tokens[agent_id] = token self._tokens[agent_id] = token
self._save_tokens() self._save_tokens()
# === Agents === # === Agents ===
def get_or_create_agent(self, agent_id: str, hostname: str) -> AgentData: def get_or_create_agent(self, agent_id: str, hostname: str) -> AgentData:
if agent_id not in self.agents: if agent_id not in self.agents:
@@ -208,10 +208,10 @@ class DataStore:
self.agents[agent_id].token = self._tokens[agent_id] self.agents[agent_id].token = self._tokens[agent_id]
self.agents[agent_id].status = 'online' self.agents[agent_id].status = 'online'
return self.agents[agent_id] return self.agents[agent_id]
def get_agent(self, agent_id: str) -> Optional[AgentData]: def get_agent(self, agent_id: str) -> Optional[AgentData]:
return self.agents.get(agent_id) return self.agents.get(agent_id)
def get_all_agents(self) -> List[Dict]: def get_all_agents(self) -> List[Dict]:
result = [] result = []
for agent in self.agents.values(): for agent in self.agents.values():
@@ -224,7 +224,7 @@ class DataStore:
status = 'offline' status = 'offline'
except: except:
pass pass
result.append({ result.append({
'id': agent.id, 'id': agent.id,
'hostname': agent.hostname, 'hostname': agent.hostname,
@@ -239,14 +239,14 @@ class DataStore:
'shops_active': agent.shops_active 'shops_active': agent.shops_active
}) })
return result return result
# === Shops === # === Shops ===
def update_shop(self, agent_id: str, agent_hostname: str, shop_data: Dict) -> ShopData: def update_shop(self, agent_id: str, agent_hostname: str, shop_data: Dict) -> ShopData:
domain = shop_data.get('domain') domain = shop_data.get('domain')
if domain not in self.shops: if domain not in self.shops:
self.shops[domain] = ShopData(domain=domain, agent_id=agent_id) self.shops[domain] = ShopData(domain=domain, agent_id=agent_id)
shop = self.shops[domain] shop = self.shops[domain]
shop.agent_id = agent_id shop.agent_id = agent_id
shop.agent_hostname = agent_hostname shop.agent_hostname = agent_hostname
@@ -260,7 +260,7 @@ class DataStore:
shop.link11_ip = shop_data.get('link11_ip', '') shop.link11_ip = shop_data.get('link11_ip', '')
shop.activated = shop_data.get('activated', '') shop.activated = shop_data.get('activated', '')
shop.runtime_minutes = shop_data.get('runtime_minutes', 0) shop.runtime_minutes = shop_data.get('runtime_minutes', 0)
# Stats # Stats
stats = shop_data.get('stats', {}) stats = shop_data.get('stats', {})
if stats: if stats:
@@ -273,20 +273,20 @@ class DataStore:
shop.unique_bots = stats.get('unique_bots', 0) shop.unique_bots = stats.get('unique_bots', 0)
shop.top_bots = stats.get('top_bots', {}) shop.top_bots = stats.get('top_bots', {})
shop.top_ips = stats.get('top_ips', {}) shop.top_ips = stats.get('top_ips', {})
# History für Graph # History für Graph
shop.history.append({ shop.history.append({
'timestamp': utc_now_str(), 'timestamp': utc_now_str(),
'req_per_min': shop.req_per_min, 'req_per_min': shop.req_per_min,
'active_bans': shop.active_bans 'active_bans': shop.active_bans
}) })
return shop return shop
def update_shop_stats(self, domain: str, stats: Dict): def update_shop_stats(self, domain: str, stats: Dict):
if domain not in self.shops: if domain not in self.shops:
return return
shop = self.shops[domain] shop = self.shops[domain]
shop.log_entries = stats.get('log_entries', shop.log_entries) shop.log_entries = stats.get('log_entries', shop.log_entries)
shop.total_bans = stats.get('total_bans', shop.total_bans) shop.total_bans = stats.get('total_bans', shop.total_bans)
@@ -297,16 +297,16 @@ class DataStore:
shop.unique_bots = stats.get('unique_bots', shop.unique_bots) shop.unique_bots = stats.get('unique_bots', shop.unique_bots)
shop.top_bots = stats.get('top_bots', shop.top_bots) shop.top_bots = stats.get('top_bots', shop.top_bots)
shop.top_ips = stats.get('top_ips', shop.top_ips) shop.top_ips = stats.get('top_ips', shop.top_ips)
timestamp = utc_now_str() timestamp = utc_now_str()
# Gesamt-History # Gesamt-History
shop.history.append({ shop.history.append({
'timestamp': timestamp, 'timestamp': timestamp,
'req_per_min': shop.req_per_min, 'req_per_min': shop.req_per_min,
'active_bans': shop.active_bans 'active_bans': shop.active_bans
}) })
# Bot-History aktualisieren # Bot-History aktualisieren
top_bots = stats.get('top_bots', {}) top_bots = stats.get('top_bots', {})
for bot_name, count in top_bots.items(): for bot_name, count in top_bots.items():
@@ -316,10 +316,10 @@ class DataStore:
'timestamp': timestamp, 'timestamp': timestamp,
'count': count 'count': count
}) })
def get_shop(self, domain: str) -> Optional[ShopData]: def get_shop(self, domain: str) -> Optional[ShopData]:
return self.shops.get(domain) return self.shops.get(domain)
def get_all_shops(self) -> List[Dict]: def get_all_shops(self) -> List[Dict]:
result = [] result = []
for shop in self.shops.values(): for shop in self.shops.values():
@@ -350,22 +350,22 @@ class DataStore:
} }
}) })
return result return result
def get_shop_history(self, domain: str) -> Dict: def get_shop_history(self, domain: str) -> Dict:
shop = self.shops.get(domain) shop = self.shops.get(domain)
if not shop: if not shop:
return {'history': [], 'bot_history': {}} return {'history': [], 'bot_history': {}}
# Bot-History in JSON-serialisierbares Format # Bot-History in JSON-serialisierbares Format
bot_history = {} bot_history = {}
for bot_name, history in shop.bot_history.items(): for bot_name, history in shop.bot_history.items():
bot_history[bot_name] = list(history) bot_history[bot_name] = list(history)
return { return {
'history': list(shop.history), 'history': list(shop.history),
'bot_history': bot_history 'bot_history': bot_history
} }
def get_top_shops(self, limit: int = 10, sort_by: str = 'req_per_min') -> List[Dict]: 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 req_per_min oder active_bans zurück."""
shops_list = [] shops_list = []
@@ -378,30 +378,30 @@ class DataStore:
'active_bans': shop.active_bans, 'active_bans': shop.active_bans,
'link11': shop.link11 'link11': shop.link11
}) })
# Sortieren # Sortieren
if sort_by == 'active_bans': if sort_by == 'active_bans':
shops_list.sort(key=lambda x: x['active_bans'], reverse=True) shops_list.sort(key=lambda x: x['active_bans'], reverse=True)
else: else:
shops_list.sort(key=lambda x: x['req_per_min'], reverse=True) shops_list.sort(key=lambda x: x['req_per_min'], reverse=True)
if limit: if limit:
return shops_list[:limit] return shops_list[:limit]
return shops_list return shops_list
def get_stats(self) -> Dict: def get_stats(self) -> Dict:
agents_online = sum(1 for a in self.agents.values() agents_online = sum(1 for a in self.agents.values()
if a.approved and a.status == 'online') if a.approved and a.status == 'online')
agents_pending = sum(1 for a in self.agents.values() if not a.approved) agents_pending = sum(1 for a in self.agents.values() if not a.approved)
shops_active = sum(1 for s in self.shops.values() if s.status == 'active') shops_active = sum(1 for s in self.shops.values() if s.status == 'active')
shops_total = len(self.shops) shops_total = len(self.shops)
shops_link11 = sum(1 for s in self.shops.values() if s.link11) shops_link11 = sum(1 for s in self.shops.values() if s.link11)
shops_direct = shops_total - shops_link11 shops_direct = shops_total - shops_link11
req_per_min = sum(s.req_per_min for s in self.shops.values()) 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_bans = sum(s.active_bans for s in self.shops.values())
return { return {
'agents_online': agents_online, 'agents_online': agents_online,
'agents_pending': agents_pending, 'agents_pending': agents_pending,
@@ -423,12 +423,12 @@ store = DataStore()
# ============================================================================= # =============================================================================
def generate_ssl_certificate(): def generate_ssl_certificate():
os.makedirs(SSL_DIR, exist_ok=True) os.makedirs(SSL_DIR, exist_ok=True)
if os.path.exists(SSL_CERT) and os.path.exists(SSL_KEY): if os.path.exists(SSL_CERT) and os.path.exists(SSL_KEY):
return return
print("🔐 Generiere SSL-Zertifikat...") print("🔐 Generiere SSL-Zertifikat...")
try: try:
subprocess.run([ subprocess.run([
'openssl', 'req', '-x509', '-nodes', 'openssl', 'req', '-x509', '-nodes',
@@ -438,10 +438,10 @@ def generate_ssl_certificate():
'-out', SSL_CERT, '-out', SSL_CERT,
'-subj', '/CN=jtl-wafi/O=JTL-WAFi/C=DE' '-subj', '/CN=jtl-wafi/O=JTL-WAFi/C=DE'
], check=True, capture_output=True) ], check=True, capture_output=True)
os.chmod(SSL_KEY, 0o600) os.chmod(SSL_KEY, 0o600)
os.chmod(SSL_CERT, 0o644) os.chmod(SSL_CERT, 0o644)
print(f"✅ SSL-Zertifikat generiert: {SSL_CERT}") print(f"✅ SSL-Zertifikat generiert: {SSL_CERT}")
except Exception as e: except Exception as e:
print(f"❌ SSL Fehler: {e}") print(f"❌ SSL Fehler: {e}")
@@ -456,7 +456,7 @@ class ConnectionManager:
self.agent_connections: Dict[str, WebSocket] = {} self.agent_connections: Dict[str, WebSocket] = {}
self.browser_connections: Set[WebSocket] = set() self.browser_connections: Set[WebSocket] = set()
self.agent_hostnames: Dict[str, str] = {} self.agent_hostnames: Dict[str, str] = {}
async def connect_agent(self, agent_id: str, hostname: str, websocket: WebSocket): async def connect_agent(self, agent_id: str, hostname: str, websocket: WebSocket):
# Alte Verbindung schließen # Alte Verbindung schließen
if agent_id in self.agent_connections: if agent_id in self.agent_connections:
@@ -464,36 +464,36 @@ class ConnectionManager:
await self.agent_connections[agent_id].close() await self.agent_connections[agent_id].close()
except: except:
pass pass
self.agent_connections[agent_id] = websocket self.agent_connections[agent_id] = websocket
self.agent_hostnames[agent_id] = hostname self.agent_hostnames[agent_id] = hostname
print(f"✅ Agent verbunden: {hostname}") print(f"✅ Agent verbunden: {hostname}")
async def disconnect_agent(self, agent_id: str): async def disconnect_agent(self, agent_id: str):
self.agent_connections.pop(agent_id, None) self.agent_connections.pop(agent_id, None)
hostname = self.agent_hostnames.pop(agent_id, "unknown") hostname = self.agent_hostnames.pop(agent_id, "unknown")
# Status updaten # Status updaten
agent = store.get_agent(agent_id) agent = store.get_agent(agent_id)
if agent: if agent:
agent.status = 'offline' agent.status = 'offline'
agent.last_seen = utc_now_str() agent.last_seen = utc_now_str()
print(f"❌ Agent getrennt: {hostname}") print(f"❌ Agent getrennt: {hostname}")
await self.broadcast_to_browsers({ await self.broadcast_to_browsers({
'type': 'agent.offline', 'type': 'agent.offline',
'data': {'agent_id': agent_id, 'hostname': hostname} 'data': {'agent_id': agent_id, 'hostname': hostname}
}) })
async def connect_browser(self, websocket: WebSocket): async def connect_browser(self, websocket: WebSocket):
self.browser_connections.add(websocket) self.browser_connections.add(websocket)
print(f"🌐 Browser verbunden (Total: {len(self.browser_connections)})") print(f"🌐 Browser verbunden (Total: {len(self.browser_connections)})")
async def disconnect_browser(self, websocket: WebSocket): async def disconnect_browser(self, websocket: WebSocket):
self.browser_connections.discard(websocket) self.browser_connections.discard(websocket)
print(f"🌐 Browser getrennt (Total: {len(self.browser_connections)})") print(f"🌐 Browser getrennt (Total: {len(self.browser_connections)})")
async def send_to_agent(self, agent_id: str, message: Dict): async def send_to_agent(self, agent_id: str, message: Dict):
ws = self.agent_connections.get(agent_id) ws = self.agent_connections.get(agent_id)
if ws: if ws:
@@ -501,7 +501,7 @@ class ConnectionManager:
await ws.send_json(message) await ws.send_json(message)
except Exception as e: except Exception as e:
print(f"Send to agent error: {e}") print(f"Send to agent error: {e}")
async def broadcast_to_browsers(self, message: Dict): async def broadcast_to_browsers(self, message: Dict):
dead = set() dead = set()
for ws in self.browser_connections: for ws in self.browser_connections:
@@ -510,11 +510,11 @@ class ConnectionManager:
except: except:
dead.add(ws) dead.add(ws)
self.browser_connections -= dead self.browser_connections -= dead
def get_agent_for_shop(self, domain: str) -> Optional[str]: def get_agent_for_shop(self, domain: str) -> Optional[str]:
shop = store.get_shop(domain) shop = store.get_shop(domain)
return shop.agent_id if shop else None return shop.agent_id if shop else None
def is_agent_connected(self, agent_id: str) -> bool: def is_agent_connected(self, agent_id: str) -> bool:
return agent_id in self.agent_connections return agent_id in self.agent_connections
@@ -532,7 +532,7 @@ async def lifespan(app: FastAPI):
app = FastAPI(title="JTL-WAFi Dashboard", version=VERSION, lifespan=lifespan) app = FastAPI(title="JTL-WAFi Dashboard", version=VERSION, lifespan=lifespan)
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY, session_cookie="geoip_session", max_age=86400) app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY, session_cookie="jtl_wafi_session", max_age=86400)
# ============================================================================= # =============================================================================
@@ -550,14 +550,14 @@ async def get_current_user(request: Request) -> Optional[str]:
async def agent_websocket(websocket: WebSocket): async def agent_websocket(websocket: WebSocket):
await websocket.accept() await websocket.accept()
agent_id = None agent_id = None
try: try:
async for message in websocket.iter_text(): async for message in websocket.iter_text():
try: try:
data = json.loads(message) data = json.loads(message)
event_type = data.get('type') event_type = data.get('type')
event_data = data.get('data', {}) event_data = data.get('data', {})
if event_type == 'agent.connect': if event_type == 'agent.connect':
agent_id = event_data.get('agent_id') agent_id = event_data.get('agent_id')
hostname = event_data.get('hostname') hostname = event_data.get('hostname')
@@ -565,7 +565,7 @@ async def agent_websocket(websocket: WebSocket):
version = event_data.get('version', '') version = event_data.get('version', '')
os_info = event_data.get('os_info', {}) os_info = event_data.get('os_info', {})
shops_summary = event_data.get('shops_summary', {}) shops_summary = event_data.get('shops_summary', {})
# Agent registrieren # Agent registrieren
agent = store.get_or_create_agent(agent_id, hostname) agent = store.get_or_create_agent(agent_id, hostname)
agent.hostname = hostname agent.hostname = hostname
@@ -574,16 +574,16 @@ async def agent_websocket(websocket: WebSocket):
agent.last_seen = utc_now_str() agent.last_seen = utc_now_str()
agent.shops_total = shops_summary.get('total', 0) agent.shops_total = shops_summary.get('total', 0)
agent.shops_active = shops_summary.get('active', 0) agent.shops_active = shops_summary.get('active', 0)
# Token prüfen # Token prüfen
stored_token = store.get_agent_token(agent_id) stored_token = store.get_agent_token(agent_id)
if stored_token and token == stored_token: if stored_token and token == stored_token:
agent.approved = True agent.approved = True
agent.token = stored_token agent.token = stored_token
agent.status = 'online' agent.status = 'online'
await manager.connect_agent(agent_id, hostname, websocket) await manager.connect_agent(agent_id, hostname, websocket)
# Browser informieren # Browser informieren
await manager.broadcast_to_browsers({ await manager.broadcast_to_browsers({
'type': 'agent.online' if agent.approved else 'agent.pending', 'type': 'agent.online' if agent.approved else 'agent.pending',
@@ -597,21 +597,21 @@ async def agent_websocket(websocket: WebSocket):
'shops_active': shops_summary.get('active', 0) 'shops_active': shops_summary.get('active', 0)
} }
}) })
# Token senden wenn approved # Token senden wenn approved
if agent.approved: if agent.approved:
await websocket.send_json({ await websocket.send_json({
'type': 'auth.approved', 'type': 'auth.approved',
'data': {'token': agent.token} 'data': {'token': agent.token}
}) })
elif event_type == 'agent.heartbeat': elif event_type == 'agent.heartbeat':
if agent_id: if agent_id:
agent = store.get_agent(agent_id) agent = store.get_agent(agent_id)
if agent: if agent:
system = event_data.get('system', {}) system = event_data.get('system', {})
shops_summary = event_data.get('shops_summary', {}) shops_summary = event_data.get('shops_summary', {})
agent.last_seen = utc_now_str() agent.last_seen = utc_now_str()
agent.load_1m = system.get('load_1m', 0) agent.load_1m = system.get('load_1m', 0)
agent.load_5m = system.get('load_5m', 0) agent.load_5m = system.get('load_5m', 0)
@@ -619,7 +619,7 @@ async def agent_websocket(websocket: WebSocket):
agent.uptime_seconds = system.get('uptime_seconds', 0) agent.uptime_seconds = system.get('uptime_seconds', 0)
agent.shops_total = shops_summary.get('total', 0) agent.shops_total = shops_summary.get('total', 0)
agent.shops_active = shops_summary.get('active', 0) agent.shops_active = shops_summary.get('active', 0)
await manager.broadcast_to_browsers({ await manager.broadcast_to_browsers({
'type': 'agent.update', 'type': 'agent.update',
'data': { 'data': {
@@ -629,16 +629,16 @@ async def agent_websocket(websocket: WebSocket):
'shops_summary': shops_summary 'shops_summary': shops_summary
} }
}) })
elif event_type == 'shop.full_update': elif event_type == 'shop.full_update':
if agent_id: if agent_id:
agent = store.get_agent(agent_id) agent = store.get_agent(agent_id)
hostname = agent.hostname if agent else '' hostname = agent.hostname if agent else ''
shops = event_data.get('shops', []) shops = event_data.get('shops', [])
for shop_data in shops: for shop_data in shops:
store.update_shop(agent_id, hostname, shop_data) store.update_shop(agent_id, hostname, shop_data)
await manager.broadcast_to_browsers({ await manager.broadcast_to_browsers({
'type': 'shop.full_update', 'type': 'shop.full_update',
'data': { 'data': {
@@ -647,42 +647,42 @@ async def agent_websocket(websocket: WebSocket):
'shops': shops 'shops': shops
} }
}) })
elif event_type == 'shop.stats': elif event_type == 'shop.stats':
if agent_id: if agent_id:
domain = event_data.get('domain') domain = event_data.get('domain')
stats = event_data.get('stats', {}) stats = event_data.get('stats', {})
store.update_shop_stats(domain, stats) store.update_shop_stats(domain, stats)
await manager.broadcast_to_browsers({ await manager.broadcast_to_browsers({
'type': 'shop.stats', 'type': 'shop.stats',
'data': {'domain': domain, 'stats': stats} 'data': {'domain': domain, 'stats': stats}
}) })
elif event_type == 'log.entry': elif event_type == 'log.entry':
await manager.broadcast_to_browsers({ await manager.broadcast_to_browsers({
'type': 'log.entry', 'type': 'log.entry',
'data': event_data 'data': event_data
}) })
elif event_type == 'bot.banned': elif event_type == 'bot.banned':
await manager.broadcast_to_browsers({ await manager.broadcast_to_browsers({
'type': 'bot.banned', 'type': 'bot.banned',
'data': event_data 'data': event_data
}) })
elif event_type == 'command.result': elif event_type == 'command.result':
await manager.broadcast_to_browsers({ await manager.broadcast_to_browsers({
'type': 'command.result', 'type': 'command.result',
'data': event_data 'data': event_data
}) })
except json.JSONDecodeError: except json.JSONDecodeError:
pass pass
except Exception as e: except Exception as e:
print(f"Agent message error: {e}") print(f"Agent message error: {e}")
except WebSocketDisconnect: except WebSocketDisconnect:
pass pass
except Exception as e: except Exception as e:
@@ -699,7 +699,7 @@ async def agent_websocket(websocket: WebSocket):
async def dashboard_websocket(websocket: WebSocket): async def dashboard_websocket(websocket: WebSocket):
await websocket.accept() await websocket.accept()
await manager.connect_browser(websocket) await manager.connect_browser(websocket)
try: try:
# Initial state senden # Initial state senden
await websocket.send_json({ await websocket.send_json({
@@ -710,13 +710,13 @@ async def dashboard_websocket(websocket: WebSocket):
'stats': store.get_stats() 'stats': store.get_stats()
} }
}) })
async for message in websocket.iter_text(): async for message in websocket.iter_text():
try: try:
data = json.loads(message) data = json.loads(message)
event_type = data.get('type') event_type = data.get('type')
event_data = data.get('data', {}) event_data = data.get('data', {})
if event_type == 'log.subscribe': if event_type == 'log.subscribe':
domain = event_data.get('shop') domain = event_data.get('shop')
agent_id = manager.get_agent_for_shop(domain) agent_id = manager.get_agent_for_shop(domain)
@@ -725,7 +725,7 @@ async def dashboard_websocket(websocket: WebSocket):
'type': 'log.subscribe', 'type': 'log.subscribe',
'data': {'shop': domain} 'data': {'shop': domain}
}) })
elif event_type == 'log.unsubscribe': elif event_type == 'log.unsubscribe':
domain = event_data.get('shop') domain = event_data.get('shop')
agent_id = manager.get_agent_for_shop(domain) agent_id = manager.get_agent_for_shop(domain)
@@ -734,7 +734,7 @@ async def dashboard_websocket(websocket: WebSocket):
'type': 'log.unsubscribe', 'type': 'log.unsubscribe',
'data': {'shop': domain} 'data': {'shop': domain}
}) })
elif event_type == 'get_shop_history': elif event_type == 'get_shop_history':
domain = event_data.get('domain') domain = event_data.get('domain')
data = store.get_shop_history(domain) data = store.get_shop_history(domain)
@@ -742,7 +742,7 @@ async def dashboard_websocket(websocket: WebSocket):
'type': 'shop_history', 'type': 'shop_history',
'data': {'domain': domain, **data} 'data': {'domain': domain, **data}
}) })
elif event_type == 'get_top_shops': elif event_type == 'get_top_shops':
sort_by = event_data.get('sort_by', 'req_per_min') sort_by = event_data.get('sort_by', 'req_per_min')
limit = event_data.get('limit', 10) limit = event_data.get('limit', 10)
@@ -751,7 +751,7 @@ async def dashboard_websocket(websocket: WebSocket):
'type': 'top_shops', 'type': 'top_shops',
'data': {'shops': shops, 'sort_by': sort_by} 'data': {'shops': shops, 'sort_by': sort_by}
}) })
elif event_type == 'get_all_shops_sorted': elif event_type == 'get_all_shops_sorted':
sort_by = event_data.get('sort_by', 'req_per_min') sort_by = event_data.get('sort_by', 'req_per_min')
shops = store.get_top_shops(limit=None, sort_by=sort_by) shops = store.get_top_shops(limit=None, sort_by=sort_by)
@@ -759,7 +759,7 @@ async def dashboard_websocket(websocket: WebSocket):
'type': 'all_shops_sorted', 'type': 'all_shops_sorted',
'data': {'shops': shops, 'sort_by': sort_by} 'data': {'shops': shops, 'sort_by': sort_by}
}) })
elif event_type == 'refresh': elif event_type == 'refresh':
await websocket.send_json({ await websocket.send_json({
'type': 'refresh', 'type': 'refresh',
@@ -769,10 +769,10 @@ async def dashboard_websocket(websocket: WebSocket):
'stats': store.get_stats() 'stats': store.get_stats()
} }
}) })
except Exception as e: except Exception as e:
print(f"Browser message error: {e}") print(f"Browser message error: {e}")
except WebSocketDisconnect: except WebSocketDisconnect:
pass pass
except Exception as e: except Exception as e:
@@ -833,30 +833,30 @@ async def approve_agent(agent_id: str, request: Request):
user = await get_current_user(request) user = await get_current_user(request)
if not user: if not user:
raise HTTPException(401) raise HTTPException(401)
agent = store.get_agent(agent_id) agent = store.get_agent(agent_id)
if not agent: if not agent:
raise HTTPException(404, "Agent nicht gefunden") raise HTTPException(404, "Agent nicht gefunden")
# Token generieren # Token generieren
token = secrets.token_hex(32) token = secrets.token_hex(32)
agent.approved = True agent.approved = True
agent.token = token agent.token = token
agent.status = 'online' agent.status = 'online'
store.set_agent_token(agent_id, token) store.set_agent_token(agent_id, token)
# Token an Agent senden # Token an Agent senden
if manager.is_agent_connected(agent_id): if manager.is_agent_connected(agent_id):
await manager.send_to_agent(agent_id, { await manager.send_to_agent(agent_id, {
'type': 'auth.approved', 'type': 'auth.approved',
'data': {'token': token} 'data': {'token': token}
}) })
await manager.broadcast_to_browsers({ await manager.broadcast_to_browsers({
'type': 'agent.approved', 'type': 'agent.approved',
'data': {'agent_id': agent_id} 'data': {'agent_id': agent_id}
}) })
return {"success": True} return {"success": True}
@@ -873,14 +873,14 @@ async def activate_shop(
user = await get_current_user(request) user = await get_current_user(request)
if not user: if not user:
raise HTTPException(401) raise HTTPException(401)
# String "true"/"false" zu Boolean konvertieren # String "true"/"false" zu Boolean konvertieren
is_monitor_only = bot_monitor_only.lower() in ('true', '1', 'yes', 'on') is_monitor_only = bot_monitor_only.lower() in ('true', '1', 'yes', 'on')
agent_id = manager.get_agent_for_shop(domain) agent_id = manager.get_agent_for_shop(domain)
if not agent_id or not manager.is_agent_connected(agent_id): if not agent_id or not manager.is_agent_connected(agent_id):
return JSONResponse({"success": False, "error": "Agent nicht verbunden"}) return JSONResponse({"success": False, "error": "Agent nicht verbunden"})
command_id = secrets.token_hex(8) command_id = secrets.token_hex(8)
await manager.send_to_agent(agent_id, { await manager.send_to_agent(agent_id, {
'type': 'command.activate', 'type': 'command.activate',
@@ -894,7 +894,7 @@ async def activate_shop(
'bot_monitor_only': is_monitor_only if mode == 'bot' else False 'bot_monitor_only': is_monitor_only if mode == 'bot' else False
} }
}) })
return {"success": True, "command_id": command_id} return {"success": True, "command_id": command_id}
@@ -903,17 +903,17 @@ async def deactivate_shop(request: Request, domain: str = Form(...)):
user = await get_current_user(request) user = await get_current_user(request)
if not user: if not user:
raise HTTPException(401) raise HTTPException(401)
agent_id = manager.get_agent_for_shop(domain) agent_id = manager.get_agent_for_shop(domain)
if not agent_id or not manager.is_agent_connected(agent_id): if not agent_id or not manager.is_agent_connected(agent_id):
return JSONResponse({"success": False, "error": "Agent nicht verbunden"}) return JSONResponse({"success": False, "error": "Agent nicht verbunden"})
command_id = secrets.token_hex(8) command_id = secrets.token_hex(8)
await manager.send_to_agent(agent_id, { await manager.send_to_agent(agent_id, {
'type': 'command.deactivate', 'type': 'command.deactivate',
'data': {'command_id': command_id, 'shop': domain} 'data': {'command_id': command_id, 'shop': domain}
}) })
return {"success": True, "command_id": command_id} return {"success": True, "command_id": command_id}
@@ -930,26 +930,26 @@ async def bulk_activate(
user = await get_current_user(request) user = await get_current_user(request)
if not user: if not user:
raise HTTPException(401) raise HTTPException(401)
# String "true"/"false"/"on" zu Boolean konvertieren # String "true"/"false"/"on" zu Boolean konvertieren
is_monitor_only = bot_monitor_only.lower() in ('true', '1', 'yes', 'on') is_monitor_only = bot_monitor_only.lower() in ('true', '1', 'yes', 'on')
activated = 0 activated = 0
shops = store.get_all_shops() shops = store.get_all_shops()
for shop in shops: for shop in shops:
if shop['status'] == 'active': if shop['status'] == 'active':
continue continue
if filter_type == 'direct' and shop['link11']: if filter_type == 'direct' and shop['link11']:
continue continue
if filter_type == 'link11' and not shop['link11']: if filter_type == 'link11' and not shop['link11']:
continue continue
agent_id = shop.get('agent_id') agent_id = shop.get('agent_id')
if not agent_id or not manager.is_agent_connected(agent_id): if not agent_id or not manager.is_agent_connected(agent_id):
continue continue
command_id = secrets.token_hex(8) command_id = secrets.token_hex(8)
await manager.send_to_agent(agent_id, { await manager.send_to_agent(agent_id, {
'type': 'command.activate', 'type': 'command.activate',
@@ -964,11 +964,11 @@ async def bulk_activate(
} }
}) })
activated += 1 activated += 1
# Kleine Pause um nicht zu überlasten # Kleine Pause um nicht zu überlasten
if activated % 5 == 0: if activated % 5 == 0:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
return {"success": True, "activated": activated} return {"success": True, "activated": activated}
@@ -977,33 +977,33 @@ async def bulk_deactivate(request: Request, filter_type: str = Form("all")):
user = await get_current_user(request) user = await get_current_user(request)
if not user: if not user:
raise HTTPException(401) raise HTTPException(401)
deactivated = 0 deactivated = 0
shops = store.get_all_shops() shops = store.get_all_shops()
for shop in shops: for shop in shops:
if shop['status'] != 'active': if shop['status'] != 'active':
continue continue
if filter_type == 'direct' and shop['link11']: if filter_type == 'direct' and shop['link11']:
continue continue
if filter_type == 'link11' and not shop['link11']: if filter_type == 'link11' and not shop['link11']:
continue continue
agent_id = shop.get('agent_id') agent_id = shop.get('agent_id')
if not agent_id or not manager.is_agent_connected(agent_id): if not agent_id or not manager.is_agent_connected(agent_id):
continue continue
command_id = secrets.token_hex(8) command_id = secrets.token_hex(8)
await manager.send_to_agent(agent_id, { await manager.send_to_agent(agent_id, {
'type': 'command.deactivate', 'type': 'command.deactivate',
'data': {'command_id': command_id, 'shop': shop['domain']} 'data': {'command_id': command_id, 'shop': shop['domain']}
}) })
deactivated += 1 deactivated += 1
if deactivated % 5 == 0: if deactivated % 5 == 0:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
return {"success": True, "deactivated": deactivated} return {"success": True, "deactivated": deactivated}
@@ -1017,14 +1017,14 @@ async def change_password(
user = await get_current_user(request) user = await get_current_user(request)
if not user: if not user:
raise HTTPException(401) raise HTTPException(401)
if not store.verify_password(current): if not store.verify_password(current):
return {"success": False, "error": "Aktuelles Passwort falsch"} return {"success": False, "error": "Aktuelles Passwort falsch"}
if new_pw != confirm: if new_pw != confirm:
return {"success": False, "error": "Neue Passwörter stimmen nicht überein"} return {"success": False, "error": "Neue Passwörter stimmen nicht überein"}
if len(new_pw) < 8: if len(new_pw) < 8:
return {"success": False, "error": "Mindestens 8 Zeichen"} return {"success": False, "error": "Mindestens 8 Zeichen"}
store.set_password(new_pw) store.set_password(new_pw)
return {"success": True} return {"success": True}
@@ -1034,7 +1034,7 @@ async def get_shop_history_api(domain: str, request: Request):
user = await get_current_user(request) user = await get_current_user(request)
if not user: if not user:
raise HTTPException(401) raise HTTPException(401)
data = store.get_shop_history(domain) data = store.get_shop_history(domain)
return {"domain": domain, **data} return {"domain": domain, **data}
@@ -1368,27 +1368,27 @@ def main():
parser.add_argument("--no-ssl", action="store_true") parser.add_argument("--no-ssl", action="store_true")
parser.add_argument("--install-service", action="store_true") parser.add_argument("--install-service", action="store_true")
args = parser.parse_args() args = parser.parse_args()
if args.install_service: if args.install_service:
create_systemd_service() create_systemd_service()
return return
os.makedirs(DATA_DIR, exist_ok=True) os.makedirs(DATA_DIR, exist_ok=True)
print("=" * 60) print("=" * 60)
print(f"🌍 JTL-WAFi Dashboard v{VERSION} (In-Memory)") print(f"🌍 JTL-WAFi Dashboard v{VERSION} (In-Memory)")
print("=" * 60) print("=" * 60)
print(f"Host: {args.host}:{args.port}") print(f"Host: {args.host}:{args.port}")
print(f"SSL: {'Nein' if args.no_ssl else 'Ja'}") print(f"SSL: {'Nein' if args.no_ssl else 'Ja'}")
print("=" * 60) print("=" * 60)
ssl_config = {} ssl_config = {}
if not args.no_ssl: if not args.no_ssl:
generate_ssl_certificate() generate_ssl_certificate()
ssl_config = {"ssl_certfile": SSL_CERT, "ssl_keyfile": SSL_KEY} ssl_config = {"ssl_certfile": SSL_CERT, "ssl_keyfile": SSL_KEY}
uvicorn.run(app, host=args.host, port=args.port, **ssl_config, log_level="info") uvicorn.run(app, host=args.host, port=args.port, **ssl_config, log_level="info")
if __name__ == "__main__": if __name__ == "__main__":
main() main()