// Disk Monitoring - Admin UI // CSP-compliant implementation using DOM manipulation async function loadMetrics() { const loading = document.getElementById('loading'); const metricsContainer = document.getElementById('metrics-container'); const errorDiv = document.getElementById('error'); try { loading.classList.remove('hidden'); metricsContainer.classList.add('hidden'); errorDiv.classList.add('hidden'); const token = localStorage.getItem('admin_token'); if (!token) { throw new Error('Not authenticated. Please log in.'); } const response = await fetch('/api/admin/disk-metrics', { headers: { 'Authorization': 'Bearer ' + token } }); if (!response.ok) { throw new Error('Failed to fetch metrics: ' + response.status); } const result = await response.json(); if (!result.success) { throw new Error(result.error || 'Unknown error'); } renderMetrics(result.data); loading.classList.add('hidden'); metricsContainer.classList.remove('hidden'); } catch (err) { console.error('Load metrics error:', err); loading.classList.add('hidden'); errorDiv.classList.remove('hidden'); const errorMessage = document.getElementById('error-message'); errorMessage.textContent = err.message || 'An unexpected error occurred'; } } function renderMetrics(data) { const localContainer = document.getElementById('local-metrics'); const remoteContainer = document.getElementById('remote-metrics'); // Clear existing content localContainer.textContent = ''; remoteContainer.textContent = ''; // Render local metrics if (data.local) { renderSystemMetrics(localContainer, data.local, 'Local Development'); } else { renderError(localContainer, 'Local metrics unavailable'); } // Render remote metrics if (data.remote) { renderSystemMetrics(remoteContainer, data.remote, 'Production VPS'); } else { renderError(remoteContainer, 'Remote metrics unavailable'); } } function renderSystemMetrics(container, metrics, label) { // Disk Usage Card const diskCard = createMetricCard( 'Disk Usage', metrics.health, [ { label: 'Total', value: metrics.total }, { label: 'Used', value: metrics.used }, { label: 'Available', value: metrics.available }, { label: 'Usage', value: metrics.usedPercent + '%', progress: metrics.usedPercent } ] ); container.appendChild(diskCard); // Memory Card if (metrics.memory) { const memoryCard = createMetricCard( 'Memory', { level: metrics.memory.usedPercent >= 90 ? 'critical' : metrics.memory.usedPercent >= 80 ? 'warning' : 'healthy' }, [ { label: 'Total', value: metrics.memory.total }, { label: 'Used', value: metrics.memory.usedPercent + '%', progress: metrics.memory.usedPercent } ] ); container.appendChild(memoryCard); } // System Info Card const sysCard = createMetricCard( 'System Info', null, [ { label: 'Hostname', value: metrics.hostname || 'Unknown' }, { label: 'Platform', value: metrics.platform || 'Unknown' }, { label: 'Uptime', value: (metrics.uptime || 0) + ' hours' } ] ); container.appendChild(sysCard); // Docker Volumes (if present) if (metrics.docker) { const dockerCard = createMetricCard( 'Docker Volumes', null, [ { label: 'Total', value: metrics.docker.total }, { label: 'Used', value: metrics.docker.used } ] ); container.appendChild(dockerCard); } } function createMetricCard(title, health, items) { const card = document.createElement('div'); card.className = 'metric-card bg-white rounded-lg shadow-lg p-6'; // Card header const header = document.createElement('div'); header.className = 'flex items-center justify-between mb-4'; const titleEl = document.createElement('h3'); titleEl.className = 'text-lg font-semibold text-gray-900'; titleEl.textContent = title; header.appendChild(titleEl); // Health indicator (if present) if (health) { const indicator = document.createElement('span'); indicator.className = 'health-indicator health-' + health.level; indicator.title = health.level.charAt(0).toUpperCase() + health.level.slice(1); header.appendChild(indicator); } card.appendChild(header); // Card content items.forEach(item => { const row = document.createElement('div'); row.className = 'mb-3'; const labelDiv = document.createElement('div'); labelDiv.className = 'flex justify-between text-sm mb-1'; const labelSpan = document.createElement('span'); labelSpan.className = 'text-gray-600'; labelSpan.textContent = item.label; labelDiv.appendChild(labelSpan); const valueSpan = document.createElement('span'); valueSpan.className = 'font-semibold text-gray-900'; valueSpan.textContent = item.value; labelDiv.appendChild(valueSpan); row.appendChild(labelDiv); // Progress bar (if present) if (item.progress !== undefined) { const progressBg = document.createElement('div'); progressBg.className = 'w-full bg-gray-200 rounded-full h-2'; const progressBar = document.createElement('div'); progressBar.className = 'progress-bar h-2 rounded-full ' + getProgressColor(item.progress); progressBar.style.width = item.progress + '%'; progressBg.appendChild(progressBar); row.appendChild(progressBg); } card.appendChild(row); }); return card; } function renderError(container, message) { const errorDiv = document.createElement('div'); errorDiv.className = 'col-span-3 bg-yellow-50 border-l-4 border-yellow-500 p-4 rounded'; const errorText = document.createElement('p'); errorText.className = 'text-yellow-800'; errorText.textContent = '⚠️ ' + message; errorDiv.appendChild(errorText); container.appendChild(errorDiv); } function getProgressColor(percent) { if (percent >= 90) return 'bg-red-600'; if (percent >= 80) return 'bg-orange-500'; if (percent >= 70) return 'bg-yellow-500'; return 'bg-green-600'; } // Refresh functionality document.addEventListener('DOMContentLoaded', () => { loadMetrics(); // Refresh button const refreshBtn = document.getElementById('refresh-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', loadMetrics); } // Auto-refresh every 5 minutes setInterval(loadMetrics, 5 * 60 * 1000); });