diff --git a/luci-app-system-hub/Makefile b/luci-app-system-hub/Makefile index ad8f4c73..040afe2e 100644 --- a/luci-app-system-hub/Makefile +++ b/luci-app-system-hub/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-system-hub -PKG_VERSION:=0.3.1 +PKG_VERSION:=0.3.2 PKG_RELEASE:=1 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=CyberMind 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 3e152122..89fc429f 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 @@ -65,10 +65,10 @@ font-weight: 700; } -/* === Stats Overview Grid (Demo Style - Compact) === */ +/* === Stats Overview Grid (v0.3.2 - updated for 5 cards) === */ .sh-stats-overview-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; margin-bottom: 32px; } @@ -107,6 +107,9 @@ .sh-stat-cpu::before { background: linear-gradient(90deg, #6366f1, #8b5cf6); opacity: 1; } .sh-stat-memory::before { background: linear-gradient(90deg, #8b5cf6, #ec4899); opacity: 1; } .sh-stat-disk::before { background: linear-gradient(90deg, #ec4899, #f43f5e); opacity: 1; } +.sh-stat-network::before { background: linear-gradient(90deg, #06b6d4, #3b82f6); opacity: 1; } +.sh-stat-ok::before { background: #22c55e; opacity: 1; } +.sh-stat-error::before { background: #ef4444; opacity: 1; } .sh-stat-overview-icon { font-size: 36px; @@ -121,6 +124,10 @@ margin-bottom: 10px; color: var(--sh-text-primary); font-family: 'JetBrains Mono', monospace; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; } .sh-stat-overview-label { @@ -138,6 +145,26 @@ color: var(--sh-text-secondary); } +/* v0.3.2 - Enhanced dynamic stats */ +.sh-stat-status-icon { + font-size: 18px; + opacity: 0.9; + animation: pulse-subtle 2s ease-in-out infinite; +} + +.sh-stat-overview-detail { + font-size: 10px; + font-weight: 500; + color: var(--sh-text-muted); + margin-top: 6px; + font-family: 'JetBrains Mono', monospace; +} + +@keyframes pulse-subtle { + 0%, 100% { opacity: 0.8; } + 50% { opacity: 1; } +} + /* === Old Header (deprecated, keep for compatibility) === */ .sh-overview-header { display: flex; 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 4336121a..dfb3d9a2 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 @@ -79,7 +79,7 @@ return view.extend({ ]), E('div', { 'class': 'sh-dashboard-header-info' }, [ E('span', { 'class': 'sh-dashboard-badge sh-dashboard-badge-version' }, - 'v0.3.1'), + 'v0.3.2'), E('span', { 'class': 'sh-dashboard-badge' }, '⏱️ ' + (this.sysInfo.uptime_formatted || '0d 0h 0m')), E('span', { 'class': 'sh-dashboard-badge' }, @@ -94,26 +94,96 @@ return view.extend({ var scoreClass = score >= 80 ? 'excellent' : (score >= 60 ? 'good' : (score >= 40 ? 'warning' : 'critical')); var scoreLabel = score >= 80 ? 'Excellent' : (score >= 60 ? 'Good' : (score >= 40 ? 'Warning' : 'Critical')); + // Enhanced stats with status indicators (v0.3.2) + var cpu = this.healthData.cpu || {}; + var memory = this.healthData.memory || {}; + var disk = this.healthData.disk || {}; + var network = this.healthData.network || {}; + + // Process count (v0.3.2) + var processes = (cpu.processes_running || 0) + '/' + (cpu.processes_total || 0); + + // Network throughput (v0.3.2) - format bytes + var rxGB = ((network.rx_bytes || 0) / 1024 / 1024 / 1024).toFixed(2); + var txGB = ((network.tx_bytes || 0) / 1024 / 1024 / 1024).toFixed(2); + + // Status icons (v0.3.2) + var getStatusIcon = function(status) { + if (status === 'critical') return '⚠️'; + if (status === 'warning') return '⚡'; + return '✓'; + }; + return E('div', { 'class': 'sh-stats-overview-grid' }, [ + // Health Score Card E('div', { 'class': 'sh-stat-overview-card sh-stat-' + scoreClass }, [ E('div', { 'class': 'sh-stat-overview-value' }, score), E('div', { 'class': 'sh-stat-overview-label' }, 'Health Score'), E('div', { 'class': 'sh-stat-overview-status' }, scoreLabel) ]), - E('div', { 'class': 'sh-stat-overview-card sh-stat-cpu' }, [ + + // CPU Card with enhanced info + E('div', { + 'class': 'sh-stat-overview-card sh-stat-cpu sh-stat-' + (cpu.status || 'ok'), + 'title': 'Load: ' + (cpu.load_1m || '0') + ' | ' + (cpu.cores || 0) + ' cores | ' + processes + ' processes' + }, [ E('div', { 'class': 'sh-stat-overview-icon' }, '🔥'), - E('div', { 'class': 'sh-stat-overview-value' }, (this.healthData.cpu?.usage || 0) + '%'), - E('div', { 'class': 'sh-stat-overview-label' }, 'CPU Usage') + E('div', { 'class': 'sh-stat-overview-value' }, [ + E('span', {}, (cpu.usage || 0) + '%'), + E('span', { 'class': 'sh-stat-status-icon' }, getStatusIcon(cpu.status)) + ]), + E('div', { 'class': 'sh-stat-overview-label' }, 'CPU Usage'), + E('div', { 'class': 'sh-stat-overview-detail' }, + 'Load: ' + (cpu.load_1m || '0') + ' • ' + processes + ' proc') ]), - E('div', { 'class': 'sh-stat-overview-card sh-stat-memory' }, [ + + // Memory Card with swap info + E('div', { + 'class': 'sh-stat-overview-card sh-stat-memory sh-stat-' + (memory.status || 'ok'), + 'title': ((memory.used_kb || 0) / 1024).toFixed(0) + ' MB / ' + ((memory.total_kb || 0) / 1024).toFixed(0) + ' MB' + + (memory.swap_total_kb > 0 ? ' | Swap: ' + (memory.swap_usage || 0) + '%' : '') + }, [ E('div', { 'class': 'sh-stat-overview-icon' }, '💾'), - E('div', { 'class': 'sh-stat-overview-value' }, (this.healthData.memory?.usage || 0) + '%'), - E('div', { 'class': 'sh-stat-overview-label' }, 'Memory Usage') + E('div', { 'class': 'sh-stat-overview-value' }, [ + E('span', {}, (memory.usage || 0) + '%'), + E('span', { 'class': 'sh-stat-status-icon' }, getStatusIcon(memory.status)) + ]), + E('div', { 'class': 'sh-stat-overview-label' }, 'Memory'), + E('div', { 'class': 'sh-stat-overview-detail' }, + ((memory.used_kb || 0) / 1024).toFixed(0) + 'MB / ' + + ((memory.total_kb || 0) / 1024).toFixed(0) + 'MB' + + (memory.swap_total_kb > 0 ? ' • Swap: ' + (memory.swap_usage || 0) + '%' : '')) ]), - E('div', { 'class': 'sh-stat-overview-card sh-stat-disk' }, [ + + // Disk Card + E('div', { + 'class': 'sh-stat-overview-card sh-stat-disk sh-stat-' + (disk.status || 'ok'), + 'title': ((disk.used_kb || 0) / 1024 / 1024).toFixed(1) + ' GB / ' + ((disk.total_kb || 0) / 1024 / 1024).toFixed(1) + ' GB' + }, [ E('div', { 'class': 'sh-stat-overview-icon' }, '💿'), - E('div', { 'class': 'sh-stat-overview-value' }, (this.healthData.disk?.usage || 0) + '%'), - E('div', { 'class': 'sh-stat-overview-label' }, 'Disk Usage') + E('div', { 'class': 'sh-stat-overview-value' }, [ + E('span', {}, (disk.usage || 0) + '%'), + E('span', { 'class': 'sh-stat-status-icon' }, getStatusIcon(disk.status)) + ]), + E('div', { 'class': 'sh-stat-overview-label' }, 'Disk Usage'), + E('div', { 'class': 'sh-stat-overview-detail' }, + ((disk.used_kb || 0) / 1024 / 1024).toFixed(1) + 'GB / ' + + ((disk.total_kb || 0) / 1024 / 1024).toFixed(1) + 'GB') + ]), + + // Network Card (v0.3.2 - NEW) + E('div', { + 'class': 'sh-stat-overview-card sh-stat-network sh-stat-' + (network.wan_up ? 'ok' : 'error'), + 'title': 'RX: ' + rxGB + ' GB | TX: ' + txGB + ' GB' + }, [ + E('div', { 'class': 'sh-stat-overview-icon' }, '🌐'), + E('div', { 'class': 'sh-stat-overview-value' }, [ + E('span', {}, network.wan_up ? 'Online' : 'Offline'), + E('span', { 'class': 'sh-stat-status-icon' }, network.wan_up ? '✓' : '✗') + ]), + E('div', { 'class': 'sh-stat-overview-label' }, 'Network'), + E('div', { 'class': 'sh-stat-overview-detail' }, + '↓ ' + rxGB + 'GB • ↑ ' + txGB + 'GB') ]) ]); }, diff --git a/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub b/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub index 03dd0290..b44307c0 100755 --- a/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub +++ b/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub @@ -106,6 +106,10 @@ get_health() { local load5=$(echo $load | awk '{print $2}') local load15=$(echo $load | awk '{print $3}') + # Process count (v0.3.2) + local processes_running=$(echo $load | awk '{split($4,a,"/"); print a[1]}') + local processes_total=$(echo $load | awk '{split($4,a,"/"); print a[2]}') + # Calculate CPU usage percentage (load / cores * 100) local cpu_usage=$(awk -v load="$load1" -v cores="$cpu_cores" 'BEGIN { printf "%.0f", (load / cores) * 100 }') [ "$cpu_usage" -gt 100 ] && cpu_usage=100 @@ -122,9 +126,11 @@ get_health() { json_add_string "load_5m" "$load5" json_add_string "load_15m" "$load15" json_add_int "cores" "$cpu_cores" + json_add_int "processes_running" "${processes_running:-0}" + json_add_int "processes_total" "${processes_total:-0}" json_close_object - # Memory + # Memory (v0.3.2: added swap support) local mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_free=$(awk '/MemFree/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_available=$(awk '/MemAvailable/ {print $2}' /proc/meminfo 2>/dev/null || echo $mem_free) @@ -132,6 +138,15 @@ get_health() { local mem_cached=$(awk '/^Cached/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_used=$((mem_total - mem_available)) + # Swap info (v0.3.2) + local swap_total=$(awk '/SwapTotal/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) + local swap_free=$(awk '/SwapFree/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) + local swap_used=$((swap_total - swap_free)) + local swap_usage=0 + if [ "$swap_total" -gt 0 ]; then + swap_usage=$(( (swap_used * 100) / swap_total )) + fi + local mem_usage=0 if [ "$mem_total" -gt 0 ]; then mem_usage=$(( (mem_used * 100) / mem_total )) @@ -151,6 +166,9 @@ get_health() { json_add_int "cached_kb" "$mem_cached" json_add_int "usage" "$mem_usage" json_add_string "status" "$mem_status" + json_add_int "swap_total_kb" "$swap_total" + json_add_int "swap_used_kb" "$swap_used" + json_add_int "swap_usage" "$swap_usage" json_close_object # Disk (root filesystem) @@ -190,7 +208,7 @@ get_health() { json_add_string "status" "$temp_status" json_close_object - # Network (WAN connectivity) + # Network (WAN connectivity + throughput v0.3.2) local wan_up=0 local wan_status="error" if ping -c 1 -W 2 8.8.8.8 >/dev/null 2>&1; then @@ -198,9 +216,27 @@ get_health() { wan_status="ok" fi + # Network throughput (v0.3.2) - get total RX/TX from all interfaces + local rx_bytes=0 + local tx_bytes=0 + for iface in /sys/class/net/*; do + [ -d "$iface" ] || continue + local ifname=$(basename "$iface") + # Skip loopback and virtual interfaces + case "$ifname" in + lo|br-*|wlan*-*) continue ;; + esac + local rx=$(cat "$iface/statistics/rx_bytes" 2>/dev/null || echo 0) + local tx=$(cat "$iface/statistics/tx_bytes" 2>/dev/null || echo 0) + rx_bytes=$((rx_bytes + rx)) + tx_bytes=$((tx_bytes + tx)) + done + json_add_object "network" json_add_boolean "wan_up" "$wan_up" json_add_string "status" "$wan_status" + json_add_int "rx_bytes" "$rx_bytes" + json_add_int "tx_bytes" "$tx_bytes" json_close_object # Services