// ========== RESOURCE MONITOR (Enhanced) ========== (function() { // Inject modal HTML injectModal('stats-modal', `

📊 Resource Monitor

Loading container stats...
📈 Loading 24-hour aggregated metrics...
🔔 Loading alert configurations...
`); const modal = document.getElementById('stats-modal'); const openBtn = document.getElementById('container-stats-btn'); const cancelBtn = document.getElementById('stats-cancel'); const refreshBtn = document.getElementById('stats-refresh-btn'); const autoRefreshCheckbox = document.getElementById('stats-auto-refresh'); const container = document.getElementById('stats-container'); const aggregatedContainer = document.getElementById('stats-aggregated-container'); const alertsContainer = document.getElementById('stats-alerts-container'); const lastUpdateSpan = document.getElementById('stats-last-update'); let refreshInterval = null; let cachedMonitoringData = null; function formatBytes(bytes) { if (bytes === 0 || !bytes) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } function getCpuColor(percent) { if (percent < 30) return '#2ecc71'; if (percent < 70) return '#f39c12'; return '#e74c3c'; } function getMemColor(percent) { if (percent < 50) return '#2ecc71'; if (percent < 80) return '#f39c12'; return '#e74c3c'; } async function loadStats() { try { // Try new monitoring API first, fall back to old let stats = null; let isNewApi = false; try { const res = await fetch('/api/v1/monitoring/stats'); const data = await res.json(); if (data.success && data.stats) { stats = data.stats; isNewApi = true; cachedMonitoringData = data.stats; } } catch (_) {} if (!isNewApi) { const response = await fetch('/api/v1/stats/containers'); const data = await response.json(); if (data.success && data.stats) { // Convert array format to object format stats = {}; for (const s of data.stats) { stats[s.name] = { name: s.name, current: { cpu: s.cpu, memory: { percent: s.memory.percent, usage: s.memory.used, limit: s.memory.limit, usageMB: Math.round(s.memory.used / 1048576), limitMB: Math.round(s.memory.limit / 1048576) }, network: { rxBytes: s.network.rx, txBytes: s.network.tx, rxMB: (s.network.rx / 1048576).toFixed(1), txMB: (s.network.tx / 1048576).toFixed(1) }, disk: { readMB: 0, writeMB: 0 } }, status: s.status }; } cachedMonitoringData = stats; } } if (!stats || Object.keys(stats).length === 0) { container.innerHTML = '
No running containers found
'; return; } let html = '
'; for (const [id, info] of Object.entries(stats)) { const cur = info.current || info; const cpu = cur.cpu?.percent || 0; const mem = cur.memory?.percent || 0; const cpuColor = getCpuColor(cpu); const memColor = getMemColor(mem); const memUsed = cur.memory?.usage || cur.memory?.used || 0; const memLimit = cur.memory?.limit || 0; const netRx = cur.network?.rxBytes || cur.network?.rx || 0; const netTx = cur.network?.txBytes || cur.network?.tx || 0; const agg = info.aggregated; html += `
${info.name || id} ${agg ? `avg ${agg.cpu?.avg?.toFixed(0) || 0}% cpu` : ''} ${info.status || 'running'}
CPU
${cpu.toFixed(1)}%
Memory
${mem.toFixed(1)}%
${formatBytes(memUsed)} / ${formatBytes(memLimit)}
Network
↓ ${formatBytes(netRx)} / ↑ ${formatBytes(netTx)}
`; } html += '
'; container.innerHTML = html; lastUpdateSpan.textContent = 'Updated: ' + new Date().toLocaleTimeString(); } catch (e) { container.innerHTML = `
❌ Failed to load stats: ${escapeHtml(e.message)}
`; } } // === 24h Aggregated Tab === async function loadAggregated() { if (!aggregatedContainer) return; const data = cachedMonitoringData; if (!data || Object.keys(data).length === 0) { aggregatedContainer.innerHTML = '
📈No monitoring data available. Open the Live Stats tab first.
'; return; } let html = '
'; for (const [id, info] of Object.entries(data)) { const agg = info.aggregated; if (!agg) continue; html += `
${info.name || id}
${agg.cpu?.avg?.toFixed(1) || 0}%Avg CPU
${agg.cpu?.max?.toFixed(1) || 0}%Max CPU
${agg.memory?.avg?.toFixed(1) || 0}%Avg Mem
${agg.memory?.max?.toFixed(1) || 0}%Max Mem
${agg.dataPoints ? `
${agg.dataPoints} data points over ${agg.timeRange || 24}h
` : ''}
`; } html += '
'; aggregatedContainer.innerHTML = html; } // === Alerts Tab === async function loadAlerts() { if (!alertsContainer) return; alertsContainer.innerHTML = '
Loading alerts...
'; const data = cachedMonitoringData; if (!data || Object.keys(data).length === 0) { alertsContainer.innerHTML = '
🔔No containers found. Open the Live Stats tab first.
'; return; } let html = '
'; for (const [id, info] of Object.entries(data)) { const alertCfg = info.alertConfig || {}; html += `
${info.name || id}
`; } html += '
'; alertsContainer.innerHTML = html; // Wire up save buttons alertsContainer.querySelectorAll('.alert-save-btn').forEach(btn => { btn.addEventListener('click', async () => { const cId = btn.dataset.container; const enabled = alertsContainer.querySelector(`.alert-enabled[data-container="${cId}"]`)?.checked || false; const cpuThreshold = parseInt(alertsContainer.querySelector(`.alert-cpu[data-container="${cId}"]`)?.value) || 80; const memoryThreshold = parseInt(alertsContainer.querySelector(`.alert-mem[data-container="${cId}"]`)?.value) || 85; const cooldownMinutes = parseInt(alertsContainer.querySelector(`.alert-cooldown[data-container="${cId}"]`)?.value) || 15; const autoRestart = alertsContainer.querySelector(`.alert-autorestart[data-container="${cId}"]`)?.checked || false; try { const res = await secureFetch(`/api/v1/monitoring/alerts/${cId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled, cpuThreshold, memoryThreshold, cooldownMinutes, autoRestart }) }); const data = await res.json(); btn.textContent = data.success ? '✅ Saved' : '⚠️ Failed'; setTimeout(() => { btn.textContent = 'Save'; }, 2000); } catch (e) { btn.textContent = '❌ Error'; setTimeout(() => { btn.textContent = 'Save'; }, 2000); } }); }); } function startAutoRefresh() { if (refreshInterval) clearInterval(refreshInterval); if (autoRefreshCheckbox?.checked) { refreshInterval = setInterval(loadStats, DC.POLL.STATS); } } function stopAutoRefresh() { if (refreshInterval) { clearInterval(refreshInterval); refreshInterval = null; } } // Open modal openBtn?.addEventListener('click', () => { modal.classList.add('show'); loadStats(); startAutoRefresh(); }); // Close modal cancelBtn?.addEventListener('click', () => { modal.classList.remove('show'); stopAutoRefresh(); }); modal?.addEventListener('click', (e) => { if (e.target === modal) { modal.classList.remove('show'); stopAutoRefresh(); } }); refreshBtn?.addEventListener('click', loadStats); autoRefreshCheckbox?.addEventListener('change', () => { if (autoRefreshCheckbox.checked) startAutoRefresh(); else stopAutoRefresh(); }); // Lazy-load tabs document.querySelector('[data-panel="stats-aggregated"]')?.addEventListener('click', loadAggregated); document.querySelector('[data-panel="stats-alerts"]')?.addEventListener('click', loadAlerts); })();