Feature: Storage donut utilization chart - substituindo cilindro por gráfico donut PatternFly com Victory.js

This commit is contained in:
2025-10-17 15:20:36 -03:00
parent ea7f92c8fc
commit 40f876cc17

View File

@@ -343,114 +343,47 @@
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
} }
/* Storage Cylinder Chart */ /* Storage Donut Chart */
.storage-cylinder-card { .storage-donut-card {
background: linear-gradient(135deg, #2B2B2B 0%, #1E1E1E 100%); background: linear-gradient(135deg, #2B2B2B 0%, #1E1E1E 100%);
border: 1px solid #404040; border: 1px solid #404040;
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
text-align: center;
transition: all 0.3s ease; transition: all 0.3s ease;
display: flex; min-height: 300px;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
} }
.storage-cylinder-card:hover { .storage-donut-card:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
} }
.storage-cylinder { .storage-donut-chart {
width: 80px;
height: 120px;
position: relative;
margin: 16px 0;
}
.cylinder-container {
width: 100%; width: 100%;
height: 100%; height: 250px;
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.cylinder-outline { .storage-donut-info {
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;
text-align: center; 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; font-size: 14px;
color: var(--pf-global--Color--200); color: var(--pf-global--Color--200);
margin-bottom: 4px; 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 Cards */
.chart-card { .chart-card {
background: linear-gradient(135deg, #2B2B2B 0%, #1E1E1E 100%); background: linear-gradient(135deg, #2B2B2B 0%, #1E1E1E 100%);
@@ -1931,23 +1864,20 @@
<!-- Storage Overview --> <!-- Storage Overview -->
<div class="metrics-grid" style="margin-top: 24px;"> <div class="metrics-grid" style="margin-top: 24px;">
<div class="storage-cylinder-card"> <div class="storage-donut-card">
<div class="metric-icon success"> <h3 class="chart-title">
<i class="fas fa-database"></i> <i class="fas fa-database"></i>
</div> Storage Utilization
<div class="storage-cylinder"> </h3>
<div class="cylinder-container"> <div class="storage-donut-chart" id="storage-donut-chart">
<div class="cylinder-outline"> <div class="loading-spinner">
<div class="cylinder-fill" id="storage-cylinder-fill" style="height: 0%"></div> <i class="fas fa-spinner fa-spin"></i>
</div> Loading storage utilization...
<div class="cylinder-top"></div>
<div class="cylinder-bottom"></div>
</div> </div>
</div> </div>
<div class="storage-cylinder-info"> <div class="storage-donut-info">
<div class="storage-cylinder-label">Storage Usage</div> <div class="storage-donut-title" id="storage-donut-title">Storage Usage</div>
<div class="storage-cylinder-value" id="storage-cylinder-used">-</div> <div class="storage-donut-subtitle" id="storage-donut-subtitle">0% of 100 GB</div>
<div class="storage-cylinder-percentage" id="storage-cylinder-percentage">0%</div>
</div> </div>
</div> </div>
<div class="metric-card"> <div class="metric-card">
@@ -2910,52 +2840,109 @@
document.getElementById('dashboard-storage-warnings').textContent = data.storage_warnings || '0'; document.getElementById('dashboard-storage-warnings').textContent = data.storage_warnings || '0';
document.getElementById('dashboard-storage-classes').textContent = data.storage_classes ? data.storage_classes.length : '0'; document.getElementById('dashboard-storage-classes').textContent = data.storage_classes ? data.storage_classes.length : '0';
// Update storage cylinder // Update storage donut chart
updateStorageCylinder(data); updateStorageDonutChart(data);
} catch (error) { } catch (error) {
console.error('Error updating dashboard storage metrics:', error); console.error('Error updating dashboard storage metrics:', error);
} }
} }
function updateStorageCylinder(data) { function updateStorageDonutChart(data) {
try { try {
const totalStorageUsed = data.total_storage_used || 0; const totalStorageUsed = data.total_storage_used || 0;
const storageUtilization = data.storage_utilization_percent || 0; const storageUtilization = data.storage_utilization_percent || 0;
// Calculate cylinder fill percentage (use storage utilization or mock calculation) // Calculate utilization percentage
const fillPercentage = Math.min(100, Math.max(0, storageUtilization)); const utilizationPercentage = Math.min(100, Math.max(0, storageUtilization));
const unusedPercentage = 100 - utilizationPercentage;
// Update cylinder fill // Update donut chart info
const cylinderFill = document.getElementById('storage-cylinder-fill'); document.getElementById('storage-donut-title').textContent = `${utilizationPercentage.toFixed(1)}%`;
if (cylinderFill) { document.getElementById('storage-donut-subtitle').textContent = `${formatBytes(totalStorageUsed)} of 100 GB`;
cylinderFill.style.height = `${fillPercentage}%`;
}
// Update cylinder info // Create donut chart using Victory.js
document.getElementById('storage-cylinder-used').textContent = formatBytes(totalStorageUsed); const container = document.getElementById('storage-donut-chart');
document.getElementById('storage-cylinder-percentage').textContent = `${fillPercentage.toFixed(1)}%`;
// Change color based on utilization level // Clear container
const cylinderPercentage = document.getElementById('storage-cylinder-percentage'); container.innerHTML = '';
if (cylinderPercentage) {
if (fillPercentage >= 90) { // Create React element for donut chart
cylinderPercentage.style.color = 'var(--pf-global--danger-color--100)'; const donutChart = React.createElement(Victory.VictoryPie, {
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%)'; data: [
} else if (fillPercentage >= 75) { { x: 'Used', y: utilizationPercentage },
cylinderPercentage.style.color = 'var(--pf-global--warning-color--100)'; { x: 'Unused', y: unusedPercentage }
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) { width: 200,
cylinderPercentage.style.color = 'var(--pf-global--info-color--100)'; height: 200,
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%)'; innerRadius: 60,
} else { colorScale: [
cylinderPercentage.style.color = 'var(--pf-global--success-color--100)'; utilizationPercentage >= 90 ? '#dc2626' :
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%)'; 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) { } 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 = `
<div style="text-align: center; padding: 40px; color: var(--pf-global--Color--300);">
<i class="fas fa-chart-pie" style="font-size: 48px; margin-bottom: 16px;"></i>
<p>Storage utilization chart</p>
<p style="font-size: 14px; color: var(--pf-global--Color--400);">
Chart library not available
</p>
</div>
`;
} }
} }