feat: Remove captive portal and add auto-zoning to Client Guardian (v0.6.0-r24)

Major enhancements to Client Guardian:

**Removed Captive Portal:**
- Deleted portal.js and captive.js views
- Removed portal configuration from UCI
- Removed portal RPC methods (get_portal, update_portal, list_sessions, authorize_client, deauthorize_client)
- Cleaned menu and ACL definitions
- Updated default policy from 'captive' to 'quarantine'

**Added Auto-Zoning System:**
- Implemented get_vendor_from_mac() for OUI lookups
- Added apply_auto_zoning() with rule-based zone assignment
- Support for vendor, hostname pattern, and MAC prefix matching
- 8 pre-configured auto-zoning rules (IoT devices, mobile, guests)
- Auto-parking zone for unmatched clients
- GridSection UI for managing auto-zoning rules

**Threat Intelligence Integration:**
- Added threat_policy UCI section
- Auto-ban/quarantine based on threat score thresholds
- Threat indicators on client displays
- Integration with Security Threats Dashboard

**Dashboard Improvements:**
- Fixed boolean conversion (UCI "true"/"false" to JSON 0/1)
- Fixed RPC expect parameter issues causing empty arrays
- Added real-time polling with configurable intervals
- Removed all window.location.reload() calls
- Smooth DOM updates without page flickers

**Settings Enhancements:**
- Added reactiveness section (auto-refresh toggle, interval)
- Added threat intelligence settings
- Removed captive portal settings section
- Updated policy descriptions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-08 08:44:39 +01:00
parent 1823913582
commit 0564de0811
12 changed files with 1693 additions and 921 deletions

View File

@ -41,11 +41,6 @@ var callParental = rpc.declare({
expect: { }
});
var callPortal = rpc.declare({
object: 'luci.client-guardian',
method: 'portal',
expect: { }
});
var callAlerts = rpc.declare({
object: 'luci.client-guardian',
@ -95,13 +90,6 @@ var callUpdateZone = rpc.declare({
expect: { success: false }
});
var callUpdatePortal = rpc.declare({
object: 'luci.client-guardian',
method: 'update_portal',
params: ['title', 'subtitle', 'accent_color'],
expect: { success: false }
});
var callSendTestAlert = rpc.declare({
object: 'luci.client-guardian',
method: 'send_test_alert',
@ -109,13 +97,6 @@ var callSendTestAlert = rpc.declare({
expect: { success: false }
});
// Nodogsplash Captive Portal Methods
var callListSessions = rpc.declare({
object: 'luci.client-guardian',
method: 'list_sessions',
expect: { sessions: [] }
});
var callGetPolicy = rpc.declare({
object: 'luci.client-guardian',
method: 'get_policy',
@ -125,21 +106,13 @@ var callGetPolicy = rpc.declare({
var callSetPolicy = rpc.declare({
object: 'luci.client-guardian',
method: 'set_policy',
params: ['policy', 'portal_enabled', 'auto_approve', 'session_timeout'],
params: ['policy', 'auto_approve', 'session_timeout'],
expect: { success: false }
});
var callAuthorizeClient = rpc.declare({
var callSyncZones = rpc.declare({
object: 'luci.client-guardian',
method: 'authorize_client',
params: ['mac', 'ip'],
expect: { success: false }
});
var callDeauthorizeClient = rpc.declare({
object: 'luci.client-guardian',
method: 'deauthorize_client',
params: ['mac', 'ip'],
method: 'sync_zones',
expect: { success: false }
});
@ -165,6 +138,42 @@ function formatBytes(bytes) {
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + units[i];
}
function getDeviceIcon(hostname, mac) {
hostname = (hostname || '').toLowerCase();
mac = (mac || '').toLowerCase();
// Mobile devices
if (hostname.match(/android|iphone|ipad|mobile|phone|samsung|xiaomi|huawei/))
return '📱';
// Computers
if (hostname.match(/pc|laptop|desktop|macbook|imac|windows|linux|ubuntu/))
return '💻';
// IoT devices
if (hostname.match(/camera|bulb|switch|sensor|thermostat|doorbell|lock/))
return '📷';
// Smart TV / Media
if (hostname.match(/tv|roku|chromecast|firestick|appletv|media/))
return '📺';
// Gaming
if (hostname.match(/playstation|xbox|nintendo|switch|steam/))
return '🎮';
// Network equipment
if (hostname.match(/router|switch|ap|access[-_]?point|bridge/))
return '🌐';
// Printers
if (hostname.match(/printer|print|hp-|canon-|epson-/))
return '🖨️';
// Default
return '🔌';
}
return baseclass.extend({
// Core methods
getStatus: callStatus,
@ -172,7 +181,6 @@ return baseclass.extend({
getClient: callGetClient,
getZones: callZones,
getParental: callParental,
getPortal: callPortal,
getAlerts: callAlerts,
getLogs: callLogs,
@ -184,18 +192,14 @@ return baseclass.extend({
// Configuration
updateZone: callUpdateZone,
updatePortal: callUpdatePortal,
sendTestAlert: callSendTestAlert,
// Nodogsplash Captive Portal
listSessions: callListSessions,
syncZones: callSyncZones,
getPolicy: callGetPolicy,
setPolicy: callSetPolicy,
authorizeClient: callAuthorizeClient,
deauthorizeClient: callDeauthorizeClient,
// Utility functions
formatMac: formatMac,
formatDuration: formatDuration,
formatBytes: formatBytes
formatBytes: formatBytes,
getDeviceIcon: getDeviceIcon
});

View File

@ -9,41 +9,50 @@
--cg-bg-tertiary: #251a1a;
--cg-border: #3d2828;
--cg-border-light: #4a3333;
--cg-text-primary: #fafafa;
--cg-text-secondary: #b8a8a8;
--cg-text-muted: #8a7575;
--cg-accent-red: #ef4444;
/* SecuBox Brand Colors (Indigo/Purple) */
--cg-primary: #6366f1;
--cg-primary-end: #8b5cf6;
/* Accent Colors */
--cg-accent-orange: #f97316;
--cg-accent-amber: #f59e0b;
--cg-accent-green: #22c55e;
--cg-accent-blue: #3b82f6;
--cg-accent-purple: #8b5cf6;
--cg-accent-cyan: #06b6d4;
--cg-danger: #dc2626;
/* State Colors */
--cg-danger: #ef4444;
--cg-warning: #f59e0b;
--cg-success: #16a34a;
--cg-info: #0284c7;
--cg-gradient: linear-gradient(135deg, #ef4444, #dc2626, #b91c1c);
--cg-gradient-soft: linear-gradient(135deg, rgba(239, 68, 68, 0.2), rgba(220, 38, 38, 0.1));
/* Primary Gradients (SecuBox Indigo/Purple) */
--cg-gradient: linear-gradient(135deg, #6366f1, #8b5cf6);
--cg-gradient-soft: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(139, 92, 246, 0.1));
/* Zone Colors (Keep as-is) */
--cg-zone-private: #22c55e;
--cg-zone-iot: #f59e0b;
--cg-zone-kids: #06b6d4;
--cg-zone-guest: #8b5cf6;
--cg-zone-quarantine: #ef4444;
--cg-zone-blocked: #6b7280;
/* Typography */
--cg-font-mono: 'JetBrains Mono', 'Fira Code', monospace;
--cg-font-sans: 'Inter', -apple-system, sans-serif;
/* Layout */
--cg-radius: 8px;
--cg-radius-lg: 12px;
--cg-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
--cg-shadow-glow: 0 0 30px rgba(239, 68, 68, 0.3);
--cg-shadow-glow: 0 0 30px rgba(99, 102, 241, 0.3);
}
/* Base */
@ -164,14 +173,14 @@
}
.cg-status-badge.approved {
background: rgba(34, 197, 94, 0.15);
color: var(--cg-accent-green);
border: 1px solid rgba(34, 197, 94, 0.3);
background: rgba(99, 102, 241, 0.15);
color: var(--cg-primary);
border: 1px solid rgba(99, 102, 241, 0.3);
}
.cg-status-badge.quarantine {
background: rgba(239, 68, 68, 0.15);
color: var(--cg-accent-red);
color: var(--cg-danger);
border: 1px solid rgba(239, 68, 68, 0.3);
}
@ -259,8 +268,8 @@
}
.cg-client-item:hover {
border-color: var(--cg-accent-red);
background: rgba(239, 68, 68, 0.05);
border-color: var(--cg-primary);
background: rgba(99, 102, 241, 0.05);
}
.cg-client-item.online {
@ -273,7 +282,7 @@
}
.cg-client-item.quarantine {
border-left: 3px solid var(--cg-accent-red);
border-left: 3px solid var(--cg-danger);
background: rgba(239, 68, 68, 0.08);
}
@ -373,13 +382,37 @@
.cg-client-action:hover {
background: var(--cg-bg-tertiary);
border-color: var(--cg-accent-red);
border-color: var(--cg-primary);
}
.cg-client-action.approve:hover { border-color: var(--cg-accent-green); background: rgba(34, 197, 94, 0.1); }
.cg-client-action.ban:hover { border-color: var(--cg-danger); background: rgba(220, 38, 38, 0.1); }
.cg-client-action.edit:hover { border-color: var(--cg-accent-blue); background: rgba(59, 130, 246, 0.1); }
/* Threat Indicators */
.cg-threat-badge {
display: inline-flex;
align-items: center;
justify-content: center;
animation: pulse-threat 2s ease-in-out infinite;
}
@keyframes pulse-threat {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
}
.cg-client-item .cg-threat-badge:hover {
animation: none;
transform: scale(1.2);
}
/* Zone Cards */
.cg-zones-grid {
display: grid;
@ -515,7 +548,7 @@
transition: background 0.3s;
}
.cg-toggle-switch.active { background: var(--cg-accent-red); }
.cg-toggle-switch.active { background: var(--cg-primary); }
.cg-toggle-switch::after {
content: '';
@ -554,7 +587,7 @@
.cg-select:focus,
.cg-textarea:focus {
outline: none;
border-color: var(--cg-accent-red);
border-color: var(--cg-primary);
}
.cg-textarea { min-height: 100px; resize: vertical; }
@ -584,7 +617,7 @@
transition: all 0.2s;
}
.cg-btn:hover { border-color: var(--cg-accent-red); }
.cg-btn:hover { border-color: var(--cg-primary); }
.cg-btn-primary {
background: var(--cg-gradient);
@ -618,7 +651,7 @@
padding: 16px;
background: var(--cg-bg-tertiary);
border-radius: var(--cg-radius);
border-left: 4px solid var(--cg-accent-red);
border-left: 4px solid var(--cg-danger);
margin-bottom: 16px;
}
@ -647,8 +680,8 @@
.cg-schedule-day.active {
background: rgba(239, 68, 68, 0.2);
border-color: var(--cg-accent-red);
color: var(--cg-accent-red);
border-color: var(--cg-danger);
color: var(--cg-danger);
}
.cg-schedule-day-name { font-size: 11px; font-weight: 600; }
@ -729,7 +762,7 @@
.cg-log-level.info { background: rgba(59, 130, 246, 0.15); color: var(--cg-accent-blue); }
.cg-log-level.warning { background: rgba(245, 158, 11, 0.15); color: var(--cg-accent-amber); }
.cg-log-level.error { background: rgba(239, 68, 68, 0.15); color: var(--cg-accent-red); }
.cg-log-level.error { background: rgba(239, 68, 68, 0.15); color: var(--cg-danger); }
.cg-log-message { flex: 1; color: var(--cg-text-secondary); }
@ -761,10 +794,428 @@
.client-guardian-dashboard ::-webkit-scrollbar-thumb { background: var(--cg-border); border-radius: 4px; }
.client-guardian-dashboard ::-webkit-scrollbar-thumb:hover { background: var(--cg-text-muted); }
/* Loading States */
.cg-status-badge.loading {
opacity: 0.6;
}
.cg-status-badge.loading .cg-status-dot {
animation: spin 1s linear infinite;
}
/* Smooth Transitions */
.cg-stat-value {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.cg-client-item {
transition: all 0.3s ease;
}
.cg-stat-card.updated {
animation: flash 0.5s ease-in-out;
}
/* Animations */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes flash {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; background: rgba(99, 102, 241, 0.1); }
}
@keyframes client-pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
50% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
}
.cg-client-item.quarantine { animation: client-pulse 2s infinite; }
/* Wizard Styles */
.cg-wizard {
max-width: 1400px;
margin: 0 auto;
padding: 24px;
}
.cg-wizard-header {
text-align: center;
margin-bottom: 48px;
}
.cg-wizard-icon {
font-size: 72px;
margin-bottom: 16px;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.cg-wizard-title {
font-size: 32px;
font-weight: 700;
color: var(--cg-text-primary);
margin: 0 0 8px 0;
background: var(--cg-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.cg-wizard-subtitle {
font-size: 16px;
color: var(--cg-text-secondary);
max-width: 600px;
margin: 0 auto;
}
.cg-profiles-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.cg-profile-card {
background: var(--cg-bg-secondary);
border: 2px solid var(--cg-border);
border-radius: 16px;
padding: 24px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.cg-profile-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--cg-gradient);
opacity: 0;
transition: opacity 0.3s;
}
.cg-profile-card:hover::before {
opacity: 1;
}
.cg-profile-card:hover {
transform: translateY(-4px);
border-color: var(--cg-primary);
box-shadow: 0 8px 24px rgba(99, 102, 241, 0.15);
}
.cg-profile-icon {
font-size: 48px;
text-align: center;
margin-bottom: 16px;
}
.cg-profile-name {
font-size: 20px;
font-weight: 600;
color: var(--cg-text-primary);
text-align: center;
margin-bottom: 8px;
}
.cg-profile-desc {
font-size: 14px;
color: var(--cg-text-secondary);
text-align: center;
margin-bottom: 16px;
min-height: 40px;
}
.cg-profile-zones {
background: var(--cg-bg-tertiary);
padding: 12px;
border-radius: 8px;
margin-bottom: 16px;
}
.cg-profile-zones strong {
display: block;
margin-bottom: 8px;
font-size: 12px;
text-transform: uppercase;
color: var(--cg-text-muted);
}
.cg-profile-zone-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.cg-profile-zone-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
color: white;
}
.cg-profile-more {
font-size: 12px;
color: var(--cg-text-muted);
font-style: italic;
}
.cg-profile-btn {
width: 100%;
justify-content: center;
}
.cg-wizard-footer {
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 8px;
padding: 16px 20px;
}
.cg-wizard-note {
color: var(--cg-text-secondary);
font-size: 14px;
}
.cg-modal-profile {
padding: 16px;
}
.cg-modal-profile h3 {
text-align: center;
color: var(--cg-primary);
margin-bottom: 8px;
}
.cg-modal-profile p {
text-align: center;
color: var(--cg-text-secondary);
margin-bottom: 16px;
}
.cg-modal-profile ul {
list-style: none;
padding: 0;
}
.cg-modal-profile li {
padding: 8px 0;
border-bottom: 1px solid var(--cg-border);
}
.cg-modal-profile li:last-child {
border-bottom: none;
}
.spinner {
border: 4px solid rgba(99, 102, 241, 0.1);
border-top: 4px solid var(--cg-primary);
border-radius: 50%;
width: 48px;
height: 48px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
/* Debug Interface Styles */
.cg-debug-controls {
display: flex;
gap: 8px;
}
.cg-btn-sm {
padding: 6px 12px;
font-size: 13px;
}
.cg-debug-status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
}
.cg-debug-status-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.cg-debug-status-label {
font-size: 12px;
text-transform: uppercase;
color: var(--cg-text-muted);
font-weight: 600;
}
.cg-debug-status-value {
font-size: 14px;
color: var(--cg-text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.cg-input-sm {
padding: 6px 10px;
font-size: 13px;
}
.cg-system-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 12px;
}
.cg-info-item {
display: flex;
padding: 8px 0;
border-bottom: 1px solid var(--cg-border);
}
.cg-info-label {
font-weight: 600;
color: var(--cg-text-secondary);
min-width: 140px;
}
.cg-info-value {
color: var(--cg-text-primary);
word-break: break-word;
}
.cg-log-container {
max-height: 600px;
overflow-y: auto;
border: 1px solid var(--cg-border);
border-radius: 8px;
background: var(--cg-bg-tertiary);
}
.cg-log-entry {
padding: 12px;
border-bottom: 1px solid var(--cg-border);
font-family: var(--cg-font-mono);
font-size: 13px;
transition: background 0.2s;
}
.cg-log-entry:hover {
background: rgba(99, 102, 241, 0.05);
}
.cg-log-entry:last-child {
border-bottom: none;
}
.cg-log-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
}
.cg-log-icon {
font-size: 16px;
}
.cg-log-level {
font-weight: 700;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
}
.cg-log-error .cg-log-level {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
.cg-log-warn .cg-log-level {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
}
.cg-log-info .cg-log-level {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}
.cg-log-debug .cg-log-level {
background: rgba(139, 92, 246, 0.15);
color: #8b5cf6;
}
.cg-log-trace .cg-log-level {
background: rgba(107, 114, 128, 0.15);
color: #6b7280;
}
.cg-log-time {
color: var(--cg-text-muted);
font-size: 11px;
margin-left: auto;
}
.cg-log-message {
color: var(--cg-text-primary);
margin-bottom: 4px;
line-height: 1.5;
}
.cg-log-details {
margin-top: 8px;
}
.cg-log-details summary {
cursor: pointer;
color: var(--cg-primary);
font-size: 12px;
user-select: none;
}
.cg-log-details summary:hover {
text-decoration: underline;
}
.cg-log-data {
margin-top: 8px;
padding: 12px;
background: var(--cg-bg-primary);
border-radius: 6px;
overflow-x: auto;
font-size: 12px;
color: var(--cg-text-secondary);
line-height: 1.6;
}
/* Scrollbar styling for log container */
.cg-log-container::-webkit-scrollbar {
width: 8px;
}
.cg-log-container::-webkit-scrollbar-track {
background: var(--cg-bg-secondary);
border-radius: 4px;
}
.cg-log-container::-webkit-scrollbar-thumb {
background: var(--cg-border);
border-radius: 4px;
}
.cg-log-container::-webkit-scrollbar-thumb:hover {
background: var(--cg-primary);
}

View File

@ -1,257 +0,0 @@
'use strict';
'require view';
'require secubox-theme/theme as Theme';
'require dom';
'require poll';
'require ui';
'require client-guardian/api as API';
return view.extend({
load: function() {
return Promise.all([
API.listSessions(),
API.getPolicy(),
API.getStatus()
]);
},
render: function(data) {
var sessions = data[0] || {};
var policy = data[1] || {};
var status = data[2] || {};
var sessionList = sessions.sessions || [];
var nds = sessions.nodogsplash || {};
var view = E('div', { 'class': 'cbi-map' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
E('h2', {}, _('Captive Portal Management')),
// Nodogsplash Status Card
E('div', { 'class': 'cbi-section', 'style': 'background: ' + (nds.running ? '#d4edda' : '#f8d7da') + '; border-left: 4px solid ' + (nds.running ? '#28a745' : '#dc3545') + '; padding: 1em; margin-bottom: 1em;' }, [
E('h3', { 'style': 'margin-top: 0;' }, _('Nodogsplash Status')),
E('div', { 'style': 'display: flex; gap: 2em; align-items: center;' }, [
E('div', {}, [
E('strong', {}, _('Service:')),
' ',
E('span', { 'class': 'badge', 'style': 'background: ' + (nds.running ? '#28a745' : '#dc3545') + '; color: white; padding: 0.25em 0.6em; border-radius: 3px;' },
nds.running ? _('RUNNING') : _('STOPPED'))
]),
E('div', {}, [
E('strong', {}, _('Active Sessions:')),
' ',
E('span', { 'style': 'font-size: 1.5em; color: #0088cc; font-weight: bold;' }, sessionList.length.toString())
]),
E('div', {}, [
E('strong', {}, _('Default Policy:')),
' ',
E('span', { 'class': 'badge', 'style': 'background: #0088cc; color: white; padding: 0.25em 0.6em; border-radius: 3px;' },
policy.default_policy || 'captive')
])
]),
!nds.running ? E('p', { 'style': 'margin: 1em 0 0 0; color: #856404; background: #fff3cd; padding: 0.75em; border-radius: 4px;' }, [
E('strong', {}, _('Note:')),
' ',
_('Nodogsplash is not running. Start the service to enable captive portal functionality.')
]) : null
]),
// Active Sessions Table
E('div', { 'class': 'cbi-section' }, [
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 1em;' }, [
E('h3', { 'style': 'margin: 0;' }, _('Active Portal Sessions')),
E('button', {
'class': 'cbi-button cbi-button-action',
'click': L.bind(this.handleRefresh, this)
}, _('Refresh'))
]),
E('div', { 'class': 'table-wrapper' }, [
E('table', { 'class': 'table', 'id': 'sessions-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, _('MAC Address')),
E('th', {}, _('IP Address')),
E('th', {}, _('Hostname')),
E('th', {}, _('Duration')),
E('th', { 'style': 'text-align: right;' }, _('Downloaded')),
E('th', { 'style': 'text-align: right;' }, _('Uploaded')),
E('th', {}, _('State')),
E('th', { 'class': 'cbi-section-actions' }, _('Actions'))
])
]),
E('tbody', { 'id': 'sessions-tbody' },
this.renderSessionRows(sessionList)
)
])
])
]),
// Help Section
E('div', { 'class': 'cbi-section', 'style': 'background: #e8f4f8; padding: 1em; margin-top: 2em;' }, [
E('h3', {}, _('Captive Portal Information')),
E('p', {}, _('The captive portal intercepts new clients and requires them to authenticate before accessing the network. Sessions are managed by nodogsplash.')),
E('ul', {}, [
E('li', {}, _('Active sessions show clients currently authenticated through the portal')),
E('li', {}, _('Use "Deauthorize" to end a session and force re-authentication')),
E('li', {}, _('Configure portal settings in the Portal tab')),
E('li', {}, _('Change default policy in Settings to control portal behavior'))
])
])
]);
// Setup auto-refresh
poll.add(L.bind(function() {
return API.listSessions().then(L.bind(function(refreshData) {
var tbody = document.getElementById('sessions-tbody');
if (tbody) {
var refreshedSessions = refreshData.sessions || [];
dom.content(tbody, this.renderSessionRows(refreshedSessions));
}
}, this));
}, this), 5);
return view;
},
renderSessionRows: function(sessions) {
if (!sessions || sessions.length === 0) {
return E('tr', {}, [
E('td', { 'colspan': 8, 'style': 'text-align: center; padding: 2em; color: #999;' },
_('No active captive portal sessions'))
]);
}
return sessions.map(L.bind(function(session) {
return E('tr', {}, [
E('td', {}, [
E('code', { 'style': 'font-size: 0.9em;' }, session.mac || 'N/A')
]),
E('td', {}, session.ip || 'N/A'),
E('td', {}, session.hostname || 'Unknown'),
E('td', {}, this.formatDuration(session.duration || 0)),
E('td', { 'style': 'text-align: right; font-family: monospace;' },
this.formatBytes(session.downloaded || 0)),
E('td', { 'style': 'text-align: right; font-family: monospace;' },
this.formatBytes(session.uploaded || 0)),
E('td', {}, [
E('span', {
'class': 'badge',
'style': 'background: #28a745; color: white; padding: 0.25em 0.6em; border-radius: 3px;'
}, session.state || 'authenticated')
]),
E('td', { 'class': 'cbi-section-actions' }, [
E('button', {
'class': 'cbi-button cbi-button-negative cbi-button-remove',
'click': L.bind(function(ev) {
this.handleDeauth(ev, session.mac, session.ip, session.hostname);
}, this)
}, _('Deauthorize'))
])
]);
}, this));
},
formatDuration: function(seconds) {
if (!seconds || seconds === 0) return '0s';
var hours = Math.floor(seconds / 3600);
var minutes = Math.floor((seconds % 3600) / 60);
var secs = seconds % 60;
var parts = [];
if (hours > 0) parts.push(hours + 'h');
if (minutes > 0) parts.push(minutes + 'm');
if (secs > 0 || parts.length === 0) parts.push(secs + 's');
return parts.join(' ');
},
formatBytes: function(bytes) {
if (!bytes || bytes === 0) return '0 B';
var units = ['B', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(1024));
i = Math.min(i, units.length - 1);
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + units[i];
},
handleDeauth: function(ev, mac, ip, hostname) {
var btn = ev.target;
ui.showModal(_('Deauthorize Client'), [
E('p', {}, _('Are you sure you want to deauthorize this client?')),
E('div', { 'style': 'background: #f8f9fa; padding: 1em; margin: 1em 0; border-radius: 4px;' }, [
E('div', {}, [E('strong', {}, _('Hostname:')), ' ', hostname || 'Unknown']),
E('div', {}, [E('strong', {}, _('MAC:')), ' ', E('code', {}, mac)]),
E('div', {}, [E('strong', {}, _('IP:')), ' ', ip || 'N/A'])
]),
E('p', {}, _('The client will be immediately disconnected and must re-authenticate through the captive portal.')),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Cancel')),
' ',
E('button', {
'class': 'btn cbi-button-negative',
'click': L.bind(function() {
ui.hideModal();
this.deauthorize(mac, ip, btn);
}, this)
}, _('Deauthorize'))
])
]);
},
deauthorize: function(mac, ip, btn) {
btn.disabled = true;
btn.textContent = _('Deauthorizing...');
API.deauthorizeClient(mac, ip).then(L.bind(function(result) {
if (result.success) {
ui.addNotification(null,
E('p', _('Client %s has been deauthorized').format(mac)),
'info'
);
// Refresh the table
this.handleRefresh();
} else {
ui.addNotification(null,
E('p', _('Failed to deauthorize client: %s').format(result.error || 'Unknown error')),
'error'
);
btn.disabled = false;
btn.textContent = _('Deauthorize');
}
}, this)).catch(function(err) {
ui.addNotification(null,
E('p', _('Error: %s').format(err.message || err)),
'error'
);
btn.disabled = false;
btn.textContent = _('Deauthorize');
});
},
handleRefresh: function() {
poll.start();
return Promise.all([
API.listSessions(),
API.getPolicy()
]).then(L.bind(function(data) {
var tbody = document.getElementById('sessions-tbody');
if (tbody) {
var sessions = data[0].sessions || [];
dom.content(tbody, this.renderSessionRows(sessions));
}
}, this));
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -15,8 +15,8 @@ return view.extend({
},
render: function(data) {
var clients = data[0].clients || [];
var zones = data[1].zones || [];
var clients = Array.isArray(data[0]) ? data[0] : (data[0].clients || []);
var zones = Array.isArray(data[1]) ? data[1] : (data[1].zones || []);
var self = this;
var view = E('div', { 'class': 'client-guardian-dashboard' }, [
@ -188,15 +188,16 @@ return view.extend({
]),
E('div', { 'class': 'cg-btn-group', 'style': 'justify-content: flex-end' }, [
E('button', { 'class': 'cg-btn', 'click': ui.hideModal }, _('Annuler')),
E('button', { 'class': 'cg-btn cg-btn-success', 'click': function() {
E('button', { 'class': 'cg-btn cg-btn-success', 'click': L.bind(function() {
var name = document.getElementById('approve-name').value;
var zone = document.getElementById('approve-zone').value;
var notes = document.getElementById('approve-notes').value;
api.approveClient(mac, name, zone, notes).then(function() {
api.approveClient(mac, name, zone, notes).then(L.bind(function() {
ui.hideModal();
window.location.reload();
});
}}, _('Approuver'))
ui.addNotification(null, E('p', _('Client approved successfully')), 'success');
this.handleRefresh();
}, this));
}, this)}, _('Approuver'))
])
]);
},
@ -229,7 +230,7 @@ return view.extend({
]),
E('div', { 'class': 'cg-btn-group', 'style': 'justify-content: flex-end' }, [
E('button', { 'class': 'cg-btn', 'click': ui.hideModal }, _('Annuler')),
E('button', { 'class': 'cg-btn cg-btn-primary', 'click': function() {
E('button', { 'class': 'cg-btn cg-btn-primary', 'click': L.bind(function() {
api.updateClient(
client.section,
document.getElementById('edit-name').value,
@ -237,11 +238,12 @@ return view.extend({
document.getElementById('edit-notes').value,
parseInt(document.getElementById('edit-quota').value) || 0,
document.getElementById('edit-ip').value
).then(function() {
).then(L.bind(function() {
ui.hideModal();
window.location.reload();
});
}}, _('Enregistrer'))
ui.addNotification(null, E('p', _('Client updated successfully')), 'success');
this.handleRefresh();
}, this));
}, this)}, _('Enregistrer'))
])
]);
},
@ -258,26 +260,39 @@ return view.extend({
]),
E('div', { 'class': 'cg-btn-group', 'style': 'justify-content: flex-end' }, [
E('button', { 'class': 'cg-btn', 'click': ui.hideModal }, _('Annuler')),
E('button', { 'class': 'cg-btn cg-btn-danger', 'click': function() {
E('button', { 'class': 'cg-btn cg-btn-danger', 'click': L.bind(function() {
var reason = document.getElementById('ban-reason').value || 'Manual ban';
api.banClient(mac, reason).then(function() {
api.banClient(mac, reason).then(L.bind(function() {
ui.hideModal();
window.location.reload();
});
}}, _('Bannir'))
ui.addNotification(null, E('p', _('Client banned successfully')), 'info');
this.handleRefresh();
}, this));
}, this)}, _('Bannir'))
])
]);
},
handleUnban: function(ev) {
var mac = ev.currentTarget.dataset.mac;
api.quarantineClient(mac).then(function() {
window.location.reload();
});
api.quarantineClient(mac).then(L.bind(function() {
ui.addNotification(null, E('p', _('Client unbanned successfully')), 'success');
this.handleRefresh();
}, this));
},
handleRefresh: function() {
window.location.reload();
return Promise.all([
api.getClients(),
api.getZones()
]).then(L.bind(function(data) {
var container = document.querySelector('.client-guardian-dashboard');
if (container) {
var newView = this.render(data);
dom.content(container.parentNode, newView);
}
}, this)).catch(function(err) {
console.error('Failed to refresh clients list:', err);
});
},
handleSaveApply: null,

View File

@ -12,14 +12,15 @@ return view.extend({
return Promise.all([
api.getStatus(),
api.getClients(),
api.getZones()
api.getZones(),
uci.load('client-guardian')
]);
},
render: function(data) {
var status = data[0];
var clients = data[1].clients || [];
var zones = data[2].zones || [];
var clients = Array.isArray(data[1]) ? data[1] : (data[1].clients || []);
var zones = Array.isArray(data[2]) ? data[2] : (data[2].zones || []);
var onlineClients = clients.filter(function(c) { return c.online; });
var approvedClients = clients.filter(function(c) { return c.status === 'approved'; });
@ -51,8 +52,8 @@ return view.extend({
this.renderStatCard('✅', approvedClients.length, 'Approuvés'),
this.renderStatCard('⏳', quarantineClients.length, 'Quarantaine'),
this.renderStatCard('🚫', bannedClients.length, 'Bannis'),
this.renderStatCard('🌐', zones.length, 'Zones'),
this.renderStatCard('🔔', status.alerts_today || 0, 'Alertes Aujourd\'hui')
this.renderStatCard('⚠️', clients.filter(function(c) { return c.has_threats; }).length, 'Menaces Actives'),
this.renderStatCard('🌐', zones.length, 'Zones')
]),
// Recent Clients Card
@ -88,6 +89,16 @@ return view.extend({
]) : E('div')
]);
// Setup auto-refresh polling based on UCI settings
var autoRefresh = uci.get('client-guardian', 'config', 'auto_refresh');
var refreshInterval = parseInt(uci.get('client-guardian', 'config', 'refresh_interval') || '10');
if (autoRefresh === '1') {
poll.add(L.bind(function() {
return this.handleRefresh();
}, this), refreshInterval);
}
return view;
},
@ -114,11 +125,19 @@ return view.extend({
E('div', { 'class': 'cg-client-info' }, [
E('div', { 'class': 'cg-client-name' }, [
client.online ? E('span', { 'class': 'online-indicator' }) : E('span'),
client.name || client.hostname || 'Unknown'
client.name || client.hostname || 'Unknown',
client.has_threats ? E('span', {
'class': 'cg-threat-badge',
'title': (client.threat_count || 0) + ' menace(s) active(s), score de risque: ' + (client.risk_score || 0),
'style': 'margin-left: 8px; color: #ef4444; font-size: 16px; cursor: help;'
}, '⚠️') : E('span')
]),
E('div', { 'class': 'cg-client-meta' }, [
E('span', {}, client.mac),
E('span', {}, client.ip || 'N/A')
E('span', {}, client.ip || 'N/A'),
client.has_threats ? E('span', {
'style': 'color: #ef4444; font-weight: 500; margin-left: 8px;'
}, 'Risque: ' + (client.risk_score || 0) + '%') : E('span')
])
]),
E('span', { 'class': 'cg-client-zone ' + zoneClass }, client.zone || 'unknown'),
@ -176,13 +195,14 @@ return view.extend({
}, _('Annuler')),
E('button', {
'class': 'cg-btn cg-btn-success',
'click': function() {
'click': L.bind(function() {
var zone = document.getElementById('approve-zone').value;
api.approveClient(mac, '', zone, '').then(function() {
api.approveClient(mac, '', zone, '').then(L.bind(function() {
ui.hideModal();
window.location.reload();
});
}
ui.addNotification(null, E('p', _('Client approved successfully')), 'success');
this.handleRefresh();
}, this));
}, this)
}, _('Approuver'))
])
]);
@ -201,17 +221,52 @@ return view.extend({
}, _('Annuler')),
E('button', {
'class': 'cg-btn cg-btn-danger',
'click': function() {
api.banClient(mac, 'Manual ban').then(function() {
'click': L.bind(function() {
api.banClient(mac, 'Manual ban').then(L.bind(function() {
ui.hideModal();
window.location.reload();
});
}
ui.addNotification(null, E('p', _('Client banned successfully')), 'info');
this.handleRefresh();
}, this));
}, this)
}, _('Bannir'))
])
]);
},
handleRefresh: function() {
return Promise.all([
api.getStatus(),
api.getClients(),
api.getZones()
]).then(L.bind(function(data) {
// Update dashboard without full page reload
var container = document.querySelector('.client-guardian-dashboard');
if (container) {
// Show loading indicator
var statusBadge = document.querySelector('.cg-status-badge');
if (statusBadge) {
statusBadge.classList.add('loading');
}
// Reconstruct data array (status, clients, zones, uci already loaded)
var newView = this.render(data);
dom.content(container.parentNode, newView);
// Remove loading indicator
if (statusBadge) {
statusBadge.classList.remove('loading');
}
}
}, this)).catch(function(err) {
console.error('Failed to refresh Client Guardian dashboard:', err);
});
},
handleLeave: function() {
// Stop polling when leaving the view to prevent memory leaks
poll.stop();
},
handleSaveApply: null,
handleSave: null,
handleReset: null

View File

@ -1,215 +0,0 @@
'use strict';
'require view';
'require secubox-theme/theme as Theme';
'require dom';
'require ui';
'require client-guardian.api as api';
return view.extend({
load: function() {
return api.getPortal();
},
render: function(data) {
var portal = data;
var self = this;
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') }),
E('div', { 'class': 'cg-header' }, [
E('div', { 'class': 'cg-logo' }, [
E('div', { 'class': 'cg-logo-icon' }, '🚪'),
E('div', { 'class': 'cg-logo-text' }, 'Portail Captif')
]),
E('div', { 'class': 'cg-status-badge ' + (portal.enabled ? 'approved' : 'offline') }, [
E('span', { 'class': 'cg-status-dot' }),
portal.enabled ? 'Actif' : 'Inactif'
])
]),
// Configuration
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'
])
]),
E('div', { 'class': 'cg-card-body' }, [
E('div', { 'class': 'cg-toggle' }, [
E('div', { 'class': 'cg-toggle-info' }, [
E('span', { 'class': 'cg-toggle-icon' }, '🚪'),
E('div', {}, [
E('div', { 'class': 'cg-toggle-label' }, 'Portail Captif'),
E('div', { 'class': 'cg-toggle-desc' }, 'Activer pour les zones Guest et Quarantine')
])
]),
E('div', {
'class': 'cg-toggle-switch' + (portal.enabled ? ' active' : ''),
'id': 'toggle-portal',
'click': function() { this.classList.toggle('active'); }
})
]),
E('div', { 'class': 'cg-toggle' }, [
E('div', { 'class': 'cg-toggle-info' }, [
E('span', { 'class': 'cg-toggle-icon' }, '📝'),
E('div', {}, [
E('div', { 'class': 'cg-toggle-label' }, 'Conditions d\'utilisation'),
E('div', { 'class': 'cg-toggle-desc' }, 'Exiger l\'acceptation des CGU')
])
]),
E('div', {
'class': 'cg-toggle-switch' + (portal.require_terms ? ' active' : ''),
'id': 'toggle-terms',
'click': function() { this.classList.toggle('active'); }
})
]),
E('div', { 'class': 'cg-toggle' }, [
E('div', { 'class': 'cg-toggle-info' }, [
E('span', { 'class': 'cg-toggle-icon' }, '📧'),
E('div', {}, [
E('div', { 'class': 'cg-toggle-label' }, 'Inscription'),
E('div', { 'class': 'cg-toggle-desc' }, 'Permettre l\'auto-inscription (avec approbation)')
])
]),
E('div', {
'class': 'cg-toggle-switch' + (portal.allow_registration ? ' active' : ''),
'id': 'toggle-registration',
'click': function() { this.classList.toggle('active'); }
})
]),
E('div', { 'class': 'cg-form-group', 'style': 'margin-top: 20px' }, [
E('label', { 'class': 'cg-form-label' }, 'Titre du Portail'),
E('input', {
'type': 'text',
'id': 'portal-title',
'class': 'cg-input',
'value': portal.title || 'Bienvenue sur le Réseau'
})
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Sous-titre'),
E('input', {
'type': 'text',
'id': 'portal-subtitle',
'class': 'cg-input',
'value': portal.subtitle || 'Veuillez vous identifier pour accéder à Internet'
})
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Méthode d\'authentification'),
E('select', { 'id': 'portal-auth', 'class': 'cg-input' }, [
E('option', { 'value': 'password', 'selected': portal.auth_method === 'password' }, 'Mot de passe unique'),
E('option', { 'value': 'voucher', 'selected': portal.auth_method === 'voucher' }, 'Codes voucher'),
E('option', { 'value': 'click', 'selected': portal.auth_method === 'click' }, 'Click-through (acceptation CGU)')
])
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Mot de passe Invité'),
E('input', {
'type': 'text',
'id': 'portal-password',
'class': 'cg-input',
'value': portal.guest_password || 'guest2024'
})
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Couleur d\'accent'),
E('input', {
'type': 'color',
'id': 'portal-color',
'class': 'cg-input',
'style': 'width: 80px; height: 40px; padding: 4px',
'value': portal.accent_color || '#ef4444'
})
]),
E('div', { 'class': 'cg-btn-group' }, [
E('button', {
'class': 'cg-btn cg-btn-primary',
'click': L.bind(this.handleSavePortal, this)
}, [
E('span', {}, '💾'),
' Enregistrer'
])
])
])
]),
// Portal Preview
E('div', { 'class': 'cg-card' }, [
E('div', { 'class': 'cg-card-header' }, [
E('div', { 'class': 'cg-card-title' }, [
E('span', { 'class': 'cg-card-title-icon' }, '👁️'),
'Aperçu du Portail'
])
]),
E('div', { 'class': 'cg-card-body' }, [
E('div', { 'class': 'cg-portal-preview', 'id': 'portal-preview' }, [
E('div', {
'class': 'cg-portal-preview-logo',
'style': 'background: ' + (portal.accent_color || '#ef4444')
}, '🛡️'),
E('div', { 'class': 'cg-portal-preview-title', 'id': 'preview-title' },
portal.title || 'Bienvenue sur le Réseau'
),
E('div', { 'class': 'cg-portal-preview-subtitle', 'id': 'preview-subtitle' },
portal.subtitle || 'Veuillez vous identifier pour accéder à Internet'
),
E('input', {
'type': 'password',
'class': 'cg-portal-preview-input',
'placeholder': 'Mot de passe invité'
}),
E('button', {
'class': 'cg-portal-preview-btn',
'id': 'preview-btn',
'style': 'background: ' + (portal.accent_color || '#ef4444')
}, 'Se Connecter')
])
])
]),
// Active Sessions
E('div', { 'class': 'cg-card' }, [
E('div', { 'class': 'cg-card-header' }, [
E('div', { 'class': 'cg-card-title' }, [
E('span', { 'class': 'cg-card-title-icon' }, '👥'),
'Sessions Actives'
]),
E('span', { 'class': 'cg-card-badge' }, (portal.active_sessions || 0) + ' sessions')
]),
E('div', { 'class': 'cg-card-body' }, [
portal.active_sessions > 0 ?
E('p', {}, 'Liste des sessions actives...') :
E('div', { 'class': 'cg-empty-state' }, [
E('div', { 'class': 'cg-empty-state-icon' }, '🔒'),
E('div', { 'class': 'cg-empty-state-title' }, 'Aucune session active'),
E('div', { 'class': 'cg-empty-state-text' }, 'Les sessions du portail captif apparaîtront ici')
])
])
])
]);
},
handleSavePortal: function(ev) {
var title = document.getElementById('portal-title').value;
var subtitle = document.getElementById('portal-subtitle').value;
var auth = document.getElementById('portal-auth').value;
var password = document.getElementById('portal-password').value;
var color = document.getElementById('portal-color').value;
api.updatePortal(title, subtitle, color, auth, password).then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, _('Configuration du portail enregistrée')), 'success');
}
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -22,7 +22,7 @@ return view.extend({
var m, s, o;
m = new form.Map('client-guardian', _('Client Guardian Settings'),
_('Configure default network access policy and captive portal behavior.'));
_('Configure default network access policy and client management.'));
// General Settings
s = m.section(form.NamedSection, 'config', 'client-guardian', _('General Settings'));
@ -34,9 +34,9 @@ return view.extend({
o = s.option(form.ListValue, 'default_policy', _('Default Policy'));
o.value('open', _('Open - Allow all clients'));
o.value('captive', _('Captive Portal - Require portal authentication'));
o.value('quarantine', _('Quarantine - Require approval'));
o.value('whitelist', _('Whitelist Only - Allow only approved clients'));
o.default = 'captive';
o.default = 'quarantine';
o.rmempty = false;
o.description = _('Default behavior for new/unknown clients');
@ -51,57 +51,6 @@ return view.extend({
o.placeholder = '86400';
o.description = _('Maximum session duration in seconds (default: 86400 = 24 hours)');
// Captive Portal Settings
s = m.section(form.NamedSection, 'portal', 'portal', _('Captive Portal'));
o = s.option(form.Flag, 'enabled', _('Enable Captive Portal'));
o.default = '1';
o.rmempty = false;
o.description = _('Enable nodogsplash captive portal for guest authentication');
o = s.option(form.Value, 'title', _('Portal Title'));
o.default = 'Welcome';
o.placeholder = 'Welcome';
o.description = _('Main title displayed on the portal page');
o = s.option(form.Value, 'subtitle', _('Portal Subtitle'));
o.default = 'Please authenticate to access the network';
o.placeholder = 'Please authenticate to access the network';
o.description = _('Subtitle or welcome message');
o = s.option(form.ListValue, 'auth_method', _('Authentication Method'));
o.value('click', _('Click to Continue'));
o.value('password', _('Password'));
o.value('voucher', _('Voucher Code'));
o.value('email', _('Email Verification'));
o.default = 'click';
o.description = _('Method used to authenticate users');
o = s.option(form.Value, 'guest_password', _('Guest Password'));
o.depends('auth_method', 'password');
o.password = true;
o.placeholder = 'Enter password';
o.description = _('Password required for guest access');
o = s.option(form.Value, 'accent_color', _('Accent Color'));
o.default = '#0088cc';
o.placeholder = '#0088cc';
o.datatype = 'string';
o.description = _('Hex color code for portal branding');
o = s.option(form.Flag, 'require_terms', _('Require Terms Acceptance'));
o.default = '0';
o.description = _('Require users to accept terms and conditions');
o = s.option(form.Flag, 'allow_registration', _('Allow Self-Registration'));
o.default = '0';
o.description = _('Allow users to register their own devices');
o = s.option(form.Flag, 'registration_approval', _('Require Registration Approval'));
o.default = '1';
o.depends('allow_registration', '1');
o.description = _('Administrator must approve self-registered devices');
// Advanced Settings
s = m.section(form.NamedSection, 'config', 'client-guardian', _('Advanced Settings'));
@ -127,26 +76,128 @@ return view.extend({
o.default = '0';
o.description = _('Attempt to detect and block VPN connections');
// Dashboard Reactiveness
s = m.section(form.NamedSection, 'config', 'client-guardian', _('Dashboard Reactiveness'));
o = s.option(form.Flag, 'auto_refresh', _('Enable Auto-Refresh'),
_('Automatically refresh dashboard every few seconds'));
o.default = o.enabled;
o.rmempty = false;
o = s.option(form.ListValue, 'refresh_interval', _('Refresh Interval'),
_('How often to poll for updates'));
o.value('5', _('Every 5 seconds'));
o.value('10', _('Every 10 seconds (recommended)'));
o.value('30', _('Every 30 seconds'));
o.value('60', _('Every 60 seconds'));
o.default = '10';
o.depends('auto_refresh', '1');
// Threat Intelligence Integration
s = m.section(form.NamedSection, 'threat_policy', 'threat_policy', _('Threat Intelligence Integration'));
o = s.option(form.Flag, 'enabled', _('Enable Threat Intelligence'),
_('Correlate clients with Security Threats Dashboard data'));
o.default = o.enabled;
o.rmempty = false;
o = s.option(form.Value, 'auto_ban_threshold', _('Auto-Ban Threshold'),
_('Automatically ban clients with threat score above this value (0-100)'));
o.datatype = 'range(1,100)';
o.placeholder = '80';
o.default = '80';
o.depends('enabled', '1');
o = s.option(form.Value, 'auto_quarantine_threshold', _('Auto-Quarantine Threshold'),
_('Automatically quarantine clients with threat score above this value (0-100)'));
o.datatype = 'range(1,100)';
o.placeholder = '60';
o.default = '60';
o.depends('enabled', '1');
o = s.option(form.Value, 'threat_check_interval', _('Threat Check Interval'),
_('How often to check for threats (seconds)'));
o.datatype = 'uinteger';
o.placeholder = '60';
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) {
// 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;'
}, [
E('h3', { 'style': 'margin-top: 0;' }, _('Current Policy: ') + E('span', { 'style': 'color: #0088cc;' }, policy.default_policy || 'captive')),
E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 1em; margin-top: 1em;' }, [
E('div', {}, [
E('strong', {}, _('Portal Enabled:')),
' ',
E('span', { 'class': 'badge', 'style': 'background: ' + (policy.portal_enabled ? '#28a745' : '#6c757d') + '; color: white; padding: 0.25em 0.6em; border-radius: 3px;' },
policy.portal_enabled ? _('Yes') : _('No'))
]),
E('div', {}, [
E('strong', {}, _('Session Timeout:')),
' ',
E('span', {}, (policy.session_timeout || 86400) + ' ' + _('seconds'))
])
]),
E('h3', { 'style': 'margin-top: 0;' }, _('Current Policy: ') + E('span', { 'style': 'color: #0088cc;' }, policy.default_policy || 'quarantine')),
E('div', { 'style': 'margin-top: 1em;' }, [
E('strong', {}, _('Session Timeout:')),
' ',
E('span', {}, (policy.session_timeout || 86400) + ' ' + _('seconds'))
]),
E('div', { 'style': 'margin-top: 1em; padding: 0.75em; background: white; border-radius: 4px;' }, [
E('strong', {}, _('Policy Descriptions:')),
E('ul', { 'style': 'margin: 0.5em 0;' }, [
@ -156,9 +207,9 @@ return view.extend({
_('All clients can access the network without authentication. Not recommended for public networks.')
]),
E('li', {}, [
E('strong', {}, _('Captive Portal:')),
E('strong', {}, _('Quarantine:')),
' ',
_('New clients are redirected to a captive portal for authentication. Recommended for guest networks.')
_('New clients are placed in quarantine and require manual approval. Recommended for secure networks.')
]),
E('li', {}, [
E('strong', {}, _('Whitelist Only:')),

View File

@ -11,7 +11,7 @@ return view.extend({
},
render: function(data) {
var zones = data.zones || [];
var zones = Array.isArray(data) ? data : (data.zones || []);
var self = this;
return E('div', { 'class': 'client-guardian-dashboard' }, [
@ -22,11 +22,19 @@ return view.extend({
E('div', { 'class': 'cg-logo' }, [
E('div', { 'class': 'cg-logo-icon' }, '🌐'),
E('div', { 'class': 'cg-logo-text' }, 'Zones Réseau')
]),
E('button', {
'class': 'cg-btn cg-btn-primary',
'click': L.bind(this.handleSyncZones, this),
'style': 'display: flex; align-items: center; gap: 8px;'
}, [
E('span', {}, '🔄'),
'Synchroniser Firewall'
])
]),
E('p', { 'style': 'color: var(--cg-text-secondary); margin-bottom: 24px' },
'Définissez les zones de sécurité avec leurs règles d\'accès, filtrage et limitations.'
'Définissez les zones de sécurité avec leurs règles d\'accès, filtrage et limitations. Cliquez sur "Synchroniser Firewall" pour créer les zones dans la configuration firewall.'
),
E('div', { 'class': 'cg-zones-grid' },
@ -153,7 +161,7 @@ return view.extend({
]) : E('span'),
E('div', { 'class': 'cg-btn-group', 'style': 'justify-content: flex-end; margin-top: 20px' }, [
E('button', { 'class': 'cg-btn', 'click': ui.hideModal }, _('Annuler')),
E('button', { 'class': 'cg-btn cg-btn-primary', 'click': function() {
E('button', { 'class': 'cg-btn cg-btn-primary', 'click': L.bind(function() {
api.updateZone(
zone.id,
zone.name,
@ -161,15 +169,53 @@ return view.extend({
document.getElementById('zone-filter').value,
zone.time_restrictions ? document.getElementById('zone-start').value : '',
zone.time_restrictions ? document.getElementById('zone-end').value : ''
).then(function() {
).then(L.bind(function() {
ui.hideModal();
window.location.reload();
});
}}, _('Enregistrer'))
ui.addNotification(null, E('p', _('Zone updated successfully')), 'success');
this.handleRefresh();
}, this));
}, this)}, _('Enregistrer'))
])
]);
},
handleSyncZones: function(ev) {
var btn = ev.currentTarget;
btn.disabled = true;
btn.innerHTML = '<span>⏳</span> Synchronisation...';
api.syncZones().then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', {}, 'Zones firewall synchronisées avec succès'), 'success');
btn.innerHTML = '<span>✅</span> Synchronisé';
setTimeout(function() {
btn.disabled = false;
btn.innerHTML = '<span>🔄</span> Synchroniser Firewall';
}, 2000);
} else {
ui.addNotification(null, E('p', {}, 'Erreur lors de la synchronisation'), 'error');
btn.disabled = false;
btn.innerHTML = '<span>🔄</span> Synchroniser Firewall';
}
}).catch(function(err) {
ui.addNotification(null, E('p', {}, 'Erreur: ' + err), 'error');
btn.disabled = false;
btn.innerHTML = '<span>🔄</span> Synchroniser Firewall';
});
},
handleRefresh: function() {
return api.getZones().then(L.bind(function(data) {
var container = document.querySelector('.client-guardian-dashboard');
if (container) {
var newView = this.render(data);
dom.content(container.parentNode, newView);
}
}, this)).catch(function(err) {
console.error('Failed to refresh zones list:', err);
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null

View File

@ -4,10 +4,18 @@ config client-guardian 'config'
option quarantine_zone 'quarantine'
option scan_interval '30'
option auto_approve '0'
option portal_enabled '1'
option portal_url '/cgi-bin/client-guardian-portal'
option session_timeout '3600'
option log_level 'info'
# Dashboard Reactiveness
option auto_refresh '1'
option refresh_interval '10'
# Debug Mode
option debug_enabled '0'
option debug_level 'INFO'
option enable_active_scan '1'
# Auto-Zoning / Auto-Parking
option auto_zoning_enabled '1'
option auto_parking_zone 'guest'
option auto_parking_approve '0'
# Alert Configuration
config alerts 'alerts'
@ -76,7 +84,13 @@ config zone 'kids'
option content_filter 'kids'
option schedule_start '08:00'
option schedule_end '21:00'
list schedule_days 'mon' 'tue' 'wed' 'thu' 'fri' 'sat' 'sun'
list schedule_days 'mon'
list schedule_days 'tue'
list schedule_days 'wed'
list schedule_days 'thu'
list schedule_days 'fri'
list schedule_days 'sat'
list schedule_days 'sun'
config zone 'guest'
option name 'Invités'
@ -90,8 +104,6 @@ config zone 'guest'
option bandwidth_limit '25'
option time_restrictions '0'
option content_filter 'adult'
option session_duration '7200'
option portal_required '1'
config zone 'quarantine'
option name 'Quarantaine'
@ -103,8 +115,6 @@ config zone 'quarantine'
option local_access '0'
option inter_client '0'
option bandwidth_limit '1'
option portal_required '1'
option portal_only '1'
config zone 'blocked'
option name 'Bloqué'
@ -120,15 +130,23 @@ config zone 'blocked'
config filter 'kids_filter'
option name 'Filtre Enfants'
option type 'whitelist'
list categories 'education' 'kids' 'games_safe'
list blocked_categories 'adult' 'violence' 'gambling' 'drugs' 'weapons'
list categories 'education'
list categories 'kids'
list categories 'games_safe'
list blocked_categories 'adult'
list blocked_categories 'violence'
list blocked_categories 'gambling'
list blocked_categories 'drugs'
list blocked_categories 'weapons'
option safe_search '1'
option youtube_restricted '1'
config filter 'adult_filter'
option name 'Filtre Adulte'
option type 'blacklist'
list blocked_categories 'malware' 'phishing' 'illegal'
list blocked_categories 'malware'
list blocked_categories 'phishing'
list blocked_categories 'illegal'
option safe_search '0'
config filter 'strict_filter'
@ -157,7 +175,11 @@ config schedule 'school_hours'
option action 'block'
option start_time '08:00'
option end_time '16:00'
list days 'mon' 'tue' 'wed' 'thu' 'fri'
list days 'mon'
list days 'tue'
list days 'wed'
list days 'thu'
list days 'fri'
config schedule 'night_block'
option name 'Blocage Nocturne'
@ -165,31 +187,28 @@ config schedule 'night_block'
option action 'block'
option start_time '22:00'
option end_time '07:00'
list days 'mon' 'tue' 'wed' 'thu' 'fri' 'sat' 'sun'
list days 'mon'
list days 'tue'
list days 'wed'
list days 'thu'
list days 'fri'
list days 'sat'
list days 'sun'
config schedule 'weekend_limit'
option name 'Limite Weekend'
option enabled '0'
option action 'quota'
option daily_quota '180'
list days 'sat' 'sun'
list days 'sat'
list days 'sun'
# Captive Portal Configuration
config portal 'portal'
# Threat Intelligence Integration
config threat_policy 'threat_policy'
option enabled '1'
option title 'Bienvenue sur le Réseau'
option subtitle 'Veuillez vous identifier pour accéder à Internet'
option logo '/luci-static/client-guardian/logo.png'
option background_color '#0f172a'
option accent_color '#ef4444'
option require_terms '1'
option terms_url '/client-guardian/terms.html'
option auth_method 'password'
option guest_password 'guest2024'
option allow_registration '1'
option registration_approval '1'
option show_bandwidth_info '1'
option custom_css ''
option auto_ban_threshold '80'
option auto_quarantine_threshold '60'
option threat_check_interval '60'
# Example Known Clients
config client 'client_example1'
@ -229,3 +248,84 @@ config client 'client_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
# IoT Devices - Chinese brands
config auto_zone_rule 'rule_xiaomi'
option enabled '1'
option name 'Xiaomi Devices'
option match_type 'vendor'
option match_value 'Xiaomi'
option target_zone 'iot'
option auto_approve '0'
option priority '10'
config auto_zone_rule 'rule_tuya'
option enabled '1'
option name 'Tuya Smart Devices'
option match_type 'vendor'
option match_value 'Tuya'
option target_zone 'iot'
option auto_approve '0'
option priority '10'
config auto_zone_rule 'rule_tp_link'
option enabled '1'
option name 'TP-Link Smart Home'
option match_type 'vendor'
option match_value 'TP-Link'
option target_zone 'iot'
option auto_approve '0'
option priority '10'
# Mobile devices - Kids tablets
config auto_zone_rule 'rule_kids_tablet'
option enabled '1'
option name 'Kids Tablets'
option match_type 'hostname'
option match_pattern 'tablet-.*|.*-kid.*|samsung-tab-kid'
option target_zone 'kids'
option auto_approve '1'
option priority '20'
# Guest devices - Temporary
config auto_zone_rule 'rule_guest_android'
option enabled '1'
option name 'Guest Android Phones'
option match_type 'hostname'
option match_pattern 'android-.*|Galaxy-.*|Pixel-.*'
option target_zone 'guest'
option auto_approve '0'
option priority '30'
config auto_zone_rule 'rule_guest_iphone'
option enabled '1'
option name 'Guest iPhones'
option match_type 'hostname'
option match_pattern 'iPhone.*|iPad.*'
option target_zone 'guest'
option auto_approve '0'
option priority '30'
# Trusted devices - Apple ecosystem
config auto_zone_rule 'rule_apple_trusted'
option enabled '0'
option name 'Apple Devices (Trusted)'
option match_type 'vendor'
option match_value 'Apple'
option target_zone 'lan_private'
option auto_approve '1'
option priority '40'
# IoT Cameras
config auto_zone_rule 'rule_cameras'
option enabled '1'
option name 'IP Cameras'
option match_type 'hostname'
option match_pattern '.*camera.*|.*cam.*|ipcam.*|IPCam.*'
option target_zone 'iot'
option auto_approve '0'
option priority '15'

View File

@ -17,6 +17,14 @@
"path": "client-guardian/overview"
}
},
"admin/secubox/security/client-guardian/wizard": {
"title": "Setup Wizard",
"order": 15,
"action": {
"type": "view",
"path": "client-guardian/wizard"
}
},
"admin/secubox/security/client-guardian/clients": {
"title": "Clients",
"order": 20,
@ -80,5 +88,13 @@
"type": "view",
"path": "client-guardian/settings"
}
},
"admin/secubox/security/client-guardian/debug": {
"title": "Debug Console",
"order": 95,
"action": {
"type": "view",
"path": "client-guardian/debug"
}
}
}

View File

@ -3,7 +3,7 @@
"description": "Grant access to LuCI Client Guardian Dashboard",
"read": {
"ubus": {
"luci.client-guardian": [ "status", "clients", "get_client", "zones", "parental", "portal", "alerts", "logs", "list_sessions", "get_policy" ],
"luci.client-guardian": [ "status", "clients", "get_client", "zones", "parental", "portal", "alerts", "logs", "list_sessions", "get_policy", "list_profiles" ],
"system": [ "info", "board" ],
"network.interface": [ "status", "dump" ],
"file": [ "read", "stat" ]
@ -18,9 +18,10 @@
},
"write": {
"ubus": {
"luci.client-guardian": [ "approve_client", "ban_client", "quarantine_client", "update_client", "update_zone", "update_portal", "send_test_alert", "set_policy", "authorize_client", "deauthorize_client" ]
"luci.client-guardian": [ "approve_client", "ban_client", "quarantine_client", "update_client", "update_zone", "update_portal", "send_test_alert", "set_policy", "authorize_client", "deauthorize_client", "sync_zones", "apply_profile" ],
"uci": [ "apply", "commit", "set", "delete", "add", "reorder", "changes" ]
},
"uci": [ "client-guardian" ]
"uci": [ "client-guardian", "firewall" ]
}
}
}