serveroverview show how many link11

This commit is contained in:
Thomas Ciesla
2026-01-08 14:21:31 +01:00
parent b4579128d0
commit 65a47e5a08

View File

@@ -1619,7 +1619,7 @@ def get_dashboard_html() -> str:
</div>
<div class="section">
<div class="section-header"><h2 class="section-title">🖥️ Server</h2><span class="badge" id="agentCount">0</span></div>
<table id="tableAgents"><thead><tr><th class="sortable" onclick="sortAgents('status')">Status<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('hostname')">Hostname<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('shops')">Shops<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('human_rpm')">Human/min<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('bot_rpm')">Bot/min<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('load')">Load<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('memory')">Memory<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('last_seen')">Zuletzt<span class="sort-icon">⇅</span></th><th>Aktionen</th></tr></thead><tbody id="agentsTable"></tbody></table>
<table id="tableAgents"><thead><tr><th class="sortable" onclick="sortAgents('status')">Status<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('hostname')">Hostname<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('shops')">Shops<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('link11')">Link11<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('human_rpm')">Human/min<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('bot_rpm')">Bot/min<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('load')">Load<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('memory')">Memory<span class="sort-icon">⇅</span></th><th class="sortable" onclick="sortAgents('last_seen')">Zuletzt<span class="sort-icon">⇅</span></th><th>Aktionen</th></tr></thead><tbody id="agentsTable"></tbody></table>
</div>
<div class="section">
<div class="section-header"><h2 class="section-title">🛡️ Shops hinter Link11</h2><span class="badge link11" id="link11Count">0</span></div>
@@ -1682,17 +1682,18 @@ def get_dashboard_html() -> str:
function scheduleRender(){pendingRender=true;}
// Sort state für jede Tabelle
let sortState={agents:{col:null,asc:false},link11:{col:null,asc:false},direct:{col:null,asc:false}};
function updateSortIcons(tableId,col,asc){const table=document.getElementById(tableId);if(!table)return;table.querySelectorAll('th.sortable').forEach(th=>{th.classList.remove('asc','desc');const icon=th.querySelector('.sort-icon');if(icon)icon.textContent='';});if(col){const colMap={status:'sta',hostname:'hos',shops:'sho',human_rpm:'hum',bot_rpm:'bot',load:'loa',memory:'mem',last_seen:'zul',domain:'dom',server:'ser',modus:'mod',req:'req',bans:'ban',runtime:'lau'};const matchStr=colMap[col]||col.substring(0,3);const ths=table.querySelectorAll('th.sortable');ths.forEach(th=>{if(th.textContent.replace('','').trim().toLowerCase().includes(matchStr)){th.classList.add(asc?'asc':'desc');const icon=th.querySelector('.sort-icon');if(icon)icon.textContent=asc?'':'';}});}}
function updateSortIcons(tableId,col,asc){const table=document.getElementById(tableId);if(!table)return;table.querySelectorAll('th.sortable').forEach(th=>{th.classList.remove('asc','desc');const icon=th.querySelector('.sort-icon');if(icon)icon.textContent='';});if(col){const colMap={status:'sta',hostname:'hos',shops:'sho',link11:'lin',human_rpm:'hum',bot_rpm:'bot',load:'loa',memory:'mem',last_seen:'zul',domain:'dom',server:'ser',modus:'mod',req:'req',bans:'ban',runtime:'lau'};const matchStr=colMap[col]||col.substring(0,3);const ths=table.querySelectorAll('th.sortable');ths.forEach(th=>{if(th.textContent.replace('','').trim().toLowerCase().includes(matchStr)){th.classList.add(asc?'asc':'desc');const icon=th.querySelector('.sort-icon');if(icon)icon.textContent=asc?'':'';}});}}
function sortAgents(col){const st=sortState.agents;if(st.col===col){st.asc=!st.asc;}else{st.col=col;st.asc=false;}renderAgents();updateSortIcons('agentsTable',col,st.asc);}
function sortShops(type,col){const st=sortState[type];if(st.col===col){st.asc=!st.asc;}else{st.col=col;st.asc=false;}renderShops();const tableId=type==='link11'?'tableLink11':'tableDirect';updateSortIcons(tableId,col,st.asc);}
function getSortedAgents(){const list=Object.values(agents);const st=sortState.agents;if(!st.col)return list;const dir=st.asc?1:-1;return list.sort((a,b)=>{let va,vb;switch(st.col){case'status':const order={online:3,pending:2,offline:1};va=order[a.status]||0;vb=order[b.status]||0;break;case'hostname':va=(a.hostname||'').toLowerCase();vb=(b.hostname||'').toLowerCase();return dir*(va<vb?-1:va>vb?1:0);case'shops':va=(a.shops_active||0);vb=(b.shops_active||0);break;case'human_rpm':const rpmA=getAgentRpm(a.id);va=rpmA.humanRpm;const rpmB_h=getAgentRpm(b.id);vb=rpmB_h.humanRpm;break;case'bot_rpm':const rpmA_b=getAgentRpm(a.id);va=rpmA_b.botRpm;const rpmB_b=getAgentRpm(b.id);vb=rpmB_b.botRpm;break;case'load':va=(a.load_1m||0);vb=(b.load_1m||0);break;case'memory':va=(a.memory_percent||0);vb=(b.memory_percent||0);break;case'last_seen':va=a.last_seen||'';vb=b.last_seen||'';return dir*(va<vb?-1:va>vb?1:0);default:return 0;}return dir*(va-vb);});}
function getSortedAgents(){const list=Object.values(agents);const st=sortState.agents;if(!st.col)return list;const dir=st.asc?1:-1;return list.sort((a,b)=>{let va,vb;switch(st.col){case'status':const order={online:3,pending:2,offline:1};va=order[a.status]||0;vb=order[b.status]||0;break;case'hostname':va=(a.hostname||'').toLowerCase();vb=(b.hostname||'').toLowerCase();return dir*(va<vb?-1:va>vb?1:0);case'shops':va=(a.shops_active||0);vb=(b.shops_active||0);break;case'link11':const l11A=getAgentLink11(a.id);va=l11A.link11;const l11B=getAgentLink11(b.id);vb=l11B.link11;break;case'human_rpm':const rpmA=getAgentRpm(a.id);va=rpmA.humanRpm;const rpmB_h=getAgentRpm(b.id);vb=rpmB_h.humanRpm;break;case'bot_rpm':const rpmA_b=getAgentRpm(a.id);va=rpmA_b.botRpm;const rpmB_b=getAgentRpm(b.id);vb=rpmB_b.botRpm;break;case'load':va=(a.load_1m||0);vb=(b.load_1m||0);break;case'memory':va=(a.memory_percent||0);vb=(b.memory_percent||0);break;case'last_seen':va=a.last_seen||'';vb=b.last_seen||'';return dir*(va<vb?-1:va>vb?1:0);default:return 0;}return dir*(va-vb);});}
function getSortedShops(list,type){const st=sortState[type];if(!st.col)return list;const dir=st.asc?1:-1;return list.sort((a,b)=>{let va,vb;switch(st.col){case'status':va=a.status==='active'?1:0;vb=b.status==='active'?1:0;break;case'domain':va=(a.domain||'').toLowerCase();vb=(b.domain||'').toLowerCase();return dir*(va<vb?-1:va>vb?1:0);case'server':va=(a.agent_hostname||'').toLowerCase();vb=(b.agent_hostname||'').toLowerCase();return dir*(va<vb?-1:va>vb?1:0);case'modus':va=(a.mode||'').toLowerCase();vb=(b.mode||'').toLowerCase();return dir*(va<vb?-1:va>vb?1:0);case'req':va=(a.stats?.req_per_min||0);vb=(b.stats?.req_per_min||0);break;case'bans':va=((a.stats?.active_bot_bans||0)+(a.stats?.active_country_bans||0));vb=((b.stats?.active_bot_bans||0)+(b.stats?.active_country_bans||0));break;case'runtime':va=a.activated?new Date(a.activated).getTime():0;vb=b.activated?new Date(b.activated).getTime():0;break;default:return 0;}return dir*(va-vb);});}
function updateClock(){document.getElementById('clock').textContent=new Date().toLocaleTimeString('de-DE');}
setInterval(updateClock,1000);updateClock();
function connect(){const p=location.protocol==='https:'?'wss:':'ws:';ws=new WebSocket(p+'//'+location.host+'/ws/dashboard');ws.onopen=()=>{document.getElementById('wsStatus').classList.add('connected');document.getElementById('wsStatusText').textContent='Verbunden';};ws.onclose=()=>{document.getElementById('wsStatus').classList.remove('connected');document.getElementById('wsStatusText').textContent='Getrennt';setTimeout(connect,3000);};ws.onmessage=e=>handleMessage(JSON.parse(e.data));}
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()});scheduleRender();break;case'agent.offline':if(agents[msg.data.agent_id]){agents[msg.data.agent_id].status='offline';scheduleRender();}break;case'agent.approved':if(agents[msg.data.agent_id]){agents[msg.data.agent_id].status='online';agents[msg.data.agent_id].approved=true;scheduleRender();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};});scheduleRender();break;case'shop.stats':if(shops[msg.data.domain]){shops[msg.data.domain].stats=msg.data.stats;scheduleRender();}break;case'shop_history':updateBotChart(msg.data);updateCountryChart(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;case'livestats.result':renderLiveStats(msg.data);break;}}
function getAgentRpm(agentId){const agentShops=Object.values(shops).filter(s=>s.agent_id===agentId);let humanRpm=0,botRpm=0;agentShops.forEach(s=>{const st=s.stats||{};humanRpm+=(st.human_rpm||0);botRpm+=(st.bot_rpm||0);});return {humanRpm,botRpm};}
function renderAgents(){const t=document.getElementById('agentsTable'),l=getSortedAgents();document.getElementById('agentCount').textContent=l.length+' Agents';document.getElementById('agentCountDropdown').textContent=l.length;t.innerHTML=l.map(a=>{const rpm=getAgentRpm(a.id);return '<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><span class="domain-link" onclick="openServerDetailModal(\\''+a.id+'\\')"><strong>'+a.hostname+'</strong></span></td><td>'+(a.shops_active||0)+'/'+(a.shops_total||0)+'</td><td class="success">'+rpm.humanRpm.toFixed(1)+'</td><td class="warning">'+rpm.botRpm.toFixed(1)+'</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 getAgentLink11(agentId){const agentShops=Object.values(shops).filter(s=>s.agent_id===agentId);const link11Count=agentShops.filter(s=>s.link11).length;return {link11:link11Count,total:agentShops.length};}
function renderAgents(){const t=document.getElementById('agentsTable'),l=getSortedAgents();document.getElementById('agentCount').textContent=l.length+' Agents';document.getElementById('agentCountDropdown').textContent=l.length;t.innerHTML=l.map(a=>{const rpm=getAgentRpm(a.id);const l11=getAgentLink11(a.id);return '<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><span class="domain-link" onclick="openServerDetailModal(\\''+a.id+'\\')"><strong>'+a.hostname+'</strong></span></td><td>'+(a.shops_active||0)+'/'+(a.shops_total||0)+'</td><td>'+l11.link11+'/'+l11.total+'</td><td class="success">'+rpm.humanRpm.toFixed(1)+'</td><td class="warning">'+rpm.botRpm.toFixed(1)+'</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=>{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.activated)+'</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>';}