From b7e9b21b47fbc2050a0678ff5290478856072bdd Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Fri, 26 Dec 2025 09:41:12 +0100 Subject: [PATCH] feat: restore circular gauges for system health display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../resources/secubox/dashboard.css | 74 ++++++++------ .../resources/view/secubox/dashboard.js | 98 +++++++++++++------ 2 files changed, 112 insertions(+), 60 deletions(-) diff --git a/luci-app-secubox/htdocs/luci-static/resources/secubox/dashboard.css b/luci-app-secubox/htdocs/luci-static/resources/secubox/dashboard.css index afb525b9..60ac41ce 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/secubox/dashboard.css +++ b/luci-app-secubox/htdocs/luci-static/resources/secubox/dashboard.css @@ -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 */ diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js index 36d63da3..1d26c1da 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js @@ -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); } },