/
HookDev-Arch
/
ServerMonitor
Обзор
Документация
Войти
/
HookDev-Arch
/
ServerMonitor
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
2
CI/CD
Аналитика
Безопасность
master
assets/js/app.js
206 строк
10 KB
HookDev-Arch
upload files
09 окт 2025, 02:26
09 окт 2025, 02:26
8297ef2
Код
Авторство
О чём код?
// THEME const root = document.documentElement; const themeKey = "cm_theme"; function setTheme(t){ root.dataset.theme = t; localStorage.setItem(themeKey, t); } setTheme(localStorage.getItem(themeKey) || "dark"); document.getElementById("theme-toggle").addEventListener("click", () => { setTheme(root.dataset.theme === "light" ? "dark" : "light"); }); // LOGOUT document.getElementById("logout-btn").addEventListener("click", async () => { try { const response = await fetch('/api/auth/logout', { method: 'POST' }); if (response.ok) { window.location.href = '/login'; } } catch (error) { console.error('Logout error:', error); window.location.href = '/login'; } }); // LIGHT BG PARTICLES (motion-safe) (function(){ const c = document.getElementById('bg'); if (!c || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; const ctx = c.getContext('2d'); let w,h,dpr=Math.min(window.devicePixelRatio||1,2); function resize(){ w=c.width=innerWidth*dpr; h=c.height=innerHeight*dpr; } addEventListener('resize', resize, {passive:true}); resize(); const N=Math.min(70,Math.floor((innerWidth*innerHeight)/45000)); const pts=Array.from({length:N},()=>({x:Math.random()*w,y:Math.random()*h,vx:(Math.random()-0.5)*0.25*dpr,vy:(Math.random()-0.5)*0.25*dpr})); function tick(){ ctx.clearRect(0,0,w,h); for(let i=0;i<N;i++){ const p=pts[i]; p.x+=p.vx; p.y+=p.vy; if(p.x<0||p.x>w)p.vx*=-1; if(p.y<0||p.y>h)p.vy*=-1; ctx.beginPath(); ctx.arc(p.x,p.y,1.5*dpr,0,Math.PI*2); ctx.fillStyle="rgba(143,188,255,.6)"; ctx.fill(); for(let j=i+1;j<N;j++){ const q=pts[j]; const dx=p.x-q.x,dy=p.y-q.y; const d2=dx*dx+dy*dy; if(d2<(90*dpr)*(90*dpr)){ const a=0.12-(d2/((90*dpr)*(90*dpr)))*0.12; ctx.strokeStyle=`rgba(103,232,249,${a})`; ctx.lineWidth=.6*dpr; ctx.beginPath(); ctx.moveTo(p.x,p.y); ctx.lineTo(q.x,q.y); ctx.stroke(); } } } requestAnimationFrame(tick); } tick(); })(); // STATE let currentNode = 'local'; let servers = []; let interval = 2000; let tickHandle; let cachedProcesses = []; let currentSort = { column: 'cpu', direction: 'desc' }; // HELPERS function setBar(el, percent){ const p=Math.max(0,Math.min(100,percent||0)); el.style.width=p+'%'; el.classList.remove('warn','danger'); if(p>80) el.classList.add('danger'); else if(p>60) el.classList.add('warn'); } function tweenNumber(el,to,suffix='',dec=0){ const from=parseFloat(el.dataset.val||'0'); const start=performance.now(); const dur=380; function step(now){ const t=Math.min(1,(now-start)/dur); const val=from+(to-from)*t; el.textContent=val.toFixed(dec)+suffix; if(t<1) requestAnimationFrame(step); else el.dataset.val=to.toString(); } requestAnimationFrame(step); } function sortProcesses(list){ const a=[...list]; const {column,direction}=currentSort; const th=document.querySelector(`th[data-sort="${column}"]`); const type=th?th.dataset.type:'number'; a.sort((x,y)=>{ let A=x[column], B=y[column]; if(type==='number'){ A=parseFloat(A)||0; B=parseFloat(B)||0; } else { A=String(A).toLowerCase(); B=String(B).toLowerCase(); } if(A<B) return direction==='asc'?-1:1; if(A>B) return direction==='asc'?1:-1; return 0; }); return a; } function renderProcesses(list){ const rows = sortProcesses(list).map(p=>` <tr> <td>${p.pid}</td> <td>${p.name}</td> <td>${(p.cpu||0).toFixed ? p.cpu.toFixed(1) : p.cpu}%</td> <td>${(p.memory||0).toFixed ? p.memory.toFixed(2) : p.memory}%</td> <td>${p.rss}</td> </tr> `).join(''); document.getElementById('processes-table').innerHTML = rows; } // TABLE SORT document.querySelectorAll('th[data-sort]').forEach(th=>{ th.addEventListener('click', ()=>{ const col=th.dataset.sort; if(currentSort.column===col){ currentSort.direction=currentSort.direction==='asc'?'desc':'asc'; } else { currentSort.column=col; currentSort.direction=col==='name'?'asc':'desc'; } document.querySelectorAll('th[data-sort]').forEach(h=>h.classList.remove('sort-asc','sort-desc')); th.classList.add(currentSort.direction==='asc'?'sort-asc':'sort-desc'); renderProcesses(cachedProcesses); }); }); // INTERVAL document.getElementById('interval').addEventListener('change', e=>{ interval=parseInt(e.target.value||'2000',10); if(tickHandle) clearInterval(tickHandle); tickHandle=setInterval(update, interval); }); // LOAD SERVERS & TABS async function loadServers(){ try{ const r=await fetch('/api/servers'); const d=await r.json(); servers=d.servers||[]; }catch{ servers=[]; } const el=document.getElementById('tabs'); const items=['local', ...servers.map(s=>s.name)]; el.innerHTML = items.map(n=>`<div class="tab ${n===currentNode?'active':''}" data-node="${n}">${n}</div>`).join(''); el.querySelectorAll('.tab').forEach(t=>t.addEventListener('click', ()=>{ currentNode=t.dataset.node; update(); loadServers(); // rerender active class })); } // ADD SERVER document.getElementById('btn-add').addEventListener('click', async ()=>{ const name=document.getElementById('in-name').value.trim(); const host=document.getElementById('in-host').value.trim(); const port=parseInt(document.getElementById('in-port').value.trim()||'22',10); const user=(document.getElementById('in-user').value.trim()||'root'); const password=document.getElementById('in-pass').value; const api_port=parseInt(document.getElementById('in-api').value.trim()||'3333',10); if(!name||!host||!password){ alert('Name, Host, Password are required'); return; } try{ const res=await fetch('/api/add_server',{method:'POST',headers:{'Content-Type':'application/json'}, body:JSON.stringify({name,host,user,password,port,api_port})}); const data=await res.json(); if(!res.ok) throw new Error(data.error||'Add failed'); await loadServers(); currentNode=name; update(); alert('Server added and agent deployed.'); }catch(e){ alert('Error: '+e.message); } }); // UPDATE LOOP async function update(){ try{ const res=await fetch('/api/stats?node='+encodeURIComponent(currentNode)); const data=await res.json(); document.getElementById('info').classList.remove('skeleton'); const gpuInfo=(data.gpu && data.gpu.name && data.gpu.name!=='N/A') ? `${data.gpu.name} ${data.gpu.memory_total||''}` : 'N/A'; const timeStr=data.timestamp ? (data.timestamp.split(' ')[1]||data.timestamp) : ''; document.getElementById('sys').textContent = `Node: ${currentNode} | GPU: ${gpuInfo} | Time: ${timeStr}`; // CPU const cpuP = Math.min(100, Math.max(0, data.cpu?.percent || 0)); tweenNumber(document.getElementById('cpu-percent'), cpuP, '%', 1); document.getElementById('cpu-count').textContent = data.cpu?.count ?? '—'; document.getElementById('cpu-freq').textContent = data.cpu?.freq ?? '—'; setBar(document.getElementById('cpu-bar'), cpuP); // Memory document.getElementById('mem-usage').textContent = `${data.memory?.used||'—'} / ${data.memory?.total||'—'}`; document.getElementById('mem-available').textContent = data.memory?.available || '—'; setBar(document.getElementById('mem-bar'), data.memory?.percent || 0); // Swap document.getElementById('swap-usage').textContent = `${data.swap?.used||'—'} / ${data.swap?.total||'—'}`; document.getElementById('swap-free').textContent = data.swap?.free || '—'; setBar(document.getElementById('swap-bar'), data.swap?.percent || 0); // GPU document.getElementById('gpu-name').textContent = data.gpu?.name || 'N/A'; const gu = (typeof(data.gpu?.gpu_util)==='number')? data.gpu.gpu_util : 0; const gm = (typeof(data.gpu?.memory_percent)==='number')? data.gpu.memory_percent : 0; document.getElementById('gpu-util').textContent = gu + '%'; document.getElementById('gpu-mem').textContent = `${data.gpu?.memory_used || '0'} / ${data.gpu?.memory_total || '0'}`; document.getElementById('gpu-temp').textContent = (data.gpu?.temperature!==undefined)? (data.gpu.temperature + '°C') : 'N/A'; document.getElementById('gpu-power').textContent = data.gpu?.power || 'N/A'; setBar(document.getElementById('gpu-bar'), gu); setBar(document.getElementById('gpu-mem-bar'), gm); // Disk document.getElementById('disk-usage').textContent = `${data.disk?.used||'—'} / ${data.disk?.total||'—'}`; document.getElementById('disk-free').textContent = data.disk?.free || '—'; document.getElementById('disk-read-speed').textContent = data.disk?.read_speed || 'N/A'; document.getElementById('disk-write-speed').textContent = data.disk?.write_speed || 'N/A'; document.getElementById('disk-read-iops').textContent = data.disk?.read_iops || 'N/A'; document.getElementById('disk-write-iops').textContent = data.disk?.write_iops || 'N/A'; setBar(document.getElementById('disk-bar'), data.disk?.percent || 0); // Network document.getElementById('public-ip').textContent = data.network?.public_ip || 'N/A'; document.getElementById('net-download-speed').textContent = data.network?.download_speed || 'N/A'; document.getElementById('net-upload-speed').textContent = data.network?.upload_speed || 'N/A'; document.getElementById('net-sent').textContent = data.network?.sent || '0'; document.getElementById('net-recv').textContent = data.network?.recv || '0'; document.getElementById('net-pkt-sent').textContent = data.network?.packets_sent || '0'; document.getElementById('net-pkt-recv').textContent = data.network?.packets_recv || '0'; // Processes cachedProcesses = data.processes || []; renderProcesses(cachedProcesses); }catch(e){ console.error(e); } } // BOOTSTRAP document.addEventListener('DOMContentLoaded', async ()=>{ await loadServers(); update(); tickHandle=setInterval(update, interval); });