From 40f876cc17bbd60a0dd8aa340ca4cea6a1f3c90f Mon Sep 17 00:00:00 2001 From: andersonid Date: Fri, 17 Oct 2025 15:20:36 -0300 Subject: [PATCH] =?UTF-8?q?Feature:=20Storage=20donut=20utilization=20char?= =?UTF-8?q?t=20-=20substituindo=20cilindro=20por=20gr=C3=A1fico=20donut=20?= =?UTF-8?q?PatternFly=20com=20Victory.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/index.html | 241 ++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 127 deletions(-) diff --git a/app/static/index.html b/app/static/index.html index e206bae..871b4ab 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -343,113 +343,46 @@ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); } - /* Storage Cylinder Chart */ - .storage-cylinder-card { + /* Storage Donut Chart */ + .storage-donut-card { background: linear-gradient(135deg, #2B2B2B 0%, #1E1E1E 100%); border: 1px solid #404040; border-radius: 8px; padding: 20px; - text-align: center; transition: all 0.3s ease; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 200px; + min-height: 300px; } - .storage-cylinder-card:hover { + .storage-donut-card:hover { transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); } - .storage-cylinder { - width: 80px; - height: 120px; - position: relative; - margin: 16px 0; - } - - .cylinder-container { + .storage-donut-chart { width: 100%; - height: 100%; - position: relative; + height: 250px; display: flex; align-items: center; justify-content: center; } - .cylinder-outline { - width: 60px; - height: 100px; - border: 3px solid rgba(255, 255, 255, 0.3); - border-radius: 30px; - position: relative; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - } - - .cylinder-fill { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - background: linear-gradient(to top, - rgba(34, 197, 94, 0.8) 0%, - rgba(34, 197, 94, 0.6) 50%, - rgba(34, 197, 94, 0.4) 100%); - border-radius: 0 0 27px 27px; - transition: height 0.8s ease-in-out; - min-height: 2px; - } - - .cylinder-top { - position: absolute; - top: -3px; - left: -3px; - width: 66px; - height: 20px; - border: 3px solid rgba(255, 255, 255, 0.3); - border-bottom: none; - border-radius: 33px 33px 0 0; - background: rgba(255, 255, 255, 0.05); - } - - .cylinder-bottom { - position: absolute; - bottom: -3px; - left: -3px; - width: 66px; - height: 20px; - border: 3px solid rgba(255, 255, 255, 0.3); - border-top: none; - border-radius: 0 0 33px 33px; - background: rgba(255, 255, 255, 0.05); - } - - .storage-cylinder-info { - margin-top: 12px; + .storage-donut-info { text-align: center; + margin-top: 16px; } - .storage-cylinder-label { + .storage-donut-title { + font-size: 16px; + font-weight: 600; + color: var(--pf-global--Color--100); + margin-bottom: 8px; + } + + .storage-donut-subtitle { font-size: 14px; color: var(--pf-global--Color--200); margin-bottom: 4px; } - - .storage-cylinder-value { - font-size: 18px; - font-weight: 600; - color: var(--pf-global--Color--100); - margin-bottom: 2px; - } - - .storage-cylinder-percentage { - font-size: 12px; - color: var(--pf-global--success-color--100); - font-weight: 500; - } /* Chart Cards */ .chart-card { @@ -1931,23 +1864,20 @@
-
-
+
+

-

-
-
-
-
-
-
-
+ Storage Utilization + +
+
+ + Loading storage utilization...
-
-
Storage Usage
-
-
-
0%
+
+
Storage Usage
+
0% of 100 GB
@@ -2910,52 +2840,109 @@ document.getElementById('dashboard-storage-warnings').textContent = data.storage_warnings || '0'; document.getElementById('dashboard-storage-classes').textContent = data.storage_classes ? data.storage_classes.length : '0'; - // Update storage cylinder - updateStorageCylinder(data); + // Update storage donut chart + updateStorageDonutChart(data); } catch (error) { console.error('Error updating dashboard storage metrics:', error); } } - function updateStorageCylinder(data) { + function updateStorageDonutChart(data) { try { const totalStorageUsed = data.total_storage_used || 0; const storageUtilization = data.storage_utilization_percent || 0; - // Calculate cylinder fill percentage (use storage utilization or mock calculation) - const fillPercentage = Math.min(100, Math.max(0, storageUtilization)); + // Calculate utilization percentage + const utilizationPercentage = Math.min(100, Math.max(0, storageUtilization)); + const unusedPercentage = 100 - utilizationPercentage; - // Update cylinder fill - const cylinderFill = document.getElementById('storage-cylinder-fill'); - if (cylinderFill) { - cylinderFill.style.height = `${fillPercentage}%`; - } + // Update donut chart info + document.getElementById('storage-donut-title').textContent = `${utilizationPercentage.toFixed(1)}%`; + document.getElementById('storage-donut-subtitle').textContent = `${formatBytes(totalStorageUsed)} of 100 GB`; - // Update cylinder info - document.getElementById('storage-cylinder-used').textContent = formatBytes(totalStorageUsed); - document.getElementById('storage-cylinder-percentage').textContent = `${fillPercentage.toFixed(1)}%`; + // Create donut chart using Victory.js + const container = document.getElementById('storage-donut-chart'); - // Change color based on utilization level - const cylinderPercentage = document.getElementById('storage-cylinder-percentage'); - if (cylinderPercentage) { - if (fillPercentage >= 90) { - cylinderPercentage.style.color = 'var(--pf-global--danger-color--100)'; - cylinderFill.style.background = 'linear-gradient(to top, rgba(220, 38, 38, 0.8) 0%, rgba(220, 38, 38, 0.6) 50%, rgba(220, 38, 38, 0.4) 100%)'; - } else if (fillPercentage >= 75) { - cylinderPercentage.style.color = 'var(--pf-global--warning-color--100)'; - cylinderFill.style.background = 'linear-gradient(to top, rgba(245, 158, 11, 0.8) 0%, rgba(245, 158, 11, 0.6) 50%, rgba(245, 158, 11, 0.4) 100%)'; - } else if (fillPercentage >= 50) { - cylinderPercentage.style.color = 'var(--pf-global--info-color--100)'; - cylinderFill.style.background = 'linear-gradient(to top, rgba(59, 130, 246, 0.8) 0%, rgba(59, 130, 246, 0.6) 50%, rgba(59, 130, 246, 0.4) 100%)'; - } else { - cylinderPercentage.style.color = 'var(--pf-global--success-color--100)'; - cylinderFill.style.background = 'linear-gradient(to top, rgba(34, 197, 94, 0.8) 0%, rgba(34, 197, 94, 0.6) 50%, rgba(34, 197, 94, 0.4) 100%)'; + // Clear container + container.innerHTML = ''; + + // Create React element for donut chart + const donutChart = React.createElement(Victory.VictoryPie, { + data: [ + { x: 'Used', y: utilizationPercentage }, + { x: 'Unused', y: unusedPercentage } + ], + width: 200, + height: 200, + innerRadius: 60, + colorScale: [ + utilizationPercentage >= 90 ? '#dc2626' : + utilizationPercentage >= 75 ? '#f59e0b' : + utilizationPercentage >= 50 ? '#3b82f6' : '#22c55e', + '#374151' + ], + style: { + data: { + stroke: '#1f2937', + strokeWidth: 2 + } + }, + labels: () => null, + animate: { + duration: 1000, + onLoad: { duration: 500 } } - } + }); + + // Create center text + const centerText = React.createElement('text', { + x: 100, + y: 100, + textAnchor: 'middle', + dominantBaseline: 'middle', + style: { + fontSize: '24px', + fontWeight: 'bold', + fill: '#f9fafb' + } + }, `${utilizationPercentage.toFixed(1)}%`); + + // Create subtitle + const subtitle = React.createElement('text', { + x: 100, + y: 125, + textAnchor: 'middle', + dominantBaseline: 'middle', + style: { + fontSize: '12px', + fill: '#9ca3af' + } + }, 'Storage Used'); + + // Create SVG container + const svgContainer = React.createElement('svg', { + width: 200, + height: 200, + viewBox: '0 0 200 200' + }, donutChart, centerText, subtitle); + + // Render the chart + ReactDOM.render(svgContainer, container); } catch (error) { - console.error('Error updating storage cylinder:', error); + console.error('Error updating storage donut chart:', error); + // Fallback to simple text display + const container = document.getElementById('storage-donut-chart'); + container.innerHTML = ` +
+ +

Storage utilization chart

+

+ Chart library not available +

+
+ `; } }