notifikation glocke
This commit is contained in:
@@ -1566,7 +1566,16 @@ def get_dashboard_html() -> str:
|
||||
.toast.success { border-color: var(--success); }
|
||||
.toast.error { border-color: var(--danger); }
|
||||
.toast.info { border-color: var(--accent); }
|
||||
.toast.warning { border-color: var(--warning); }
|
||||
@keyframes slideIn { from { transform: translateX(100px); opacity: 0; } }
|
||||
.notification-badge { position: absolute; top: -4px; right: -4px; background: var(--danger); color: white; font-size: 10px; font-weight: 700; min-width: 16px; height: 16px; border-radius: 8px; display: flex; align-items: center; justify-content: center; padding: 0 4px; }
|
||||
.notification-item { padding: 12px 16px; border-bottom: 1px solid var(--border); font-size: 13px; display: flex; gap: 10px; align-items: flex-start; }
|
||||
.notification-item:last-child { border-bottom: none; }
|
||||
.notification-item:hover { background: var(--bg); }
|
||||
.notification-icon { font-size: 16px; flex-shrink: 0; }
|
||||
.notification-content { flex: 1; min-width: 0; }
|
||||
.notification-text { color: var(--text); word-break: break-word; }
|
||||
.notification-time { font-size: 11px; color: var(--text-secondary); margin-top: 4px; }
|
||||
@media (max-width: 1400px) { .stats-grid { grid-template-columns: repeat(3, 1fr); } .top-shops-list { grid-template-columns: repeat(3, 1fr); } }
|
||||
@media (max-width: 900px) { .stats-grid { grid-template-columns: repeat(2, 1fr); } .detail-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
</style>
|
||||
@@ -1579,6 +1588,7 @@ def get_dashboard_html() -> str:
|
||||
<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">🔄 v3.1</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 class="notification-dropdown" style="position:relative"><button class="btn-header" onclick="toggleNotificationDropdown()" id="notificationBtn" title="Benachrichtigungen">🔔<span class="notification-badge" id="notificationBadge" style="display:none">0</span></button><div id="notificationDropdown" style="display:none;position:absolute;right:0;top:100%;margin-top:4px;background:var(--card-bg);border:1px solid var(--border);border-radius:8px;min-width:350px;max-width:450px;z-index:1000;box-shadow:0 4px 12px rgba(0,0,0,0.3)"><div style="display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--border)"><span style="font-weight:600;font-size:14px">📜 Benachrichtigungen</span><button onclick="clearNotifications()" style="background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:12px" title="Alle löschen">🗑️ Leeren</button></div><div id="notificationList" style="max-height:400px;overflow-y:auto"></div><div id="notificationEmpty" style="padding:24px;text-align:center;color:var(--text-secondary);font-size:13px">Keine Benachrichtigungen</div></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>
|
||||
@@ -1661,6 +1671,7 @@ def get_dashboard_html() -> str:
|
||||
<script>
|
||||
let ws=null,agents={},shops={},currentLogsShop=null,currentSortBy='req_per_min',currentDetailShop=null;
|
||||
let updateInterval=10000,pendingRender=false,renderTimer=null;
|
||||
let notifications=[],maxNotifications=50,unreadCount=0;
|
||||
const BOT_COLORS=['#4a9eff','#00d26a','#ff4757','#ffc107','#9b59b6','#1abc9c','#e74c3c','#3498db','#f39c12','#2ecc71'];
|
||||
function setUpdateInterval(ms){updateInterval=parseInt(ms);if(renderTimer)clearInterval(renderTimer);renderTimer=setInterval(()=>{if(pendingRender){renderAgents();renderShops();renderTopShops();refreshStats();pendingRender=false;}},updateInterval);}
|
||||
function scheduleRender(){pendingRender=true;}
|
||||
@@ -1720,7 +1731,14 @@ def get_dashboard_html() -> str:
|
||||
function addLogEntry(line){const c=document.getElementById('logsContent');if(c.querySelector('div[style*="color:#666"]'))c.innerHTML='';const e=document.createElement('div');e.className='log-entry'+(line.includes('BANNED')?' banned':'');e.textContent=line;c.insertBefore(e,c.firstChild);while(c.children.length>100)c.removeChild(c.lastChild);}
|
||||
function formatTime(iso){return new Date(iso).toLocaleTimeString('de-DE');}
|
||||
function formatRuntime(m){if(!m||m<=0)return'-';if(m<60)return Math.round(m)+'m';const h=m/60;if(h<24)return Math.round(h)+'h';return Math.round(h/24)+'d';}
|
||||
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);}
|
||||
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);addNotification(msg,type);}
|
||||
function addNotification(msg,type='info'){const icons={success:'✅',error:'❌',warning:'⚠️',info:'ℹ️'};const n={id:Date.now(),message:msg,type:type,icon:icons[type]||'ℹ️',time:new Date()};notifications.unshift(n);if(notifications.length>maxNotifications)notifications.pop();unreadCount++;updateNotificationBadge();renderNotifications();}
|
||||
function updateNotificationBadge(){const badge=document.getElementById('notificationBadge');if(unreadCount>0){badge.textContent=unreadCount>99?'99+':unreadCount;badge.style.display='flex';}else{badge.style.display='none';}}
|
||||
function renderNotifications(){const list=document.getElementById('notificationList'),empty=document.getElementById('notificationEmpty');if(notifications.length===0){list.innerHTML='';empty.style.display='block';return;}empty.style.display='none';list.innerHTML=notifications.map(n=>'<div class="notification-item"><span class="notification-icon">'+n.icon+'</span><div class="notification-content"><div class="notification-text">'+n.message+'</div><div class="notification-time">'+formatNotificationTime(n.time)+'</div></div></div>').join('');}
|
||||
function formatNotificationTime(date){const now=new Date(),diff=Math.floor((now-date)/1000);if(diff<60)return 'gerade eben';if(diff<3600)return Math.floor(diff/60)+' Min';if(diff<86400)return Math.floor(diff/3600)+' Std';return date.toLocaleDateString('de-DE')+' '+date.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'});}
|
||||
function toggleNotificationDropdown(){const dd=document.getElementById('notificationDropdown'),isOpen=dd.style.display!=='none';document.getElementById('updateDropdownMenu').style.display='none';dd.style.display=isOpen?'none':'block';if(!isOpen){unreadCount=0;updateNotificationBadge();}}
|
||||
function clearNotifications(){notifications=[];unreadCount=0;updateNotificationBadge();renderNotifications();}
|
||||
document.addEventListener('click',e=>{const nb=document.getElementById('notificationBtn'),nd=document.getElementById('notificationDropdown');if(nd&&nd.style.display!=='none'&&!nb.contains(e.target)&&!nd.contains(e.target)){nd.style.display='none';}});
|
||||
async function detailDeactivate(){if(!currentDetailShop)return;if(!confirm('Shop '+currentDetailShop+' deaktivieren?'))return;const fd=new FormData();fd.append('domain',currentDetailShop);fd.append('restart_fpm',document.getElementById('autoFpmRestart').checked?'true':'false');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;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');}
|
||||
|
||||
Reference in New Issue
Block a user