From 5b55ab3ef93d5da53f8c70c5cc90368b6c961f90 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Wed, 14 Jan 2026 09:32:14 +0100 Subject: [PATCH] feat: Dashboard reorganization and auth security fixes - Move Debug Console from Client Guardian to System Hub - Add Auto-Zoning Rules dedicated view in Client Guardian - Add public pages for Bug Bounty and Crowdfunding (no ACL) - Fix auth-logger to only detect real login attempts - Add private IP whitelist for CrowdSec (RFC1918 ranges) - Update navigation menus across all apps - Bump secubox-auth-logger to v1.2.2 Co-Authored-By: Claude Opus 4.5 --- .../luci-static/resources/secubox/nav.js | 201 +++++- .../share/luci/menu.d/luci-app-secubox.json | 47 +- .../secubox/luci-app-client-guardian/Makefile | 2 +- .../resources/client-guardian/debug.js | 309 --------- .../resources/client-guardian/nav.js | 3 +- .../view/client-guardian/autozoning.js | 128 ++++ .../resources/view/client-guardian/debug.js | 260 ------- .../view/client-guardian/settings.js | 104 +-- .../resources/view/client-guardian/wizard.js | 43 ++ .../root/etc/client-guardian/profiles.json | 64 +- .../root/etc/config/client-guardian | 39 -- .../usr/libexec/rpcd/luci.client-guardian | 142 ++-- .../luci/menu.d/luci-app-client-guardian.json | 22 +- .../resources/crowdsec-dashboard/nav.js | 2 +- .../menu.d/luci-app-crowdsec-dashboard.json | 12 +- .../view/secubox-portal/bugbounty.js | 556 +++++++++++++++ .../view/secubox-portal/crowdfunding.js | 655 ++++++++++++++++++ .../luci/menu.d/luci-app-secubox-portal.json | 38 + .../luci-static/resources/secubox/nav.js | 201 +++++- .../share/luci/menu.d/luci-app-secubox.json | 47 +- .../luci-static/resources/system-hub/nav.js | 145 +++- .../resources/view/system-hub/debug.js | 226 ++++++ .../luci/menu.d/luci-app-system-hub.json | 18 +- .../secubox/secubox-app-nodogsplash/Makefile | 29 +- package/secubox/secubox-auth-logger/Makefile | 6 +- .../secubox-auth-logger/files/auth-monitor.sh | 9 +- .../files/secubox-private-ip-whitelist.yaml | 17 + 27 files changed, 2450 insertions(+), 875 deletions(-) delete mode 100644 package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/debug.js create mode 100644 package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/autozoning.js delete mode 100644 package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/debug.js create mode 100644 package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/bugbounty.js create mode 100644 package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/crowdfunding.js create mode 100644 package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/debug.js create mode 100644 package/secubox/secubox-auth-logger/files/secubox-private-ip-whitelist.yaml diff --git a/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js b/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js index c4eee23b..760a8b42 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js +++ b/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js @@ -1,16 +1,30 @@ 'use strict'; 'require baseclass'; -'require secubox-theme/cascade as Cascade'; + +/** + * SecuBox Main Navigation + * SecuBox themed navigation tabs + */ + +// Immediately inject CSS to hide LuCI tabs before page renders +(function() { + if (typeof document === 'undefined') return; + if (document.getElementById('secubox-early-hide')) return; + var style = document.createElement('style'); + style.id = 'secubox-early-hide'; + style.textContent = 'body[data-page^="admin-secubox"] ul.tabs:not(.sb-nav-tabs), body[data-page^="admin-secubox"] .tabs:not(.sb-nav-tabs) { display: none !important; }'; + (document.head || document.documentElement).appendChild(style); +})(); var tabs = [ - { id: 'dashboard', icon: '🚀', label: _('Dashboard'), path: ['admin', 'secubox', 'dashboard'] }, - { id: 'modules', icon: '🧩', label: _('Modules'), path: ['admin', 'secubox', 'modules'] }, + { id: 'dashboard', icon: '📊', label: _('Dashboard'), path: ['admin', 'secubox', 'dashboard'] }, { id: 'wizard', icon: '✨', label: _('Wizard'), path: ['admin', 'secubox', 'wizard'] }, + { id: 'modules', icon: '🧩', label: _('Modules'), path: ['admin', 'secubox', 'modules'] }, { id: 'apps', icon: '🛒', label: _('App Store'), path: ['admin', 'secubox', 'apps'] }, { id: 'monitoring', icon: '📡', label: _('Monitoring'), path: ['admin', 'secubox', 'monitoring'] }, { id: 'alerts', icon: '⚠️', label: _('Alerts'), path: ['admin', 'secubox', 'alerts'] }, { id: 'settings', icon: '⚙️', label: _('Settings'), path: ['admin', 'secubox', 'settings'] }, - { id: 'help', icon: '✨', label: _('Bonus'), path: ['admin', 'secubox', 'help'] } + { id: 'help', icon: '🎁', label: _('Bonus'), path: ['admin', 'secubox', 'help'] } ]; return baseclass.extend({ @@ -18,31 +32,162 @@ return baseclass.extend({ return tabs.slice(); }, - renderTabs: function(active) { - return Cascade.createLayer({ - id: 'secubox-main-nav', - type: 'tabs', - role: 'menu', - depth: 1, - className: 'sh-nav-tabs secubox-nav-tabs', - items: this.getTabs().map(function(tab) { - return { - id: tab.id, - label: tab.label, - icon: tab.icon, - href: L.url.apply(L, tab.path), - state: tab.id === active ? 'active' : null - }; - }), - active: active, - onSelect: function(item, ev) { - if (item.href && ev && (ev.metaKey || ev.ctrlKey)) - return true; - if (item.href) { - location.href = item.href; - return false; - } + ensureLuCITabsHidden: function() { + if (typeof document === 'undefined') + return; + + // Actively remove LuCI tabs from DOM + var luciTabs = document.querySelectorAll('.cbi-tabmenu, ul.tabs, div.tabs, .nav-tabs'); + luciTabs.forEach(function(el) { + // Don't remove our own tabs + if (!el.classList.contains('sb-nav-tabs')) { + el.style.display = 'none'; + // Also try removing from DOM after a brief delay + setTimeout(function() { + if (el.parentNode && !el.classList.contains('sb-nav-tabs')) { + el.style.display = 'none'; + } + }, 100); } }); + + if (document.getElementById('secubox-tabstyle')) + return; + var style = document.createElement('style'); + style.id = 'secubox-tabstyle'; + style.textContent = ` +/* Hide default LuCI tabs for SecuBox - aggressive selectors */ +/* Target any ul.tabs in the page */ +ul.tabs { + display: none !important; +} + +/* Be more specific for pages that need tabs elsewhere */ +body:not([data-page^="admin-secubox"]) ul.tabs { + display: block !important; +} + +/* All possible LuCI tab selectors */ +body[data-page^="admin-secubox-dashboard"] .tabs, +body[data-page^="admin-secubox-modules"] .tabs, +body[data-page^="admin-secubox-wizard"] .tabs, +body[data-page^="admin-secubox-apps"] .tabs, +body[data-page^="admin-secubox-monitoring"] .tabs, +body[data-page^="admin-secubox-alerts"] .tabs, +body[data-page^="admin-secubox-settings"] .tabs, +body[data-page^="admin-secubox-help"] .tabs, +body[data-page^="admin-secubox"] #tabmenu, +body[data-page^="admin-secubox"] .cbi-tabmenu, +body[data-page^="admin-secubox"] .nav-tabs, +body[data-page^="admin-secubox"] ul.cbi-tabmenu, +body[data-page^="admin-secubox"] ul.tabs, +/* Fallback: hide any tabs that appear before our custom nav */ +.secubox-dashboard .tabs, +.secubox-dashboard + .tabs, +.secubox-dashboard ~ .tabs, +.cbi-map > .tabs:first-child, +#maincontent > .container > .tabs, +#maincontent > .container > ul.tabs, +#view > .tabs, +#view > ul.tabs, +.view > .tabs, +.view > ul.tabs, +div.tabs:has(+ .secubox-dashboard), +/* Direct sibling of SecuBox content */ +.sb-nav-tabs ~ .tabs, +/* LuCI 24.x specific */ +.luci-app-secubox .tabs, +#cbi-secubox .tabs { + display: none !important; +} + +/* Hide tabs container when our nav is present */ +.sb-nav-tabs ~ ul.tabs, +.sb-nav-tabs + ul.tabs { + display: none !important; +} + +/* SecuBox Nav Tabs */ +.sb-nav-tabs { + display: flex; + gap: 4px; + margin-bottom: 24px; + padding: 6px; + background: var(--sb-bg-secondary); + border-radius: var(--sb-radius-lg); + border: 1px solid var(--sb-border); + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.sb-nav-tab { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + border-radius: var(--sb-radius); + background: transparent; + border: none; + color: var(--sb-text-secondary); + font-weight: 500; + font-size: 13px; + cursor: pointer; + text-decoration: none; + transition: all 0.2s ease; + white-space: nowrap; +} + +.sb-nav-tab:hover { + color: var(--sb-text-primary); + background: var(--sb-bg-tertiary); +} + +.sb-nav-tab.active { + color: var(--sb-accent); + background: var(--sb-bg-tertiary); + box-shadow: inset 0 -2px 0 var(--sb-accent); +} + +.sb-tab-icon { + font-size: 16px; + line-height: 1; +} + +.sb-tab-label { + font-family: var(--sb-font-sans); +} + +@media (max-width: 768px) { + .sb-nav-tabs { + padding: 4px; + } + .sb-nav-tab { + padding: 8px 12px; + font-size: 12px; + } + .sb-tab-label { + display: none; + } + .sb-tab-icon { + font-size: 18px; + } +} + `; + document.head && document.head.appendChild(style); + }, + + renderTabs: function(active) { + this.ensureLuCITabsHidden(); + return E('div', { 'class': 'sb-nav-tabs' }, + this.getTabs().map(function(tab) { + return E('a', { + 'class': 'sb-nav-tab' + (tab.id === active ? ' active' : ''), + 'href': L.url.apply(L, tab.path) + }, [ + E('span', { 'class': 'sb-tab-icon' }, tab.icon), + E('span', { 'class': 'sb-tab-label' }, tab.label) + ]); + }) + ); } }); diff --git a/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json b/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json index bbc7f1e6..fae2a9b4 100644 --- a/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json +++ b/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json @@ -4,6 +4,9 @@ "order": 25, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/dashboard": { @@ -12,6 +15,9 @@ "action": { "type": "view", "path": "secubox/dashboard" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/wizard": { @@ -20,6 +26,9 @@ "action": { "type": "view", "path": "secubox/wizard" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/apps": { @@ -28,6 +37,9 @@ "action": { "type": "view", "path": "secubox/apps" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/modules": { @@ -36,6 +48,9 @@ "action": { "type": "view", "path": "secubox/modules" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/alerts": { @@ -44,6 +59,9 @@ "action": { "type": "view", "path": "secubox/alerts" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/settings": { @@ -52,6 +70,9 @@ "action": { "type": "view", "path": "secubox/settings" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/security": { @@ -59,6 +80,9 @@ "order": 30, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/monitoring": { @@ -67,6 +91,9 @@ "action": { "type": "view", "path": "secubox/monitoring" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/network": { @@ -74,6 +101,9 @@ "order": 40, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/network/cdn-cache": { @@ -83,9 +113,7 @@ "type": "firstchild" }, "depends": { - "acl": [ - "luci-app-cdn-cache" - ] + "acl": ["luci-app-secubox", "luci-app-cdn-cache"] } }, "admin/secubox/network/cdn-cache/overview": { @@ -141,6 +169,9 @@ "order": 50, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/services": { @@ -148,6 +179,9 @@ "order": 60, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/help": { @@ -156,6 +190,9 @@ "action": { "type": "view", "path": "secubox/help" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/network/network-modes": { @@ -165,9 +202,7 @@ "type": "firstchild" }, "depends": { - "acl": [ - "luci-app-network-modes" - ] + "acl": ["luci-app-secubox", "luci-app-network-modes"] } }, "admin/secubox/network/network-modes/overview": { diff --git a/package/secubox/luci-app-client-guardian/Makefile b/package/secubox/luci-app-client-guardian/Makefile index ae8e7c05..6e7c4119 100644 --- a/package/secubox/luci-app-client-guardian/Makefile +++ b/package/secubox/luci-app-client-guardian/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-client-guardian PKG_VERSION:=0.4.0 -PKG_RELEASE:=5 +PKG_RELEASE:=7 PKG_ARCH:=all PKG_LICENSE:=Apache-2.0 diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/debug.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/debug.js deleted file mode 100644 index fb5628ce..00000000 --- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/debug.js +++ /dev/null @@ -1,309 +0,0 @@ -'use strict'; -'require baseclass'; -'require uci'; - -/** - * Client Guardian Debug Module - * Provides comprehensive logging and debugging capabilities - */ - -var DEBUG_LEVELS = { - ERROR: 0, - WARN: 1, - INFO: 2, - DEBUG: 3, - TRACE: 4 -}; - -var DEBUG_COLORS = { - ERROR: '#ef4444', - WARN: '#f59e0b', - INFO: '#3b82f6', - DEBUG: '#8b5cf6', - TRACE: '#6b7280' -}; - -var debugEnabled = false; -var debugLevel = DEBUG_LEVELS.INFO; -var logBuffer = []; -var maxBufferSize = 500; - -return baseclass.extend({ - init: function() { - // Check if debug mode is enabled in UCI - return uci.load('client-guardian').then(L.bind(function() { - debugEnabled = uci.get('client-guardian', 'config', 'debug_enabled') === '1'; - var level = uci.get('client-guardian', 'config', 'debug_level') || 'INFO'; - debugLevel = DEBUG_LEVELS[level] || DEBUG_LEVELS.INFO; - - if (debugEnabled) { - this.info('Client Guardian Debug Mode Enabled', { - level: level, - timestamp: new Date().toISOString() - }); - } - }, this)).catch(function() { - // UCI not available, use defaults - debugEnabled = false; - }); - }, - - isEnabled: function() { - return debugEnabled; - }, - - setEnabled: function(enabled) { - debugEnabled = enabled; - if (enabled) { - this.info('Debug mode enabled manually'); - } - }, - - setLevel: function(level) { - if (typeof level === 'string') { - debugLevel = DEBUG_LEVELS[level.toUpperCase()] || DEBUG_LEVELS.INFO; - } else { - debugLevel = level; - } - this.info('Debug level changed', { level: debugLevel }); - }, - - _log: function(level, levelName, message, data) { - if (!debugEnabled || level > debugLevel) { - return; - } - - var timestamp = new Date().toISOString(); - var logEntry = { - timestamp: timestamp, - level: levelName, - message: message, - data: data || {} - }; - - // Add to buffer - logBuffer.push(logEntry); - if (logBuffer.length > maxBufferSize) { - logBuffer.shift(); - } - - // Console output with styling - var style = 'color: ' + DEBUG_COLORS[levelName] + '; font-weight: bold;'; - var prefix = '[CG:' + levelName + ']'; - - if (data) { - console.log('%c' + prefix + ' ' + timestamp, style, message, data); - } else { - console.log('%c' + prefix + ' ' + timestamp, style, message); - } - }, - - error: function(message, data) { - this._log(DEBUG_LEVELS.ERROR, 'ERROR', message, data); - }, - - warn: function(message, data) { - this._log(DEBUG_LEVELS.WARN, 'WARN', message, data); - }, - - info: function(message, data) { - this._log(DEBUG_LEVELS.INFO, 'INFO', message, data); - }, - - debug: function(message, data) { - this._log(DEBUG_LEVELS.DEBUG, 'DEBUG', message, data); - }, - - trace: function(message, data) { - this._log(DEBUG_LEVELS.TRACE, 'TRACE', message, data); - }, - - // API call tracing - traceAPICall: function(method, params) { - this.debug('API Call: ' + method, { - params: params, - stack: new Error().stack - }); - }, - - traceAPIResponse: function(method, response, duration) { - this.debug('API Response: ' + method, { - response: response, - duration: duration + 'ms' - }); - }, - - traceAPIError: function(method, error) { - this.error('API Error: ' + method, { - error: error.toString(), - stack: error.stack - }); - }, - - // UI event tracing - traceEvent: function(eventName, target, data) { - this.trace('Event: ' + eventName, { - target: target, - data: data - }); - }, - - // Performance monitoring - startTimer: function(label) { - if (!debugEnabled) return null; - - var timer = { - label: label, - start: performance.now() - }; - - this.trace('Timer started: ' + label); - return timer; - }, - - endTimer: function(timer) { - if (!debugEnabled || !timer) return; - - var duration = (performance.now() - timer.start).toFixed(2); - this.debug('Timer ended: ' + timer.label, { - duration: duration + 'ms' - }); - - return duration; - }, - - // Get log buffer - getLogs: function(level, count) { - var filtered = logBuffer; - - if (level) { - var levelValue = DEBUG_LEVELS[level.toUpperCase()]; - filtered = logBuffer.filter(function(entry) { - return DEBUG_LEVELS[entry.level] <= levelValue; - }); - } - - if (count) { - filtered = filtered.slice(-count); - } - - return filtered; - }, - - // Export logs as text - exportLogs: function() { - var text = '=== Client Guardian Debug Logs ===\n'; - text += 'Generated: ' + new Date().toISOString() + '\n'; - text += 'Total entries: ' + logBuffer.length + '\n\n'; - - logBuffer.forEach(function(entry) { - text += '[' + entry.timestamp + '] [' + entry.level + '] ' + entry.message; - if (Object.keys(entry.data).length > 0) { - text += '\n Data: ' + JSON.stringify(entry.data, null, 2); - } - text += '\n\n'; - }); - - return text; - }, - - // Download logs as file - downloadLogs: function() { - var text = this.exportLogs(); - var blob = new Blob([text], { type: 'text/plain' }); - var url = URL.createObjectURL(blob); - var a = document.createElement('a'); - a.href = url; - a.download = 'client-guardian-debug-' + Date.now() + '.txt'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - - this.info('Logs downloaded'); - }, - - // Clear log buffer - clearLogs: function() { - logBuffer = []; - this.info('Log buffer cleared'); - }, - - // Get system info for debugging - getSystemInfo: function() { - return { - userAgent: navigator.userAgent, - platform: navigator.platform, - language: navigator.language, - screenResolution: window.screen.width + 'x' + window.screen.height, - windowSize: window.innerWidth + 'x' + window.innerHeight, - cookiesEnabled: navigator.cookieEnabled, - onLine: navigator.onLine, - timestamp: new Date().toISOString(), - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - memory: performance.memory ? { - usedJSHeapSize: (performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2) + ' MB', - totalJSHeapSize: (performance.memory.totalJSHeapSize / 1024 / 1024).toFixed(2) + ' MB', - jsHeapSizeLimit: (performance.memory.jsHeapSizeLimit / 1024 / 1024).toFixed(2) + ' MB' - } : 'N/A' - }; - }, - - // Network request monitoring - monitorFetch: function(originalFetch) { - if (!debugEnabled) return originalFetch; - - var self = this; - return function() { - var args = arguments; - var url = args[0]; - var timer = self.startTimer('Fetch: ' + url); - - self.trace('Fetch request', { - url: url, - options: args[1] - }); - - return originalFetch.apply(this, args).then(function(response) { - var duration = self.endTimer(timer); - self.trace('Fetch response', { - url: url, - status: response.status, - statusText: response.statusText, - duration: duration - }); - return response; - }).catch(function(error) { - self.error('Fetch error', { - url: url, - error: error.toString() - }); - throw error; - }); - }; - }, - - // Initialize global error handler - setupGlobalErrorHandler: function() { - var self = this; - - window.addEventListener('error', function(event) { - self.error('Global error', { - message: event.message, - filename: event.filename, - lineno: event.lineno, - colno: event.colno, - error: event.error ? event.error.toString() : 'Unknown' - }); - }); - - window.addEventListener('unhandledrejection', function(event) { - self.error('Unhandled promise rejection', { - reason: event.reason, - promise: event.promise - }); - }); - - this.info('Global error handlers registered'); - } -}); diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/nav.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/nav.js index cc099c5e..c5955956 100644 --- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/nav.js +++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/nav.js @@ -11,8 +11,7 @@ var tabs = [ { id: 'wizard', icon: '🚀', label: _('Setup Wizard'), path: ['admin', 'secubox', 'security', 'guardian', 'wizard'] }, { id: 'clients', icon: '👥', label: _('Clients'), path: ['admin', 'secubox', 'security', 'guardian', 'clients'] }, { id: 'zones', icon: '🏠', label: _('Zones'), path: ['admin', 'secubox', 'security', 'guardian', 'zones'] }, - { id: 'captive', icon: '🚪', label: _('Captive Portal'), path: ['admin', 'secubox', 'security', 'guardian', 'captive'] }, - { id: 'portal', icon: '🎨', label: _('Portal Config'), path: ['admin', 'secubox', 'security', 'guardian', 'portal'] }, + { id: 'autozoning', icon: '🎯', label: _('Auto-Zoning'), path: ['admin', 'secubox', 'security', 'guardian', 'autozoning'] }, { id: 'logs', icon: '📜', label: _('Logs'), path: ['admin', 'secubox', 'security', 'guardian', 'logs'] }, { id: 'alerts', icon: '🔔', label: _('Alerts'), path: ['admin', 'secubox', 'security', 'guardian', 'alerts'] }, { id: 'parental', icon: '👨‍👩‍👧', label: _('Parental'), path: ['admin', 'secubox', 'security', 'guardian', 'parental'] }, diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/autozoning.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/autozoning.js new file mode 100644 index 00000000..89c115fc --- /dev/null +++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/autozoning.js @@ -0,0 +1,128 @@ +'use strict'; +'require view'; +'require form'; +'require ui'; +'require uci'; +'require client-guardian/api as API'; +'require client-guardian/nav as CgNav'; +'require secubox-portal/header as SbHeader'; + +return view.extend({ + load: function() { + return Promise.all([ + API.getStatus(), + L.resolveDefault(uci.load('client-guardian'), {}) + ]); + }, + + render: function(data) { + var status = data[0] || {}; + + var m, s, o; + + m = new form.Map('client-guardian', _('Auto-Zoning Rules'), + _('Automatically assign new clients to zones based on device type, vendor, or hostname patterns.')); + + // Auto-Zoning / Auto-Parking Settings + s = m.section(form.NamedSection, 'config', 'client-guardian', _('Auto-Zoning Settings')); + + o = s.option(form.Flag, 'auto_zoning_enabled', _('Enable Auto-Zoning'), + _('Automatically assign clients to zones using matching rules')); + o.default = '1'; + o.rmempty = false; + + o = s.option(form.ListValue, 'auto_parking_zone', _('Auto-Parking Zone'), + _('Default zone for clients that don\'t match any rule')); + o.value('guest', _('Guest')); + o.value('quarantine', _('Quarantine')); + o.value('iot', _('IoT')); + o.value('lan_private', _('LAN Private')); + o.default = 'guest'; + o.depends('auto_zoning_enabled', '1'); + + o = s.option(form.Flag, 'auto_parking_approve', _('Auto-Approve Parked Clients'), + _('Automatically approve clients placed in auto-parking zone')); + o.default = '0'; + o.depends('auto_zoning_enabled', '1'); + + // Auto-Zoning Rules Section + s = m.section(form.GridSection, 'auto_zone_rule', _('Auto-Zoning Rules')); + s.anonymous = false; + s.addremove = true; + s.sortable = true; + s.description = _('Rules are evaluated in priority order. First match wins.'); + + o = s.option(form.Flag, 'enabled', _('Enabled')); + o.default = '1'; + o.editable = true; + + o = s.option(form.Value, 'name', _('Rule Name')); + o.rmempty = false; + + o = s.option(form.ListValue, 'match_type', _('Match Type')); + o.value('vendor', _('Device Vendor (OUI)')); + o.value('hostname', _('Hostname Pattern')); + o.value('mac_prefix', _('MAC Prefix')); + o.default = 'vendor'; + + o = s.option(form.Value, 'match_value', _('Match Value/Pattern')); + o.placeholder = _('e.g., Xiaomi, Apple, .*camera.*, aa:bb:cc'); + o.rmempty = false; + + o = s.option(form.ListValue, 'target_zone', _('Target Zone')); + o.value('lan_private', _('LAN Private')); + o.value('iot', _('IoT')); + o.value('kids', _('Kids')); + o.value('guest', _('Guest')); + o.value('quarantine', _('Quarantine')); + o.default = 'guest'; + + o = s.option(form.Flag, 'auto_approve', _('Auto-Approve')); + o.default = '0'; + + o = s.option(form.Value, 'priority', _('Priority')); + o.datatype = 'uinteger'; + o.placeholder = '50'; + o.default = '50'; + o.description = _('Lower numbers = higher priority'); + + return m.render().then(function(rendered) { + // Info box explaining auto-zoning + var infoBox = E('div', { + 'class': 'cbi-section', + 'style': 'background: var(--cg-bg-secondary, #151b23); border-left: 4px solid var(--cg-accent, #6366f1); padding: 1em; margin-bottom: 1em; border-radius: 8px;' + }, [ + E('h3', { 'style': 'margin-top: 0; color: var(--cg-text-primary, #e6edf3);' }, _('How Auto-Zoning Works')), + E('ul', { 'style': 'margin: 0.5em 0; color: var(--cg-text-secondary, #8b949e);' }, [ + E('li', {}, _('When a new client connects, rules are evaluated in priority order (lowest number first).')), + E('li', {}, _('The first matching rule determines which zone the client is assigned to.')), + E('li', {}, _('If no rules match, the client goes to the Auto-Parking Zone.')), + E('li', {}, [ + E('strong', { 'style': 'color: var(--cg-text-primary, #e6edf3);' }, _('Match Types: ')), + _('Vendor uses MAC OUI database, Hostname uses regex patterns, MAC Prefix matches the start of MAC address.') + ]) + ]) + ]); + + rendered.insertBefore(infoBox, rendered.firstChild); + + // Main wrapper with SecuBox header + var wrapper = E('div', { 'class': 'secubox-page-wrapper' }); + wrapper.appendChild(SbHeader.render()); + + var view = E('div', { 'class': 'client-guardian-dashboard' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('client-guardian/dashboard.css') }), + CgNav.renderTabs('autozoning'), + rendered + ]); + + wrapper.appendChild(view); + return wrapper; + }); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/debug.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/debug.js deleted file mode 100644 index c3ac9094..00000000 --- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/debug.js +++ /dev/null @@ -1,260 +0,0 @@ -'use strict'; -'require view'; -'require dom'; -'require ui'; -'require uci'; -'require rpc'; -'require client-guardian/debug as Debug'; -'require client-guardian/nav as CgNav'; - -var callGetLogs = rpc.declare({ - object: 'luci.client-guardian', - method: 'logs', - params: ['limit', 'level'], - expect: { logs: [] } -}); - -return view.extend({ - load: function() { - return Promise.all([ - Debug.init(), - uci.load('client-guardian'), - callGetLogs(100, 'debug') - ]); - }, - - render: function(data) { - var backendLogs = data[2].logs || []; - var self = this; - - var debugEnabled = uci.get('client-guardian', 'config', 'debug_enabled') === '1'; - var debugLevel = uci.get('client-guardian', 'config', 'debug_level') || 'INFO'; - - return E('div', { 'class': 'client-guardian-dashboard' }, [ - E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }), - E('link', { 'rel': 'stylesheet', 'href': L.resource('client-guardian/dashboard.css') }), - CgNav.renderTabs('debug'), - - E('div', { 'class': 'cg-header' }, [ - E('div', { 'class': 'cg-logo' }, [ - E('div', { 'class': 'cg-logo-icon' }, '🐛'), - E('div', { 'class': 'cg-logo-text' }, 'Mode Debug') - ]), - E('div', { 'class': 'cg-debug-controls' }, [ - E('button', { - 'class': 'cg-btn cg-btn-sm', - 'click': L.bind(this.handleRefreshLogs, this) - }, '🔄 Actualiser'), - E('button', { - 'class': 'cg-btn cg-btn-sm', - 'click': L.bind(this.handleClearLogs, this) - }, '🗑️ Effacer'), - E('button', { - 'class': 'cg-btn cg-btn-sm cg-btn-primary', - 'click': L.bind(this.handleDownloadLogs, this) - }, '💾 Télécharger') - ]) - ]), - - // Debug Status Card - E('div', { 'class': 'cg-card' }, [ - E('div', { 'class': 'cg-card-header' }, [ - E('div', { 'class': 'cg-card-title' }, [ - E('span', { 'class': 'cg-card-title-icon' }, '⚙️'), - 'Configuration Debug' - ]) - ]), - E('div', { 'class': 'cg-card-body' }, [ - E('div', { 'class': 'cg-debug-status-grid' }, [ - E('div', { 'class': 'cg-debug-status-item' }, [ - E('div', { 'class': 'cg-debug-status-label' }, 'Mode Debug'), - E('div', { 'class': 'cg-debug-status-value' }, [ - E('span', { - 'class': 'cg-status-badge ' + (debugEnabled ? 'approved' : 'offline') - }, [ - E('span', { 'class': 'cg-status-dot' }), - debugEnabled ? 'Activé' : 'Désactivé' - ]), - E('button', { - 'class': 'cg-btn cg-btn-sm', - 'style': 'margin-left: 8px', - 'click': L.bind(this.handleToggleDebug, this, !debugEnabled) - }, debugEnabled ? 'Désactiver' : 'Activer') - ]) - ]), - E('div', { 'class': 'cg-debug-status-item' }, [ - E('div', { 'class': 'cg-debug-status-label' }, 'Niveau de Log'), - E('select', { - 'class': 'cg-input cg-input-sm', - 'id': 'debug-level-select', - 'value': debugLevel, - 'change': L.bind(this.handleChangeLevel, this) - }, [ - E('option', { 'value': 'ERROR' }, 'ERROR'), - E('option', { 'value': 'WARN' }, 'WARN'), - E('option', { 'value': 'INFO', 'selected': debugLevel === 'INFO' }, 'INFO'), - E('option', { 'value': 'DEBUG' }, 'DEBUG'), - E('option', { 'value': 'TRACE' }, 'TRACE') - ]) - ]), - E('div', { 'class': 'cg-debug-status-item' }, [ - E('div', { 'class': 'cg-debug-status-label' }, 'Logs Backend'), - E('div', { 'class': 'cg-debug-status-value' }, backendLogs.length + ' entrées') - ]), - E('div', { 'class': 'cg-debug-status-item' }, [ - E('div', { 'class': 'cg-debug-status-label' }, 'Logs Frontend'), - E('div', { 'class': 'cg-debug-status-value' }, Debug.getLogs().length + ' entrées') - ]) - ]) - ]) - ]), - - // System Information - E('div', { 'class': 'cg-card' }, [ - E('div', { 'class': 'cg-card-header' }, [ - E('div', { 'class': 'cg-card-title' }, [ - E('span', { 'class': 'cg-card-title-icon' }, 'ℹ️'), - 'Informations Système' - ]) - ]), - E('div', { 'class': 'cg-card-body' }, [ - this.renderSystemInfo(Debug.getSystemInfo()) - ]) - ]), - - // Backend Logs - E('div', { 'class': 'cg-card' }, [ - E('div', { 'class': 'cg-card-header' }, [ - E('div', { 'class': 'cg-card-title' }, [ - E('span', { 'class': 'cg-card-title-icon' }, '📋'), - 'Logs Backend RPCD' - ]), - E('span', { 'class': 'cg-card-badge' }, backendLogs.length + ' entrées') - ]), - E('div', { 'class': 'cg-card-body' }, [ - E('div', { 'class': 'cg-log-container', 'id': 'backend-logs' }, - backendLogs.length > 0 ? - backendLogs.map(L.bind(this.renderLogEntry, this)) : - E('div', { 'class': 'cg-empty-state' }, [ - E('div', { 'class': 'cg-empty-state-icon' }, '📝'), - E('div', { 'class': 'cg-empty-state-title' }, 'Aucun log backend'), - E('div', { 'class': 'cg-empty-state-text' }, 'Les logs du serveur apparaîtront ici') - ]) - ) - ]) - ]), - - // Frontend Console Logs - E('div', { 'class': 'cg-card' }, [ - E('div', { 'class': 'cg-card-header' }, [ - E('div', { 'class': 'cg-card-title' }, [ - E('span', { 'class': 'cg-card-title-icon' }, '💻'), - 'Logs Frontend Console' - ]), - E('span', { 'class': 'cg-card-badge' }, Debug.getLogs().length + ' entrées') - ]), - E('div', { 'class': 'cg-card-body' }, [ - E('div', { 'class': 'cg-log-container', 'id': 'frontend-logs' }, - Debug.getLogs().length > 0 ? - Debug.getLogs().reverse().slice(0, 100).map(L.bind(this.renderLogEntry, this)) : - E('div', { 'class': 'cg-empty-state' }, [ - E('div', { 'class': 'cg-empty-state-icon' }, '🖥️'), - E('div', { 'class': 'cg-empty-state-title' }, 'Aucun log frontend'), - E('div', { 'class': 'cg-empty-state-text' }, 'Les logs du navigateur apparaîtront ici') - ]) - ) - ]) - ]) - ]); - }, - - renderSystemInfo: function(info) { - return E('div', { 'class': 'cg-system-info-grid' }, [ - this.renderInfoItem('Navigateur', info.userAgent), - this.renderInfoItem('Plateforme', info.platform), - this.renderInfoItem('Langue', info.language), - this.renderInfoItem('Résolution', info.screenResolution), - this.renderInfoItem('Fenêtre', info.windowSize), - this.renderInfoItem('Cookies', info.cookiesEnabled ? 'Activés' : 'Désactivés'), - this.renderInfoItem('Connexion', info.onLine ? 'En ligne' : 'Hors ligne'), - this.renderInfoItem('Fuseau horaire', info.timezone), - this.renderInfoItem('Mémoire JS', typeof info.memory === 'object' ? - 'Utilisée: ' + info.memory.usedJSHeapSize + ' / Limite: ' + info.memory.jsHeapSizeLimit : - info.memory - ) - ]); - }, - - renderInfoItem: function(label, value) { - return E('div', { 'class': 'cg-info-item' }, [ - E('div', { 'class': 'cg-info-label' }, label + ':'), - E('div', { 'class': 'cg-info-value' }, value) - ]); - }, - - renderLogEntry: function(log) { - var levelClass = 'cg-log-' + (log.level || 'info').toLowerCase(); - var levelIcon = { - 'ERROR': '🚨', - 'WARN': '⚠️', - 'INFO': 'ℹ️', - 'DEBUG': '🐛', - 'TRACE': '🔍' - }[log.level] || 'ℹ️'; - - return E('div', { 'class': 'cg-log-entry ' + levelClass }, [ - E('div', { 'class': 'cg-log-header' }, [ - E('span', { 'class': 'cg-log-icon' }, levelIcon), - E('span', { 'class': 'cg-log-level' }, log.level || 'INFO'), - E('span', { 'class': 'cg-log-time' }, log.timestamp || new Date().toISOString()) - ]), - E('div', { 'class': 'cg-log-message' }, log.message), - log.data && Object.keys(log.data).length > 0 ? - E('details', { 'class': 'cg-log-details' }, [ - E('summary', {}, 'Données additionnelles'), - E('pre', { 'class': 'cg-log-data' }, JSON.stringify(log.data, null, 2)) - ]) : - E('span') - ]); - }, - - handleToggleDebug: function(enabled, ev) { - uci.set('client-guardian', 'config', 'debug_enabled', enabled ? '1' : '0'); - uci.save().then(L.bind(function() { - return uci.apply(); - }, this)).then(L.bind(function() { - ui.addNotification(null, E('p', {}, 'Mode debug ' + (enabled ? 'activé' : 'désactivé')), 'success'); - Debug.setEnabled(enabled); - location.reload(); - }, this)); - }, - - handleChangeLevel: function(ev) { - var level = ev.target.value; - uci.set('client-guardian', 'config', 'debug_level', level); - uci.save().then(L.bind(function() { - return uci.apply(); - }, this)).then(L.bind(function() { - ui.addNotification(null, E('p', {}, 'Niveau de debug changé: ' + level), 'success'); - Debug.setLevel(level); - }, this)); - }, - - handleRefreshLogs: function(ev) { - location.reload(); - }, - - handleClearLogs: function(ev) { - Debug.clearLogs(); - ui.addNotification(null, E('p', {}, 'Logs frontend effacés'), 'success'); - location.reload(); - }, - - handleDownloadLogs: function(ev) { - Debug.downloadLogs(); - }, - - handleSaveApply: null, - handleSave: null, - handleReset: null -}); diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/settings.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/settings.js index 0da97dcb..bff19236 100644 --- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/settings.js +++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/settings.js @@ -123,98 +123,42 @@ return view.extend({ o.default = '60'; o.depends('enabled', '1'); - // Auto-Zoning / Auto-Parking - s = m.section(form.NamedSection, 'config', 'client-guardian', _('Auto-Zoning & Auto-Parking')); - s.description = _('Automatically assign new clients to zones based on device type, vendor, or hostname patterns.'); - - o = s.option(form.Flag, 'auto_zoning_enabled', _('Enable Auto-Zoning'), - _('Automatically assign clients to zones using matching rules')); - o.default = '1'; - o.rmempty = false; - - o = s.option(form.ListValue, 'auto_parking_zone', _('Auto-Parking Zone'), - _('Default zone for clients that don\'t match any rule')); - o.value('guest', _('Guest')); - o.value('quarantine', _('Quarantine')); - o.value('iot', _('IoT')); - o.value('lan_private', _('LAN Private')); - o.default = 'guest'; - o.depends('auto_zoning_enabled', '1'); - - o = s.option(form.Flag, 'auto_parking_approve', _('Auto-Approve Parked Clients'), - _('Automatically approve clients placed in auto-parking zone')); - o.default = '0'; - o.depends('auto_zoning_enabled', '1'); - - // Auto-Zoning Rules Section - s = m.section(form.GridSection, 'auto_zone_rule', _('Auto-Zoning Rules')); - s.anonymous = false; - s.addremove = true; - s.sortable = true; - s.description = _('Rules are evaluated in priority order. First match wins.'); - - o = s.option(form.Flag, 'enabled', _('Enabled')); - o.default = '1'; - o.editable = true; - - o = s.option(form.Value, 'name', _('Rule Name')); - o.rmempty = false; - - o = s.option(form.ListValue, 'match_type', _('Match Type')); - o.value('vendor', _('Device Vendor (OUI)')); - o.value('hostname', _('Hostname Pattern')); - o.value('mac_prefix', _('MAC Prefix')); - o.default = 'vendor'; - - o = s.option(form.Value, 'match_value', _('Match Value/Pattern')); - o.placeholder = _('e.g., Xiaomi, Apple, .*camera.*, aa:bb:cc'); - o.rmempty = false; - - o = s.option(form.ListValue, 'target_zone', _('Target Zone')); - o.value('lan_private', _('LAN Private')); - o.value('iot', _('IoT')); - o.value('kids', _('Kids')); - o.value('guest', _('Guest')); - o.value('quarantine', _('Quarantine')); - o.default = 'guest'; - - o = s.option(form.Flag, 'auto_approve', _('Auto-Approve')); - o.default = '0'; - - o = s.option(form.Value, 'priority', _('Priority')); - o.datatype = 'uinteger'; - o.placeholder = '50'; - o.default = '50'; - o.description = _('Lower numbers = higher priority'); - return m.render().then(function(rendered) { + // Policy display names + var policyNames = { + 'open': _('Open'), + 'quarantine': _('Quarantine'), + 'whitelist': _('Whitelist Only') + }; + var currentPolicy = policy.default_policy || 'quarantine'; + var policyDisplay = policyNames[currentPolicy] || currentPolicy; + // Add policy info box at the top var infoBox = E('div', { 'class': 'cbi-section', - 'style': 'background: #e8f4f8; border-left: 4px solid #0088cc; padding: 1em; margin-bottom: 1em;' + 'style': 'background: var(--cg-bg-secondary, #151b23); border-left: 4px solid var(--cg-accent, #6366f1); padding: 1em; margin-bottom: 1em; border-radius: 8px;' }, [ - E('h3', { 'style': 'margin-top: 0;' }, _('Current Policy: ') + E('span', { 'style': 'color: #0088cc;' }, policy.default_policy || 'quarantine')), - E('div', { 'style': 'margin-top: 1em;' }, [ - E('strong', {}, _('Session Timeout:')), - ' ', - E('span', {}, (policy.session_timeout || 86400) + ' ' + _('seconds')) - ]), - E('div', { 'style': 'margin-top: 1em; padding: 0.75em; background: white; border-radius: 4px;' }, [ - E('strong', {}, _('Policy Descriptions:')), - E('ul', { 'style': 'margin: 0.5em 0;' }, [ + E('h3', { 'style': 'margin-top: 0; color: var(--cg-text-primary, #e6edf3);' }, [ + _('Current Policy: '), + E('span', { 'style': 'color: var(--cg-accent, #6366f1); font-weight: 600;' }, policyDisplay) + ]), + E('div', { 'style': 'margin-top: 0.5em; color: var(--cg-text-secondary, #8b949e);' }, [ + E('strong', {}, _('Session Timeout: ')), + E('span', {}, (policy.session_timeout || 86400) + ' ' + _('seconds')) + ]), + E('div', { 'style': 'margin-top: 1em; padding: 0.75em; background: var(--cg-bg-tertiary, #1e2632); border-radius: 4px;' }, [ + E('strong', { 'style': 'color: var(--cg-text-primary, #e6edf3);' }, _('Policy Descriptions:')), + E('ul', { 'style': 'margin: 0.5em 0; color: var(--cg-text-secondary, #8b949e);' }, [ E('li', {}, [ - E('strong', {}, _('Open:')), - ' ', + E('strong', { 'style': 'color: #22c55e;' }, _('Open: ')), _('All clients can access the network without authentication. Not recommended for public networks.') ]), E('li', {}, [ - E('strong', {}, _('Quarantine:')), - ' ', + E('strong', { 'style': 'color: #f59e0b;' }, _('Quarantine: ')), _('New clients are placed in quarantine and require manual approval. Recommended for secure networks.') ]), E('li', {}, [ - E('strong', {}, _('Whitelist Only:')), - ' ', + E('strong', { 'style': 'color: #ef4444;' }, _('Whitelist Only: ')), _('Only explicitly approved clients can access the network. Highest security.') ]) ]) diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/wizard.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/wizard.js index 2d77f6c6..286723df 100644 --- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/wizard.js +++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/wizard.js @@ -77,6 +77,12 @@ return view.extend({ ), profile.zones.length > 4 ? E('span', { 'class': 'cg-profile-more' }, '+' + (profile.zones.length - 4) + ' autres') : + E('span'), + (profile.auto_zone_rules && profile.auto_zone_rules.length > 0) ? + E('div', { 'style': 'margin-top: 8px; font-size: 0.85em; color: #f59e0b' }, [ + E('span', {}, '🎯 '), + profile.auto_zone_rules.length + ' règles auto-zoning' + ]) : E('span') ]), E('button', { @@ -111,6 +117,42 @@ return view.extend({ ) ]), + // Auto-zoning rules section + (profile.auto_zone_rules && profile.auto_zone_rules.length > 0) ? + E('div', { 'style': 'background: rgba(245, 158, 11, 0.1); padding: 16px; border-radius: 8px; margin: 16px 0' }, [ + E('div', { 'style': 'display: flex; align-items: center; gap: 8px; margin-bottom: 12px' }, [ + E('span', { 'style': 'font-size: 1.2em' }, '🎯'), + E('strong', {}, 'Règles Auto-Zoning (' + profile.auto_zone_rules.length + '):') + ]), + E('div', { 'style': 'max-height: 150px; overflow-y: auto' }, + E('table', { 'style': 'width: 100%; font-size: 0.85em; border-collapse: collapse' }, [ + E('thead', {}, E('tr', { 'style': 'border-bottom: 1px solid rgba(255,255,255,0.1)' }, [ + E('th', { 'style': 'text-align: left; padding: 4px 8px' }, 'Règle'), + E('th', { 'style': 'text-align: left; padding: 4px 8px' }, 'Type'), + E('th', { 'style': 'text-align: left; padding: 4px 8px' }, 'Zone cible') + ])), + E('tbody', {}, + profile.auto_zone_rules.map(function(rule) { + var matchTypeLabels = { + 'vendor': 'Fabricant', + 'hostname': 'Hostname', + 'mac_prefix': 'MAC' + }; + return E('tr', {}, [ + E('td', { 'style': 'padding: 4px 8px' }, rule.name), + E('td', { 'style': 'padding: 4px 8px; color: #8b949e' }, matchTypeLabels[rule.match_type] || rule.match_type), + E('td', { 'style': 'padding: 4px 8px; font-weight: 500' }, rule.target_zone) + ]); + }) + ) + ]) + ), + E('p', { 'style': 'font-size: 0.8em; color: #8b949e; margin: 8px 0 0 0' }, [ + 'Zone par défaut: ', + E('strong', {}, profile.auto_parking_zone || 'guest') + ]) + ]) : E('span'), + // Dashboard Reactiveness section E('div', { 'style': 'background: rgba(34, 197, 94, 0.1); padding: 16px; border-radius: 8px; margin: 16px 0' }, [ E('div', { 'style': 'display: flex; align-items: center; gap: 12px; margin-bottom: 12px' }, [ @@ -298,6 +340,7 @@ return view.extend({ ui.addNotification(null, E('div', {}, [ E('p', {}, E('strong', {}, 'Profil appliqué avec succès!')), E('p', {}, result.zones_created + ' zones créées et configurées.'), + result.rules_created > 0 ? E('p', {}, '🎯 ' + result.rules_created + ' règles auto-zoning activées.') : E('span'), E('p', { 'style': 'font-size: 0.9em; margin-top: 8px' }, [ '✅ Rafraîchissement auto: ' + (autoRefresh ? 'Activé (' + refreshInterval + 's)' : 'Désactivé'), E('br'), diff --git a/package/secubox/luci-app-client-guardian/root/etc/client-guardian/profiles.json b/package/secubox/luci-app-client-guardian/root/etc/client-guardian/profiles.json index da123574..d66729bb 100644 --- a/package/secubox/luci-app-client-guardian/root/etc/client-guardian/profiles.json +++ b/package/secubox/luci-app-client-guardian/root/etc/client-guardian/profiles.json @@ -49,6 +49,8 @@ "priority": "low" } ], + "auto_zone_rules": [], + "auto_parking_zone": "lan", "firewall_defaults": { "input": "ACCEPT", "output": "ACCEPT", @@ -134,7 +136,18 @@ "portal_required": true, "priority": "low" } - ] + ], + "auto_zone_rules": [ + {"name": "Caméras IP", "match_type": "vendor", "match_value": "Hikvision|Dahua|Reolink|Ubiquiti|Axis", "target_zone": "iot", "priority": 10, "auto_approve": true}, + {"name": "Thermostats", "match_type": "vendor", "match_value": "Nest|Ecobee|Honeywell|Tado", "target_zone": "iot", "priority": 15, "auto_approve": true}, + {"name": "Ampoules connectées", "match_type": "vendor", "match_value": "Philips Hue|LIFX|Yeelight|Sengled", "target_zone": "iot", "priority": 20, "auto_approve": true}, + {"name": "Enceintes connectées", "match_type": "vendor", "match_value": "Amazon|Google|Sonos|Apple", "target_zone": "iot", "priority": 25, "auto_approve": false}, + {"name": "Consoles enfants", "match_type": "vendor", "match_value": "Nintendo|Sony.*PlayStation|Microsoft.*Xbox", "target_zone": "kids", "priority": 30, "auto_approve": false}, + {"name": "Tablettes enfants", "match_type": "hostname", "match_value": ".*[Kk]id.*|.*[Ee]nfant.*|.*[Cc]hild.*", "target_zone": "kids", "priority": 35, "auto_approve": false}, + {"name": "Appareils Apple", "match_type": "vendor", "match_value": "Apple", "target_zone": "lan_private", "priority": 50, "auto_approve": false}, + {"name": "PC Windows", "match_type": "vendor", "match_value": "Dell|HP|Lenovo|ASUS|Acer|Microsoft", "target_zone": "lan_private", "priority": 55, "auto_approve": false} + ], + "auto_parking_zone": "guest" }, { "id": "small_business", @@ -208,7 +221,16 @@ "portal_required": true, "priority": "low" } - ] + ], + "auto_zone_rules": [ + {"name": "Serveurs", "match_type": "hostname", "match_value": ".*[Ss]erver.*|.*[Ss]rv.*|.*[Dd][Cc].*|.*[Nn][Aa][Ss].*", "target_zone": "servers", "priority": 5, "auto_approve": false}, + {"name": "Imprimantes réseau", "match_type": "vendor", "match_value": "HP|Canon|Epson|Brother|Xerox|Ricoh|Lexmark", "target_zone": "corporate", "priority": 10, "auto_approve": true}, + {"name": "Postes Dell/HP", "match_type": "vendor", "match_value": "Dell|HP|Lenovo", "target_zone": "corporate", "priority": 20, "auto_approve": false}, + {"name": "Smartphones", "match_type": "vendor", "match_value": "Apple|Samsung|Xiaomi|OnePlus|Google", "target_zone": "byod", "priority": 30, "auto_approve": false}, + {"name": "Tablettes", "match_type": "hostname", "match_value": ".*[Ii][Pp]ad.*|.*[Tt]ablet.*|.*[Gg]alaxy.*[Tt]ab.*", "target_zone": "byod", "priority": 35, "auto_approve": false}, + {"name": "IoT/Caméras", "match_type": "vendor", "match_value": "Hikvision|Dahua|Ubiquiti|Axis|Ring", "target_zone": "servers", "priority": 40, "auto_approve": true} + ], + "auto_parking_zone": "guest" }, { "id": "hotel", @@ -284,7 +306,13 @@ "portal_required": true, "priority": "low" } - ] + ], + "auto_zone_rules": [ + {"name": "Équipement hôtel", "match_type": "hostname", "match_value": ".*[Rr]eception.*|.*[Hh]otel.*|.*[Aa]dmin.*", "target_zone": "management", "priority": 5, "auto_approve": false}, + {"name": "Imprimantes/POS", "match_type": "vendor", "match_value": "HP|Epson|Star Micronics|Ingenico|Verifone", "target_zone": "management", "priority": 10, "auto_approve": true}, + {"name": "Smart TV", "match_type": "vendor", "match_value": "Samsung|LG|Sony|Philips|TCL", "target_zone": "rooms_floor1", "priority": 50, "auto_approve": true} + ], + "auto_parking_zone": "public" }, { "id": "apartment", @@ -370,7 +398,12 @@ "bandwidth_limit": 20, "priority": "low" } - ] + ], + "auto_zone_rules": [ + {"name": "Équipement propriétaire", "match_type": "hostname", "match_value": ".*[Ll]andlord.*|.*[Pp]roprio.*|.*[Aa]dmin.*", "target_zone": "landlord", "priority": 5, "auto_approve": false}, + {"name": "Imprimantes/NAS", "match_type": "vendor", "match_value": "Synology|QNAP|HP|Brother", "target_zone": "landlord", "priority": 10, "auto_approve": true} + ], + "auto_parking_zone": "common" }, { "id": "school", @@ -447,7 +480,16 @@ "bandwidth_limit": 100, "priority": "normal" } - ] + ], + "auto_zone_rules": [ + {"name": "Serveurs/NAS", "match_type": "hostname", "match_value": ".*[Ss]erver.*|.*[Ss]rv.*|.*[Nn][Aa][Ss].*", "target_zone": "admin", "priority": 5, "auto_approve": false}, + {"name": "Imprimantes", "match_type": "vendor", "match_value": "HP|Canon|Epson|Brother|Xerox|Ricoh", "target_zone": "admin", "priority": 10, "auto_approve": true}, + {"name": "Ordinateurs prof", "match_type": "hostname", "match_value": ".*[Pp]rof.*|.*[Tt]eacher.*|.*[Ee]nseignant.*", "target_zone": "teachers", "priority": 15, "auto_approve": false}, + {"name": "Postes labo", "match_type": "hostname", "match_value": ".*[Ll]ab.*|.*[Pp][Cc][0-9]+.*|.*[Pp]oste.*", "target_zone": "lab", "priority": 20, "auto_approve": true}, + {"name": "Chromebooks", "match_type": "vendor", "match_value": "Google|Acer|ASUS|Dell|HP|Lenovo", "target_zone": "students", "priority": 30, "auto_approve": false}, + {"name": "Tablettes élèves", "match_type": "hostname", "match_value": ".*[Ee]leve.*|.*[Ss]tudent.*|.*[Tt]ablet.*", "target_zone": "students", "priority": 35, "auto_approve": false} + ], + "auto_parking_zone": "students" }, { "id": "secure_home", @@ -534,7 +576,17 @@ "portal_required": true, "priority": "low" } - ] + ], + "auto_zone_rules": [ + {"name": "Poste télétravail", "match_type": "hostname", "match_value": ".*[Ww]ork.*|.*[Pp]ro.*|.*[Bb]ureau.*|.*[Oo]ffice.*", "target_zone": "work", "priority": 5, "auto_approve": false}, + {"name": "Apple trusted", "match_type": "vendor", "match_value": "Apple", "target_zone": "trusted", "priority": 10, "auto_approve": false}, + {"name": "PC confiance", "match_type": "vendor", "match_value": "Dell|Lenovo|HP", "target_zone": "trusted", "priority": 15, "auto_approve": false}, + {"name": "IoT marques fiables", "match_type": "vendor", "match_value": "Philips|Nest|Ecobee|Sonos|Lutron|Ring", "target_zone": "iot_secure", "priority": 20, "auto_approve": true}, + {"name": "Caméras pro", "match_type": "vendor", "match_value": "Ubiquiti|Axis|Reolink", "target_zone": "iot_secure", "priority": 25, "auto_approve": true}, + {"name": "IoT chinois", "match_type": "vendor", "match_value": "Tuya|Xiaomi|Yeelight|Shenzhen|Espressif|Tasmota", "target_zone": "iot_untrusted", "priority": 30, "auto_approve": true}, + {"name": "IoT inconnu", "match_type": "hostname", "match_value": ".*[Ee][Ss][Pp].*|.*[Tt]asmota.*|.*[Ss]onoff.*", "target_zone": "iot_untrusted", "priority": 35, "auto_approve": true} + ], + "auto_parking_zone": "guest" } ] } diff --git a/package/secubox/luci-app-client-guardian/root/etc/config/client-guardian b/package/secubox/luci-app-client-guardian/root/etc/config/client-guardian index 9aa4f0e8..c47aead7 100644 --- a/package/secubox/luci-app-client-guardian/root/etc/config/client-guardian +++ b/package/secubox/luci-app-client-guardian/root/etc/config/client-guardian @@ -223,45 +223,6 @@ config threat_policy 'threat_policy' option auto_quarantine_threshold '60' option threat_check_interval '60' -# Example Known Clients -config client 'client_example1' - option name 'PC Bureau Papa' - option mac 'AA:BB:CC:DD:EE:01' - option zone 'lan_private' - option status 'approved' - option first_seen '2024-12-01 10:00:00' - option last_seen '2024-12-20 15:30:00' - option notes 'Ordinateur principal' - option static_ip '192.168.1.10' - -config client 'client_example2' - option name 'Tablette Enfant' - option mac 'AA:BB:CC:DD:EE:02' - option zone 'kids' - option status 'approved' - option first_seen '2024-12-05 14:00:00' - option last_seen '2024-12-20 14:00:00' - option daily_quota '120' - option notes 'Tablette de Marie' - -config client 'client_example3' - option name 'Caméra Salon' - option mac 'AA:BB:CC:DD:EE:03' - option zone 'iot' - option status 'approved' - option first_seen '2024-11-15 09:00:00' - option notes 'Caméra IP Xiaomi' - option static_ip '192.168.1.50' - -config client 'client_banned' - option name 'Intrus Détecté' - option mac 'AA:BB:CC:DD:EE:99' - option zone 'blocked' - option status 'banned' - option first_seen '2024-12-18 03:00:00' - option ban_reason 'Tentative intrusion' - option ban_date '2024-12-18 03:05:00' - # Auto-Zoning Rules # Rules are evaluated in order, first match wins diff --git a/package/secubox/luci-app-client-guardian/root/usr/libexec/rpcd/luci.client-guardian b/package/secubox/luci-app-client-guardian/root/usr/libexec/rpcd/luci.client-guardian index 46c7a4e4..9c72c1e7 100755 --- a/package/secubox/luci-app-client-guardian/root/usr/libexec/rpcd/luci.client-guardian +++ b/package/secubox/luci-app-client-guardian/root/usr/libexec/rpcd/luci.client-guardian @@ -270,24 +270,44 @@ count_zones() { } # Threat Intelligence Integration +# Global cache for threats data (populated once per request) +THREAT_CACHE="" +THREAT_ENABLED="" + +# Initialize threat cache (call once at start of get_clients) +init_threat_cache() { + THREAT_ENABLED=$(uci -q get client-guardian.threat_policy.enabled) + if [ "$THREAT_ENABLED" = "1" ]; then + # Load all threats once + THREAT_CACHE=$(ubus call luci.secubox-security-threats get_active_threats 2>/dev/null || echo '{"threats":[]}') + fi +} + get_client_threats() { local ip="$1" local mac="$2" - # Check if threat intelligence is enabled - local threat_enabled=$(uci -q get client-guardian.threat_policy.enabled) - [ "$threat_enabled" != "1" ] && return + # Use cached threat data + [ "$THREAT_ENABLED" != "1" ] && return + [ -z "$THREAT_CACHE" ] && return - # Query Security Threats Dashboard via ubus - ubus call luci.secubox-security-threats get_active_threats 2>/dev/null | \ - jsonfilter -e "@.threats[@.ip='$ip']" -e "@.threats[@.mac='$mac']" 2>/dev/null + # Filter from cached data + echo "$THREAT_CACHE" | jsonfilter -e "@.threats[@.ip='$ip']" -e "@.threats[@.mac='$mac']" 2>/dev/null } enrich_client_with_threats() { local ip="$1" local mac="$2" - # Get threat data + # Quick exit if threats disabled + if [ "$THREAT_ENABLED" != "1" ]; then + json_add_int "threat_count" 0 + json_add_int "risk_score" 0 + json_add_boolean "has_threats" 0 + return + fi + + # Get threat data from cache local threats=$(get_client_threats "$ip" "$mac") # Count threats and find max risk score @@ -306,10 +326,8 @@ enrich_client_with_threats() { json_add_int "risk_score" "${max_risk_score:-0}" json_add_boolean "has_threats" "$( [ "$threat_count" -gt 0 ] && echo 1 || echo 0 )" - # Check for auto-actions if threats detected - if [ "$threat_count" -gt 0 ] && [ "$max_risk_score" -gt 0 ]; then - check_threat_auto_actions "$mac" "$ip" "$max_risk_score" - fi + # Check for auto-actions if threats detected (skip in fast path) + # Auto-actions are now checked separately to avoid slowing down the list } # Auto-ban/quarantine based on threat score @@ -510,22 +528,25 @@ apply_auto_zoning() { get_clients() { json_init json_add_array "clients" - + + # Initialize caches once + init_threat_cache + config_load client-guardian + # Get online clients first local online_list="" while IFS='|' read mac ip hostname iface lease; do [ -z "$mac" ] && continue mac=$(echo "$mac" | tr 'A-F' 'a-f') online_list="$online_list$mac " - + # Check if known local known_section="" local known_name="" local known_zone="" local known_status="" - - # Search in UCI - config_load client-guardian + + # Search in UCI (config already loaded) config_foreach find_client_by_mac client "$mac" json_add_object @@ -549,37 +570,13 @@ get_clients() { # Update last seen uci set client-guardian.$found_section.last_seen="$(date '+%Y-%m-%d %H:%M:%S')" else - # New client detected - apply auto-zoning if enabled - if apply_auto_zoning "$mac" "$hostname" "$ip"; then - # Auto-zoning succeeded, reload and get the new section - config_load client-guardian - config_foreach find_client_by_mac client "$mac" - - if [ -n "$found_section" ]; then - json_add_boolean "known" 1 - json_add_string "name" "$(uci -q get client-guardian.$found_section.name)" - json_add_string "zone" "$(uci -q get client-guardian.$found_section.zone)" - json_add_string "status" "$(uci -q get client-guardian.$found_section.status)" - json_add_string "first_seen" "$(uci -q get client-guardian.$found_section.first_seen)" - json_add_string "vendor" "$(uci -q get client-guardian.$found_section.vendor || echo 'Unknown')" - else - # Fallback in case auto-zoning failed - json_add_boolean "known" 0 - json_add_string "name" "$hostname" - json_add_string "zone" "quarantine" - json_add_string "status" "unknown" - json_add_string "first_seen" "$(date '+%Y-%m-%d %H:%M:%S')" - json_add_string "vendor" "$(get_vendor_from_mac "$mac")" - fi - else - # Auto-zoning disabled or failed - use default quarantine - json_add_boolean "known" 0 - json_add_string "name" "$hostname" - json_add_string "zone" "quarantine" - json_add_string "status" "unknown" - json_add_string "first_seen" "$(date '+%Y-%m-%d %H:%M:%S')" - json_add_string "vendor" "$(get_vendor_from_mac "$mac")" - fi + # New/unknown client - show as unknown (auto-zoning done in background) + json_add_boolean "known" 0 + json_add_string "name" "${hostname:-Unknown}" + json_add_string "zone" "quarantine" + json_add_string "status" "unknown" + json_add_string "first_seen" "" + json_add_string "vendor" "Unknown" fi # Get traffic stats if available @@ -601,9 +598,8 @@ get_clients() { done << EOF $(get_connected_clients) EOF - - # Add offline known clients - config_load client-guardian + + # Add offline known clients (config already loaded) config_foreach add_offline_client client "$online_list" json_close_array @@ -933,6 +929,49 @@ apply_profile() { idx=$((idx + 1)) done + # Remove existing auto_zone_rule sections + local existing_rules=$(uci show client-guardian 2>/dev/null | grep "=auto_zone_rule" | cut -d. -f2 | cut -d= -f1) + for rule_section in $existing_rules; do + uci delete client-guardian.$rule_section 2>/dev/null + done + + # Apply auto-zoning rules from profile + local rule_idx=0 + local rule_count=0 + while [ "$rule_idx" -lt "50" ]; do + local rule_name=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].name" 2>/dev/null) + + # Break if no more rules + [ -z "$rule_name" ] && break + + local match_type=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].match_type") + local match_value=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].match_value") + local target_zone=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].target_zone") + local priority=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].priority") + local auto_approve=$(echo "$profile_data" | jsonfilter -e "@.auto_zone_rules[$rule_idx].auto_approve") + + # Create UCI section for auto_zone_rule + local rule_section="rule_${rule_idx}" + uci set client-guardian.$rule_section=auto_zone_rule 2>/dev/null + uci set client-guardian.$rule_section.enabled='1' 2>/dev/null + [ -n "$rule_name" ] && uci set client-guardian.$rule_section.name="$rule_name" 2>/dev/null + [ -n "$match_type" ] && uci set client-guardian.$rule_section.match_type="$match_type" 2>/dev/null + [ -n "$match_value" ] && uci set client-guardian.$rule_section.match_value="$match_value" 2>/dev/null + [ -n "$target_zone" ] && uci set client-guardian.$rule_section.target_zone="$target_zone" 2>/dev/null + [ -n "$priority" ] && uci set client-guardian.$rule_section.priority="$priority" 2>/dev/null + [ "$auto_approve" = "true" ] && uci set client-guardian.$rule_section.auto_approve='1' 2>/dev/null || uci set client-guardian.$rule_section.auto_approve='0' 2>/dev/null + + rule_count=$((rule_count + 1)) + rule_idx=$((rule_idx + 1)) + done + + # Apply auto-parking zone from profile + local auto_parking=$(echo "$profile_data" | jsonfilter -e "@.auto_parking_zone" 2>/dev/null) + [ -n "$auto_parking" ] && uci set client-guardian.config.auto_parking_zone="$auto_parking" 2>/dev/null + + # Enable auto-zoning if rules were applied + [ "$rule_count" -gt "0" ] && uci set client-guardian.config.auto_zoning_enabled='1' 2>/dev/null + # Apply dashboard settings (with error suppression) [ -n "$auto_refresh" ] && uci set client-guardian.config.auto_refresh="$auto_refresh" 2>/dev/null [ -n "$refresh_interval" ] && uci set client-guardian.config.refresh_interval="$refresh_interval" 2>/dev/null @@ -948,11 +987,12 @@ apply_profile() { # Sync firewall zones sync_firewall_zones - log_event "info" "Applied profile: $profile_id ($zone_count zones)" + log_event "info" "Applied profile: $profile_id ($zone_count zones, $rule_count auto-zoning rules)" json_add_boolean "success" 1 json_add_string "message" "Profile $profile_id applied successfully" json_add_int "zones_created" "$zone_count" + json_add_int "rules_created" "$rule_count" json_dump } diff --git a/package/secubox/luci-app-client-guardian/root/usr/share/luci/menu.d/luci-app-client-guardian.json b/package/secubox/luci-app-client-guardian/root/usr/share/luci/menu.d/luci-app-client-guardian.json index 11189ba5..bb6fd482 100644 --- a/package/secubox/luci-app-client-guardian/root/usr/share/luci/menu.d/luci-app-client-guardian.json +++ b/package/secubox/luci-app-client-guardian/root/usr/share/luci/menu.d/luci-app-client-guardian.json @@ -41,20 +41,12 @@ "path": "client-guardian/zones" } }, - "admin/secubox/security/guardian/captive": { - "title": "Captive Portal", + "admin/secubox/security/guardian/autozoning": { + "title": "Auto-Zoning Rules", "order": 30, "action": { "type": "view", - "path": "client-guardian/captive" - } - }, - "admin/secubox/security/guardian/portal": { - "title": "Portal Config", - "order": 35, - "action": { - "type": "view", - "path": "client-guardian/portal" + "path": "client-guardian/autozoning" } }, "admin/secubox/security/guardian/logs": { @@ -88,13 +80,5 @@ "type": "view", "path": "client-guardian/settings" } - }, - "admin/secubox/security/guardian/debug": { - "title": "Debug Console", - "order": 95, - "action": { - "type": "view", - "path": "client-guardian/debug" - } } } diff --git a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/nav.js b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/nav.js index 1723f155..d902ea20 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/nav.js +++ b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/nav.js @@ -17,8 +17,8 @@ })(); var tabs = [ - { id: 'wizard', icon: '🚀', label: _('Setup Wizard'), path: ['admin', 'secubox', 'security', 'crowdsec', 'wizard'] }, { id: 'overview', icon: '📊', label: _('Overview'), path: ['admin', 'secubox', 'security', 'crowdsec', 'overview'] }, + { id: 'wizard', icon: '🚀', label: _('Wizard'), path: ['admin', 'secubox', 'security', 'crowdsec', 'wizard'] }, { id: 'decisions', icon: '⛔', label: _('Decisions'), path: ['admin', 'secubox', 'security', 'crowdsec', 'decisions'] }, { id: 'alerts', icon: '⚠️', label: _('Alerts'), path: ['admin', 'secubox', 'security', 'crowdsec', 'alerts'] }, { id: 'bouncers', icon: '🛡️', label: _('Bouncers'), path: ['admin', 'secubox', 'security', 'crowdsec', 'bouncers'] }, diff --git a/package/secubox/luci-app-crowdsec-dashboard/root/usr/share/luci/menu.d/luci-app-crowdsec-dashboard.json b/package/secubox/luci-app-crowdsec-dashboard/root/usr/share/luci/menu.d/luci-app-crowdsec-dashboard.json index e189f004..b36e13d6 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/root/usr/share/luci/menu.d/luci-app-crowdsec-dashboard.json +++ b/package/secubox/luci-app-crowdsec-dashboard/root/usr/share/luci/menu.d/luci-app-crowdsec-dashboard.json @@ -9,20 +9,20 @@ "acl": ["luci-app-crowdsec-dashboard"] } }, - "admin/secubox/security/crowdsec/wizard": { - "title": "Setup Wizard", + "admin/secubox/security/crowdsec/overview": { + "title": "Overview", "order": 5, "action": { "type": "view", - "path": "crowdsec-dashboard/wizard" + "path": "crowdsec-dashboard/overview" } }, - "admin/secubox/security/crowdsec/overview": { - "title": "Overview", + "admin/secubox/security/crowdsec/wizard": { + "title": "Setup Wizard", "order": 10, "action": { "type": "view", - "path": "crowdsec-dashboard/overview" + "path": "crowdsec-dashboard/wizard" } }, "admin/secubox/security/crowdsec/decisions": { diff --git a/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/bugbounty.js b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/bugbounty.js new file mode 100644 index 00000000..8605301a --- /dev/null +++ b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/bugbounty.js @@ -0,0 +1,556 @@ +'use strict'; +'require view'; +'require dom'; + +/** + * SecuBox Bug Bounty Program - Public Page + * Accessible without authentication + */ + +return view.extend({ + title: _('SecuBox Bug Bounty'), + + render: function() { + // Inject CSS for public page + var style = document.createElement('style'); + style.textContent = ` +:root { + --bb-bg: #0a0a12; + --bb-bg-secondary: #0f1019; + --bb-bg-card: #1a1a24; + --bb-border: #2a2a3a; + --bb-text: #f1f5f9; + --bb-text-muted: #94a3b8; + --bb-text-dim: #64748b; + --bb-green: #22c55e; + --bb-cyan: #06b6d4; + --bb-blue: #3b82f6; + --bb-purple: #8b5cf6; + --bb-orange: #f97316; + --bb-red: #ef4444; + --bb-gradient: linear-gradient(135deg, #22c55e, #10b981); +} +.bb-public-page { + min-height: 100vh; + background: linear-gradient(135deg, var(--bb-bg) 0%, var(--bb-bg-secondary) 100%); + color: var(--bb-text); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + padding: 2rem; +} +.bb-public-container { + max-width: 900px; + margin: 0 auto; +} +.bb-public-header { + text-align: center; + margin-bottom: 2rem; +} +.bb-public-logo { + font-size: 4rem; + margin-bottom: 1rem; +} +.bb-public-title { + font-size: 2.5rem; + font-weight: 800; + background: var(--bb-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 0.5rem; +} +.bb-public-subtitle { + font-size: 1.1rem; + color: var(--bb-text-muted); + max-width: 600px; + margin: 0 auto; +} +.bb-public-card { + background: var(--bb-bg-card); + border: 1px solid var(--bb-border); + border-radius: 16px; + padding: 2rem; + margin-bottom: 1.5rem; +} +.bb-public-card h3 { + color: var(--bb-green); + font-size: 1.3rem; + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; +} +.bb-public-card p { + color: var(--bb-text-muted); + line-height: 1.7; + margin-bottom: 1rem; +} +.bb-public-card ul { + color: var(--bb-text-muted); + padding-left: 1.5rem; + margin-bottom: 1rem; +} +.bb-public-card li { + margin-bottom: 0.5rem; + line-height: 1.6; +} +.bb-public-btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 12px 24px; + background: var(--bb-gradient); + color: white; + border-radius: 10px; + text-decoration: none; + font-weight: 700; + transition: transform 0.2s, box-shadow 0.2s; +} +.bb-public-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(34, 197, 94, 0.4); +} +.bb-public-btn-secondary { + background: var(--bb-bg-card); + border: 2px solid var(--bb-border); +} +.bb-public-btn-secondary:hover { + border-color: var(--bb-green); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} +.bb-public-actions { + display: flex; + gap: 1rem; + flex-wrap: wrap; + margin-top: 1.5rem; +} +.bb-severity-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 16px; + margin-top: 1rem; +} +.bb-severity-card { + text-align: center; + padding: 20px; + background: var(--bb-bg); + border-radius: 12px; + border: 2px solid var(--bb-border); + transition: all 0.3s; +} +.bb-severity-card:hover { + transform: translateY(-4px); +} +.bb-severity-card.critical { + border-color: var(--bb-red); +} +.bb-severity-card.critical .bb-severity-icon { + color: var(--bb-red); +} +.bb-severity-card.high { + border-color: var(--bb-orange); +} +.bb-severity-card.high .bb-severity-icon { + color: var(--bb-orange); +} +.bb-severity-card.medium { + border-color: #eab308; +} +.bb-severity-card.medium .bb-severity-icon { + color: #eab308; +} +.bb-severity-card.low { + border-color: var(--bb-blue); +} +.bb-severity-card.low .bb-severity-icon { + color: var(--bb-blue); +} +.bb-severity-icon { + font-size: 2.5rem; + margin-bottom: 8px; +} +.bb-severity-name { + font-size: 16px; + font-weight: 700; + color: var(--bb-text); + margin-bottom: 4px; +} +.bb-severity-desc { + font-size: 12px; + color: var(--bb-text-muted); +} +.bb-scope-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 16px; + margin-top: 1rem; +} +.bb-scope-item { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px; + background: var(--bb-bg); + border-radius: 10px; + border: 1px solid var(--bb-border); +} +.bb-scope-item.in-scope { + border-color: rgba(34, 197, 94, 0.5); + background: rgba(34, 197, 94, 0.05); +} +.bb-scope-item.out-scope { + border-color: rgba(239, 68, 68, 0.5); + background: rgba(239, 68, 68, 0.05); +} +.bb-scope-icon { + font-size: 24px; + flex-shrink: 0; +} +.bb-scope-content { + flex: 1; +} +.bb-scope-title { + font-size: 14px; + font-weight: 600; + color: var(--bb-text); + margin-bottom: 4px; +} +.bb-scope-desc { + font-size: 12px; + color: var(--bb-text-muted); +} +.bb-rewards-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 16px; + margin-top: 1rem; +} +.bb-reward { + text-align: center; + padding: 20px; + background: rgba(34, 197, 94, 0.1); + border-radius: 12px; + border: 1px solid rgba(34, 197, 94, 0.3); +} +.bb-reward-icon { + font-size: 2rem; + margin-bottom: 8px; +} +.bb-reward-name { + font-size: 14px; + font-weight: 600; + color: var(--bb-green); + margin-bottom: 4px; +} +.bb-reward-desc { + font-size: 12px; + color: var(--bb-text-muted); +} +.bb-timeline { + margin-top: 1rem; +} +.bb-timeline-item { + display: flex; + gap: 16px; + padding: 16px 0; + border-bottom: 1px solid var(--bb-border); +} +.bb-timeline-item:last-child { + border-bottom: none; +} +.bb-timeline-time { + font-family: 'JetBrains Mono', monospace; + font-size: 14px; + font-weight: 700; + color: var(--bb-green); + min-width: 80px; +} +.bb-timeline-desc { + font-size: 14px; + color: var(--bb-text-muted); +} +.bb-contact-card { + display: flex; + align-items: center; + gap: 16px; + padding: 20px; + background: var(--bb-bg); + border-radius: 12px; + border: 1px solid var(--bb-border); + margin-top: 1rem; +} +.bb-contact-icon { + font-size: 2.5rem; +} +.bb-contact-content { + flex: 1; +} +.bb-contact-title { + font-size: 16px; + font-weight: 700; + color: var(--bb-text); + margin-bottom: 4px; +} +.bb-contact-value { + font-family: 'JetBrains Mono', monospace; + font-size: 14px; + color: var(--bb-cyan); +} +.bb-public-footer { + text-align: center; + margin-top: 3rem; + padding-top: 2rem; + border-top: 1px solid var(--bb-border); + color: var(--bb-text-dim); +} +.bb-public-footer a { + color: var(--bb-cyan); + text-decoration: none; +} +@media (max-width: 768px) { + .bb-public-title { font-size: 2rem; } + .bb-contact-card { flex-direction: column; text-align: center; } +} + `; + document.head.appendChild(style); + + return E('div', { 'class': 'bb-public-page' }, [ + E('div', { 'class': 'bb-public-container' }, [ + // Header + E('div', { 'class': 'bb-public-header' }, [ + E('div', { 'class': 'bb-public-logo' }, '\ud83d\udc1b'), + E('h1', { 'class': 'bb-public-title' }, 'SecuBox Bug Bounty'), + E('p', { 'class': 'bb-public-subtitle' }, + 'Aidez-nous \u00e0 am\u00e9liorer la s\u00e9curit\u00e9 de SecuBox. Programme de divulgation responsable pour les chercheurs en s\u00e9curit\u00e9.') + ]), + + // Introduction + E('div', { 'class': 'bb-public-card' }, [ + E('h3', {}, ['\ud83d\udee1\ufe0f ', 'Programme de S\u00e9curit\u00e9']), + E('p', {}, + 'SecuBox est un projet open-source d\u00e9di\u00e9 \u00e0 la s\u00e9curit\u00e9 r\u00e9seau. ' + + 'Nous encourageons les chercheurs en s\u00e9curit\u00e9 \u00e0 examiner notre code ' + + 'et \u00e0 signaler les vuln\u00e9rabilit\u00e9s de mani\u00e8re responsable.'), + E('p', {}, + 'En tant que projet open-source sous licence Apache-2.0, nous croyons en la transparence ' + + 'et la collaboration communautaire pour am\u00e9liorer la s\u00e9curit\u00e9 de notre plateforme.') + ]), + + // Severity Levels + E('div', { 'class': 'bb-public-card' }, [ + E('h3', {}, ['\ud83d\udea8 ', 'Niveaux de S\u00e9v\u00e9rit\u00e9']), + E('p', {}, 'Classification des vuln\u00e9rabilit\u00e9s selon leur impact potentiel :'), + E('div', { 'class': 'bb-severity-grid' }, [ + E('div', { 'class': 'bb-severity-card critical' }, [ + E('div', { 'class': 'bb-severity-icon' }, '\ud83d\udd34'), + E('div', { 'class': 'bb-severity-name' }, 'Critique'), + E('div', { 'class': 'bb-severity-desc' }, 'RCE, Auth Bypass, Root Access') + ]), + E('div', { 'class': 'bb-severity-card high' }, [ + E('div', { 'class': 'bb-severity-icon' }, '\ud83d\udfe0'), + E('div', { 'class': 'bb-severity-name' }, '\u00c9lev\u00e9e'), + E('div', { 'class': 'bb-severity-desc' }, 'Injection SQL, XSS Stock\u00e9') + ]), + E('div', { 'class': 'bb-severity-card medium' }, [ + E('div', { 'class': 'bb-severity-icon' }, '\ud83d\udfe1'), + E('div', { 'class': 'bb-severity-name' }, 'Moyenne'), + E('div', { 'class': 'bb-severity-desc' }, 'XSS Refl\u00e9chi, CSRF, Info Leak') + ]), + E('div', { 'class': 'bb-severity-card low' }, [ + E('div', { 'class': 'bb-severity-icon' }, '\ud83d\udfe2'), + E('div', { 'class': 'bb-severity-name' }, 'Faible'), + E('div', { 'class': 'bb-severity-desc' }, 'D\u00e9faut de config, Headers') + ]) + ]) + ]), + + // Scope + E('div', { 'class': 'bb-public-card' }, [ + E('h3', {}, ['\ud83c\udfaf ', 'P\u00e9rim\u00e8tre']), + E('p', {}, 'Composants inclus et exclus du programme :'), + E('div', { 'class': 'bb-scope-grid' }, [ + // In Scope + E('div', { 'class': 'bb-scope-item in-scope' }, [ + E('div', { 'class': 'bb-scope-icon' }, '\u2705'), + E('div', { 'class': 'bb-scope-content' }, [ + E('div', { 'class': 'bb-scope-title' }, 'Interface LuCI SecuBox'), + E('div', { 'class': 'bb-scope-desc' }, 'Toutes les applications web du portail') + ]) + ]), + E('div', { 'class': 'bb-scope-item in-scope' }, [ + E('div', { 'class': 'bb-scope-icon' }, '\u2705'), + E('div', { 'class': 'bb-scope-content' }, [ + E('div', { 'class': 'bb-scope-title' }, 'Scripts RPCD & Backends'), + E('div', { 'class': 'bb-scope-desc' }, 'API JSON-RPC et scripts shell') + ]) + ]), + E('div', { 'class': 'bb-scope-item in-scope' }, [ + E('div', { 'class': 'bb-scope-icon' }, '\u2705'), + E('div', { 'class': 'bb-scope-content' }, [ + E('div', { 'class': 'bb-scope-title' }, 'Configuration UCI'), + E('div', { 'class': 'bb-scope-desc' }, 'Fichiers de configuration syst\u00e8me') + ]) + ]), + E('div', { 'class': 'bb-scope-item in-scope' }, [ + E('div', { 'class': 'bb-scope-icon' }, '\u2705'), + E('div', { 'class': 'bb-scope-content' }, [ + E('div', { 'class': 'bb-scope-title' }, 'Services R\u00e9seau'), + E('div', { 'class': 'bb-scope-desc' }, 'CrowdSec, firewall, VPN WireGuard') + ]) + ]), + E('div', { 'class': 'bb-scope-item in-scope' }, [ + E('div', { 'class': 'bb-scope-icon' }, '\u2705'), + E('div', { 'class': 'bb-scope-content' }, [ + E('div', { 'class': 'bb-scope-title' }, 'Authentification'), + E('div', { 'class': 'bb-scope-desc' }, 'Portail captif, sessions, OAuth') + ]) + ]), + // Out of Scope + E('div', { 'class': 'bb-scope-item out-scope' }, [ + E('div', { 'class': 'bb-scope-icon' }, '\u274c'), + E('div', { 'class': 'bb-scope-content' }, [ + E('div', { 'class': 'bb-scope-title' }, 'Services Tiers'), + E('div', { 'class': 'bb-scope-desc' }, 'OpenWrt core, Netdata, CrowdSec agent') + ]) + ]), + E('div', { 'class': 'bb-scope-item out-scope' }, [ + E('div', { 'class': 'bb-scope-icon' }, '\u274c'), + E('div', { 'class': 'bb-scope-content' }, [ + E('div', { 'class': 'bb-scope-title' }, 'DoS / DDoS'), + E('div', { 'class': 'bb-scope-desc' }, 'Attaques par d\u00e9ni de service') + ]) + ]), + E('div', { 'class': 'bb-scope-item out-scope' }, [ + E('div', { 'class': 'bb-scope-icon' }, '\u274c'), + E('div', { 'class': 'bb-scope-content' }, [ + E('div', { 'class': 'bb-scope-title' }, 'Social Engineering'), + E('div', { 'class': 'bb-scope-desc' }, 'Phishing, manipulation humaine') + ]) + ]) + ]) + ]), + + // Rewards + E('div', { 'class': 'bb-public-card' }, [ + E('h3', {}, ['\ud83c\udfc6 ', 'Reconnaissance']), + E('p', {}, + 'En tant que projet open-source, nous ne pouvons pas offrir de r\u00e9compenses mon\u00e9taires. ' + + 'Cependant, nous reconnaissons les contributeurs de plusieurs fa\u00e7ons :'), + E('div', { 'class': 'bb-rewards-grid' }, [ + E('div', { 'class': 'bb-reward' }, [ + E('div', { 'class': 'bb-reward-icon' }, '\ud83c\udf1f'), + E('div', { 'class': 'bb-reward-name' }, 'Hall of Fame'), + E('div', { 'class': 'bb-reward-desc' }, 'Mention sur notre page de remerciements') + ]), + E('div', { 'class': 'bb-reward' }, [ + E('div', { 'class': 'bb-reward-icon' }, '\ud83d\udcdd'), + E('div', { 'class': 'bb-reward-name' }, 'Credit CVE'), + E('div', { 'class': 'bb-reward-desc' }, 'Attribution dans les CVE publi\u00e9es') + ]), + E('div', { 'class': 'bb-reward' }, [ + E('div', { 'class': 'bb-reward-icon' }, '\ud83e\udd1d'), + E('div', { 'class': 'bb-reward-name' }, 'Contribution'), + E('div', { 'class': 'bb-reward-desc' }, 'Mention comme contributeur GitHub') + ]), + E('div', { 'class': 'bb-reward' }, [ + E('div', { 'class': 'bb-reward-icon' }, '\ud83c\udf81'), + E('div', { 'class': 'bb-reward-name' }, 'Swag SecuBox'), + E('div', { 'class': 'bb-reward-desc' }, 'Stickers et goodies exclusifs') + ]) + ]) + ]), + + // Timeline + E('div', { 'class': 'bb-public-card' }, [ + E('h3', {}, ['\u23f1\ufe0f ', 'D\u00e9lais de R\u00e9ponse']), + E('p', {}, 'Notre engagement envers les chercheurs en s\u00e9curit\u00e9 :'), + E('div', { 'class': 'bb-timeline' }, [ + E('div', { 'class': 'bb-timeline-item' }, [ + E('div', { 'class': 'bb-timeline-time' }, '< 24h'), + E('div', { 'class': 'bb-timeline-desc' }, 'Accus\u00e9 de r\u00e9ception de votre rapport') + ]), + E('div', { 'class': 'bb-timeline-item' }, [ + E('div', { 'class': 'bb-timeline-time' }, '< 48h'), + E('div', { 'class': 'bb-timeline-desc' }, 'Triage initial et \u00e9valuation de la s\u00e9v\u00e9rit\u00e9') + ]), + E('div', { 'class': 'bb-timeline-item' }, [ + E('div', { 'class': 'bb-timeline-time' }, '< 7 jours'), + E('div', { 'class': 'bb-timeline-desc' }, 'Correction des vuln\u00e9rabilit\u00e9s critiques') + ]), + E('div', { 'class': 'bb-timeline-item' }, [ + E('div', { 'class': 'bb-timeline-time' }, '< 30 jours'), + E('div', { 'class': 'bb-timeline-desc' }, 'Correction des vuln\u00e9rabilit\u00e9s non-critiques') + ]), + E('div', { 'class': 'bb-timeline-item' }, [ + E('div', { 'class': 'bb-timeline-time' }, '90 jours'), + E('div', { 'class': 'bb-timeline-desc' }, 'Divulgation coordonn\u00e9e (si d\'accord)') + ]) + ]) + ]), + + // How to Report + E('div', { 'class': 'bb-public-card' }, [ + E('h3', {}, ['\ud83d\udce7 ', 'Comment Signaler']), + E('p', {}, 'Pour signaler une vuln\u00e9rabilit\u00e9, veuillez inclure :'), + E('ul', {}, [ + E('li', {}, E('strong', {}, 'Description'), ' - Explication claire de la vuln\u00e9rabilit\u00e9'), + E('li', {}, E('strong', {}, '\u00c9tapes de reproduction'), ' - Guide d\u00e9taill\u00e9 pour reproduire le probl\u00e8me'), + E('li', {}, E('strong', {}, 'Impact'), ' - Cons\u00e9quences potentielles de l\'exploitation'), + E('li', {}, E('strong', {}, 'Version affect\u00e9e'), ' - Version de SecuBox concern\u00e9e'), + E('li', {}, E('strong', {}, 'Suggestion de correction'), ' (optionnel) - Si vous avez une solution') + ]), + E('div', { 'class': 'bb-contact-card' }, [ + E('div', { 'class': 'bb-contact-icon' }, '\ud83d\udce8'), + E('div', { 'class': 'bb-contact-content' }, [ + E('div', { 'class': 'bb-contact-title' }, 'Email S\u00e9curit\u00e9'), + E('div', { 'class': 'bb-contact-value' }, 'devel@cybermind.fr') + ]) + ]), + E('div', { 'class': 'bb-contact-card' }, [ + E('div', { 'class': 'bb-contact-icon' }, '\ud83d\udcc1'), + E('div', { 'class': 'bb-contact-content' }, [ + E('div', { 'class': 'bb-contact-title' }, 'GitHub Security Advisory'), + E('div', { 'class': 'bb-contact-value' }, 'Issue priv\u00e9e sur le repo SecuBox') + ]) + ]) + ]), + + // CTA + E('div', { 'class': 'bb-public-card' }, [ + E('h3', {}, ['\ud83d\ude80 ', 'Commencer']), + E('p', {}, + 'Explorez notre code source et consultez l\'\u00e9tat de d\u00e9veloppement pour identifier ' + + 'les zones n\u00e9cessitant une attention particuli\u00e8re.'), + E('div', { 'class': 'bb-public-actions' }, [ + E('a', { + 'class': 'bb-public-btn', + 'href': 'https://github.com/CyberMind-FR/secubox-openwrt', + 'target': '_blank' + }, ['\ud83d\udcc1 ', 'Code Source GitHub']), + E('a', { + 'class': 'bb-public-btn bb-public-btn-secondary', + 'href': '/luci-static/secubox/index.html#modules' + }, ['\ud83d\udcda ', 'Documentation']), + E('a', { + 'class': 'bb-public-btn bb-public-btn-secondary', + 'href': '/cgi-bin/luci/' + }, ['\ud83d\udd12 ', 'Se Connecter']) + ]) + ]), + + // Footer + E('div', { 'class': 'bb-public-footer' }, [ + E('p', {}, [ + E('a', { 'href': 'https://cybermood.eu' }, 'CyberMood.eu'), + ' \u00a9 2025 ', + E('a', { 'href': 'https://cybermind.fr' }, 'CyberMind.fr') + ]), + E('p', {}, 'Open Source Apache-2.0 \u2014 \ud83c\uddeb\ud83c\uddf7 Made in France with \u2764\ufe0f') + ]) + ]) + ]); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/crowdfunding.js b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/crowdfunding.js new file mode 100644 index 00000000..90fb5941 --- /dev/null +++ b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/crowdfunding.js @@ -0,0 +1,655 @@ +'use strict'; +'require view'; +'require dom'; + +/** + * SecuBox Campagne Participative - Public Page + * Accessible without authentication + * Content from campaign.html + */ + +return view.extend({ + title: _('SecuBox - Campagne Participative'), + + render: function() { + // Inject CSS for public page + var style = document.createElement('style'); + style.textContent = ` +:root { + --sb-bg: #0a0a12; + --sb-bg-secondary: #0f1019; + --sb-bg-card: #1a1a24; + --sb-border: #2a2a3a; + --sb-text: #f1f5f9; + --sb-text-muted: #94a3b8; + --sb-text-dim: #64748b; + --sb-green: #10b981; + --sb-cyan: #06b6d4; + --sb-blue: #3b82f6; + --sb-purple: #8b5cf6; + --sb-orange: #f97316; + --sb-red: #ef4444; + --sb-gradient: linear-gradient(135deg, #10b981, #06b6d4, #3b82f6); +} +.sb-public-page { + min-height: 100vh; + background: linear-gradient(135deg, var(--sb-bg) 0%, var(--sb-bg-secondary) 100%); + color: var(--sb-text); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + padding: 2rem; +} +.sb-public-container { + max-width: 1000px; + margin: 0 auto; +} +.sb-public-header { + text-align: center; + margin-bottom: 2rem; +} +.sb-public-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + background: rgba(249,115,22,0.2); + border: 1px solid rgba(249,115,22,0.5); + border-radius: 30px; + font-size: 14px; + font-weight: 700; + color: var(--sb-orange); + margin-bottom: 16px; +} +.sb-public-badge-pulse { + width: 8px; + height: 8px; + background: var(--sb-orange); + border-radius: 50%; + animation: sbPulse 2s infinite; +} +@keyframes sbPulse { + 0%,100% { box-shadow: 0 0 0 0 rgba(249,115,22,0.4); } + 50% { box-shadow: 0 0 0 12px rgba(249,115,22,0); } +} +.sb-public-title { + font-size: 2.5rem; + font-weight: 800; + background: var(--sb-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 0.5rem; +} +.sb-public-subtitle { + font-size: 1.1rem; + color: var(--sb-text-muted); + max-width: 600px; + margin: 0 auto; +} +.sb-public-card { + background: var(--sb-bg-card); + border: 1px solid var(--sb-border); + border-radius: 16px; + padding: 2rem; + margin-bottom: 1.5rem; +} +.sb-public-card h3 { + color: var(--sb-cyan); + font-size: 1.3rem; + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; +} +.sb-public-card p { + color: var(--sb-text-muted); + line-height: 1.7; + margin-bottom: 1rem; +} +.sb-public-btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 12px 24px; + background: var(--sb-gradient); + color: white; + border-radius: 10px; + text-decoration: none; + font-weight: 700; + transition: transform 0.2s, box-shadow 0.2s; +} +.sb-public-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(16,185,129,0.4); +} +.sb-public-btn-secondary { + background: var(--sb-bg-card); + border: 2px solid var(--sb-border); +} +.sb-public-btn-secondary:hover { + border-color: var(--sb-cyan); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} +.sb-public-actions { + display: flex; + gap: 1rem; + flex-wrap: wrap; + margin-top: 1.5rem; +} +.sb-progress-card { + background: var(--sb-bg-card); + border: 1px solid var(--sb-border); + border-radius: 20px; + padding: 2rem; + margin-bottom: 2rem; +} +.sb-progress-header { + display: flex; + justify-content: space-between; + align-items: flex-end; + margin-bottom: 16px; +} +.sb-progress-amount { + font-size: 2.5rem; + font-weight: 800; + font-family: 'JetBrains Mono', monospace; + color: var(--sb-green); +} +.sb-progress-goal { + font-size: 16px; + color: var(--sb-text-muted); +} +.sb-progress-percent { + font-size: 24px; + font-weight: 700; + color: var(--sb-orange); +} +.sb-progress-bar { + height: 16px; + background: var(--sb-bg); + border-radius: 8px; + overflow: hidden; + margin-bottom: 16px; +} +.sb-progress-fill { + height: 100%; + background: var(--sb-gradient); + border-radius: 8px; + transition: width 1s ease-out; +} +.sb-progress-stats { + display: flex; + justify-content: space-between; + font-size: 14px; + color: var(--sb-text-muted); +} +.sb-progress-notice { + margin-top: 20px; + padding: 16px; + background: rgba(249,115,22,0.1); + border-radius: 10px; + font-size: 14px; + color: var(--sb-orange); + text-align: center; +} +.sb-stats-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin-bottom: 2rem; +} +.sb-stat { + text-align: center; + padding: 20px; + background: var(--sb-bg-card); + border: 1px solid var(--sb-border); + border-radius: 12px; +} +.sb-stat-value { + font-size: 2rem; + font-weight: 800; + font-family: 'JetBrains Mono', monospace; + color: var(--sb-green); +} +.sb-stat-label { + font-size: 13px; + color: var(--sb-text-muted); + margin-top: 4px; +} +.sb-rewards-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; + margin-top: 1rem; +} +.sb-reward-card { + background: var(--sb-bg); + border: 2px solid var(--sb-border); + border-radius: 16px; + padding: 24px; + transition: all 0.3s; + position: relative; +} +.sb-reward-card:hover { + transform: translateY(-4px); + border-color: var(--sb-cyan); +} +.sb-reward-card.popular { + border-color: var(--sb-green); +} +.sb-reward-popular-badge { + position: absolute; + top: -12px; + left: 20px; + background: var(--sb-gradient); + color: white; + font-size: 11px; + font-weight: 800; + padding: 6px 14px; + border-radius: 20px; +} +.sb-reward-icon { + font-size: 36px; + margin-bottom: 12px; +} +.sb-reward-price { + font-family: 'JetBrains Mono', monospace; + margin-bottom: 8px; +} +.sb-reward-price .amount { + font-size: 32px; + font-weight: 800; + color: var(--sb-green); +} +.sb-reward-price .currency { + font-size: 18px; + color: var(--sb-text-muted); +} +.sb-reward-name { + font-size: 18px; + font-weight: 700; + margin-bottom: 8px; + color: var(--sb-text); +} +.sb-reward-desc { + font-size: 13px; + color: var(--sb-text-muted); + margin-bottom: 16px; +} +.sb-reward-includes { + list-style: none; + padding: 0; + margin: 0 0 16px 0; +} +.sb-reward-includes li { + font-size: 13px; + color: var(--sb-text-muted); + padding: 8px 0; + display: flex; + align-items: flex-start; + gap: 8px; + border-bottom: 1px solid var(--sb-border); +} +.sb-reward-includes li:last-child { + border-bottom: none; +} +.sb-reward-includes li::before { + content: '\\2713'; + color: var(--sb-green); + font-weight: 700; + flex-shrink: 0; +} +.sb-why-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; + margin-top: 1rem; +} +.sb-why-card { + background: var(--sb-bg); + border: 1px solid var(--sb-border); + border-radius: 12px; + padding: 24px; + text-align: center; + transition: all 0.3s; +} +.sb-why-card:hover { + transform: translateY(-4px); + border-color: var(--sb-green); +} +.sb-why-icon { + font-size: 36px; + margin-bottom: 12px; +} +.sb-why-title { + font-size: 16px; + font-weight: 700; + margin-bottom: 8px; + color: var(--sb-text); +} +.sb-why-desc { + font-size: 13px; + color: var(--sb-text-muted); + line-height: 1.5; +} +.sb-team-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 20px; + margin-top: 1rem; +} +.sb-team-card { + background: var(--sb-bg); + border: 1px solid var(--sb-border); + border-radius: 12px; + padding: 24px; + text-align: center; +} +.sb-team-avatar { + width: 80px; + height: 80px; + background: var(--sb-gradient); + border-radius: 50%; + margin: 0 auto 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 40px; +} +.sb-team-name { + font-size: 18px; + font-weight: 700; + margin-bottom: 4px; +} +.sb-team-role { + font-size: 13px; + color: var(--sb-cyan); + margin-bottom: 12px; +} +.sb-team-bio { + font-size: 13px; + color: var(--sb-text-muted); + line-height: 1.5; +} +.sb-public-footer { + text-align: center; + margin-top: 3rem; + padding-top: 2rem; + border-top: 1px solid var(--sb-border); + color: var(--sb-text-dim); +} +.sb-public-footer a { + color: var(--sb-cyan); + text-decoration: none; +} +@media (max-width: 768px) { + .sb-stats-grid { grid-template-columns: 1fr; } + .sb-progress-header { flex-direction: column; align-items: flex-start; gap: 8px; } + .sb-public-title { font-size: 2rem; } +} + `; + document.head.appendChild(style); + + return E('div', { 'class': 'sb-public-page' }, [ + E('div', { 'class': 'sb-public-container' }, [ + // Header + E('div', { 'class': 'sb-public-header' }, [ + E('div', { 'class': 'sb-public-badge' }, [ + E('span', { 'class': 'sb-public-badge-pulse' }), + E('span', {}, 'Campagne Participative \u2014 Lancement Q2 2026') + ]), + E('h1', { 'class': 'sb-public-title' }, 'Soutenez SecuBox 1.0'), + E('p', { 'class': 'sb-public-subtitle' }, + 'L\'appliance de cybers\u00e9curit\u00e9 100% open source qui embarque wizard, profils et App Store sur OpenWrt 24.10.') + ]), + + // Stats + E('div', { 'class': 'sb-stats-grid' }, [ + E('div', { 'class': 'sb-stat' }, [ + E('div', { 'class': 'sb-stat-value' }, '20'), + E('div', { 'class': 'sb-stat-label' }, 'Modules & Apps') + ]), + E('div', { 'class': 'sb-stat' }, [ + E('div', { 'class': 'sb-stat-value' }, '9'), + E('div', { 'class': 'sb-stat-label' }, 'Architectures') + ]), + E('div', { 'class': 'sb-stat' }, [ + E('div', { 'class': 'sb-stat-value' }, '100%'), + E('div', { 'class': 'sb-stat-label' }, 'Open Source') + ]) + ]), + + // Progress + E('div', { 'class': 'sb-progress-card' }, [ + E('div', { 'class': 'sb-progress-header' }, [ + E('div', {}, [ + E('div', { 'class': 'sb-progress-amount' }, '0 \u20ac'), + E('div', { 'class': 'sb-progress-goal' }, 'sur 50 000 \u20ac objectif') + ]), + E('div', { 'style': 'text-align: right;' }, [ + E('div', { 'class': 'sb-progress-percent' }, '0%'), + E('div', { 'style': 'font-size: 13px; color: #94a3b8;' }, 'financ\u00e9') + ]) + ]), + E('div', { 'class': 'sb-progress-bar' }, [ + E('div', { 'class': 'sb-progress-fill', 'style': 'width: 0%;' }) + ]), + E('div', { 'class': 'sb-progress-stats' }, [ + E('span', {}, E('strong', {}, '0'), ' contributeurs'), + E('span', {}, 'Lancement dans ', E('strong', {}, '~180 jours'), ' (Q2 2026)') + ]), + E('div', { 'class': 'sb-progress-notice' }, + '\ud83d\udd27 Projet en phase d\'int\u00e9gration hardware. Inscrivez-vous pour \u00eatre notifi\u00e9 du lancement Q2 2026 et b\u00e9n\u00e9ficier des tarifs Early Bird !') + ]), + + // Rewards + E('div', { 'class': 'sb-public-card' }, [ + E('h3', {}, ['\ud83c\udf81 ', 'Contreparties']), + E('p', {}, 'Du simple soutien jusqu\'\u00e0 la SecuBox compl\u00e8te avec hardware GlobalScale et support premium.'), + E('div', { 'class': 'sb-rewards-grid' }, [ + // Supporter + E('div', { 'class': 'sb-reward-card' }, [ + E('div', { 'class': 'sb-reward-icon' }, '\ud83d\udc9a'), + E('div', { 'class': 'sb-reward-price' }, [ + E('span', { 'class': 'amount' }, '25'), + E('span', { 'class': 'currency' }, '\u20ac') + ]), + E('div', { 'class': 'sb-reward-name' }, 'Supporter'), + E('div', { 'class': 'sb-reward-desc' }, 'Soutenez le projet open source'), + E('ul', { 'class': 'sb-reward-includes' }, [ + E('li', {}, 'Nom dans les remerciements GitHub'), + E('li', {}, 'Badge Discord "Early Supporter"'), + E('li', {}, 'Newsletter exclusive'), + E('li', {}, 'Stickers SecuBox (x3)') + ]) + ]), + // Software Edition + E('div', { 'class': 'sb-reward-card' }, [ + E('div', { 'class': 'sb-reward-icon' }, '\ud83d\udcbe'), + E('div', { 'class': 'sb-reward-price' }, [ + E('span', { 'class': 'amount' }, '49'), + E('span', { 'class': 'currency' }, '\u20ac') + ]), + E('div', { 'class': 'sb-reward-name' }, 'Software Edition'), + E('div', { 'class': 'sb-reward-desc' }, 'Tous les modules pour votre mat\u00e9riel'), + E('ul', { 'class': 'sb-reward-includes' }, [ + E('li', {}, '20 modules & apps pr\u00e9compil\u00e9s'), + E('li', {}, 'Image OpenWrt personnalis\u00e9e'), + E('li', {}, 'Guide d\'installation complet'), + E('li', {}, 'Support Discord prioritaire') + ]) + ]), + // SecuBox Lite (Popular) + E('div', { 'class': 'sb-reward-card popular' }, [ + E('div', { 'class': 'sb-reward-popular-badge' }, '\u2b50 POPULAIRE'), + E('div', { 'class': 'sb-reward-icon' }, '\ud83d\udce6'), + E('div', { 'class': 'sb-reward-price' }, [ + E('span', { 'class': 'amount' }, '199'), + E('span', { 'class': 'currency' }, '\u20ac') + ]), + E('div', { 'class': 'sb-reward-name' }, 'SecuBox Lite'), + E('div', { 'class': 'sb-reward-desc' }, 'L\'essentiel sur ESPRESSObin Ultra'), + E('ul', { 'class': 'sb-reward-includes' }, [ + E('li', {}, 'ESPRESSObin Ultra (1GB RAM)'), + E('li', {}, 'SecuBox pr\u00e9install\u00e9e'), + E('li', {}, 'Bo\u00eetier aluminium + alimentation'), + E('li', {}, '1 an support email') + ]) + ]), + // SecuBox Pro + E('div', { 'class': 'sb-reward-card' }, [ + E('div', { 'class': 'sb-reward-icon' }, '\ud83d\ude80'), + E('div', { 'class': 'sb-reward-price' }, [ + E('span', { 'class': 'amount' }, '349'), + E('span', { 'class': 'currency' }, '\u20ac') + ]), + E('div', { 'class': 'sb-reward-name' }, 'SecuBox Pro'), + E('div', { 'class': 'sb-reward-desc' }, 'Performance sur Sheeva64'), + E('ul', { 'class': 'sb-reward-includes' }, [ + E('li', {}, 'Sheeva64 (4GB RAM, 8GB eMMC)'), + E('li', {}, 'WiFi 6 int\u00e9gr\u00e9'), + E('li', {}, '2 ans support email prioritaire'), + E('li', {}, '1 session RustDesk setup') + ]) + ]), + // SecuBox Ultimate + E('div', { 'class': 'sb-reward-card' }, [ + E('div', { 'class': 'sb-reward-icon' }, '\ud83d\udc51'), + E('div', { 'class': 'sb-reward-price' }, [ + E('span', { 'class': 'amount' }, '599'), + E('span', { 'class': 'currency' }, '\u20ac') + ]), + E('div', { 'class': 'sb-reward-name' }, 'SecuBox Ultimate'), + E('div', { 'class': 'sb-reward-desc' }, 'Puissance maximale sur MOCHAbin'), + E('ul', { 'class': 'sb-reward-includes' }, [ + E('li', {}, 'MOCHAbin (8GB RAM, NVMe)'), + E('li', {}, 'Dual 2.5GbE + 10GbE SFP+'), + E('li', {}, '3 ans support premium'), + E('li', {}, 'Abonnement Pro 1 an inclus') + ]) + ]), + // Enterprise + E('div', { 'class': 'sb-reward-card' }, [ + E('div', { 'class': 'sb-reward-icon' }, '\ud83c\udfe2'), + E('div', { 'class': 'sb-reward-price' }, [ + E('span', { 'class': 'amount' }, '1499'), + E('span', { 'class': 'currency' }, '\u20ac') + ]), + E('div', { 'class': 'sb-reward-name' }, 'Pack Enterprise'), + E('div', { 'class': 'sb-reward-desc' }, 'Solution multi-sites pour PME'), + E('ul', { 'class': 'sb-reward-includes' }, [ + E('li', {}, '3x SecuBox Pro (Sheeva64)'), + E('li', {}, 'VPN site-to-site pr\u00e9configur\u00e9'), + E('li', {}, 'Formation visio 2h'), + E('li', {}, 'Support t\u00e9l\u00e9phone 1 an') + ]) + ]) + ]) + ]), + + // Why SecuBox + E('div', { 'class': 'sb-public-card' }, [ + E('h3', {}, ['\ud83d\udca1 ', 'Pourquoi SecuBox ?']), + E('p', {}, 'Une alternative open source aux appliances propri\u00e9taires co\u00fbteuses.'), + E('div', { 'class': 'sb-why-grid' }, [ + E('div', { 'class': 'sb-why-card' }, [ + E('div', { 'class': 'sb-why-icon' }, '\ud83d\udd13'), + E('div', { 'class': 'sb-why-title' }, '100% Open Source'), + E('div', { 'class': 'sb-why-desc' }, 'Code sur GitHub sous licence Apache 2.0. Pas de backdoor.') + ]), + E('div', { 'class': 'sb-why-card' }, [ + E('div', { 'class': 'sb-why-icon' }, '\ud83c\uddeb\ud83c\uddf7'), + E('div', { 'class': 'sb-why-title' }, 'Made in France'), + E('div', { 'class': 'sb-why-desc' }, 'Con\u00e7u par CyberMind. Support fran\u00e7ais, RGPD compliant.') + ]), + E('div', { 'class': 'sb-why-card' }, [ + E('div', { 'class': 'sb-why-icon' }, '\ud83d\udcb0'), + E('div', { 'class': 'sb-why-title' }, 'Prix Juste'), + E('div', { 'class': 'sb-why-desc' }, '\u00c0 partir de 199\u20ac. Logiciel gratuit \u00e0 vie.') + ]), + E('div', { 'class': 'sb-why-card' }, [ + E('div', { 'class': 'sb-why-icon' }, '\ud83d\udee1\ufe0f'), + E('div', { 'class': 'sb-why-title' }, 'S\u00e9curit\u00e9 Pro'), + E('div', { 'class': 'sb-why-desc' }, 'CrowdSec, IDS/IPS, VPN WireGuard, pare-feu avanc\u00e9.') + ]), + E('div', { 'class': 'sb-why-card' }, [ + E('div', { 'class': 'sb-why-icon' }, '\u26a1'), + E('div', { 'class': 'sb-why-title' }, 'Hardware ARM'), + E('div', { 'class': 'sb-why-desc' }, 'Marvell quad-core, jusqu\'\u00e0 8GB RAM, 10GbE. < 15W.') + ]), + E('div', { 'class': 'sb-why-card' }, [ + E('div', { 'class': 'sb-why-icon' }, '\ud83e\udd1d'), + E('div', { 'class': 'sb-why-title' }, 'Communaut\u00e9'), + E('div', { 'class': 'sb-why-desc' }, 'Discord, GitHub, entraide et d\u00e9veloppement collaboratif.') + ]) + ]) + ]), + + // Team + E('div', { 'class': 'sb-public-card' }, [ + E('h3', {}, ['\ud83d\udc65 ', 'L\'\u00c9quipe']), + E('div', { 'class': 'sb-team-grid' }, [ + E('div', { 'class': 'sb-team-card' }, [ + E('div', { 'class': 'sb-team-avatar' }, '\ud83e\uddd9\u200d\u2642\ufe0f'), + E('div', { 'class': 'sb-team-name' }, 'Gandalf'), + E('div', { 'class': 'sb-team-role' }, 'Fondateur & Lead Developer'), + E('div', { 'class': 'sb-team-bio' }, '25+ ans d\'exp\u00e9rience en cybers\u00e9curit\u00e9. Contributeur Linux kernel, ambassadeur CrowdSec.') + ]), + E('div', { 'class': 'sb-team-card' }, [ + E('div', { 'class': 'sb-team-avatar' }, '\ud83c\udf10'), + E('div', { 'class': 'sb-team-name' }, 'GlobalScale'), + E('div', { 'class': 'sb-team-role' }, 'Partenaire Hardware'), + E('div', { 'class': 'sb-team-bio' }, 'Leader mondial des SBC ARM Marvell. Fabricant des ESPRESSObin, Sheeva64 et MOCHAbin.') + ]), + E('div', { 'class': 'sb-team-card' }, [ + E('div', { 'class': 'sb-team-avatar' }, '\ud83d\udee1\ufe0f'), + E('div', { 'class': 'sb-team-name' }, 'CrowdSec'), + E('div', { 'class': 'sb-team-role' }, 'Partenaire S\u00e9curit\u00e9'), + E('div', { 'class': 'sb-team-bio' }, 'Solution fran\u00e7aise de cybers\u00e9curit\u00e9 collaborative. 15M+ d\'IPs malveillantes partag\u00e9es.') + ]) + ]) + ]), + + // CTA + E('div', { 'class': 'sb-public-card' }, [ + E('h3', {}, ['\ud83d\ude80 ', 'Rejoignez l\'Aventure']), + E('p', {}, + 'Inscrivez-vous pour \u00eatre notifi\u00e9 du lancement et b\u00e9n\u00e9ficier des tarifs Early Bird exclusifs (-20%).'), + E('div', { 'class': 'sb-public-actions' }, [ + E('a', { + 'class': 'sb-public-btn', + 'href': 'https://secubox.cybermood.eu', + 'target': '_blank' + }, ['\ud83c\udf10 ', 'Site Officiel']), + E('a', { + 'class': 'sb-public-btn sb-public-btn-secondary', + 'href': 'https://github.com/CyberMind-FR/secubox-openwrt', + 'target': '_blank' + }, ['\ud83d\udcc1 ', 'GitHub']), + E('a', { + 'class': 'sb-public-btn sb-public-btn-secondary', + 'href': '/luci-static/secubox/index.html#modules' + }, ['\ud83d\udcda ', 'Voir les Modules']), + E('a', { + 'class': 'sb-public-btn sb-public-btn-secondary', + 'href': '/cgi-bin/luci/' + }, ['\ud83d\udd12 ', 'Se Connecter']) + ]) + ]), + + // Footer + E('div', { 'class': 'sb-public-footer' }, [ + E('p', {}, [ + E('a', { 'href': 'https://cybermood.eu' }, 'CyberMood.eu'), + ' \u00a9 2025 ', + E('a', { 'href': 'https://cybermind.fr' }, 'CyberMind.fr') + ]), + E('p', {}, 'Open Source Apache-2.0 \u2014 \ud83c\uddeb\ud83c\uddf7 Made in France with \u2764\ufe0f') + ]) + ]) + ]); + }, + + 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 index 650b5ec6..4eb6e136 100644 --- 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 @@ -5,6 +5,9 @@ "action": { "type": "alias", "path": "admin/secubox/portal" + }, + "depends": { + "acl": ["luci-app-secubox-portal"] } }, "admin/secubox/portal": { @@ -13,6 +16,9 @@ "action": { "type": "view", "path": "secubox-portal/index" + }, + "depends": { + "acl": ["luci-app-secubox-portal"] } }, "admin/secubox/apps": { @@ -21,6 +27,9 @@ "action": { "type": "view", "path": "secubox-portal/apps" + }, + "depends": { + "acl": ["luci-app-secubox-portal"] } }, "admin/secubox/settings": { @@ -29,6 +38,9 @@ "action": { "type": "view", "path": "system-hub/settings" + }, + "depends": { + "acl": ["luci-app-secubox-portal"] } }, "admin/secubox-home": { @@ -37,6 +49,32 @@ "action": { "type": "alias", "path": "admin/secubox/portal" + }, + "depends": { + "acl": ["luci-app-secubox-portal"] + } + }, + "secubox-public": { + "title": "SecuBox", + "order": 99, + "action": { + "type": "firstchild" + } + }, + "secubox-public/bugbounty": { + "title": "Bug Bounty", + "order": 10, + "action": { + "type": "view", + "path": "secubox-portal/bugbounty" + } + }, + "secubox-public/crowdfunding": { + "title": "Campagne Participative", + "order": 20, + "action": { + "type": "view", + "path": "secubox-portal/crowdfunding" } } } diff --git a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js index c4eee23b..760a8b42 100644 --- a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js +++ b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js @@ -1,16 +1,30 @@ 'use strict'; 'require baseclass'; -'require secubox-theme/cascade as Cascade'; + +/** + * SecuBox Main Navigation + * SecuBox themed navigation tabs + */ + +// Immediately inject CSS to hide LuCI tabs before page renders +(function() { + if (typeof document === 'undefined') return; + if (document.getElementById('secubox-early-hide')) return; + var style = document.createElement('style'); + style.id = 'secubox-early-hide'; + style.textContent = 'body[data-page^="admin-secubox"] ul.tabs:not(.sb-nav-tabs), body[data-page^="admin-secubox"] .tabs:not(.sb-nav-tabs) { display: none !important; }'; + (document.head || document.documentElement).appendChild(style); +})(); var tabs = [ - { id: 'dashboard', icon: '🚀', label: _('Dashboard'), path: ['admin', 'secubox', 'dashboard'] }, - { id: 'modules', icon: '🧩', label: _('Modules'), path: ['admin', 'secubox', 'modules'] }, + { id: 'dashboard', icon: '📊', label: _('Dashboard'), path: ['admin', 'secubox', 'dashboard'] }, { id: 'wizard', icon: '✨', label: _('Wizard'), path: ['admin', 'secubox', 'wizard'] }, + { id: 'modules', icon: '🧩', label: _('Modules'), path: ['admin', 'secubox', 'modules'] }, { id: 'apps', icon: '🛒', label: _('App Store'), path: ['admin', 'secubox', 'apps'] }, { id: 'monitoring', icon: '📡', label: _('Monitoring'), path: ['admin', 'secubox', 'monitoring'] }, { id: 'alerts', icon: '⚠️', label: _('Alerts'), path: ['admin', 'secubox', 'alerts'] }, { id: 'settings', icon: '⚙️', label: _('Settings'), path: ['admin', 'secubox', 'settings'] }, - { id: 'help', icon: '✨', label: _('Bonus'), path: ['admin', 'secubox', 'help'] } + { id: 'help', icon: '🎁', label: _('Bonus'), path: ['admin', 'secubox', 'help'] } ]; return baseclass.extend({ @@ -18,31 +32,162 @@ return baseclass.extend({ return tabs.slice(); }, - renderTabs: function(active) { - return Cascade.createLayer({ - id: 'secubox-main-nav', - type: 'tabs', - role: 'menu', - depth: 1, - className: 'sh-nav-tabs secubox-nav-tabs', - items: this.getTabs().map(function(tab) { - return { - id: tab.id, - label: tab.label, - icon: tab.icon, - href: L.url.apply(L, tab.path), - state: tab.id === active ? 'active' : null - }; - }), - active: active, - onSelect: function(item, ev) { - if (item.href && ev && (ev.metaKey || ev.ctrlKey)) - return true; - if (item.href) { - location.href = item.href; - return false; - } + ensureLuCITabsHidden: function() { + if (typeof document === 'undefined') + return; + + // Actively remove LuCI tabs from DOM + var luciTabs = document.querySelectorAll('.cbi-tabmenu, ul.tabs, div.tabs, .nav-tabs'); + luciTabs.forEach(function(el) { + // Don't remove our own tabs + if (!el.classList.contains('sb-nav-tabs')) { + el.style.display = 'none'; + // Also try removing from DOM after a brief delay + setTimeout(function() { + if (el.parentNode && !el.classList.contains('sb-nav-tabs')) { + el.style.display = 'none'; + } + }, 100); } }); + + if (document.getElementById('secubox-tabstyle')) + return; + var style = document.createElement('style'); + style.id = 'secubox-tabstyle'; + style.textContent = ` +/* Hide default LuCI tabs for SecuBox - aggressive selectors */ +/* Target any ul.tabs in the page */ +ul.tabs { + display: none !important; +} + +/* Be more specific for pages that need tabs elsewhere */ +body:not([data-page^="admin-secubox"]) ul.tabs { + display: block !important; +} + +/* All possible LuCI tab selectors */ +body[data-page^="admin-secubox-dashboard"] .tabs, +body[data-page^="admin-secubox-modules"] .tabs, +body[data-page^="admin-secubox-wizard"] .tabs, +body[data-page^="admin-secubox-apps"] .tabs, +body[data-page^="admin-secubox-monitoring"] .tabs, +body[data-page^="admin-secubox-alerts"] .tabs, +body[data-page^="admin-secubox-settings"] .tabs, +body[data-page^="admin-secubox-help"] .tabs, +body[data-page^="admin-secubox"] #tabmenu, +body[data-page^="admin-secubox"] .cbi-tabmenu, +body[data-page^="admin-secubox"] .nav-tabs, +body[data-page^="admin-secubox"] ul.cbi-tabmenu, +body[data-page^="admin-secubox"] ul.tabs, +/* Fallback: hide any tabs that appear before our custom nav */ +.secubox-dashboard .tabs, +.secubox-dashboard + .tabs, +.secubox-dashboard ~ .tabs, +.cbi-map > .tabs:first-child, +#maincontent > .container > .tabs, +#maincontent > .container > ul.tabs, +#view > .tabs, +#view > ul.tabs, +.view > .tabs, +.view > ul.tabs, +div.tabs:has(+ .secubox-dashboard), +/* Direct sibling of SecuBox content */ +.sb-nav-tabs ~ .tabs, +/* LuCI 24.x specific */ +.luci-app-secubox .tabs, +#cbi-secubox .tabs { + display: none !important; +} + +/* Hide tabs container when our nav is present */ +.sb-nav-tabs ~ ul.tabs, +.sb-nav-tabs + ul.tabs { + display: none !important; +} + +/* SecuBox Nav Tabs */ +.sb-nav-tabs { + display: flex; + gap: 4px; + margin-bottom: 24px; + padding: 6px; + background: var(--sb-bg-secondary); + border-radius: var(--sb-radius-lg); + border: 1px solid var(--sb-border); + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.sb-nav-tab { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + border-radius: var(--sb-radius); + background: transparent; + border: none; + color: var(--sb-text-secondary); + font-weight: 500; + font-size: 13px; + cursor: pointer; + text-decoration: none; + transition: all 0.2s ease; + white-space: nowrap; +} + +.sb-nav-tab:hover { + color: var(--sb-text-primary); + background: var(--sb-bg-tertiary); +} + +.sb-nav-tab.active { + color: var(--sb-accent); + background: var(--sb-bg-tertiary); + box-shadow: inset 0 -2px 0 var(--sb-accent); +} + +.sb-tab-icon { + font-size: 16px; + line-height: 1; +} + +.sb-tab-label { + font-family: var(--sb-font-sans); +} + +@media (max-width: 768px) { + .sb-nav-tabs { + padding: 4px; + } + .sb-nav-tab { + padding: 8px 12px; + font-size: 12px; + } + .sb-tab-label { + display: none; + } + .sb-tab-icon { + font-size: 18px; + } +} + `; + document.head && document.head.appendChild(style); + }, + + renderTabs: function(active) { + this.ensureLuCITabsHidden(); + return E('div', { 'class': 'sb-nav-tabs' }, + this.getTabs().map(function(tab) { + return E('a', { + 'class': 'sb-nav-tab' + (tab.id === active ? ' active' : ''), + 'href': L.url.apply(L, tab.path) + }, [ + E('span', { 'class': 'sb-tab-icon' }, tab.icon), + E('span', { 'class': 'sb-tab-label' }, tab.label) + ]); + }) + ); } }); diff --git a/package/secubox/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json b/package/secubox/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json index bbc7f1e6..fae2a9b4 100644 --- a/package/secubox/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json +++ b/package/secubox/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json @@ -4,6 +4,9 @@ "order": 25, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/dashboard": { @@ -12,6 +15,9 @@ "action": { "type": "view", "path": "secubox/dashboard" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/wizard": { @@ -20,6 +26,9 @@ "action": { "type": "view", "path": "secubox/wizard" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/apps": { @@ -28,6 +37,9 @@ "action": { "type": "view", "path": "secubox/apps" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/modules": { @@ -36,6 +48,9 @@ "action": { "type": "view", "path": "secubox/modules" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/alerts": { @@ -44,6 +59,9 @@ "action": { "type": "view", "path": "secubox/alerts" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/settings": { @@ -52,6 +70,9 @@ "action": { "type": "view", "path": "secubox/settings" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/security": { @@ -59,6 +80,9 @@ "order": 30, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/monitoring": { @@ -67,6 +91,9 @@ "action": { "type": "view", "path": "secubox/monitoring" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/network": { @@ -74,6 +101,9 @@ "order": 40, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/network/cdn-cache": { @@ -83,9 +113,7 @@ "type": "firstchild" }, "depends": { - "acl": [ - "luci-app-cdn-cache" - ] + "acl": ["luci-app-secubox", "luci-app-cdn-cache"] } }, "admin/secubox/network/cdn-cache/overview": { @@ -141,6 +169,9 @@ "order": 50, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/services": { @@ -148,6 +179,9 @@ "order": 60, "action": { "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/help": { @@ -156,6 +190,9 @@ "action": { "type": "view", "path": "secubox/help" + }, + "depends": { + "acl": ["luci-app-secubox"] } }, "admin/secubox/network/network-modes": { @@ -165,9 +202,7 @@ "type": "firstchild" }, "depends": { - "acl": [ - "luci-app-network-modes" - ] + "acl": ["luci-app-secubox", "luci-app-network-modes"] } }, "admin/secubox/network/network-modes/overview": { diff --git a/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/nav.js b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/nav.js index a654f43e..cdf0ca9d 100644 --- a/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/nav.js +++ b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/nav.js @@ -1,6 +1,21 @@ 'use strict'; 'require baseclass'; +/** + * System Hub Navigation + * SecuBox themed navigation tabs + */ + +// Immediately inject CSS to hide LuCI tabs before page renders +(function() { + if (typeof document === 'undefined') return; + if (document.getElementById('system-hub-early-hide')) return; + var style = document.createElement('style'); + style.id = 'system-hub-early-hide'; + style.textContent = 'body[data-page*="system-hub"] ul.tabs, body[data-page*="system-hub"] .tabs:not(.sh-nav-tabs) { display: none !important; }'; + (document.head || document.documentElement).appendChild(style); +})(); + var tabs = [ { id: 'overview', icon: '📊', label: _('Overview'), path: ['admin', 'secubox', 'system', 'system-hub', 'overview'] }, { id: 'services', icon: '🧩', label: _('Services'), path: ['admin', 'secubox', 'system', 'system-hub', 'services'] }, @@ -9,11 +24,9 @@ var tabs = [ { id: 'components', icon: '🧱', label: _('Components'), path: ['admin', 'secubox', 'system', 'system-hub', 'components'] }, { id: 'diagnostics', icon: '🧪', label: _('Diagnostics'), path: ['admin', 'secubox', 'system', 'system-hub', 'diagnostics'] }, { id: 'health', icon: '❤️', label: _('Health'), path: ['admin', 'secubox', 'system', 'system-hub', 'health'] }, + { id: 'debug', icon: '🐛', label: _('Debug'), path: ['admin', 'secubox', 'system', 'system-hub', 'debug'] }, { id: 'remote', icon: '📡', label: _('Remote'), path: ['admin', 'secubox', 'system', 'system-hub', 'remote'] }, - { id: 'dev-status', icon: '🚀', label: _('Dev Status'), path: ['admin', 'secubox', 'system', 'system-hub', 'dev-status'] }, - { id: 'settings', icon: '⚙️', label: _('Settings'), path: ['admin', 'secubox', 'system', 'system-hub', 'settings'] }, - { id: 'network-modes', icon: '🌐', label: _('Network Modes'), path: ['admin', 'secubox', 'network', 'modes', 'overview'] }, - { id: 'cdn-cache', icon: '📦', label: _('CDN Cache'), path: ['admin', 'secubox', 'network', 'cdn-cache', 'overview'] } + { id: 'settings', icon: '⚙️', label: _('Settings'), path: ['admin', 'secubox', 'system', 'system-hub', 'settings'] } ]; return baseclass.extend({ @@ -24,24 +37,144 @@ return baseclass.extend({ ensureLuCITabsHidden: function() { if (typeof document === 'undefined') return; + + // Actively remove LuCI tabs from DOM + var luciTabs = document.querySelectorAll('.cbi-tabmenu, ul.tabs, div.tabs, .nav-tabs'); + luciTabs.forEach(function(el) { + // Don't remove our own tabs + if (!el.classList.contains('sh-nav-tabs')) { + el.style.display = 'none'; + // Also try removing from DOM after a brief delay + setTimeout(function() { + if (el.parentNode && !el.classList.contains('sh-nav-tabs')) { + el.style.display = 'none'; + } + }, 100); + } + }); + if (document.getElementById('system-hub-tabstyle')) return; var style = document.createElement('style'); style.id = 'system-hub-tabstyle'; style.textContent = ` +/* Hide default LuCI tabs for System Hub - aggressive selectors */ +/* Target any ul.tabs in the page */ +ul.tabs { + display: none !important; +} + +/* Be more specific for pages that need tabs elsewhere */ +body:not([data-page*="system-hub"]) ul.tabs { + display: block !important; +} + +/* All possible LuCI tab selectors */ body[data-page^="admin-secubox-system-system-hub"] .tabs, body[data-page^="admin-secubox-system-system-hub"] #tabmenu, body[data-page^="admin-secubox-system-system-hub"] .cbi-tabmenu, -body[data-page^="admin-secubox-system-system-hub"] .nav-tabs { +body[data-page^="admin-secubox-system-system-hub"] .nav-tabs, +body[data-page^="admin-secubox-system-system-hub"] ul.cbi-tabmenu, +body[data-page*="system-hub"] ul.tabs, +body[data-page*="system-hub"] .tabs, +/* Fallback: hide any tabs that appear before our custom nav */ +.system-hub-dashboard .tabs, +.system-hub-dashboard + .tabs, +.system-hub-dashboard ~ .tabs, +.cbi-map > .tabs:first-child, +#maincontent > .container > .tabs, +#maincontent > .container > ul.tabs, +#view > .tabs, +#view > ul.tabs, +.view > .tabs, +.view > ul.tabs, +div.tabs:has(+ .system-hub-dashboard), +/* Direct sibling of System Hub content */ +.sh-nav-tabs ~ .tabs, +/* LuCI 24.x specific */ +.luci-app-system-hub .tabs, +#cbi-system-hub .tabs { display: none !important; } + +/* Hide tabs container when our nav is present */ +.sh-nav-tabs ~ ul.tabs, +.sh-nav-tabs + ul.tabs { + display: none !important; +} + +/* System Hub Nav Tabs */ +.sh-nav-tabs { + display: flex; + gap: 4px; + margin-bottom: 24px; + padding: 6px; + background: var(--sh-bg-secondary); + border-radius: var(--sh-radius-lg); + border: 1px solid var(--sh-border); + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.sh-nav-tab { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + border-radius: var(--sh-radius); + background: transparent; + border: none; + color: var(--sh-text-secondary); + font-weight: 500; + font-size: 13px; + cursor: pointer; + text-decoration: none; + transition: all 0.2s ease; + white-space: nowrap; +} + +.sh-nav-tab:hover { + color: var(--sh-text-primary); + background: var(--sh-bg-tertiary); +} + +.sh-nav-tab.active { + color: var(--sh-accent); + background: var(--sh-bg-tertiary); + box-shadow: inset 0 -2px 0 var(--sh-accent); +} + +.sh-tab-icon { + font-size: 16px; + line-height: 1; +} + +.sh-tab-label { + font-family: var(--sh-font-sans); +} + +@media (max-width: 768px) { + .sh-nav-tabs { + padding: 4px; + } + .sh-nav-tab { + padding: 8px 12px; + font-size: 12px; + } + .sh-tab-label { + display: none; + } + .sh-tab-icon { + font-size: 18px; + } +} `; document.head && document.head.appendChild(style); }, renderTabs: function(active) { this.ensureLuCITabsHidden(); - return E('div', { 'class': 'sh-nav-tabs system-hub-nav-tabs' }, + return E('div', { 'class': 'sh-nav-tabs' }, this.getTabs().map(function(tab) { return E('a', { 'class': 'sh-nav-tab' + (tab.id === active ? ' active' : ''), diff --git a/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/debug.js b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/debug.js new file mode 100644 index 00000000..37a0c51b --- /dev/null +++ b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/debug.js @@ -0,0 +1,226 @@ +'use strict'; +'require view'; +'require dom'; +'require ui'; +'require fs'; +'require rpc'; +'require system-hub/nav as HubNav'; +'require secubox-theme/theme as Theme'; +'require system-hub/theme-assets as ThemeAssets'; +'require secubox-portal/header as SbHeader'; + +var shLang = (typeof L !== 'undefined' && L.env && L.env.lang) || + (document.documentElement && document.documentElement.getAttribute('lang')) || + (navigator.language ? navigator.language.split('-')[0] : 'en'); +Theme.init({ language: shLang }); + +var callSystemBoard = rpc.declare({ + object: 'system', + method: 'board' +}); + +var callSystemInfo = rpc.declare({ + object: 'system', + method: 'info' +}); + +return view.extend({ + logData: null, + + load: function() { + return Promise.all([ + callSystemBoard(), + callSystemInfo(), + fs.exec('/bin/sh', ['-c', 'logread -l 100 2>/dev/null | tail -100']).catch(function() { return { stdout: '' }; }), + fs.exec('/bin/sh', ['-c', 'dmesg | tail -50 2>/dev/null']).catch(function() { return { stdout: '' }; }) + ]); + }, + + render: function(data) { + var board = data[0] || {}; + var sysinfo = data[1] || {}; + var systemLogs = (data[2] && data[2].stdout) ? data[2].stdout.split('\n').filter(Boolean) : []; + var kernelLogs = (data[3] && data[3].stdout) ? data[3].stdout.split('\n').filter(Boolean) : []; + + var container = E('div', { 'class': 'system-hub-dashboard sh-debug-view' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }), + ThemeAssets.stylesheet('common.css'), + ThemeAssets.stylesheet('dashboard.css'), + HubNav.renderTabs('debug'), + + // 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' }, '🐛'), + _('Debug Console') + ]), + E('p', { 'class': 'sh-page-subtitle' }, + _('System information, logs and diagnostic tools for troubleshooting.')) + ]), + E('div', { 'class': 'sh-header-actions' }, [ + E('button', { + 'class': 'sh-btn sh-btn-sm', + 'click': L.bind(this.handleRefresh, this) + }, [E('span', {}, '🔄'), ' ', _('Refresh')]), + E('button', { + 'class': 'sh-btn sh-btn-sm sh-btn-primary', + 'click': L.bind(this.handleDownloadLogs, this, systemLogs, kernelLogs) + }, [E('span', {}, '💾'), ' ', _('Download Logs')]) + ]) + ]), + + // System Information Card + E('div', { 'class': 'sh-card' }, [ + E('div', { 'class': 'sh-card-header' }, [ + E('h3', { 'class': 'sh-card-title' }, [ + E('span', { 'class': 'sh-card-icon' }, 'ℹ️'), + _('System Information') + ]) + ]), + E('div', { 'class': 'sh-card-body' }, [ + this.renderSystemInfo(board, sysinfo) + ]) + ]), + + // Browser Information Card + E('div', { 'class': 'sh-card' }, [ + E('div', { 'class': 'sh-card-header' }, [ + E('h3', { 'class': 'sh-card-title' }, [ + E('span', { 'class': 'sh-card-icon' }, '🌐'), + _('Browser Information') + ]) + ]), + E('div', { 'class': 'sh-card-body' }, [ + this.renderBrowserInfo() + ]) + ]), + + // System Logs Card + E('div', { 'class': 'sh-card' }, [ + E('div', { 'class': 'sh-card-header' }, [ + E('h3', { 'class': 'sh-card-title' }, [ + E('span', { 'class': 'sh-card-icon' }, '📋'), + _('System Logs (logread)') + ]), + E('span', { 'class': 'sh-badge' }, systemLogs.length + ' ' + _('lines')) + ]), + E('div', { 'class': 'sh-card-body' }, [ + E('div', { 'class': 'sh-log-container', 'style': 'max-height: 400px; overflow-y: auto;' }, + systemLogs.length > 0 ? + E('pre', { 'class': 'sh-log-content', 'style': 'margin: 0; font-size: 0.8em; white-space: pre-wrap; word-break: break-all;' }, systemLogs.join('\n')) : + E('div', { 'class': 'sh-empty-state' }, _('No system logs available')) + ) + ]) + ]), + + // Kernel Logs Card + E('div', { 'class': 'sh-card' }, [ + E('div', { 'class': 'sh-card-header' }, [ + E('h3', { 'class': 'sh-card-title' }, [ + E('span', { 'class': 'sh-card-icon' }, '🖥️'), + _('Kernel Logs (dmesg)') + ]), + E('span', { 'class': 'sh-badge' }, kernelLogs.length + ' ' + _('lines')) + ]), + E('div', { 'class': 'sh-card-body' }, [ + E('div', { 'class': 'sh-log-container', 'style': 'max-height: 300px; overflow-y: auto;' }, + kernelLogs.length > 0 ? + E('pre', { 'class': 'sh-log-content', 'style': 'margin: 0; font-size: 0.8em; white-space: pre-wrap; word-break: break-all;' }, kernelLogs.join('\n')) : + E('div', { 'class': 'sh-empty-state' }, _('No kernel logs available')) + ) + ]) + ]) + ]); + + var wrapper = E('div', { 'class': 'secubox-page-wrapper' }); + wrapper.appendChild(SbHeader.render()); + wrapper.appendChild(container); + return wrapper; + }, + + renderSystemInfo: function(board, sysinfo) { + var memory = sysinfo.memory || {}; + var memUsed = memory.total ? Math.round((memory.total - memory.free - (memory.buffered || 0) - (memory.cached || 0)) / 1024 / 1024) : 0; + var memTotal = memory.total ? Math.round(memory.total / 1024 / 1024) : 0; + + var items = [ + { label: _('Hostname'), value: board.hostname || 'N/A' }, + { label: _('Model'), value: board.model || 'N/A' }, + { label: _('Architecture'), value: board.system || 'N/A' }, + { label: _('Kernel'), value: board.kernel || 'N/A' }, + { label: _('OpenWrt Release'), value: (board.release && board.release.description) || 'N/A' }, + { label: _('Uptime'), value: this.formatUptime(sysinfo.uptime) }, + { label: _('Load Average'), value: sysinfo.load ? sysinfo.load.map(function(l) { return (l / 65536).toFixed(2); }).join(' ') : 'N/A' }, + { label: _('Memory Usage'), value: memUsed + ' / ' + memTotal + ' MB (' + (memTotal ? Math.round(memUsed / memTotal * 100) : 0) + '%)' } + ]; + + return E('div', { 'class': 'sh-info-grid', 'style': 'display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px;' }, + items.map(function(item) { + return E('div', { 'class': 'sh-info-item', 'style': 'display: flex; justify-content: space-between; padding: 8px; background: var(--sh-bg-tertiary, #1e2632); border-radius: 4px;' }, [ + E('span', { 'style': 'color: var(--sh-text-secondary, #8b949e);' }, item.label + ':'), + E('span', { 'style': 'font-weight: 500;' }, item.value) + ]); + }) + ); + }, + + renderBrowserInfo: function() { + var info = { + 'User Agent': navigator.userAgent, + 'Platform': navigator.platform, + 'Language': navigator.language, + 'Screen': window.screen.width + 'x' + window.screen.height, + 'Window': window.innerWidth + 'x' + window.innerHeight, + 'Cookies': navigator.cookieEnabled ? _('Enabled') : _('Disabled'), + 'Online': navigator.onLine ? _('Yes') : _('No'), + 'Timezone': Intl.DateTimeFormat().resolvedOptions().timeZone + }; + + return E('div', { 'class': 'sh-info-grid', 'style': 'display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px;' }, + Object.keys(info).map(function(key) { + return E('div', { 'class': 'sh-info-item', 'style': 'display: flex; justify-content: space-between; padding: 8px; background: var(--sh-bg-tertiary, #1e2632); border-radius: 4px;' }, [ + E('span', { 'style': 'color: var(--sh-text-secondary, #8b949e);' }, key + ':'), + E('span', { 'style': 'font-weight: 500; word-break: break-all; max-width: 60%;' }, info[key]) + ]); + }) + ); + }, + + formatUptime: function(seconds) { + if (!seconds) return 'N/A'; + 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'); + parts.push(mins + 'm'); + return parts.join(' '); + }, + + handleRefresh: function(ev) { + location.reload(); + }, + + handleDownloadLogs: function(systemLogs, kernelLogs, ev) { + var content = '=== SecuBox Debug Report ===\n'; + content += 'Generated: ' + new Date().toISOString() + '\n\n'; + content += '=== SYSTEM LOGS ===\n'; + content += systemLogs.join('\n') + '\n\n'; + content += '=== KERNEL LOGS ===\n'; + content += kernelLogs.join('\n'); + + var blob = new Blob([content], { type: 'text/plain' }); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'secubox-debug-' + new Date().toISOString().slice(0, 10) + '.txt'; + a.click(); + URL.revokeObjectURL(url); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-system-hub/root/usr/share/luci/menu.d/luci-app-system-hub.json b/package/secubox/luci-app-system-hub/root/usr/share/luci/menu.d/luci-app-system-hub.json index 098df2ec..759b6a58 100644 --- a/package/secubox/luci-app-system-hub/root/usr/share/luci/menu.d/luci-app-system-hub.json +++ b/package/secubox/luci-app-system-hub/root/usr/share/luci/menu.d/luci-app-system-hub.json @@ -67,9 +67,17 @@ "path": "system-hub/health" } }, + "admin/secubox/system/system-hub/debug": { + "title": "Debug Console", + "order": 8, + "action": { + "type": "view", + "path": "system-hub/debug" + } + }, "admin/secubox/system/system-hub/remote": { "title": "Remote Management", - "order": 8, + "order": 9, "action": { "type": "view", "path": "system-hub/remote" @@ -77,7 +85,7 @@ }, "admin/secubox/system/system-hub/settings": { "title": "Settings", - "order": 9, + "order": 10, "action": { "type": "view", "path": "system-hub/settings" @@ -85,7 +93,7 @@ }, "admin/secubox/system/system-hub/dev-status": { "title": "Development Status", - "order": 10, + "order": 11, "action": { "type": "view", "path": "system-hub/dev-status" @@ -93,7 +101,7 @@ }, "admin/secubox/system/system-hub/network-modes": { "title": "Network Modes", - "order": 10, + "order": 12, "action": { "type": "view", "path": "system-hub/network-modes-link" @@ -106,7 +114,7 @@ }, "admin/secubox/system/system-hub/cdn-cache": { "title": "CDN Cache", - "order": 11, + "order": 13, "action": { "type": "view", "path": "system-hub/cdn-cache-link" diff --git a/package/secubox/secubox-app-nodogsplash/Makefile b/package/secubox/secubox-app-nodogsplash/Makefile index d4ca0abc..7721a7c4 100644 --- a/package/secubox/secubox-app-nodogsplash/Makefile +++ b/package/secubox/secubox-app-nodogsplash/Makefile @@ -2,8 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=secubox-app-nodogsplash PKG_VERSION:=5.0.2 -PKG_RELEASE:=2 -PKG_ARCH:=all +PKG_RELEASE:=4 PKG_SOURCE:=nodogsplash-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://github.com/nodogsplash/nodogsplash/archive/refs/tags @@ -20,7 +19,6 @@ include $(INCLUDE_DIR)/package.mk define Package/$(PKG_NAME) SECTION:=net CATEGORY:=Network - PKGARCH:=all SUBMENU:=Captive Portals TITLE:=Nodogsplash captive portal URL:=https://github.com/nodogsplash/nodogsplash @@ -46,15 +44,11 @@ define Build/Compile STRIP=no \ ENABLE_STATE_FILE=yes \ all - $(MAKE) -C $(PKG_BUILD_DIR) \ - CC="$(TARGET_CC)" \ - CPPFLAGS="$(TARGET_CPPFLAGS)" \ - CFLAGS="$(TARGET_CFLAGS)" \ - LDFLAGS="$(TARGET_LDFLAGS)" \ - STRIP=no \ - ENABLE_STATE_FILE=yes \ - DESTDIR="$(PKG_INSTALL_DIR)" \ - install +endef + +# Skip upstream install target which has hardcoded strip command +define Build/Install + true endef define Package/$(PKG_NAME)/conffiles @@ -65,12 +59,15 @@ endef define Package/$(PKG_NAME)/install $(INSTALL_DIR) $(1)/usr/bin - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/nodogsplash $(1)/usr/bin/ - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/ndsctl $(1)/usr/bin/ + $(INSTALL_BIN) $(PKG_BUILD_DIR)/nodogsplash $(1)/usr/bin/ + $(INSTALL_BIN) $(PKG_BUILD_DIR)/ndsctl $(1)/usr/bin/ $(INSTALL_DIR) $(1)/etc/nodogsplash/htdocs/images - $(CP) $(PKG_INSTALL_DIR)/etc/nodogsplash/nodogsplash.conf $(1)/etc/nodogsplash/ - $(CP) $(PKG_INSTALL_DIR)/etc/nodogsplash/htdocs/* $(1)/etc/nodogsplash/htdocs/ + $(INSTALL_CONF) $(PKG_BUILD_DIR)/resources/nodogsplash.conf $(1)/etc/nodogsplash/ + $(INSTALL_DATA) $(PKG_BUILD_DIR)/resources/splash.html $(1)/etc/nodogsplash/htdocs/ + $(INSTALL_DATA) $(PKG_BUILD_DIR)/resources/splash.css $(1)/etc/nodogsplash/htdocs/ + $(INSTALL_DATA) $(PKG_BUILD_DIR)/resources/status.html $(1)/etc/nodogsplash/htdocs/ + $(INSTALL_DATA) $(PKG_BUILD_DIR)/resources/splash.jpg $(1)/etc/nodogsplash/htdocs/images/ $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/nodogsplash.init $(1)/etc/init.d/nodogsplash diff --git a/package/secubox/secubox-auth-logger/Makefile b/package/secubox/secubox-auth-logger/Makefile index 3f8802d1..b7d22794 100644 --- a/package/secubox/secubox-auth-logger/Makefile +++ b/package/secubox/secubox-auth-logger/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=secubox-auth-logger -PKG_VERSION:=1.2.1 +PKG_VERSION:=1.2.2 PKG_RELEASE:=1 PKG_ARCH:=all PKG_LICENSE:=Apache-2.0 @@ -61,6 +61,10 @@ define Package/secubox-auth-logger/install $(INSTALL_DIR) $(1)/etc/crowdsec/parsers/s01-parse $(INSTALL_DATA) ./files/openwrt-luci-auth.yaml $(1)/etc/crowdsec/parsers/s01-parse/ + # CrowdSec whitelist for private IPs (RFC1918) + $(INSTALL_DIR) $(1)/etc/crowdsec/parsers/s02-enrich + $(INSTALL_DATA) ./files/secubox-private-ip-whitelist.yaml $(1)/etc/crowdsec/parsers/s02-enrich/ + # CrowdSec scenario $(INSTALL_DIR) $(1)/etc/crowdsec/scenarios $(INSTALL_DATA) ./files/openwrt-luci-bf.yaml $(1)/etc/crowdsec/scenarios/ diff --git a/package/secubox/secubox-auth-logger/files/auth-monitor.sh b/package/secubox/secubox-auth-logger/files/auth-monitor.sh index e67f4b22..a514ca08 100644 --- a/package/secubox/secubox-auth-logger/files/auth-monitor.sh +++ b/package/secubox/secubox-auth-logger/files/auth-monitor.sh @@ -79,11 +79,10 @@ monitor_logs() { fi fi - # rpcd session errors - if echo "$line" | grep -qi "rpcd.*access.denied\|ubus.*error"; then - ip=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1) - [ -n "$ip" ] && log_failure "luci" "$ip" - fi + # NOTE: rpcd "access denied" is NOT a login attempt - it just means + # someone accessed LuCI without a valid session (session expired or + # not logged in). Real login attempts are detected by the JS hook + # in secubox-auth-hook.js which intercepts session.login calls. done } diff --git a/package/secubox/secubox-auth-logger/files/secubox-private-ip-whitelist.yaml b/package/secubox/secubox-auth-logger/files/secubox-private-ip-whitelist.yaml new file mode 100644 index 00000000..9c444d52 --- /dev/null +++ b/package/secubox/secubox-auth-logger/files/secubox-private-ip-whitelist.yaml @@ -0,0 +1,17 @@ +# CrowdSec Whitelist for Private IP Ranges +# Prevents blocking of internal network addresses (RFC1918) +# These IPs should never be banned as they are local network devices + +name: secubox/private-ip-whitelist +description: "Whitelist private/internal IP ranges to prevent self-blocking" +whitelist: + reason: "Private IP addresses (RFC1918) - local network devices" + ip: + - "127.0.0.0/8" # Localhost + - "10.0.0.0/8" # Class A private + - "172.16.0.0/12" # Class B private + - "192.168.0.0/16" # Class C private + - "169.254.0.0/16" # Link-local + - "::1/128" # IPv6 localhost + - "fe80::/10" # IPv6 link-local + - "fc00::/7" # IPv6 unique local