From a9e5bc0262268c000be188bde9e11f6953c5bc45 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Fri, 9 Jan 2026 14:02:23 +0100 Subject: [PATCH] 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 --- .../secubox/luci-app-secubox-portal/Makefile | 20 + .../resources/secubox-portal/portal.css | 573 ++++++++++++++++++ .../resources/secubox-portal/portal.js | 300 +++++++++ .../resources/view/secubox-portal/index.js | 371 ++++++++++++ .../luci/menu.d/luci-app-secubox-portal.json | 18 + 5 files changed, 1282 insertions(+) create mode 100644 package/secubox/luci-app-secubox-portal/Makefile create mode 100644 package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox-portal/portal.css create mode 100644 package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox-portal/portal.js create mode 100644 package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/index.js create mode 100644 package/secubox/luci-app-secubox-portal/root/usr/share/luci/menu.d/luci-app-secubox-portal.json diff --git a/package/secubox/luci-app-secubox-portal/Makefile b/package/secubox/luci-app-secubox-portal/Makefile new file mode 100644 index 00000000..31728825 --- /dev/null +++ b/package/secubox/luci-app-secubox-portal/Makefile @@ -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 + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildance! +$(eval $(call BuildPackage,luci-app-secubox-portal)) diff --git a/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox-portal/portal.css b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox-portal/portal.css new file mode 100644 index 00000000..d3d76831 --- /dev/null +++ b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox-portal/portal.css @@ -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; + } +} diff --git a/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox-portal/portal.js b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox-portal/portal.js new file mode 100644 index 00000000..5bba9d78 --- /dev/null +++ b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox-portal/portal.js @@ -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; + }); + } +}); diff --git a/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/index.js b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/index.js new file mode 100644 index 00000000..c46af8a8 --- /dev/null +++ b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/index.js @@ -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 +}); diff --git a/package/secubox/luci-app-secubox-portal/root/usr/share/luci/menu.d/luci-app-secubox-portal.json b/package/secubox/luci-app-secubox-portal/root/usr/share/luci/menu.d/luci-app-secubox-portal.json new file mode 100644 index 00000000..98d1b5c0 --- /dev/null +++ b/package/secubox/luci-app-secubox-portal/root/usr/share/luci/menu.d/luci-app-secubox-portal.json @@ -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" + } + } +}