feat: Add SecuBox Portal - Unified WebUI entry point (Phase 4)

New package: luci-app-secubox-portal v1.0.0

Creates unified entry point for all SecuBox applications with:

Portal Features:
- Top navigation bar with SecuBox branding
- Section-based navigation: Dashboard, Security, Network, Monitoring, System
- "Return to Standard LuCI" link for quick access to main LuCI interface
- Real-time service status detection for all apps

Dashboard Section:
- System overview with hostname, model, uptime, memory usage
- Quick stats showing running services count
- Featured apps grid with quick access cards
- Service status indicators (running/stopped)

App Registry:
- Security: CrowdSec, Client Guardian, Auth Guardian
- Network: Bandwidth Manager, Traffic Shaper, WireGuard, Network Modes
- Monitoring: Media Flow, nDPId, Netifyd, Netdata
- System: System Hub, CDN Cache, SecuBox Settings

Styling:
- Full dark theme with cyber aesthetic
- App cards with icon backgrounds and status dots
- Responsive design for mobile devices
- Smooth section transitions with animations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-09 14:02:23 +01:00
parent e7c53cec1d
commit a9e5bc0262
5 changed files with 1282 additions and 0 deletions

View File

@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# luci-app-secubox-portal - Unified SecuBox WebUI Portal
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=SecuBox Portal - Unified WebUI
LUCI_DESCRIPTION:=Unified entry point for all SecuBox applications with tabbed navigation
LUCI_DEPENDS:=+luci-base +luci-theme-secubox
LUCI_PKGARCH:=all
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_LICENSE:=GPL-3.0-or-later
PKG_MAINTAINER:=SecuBox Team <secubox@example.com>
include ../../luci.mk
# call BuildPackage - OpenWrt buildance!
$(eval $(call BuildPackage,luci-app-secubox-portal))

View File

@ -0,0 +1,573 @@
/**
* SecuBox Portal - Unified WebUI Styles
* File: portal.css
* Version: 1.0.0
*/
/* Portal Container */
.secubox-portal {
font-family: var(--sb-font-family, 'Inter', -apple-system, BlinkMacSystemFont, sans-serif);
min-height: 100vh;
background: var(--cyber-bg-primary, #0a0a0f);
color: var(--cyber-text-primary, #e4e4e7);
}
/* Top Header Bar */
.sb-portal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1.5rem;
height: 60px;
background: var(--cyber-bg-secondary, #141419);
border-bottom: 1px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.08));
position: sticky;
top: 0;
z-index: 100;
}
.sb-portal-brand {
display: flex;
align-items: center;
gap: 0.75rem;
}
.sb-portal-logo {
width: 36px;
height: 36px;
background: var(--sb-accent-gradient, linear-gradient(135deg, #667eea 0%, #764ba2 100%));
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1.25rem;
color: white;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.sb-portal-title {
font-size: 1.25rem;
font-weight: 600;
background: var(--sb-accent-gradient, linear-gradient(135deg, #667eea 0%, #764ba2 100%));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.sb-portal-version {
font-size: 0.75rem;
color: var(--cyber-text-tertiary, #71717a);
margin-left: 0.5rem;
}
/* Main Navigation */
.sb-portal-nav {
display: flex;
gap: 0.25rem;
padding: 0 1rem;
}
.sb-portal-nav-item {
padding: 0.625rem 1rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--cyber-text-secondary, #a1a1aa);
background: transparent;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
}
.sb-portal-nav-item:hover {
color: var(--cyber-text-primary, #e4e4e7);
background: var(--cyber-bg-tertiary, rgba(255, 255, 255, 0.05));
}
.sb-portal-nav-item.active {
color: white;
background: var(--sb-accent-gradient, linear-gradient(135deg, #667eea 0%, #764ba2 100%));
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.sb-portal-nav-icon {
width: 18px;
height: 18px;
opacity: 0.8;
}
/* Header Actions */
.sb-portal-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.sb-luci-return {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.8125rem;
font-weight: 500;
color: var(--cyber-text-secondary, #a1a1aa);
background: var(--cyber-bg-tertiary, rgba(255, 255, 255, 0.05));
border: 1px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.08));
border-radius: 6px;
text-decoration: none;
transition: all 0.2s ease;
}
.sb-luci-return:hover {
color: var(--cyber-text-primary, #e4e4e7);
background: var(--cyber-bg-elevated, rgba(255, 255, 255, 0.08));
border-color: var(--cyber-border-default, rgba(255, 255, 255, 0.15));
}
/* Main Content Area */
.sb-portal-content {
padding: 1.5rem;
max-width: 1600px;
margin: 0 auto;
}
/* Section Container */
.sb-portal-section {
display: none;
animation: fadeIn 0.3s ease;
}
.sb-portal-section.active {
display: block;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Section Header */
.sb-section-header {
margin-bottom: 1.5rem;
}
.sb-section-title {
font-size: 1.5rem;
font-weight: 600;
color: var(--cyber-text-primary, #e4e4e7);
margin: 0 0 0.5rem 0;
}
.sb-section-subtitle {
font-size: 0.875rem;
color: var(--cyber-text-secondary, #a1a1aa);
margin: 0;
}
/* Sub-tabs for sections */
.sb-section-tabs {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
background: var(--cyber-bg-secondary, #141419);
border-radius: 10px;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.sb-section-tab {
padding: 0.625rem 1rem;
font-size: 0.8125rem;
font-weight: 500;
color: var(--cyber-text-secondary, #a1a1aa);
background: transparent;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
.sb-section-tab:hover {
color: var(--cyber-text-primary, #e4e4e7);
background: var(--cyber-bg-tertiary, rgba(255, 255, 255, 0.05));
}
.sb-section-tab.active {
color: white;
background: var(--cyber-bg-elevated, rgba(255, 255, 255, 0.1));
}
.sb-section-tab-badge {
padding: 0.125rem 0.375rem;
font-size: 0.625rem;
font-weight: 600;
background: var(--sb-accent-primary, #667eea);
color: white;
border-radius: 4px;
}
/* Dashboard Grid */
.sb-dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.25rem;
margin-bottom: 1.5rem;
}
/* Quick Stats */
.sb-quick-stat {
background: var(--cyber-bg-secondary, #141419);
border: 1px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.08));
border-radius: 12px;
padding: 1.25rem;
transition: all 0.2s ease;
}
.sb-quick-stat:hover {
border-color: var(--cyber-border-default, rgba(255, 255, 255, 0.15));
transform: translateY(-2px);
}
.sb-quick-stat-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.sb-quick-stat-icon {
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
}
.sb-quick-stat-icon.security {
background: rgba(0, 212, 170, 0.15);
color: #00d4aa;
}
.sb-quick-stat-icon.network {
background: rgba(6, 182, 212, 0.15);
color: #06b6d4;
}
.sb-quick-stat-icon.monitoring {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
}
.sb-quick-stat-icon.system {
background: rgba(249, 115, 22, 0.15);
color: #f97316;
}
.sb-quick-stat-status {
padding: 0.25rem 0.5rem;
font-size: 0.6875rem;
font-weight: 600;
border-radius: 4px;
text-transform: uppercase;
}
.sb-quick-stat-status.running {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
}
.sb-quick-stat-status.stopped {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
.sb-quick-stat-status.warning {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
}
.sb-quick-stat-value {
font-size: 1.75rem;
font-weight: 700;
color: var(--cyber-text-primary, #e4e4e7);
line-height: 1.2;
}
.sb-quick-stat-label {
font-size: 0.8125rem;
color: var(--cyber-text-secondary, #a1a1aa);
margin-top: 0.25rem;
}
/* App Cards */
.sb-app-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.sb-app-card {
background: var(--cyber-bg-secondary, #141419);
border: 1px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.08));
border-radius: 12px;
padding: 1.25rem;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
display: block;
}
.sb-app-card:hover {
border-color: var(--sb-accent-primary, #667eea);
background: var(--cyber-bg-elevated, rgba(255, 255, 255, 0.03));
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.sb-app-card-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.sb-app-card-icon {
width: 44px;
height: 44px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.375rem;
}
.sb-app-card-title {
font-size: 1rem;
font-weight: 600;
color: var(--cyber-text-primary, #e4e4e7);
margin: 0;
}
.sb-app-card-version {
font-size: 0.75rem;
color: var(--cyber-text-tertiary, #71717a);
}
.sb-app-card-desc {
font-size: 0.8125rem;
color: var(--cyber-text-secondary, #a1a1aa);
line-height: 1.5;
margin: 0;
}
.sb-app-card-status {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.08));
}
.sb-app-card-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.sb-app-card-status-dot.running {
background: #22c55e;
box-shadow: 0 0 8px rgba(34, 197, 94, 0.5);
}
.sb-app-card-status-dot.stopped {
background: #71717a;
}
.sb-app-card-status-text {
font-size: 0.75rem;
color: var(--cyber-text-tertiary, #71717a);
}
/* Recent Events */
.sb-events-list {
background: var(--cyber-bg-secondary, #141419);
border: 1px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.08));
border-radius: 12px;
overflow: hidden;
}
.sb-events-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.08));
}
.sb-events-title {
font-size: 1rem;
font-weight: 600;
color: var(--cyber-text-primary, #e4e4e7);
margin: 0;
}
.sb-events-item {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.875rem 1.25rem;
border-bottom: 1px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.05));
transition: background 0.2s ease;
}
.sb-events-item:last-child {
border-bottom: none;
}
.sb-events-item:hover {
background: var(--cyber-bg-tertiary, rgba(255, 255, 255, 0.03));
}
.sb-events-icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
flex-shrink: 0;
}
.sb-events-icon.alert {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
.sb-events-icon.info {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}
.sb-events-icon.success {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
}
.sb-events-icon.warning {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
}
.sb-events-content {
flex: 1;
min-width: 0;
}
.sb-events-message {
font-size: 0.8125rem;
color: var(--cyber-text-primary, #e4e4e7);
margin: 0 0 0.25rem 0;
line-height: 1.4;
}
.sb-events-meta {
font-size: 0.75rem;
color: var(--cyber-text-tertiary, #71717a);
}
/* Embedded View Container */
.sb-embedded-container {
background: var(--cyber-bg-secondary, #141419);
border: 1px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.08));
border-radius: 12px;
overflow: hidden;
min-height: 500px;
}
.sb-embedded-view {
width: 100%;
height: calc(100vh - 200px);
min-height: 500px;
border: none;
background: transparent;
}
/* Loading State */
.sb-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
color: var(--cyber-text-secondary, #a1a1aa);
}
.sb-loading-spinner {
width: 40px;
height: 40px;
border: 3px solid var(--cyber-border-subtle, rgba(255, 255, 255, 0.08));
border-top-color: var(--sb-accent-primary, #667eea);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Responsive */
@media (max-width: 768px) {
.sb-portal-header {
flex-wrap: wrap;
height: auto;
padding: 0.75rem 1rem;
gap: 0.75rem;
}
.sb-portal-nav {
order: 3;
width: 100%;
overflow-x: auto;
padding: 0;
gap: 0.125rem;
}
.sb-portal-nav-item {
padding: 0.5rem 0.75rem;
font-size: 0.8125rem;
white-space: nowrap;
}
.sb-portal-content {
padding: 1rem;
}
.sb-dashboard-grid {
grid-template-columns: 1fr;
}
.sb-app-grid {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,300 @@
'use strict';
'require baseclass';
/**
* SecuBox Portal Module
* Provides unified navigation and app management
*/
return baseclass.extend({
version: '1.0.0',
// SecuBox app registry
apps: {
// Security Apps
'crowdsec': {
id: 'crowdsec',
name: 'CrowdSec Dashboard',
desc: 'Community-driven security with real-time threat detection and crowd-sourced intelligence',
icon: '\ud83d\udee1\ufe0f',
iconBg: 'rgba(0, 212, 170, 0.15)',
iconColor: '#00d4aa',
section: 'security',
path: 'admin/services/crowdsec-dashboard',
service: 'crowdsec',
version: '0.7.0'
},
'client-guardian': {
id: 'client-guardian',
name: 'Client Guardian',
desc: 'Monitor and manage network clients with access control and parental features',
icon: '\ud83d\udc65',
iconBg: 'rgba(139, 92, 246, 0.15)',
iconColor: '#8b5cf6',
section: 'security',
path: 'admin/services/client-guardian',
service: 'client-guardian',
version: '0.5.0'
},
'auth-guardian': {
id: 'auth-guardian',
name: 'Auth Guardian',
desc: 'Two-factor authentication and access control for network services',
icon: '\ud83d\udd10',
iconBg: 'rgba(239, 68, 68, 0.15)',
iconColor: '#ef4444',
section: 'security',
path: 'admin/services/auth-guardian',
service: 'auth-guardian',
version: '0.3.0'
},
// Network Apps
'bandwidth-manager': {
id: 'bandwidth-manager',
name: 'Bandwidth Manager',
desc: 'Control bandwidth allocation with QoS, quotas, and traffic shaping',
icon: '\ud83d\udcc8',
iconBg: 'rgba(59, 130, 246, 0.15)',
iconColor: '#3b82f6',
section: 'network',
path: 'admin/services/bandwidth-manager',
service: 'bandwidth-manager',
version: '0.5.0'
},
'traffic-shaper': {
id: 'traffic-shaper',
name: 'Traffic Shaper',
desc: 'Advanced traffic shaping with SQM and cake-based queue management',
icon: '\ud83c\udf0a',
iconBg: 'rgba(20, 184, 166, 0.15)',
iconColor: '#14b8a6',
section: 'network',
path: 'admin/network/sqm',
service: null,
version: null
},
'wireguard': {
id: 'wireguard',
name: 'WireGuard VPN',
desc: 'Modern, fast, and secure VPN tunnel management',
icon: '\ud83d\udd12',
iconBg: 'rgba(239, 68, 68, 0.15)',
iconColor: '#ef4444',
section: 'network',
path: 'admin/network/wireguard',
service: 'wgserver',
version: null
},
'network-modes': {
id: 'network-modes',
name: 'Network Modes',
desc: 'Configure router, AP, or bridge mode with one-click setup',
icon: '\ud83c\udf10',
iconBg: 'rgba(102, 126, 234, 0.15)',
iconColor: '#667eea',
section: 'network',
path: 'admin/services/network-modes',
service: null,
version: '0.2.0'
},
// Monitoring Apps
'media-flow': {
id: 'media-flow',
name: 'Media Flow',
desc: 'Monitor streaming services and media traffic in real-time',
icon: '\ud83c\udfac',
iconBg: 'rgba(236, 72, 153, 0.15)',
iconColor: '#ec4899',
section: 'monitoring',
path: 'admin/services/media-flow',
service: 'media-flow',
version: '0.6.0'
},
'ndpid': {
id: 'ndpid',
name: 'nDPId Flows',
desc: 'Deep packet inspection with application detection and flow analysis',
icon: '\ud83d\udd0d',
iconBg: 'rgba(6, 182, 212, 0.15)',
iconColor: '#06b6d4',
section: 'monitoring',
path: 'admin/services/ndpid',
service: 'ndpid',
version: '1.1.0'
},
'netifyd': {
id: 'netifyd',
name: 'Netifyd',
desc: 'Network intelligence agent for traffic classification',
icon: '\ud83d\udce1',
iconBg: 'rgba(6, 182, 212, 0.15)',
iconColor: '#06b6d4',
section: 'monitoring',
path: 'admin/services/secubox-netifyd',
service: 'netifyd',
version: '1.2.0'
},
'netdata': {
id: 'netdata',
name: 'Netdata Dashboard',
desc: 'Real-time system and network performance monitoring',
icon: '\ud83d\udcca',
iconBg: 'rgba(34, 197, 94, 0.15)',
iconColor: '#22c55e',
section: 'monitoring',
path: 'admin/services/netdata-dashboard',
service: 'netdata',
version: '0.4.0'
},
// System Apps
'system-hub': {
id: 'system-hub',
name: 'System Hub',
desc: 'Centralized system administration and configuration',
icon: '\u2699\ufe0f',
iconBg: 'rgba(249, 115, 22, 0.15)',
iconColor: '#f97316',
section: 'system',
path: 'admin/services/system-hub',
service: null,
version: '0.4.0'
},
'cdn-cache': {
id: 'cdn-cache',
name: 'CDN Cache',
desc: 'Local content caching for faster repeated downloads',
icon: '\ud83d\udce6',
iconBg: 'rgba(20, 184, 166, 0.15)',
iconColor: '#14b8a6',
section: 'system',
path: 'admin/services/cdn-cache',
service: 'squid',
version: '0.3.0'
},
'secubox-settings': {
id: 'secubox-settings',
name: 'SecuBox Settings',
desc: 'Global SecuBox configuration and preferences',
icon: '\ud83d\udd27',
iconBg: 'rgba(161, 161, 170, 0.15)',
iconColor: '#a1a1aa',
section: 'system',
path: 'admin/system/secubox',
service: null,
version: null
}
},
// Section definitions
sections: {
'dashboard': {
id: 'dashboard',
name: 'Dashboard',
icon: '\ud83c\udfe0',
order: 1
},
'security': {
id: 'security',
name: 'Security',
icon: '\ud83d\udee1\ufe0f',
order: 2
},
'network': {
id: 'network',
name: 'Network',
icon: '\ud83c\udf10',
order: 3
},
'monitoring': {
id: 'monitoring',
name: 'Monitoring',
icon: '\ud83d\udcca',
order: 4
},
'system': {
id: 'system',
name: 'System',
icon: '\u2699\ufe0f',
order: 5
}
},
/**
* Get apps by section
*/
getAppsBySection: function(sectionId) {
var self = this;
var apps = [];
Object.keys(this.apps).forEach(function(key) {
if (self.apps[key].section === sectionId) {
apps.push(self.apps[key]);
}
});
return apps;
},
/**
* Get all sections
*/
getSections: function() {
var self = this;
return Object.keys(this.sections)
.map(function(key) { return self.sections[key]; })
.sort(function(a, b) { return a.order - b.order; });
},
/**
* Build LuCI URL
*/
buildUrl: function(path) {
return L.url(path);
},
/**
* Check if service is running
*/
checkServiceStatus: function(serviceName) {
if (!serviceName) {
return Promise.resolve(null);
}
return L.resolveDefault(
fs.exec('/etc/init.d/' + serviceName, ['status']),
{ code: 1 }
).then(function(res) {
return res.code === 0 ? 'running' : 'stopped';
});
},
/**
* Get all app statuses
*/
getAllAppStatuses: function() {
var self = this;
var promises = [];
var appKeys = Object.keys(this.apps);
appKeys.forEach(function(key) {
var app = self.apps[key];
if (app.service) {
promises.push(
self.checkServiceStatus(app.service).then(function(status) {
return { id: key, status: status };
})
);
} else {
promises.push(Promise.resolve({ id: key, status: null }));
}
});
return Promise.all(promises).then(function(results) {
var statuses = {};
results.forEach(function(r) {
statuses[r.id] = r.status;
});
return statuses;
});
}
});

View File

@ -0,0 +1,371 @@
'use strict';
'require view';
'require dom';
'require poll';
'require uci';
'require rpc';
'require fs';
'require ui';
'require secubox-portal/portal as portal';
var callSystemBoard = rpc.declare({
object: 'system',
method: 'board'
});
var callSystemInfo = rpc.declare({
object: 'system',
method: 'info'
});
return view.extend({
currentSection: 'dashboard',
appStatuses: {},
load: function() {
return Promise.all([
callSystemBoard(),
callSystemInfo(),
this.loadAppStatuses()
]);
},
loadAppStatuses: function() {
var self = this;
var apps = portal.apps;
var promises = [];
Object.keys(apps).forEach(function(key) {
var app = apps[key];
if (app.service) {
promises.push(
fs.exec('/etc/init.d/' + app.service, ['status'])
.then(function(res) {
return { id: key, status: (res && res.code === 0) ? 'running' : 'stopped' };
})
.catch(function() {
return { id: key, status: 'stopped' };
})
);
}
});
return Promise.all(promises).then(function(results) {
results.forEach(function(r) {
self.appStatuses[r.id] = r.status;
});
return self.appStatuses;
});
},
render: function(data) {
var boardInfo = data[0] || {};
var sysInfo = data[1] || {};
var self = this;
// Set portal app context
document.body.setAttribute('data-secubox-app', 'portal');
// Inject CSS
var cssLink = document.querySelector('link[href*="secubox-portal/portal.css"]');
if (!cssLink) {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = L.resource('secubox-portal/portal.css');
document.head.appendChild(link);
}
var container = E('div', { 'class': 'secubox-portal' }, [
// Header
this.renderHeader(),
// Content
E('div', { 'class': 'sb-portal-content' }, [
this.renderDashboardSection(boardInfo, sysInfo),
this.renderSecuritySection(),
this.renderNetworkSection(),
this.renderMonitoringSection(),
this.renderSystemSection()
])
]);
// Set initial active section
this.switchSection('dashboard');
return container;
},
renderHeader: function() {
var self = this;
var sections = portal.getSections();
return E('div', { 'class': 'sb-portal-header' }, [
// Brand
E('div', { 'class': 'sb-portal-brand' }, [
E('div', { 'class': 'sb-portal-logo' }, 'S'),
E('span', { 'class': 'sb-portal-title' }, 'SecuBox'),
E('span', { 'class': 'sb-portal-version' }, 'v0.14.0')
]),
// Navigation
E('nav', { 'class': 'sb-portal-nav' },
sections.map(function(section) {
return E('button', {
'class': 'sb-portal-nav-item' + (section.id === 'dashboard' ? ' active' : ''),
'data-section': section.id,
'click': function() { self.switchSection(section.id); }
}, [
E('span', { 'class': 'sb-portal-nav-icon' }, section.icon),
section.name
]);
})
),
// Actions
E('div', { 'class': 'sb-portal-actions' }, [
E('a', {
'class': 'sb-luci-return',
'href': L.url('admin/status/overview'),
'title': 'Return to standard LuCI interface'
}, [
'\u2190 Standard LuCI'
])
])
]);
},
switchSection: function(sectionId) {
this.currentSection = sectionId;
// Update nav active state
document.querySelectorAll('.sb-portal-nav-item').forEach(function(btn) {
btn.classList.toggle('active', btn.dataset.section === sectionId);
});
// Update section visibility
document.querySelectorAll('.sb-portal-section').forEach(function(section) {
section.classList.toggle('active', section.dataset.section === sectionId);
});
},
renderDashboardSection: function(boardInfo, sysInfo) {
var self = this;
var securityApps = portal.getAppsBySection('security');
var networkApps = portal.getAppsBySection('network');
var monitoringApps = portal.getAppsBySection('monitoring');
// Count running services
var runningCount = Object.values(this.appStatuses).filter(function(s) { return s === 'running'; }).length;
var totalServices = Object.keys(this.appStatuses).length;
return E('div', { 'class': 'sb-portal-section active', 'data-section': 'dashboard' }, [
E('div', { 'class': 'sb-section-header' }, [
E('h2', { 'class': 'sb-section-title' }, 'SecuBox Dashboard'),
E('p', { 'class': 'sb-section-subtitle' },
'Welcome to SecuBox - Your unified network security and management platform')
]),
// Quick Stats
E('div', { 'class': 'sb-dashboard-grid' }, [
// System Status
E('div', { 'class': 'sb-quick-stat' }, [
E('div', { 'class': 'sb-quick-stat-header' }, [
E('div', { 'class': 'sb-quick-stat-icon system' }, '\ud83d\udda5\ufe0f'),
E('span', { 'class': 'sb-quick-stat-status running' }, 'Online')
]),
E('div', { 'class': 'sb-quick-stat-value' }, boardInfo.hostname || 'SecuBox'),
E('div', { 'class': 'sb-quick-stat-label' }, boardInfo.model || 'Router')
]),
// Services Status
E('div', { 'class': 'sb-quick-stat' }, [
E('div', { 'class': 'sb-quick-stat-header' }, [
E('div', { 'class': 'sb-quick-stat-icon monitoring' }, '\u2699\ufe0f'),
E('span', { 'class': 'sb-quick-stat-status ' + (runningCount > 0 ? 'running' : 'warning') },
runningCount > 0 ? 'Active' : 'Idle')
]),
E('div', { 'class': 'sb-quick-stat-value' }, runningCount + '/' + totalServices),
E('div', { 'class': 'sb-quick-stat-label' }, 'Services Running')
]),
// Security Apps
E('div', { 'class': 'sb-quick-stat' }, [
E('div', { 'class': 'sb-quick-stat-header' }, [
E('div', { 'class': 'sb-quick-stat-icon security' }, '\ud83d\udee1\ufe0f'),
E('span', { 'class': 'sb-quick-stat-status running' }, 'Protected')
]),
E('div', { 'class': 'sb-quick-stat-value' }, securityApps.length),
E('div', { 'class': 'sb-quick-stat-label' }, 'Security Modules')
]),
// Network Apps
E('div', { 'class': 'sb-quick-stat' }, [
E('div', { 'class': 'sb-quick-stat-header' }, [
E('div', { 'class': 'sb-quick-stat-icon network' }, '\ud83c\udf10'),
E('span', { 'class': 'sb-quick-stat-status running' }, 'Configured')
]),
E('div', { 'class': 'sb-quick-stat-value' }, networkApps.length),
E('div', { 'class': 'sb-quick-stat-label' }, 'Network Tools')
])
]),
// Featured Apps
E('h3', { 'style': 'margin: 1.5rem 0 1rem; color: var(--cyber-text-primary);' }, 'Quick Access'),
E('div', { 'class': 'sb-app-grid' },
this.renderFeaturedApps(['crowdsec', 'bandwidth-manager', 'media-flow', 'ndpid'])
),
// Recent Events placeholder
E('div', { 'class': 'sb-events-list', 'style': 'margin-top: 1.5rem;' }, [
E('div', { 'class': 'sb-events-header' }, [
E('h4', { 'class': 'sb-events-title' }, 'System Overview')
]),
E('div', { 'class': 'sb-events-item' }, [
E('div', { 'class': 'sb-events-icon info' }, '\u2139\ufe0f'),
E('div', { 'class': 'sb-events-content' }, [
E('p', { 'class': 'sb-events-message' },
'System: ' + (boardInfo.system || 'Unknown') + ' | Kernel: ' + (boardInfo.kernel || 'Unknown')),
E('span', { 'class': 'sb-events-meta' }, 'System Information')
])
]),
E('div', { 'class': 'sb-events-item' }, [
E('div', { 'class': 'sb-events-icon success' }, '\u2705'),
E('div', { 'class': 'sb-events-content' }, [
E('p', { 'class': 'sb-events-message' },
'Uptime: ' + this.formatUptime(sysInfo.uptime || 0)),
E('span', { 'class': 'sb-events-meta' }, 'System Status')
])
]),
E('div', { 'class': 'sb-events-item' }, [
E('div', { 'class': 'sb-events-icon info' }, '\ud83d\udcbe'),
E('div', { 'class': 'sb-events-content' }, [
E('p', { 'class': 'sb-events-message' },
'Memory: ' + this.formatBytes(sysInfo.memory ? sysInfo.memory.total - sysInfo.memory.free : 0) +
' / ' + this.formatBytes(sysInfo.memory ? sysInfo.memory.total : 0) + ' used'),
E('span', { 'class': 'sb-events-meta' }, 'Resource Usage')
])
])
])
]);
},
renderFeaturedApps: function(appIds) {
var self = this;
return appIds.map(function(id) {
var app = portal.apps[id];
if (!app) return null;
var status = self.appStatuses[id];
return E('a', {
'class': 'sb-app-card',
'href': L.url(app.path)
}, [
E('div', { 'class': 'sb-app-card-header' }, [
E('div', {
'class': 'sb-app-card-icon',
'style': 'background: ' + app.iconBg + '; color: ' + app.iconColor + ';'
}, app.icon),
E('div', {}, [
E('h4', { 'class': 'sb-app-card-title' }, app.name),
app.version ? E('span', { 'class': 'sb-app-card-version' }, 'v' + app.version) : null
])
]),
E('p', { 'class': 'sb-app-card-desc' }, app.desc),
status ? E('div', { 'class': 'sb-app-card-status' }, [
E('span', { 'class': 'sb-app-card-status-dot ' + status }),
E('span', { 'class': 'sb-app-card-status-text' },
status === 'running' ? 'Service running' : 'Service stopped')
]) : null
]);
}).filter(function(el) { return el !== null; });
},
renderSecuritySection: function() {
var apps = portal.getAppsBySection('security');
return this.renderAppSection('security', 'Security',
'Protect your network with advanced security tools', apps);
},
renderNetworkSection: function() {
var apps = portal.getAppsBySection('network');
return this.renderAppSection('network', 'Network',
'Configure and optimize your network connections', apps);
},
renderMonitoringSection: function() {
var apps = portal.getAppsBySection('monitoring');
return this.renderAppSection('monitoring', 'Monitoring',
'Monitor traffic, applications, and system performance', apps);
},
renderSystemSection: function() {
var apps = portal.getAppsBySection('system');
return this.renderAppSection('system', 'System',
'System administration and configuration tools', apps);
},
renderAppSection: function(sectionId, title, subtitle, apps) {
var self = this;
return E('div', { 'class': 'sb-portal-section', 'data-section': sectionId }, [
E('div', { 'class': 'sb-section-header' }, [
E('h2', { 'class': 'sb-section-title' }, title),
E('p', { 'class': 'sb-section-subtitle' }, subtitle)
]),
E('div', { 'class': 'sb-app-grid' },
apps.map(function(app) {
var status = self.appStatuses[app.id];
return E('a', {
'class': 'sb-app-card',
'href': L.url(app.path)
}, [
E('div', { 'class': 'sb-app-card-header' }, [
E('div', {
'class': 'sb-app-card-icon',
'style': 'background: ' + app.iconBg + '; color: ' + app.iconColor + ';'
}, app.icon),
E('div', {}, [
E('h4', { 'class': 'sb-app-card-title' }, app.name),
app.version ? E('span', { 'class': 'sb-app-card-version' }, 'v' + app.version) : null
])
]),
E('p', { 'class': 'sb-app-card-desc' }, app.desc),
status ? E('div', { 'class': 'sb-app-card-status' }, [
E('span', { 'class': 'sb-app-card-status-dot ' + status }),
E('span', { 'class': 'sb-app-card-status-text' },
status === 'running' ? 'Service running' : 'Service stopped')
]) : null
]);
})
)
]);
},
formatUptime: function(seconds) {
if (!seconds) return 'Unknown';
var days = Math.floor(seconds / 86400);
var hours = Math.floor((seconds % 86400) / 3600);
var mins = Math.floor((seconds % 3600) / 60);
var parts = [];
if (days > 0) parts.push(days + 'd');
if (hours > 0) parts.push(hours + 'h');
if (mins > 0) parts.push(mins + 'm');
return parts.join(' ') || '< 1m';
},
formatBytes: function(bytes) {
if (!bytes) return '0 B';
var units = ['B', 'KB', 'MB', 'GB', 'TB'];
var i = 0;
while (bytes >= 1024 && i < units.length - 1) {
bytes /= 1024;
i++;
}
return bytes.toFixed(1) + ' ' + units[i];
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -0,0 +1,18 @@
{
"admin/secubox": {
"title": "SecuBox",
"order": 1,
"action": {
"type": "alias",
"path": "admin/secubox/portal"
}
},
"admin/secubox/portal": {
"title": "Portal",
"order": 10,
"action": {
"type": "view",
"path": "secubox-portal/index"
}
}
}