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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-14 09:32:14 +01:00
parent e75d0f3741
commit 5b55ab3ef9
27 changed files with 2450 additions and 875 deletions

View File

@ -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)
]);
})
);
}
});

View File

@ -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": {

View File

@ -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

View File

@ -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');
}
});

View File

@ -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'] },

View File

@ -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
});

View File

@ -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
});

View File

@ -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.')
])
])

View File

@ -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'),

View File

@ -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"
}
]
}

View File

@ -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

View File

@ -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
}

View File

@ -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"
}
}
}

View File

@ -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'] },

View File

@ -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": {

View File

@ -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
});

View File

@ -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
});

View File

@ -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"
}
}
}

View File

@ -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)
]);
})
);
}
});

View File

@ -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": {

View File

@ -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' : ''),

View File

@ -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
});

View File

@ -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"

View File

@ -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

View File

@ -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/

View File

@ -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
}

View File

@ -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