feat: restore circular gauges for system health display

- Replaced horizontal progress bars with beautiful circular SVG gauges
- Added smooth animations for gauge updates
- Color-coded gauges (green < 70%, orange < 85%, red >= 85%)
- Centered layout with better visual hierarchy
- Maintains auto-refresh and dynamic updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2025-12-26 09:41:12 +01:00
parent d61dcf2821
commit b7e9b21b47
2 changed files with 112 additions and 60 deletions

View File

@ -141,52 +141,64 @@
padding-bottom: 12px;
}
/* Progress Bars */
/* Circular Gauges */
.secubox-health-grid {
display: flex;
flex-direction: column;
gap: 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 24px;
justify-items: center;
}
.secubox-progress-item {
.secubox-gauge-container {
display: flex;
flex-direction: column;
gap: 8px;
}
.secubox-progress-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.secubox-progress-label {
font-weight: 600;
color: #1e293b;
font-size: 14px;
.secubox-gauge {
position: relative;
width: 120px;
height: 120px;
}
.secubox-progress-value {
font-weight: 700;
font-size: 16px;
}
.secubox-progress-bar {
height: 8px;
background: #f1f5f9;
border-radius: 4px;
overflow: hidden;
}
.secubox-progress-fill {
.secubox-gauge-svg {
width: 100%;
height: 100%;
transition: width 0.6s ease, background 0.3s ease;
border-radius: 4px;
}
.secubox-progress-details {
.secubox-gauge-progress {
transition: stroke-dashoffset 0.8s ease, stroke 0.3s ease;
}
.secubox-gauge-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.secubox-gauge-percent {
font-size: 24px;
font-weight: 700;
line-height: 1;
margin-bottom: 4px;
}
.secubox-gauge-label {
font-size: 12px;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
.secubox-gauge-details {
font-size: 12px;
color: #64748b;
text-align: center;
max-width: 140px;
}
/* Active Modules */

View File

@ -154,39 +154,64 @@ return view.extend({
return E('div', { 'class': 'secubox-card' }, [
E('h3', { 'class': 'secubox-card-title' }, '📊 System Health'),
E('div', { 'class': 'secubox-health-grid' }, [
this.renderProgressBar('CPU', cpu.percent || 0, cpu.load_1min || '0.00', 'cpu'),
this.renderProgressBar('Memory', memory.percent || 0,
this.renderCircularGauge('CPU', cpu.percent || 0,
'Load: ' + (cpu.load_1min || '0.00'), 'cpu', '#6366f1'),
this.renderCircularGauge('Memory', memory.percent || 0,
API.formatBytes((memory.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((memory.total_kb || 0) * 1024), 'memory'),
this.renderProgressBar('Disk', disk.percent || 0,
API.formatBytes((memory.total_kb || 0) * 1024), 'memory', '#22c55e'),
this.renderCircularGauge('Disk', disk.percent || 0,
API.formatBytes((disk.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((disk.total_kb || 0) * 1024), 'disk')
API.formatBytes((disk.total_kb || 0) * 1024), 'disk', '#f59e0b')
])
]);
},
renderProgressBar: function(label, percent, details, id) {
renderCircularGauge: function(label, percent, details, id, baseColor) {
var color = percent < 70 ? '#22c55e' : percent < 85 ? '#f59e0b' : '#ef4444';
var radius = 45;
var circumference = 2 * Math.PI * radius;
var offset = circumference - (percent / 100) * circumference;
return E('div', { 'class': 'secubox-progress-item' }, [
E('div', { 'class': 'secubox-progress-header' }, [
E('span', { 'class': 'secubox-progress-label' }, label),
E('span', {
'class': 'secubox-progress-value',
'id': 'health-' + id + '-percent',
'style': 'color: ' + color
}, percent + '%')
]),
E('div', { 'class': 'secubox-progress-bar' }, [
E('div', {
'class': 'secubox-progress-fill',
'id': 'health-' + id + '-bar',
'style': 'width: ' + percent + '%; background: ' + color
})
return E('div', { 'class': 'secubox-gauge-container' }, [
E('div', { 'class': 'secubox-gauge' }, [
E('svg', { 'viewBox': '0 0 120 120', 'class': 'secubox-gauge-svg' }, [
// Background circle
E('circle', {
'cx': '60',
'cy': '60',
'r': radius,
'fill': 'none',
'stroke': '#f1f5f9',
'stroke-width': '10'
}),
// Progress circle
E('circle', {
'id': 'gauge-' + id,
'cx': '60',
'cy': '60',
'r': radius,
'fill': 'none',
'stroke': color,
'stroke-width': '10',
'stroke-linecap': 'round',
'stroke-dasharray': circumference,
'stroke-dashoffset': offset,
'transform': 'rotate(-90 60 60)',
'class': 'secubox-gauge-progress'
})
]),
E('div', { 'class': 'secubox-gauge-content' }, [
E('div', {
'class': 'secubox-gauge-percent',
'id': 'gauge-' + id + '-percent',
'style': 'color: ' + color
}, Math.round(percent) + '%'),
E('div', { 'class': 'secubox-gauge-label' }, label)
])
]),
E('div', {
'class': 'secubox-progress-details',
'id': 'health-' + id + '-details'
'class': 'secubox-gauge-details',
'id': 'gauge-' + id + '-details'
}, details)
]);
},
@ -371,16 +396,31 @@ return view.extend({
var percent = data.percent || 0;
var color = percent < 70 ? '#22c55e' : percent < 85 ? '#f59e0b' : '#ef4444';
var percentEl = document.getElementById('health-' + type + '-percent');
var barEl = document.getElementById('health-' + type + '-bar');
var percentEl = document.getElementById('gauge-' + type + '-percent');
var gaugeEl = document.getElementById('gauge-' + type);
if (percentEl) {
percentEl.textContent = percent + '%';
percentEl.textContent = Math.round(percent) + '%';
percentEl.style.color = color;
}
if (barEl) {
barEl.style.width = percent + '%';
barEl.style.background = color;
if (gaugeEl) {
var radius = 45;
var circumference = 2 * Math.PI * radius;
var offset = circumference - (percent / 100) * circumference;
gaugeEl.setAttribute('stroke-dashoffset', offset);
gaugeEl.setAttribute('stroke', color);
}
// Update details
var detailsEl = document.getElementById('gauge-' + type + '-details');
if (detailsEl && type === 'cpu') {
detailsEl.textContent = 'Load: ' + (data.load_1min || '0.00');
} else if (detailsEl && type === 'memory') {
detailsEl.textContent = API.formatBytes((data.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((data.total_kb || 0) * 1024);
} else if (detailsEl && type === 'disk') {
detailsEl.textContent = API.formatBytes((data.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((data.total_kb || 0) * 1024);
}
},