jtl-wafi-dashboard.py aktualisiert

This commit is contained in:
2025-12-30 13:29:49 +01:00
parent 511ade8666
commit aa35c24819

View File

@@ -48,6 +48,12 @@ HISTORY_MAX_POINTS = 1000 # Max Datenpunkte pro Shop
SECRET_KEY = os.environ.get("DASHBOARD_SECRET", secrets.token_hex(32))
# =============================================================================
# AUTO-UPDATE
# =============================================================================
DASHBOARD_UPDATE_URL = "https://git.jtl-hosting.de/thomasciesla/JTL-WAFI/raw/branch/main/jtl-wafi-dashboard.py"
AGENT_UPDATE_URL = "https://git.jtl-hosting.de/thomasciesla/JTL-WAFI/raw/branch/main/jtl-wafi-agent.py"
# =============================================================================
# UTILITY
@@ -1176,6 +1182,99 @@ async def get_shop_history_api(domain: str, request: Request):
return {"domain": domain, **data}
@app.post("/api/update-dashboard")
async def update_dashboard(request: Request):
"""Dashboard selbst updaten."""
import urllib.request
import urllib.error
user = await get_current_user(request)
if not user:
raise HTTPException(401)
try:
# 1. Download neue Version
req = urllib.request.Request(
DASHBOARD_UPDATE_URL,
headers={'User-Agent': f'JTL-WAFi-Dashboard/{VERSION}'}
)
with urllib.request.urlopen(req, timeout=30) as response:
new_content = response.read().decode('utf-8')
# 2. Syntax-Check
compile(new_content, '<update>', 'exec')
# 3. Version extrahieren
new_version = "unknown"
for line in new_content.split('\n')[:50]:
if 'VERSION = ' in line:
new_version = line.split('=')[1].strip().strip('"\'')
break
# 4. Script-Pfad ermitteln
script_path = os.path.abspath(__file__)
backup_path = script_path + '.backup'
# 5. Backup erstellen
import shutil
shutil.copy(script_path, backup_path)
# 6. Neue Version schreiben
with open(script_path, 'w', encoding='utf-8') as f:
f.write(new_content)
# 7. Neustart planen (nach Response)
async def restart_dashboard():
await asyncio.sleep(2)
os.execv(sys.executable, [sys.executable, script_path])
asyncio.create_task(restart_dashboard())
return {
"success": True,
"message": f"Update erfolgreich ({VERSION} -> {new_version}). Dashboard startet neu...",
"old_version": VERSION,
"new_version": new_version
}
except urllib.error.URLError as e:
return {"success": False, "error": f"Download fehlgeschlagen: {str(e)}"}
except SyntaxError as e:
return {"success": False, "error": f"Syntax-Fehler in neuer Version: {str(e)}"}
except Exception as e:
return {"success": False, "error": f"Update fehlgeschlagen: {str(e)}"}
@app.post("/api/update-agents")
async def update_agents(request: Request):
"""Alle verbundenen Agents updaten."""
user = await get_current_user(request)
if not user:
raise HTTPException(401)
updated = 0
failed = 0
for agent_id in list(manager.agent_connections.keys()):
if manager.is_agent_connected(agent_id):
try:
command_id = secrets.token_hex(8)
await manager.send_to_agent(agent_id, {
'type': 'command.update',
'data': {'command_id': command_id}
})
updated += 1
except Exception:
failed += 1
return {
"success": True,
"updated": updated,
"failed": failed,
"message": f"{updated} Agent(s) werden aktualisiert..."
}
# =============================================================================
# HTML TEMPLATES
# =============================================================================
@@ -1386,6 +1485,7 @@ def get_dashboard_html() -> str:
<div class="clock" id="clock">--:--:--</div>
<div class="connection-status"><div class="status-dot" id="wsStatus"></div><span id="wsStatusText">Verbinde...</span></div>
<div style="display:flex;align-items:center;gap:8px"><span style="font-size:12px;color:var(--text-secondary)">Update:</span><select id="updateInterval" onchange="setUpdateInterval(this.value)" style="padding:4px 8px;border-radius:4px;border:1px solid var(--border);background:var(--card-bg);color:var(--text);font-size:12px"><option value="2000">2s</option><option value="5000">5s</option><option value="10000" selected>10s</option><option value="15000">15s</option></select></div><div style="display:flex;align-items:center;gap:6px;padding:4px 8px;border-radius:4px;border:1px solid var(--border);background:var(--card-bg)"><label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--text-secondary)" title="OPcache nach Aktivierung/Deaktivierung leeren"><input type="checkbox" id="autoFpmRestart" checked style="margin:0"><span>FPM-Restart</span></label></div>
<div class="update-dropdown" style="position:relative"><button class="btn-header" onclick="toggleUpdateDropdown()" id="updateDropdownBtn">🔄 v{VERSION}</button><div id="updateDropdownMenu" style="display:none;position:absolute;right:0;top:100%;margin-top:4px;background:var(--card-bg);border:1px solid var(--border);border-radius:8px;padding:8px 0;min-width:200px;z-index:1000;box-shadow:0 4px 12px rgba(0,0,0,0.3)"><button onclick="updateDashboard()" style="display:block;width:100%;text-align:left;padding:8px 16px;border:none;background:none;color:var(--text);cursor:pointer;font-size:13px" onmouseover="this.style.background='var(--bg)'" onmouseout="this.style.background='none'">📊 Dashboard updaten</button><button onclick="updateAgents()" style="display:block;width:100%;text-align:left;padding:8px 16px;border:none;background:none;color:var(--text);cursor:pointer;font-size:13px" onmouseover="this.style.background='var(--bg)'" onmouseout="this.style.background='none'">🖥️ Alle Agents updaten (<span id="agentCountDropdown">0</span>)</button></div></div>
<div style="display:flex;gap:8px"><button class="btn-header" onclick="openPasswordModal()">🔑</button><a href="/logout" class="btn-header">Abmelden</a></div>
</div>
</header>
@@ -1489,6 +1589,10 @@ def get_dashboard_html() -> str:
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;const restartFpm=document.getElementById('autoFpmRestart').checked?'true':'false';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);dfd.append('restart_fpm',restartFpm);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');fd.append('restart_fpm',restartFpm);await fetch('/api/shops/activate',{method:'POST',body:fd});closeModal('detailModal');}
async function detailSwitchMode(mode){if(!currentDetailShop)return;const s=shops[currentDetailShop];const restartFpm=document.getElementById('autoFpmRestart').checked?'true':'false';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);dfd.append('restart_fpm',restartFpm);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('restart_fpm',restartFpm);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');}
function toggleUpdateDropdown(){const menu=document.getElementById('updateDropdownMenu');menu.style.display=menu.style.display==='none'?'block':'none';const cnt=Object.values(agents).filter(a=>a.status==='online').length;document.getElementById('agentCountDropdown').textContent=cnt;}
document.addEventListener('click',e=>{const dd=document.querySelector('.update-dropdown');if(dd&&!dd.contains(e.target)){document.getElementById('updateDropdownMenu').style.display='none';}});
async function updateDashboard(){document.getElementById('updateDropdownMenu').style.display='none';if(!confirm('Dashboard aktualisieren? Das Dashboard wird nach dem Update neu gestartet.'))return;toast('Dashboard wird aktualisiert...','info');try{const r=await fetch('/api/update-dashboard',{method:'POST'});const d=await r.json();if(d.success){toast(d.message,'success');setTimeout(()=>location.reload(),3000);}else{toast(d.error,'error');}}catch(e){toast('Update fehlgeschlagen: '+e,'error');}}
async function updateAgents(){document.getElementById('updateDropdownMenu').style.display='none';const cnt=Object.values(agents).filter(a=>a.status==='online').length;if(cnt===0){toast('Keine Agents online','warning');return;}if(!confirm(cnt+' Agent(s) aktualisieren? Die Agents werden nach dem Update neu gestartet.'))return;toast('Agents werden aktualisiert...','info');try{const r=await fetch('/api/update-agents',{method:'POST'});const d=await r.json();if(d.success){toast(d.message,'success');}else{toast(d.error,'error');}}catch(e){toast('Update fehlgeschlagen: '+e,'error');}}
connect();
setUpdateInterval(10000);
</script>