versheaders

This commit is contained in:
CyberMind-FR 2025-12-29 07:51:33 +01:00
parent bd96ab1d31
commit 76955f48d0
24 changed files with 619 additions and 98 deletions

View File

@ -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. **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 23 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:** **HTML Structure:**
```javascript ```javascript
E('div', { 'class': 'sh-page-header' }, [ E('div', { 'class': 'sh-page-header' }, [

View File

@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-cdn-cache PKG_NAME:=luci-app-cdn-cache
PKG_VERSION:=0.4.1 PKG_VERSION:=0.4.1
PKG_RELEASE:=2 PKG_RELEASE:=3
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -431,3 +431,79 @@ pre {
grid-template-columns: 1fr; 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;
}

View File

@ -82,12 +82,12 @@ return view.extend({
renderHeader: function(status) { renderHeader: function(status) {
var stats = [ var stats = [
{ label: _('Service'), value: status.running ? _('Running') : _('Stopped') }, { icon: '🟢', label: _('Service'), value: status.running ? _('Running') : _('Stopped'), tone: status.running ? 'success' : 'danger' },
{ label: _('Uptime'), value: formatUptime(status.uptime || 0) }, { icon: '⏱', label: _('Uptime'), value: formatUptime(status.uptime || 0) },
{ label: _('Cache files'), value: (status.cache_files || 0).toLocaleString() } { 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('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [ E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '📦'), E('span', { 'class': 'sh-page-title-icon' }, '📦'),
@ -96,14 +96,17 @@ return view.extend({
E('p', { 'class': 'sh-page-subtitle' }, E('p', { 'class': 'sh-page-subtitle' },
_('Edge caching for media, firmware, and downloads')) _('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) { renderHeaderChip: function(stat) {
return E('div', { 'class': 'sh-stat-badge' }, [ return E('div', { 'class': 'sh-header-chip' + (stat.tone ? ' ' + stat.tone : '') }, [
E('div', { 'class': 'sh-stat-value' }, stat.value), E('span', { 'class': 'sh-chip-icon' }, stat.icon || '•'),
E('div', { 'class': 'sh-stat-label' }, stat.label) E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, stat.label),
E('strong', {}, stat.value)
])
]); ]);
}, },

View File

@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-network-modes PKG_NAME:=luci-app-network-modes
PKG_VERSION:=0.4.6 PKG_VERSION:=0.4.6
PKG_RELEASE:=2 PKG_RELEASE:=3
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -431,3 +431,79 @@ pre {
grid-template-columns: 1fr; 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;
}

View File

@ -367,12 +367,12 @@ return view.extend({
renderHeader: function(status, currentModeInfo) { renderHeader: function(status, currentModeInfo) {
var modeName = currentModeInfo ? currentModeInfo.name : (status.current_mode || 'router'); var modeName = currentModeInfo ? currentModeInfo.name : (status.current_mode || 'router');
var stats = [ var stats = [
{ label: _('Mode'), value: modeName }, { label: _('Mode'), value: modeName, icon: '🧭' },
{ label: _('WAN IP'), value: status.wan_ip || _('Unknown') }, { label: _('WAN IP'), value: status.wan_ip || _('Unknown'), icon: '🌍' },
{ label: _('LAN IP'), value: status.lan_ip || _('Unknown') } { 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('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [ E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '🌐'), E('span', { 'class': 'sh-page-title-icon' }, '🌐'),
@ -381,14 +381,17 @@ return view.extend({
E('p', { 'class': 'sh-page-subtitle' }, E('p', { 'class': 'sh-page-subtitle' },
_('Switch between curated router, bridge, relay, and travel modes.')) _('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) { renderHeaderChip: function(stat) {
return E('div', { 'class': 'sh-stat-badge' }, [ return E('div', { 'class': 'sh-header-chip' }, [
E('div', { 'class': 'sh-stat-value' }, stat.value || '-'), E('span', { 'class': 'sh-chip-icon' }, stat.icon || '•'),
E('div', { 'class': 'sh-stat-label' }, stat.label) E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, stat.label),
E('strong', {}, stat.value || '-')
])
]); ]);
}, },

View File

@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-secubox PKG_NAME:=luci-app-secubox
PKG_VERSION:=0.4.6 PKG_VERSION:=0.4.6
PKG_RELEASE:=3 PKG_RELEASE:=4
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -431,3 +431,79 @@ pre {
grid-template-columns: 1fr; 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;
}

View File

@ -43,6 +43,7 @@ return view.extend({
render: function(data) { render: function(data) {
var self = this; var self = this;
var container = E('div', { 'class': 'secubox-alerts-page' }, [ var container = E('div', { 'class': 'secubox-alerts-page' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
SecuNav.renderTabs('alerts'), SecuNav.renderTabs('alerts'),
this.renderHeader(), this.renderHeader(),
this.renderControls(), this.renderControls(),

View File

@ -48,6 +48,7 @@ return view.extend({
render: function() { render: function() {
var container = E('div', { 'class': 'secubox-dashboard' }, [ 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') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/dashboard.css') }),
SecuNav.renderTabs('dashboard'), SecuNav.renderTabs('dashboard'),
this.renderHeader(), this.renderHeader(),

View File

@ -45,6 +45,7 @@ return view.extend({
var modules = this.modulesData; var modules = this.modulesData;
var container = E('div', { 'class': 'secubox-modules-page' }, [ var container = E('div', { 'class': 'secubox-modules-page' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
SecuNav.renderTabs('modules'), SecuNav.renderTabs('modules'),
this.renderHeader(modules), this.renderHeader(modules),
this.renderFilterTabs(), this.renderFilterTabs(),

View File

@ -55,6 +55,7 @@ return view.extend({
render: function() { render: function() {
var container = E('div', { 'class': 'secubox-monitoring-page' }, [ 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-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/secubox.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/monitoring.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/monitoring.css') }),
SecuNav.renderTabs('monitoring'), SecuNav.renderTabs('monitoring'),

View File

@ -28,8 +28,7 @@ return view.extend({
SecuNav.renderTabs('settings'), SecuNav.renderTabs('settings'),
// Modern header E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', { 'class': 'sh-page-header' }, [
E('div', {}, [ E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [ E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '⚙️'), E('span', { 'class': 'sh-page-title-icon' }, '⚙️'),
@ -38,24 +37,25 @@ return view.extend({
E('p', { 'class': 'sh-page-subtitle' }, E('p', { 'class': 'sh-page-subtitle' },
'Configure global settings for the SecuBox security suite') 'Configure global settings for the SecuBox security suite')
]), ]),
E('div', { 'class': 'sh-stats-grid' }, [ E('div', { 'class': 'sh-header-meta' }, [
E('div', { 'class': 'sh-stat-badge' }, [ this.renderHeaderChip('🏷️', _('Version'), status.version || '0.1.2'),
E('div', { 'class': 'sh-stat-value' }, status.version || 'v0.1.2'), this.renderHeaderChip('⚡', _('Status'), status.enabled ? _('On') : _('Off'), status.enabled ? 'success' : 'danger'),
E('div', { 'class': 'sh-stat-label' }, 'Version') this.renderHeaderChip('🧩', _('Modules'), status.modules_count || '14')
]),
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')
])
]) ])
]) ])
]); ]);
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 // Create form
m = new form.Map('secubox', null, null); m = new form.Map('secubox', null, null);

View File

@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-system-hub PKG_NAME:=luci-app-system-hub
PKG_VERSION:=0.4.6 PKG_VERSION:=0.4.6
PKG_RELEASE:=2 PKG_RELEASE:=3
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -549,3 +549,79 @@ pre {
background: var(--sh-bg-secondary); background: var(--sh-bg-secondary);
border-color: var(--sh-border); 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;
}

View File

@ -29,10 +29,13 @@ return view.extend({
HubNav.renderTabs('components'), HubNav.renderTabs('components'),
E('div', { 'class': 'sh-components-header' }, [ E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('h2', { 'class': 'sh-page-title' }, [ E('div', {}, [
E('span', { 'class': 'sh-title-icon' }, '🧩'), E('h2', { 'class': 'sh-page-title' }, [
' System Components' E('span', { 'class': 'sh-page-title-icon' }, '🧩'),
_('System Components')
]),
E('p', { 'class': 'sh-page-subtitle' }, _('Installed modules grouped by category'))
]), ]),
this.renderFilterTabs() this.renderFilterTabs()
]), ]),
@ -99,9 +102,17 @@ return view.extend({
}, },
renderComponentsGrid: function(components, filter) { 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' var filtered = filter === 'all'
? components ? list
: components.filter(function(c) { return c.category === filter; }); : list.filter(function(c) { return c.category === filter; });
if (filtered.length === 0) { if (filtered.length === 0) {
return E('div', { 'class': 'sh-empty-state' }, [ return E('div', { 'class': 'sh-empty-state' }, [

View File

@ -61,13 +61,13 @@ return view.extend({
var score = (this.healthData.score || 0); var score = (this.healthData.score || 0);
var stats = [ var stats = [
{ label: _('Uptime'), value: uptime }, { label: _('Uptime'), value: uptime, icon: '⏱' },
{ label: _('Hostname'), value: hostname }, { label: _('Hostname'), value: hostname, icon: '🖥' },
{ label: _('Kernel'), value: kernel, copy: kernel }, { label: _('Kernel'), value: kernel, copy: kernel, icon: '🧬' },
{ label: _('Health'), value: score + '/100' } { 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('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [ E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '⚙️'), E('span', { 'class': 'sh-page-title-icon' }, '⚙️'),
@ -75,26 +75,29 @@ return view.extend({
]), ]),
E('p', { 'class': 'sh-page-subtitle' }, _('Unified telemetry & orchestration')) 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) { renderHeaderChip: function(stat) {
var badge = E('div', { 'class': 'sh-stat-badge' }, [ var chip = E('div', { 'class': 'sh-header-chip' }, [
E('div', { 'class': 'sh-stat-value' }, stat.value || '-'), E('span', { 'class': 'sh-chip-icon' }, stat.icon || '•'),
E('div', { 'class': 'sh-stat-label' }, stat.label) E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, stat.label),
E('strong', {}, stat.value || '-')
])
]); ]);
if (stat.copy && navigator.clipboard) { if (stat.copy && navigator.clipboard) {
badge.style.cursor = 'pointer'; chip.style.cursor = 'pointer';
badge.addEventListener('click', function() { chip.addEventListener('click', function() {
navigator.clipboard.writeText(stat.copy).then(function() { navigator.clipboard.writeText(stat.copy).then(function() {
ui.addNotification(null, E('p', {}, _('Copied to clipboard')), 'info'); ui.addNotification(null, E('p', {}, _('Copied to clipboard')), 'info');
}); });
}); });
} }
return badge; return chip;
}, },
renderInfoGrid: function() { renderInfoGrid: function() {

View File

@ -58,16 +58,29 @@ return view.extend({
renderHeader: function() { renderHeader: function() {
var stats = this.getStats(); 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('div', {}, [
E('h1', {}, _('Service Control Center')), E('h2', { 'class': 'sh-page-title' }, [
E('p', {}, _('Start, stop, enable, and inspect all init.d services')) 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' }, [ E('div', { 'class': 'sh-header-meta', 'id': 'sh-services-stats' }, [
this.createStatCard('sh-stat-total', _('Total'), stats.total), this.renderHeaderChip(_('Total'), stats.total, '📦'),
this.createStatCard('sh-stat-running', _('Running'), stats.running, 'success'), this.renderHeaderChip(_('Running'), stats.running, '🟢', stats.running > 0 ? 'success' : ''),
this.createStatCard('sh-stat-stopped', _('Stopped'), stats.stopped, 'danger'), this.renderHeaderChip(_('Enabled'), stats.enabled, '✅'),
this.createStatCard('sh-stat-enabled', _('Enabled'), stats.enabled, 'info') 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() { 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; var matchesFilter = true;
switch (this.activeFilter) { switch (this.activeFilter) {
case 'running': matchesFilter = service.running; break; case 'running': matchesFilter = service.running; break;
@ -145,7 +166,7 @@ return view.extend({
case 'disabled': matchesFilter = !service.enabled; break; case 'disabled': matchesFilter = !service.enabled; break;
} }
var matchesSearch = !this.searchQuery || var matchesSearch = !this.searchQuery ||
service.name.toLowerCase().includes(this.searchQuery); (service.name || '').toLowerCase().includes(this.searchQuery);
return matchesFilter && matchesSearch; return matchesFilter && matchesSearch;
}, this); }, this);
}, },

View File

@ -4,8 +4,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-vhost-manager PKG_NAME:=luci-app-vhost-manager
PKG_VERSION:=0.2.2 PKG_VERSION:=0.4.1
PKG_RELEASE:=1 PKG_RELEASE:=2
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -431,3 +431,79 @@ pre {
grid-template-columns: 1fr; 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;
}

View File

@ -69,7 +69,7 @@ return view.extend({
return days !== null && days <= 30; return days !== null && days <= 30;
}).length; }).length;
return E('div', { 'class': 'sh-page-header' }, [ return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [ E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [ E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '🌐'), E('span', { 'class': 'sh-page-title-icon' }, '🌐'),
@ -78,18 +78,23 @@ return view.extend({
E('p', { 'class': 'sh-page-subtitle' }, E('p', { 'class': 'sh-page-subtitle' },
_('Reverse proxy, SSL automation and hardened headers for SecuBox deployments.')) _('Reverse proxy, SSL automation and hardened headers for SecuBox deployments.'))
]), ]),
E('div', { 'class': 'sh-stats-grid' }, [ E('div', { 'class': 'sh-header-meta' }, [
this.renderStatBadge(status.vhost_count || vhosts.length, _('Virtual Hosts')), this.renderHeaderChip('🏷️', _('Version'), status.version || '0.4.1'),
this.renderStatBadge(sslEnabled, _('TLS Enabled')), this.renderHeaderChip('📁', _('Virtual Hosts'), (status.vhost_count || vhosts.length)),
this.renderStatBadge(expiringSoon, _('Expiring Certs')) this.renderHeaderChip('🔒', _('TLS Enabled'), sslEnabled),
this.renderHeaderChip('⏳', _('Expiring'), expiringSoon, expiringSoon > 0 ? 'warn' : '')
]) ])
]); ]);
}, },
renderStatBadge: function(value, label) { renderHeaderChip: function(icon, label, value, tone) {
return E('div', { 'class': 'sh-stat-badge' }, [ var display = (value == null ? '—' : value).toString();
E('div', { 'class': 'sh-stat-value' }, value.toString()), return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [
E('div', { 'class': 'sh-stat-label' }, label) E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, display)
])
]); ]);
}, },

View File

@ -187,7 +187,7 @@ case "$1" in
json_init json_init
json_add_boolean "enabled" 1 json_add_boolean "enabled" 1
json_add_string "module" "vhost-manager" json_add_string "module" "vhost-manager"
json_add_string "version" "1.0.0" json_add_string "version" "0.4.1"
# Check nginx status # Check nginx status
if pgrep -x nginx > /dev/null 2>&1; then if pgrep -x nginx > /dev/null 2>&1; then

View File

@ -247,53 +247,66 @@ pre {
/* === Navigation Tabs === */ /* === Navigation Tabs === */
.sh-nav-tabs { .sh-nav-tabs {
display: flex; display: flex;
gap: 8px; gap: 6px;
margin-bottom: 24px; margin-bottom: 24px;
padding: 8px; padding: 12px;
background: var(--sh-bg-secondary); background: #f7f9fc;
border-radius: 12px; border-radius: 18px;
border: 1px solid var(--sh-border); 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; position: sticky;
top: 0; top: 0;
z-index: 100; z-index: 100;
backdrop-filter: blur(10px);
} }
.sh-nav-tab { .sh-nav-tab {
padding: 10px 20px; display: inline-flex;
border-radius: 8px; align-items: center;
gap: 8px;
padding: 10px 18px;
border-radius: 12px;
background: transparent; background: transparent;
border: none; border: 1px solid transparent;
color: var(--sh-text-secondary); color: var(--sh-text-secondary);
font-weight: 500; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.25s ease;
position: relative; position: relative;
font-size: 14px; font-size: 14px;
} }
.sh-nav-tab:hover { .sh-nav-tab:hover {
color: var(--sh-text-primary); color: var(--sh-text-primary);
background: var(--sh-hover-bg); background: rgba(99, 102, 241, 0.08);
} }
.sh-nav-tab.active { .sh-nav-tab.active {
color: var(--sh-primary); color: var(--sh-primary);
background: var(--sh-bg-card); background: #ffffff;
box-shadow: 0 2px 4px var(--sh-shadow); border-color: rgba(99, 102, 241, 0.25);
box-shadow: 0 6px 14px rgba(99, 102, 241, 0.15);
} }
.sh-nav-tab.active::after { .sh-nav-tab.active::after {
content: ''; content: '';
position: absolute; position: absolute;
bottom: 0; left: 18px;
left: 20%; right: 18px;
right: 20%; bottom: 6px;
height: 2px; height: 3px;
background: linear-gradient(90deg, var(--sh-primary), var(--sh-primary-end)); 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 === */ /* === Filter Tabs === */
.sh-filter-tabs { .sh-filter-tabs {
display: flex; display: flex;
@ -431,3 +444,79 @@ pre {
grid-template-columns: 1fr; 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;
}