From 76955f48d0768642998e79244e95124c0902d0b0 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Mon, 29 Dec 2025 07:51:33 +0100 Subject: [PATCH] versheaders --- DOCS/DEVELOPMENT-GUIDELINES.md | 2 + luci-app-cdn-cache/Makefile | 2 +- .../resources/cdn-cache/common.css | 76 +++++++++++ .../resources/view/cdn-cache/overview.js | 21 +-- luci-app-network-modes/Makefile | 2 +- .../resources/network-modes/common.css | 76 +++++++++++ .../resources/view/network-modes/overview.js | 21 +-- luci-app-secubox/Makefile | 2 +- .../luci-static/resources/secubox/common.css | 76 +++++++++++ .../resources/view/secubox/alerts.js | 1 + .../resources/view/secubox/dashboard.js | 1 + .../resources/view/secubox/modules.js | 1 + .../resources/view/secubox/monitoring.js | 1 + .../resources/view/secubox/settings.js | 32 ++--- luci-app-system-hub/Makefile | 2 +- .../resources/system-hub/common.css | 76 +++++++++++ .../resources/view/system-hub/components.js | 23 +++- .../resources/view/system-hub/overview.js | 29 ++-- .../resources/view/system-hub/services.js | 41 ++++-- luci-app-vhost-manager/Makefile | 4 +- .../resources/vhost-manager/common.css | 76 +++++++++++ .../resources/view/vhost-manager/overview.js | 23 ++-- .../root/usr/libexec/rpcd/luci.vhost-manager | 2 +- templates/common-css-template.css | 127 +++++++++++++++--- 24 files changed, 619 insertions(+), 98 deletions(-) diff --git a/DOCS/DEVELOPMENT-GUIDELINES.md b/DOCS/DEVELOPMENT-GUIDELINES.md index 5544d294..c4ab4c50 100644 --- a/DOCS/DEVELOPMENT-GUIDELINES.md +++ b/DOCS/DEVELOPMENT-GUIDELINES.md @@ -169,6 +169,8 @@ graph TB **REQUIREMENT:** Every module view MUST begin with this compact `.sh-page-header`. Do not introduce bespoke hero sections or oversized banners; the header keeps height predictable (title + subtitle on the left, stats on the right) and guarantees consistency across SecuBox dashboards. If no stats are needed, keep the container but supply an empty `.sh-stats-grid` for future metrics. +**Slim variant:** When the page only needs 2‑3 metrics, use `.sh-page-header-lite` + `.sh-header-chip` (see `luci-app-vhost-manager` and `luci-app-secubox` settings). Chips carry an emoji/icon, a tiny label, and the value; colors (`.success`, `.danger`, `.warn`) communicate state. This variant replaces the bulky hero blocks from older demos. + **HTML Structure:** ```javascript E('div', { 'class': 'sh-page-header' }, [ diff --git a/luci-app-cdn-cache/Makefile b/luci-app-cdn-cache/Makefile index 2e365db4..1ed94e5d 100644 --- a/luci-app-cdn-cache/Makefile +++ b/luci-app-cdn-cache/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-cdn-cache PKG_VERSION:=0.4.1 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=CyberMind diff --git a/luci-app-cdn-cache/htdocs/luci-static/resources/cdn-cache/common.css b/luci-app-cdn-cache/htdocs/luci-static/resources/cdn-cache/common.css index b662c04f..efeb269f 100644 --- a/luci-app-cdn-cache/htdocs/luci-static/resources/cdn-cache/common.css +++ b/luci-app-cdn-cache/htdocs/luci-static/resources/cdn-cache/common.css @@ -431,3 +431,79 @@ pre { grid-template-columns: 1fr; } } + + +/* Slim header utility */ +.sh-page-header-lite { + background: #f7f9fc; + border: 1px solid rgba(148, 163, 184, 0.35); + border-radius: 18px; + padding: 14px 20px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + flex-wrap: wrap; + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05); +} + +.sh-header-meta { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: center; +} + +.sh-header-chip { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: 999px; + background: #ffffff; + border: 1px solid rgba(148, 163, 184, 0.3); + font-size: 13px; + font-weight: 600; + color: var(--sh-text-secondary); +} + +.sh-header-chip strong { + display: block; + color: var(--sh-text-primary); + font-size: 14px; +} + +.sh-chip-text { + line-height: 1.1; +} + +.sh-chip-label { + display: block; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--sh-text-muted, #94a3b8); +} + +.sh-chip-icon { + font-size: 16px; +} + +.sh-header-chip.success { + background: rgba(34, 197, 94, 0.12); + border-color: rgba(34, 197, 94, 0.4); + color: #15803d; +} + +.sh-header-chip.danger { + background: rgba(239, 68, 68, 0.12); + border-color: rgba(239, 68, 68, 0.45); + color: #b91c1c; +} + +.sh-header-chip.warn { + background: rgba(245, 158, 11, 0.12); + border-color: rgba(245, 158, 11, 0.45); + color: #b45309; +} + diff --git a/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/overview.js b/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/overview.js index b32125a2..402097c9 100644 --- a/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/overview.js +++ b/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/overview.js @@ -82,12 +82,12 @@ return view.extend({ renderHeader: function(status) { var stats = [ - { label: _('Service'), value: status.running ? _('Running') : _('Stopped') }, - { label: _('Uptime'), value: formatUptime(status.uptime || 0) }, - { label: _('Cache files'), value: (status.cache_files || 0).toLocaleString() } + { icon: 'đŸŸĸ', label: _('Service'), value: status.running ? _('Running') : _('Stopped'), tone: status.running ? 'success' : 'danger' }, + { icon: '⏱', label: _('Uptime'), value: formatUptime(status.uptime || 0) }, + { icon: '📁', label: _('Cache files'), value: (status.cache_files || 0).toLocaleString() } ]; - return E('div', { 'class': 'sh-page-header' }, [ + return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ E('div', {}, [ E('h2', { 'class': 'sh-page-title' }, [ E('span', { 'class': 'sh-page-title-icon' }, 'đŸ“Ļ'), @@ -96,14 +96,17 @@ return view.extend({ E('p', { 'class': 'sh-page-subtitle' }, _('Edge caching for media, firmware, and downloads')) ]), - E('div', { 'class': 'sh-stats-grid' }, stats.map(this.renderHeaderStat, this)) + E('div', { 'class': 'sh-header-meta' }, stats.map(this.renderHeaderChip, this)) ]); }, - renderHeaderStat: function(stat) { - return E('div', { 'class': 'sh-stat-badge' }, [ - E('div', { 'class': 'sh-stat-value' }, stat.value), - E('div', { 'class': 'sh-stat-label' }, stat.label) + renderHeaderChip: function(stat) { + return E('div', { 'class': 'sh-header-chip' + (stat.tone ? ' ' + stat.tone : '') }, [ + E('span', { 'class': 'sh-chip-icon' }, stat.icon || 'â€ĸ'), + E('div', { 'class': 'sh-chip-text' }, [ + E('span', { 'class': 'sh-chip-label' }, stat.label), + E('strong', {}, stat.value) + ]) ]); }, diff --git a/luci-app-network-modes/Makefile b/luci-app-network-modes/Makefile index 7bca3071..c02a8998 100644 --- a/luci-app-network-modes/Makefile +++ b/luci-app-network-modes/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-network-modes PKG_VERSION:=0.4.6 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=CyberMind diff --git a/luci-app-network-modes/htdocs/luci-static/resources/network-modes/common.css b/luci-app-network-modes/htdocs/luci-static/resources/network-modes/common.css index b662c04f..efeb269f 100644 --- a/luci-app-network-modes/htdocs/luci-static/resources/network-modes/common.css +++ b/luci-app-network-modes/htdocs/luci-static/resources/network-modes/common.css @@ -431,3 +431,79 @@ pre { grid-template-columns: 1fr; } } + + +/* Slim header utility */ +.sh-page-header-lite { + background: #f7f9fc; + border: 1px solid rgba(148, 163, 184, 0.35); + border-radius: 18px; + padding: 14px 20px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + flex-wrap: wrap; + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05); +} + +.sh-header-meta { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: center; +} + +.sh-header-chip { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: 999px; + background: #ffffff; + border: 1px solid rgba(148, 163, 184, 0.3); + font-size: 13px; + font-weight: 600; + color: var(--sh-text-secondary); +} + +.sh-header-chip strong { + display: block; + color: var(--sh-text-primary); + font-size: 14px; +} + +.sh-chip-text { + line-height: 1.1; +} + +.sh-chip-label { + display: block; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--sh-text-muted, #94a3b8); +} + +.sh-chip-icon { + font-size: 16px; +} + +.sh-header-chip.success { + background: rgba(34, 197, 94, 0.12); + border-color: rgba(34, 197, 94, 0.4); + color: #15803d; +} + +.sh-header-chip.danger { + background: rgba(239, 68, 68, 0.12); + border-color: rgba(239, 68, 68, 0.45); + color: #b91c1c; +} + +.sh-header-chip.warn { + background: rgba(245, 158, 11, 0.12); + border-color: rgba(245, 158, 11, 0.45); + color: #b45309; +} + diff --git a/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/overview.js b/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/overview.js index 67f1a629..8f87d69b 100644 --- a/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/overview.js +++ b/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/overview.js @@ -367,12 +367,12 @@ return view.extend({ renderHeader: function(status, currentModeInfo) { var modeName = currentModeInfo ? currentModeInfo.name : (status.current_mode || 'router'); var stats = [ - { label: _('Mode'), value: modeName }, - { label: _('WAN IP'), value: status.wan_ip || _('Unknown') }, - { label: _('LAN IP'), value: status.lan_ip || _('Unknown') } + { label: _('Mode'), value: modeName, icon: '🧭' }, + { label: _('WAN IP'), value: status.wan_ip || _('Unknown'), icon: '🌍' }, + { label: _('LAN IP'), value: status.lan_ip || _('Unknown'), icon: '🏠' } ]; - return E('div', { 'class': 'sh-page-header' }, [ + return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ E('div', {}, [ E('h2', { 'class': 'sh-page-title' }, [ E('span', { 'class': 'sh-page-title-icon' }, '🌐'), @@ -381,14 +381,17 @@ return view.extend({ E('p', { 'class': 'sh-page-subtitle' }, _('Switch between curated router, bridge, relay, and travel modes.')) ]), - E('div', { 'class': 'sh-stats-grid' }, stats.map(this.renderHeaderStat, this)) + E('div', { 'class': 'sh-header-meta' }, stats.map(this.renderHeaderChip, this)) ]); }, - renderHeaderStat: function(stat) { - return E('div', { 'class': 'sh-stat-badge' }, [ - E('div', { 'class': 'sh-stat-value' }, stat.value || '-'), - E('div', { 'class': 'sh-stat-label' }, stat.label) + renderHeaderChip: function(stat) { + return E('div', { 'class': 'sh-header-chip' }, [ + E('span', { 'class': 'sh-chip-icon' }, stat.icon || 'â€ĸ'), + E('div', { 'class': 'sh-chip-text' }, [ + E('span', { 'class': 'sh-chip-label' }, stat.label), + E('strong', {}, stat.value || '-') + ]) ]); }, diff --git a/luci-app-secubox/Makefile b/luci-app-secubox/Makefile index aea09b47..75680d44 100644 --- a/luci-app-secubox/Makefile +++ b/luci-app-secubox/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-secubox PKG_VERSION:=0.4.6 -PKG_RELEASE:=3 +PKG_RELEASE:=4 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=CyberMind diff --git a/luci-app-secubox/htdocs/luci-static/resources/secubox/common.css b/luci-app-secubox/htdocs/luci-static/resources/secubox/common.css index b662c04f..efeb269f 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/secubox/common.css +++ b/luci-app-secubox/htdocs/luci-static/resources/secubox/common.css @@ -431,3 +431,79 @@ pre { grid-template-columns: 1fr; } } + + +/* Slim header utility */ +.sh-page-header-lite { + background: #f7f9fc; + border: 1px solid rgba(148, 163, 184, 0.35); + border-radius: 18px; + padding: 14px 20px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + flex-wrap: wrap; + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05); +} + +.sh-header-meta { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: center; +} + +.sh-header-chip { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: 999px; + background: #ffffff; + border: 1px solid rgba(148, 163, 184, 0.3); + font-size: 13px; + font-weight: 600; + color: var(--sh-text-secondary); +} + +.sh-header-chip strong { + display: block; + color: var(--sh-text-primary); + font-size: 14px; +} + +.sh-chip-text { + line-height: 1.1; +} + +.sh-chip-label { + display: block; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--sh-text-muted, #94a3b8); +} + +.sh-chip-icon { + font-size: 16px; +} + +.sh-header-chip.success { + background: rgba(34, 197, 94, 0.12); + border-color: rgba(34, 197, 94, 0.4); + color: #15803d; +} + +.sh-header-chip.danger { + background: rgba(239, 68, 68, 0.12); + border-color: rgba(239, 68, 68, 0.45); + color: #b91c1c; +} + +.sh-header-chip.warn { + background: rgba(245, 158, 11, 0.12); + border-color: rgba(245, 158, 11, 0.45); + color: #b45309; +} + diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js index 7ce773ba..7d1b3fef 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js @@ -43,6 +43,7 @@ return view.extend({ render: function(data) { var self = this; var container = E('div', { 'class': 'secubox-alerts-page' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }), SecuNav.renderTabs('alerts'), this.renderHeader(), this.renderControls(), 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 008b1a17..21675f0a 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 @@ -48,6 +48,7 @@ return view.extend({ render: function() { var container = E('div', { 'class': 'secubox-dashboard' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/dashboard.css') }), SecuNav.renderTabs('dashboard'), this.renderHeader(), diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js index b7b83beb..5def7067 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js @@ -45,6 +45,7 @@ return view.extend({ var modules = this.modulesData; var container = E('div', { 'class': 'secubox-modules-page' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }), SecuNav.renderTabs('modules'), this.renderHeader(modules), this.renderFilterTabs(), diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js index e65b8466..9ca71f13 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js @@ -55,6 +55,7 @@ return view.extend({ render: function() { var container = E('div', { 'class': 'secubox-monitoring-page' }, [ E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/monitoring.css') }), SecuNav.renderTabs('monitoring'), diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js index e480014a..119b021a 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js @@ -28,8 +28,7 @@ return view.extend({ SecuNav.renderTabs('settings'), - // Modern header - E('div', { 'class': 'sh-page-header' }, [ + E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ E('div', {}, [ E('h2', { 'class': 'sh-page-title' }, [ E('span', { 'class': 'sh-page-title-icon' }, 'âš™ī¸'), @@ -38,24 +37,25 @@ return view.extend({ E('p', { 'class': 'sh-page-subtitle' }, 'Configure global settings for the SecuBox security suite') ]), - E('div', { 'class': 'sh-stats-grid' }, [ - E('div', { 'class': 'sh-stat-badge' }, [ - E('div', { 'class': 'sh-stat-value' }, status.version || 'v0.1.2'), - E('div', { 'class': 'sh-stat-label' }, 'Version') - ]), - E('div', { 'class': 'sh-stat-badge' }, [ - E('div', { 'class': 'sh-stat-value', 'style': status.enabled ? 'color: #22c55e;' : 'color: #ef4444;' }, - status.enabled ? 'ON' : 'OFF'), - E('div', { 'class': 'sh-stat-label' }, 'Status') - ]), - E('div', { 'class': 'sh-stat-badge' }, [ - E('div', { 'class': 'sh-stat-value' }, status.modules_count || '14'), - E('div', { 'class': 'sh-stat-label' }, 'Modules') - ]) + E('div', { 'class': 'sh-header-meta' }, [ + this.renderHeaderChip('đŸˇī¸', _('Version'), status.version || '0.1.2'), + this.renderHeaderChip('⚡', _('Status'), status.enabled ? _('On') : _('Off'), status.enabled ? 'success' : 'danger'), + this.renderHeaderChip('🧩', _('Modules'), status.modules_count || '14') ]) ]) ]); + renderHeaderChip: function(icon, label, value, tone) { + var display = (value == null ? '—' : value).toString(); + return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [ + E('span', { 'class': 'sh-chip-icon' }, icon), + E('div', { 'class': 'sh-chip-text' }, [ + E('span', { 'class': 'sh-chip-label' }, label), + E('strong', {}, display) + ]) + ]); + }, + // Create form m = new form.Map('secubox', null, null); diff --git a/luci-app-system-hub/Makefile b/luci-app-system-hub/Makefile index fa059995..4d8ab72c 100644 --- a/luci-app-system-hub/Makefile +++ b/luci-app-system-hub/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-system-hub PKG_VERSION:=0.4.6 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=CyberMind diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/common.css b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/common.css index a1eaef5d..66de37d6 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/common.css +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/common.css @@ -549,3 +549,79 @@ pre { background: var(--sh-bg-secondary); border-color: var(--sh-border); } + + +/* Slim header utility */ +.sh-page-header-lite { + background: #f7f9fc; + border: 1px solid rgba(148, 163, 184, 0.35); + border-radius: 18px; + padding: 14px 20px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + flex-wrap: wrap; + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05); +} + +.sh-header-meta { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: center; +} + +.sh-header-chip { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: 999px; + background: #ffffff; + border: 1px solid rgba(148, 163, 184, 0.3); + font-size: 13px; + font-weight: 600; + color: var(--sh-text-secondary); +} + +.sh-header-chip strong { + display: block; + color: var(--sh-text-primary); + font-size: 14px; +} + +.sh-chip-text { + line-height: 1.1; +} + +.sh-chip-label { + display: block; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--sh-text-muted, #94a3b8); +} + +.sh-chip-icon { + font-size: 16px; +} + +.sh-header-chip.success { + background: rgba(34, 197, 94, 0.12); + border-color: rgba(34, 197, 94, 0.4); + color: #15803d; +} + +.sh-header-chip.danger { + background: rgba(239, 68, 68, 0.12); + border-color: rgba(239, 68, 68, 0.45); + color: #b91c1c; +} + +.sh-header-chip.warn { + background: rgba(245, 158, 11, 0.12); + border-color: rgba(245, 158, 11, 0.45); + color: #b45309; +} + diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js index c78b796d..0c62a4b8 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js @@ -29,10 +29,13 @@ return view.extend({ HubNav.renderTabs('components'), - E('div', { 'class': 'sh-components-header' }, [ - E('h2', { 'class': 'sh-page-title' }, [ - E('span', { 'class': 'sh-title-icon' }, '🧩'), - ' System Components' + E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ + E('div', {}, [ + E('h2', { 'class': 'sh-page-title' }, [ + E('span', { 'class': 'sh-page-title-icon' }, '🧩'), + _('System Components') + ]), + E('p', { 'class': 'sh-page-subtitle' }, _('Installed modules grouped by category')) ]), this.renderFilterTabs() ]), @@ -99,9 +102,17 @@ return view.extend({ }, renderComponentsGrid: function(components, filter) { + var list = components.slice().sort(function(a, b) { + if ((a.installed ? 1 : 0) !== (b.installed ? 1 : 0)) + return a.installed ? -1 : 1; + if ((a.running ? 1 : 0) !== (b.running ? 1 : 0)) + return a.running ? -1 : 1; + return (a.name || '').localeCompare(b.name || ''); + }); + var filtered = filter === 'all' - ? components - : components.filter(function(c) { return c.category === filter; }); + ? list + : list.filter(function(c) { return c.category === filter; }); if (filtered.length === 0) { return E('div', { 'class': 'sh-empty-state' }, [ 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 88e5593d..9974e7ca 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 @@ -61,13 +61,13 @@ return view.extend({ var score = (this.healthData.score || 0); var stats = [ - { label: _('Uptime'), value: uptime }, - { label: _('Hostname'), value: hostname }, - { label: _('Kernel'), value: kernel, copy: kernel }, - { label: _('Health'), value: score + '/100' } + { label: _('Uptime'), value: uptime, icon: '⏱' }, + { label: _('Hostname'), value: hostname, icon: 'đŸ–Ĩ' }, + { label: _('Kernel'), value: kernel, copy: kernel, icon: 'đŸ§Ŧ' }, + { label: _('Health'), value: score + '/100', icon: 'â¤ī¸' } ]; - return E('div', { 'class': 'sh-page-header' }, [ + return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ E('div', {}, [ E('h2', { 'class': 'sh-page-title' }, [ E('span', { 'class': 'sh-page-title-icon' }, 'âš™ī¸'), @@ -75,26 +75,29 @@ return view.extend({ ]), E('p', { 'class': 'sh-page-subtitle' }, _('Unified telemetry & orchestration')) ]), - E('div', { 'class': 'sh-stats-grid' }, stats.map(this.renderHeaderStat, this)) + E('div', { 'class': 'sh-header-meta' }, stats.map(this.renderHeaderChip, this)) ]); }, - renderHeaderStat: function(stat) { - var badge = E('div', { 'class': 'sh-stat-badge' }, [ - E('div', { 'class': 'sh-stat-value' }, stat.value || '-'), - E('div', { 'class': 'sh-stat-label' }, stat.label) + renderHeaderChip: function(stat) { + var chip = E('div', { 'class': 'sh-header-chip' }, [ + E('span', { 'class': 'sh-chip-icon' }, stat.icon || 'â€ĸ'), + E('div', { 'class': 'sh-chip-text' }, [ + E('span', { 'class': 'sh-chip-label' }, stat.label), + E('strong', {}, stat.value || '-') + ]) ]); if (stat.copy && navigator.clipboard) { - badge.style.cursor = 'pointer'; - badge.addEventListener('click', function() { + chip.style.cursor = 'pointer'; + chip.addEventListener('click', function() { navigator.clipboard.writeText(stat.copy).then(function() { ui.addNotification(null, E('p', {}, _('Copied to clipboard')), 'info'); }); }); } - return badge; + return chip; }, renderInfoGrid: function() { diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/services.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/services.js index 8762aa3b..fe225aef 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/services.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/services.js @@ -58,16 +58,29 @@ return view.extend({ renderHeader: function() { var stats = this.getStats(); - return E('section', { 'class': 'sh-services-hero' }, [ + return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ E('div', {}, [ - E('h1', {}, _('Service Control Center')), - E('p', {}, _('Start, stop, enable, and inspect all init.d services')) + E('h2', { 'class': 'sh-page-title' }, [ + E('span', { 'class': 'sh-page-title-icon' }, '🧩'), + _('Service Control Center') + ]), + E('p', { 'class': 'sh-page-subtitle' }, _('Start, stop, enable, and inspect all init.d services')) ]), - E('div', { 'class': 'sh-services-stats', 'id': 'sh-services-stats' }, [ - this.createStatCard('sh-stat-total', _('Total'), stats.total), - this.createStatCard('sh-stat-running', _('Running'), stats.running, 'success'), - this.createStatCard('sh-stat-stopped', _('Stopped'), stats.stopped, 'danger'), - this.createStatCard('sh-stat-enabled', _('Enabled'), stats.enabled, 'info') + E('div', { 'class': 'sh-header-meta', 'id': 'sh-services-stats' }, [ + this.renderHeaderChip(_('Total'), stats.total, 'đŸ“Ļ'), + this.renderHeaderChip(_('Running'), stats.running, 'đŸŸĸ', stats.running > 0 ? 'success' : ''), + this.renderHeaderChip(_('Enabled'), stats.enabled, '✅'), + this.renderHeaderChip(_('Stopped'), stats.stopped, 'âšī¸', stats.stopped > 0 ? 'danger' : '') + ]) + ]); + }, + + renderHeaderChip: function(label, value, icon, tone) { + return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [ + E('span', { 'class': 'sh-chip-icon' }, icon), + E('div', { 'class': 'sh-chip-text' }, [ + E('span', { 'class': 'sh-chip-label' }, label), + E('strong', {}, value.toString()) ]) ]); }, @@ -136,7 +149,15 @@ return view.extend({ }, getFilteredServices: function() { - return this.services.filter(function(service) { + var ordered = this.services.slice().sort(function(a, b) { + if (a.running !== b.running) + return a.running ? -1 : 1; + if (a.enabled !== b.enabled) + return a.enabled ? -1 : 1; + return (a.name || '').localeCompare(b.name || ''); + }); + + return ordered.filter(function(service) { var matchesFilter = true; switch (this.activeFilter) { case 'running': matchesFilter = service.running; break; @@ -145,7 +166,7 @@ return view.extend({ case 'disabled': matchesFilter = !service.enabled; break; } var matchesSearch = !this.searchQuery || - service.name.toLowerCase().includes(this.searchQuery); + (service.name || '').toLowerCase().includes(this.searchQuery); return matchesFilter && matchesSearch; }, this); }, diff --git a/luci-app-vhost-manager/Makefile b/luci-app-vhost-manager/Makefile index 633835f0..6cf0112f 100644 --- a/luci-app-vhost-manager/Makefile +++ b/luci-app-vhost-manager/Makefile @@ -4,8 +4,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-vhost-manager -PKG_VERSION:=0.2.2 -PKG_RELEASE:=1 +PKG_VERSION:=0.4.1 +PKG_RELEASE:=2 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=CyberMind diff --git a/luci-app-vhost-manager/htdocs/luci-static/resources/vhost-manager/common.css b/luci-app-vhost-manager/htdocs/luci-static/resources/vhost-manager/common.css index b662c04f..efeb269f 100644 --- a/luci-app-vhost-manager/htdocs/luci-static/resources/vhost-manager/common.css +++ b/luci-app-vhost-manager/htdocs/luci-static/resources/vhost-manager/common.css @@ -431,3 +431,79 @@ pre { grid-template-columns: 1fr; } } + + +/* Slim header utility */ +.sh-page-header-lite { + background: #f7f9fc; + border: 1px solid rgba(148, 163, 184, 0.35); + border-radius: 18px; + padding: 14px 20px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + flex-wrap: wrap; + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05); +} + +.sh-header-meta { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: center; +} + +.sh-header-chip { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: 999px; + background: #ffffff; + border: 1px solid rgba(148, 163, 184, 0.3); + font-size: 13px; + font-weight: 600; + color: var(--sh-text-secondary); +} + +.sh-header-chip strong { + display: block; + color: var(--sh-text-primary); + font-size: 14px; +} + +.sh-chip-text { + line-height: 1.1; +} + +.sh-chip-label { + display: block; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--sh-text-muted, #94a3b8); +} + +.sh-chip-icon { + font-size: 16px; +} + +.sh-header-chip.success { + background: rgba(34, 197, 94, 0.12); + border-color: rgba(34, 197, 94, 0.4); + color: #15803d; +} + +.sh-header-chip.danger { + background: rgba(239, 68, 68, 0.12); + border-color: rgba(239, 68, 68, 0.45); + color: #b91c1c; +} + +.sh-header-chip.warn { + background: rgba(245, 158, 11, 0.12); + border-color: rgba(245, 158, 11, 0.45); + color: #b45309; +} + diff --git a/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/overview.js b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/overview.js index a9ce21c3..5da727f0 100644 --- a/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/overview.js +++ b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/overview.js @@ -69,7 +69,7 @@ return view.extend({ return days !== null && days <= 30; }).length; - return E('div', { 'class': 'sh-page-header' }, [ + return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ E('div', {}, [ E('h2', { 'class': 'sh-page-title' }, [ E('span', { 'class': 'sh-page-title-icon' }, '🌐'), @@ -78,18 +78,23 @@ return view.extend({ E('p', { 'class': 'sh-page-subtitle' }, _('Reverse proxy, SSL automation and hardened headers for SecuBox deployments.')) ]), - E('div', { 'class': 'sh-stats-grid' }, [ - this.renderStatBadge(status.vhost_count || vhosts.length, _('Virtual Hosts')), - this.renderStatBadge(sslEnabled, _('TLS Enabled')), - this.renderStatBadge(expiringSoon, _('Expiring Certs')) + E('div', { 'class': 'sh-header-meta' }, [ + this.renderHeaderChip('đŸˇī¸', _('Version'), status.version || '0.4.1'), + this.renderHeaderChip('📁', _('Virtual Hosts'), (status.vhost_count || vhosts.length)), + this.renderHeaderChip('🔒', _('TLS Enabled'), sslEnabled), + this.renderHeaderChip('âŗ', _('Expiring'), expiringSoon, expiringSoon > 0 ? 'warn' : '') ]) ]); }, - renderStatBadge: function(value, label) { - return E('div', { 'class': 'sh-stat-badge' }, [ - E('div', { 'class': 'sh-stat-value' }, value.toString()), - E('div', { 'class': 'sh-stat-label' }, label) + renderHeaderChip: function(icon, label, value, tone) { + var display = (value == null ? '—' : value).toString(); + return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [ + E('span', { 'class': 'sh-chip-icon' }, icon), + E('div', { 'class': 'sh-chip-text' }, [ + E('span', { 'class': 'sh-chip-label' }, label), + E('strong', {}, display) + ]) ]); }, diff --git a/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager b/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager index 97797540..2b55ee1a 100755 --- a/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager +++ b/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager @@ -187,7 +187,7 @@ case "$1" in json_init json_add_boolean "enabled" 1 json_add_string "module" "vhost-manager" - json_add_string "version" "1.0.0" + json_add_string "version" "0.4.1" # Check nginx status if pgrep -x nginx > /dev/null 2>&1; then diff --git a/templates/common-css-template.css b/templates/common-css-template.css index b662c04f..ba64e797 100644 --- a/templates/common-css-template.css +++ b/templates/common-css-template.css @@ -247,53 +247,66 @@ pre { /* === Navigation Tabs === */ .sh-nav-tabs { display: flex; - gap: 8px; + gap: 6px; margin-bottom: 24px; - padding: 8px; - background: var(--sh-bg-secondary); - border-radius: 12px; - border: 1px solid var(--sh-border); + padding: 12px; + background: #f7f9fc; + border-radius: 18px; + border: 1px solid rgba(148, 163, 184, 0.35); + box-shadow: 0 12px 35px rgba(15, 23, 42, 0.08); + align-items: center; + flex-wrap: wrap; position: sticky; top: 0; z-index: 100; - backdrop-filter: blur(10px); } .sh-nav-tab { - padding: 10px 20px; - border-radius: 8px; + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 18px; + border-radius: 12px; background: transparent; - border: none; + border: 1px solid transparent; color: var(--sh-text-secondary); - font-weight: 500; + font-weight: 600; cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transition: all 0.25s ease; position: relative; font-size: 14px; } .sh-nav-tab:hover { color: var(--sh-text-primary); - background: var(--sh-hover-bg); + background: rgba(99, 102, 241, 0.08); } .sh-nav-tab.active { color: var(--sh-primary); - background: var(--sh-bg-card); - box-shadow: 0 2px 4px var(--sh-shadow); + background: #ffffff; + border-color: rgba(99, 102, 241, 0.25); + box-shadow: 0 6px 14px rgba(99, 102, 241, 0.15); } .sh-nav-tab.active::after { content: ''; position: absolute; - bottom: 0; - left: 20%; - right: 20%; - height: 2px; + left: 18px; + right: 18px; + bottom: 6px; + height: 3px; background: linear-gradient(90deg, var(--sh-primary), var(--sh-primary-end)); - border-radius: 2px; + border-radius: 3px; } +.sh-tab-icon { + font-size: 16px; + width: 20px; + display: inline-flex; + align-items: center; + justify-content: center; +} /* === Filter Tabs === */ .sh-filter-tabs { display: flex; @@ -431,3 +444,79 @@ pre { grid-template-columns: 1fr; } } + + +/* Slim header utility */ +.sh-page-header-lite { + background: #f7f9fc; + border: 1px solid rgba(148, 163, 184, 0.35); + border-radius: 18px; + padding: 14px 20px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + flex-wrap: wrap; + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05); +} + +.sh-header-meta { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: center; +} + +.sh-header-chip { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: 999px; + background: #ffffff; + border: 1px solid rgba(148, 163, 184, 0.3); + font-size: 13px; + font-weight: 600; + color: var(--sh-text-secondary); +} + +.sh-header-chip strong { + display: block; + color: var(--sh-text-primary); + font-size: 14px; +} + +.sh-chip-text { + line-height: 1.1; +} + +.sh-chip-label { + display: block; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--sh-text-muted, #94a3b8); +} + +.sh-chip-icon { + font-size: 16px; +} + +.sh-header-chip.success { + background: rgba(34, 197, 94, 0.12); + border-color: rgba(34, 197, 94, 0.4); + color: #15803d; +} + +.sh-header-chip.danger { + background: rgba(239, 68, 68, 0.12); + border-color: rgba(239, 68, 68, 0.45); + color: #b91c1c; +} + +.sh-header-chip.warn { + background: rgba(245, 158, 11, 0.12); + border-color: rgba(245, 158, 11, 0.45); + color: #b45309; +} +