// API Configuration // IMPORTANT: Replace these values with your actual Unraid server details const API_CONFIG = { serverUrl: 'http://YOUR_UNRAID_IP:PORT/graphql', apiKey: 'YOUR_API_KEY_HERE' }; // GraphQL query executor async function executeGraphQLQuery(query) { try { const response = await fetch(API_CONFIG.serverUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_CONFIG.apiKey }, body: JSON.stringify({ query }) }); if (!response.ok) { const errorText = await response.text(); console.error('API Error:', errorText); throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); if (result.errors) { console.error('GraphQL errors:', result.errors); throw new Error('GraphQL query failed'); } return result.data; } catch (error) { console.error('API request failed:', error); throw error; } } // Fetch system metrics (CPU, Memory usage) async function fetchSystemMetrics() { const query = `query { metrics { cpu { percentTotal } memory { total used free percentTotal } } }`; const data = await executeGraphQLQuery(query); return data.metrics; } // Fetch array and disk information async function fetchArrayInfo() { const query = `query { array { state disks { name size status temp fsSize fsFree fsUsed } parityCheckStatus { status errors date } } }`; const data = await executeGraphQLQuery(query); return data.array; } // Fetch Docker container information async function fetchDockerContainers() { const query = `query { docker { containers { id names state status autoStart } } }`; const data = await executeGraphQLQuery(query); return data.docker.containers; } // Update timestamp function updateTimestamp() { const now = new Date(); const formatted = now.toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); document.getElementById('timestamp').textContent = formatted; } // Update CPU usage function updateCPU(percentage) { const progressBar = document.getElementById('cpu-progress'); const progressText = document.getElementById('cpu-text'); progressBar.style.width = percentage + '%'; progressText.textContent = percentage + '%'; // Apply color coding progressBar.classList.remove('warning', 'critical'); if (percentage >= 90) { progressBar.classList.add('critical'); } else if (percentage >= 70) { progressBar.classList.add('warning'); } } // Update Memory usage function updateMemory(percentage) { const progressBar = document.getElementById('memory-progress'); const progressText = document.getElementById('memory-text'); progressBar.style.width = percentage + '%'; progressText.textContent = percentage + '%'; // Apply color coding progressBar.classList.remove('warning', 'critical'); if (percentage >= 90) { progressBar.classList.add('critical'); } else if (percentage >= 70) { progressBar.classList.add('warning'); } } // Update Disk Ring Chart function updateDiskRingChart(percentage, usedKB, totalKB) { const ring = document.getElementById('disk-ring'); const text = document.getElementById('disk-percentage'); const fraction = document.getElementById('disk-fraction'); // SVG circle circumference calculation: 2 * PI * radius (radius = 80) const circumference = 2 * Math.PI * 80; // 502.65 const offset = circumference - (percentage / 100) * circumference; ring.style.strokeDashoffset = offset; text.textContent = percentage + '%'; // Calculate and display fraction (convert KB to TB, rounded to 2 decimals) const usedTB = (usedKB / 1024 / 1024 / 1024).toFixed(2); const totalTB = (totalKB / 1024 / 1024 / 1024).toFixed(2); fraction.textContent = `${usedTB} / ${totalTB} TB`; // Change color based on usage if (percentage >= 90) { ring.style.stroke = '#ffffff'; text.style.fill = '#ffffff'; fraction.style.fill = '#ffffff'; } else if (percentage >= 70) { ring.style.stroke = '#ff00ff'; text.style.fill = '#ff00ff'; fraction.style.fill = '#ff00ff'; } else { ring.style.stroke = '#00ffff'; text.style.fill = '#00ffff'; fraction.style.fill = '#00ffff'; } } // Update Photos Ring Chart (Disk 7) function updatePhotosRingChart(percentage, usedKB, totalKB) { const ring = document.getElementById('photos-ring'); const text = document.getElementById('photos-percentage'); const fraction = document.getElementById('photos-fraction'); const circumference = 2 * Math.PI * 80; const offset = circumference - (percentage / 100) * circumference; ring.style.strokeDashoffset = offset; text.textContent = percentage + '%'; const usedTB = (usedKB / 1024 / 1024 / 1024).toFixed(2); const totalTB = (totalKB / 1024 / 1024 / 1024).toFixed(2); fraction.textContent = `${usedTB} / ${totalTB} TB`; // Change color based on usage if (percentage >= 90) { ring.style.stroke = '#ffffff'; text.style.fill = '#ffffff'; fraction.style.fill = '#ffffff'; } else if (percentage >= 70) { ring.style.stroke = '#ff00ff'; text.style.fill = '#ff00ff'; fraction.style.fill = '#ff00ff'; } else { ring.style.stroke = '#00ffff'; text.style.fill = '#00ffff'; fraction.style.fill = '#00ffff'; } } // Update Media Ring Chart (All disks except 6 and 7) function updateMediaRingChart(percentage, usedKB, totalKB) { const ring = document.getElementById('media-ring'); const text = document.getElementById('media-percentage'); const fraction = document.getElementById('media-fraction'); const circumference = 2 * Math.PI * 80; const offset = circumference - (percentage / 100) * circumference; ring.style.strokeDashoffset = offset; text.textContent = percentage + '%'; const usedTB = (usedKB / 1024 / 1024 / 1024).toFixed(2); const totalTB = (totalKB / 1024 / 1024 / 1024).toFixed(2); fraction.textContent = `${usedTB} / ${totalTB} TB`; // Change color based on usage if (percentage >= 90) { ring.style.stroke = '#ffffff'; text.style.fill = '#ffffff'; fraction.style.fill = '#ffffff'; } else if (percentage >= 70) { ring.style.stroke = '#ff00ff'; text.style.fill = '#ff00ff'; fraction.style.fill = '#ff00ff'; } else { ring.style.stroke = '#00ffff'; text.style.fill = '#00ffff'; fraction.style.fill = '#00ffff'; } } // Update Docker Ring Chart (Disk 6) function updateDockerRingChart(percentage, usedKB, totalKB) { const ring = document.getElementById('docker-ring'); const text = document.getElementById('docker-percentage'); const fraction = document.getElementById('docker-fraction'); const circumference = 2 * Math.PI * 80; const offset = circumference - (percentage / 100) * circumference; ring.style.strokeDashoffset = offset; text.textContent = percentage + '%'; const usedTB = (usedKB / 1024 / 1024 / 1024).toFixed(2); const totalTB = (totalKB / 1024 / 1024 / 1024).toFixed(2); fraction.textContent = `${usedTB} / ${totalTB} TB`; // Change color based on usage if (percentage >= 90) { ring.style.stroke = '#ffffff'; text.style.fill = '#ffffff'; fraction.style.fill = '#ffffff'; } else if (percentage >= 70) { ring.style.stroke = '#ff00ff'; text.style.fill = '#ff00ff'; fraction.style.fill = '#ff00ff'; } else { ring.style.stroke = '#00ffff'; text.style.fill = '#00ffff'; fraction.style.fill = '#00ffff'; } } // Update Parity status function updateParity(parityData) { const statusElement = document.getElementById('parity-status'); const errorsElement = document.getElementById('parity-errors'); statusElement.textContent = parityData.status; errorsElement.textContent = parityData.errors; if (parityData.status !== 'VALID' || parityData.errors > 0) { statusElement.classList.add('error'); errorsElement.classList.add('error'); } else { statusElement.classList.remove('error'); errorsElement.classList.remove('error'); } document.getElementById('parity-last-check').textContent = parityData.lastCheck; } // Update Disk Array function updateDisks(disks) { const container = document.getElementById('disk-container'); container.innerHTML = ''; disks.forEach(disk => { const diskElement = document.createElement('div'); diskElement.className = 'disk-item'; const usedPercentage = disk.used; let progressClass = ''; if (usedPercentage >= 90) { progressClass = 'critical'; } else if (usedPercentage >= 80) { progressClass = 'warning'; } // Add label for disk6 and disk7 let diskLabel = disk.name; if (disk.name === 'disk6') { diskLabel = disk.name + ' (docker)'; } else if (disk.name === 'disk7') { diskLabel = disk.name + ' (photos)'; } diskElement.innerHTML = `