feat(system-hub): enhance dynamic overview stats for v0.3.2
- Add network throughput stats (RX/TX total bytes) - Add process count display (running/total) - Add swap usage information - Add dynamic status indicators (✓, ⚡, ⚠️) with pulse animation - Add detailed tooltips with absolute values - Add detail text under each stat card - Enhance stats grid layout for 5 cards - Update version from 0.3.1 to 0.3.2 Backend enhancements: - Extract process count from /proc/loadavg - Calculate swap usage from /proc/meminfo - Aggregate network throughput from all interfaces Frontend enhancements: - Display process count alongside CPU load - Show swap usage when available - Display total RX/TX in GB - Add pulsing status icons - Show contextual details (MB/GB values, process count, load average) CSS improvements: - Add .sh-stat-status-icon with subtle pulse animation - Add .sh-stat-overview-detail for contextual information - Add network gradient color scheme - Adjust grid for better 5-card layout
This commit is contained in:
parent
e7975ecb7a
commit
fadf606f31
@ -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 <contact@cybermind.fr>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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')
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user