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:
parent
e75d0f3741
commit
5b55ab3ef9
@ -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();
|
||||
},
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
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)
|
||||
]);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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');
|
||||
}
|
||||
});
|
||||
@ -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'] },
|
||||
|
||||
@ -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
|
||||
});
|
||||
@ -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
|
||||
});
|
||||
@ -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('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: white; border-radius: 4px;' }, [
|
||||
E('strong', {}, _('Policy Descriptions:')),
|
||||
E('ul', { 'style': 'margin: 0.5em 0;' }, [
|
||||
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.')
|
||||
])
|
||||
])
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
@ -511,6 +529,10 @@ 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
|
||||
@ -524,8 +546,7 @@ get_clients() {
|
||||
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
|
||||
# New/unknown client - show as unknown (auto-zoning done in background)
|
||||
json_add_boolean "known" 0
|
||||
json_add_string "name" "$hostname"
|
||||
json_add_string "name" "${hostname:-Unknown}"
|
||||
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
|
||||
json_add_string "first_seen" ""
|
||||
json_add_string "vendor" "Unknown"
|
||||
fi
|
||||
|
||||
# Get traffic stats if available
|
||||
@ -602,8 +599,7 @@ get_clients() {
|
||||
$(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
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'] },
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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
|
||||
});
|
||||
@ -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
|
||||
});
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
},
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
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)
|
||||
]);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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' : ''),
|
||||
|
||||
@ -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
|
||||
});
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
Loading…
Reference in New Issue
Block a user