versheaders
This commit is contained in:
parent
bd96ab1d31
commit
76955f48d0
@ -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' }, [
|
||||
|
||||
@ -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 <contact@cybermind.fr>
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@ -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 <contact@cybermind.fr>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 || '-')
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@ -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 <contact@cybermind.fr>
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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 <contact@cybermind.fr>
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -29,10 +29,13 @@ return view.extend({
|
||||
|
||||
HubNav.renderTabs('components'),
|
||||
|
||||
E('div', { 'class': 'sh-components-header' }, [
|
||||
E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
|
||||
E('div', {}, [
|
||||
E('h2', { 'class': 'sh-page-title' }, [
|
||||
E('span', { 'class': 'sh-title-icon' }, '🧩'),
|
||||
' System Components'
|
||||
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' }, [
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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('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('p', { 'class': 'sh-page-subtitle' }, _('Start, stop, enable, and inspect all init.d services'))
|
||||
]),
|
||||
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);
|
||||
},
|
||||
|
||||
@ -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 <contact@cybermind.fr>
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user