From 00f2f20c2ff67757df74dc2c66c56ca3632e2d73 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sun, 28 Dec 2025 03:20:03 +0100 Subject: [PATCH] feat(system-hub): modernize Quick Status widgets with histograms and gradients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Transform Quick Status Indicators to modern card-based design - Add progress bars with gradient animations for each status - Implement detail grids showing additional metrics: - Internet: WAN IP, Gateway, Status - DNS: Primary/Secondary servers, Query rate - NTP: Server, Offset, Last sync - Firewall: Input/Forward/Output policies - Apply color-coded gradients: - Internet: Blue (#3b82f6 → #2563eb) - DNS: Green (#10b981 → #059669) - NTP: Purple (#8b5cf6 → #7c3aed) - Firewall: Orange (#f59e0b → #d97706) - Add shimmer animations on progress bars - Implement hover effects with lift and shadow - Use JetBrains Mono for numeric values - Full responsive support for mobile devices Version: 0.3.2 --- .../resources/system-hub/overview.css | 214 ++++++++++++++++++ .../resources/view/system-hub/overview.js | 197 ++++++++++++---- 2 files changed, 365 insertions(+), 46 deletions(-) diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/overview.css b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/overview.css index 85751bf4..906901ff 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/overview.css +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/overview.css @@ -852,6 +852,211 @@ color: var(--sh-text-primary); } +/* ======================== */ +/* Modern Quick Status (v0.3.2) */ +/* ======================== */ + +.sh-status-modern-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +/* Status metric card */ +.sh-st-metric { + position: relative; + background: var(--sh-bg-secondary); + border: 1px solid var(--sh-border); + border-radius: 12px; + padding: 20px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + overflow: hidden; +} + +.sh-st-metric::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 100%; + opacity: 0.8; + transition: opacity 0.3s ease; +} + +.sh-st-metric:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); + border-color: var(--sh-primary); +} + +.sh-st-metric:hover::before { + opacity: 1; +} + +/* Header */ +.sh-st-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 16px; +} + +.sh-st-title-group { + display: flex; + align-items: center; + gap: 10px; +} + +.sh-st-icon { + font-size: 24px; + line-height: 1; +} + +.sh-st-title { + font-size: 16px; + font-weight: 600; + color: var(--sh-text-primary); +} + +.sh-st-value-group { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 6px; +} + +.sh-st-value { + font-family: 'JetBrains Mono', 'Monaco', 'Courier New', monospace; + font-size: 20px; + font-weight: 700; + color: var(--sh-text-primary); + line-height: 1; +} + +.sh-st-badge { + padding: 4px 10px; + border-radius: 12px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.sh-st-badge-ok { + background: rgba(34, 197, 94, 0.15); + color: #22c55e; +} + +.sh-st-badge-warning { + background: rgba(251, 146, 60, 0.15); + color: #fb923c; +} + +.sh-st-badge-critical { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} + +/* Progress bar */ +.sh-st-progress-modern { + position: relative; + height: 8px; + background: var(--sh-bg-tertiary); + border-radius: 8px; + overflow: hidden; + margin-bottom: 16px; +} + +.sh-st-progress-fill { + height: 100%; + border-radius: 8px; + transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.sh-st-progress-fill::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.3), + transparent + ); + animation: shimmer 2s infinite; +} + +/* Details grid */ +.sh-st-details-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.sh-st-detail { + display: flex; + flex-direction: column; + gap: 4px; +} + +.sh-st-detail-label { + font-size: 11px; + font-weight: 500; + color: var(--sh-text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.sh-st-detail-value { + font-size: 13px; + font-weight: 600; + color: var(--sh-text-primary); +} + +/* Gradients for each status widget */ +.sh-st-gradient-internet { + background: linear-gradient(90deg, #3b82f6, #2563eb); + box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); +} + +.sh-st-metric-internet::before { + background: linear-gradient(90deg, #3b82f6, #2563eb); +} + +.sh-st-gradient-dns { + background: linear-gradient(90deg, #10b981, #059669); + box-shadow: 0 0 20px rgba(16, 185, 129, 0.4); +} + +.sh-st-metric-dns::before { + background: linear-gradient(90deg, #10b981, #059669); +} + +.sh-st-gradient-ntp { + background: linear-gradient(90deg, #8b5cf6, #7c3aed); + box-shadow: 0 0 20px rgba(139, 92, 246, 0.4); +} + +.sh-st-metric-ntp::before { + background: linear-gradient(90deg, #8b5cf6, #7c3aed); +} + +.sh-st-gradient-firewall { + background: linear-gradient(90deg, #f59e0b, #d97706); + box-shadow: 0 0 20px rgba(245, 158, 11, 0.4); +} + +.sh-st-metric-firewall::before { + background: linear-gradient(90deg, #f59e0b, #d97706); +} + /* === Responsive === */ @media (max-width: 768px) { .sh-overview-header { @@ -897,6 +1102,15 @@ height: 50px; } + /* v0.3.2 - Responsive status widgets */ + .sh-status-modern-grid { + grid-template-columns: 1fr; + } + + .sh-st-details-grid { + grid-template-columns: 1fr; + } + /* Widget prefs modal responsive */ .sh-prefs-widgets { grid-template-columns: 1fr; diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js index caf5225f..3e8cc15c 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js @@ -250,72 +250,177 @@ return view.extend({ ]); }, + // v0.3.2 - Modern Quick Status with histograms renderQuickStatusIndicators: function() { var network = this.healthData.network || {}; var services = this.healthData.services || {}; - // Determine status colors and icons - var internetStatus = network.wan_up ? 'ok' : 'error'; - var internetIcon = network.wan_up ? '✓' : '✗'; - var internetText = network.wan_up ? 'Connected' : 'Disconnected'; + // Calculate status metrics + var internetUp = network.wan_up; + var internetPercent = internetUp ? 100 : 0; + var internetStatus = internetUp ? 'ok' : 'critical'; - var dnsStatus = network.dns_ok !== false ? 'ok' : 'error'; - var dnsIcon = network.dns_ok !== false ? '✓' : '✗'; - var dnsText = network.dns_ok !== false ? 'Resolving' : 'Failed'; + var dnsOk = network.dns_ok !== false; + var dnsPercent = dnsOk ? 100 : 0; + var dnsStatus = dnsOk ? 'ok' : 'critical'; - var ntpStatus = 'ok'; // Placeholder, would need backend support - var ntpIcon = '✓'; - var ntpText = 'Synced'; + // NTP - placeholder (would need backend) + var ntpPercent = 100; + var ntpStatus = 'ok'; - var fwStatus = 'ok'; - var fwIcon = '✓'; - var fwText = (network.firewall_rules || 0) + ' rules active'; + // Firewall - calculate health based on rules + var fwRules = network.firewall_rules || 0; + var fwPercent = fwRules > 0 ? 100 : 0; + var fwStatus = fwRules > 0 ? 'ok' : 'warning'; - return E('div', { 'class': 'sh-status-indicators-grid' }, [ - // Internet connectivity - E('div', { 'class': 'sh-status-indicator sh-status-' + internetStatus }, [ - E('div', { 'class': 'sh-status-indicator-icon' }, '🌐'), - E('div', { 'class': 'sh-status-indicator-content' }, [ - E('div', { 'class': 'sh-status-indicator-label' }, 'Internet Connectivity'), - E('div', { 'class': 'sh-status-indicator-value' }, [ - E('span', { 'class': 'sh-status-badge sh-status-badge-' + internetStatus }, internetIcon), - E('span', {}, internetText) + return E('div', { 'class': 'sh-status-modern-grid' }, [ + // Internet Connectivity (v0.3.2) + E('div', { 'class': 'sh-st-metric sh-st-metric-internet' }, [ + E('div', { 'class': 'sh-st-header' }, [ + E('div', { 'class': 'sh-st-title-group' }, [ + E('span', { 'class': 'sh-st-icon' }, '🌐'), + E('span', { 'class': 'sh-st-title' }, 'Internet') + ]), + E('div', { 'class': 'sh-st-value-group' }, [ + E('span', { 'class': 'sh-st-value' }, internetUp ? 'Online' : 'Offline'), + E('span', { 'class': 'sh-st-badge sh-st-badge-' + internetStatus }, + internetUp ? 'connected' : 'disconnected') + ]) + ]), + E('div', { 'class': 'sh-st-progress-modern' }, [ + E('div', { + 'class': 'sh-st-progress-fill sh-st-gradient-internet', + 'style': 'width: ' + internetPercent + '%' + }) + ]), + E('div', { 'class': 'sh-st-details-grid' }, [ + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'WAN IP'), + E('span', { 'class': 'sh-st-detail-value sh-monospace' }, + network.wan_ip || 'N/A') + ]), + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Gateway'), + E('span', { 'class': 'sh-st-detail-value sh-monospace' }, + network.gateway || 'N/A') + ]), + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Status'), + E('span', { 'class': 'sh-st-detail-value' }, + internetUp ? '✓ Active' : '✗ Down') ]) ]) ]), - // DNS resolution - E('div', { 'class': 'sh-status-indicator sh-status-' + dnsStatus }, [ - E('div', { 'class': 'sh-status-indicator-icon' }, '🔍'), - E('div', { 'class': 'sh-status-indicator-content' }, [ - E('div', { 'class': 'sh-status-indicator-label' }, 'DNS Resolution'), - E('div', { 'class': 'sh-status-indicator-value' }, [ - E('span', { 'class': 'sh-status-badge sh-status-badge-' + dnsStatus }, dnsIcon), - E('span', {}, dnsText) + // DNS Resolution (v0.3.2) + E('div', { 'class': 'sh-st-metric sh-st-metric-dns' }, [ + E('div', { 'class': 'sh-st-header' }, [ + E('div', { 'class': 'sh-st-title-group' }, [ + E('span', { 'class': 'sh-st-icon' }, '🔍'), + E('span', { 'class': 'sh-st-title' }, 'DNS') + ]), + E('div', { 'class': 'sh-st-value-group' }, [ + E('span', { 'class': 'sh-st-value' }, dnsOk ? 'Resolving' : 'Failed'), + E('span', { 'class': 'sh-st-badge sh-st-badge-' + dnsStatus }, + dnsOk ? 'healthy' : 'error') + ]) + ]), + E('div', { 'class': 'sh-st-progress-modern' }, [ + E('div', { + 'class': 'sh-st-progress-fill sh-st-gradient-dns', + 'style': 'width: ' + dnsPercent + '%' + }) + ]), + E('div', { 'class': 'sh-st-details-grid' }, [ + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Primary DNS'), + E('span', { 'class': 'sh-st-detail-value sh-monospace' }, + network.dns_primary || '8.8.8.8') + ]), + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Secondary'), + E('span', { 'class': 'sh-st-detail-value sh-monospace' }, + network.dns_secondary || '8.8.4.4') + ]), + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Queries'), + E('span', { 'class': 'sh-st-detail-value' }, + (network.dns_queries || 0) + ' / min') ]) ]) ]), - // NTP sync - E('div', { 'class': 'sh-status-indicator sh-status-' + ntpStatus }, [ - E('div', { 'class': 'sh-status-indicator-icon' }, '🕐'), - E('div', { 'class': 'sh-status-indicator-content' }, [ - E('div', { 'class': 'sh-status-indicator-label' }, 'NTP Sync'), - E('div', { 'class': 'sh-status-indicator-value' }, [ - E('span', { 'class': 'sh-status-badge sh-status-badge-' + ntpStatus }, ntpIcon), - E('span', {}, ntpText) + // NTP Sync (v0.3.2) + E('div', { 'class': 'sh-st-metric sh-st-metric-ntp' }, [ + E('div', { 'class': 'sh-st-header' }, [ + E('div', { 'class': 'sh-st-title-group' }, [ + E('span', { 'class': 'sh-st-icon' }, '🕐'), + E('span', { 'class': 'sh-st-title' }, 'NTP Sync') + ]), + E('div', { 'class': 'sh-st-value-group' }, [ + E('span', { 'class': 'sh-st-value' }, 'Synced'), + E('span', { 'class': 'sh-st-badge sh-st-badge-' + ntpStatus }, 'active') + ]) + ]), + E('div', { 'class': 'sh-st-progress-modern' }, [ + E('div', { + 'class': 'sh-st-progress-fill sh-st-gradient-ntp', + 'style': 'width: ' + ntpPercent + '%' + }) + ]), + E('div', { 'class': 'sh-st-details-grid' }, [ + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Server'), + E('span', { 'class': 'sh-st-detail-value sh-monospace' }, + network.ntp_server || 'pool.ntp.org') + ]), + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Offset'), + E('span', { 'class': 'sh-st-detail-value' }, + (network.ntp_offset || '0') + ' ms') + ]), + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Last Sync'), + E('span', { 'class': 'sh-st-detail-value' }, + network.ntp_last_sync || 'Just now') ]) ]) ]), - // Firewall status - E('div', { 'class': 'sh-status-indicator sh-status-' + fwStatus }, [ - E('div', { 'class': 'sh-status-indicator-icon' }, '🛡️'), - E('div', { 'class': 'sh-status-indicator-content' }, [ - E('div', { 'class': 'sh-status-indicator-label' }, 'Firewall'), - E('div', { 'class': 'sh-status-indicator-value' }, [ - E('span', { 'class': 'sh-status-badge sh-status-badge-' + fwStatus }, fwIcon), - E('span', {}, fwText) + // Firewall Status (v0.3.2) + E('div', { 'class': 'sh-st-metric sh-st-metric-firewall' }, [ + E('div', { 'class': 'sh-st-header' }, [ + E('div', { 'class': 'sh-st-title-group' }, [ + E('span', { 'class': 'sh-st-icon' }, '🛡️'), + E('span', { 'class': 'sh-st-title' }, 'Firewall') + ]), + E('div', { 'class': 'sh-st-value-group' }, [ + E('span', { 'class': 'sh-st-value' }, fwRules + ' rules'), + E('span', { 'class': 'sh-st-badge sh-st-badge-' + fwStatus }, + fwRules > 0 ? 'active' : 'inactive') + ]) + ]), + E('div', { 'class': 'sh-st-progress-modern' }, [ + E('div', { + 'class': 'sh-st-progress-fill sh-st-gradient-firewall', + 'style': 'width: ' + fwPercent + '%' + }) + ]), + E('div', { 'class': 'sh-st-details-grid' }, [ + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Input'), + E('span', { 'class': 'sh-st-detail-value' }, + network.fw_input || 'DROP') + ]), + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Forward'), + E('span', { 'class': 'sh-st-detail-value' }, + network.fw_forward || 'REJECT') + ]), + E('div', { 'class': 'sh-st-detail' }, [ + E('span', { 'class': 'sh-st-detail-label' }, 'Output'), + E('span', { 'class': 'sh-st-detail-value' }, + network.fw_output || 'ACCEPT') ]) ]) ])