diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/api.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/api.js
index 8494c43c..c3f680e7 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/api.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/api.js
@@ -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
});
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/dashboard.css b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/dashboard.css
index dedcfb43..ef01291e 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/dashboard.css
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/client-guardian/dashboard.css
@@ -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);
+}
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/captive.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/captive.js
deleted file mode 100644
index fc2c9f0d..00000000
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/captive.js
+++ /dev/null
@@ -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
-});
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/clients.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/clients.js
index 7b7c02d9..12864f0e 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/clients.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/clients.js
@@ -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,
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/overview.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/overview.js
index 8e6d635b..c38383b2 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/overview.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/overview.js
@@ -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
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/portal.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/portal.js
deleted file mode 100644
index e21aec01..00000000
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/portal.js
+++ /dev/null
@@ -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
-});
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/settings.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/settings.js
index 79e452c6..4383105f 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/settings.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/settings.js
@@ -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:')),
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/zones.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/zones.js
index becb744c..94000e4c 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/zones.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/zones.js
@@ -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 = '⏳ Synchronisation...';
+
+ api.syncZones().then(function(result) {
+ if (result.success) {
+ ui.addNotification(null, E('p', {}, 'Zones firewall synchronisées avec succès'), 'success');
+ btn.innerHTML = '✅ Synchronisé';
+ setTimeout(function() {
+ btn.disabled = false;
+ btn.innerHTML = '🔄 Synchroniser Firewall';
+ }, 2000);
+ } else {
+ ui.addNotification(null, E('p', {}, 'Erreur lors de la synchronisation'), 'error');
+ btn.disabled = false;
+ btn.innerHTML = '🔄 Synchroniser Firewall';
+ }
+ }).catch(function(err) {
+ ui.addNotification(null, E('p', {}, 'Erreur: ' + err), 'error');
+ btn.disabled = false;
+ btn.innerHTML = '🔄 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
diff --git a/package/secubox/luci-app-client-guardian/root/etc/config/client-guardian b/package/secubox/luci-app-client-guardian/root/etc/config/client-guardian
index 50a24b2d..3200c343 100644
--- a/package/secubox/luci-app-client-guardian/root/etc/config/client-guardian
+++ b/package/secubox/luci-app-client-guardian/root/etc/config/client-guardian
@@ -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'
+
diff --git a/package/secubox/luci-app-client-guardian/root/usr/libexec/rpcd/luci.client-guardian b/package/secubox/luci-app-client-guardian/root/usr/libexec/rpcd/luci.client-guardian
index ab20011c..2e2287a2 100755
--- a/package/secubox/luci-app-client-guardian/root/usr/libexec/rpcd/luci.client-guardian
+++ b/package/secubox/luci-app-client-guardian/root/usr/libexec/rpcd/luci.client-guardian
@@ -11,32 +11,193 @@ LOG_FILE="/var/log/client-guardian.log"
CLIENTS_DB="/tmp/client-guardian-clients.json"
ALERTS_QUEUE="/tmp/client-guardian-alerts.json"
-# Logging function
+# Logging function with debug support
log_event() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
+
+ # Also log to syslog if debug enabled
+ local debug_enabled=$(uci -q get client-guardian.config.debug_enabled)
+ if [ "$debug_enabled" = "1" ]; then
+ logger -t client-guardian -p "daemon.$level" "$message"
+ fi
}
-# Get real-time client list from ARP and DHCP
+# Debug logging function
+log_debug() {
+ local message="$1"
+ local data="$2"
+
+ local debug_enabled=$(uci -q get client-guardian.config.debug_enabled)
+ local debug_level=$(uci -q get client-guardian.config.debug_level || echo "INFO")
+
+ if [ "$debug_enabled" != "1" ]; then
+ return
+ fi
+
+ # Log based on level hierarchy: ERROR < WARN < INFO < DEBUG < TRACE
+ case "$debug_level" in
+ ERROR) return ;; # Only errors
+ WARN) [ "$1" != "error" ] && [ "$1" != "warn" ] && return ;;
+ INFO) [ "$1" != "error" ] && [ "$1" != "warn" ] && [ "$1" != "info" ] && return ;;
+ DEBUG) [ "$1" = "trace" ] && return ;;
+ TRACE) ;; # Log everything
+ esac
+
+ local timestamp=$(date '+%Y-%m-%d %H:%M:%S.%N' | cut -c1-23)
+ local log_msg="[$timestamp] [DEBUG] $message"
+
+ if [ -n "$data" ]; then
+ log_msg="$log_msg | Data: $data"
+ fi
+
+ echo "$log_msg" >> "$LOG_FILE"
+ logger -t client-guardian-debug "$log_msg"
+}
+
+# Active network scan to discover clients
+scan_network_active() {
+ local subnet="$1"
+ local iface="$2"
+
+ # Method 1: arping (if available)
+ if command -v arping >/dev/null 2>&1; then
+ # Scan common subnet (192.168.x.0/24)
+ for i in $(seq 1 254); do
+ arping -c 1 -w 1 -I "$iface" "${subnet%.*}.$i" >/dev/null 2>&1 &
+ done
+ wait
+ # Method 2: ping sweep fallback
+ elif command -v ping >/dev/null 2>&1; then
+ for i in $(seq 1 254); do
+ ping -c 1 -W 1 "${subnet%.*}.$i" >/dev/null 2>&1 &
+ done
+ wait
+ fi
+
+ # Let ARP table populate
+ sleep 2
+}
+
+# Enhanced client detection with multiple methods
get_connected_clients() {
- local clients=""
-
- # Parse ARP table
- while read ip type flags mac mask iface; do
+ log_debug "Starting client detection" "method=get_connected_clients"
+
+ local clients_tmp="/tmp/cg-clients-$$"
+ > "$clients_tmp"
+
+ # Active scan to populate ARP table (run in background)
+ local enable_scan=$(uci -q get client-guardian.config.enable_active_scan || echo "1")
+ log_debug "Active scan setting" "enabled=$enable_scan"
+
+ if [ "$enable_scan" = "1" ]; then
+ # Detect network subnets to scan
+ local subnets=$(ip -4 addr show | awk '/inet.*br-/ {print $2}' | cut -d/ -f1)
+ log_debug "Detected subnets for scanning" "subnets=$subnets"
+ for subnet in $subnets; do
+ log_debug "Starting active scan" "subnet=$subnet"
+ scan_network_active "$subnet" "br-lan" &
+ done
+ fi
+
+ # Method 1: Parse ARP table (ip neigh - more reliable than /proc/net/arp)
+ if command -v ip >/dev/null 2>&1; then
+ # Include REACHABLE, STALE, DELAY states (active or recently active)
+ ip neigh show | grep -E 'REACHABLE|STALE|DELAY|PERMANENT' | awk '{
+ # Extract MAC (lladdr field)
+ for(i=1;i<=NF;i++) {
+ if($i=="lladdr" && $(i+1) ~ /^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/) {
+ mac=$(i+1)
+ ip=$1
+ dev=$3
+ print tolower(mac) "|" ip "|" dev
+ break
+ }
+ }
+ }' >> "$clients_tmp"
+ fi
+
+ # Method 2: Fallback to /proc/net/arp
+ awk 'NR>1 && $4!="00:00:00:00:00:00" && $3!="0x0" {
+ print tolower($4) "|" $1 "|" $6
+ }' /proc/net/arp >> "$clients_tmp"
+
+ # Method 3: DHCP leases (authoritative for IP assignments)
+ if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
+ awk '{print tolower($2) "|" $3 "|" $4 "|dhcp|" $1}' /tmp/dhcp.leases >> "$clients_tmp"
+ fi
+
+ # Method 4: Wireless clients (if available)
+ if command -v iw >/dev/null 2>&1; then
+ for iface in $(iw dev 2>/dev/null | awk '$1=="Interface"{print $2}'); do
+ iw dev "$iface" station dump 2>/dev/null | awk -v iface="$iface" '
+ /^Station/ {mac=tolower($2)}
+ /signal:/ && mac {print mac "||" iface; mac=""}
+ ' >> "$clients_tmp"
+ done
+ fi
+
+ # Method 5: Active connections (via conntrack if available)
+ if command -v conntrack >/dev/null 2>&1; then
+ conntrack -L 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u | while read ip; do
+ # Try to resolve MAC via ARP
+ local mac=$(ip neigh show "$ip" 2>/dev/null | awk '/lladdr/{print tolower($5)}' | head -1)
+ [ -n "$mac" ] && [ "$mac" != "00:00:00:00:00:00" ] && echo "$mac|$ip|br-lan" >> "$clients_tmp"
+ done
+ fi
+
+ # Method 6: Parse /proc/net/arp for any entry (last resort)
+ cat /proc/net/arp 2>/dev/null | awk 'NR>1 && $4 ~ /^[0-9a-fA-F:]+$/ && $4 != "00:00:00:00:00:00" {
+ print tolower($4) "|" $1 "|" $6
+ }' >> "$clients_tmp"
+
+ # Deduplicate and merge data
+ sort -u -t'|' -k1,1 "$clients_tmp" | while IFS='|' read mac ip iface extra; do
[ -z "$mac" ] && continue
[ "$mac" = "00:00:00:00:00:00" ] && continue
-
+
+ # Skip IPv6 addresses in IP field
+ echo "$ip" | grep -q ':' && continue
+
# Get hostname from DHCP leases
- local hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}')
+ local hostname=""
+ if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
+ hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}' | head -1)
+ fi
+
+ # Try to resolve hostname via DNS reverse lookup
+ if [ -z "$hostname" ] && [ "$ip" != "N/A" ] && [ -n "$ip" ]; then
+ hostname=$(nslookup "$ip" 2>/dev/null | awk '/name =/{print $4}' | sed 's/\.$//' | head -1)
+ fi
+
[ -z "$hostname" ] && hostname="Unknown"
-
- # Get lease expiry
- local lease_time=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $1}')
-
+
+ # Get best IP address (prefer DHCP assigned)
+ if [ -z "$ip" ] || [ "$ip" = "" ]; then
+ if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
+ ip=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $3}' | head -1)
+ fi
+ [ -z "$ip" ] && ip="N/A"
+ fi
+
+ # Get interface (prefer provided, fallback to bridge)
+ [ -z "$iface" ] && iface="br-lan"
+
+ # Get lease time
+ local lease_time=""
+ if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
+ lease_time=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $1}' | head -1)
+ fi
+
echo "$mac|$ip|$hostname|$iface|$lease_time"
- done < /proc/net/arp
+ done
+
+ rm -f "$clients_tmp"
+
+ # Wait for background scan to complete
+ wait
}
# Get dashboard status
@@ -45,11 +206,9 @@ get_status() {
local enabled=$(uci -q get client-guardian.config.enabled || echo "1")
local default_policy=$(uci -q get client-guardian.config.default_policy || echo "quarantine")
- local portal_enabled=$(uci -q get client-guardian.portal.enabled || echo "1")
json_add_boolean "enabled" "$enabled"
json_add_string "default_policy" "$default_policy"
- json_add_boolean "portal_enabled" "$portal_enabled"
# Count clients by status
local total_known=0
@@ -110,6 +269,243 @@ count_zones() {
zone_count=$((zone_count + 1))
}
+# Threat Intelligence Integration
+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
+
+ # 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
+}
+
+enrich_client_with_threats() {
+ local ip="$1"
+ local mac="$2"
+
+ # Get threat data
+ local threats=$(get_client_threats "$ip" "$mac")
+
+ # Count threats and find max risk score
+ local threat_count=0
+ local max_risk_score=0
+
+ if [ -n "$threats" ]; then
+ threat_count=$(echo "$threats" | jsonfilter -e '@[*].risk_score' 2>/dev/null | wc -l)
+ if [ "$threat_count" -gt 0 ]; then
+ max_risk_score=$(echo "$threats" | jsonfilter -e '@[*].risk_score' 2>/dev/null | sort -rn | head -1)
+ fi
+ fi
+
+ # Add threat fields to JSON
+ json_add_int "threat_count" "${threat_count:-0}"
+ 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
+}
+
+# Auto-ban/quarantine based on threat score
+check_threat_auto_actions() {
+ local mac="$1"
+ local ip="$2"
+ local risk_score="$3"
+
+ # Check if threat intelligence and auto-actions are enabled
+ local threat_enabled=$(uci -q get client-guardian.threat_policy.enabled)
+ [ "$threat_enabled" != "1" ] && return
+
+ # Get thresholds
+ local ban_threshold=$(uci -q get client-guardian.threat_policy.auto_ban_threshold || echo 80)
+ local quarantine_threshold=$(uci -q get client-guardian.threat_policy.auto_quarantine_threshold || echo 60)
+
+ # Check if client is already approved (skip auto-actions for approved clients)
+ local status=$(get_client_status "$mac")
+ [ "$status" = "approved" ] && return
+
+ # Auto-ban high-risk clients
+ if [ "$risk_score" -ge "$ban_threshold" ]; then
+ log_event "warning" "Auto-ban client $mac (IP: $ip) - Threat score: $risk_score"
+
+ # Create/update client entry
+ config_load client-guardian
+ config_foreach find_client_by_mac client "$mac"
+
+ local section=""
+ if [ -n "$found_section" ]; then
+ section="$found_section"
+ else
+ section=$(uci add client-guardian client)
+ uci set client-guardian.$section.mac="$mac"
+ uci set client-guardian.$section.name="Auto-banned Device"
+ uci set client-guardian.$section.first_seen="$(date '+%Y-%m-%d %H:%M:%S')"
+ fi
+
+ uci set client-guardian.$section.status="banned"
+ uci set client-guardian.$section.zone="blocked"
+ uci set client-guardian.$section.ban_reason="Auto-banned: Threat score $risk_score"
+ uci set client-guardian.$section.ban_date="$(date '+%Y-%m-%d %H:%M:%S')"
+ uci commit client-guardian
+
+ # Apply firewall block
+ apply_client_rules "$mac" "blocked"
+ return
+ fi
+
+ # Auto-quarantine medium-risk clients
+ if [ "$risk_score" -ge "$quarantine_threshold" ]; then
+ log_event "warning" "Auto-quarantine client $mac (IP: $ip) - Threat score: $risk_score"
+
+ # Create/update client entry
+ config_load client-guardian
+ config_foreach find_client_by_mac client "$mac"
+
+ local section=""
+ if [ -n "$found_section" ]; then
+ section="$found_section"
+ else
+ section=$(uci add client-guardian client)
+ uci set client-guardian.$section.mac="$mac"
+ uci set client-guardian.$section.name="Auto-quarantined Device"
+ uci set client-guardian.$section.first_seen="$(date '+%Y-%m-%d %H:%M:%S')"
+ fi
+
+ uci set client-guardian.$section.status="unknown"
+ uci set client-guardian.$section.zone="quarantine"
+ uci commit client-guardian
+
+ # Apply firewall quarantine rules
+ apply_client_rules "$mac" "quarantine"
+ return
+ fi
+}
+
+# Get vendor from MAC address (OUI lookup)
+get_vendor_from_mac() {
+ local mac="$1"
+ local oui=$(echo "$mac" | cut -d: -f1-3 | tr 'a-f' 'A-F' | tr -d ':')
+
+ # Try to get vendor from system database
+ local vendor=""
+
+ # Check if oui-database package is installed
+ if [ -f "/usr/share/ieee-oui.txt" ]; then
+ vendor=$(grep -i "^$oui" /usr/share/ieee-oui.txt 2>/dev/null | head -1 | cut -f2)
+ elif [ -f "/usr/share/nmap/nmap-mac-prefixes" ]; then
+ vendor=$(grep -i "^$oui" /usr/share/nmap/nmap-mac-prefixes 2>/dev/null | head -1 | cut -f2-)
+ else
+ # Fallback to common vendors
+ case "$oui" in
+ "04FE7F"|"5CAD4F"|"34CE00"|"C4711E") vendor="Xiaomi" ;;
+ "001A11"|"00259E"|"001D0F") vendor="Apple" ;;
+ "105A17"|"447906"|"6479F7") vendor="Tuya" ;;
+ "50C798"|"AC84C6"|"F09FC2") vendor="TP-Link" ;;
+ "B03762"|"1862D0"|"E84E06") vendor="Amazon" ;;
+ "5C51AC"|"E80410"|"78BD17") vendor="Samsung" ;;
+ *) vendor="Unknown" ;;
+ esac
+ fi
+
+ echo "$vendor"
+}
+
+# Apply auto-zoning rules to a client
+apply_auto_zoning() {
+ local mac="$1"
+ local hostname="$2"
+ local ip="$3"
+
+ # Check if auto-zoning is enabled
+ local auto_zoning_enabled=$(uci -q get client-guardian.config.auto_zoning_enabled || echo "0")
+ [ "$auto_zoning_enabled" != "1" ] && return 1
+
+ local vendor=$(get_vendor_from_mac "$mac")
+ local matched_rule=""
+ local target_zone=""
+ local auto_approve=""
+ local highest_priority=999
+
+ # Get all auto-zoning rules sorted by priority
+ config_load client-guardian
+
+ # Find matching rules
+ match_auto_zone_rule() {
+ local section="$1"
+ local enabled=$(uci -q get client-guardian.$section.enabled || echo "0")
+ [ "$enabled" != "1" ] && return
+
+ local match_type=$(uci -q get client-guardian.$section.match_type)
+ local priority=$(uci -q get client-guardian.$section.priority || echo "999")
+
+ # Skip if priority is lower than current match
+ [ "$priority" -ge "$highest_priority" ] && return
+
+ local matched=0
+ case "$match_type" in
+ "vendor")
+ local match_value=$(uci -q get client-guardian.$section.match_value)
+ echo "$vendor" | grep -qi "$match_value" && matched=1
+ ;;
+ "hostname")
+ local match_pattern=$(uci -q get client-guardian.$section.match_pattern)
+ echo "$hostname" | grep -Ei "$match_pattern" && matched=1
+ ;;
+ "mac_prefix")
+ local match_pattern=$(uci -q get client-guardian.$section.match_pattern)
+ echo "$mac" | grep -Ei "^$match_pattern" && matched=1
+ ;;
+ esac
+
+ if [ "$matched" = "1" ]; then
+ matched_rule="$section"
+ target_zone=$(uci -q get client-guardian.$section.target_zone)
+ auto_approve=$(uci -q get client-guardian.$section.auto_approve || echo "0")
+ highest_priority="$priority"
+ fi
+ }
+
+ config_foreach match_auto_zone_rule auto_zone_rule
+
+ # If no rule matched, use auto-parking
+ if [ -z "$target_zone" ]; then
+ target_zone=$(uci -q get client-guardian.config.auto_parking_zone || echo "guest")
+ auto_approve=$(uci -q get client-guardian.config.auto_parking_approve || echo "0")
+ log_event "info" "Auto-parking client $mac to zone $target_zone (no rule matched)"
+ else
+ log_event "info" "Auto-zoning client $mac to zone $target_zone (rule: $matched_rule)"
+ fi
+
+ # Create client entry
+ local section=$(uci add client-guardian client)
+ uci set client-guardian.$section.mac="$mac"
+ uci set client-guardian.$section.name="${hostname:-Unknown Device}"
+ uci set client-guardian.$section.zone="$target_zone"
+ uci set client-guardian.$section.first_seen="$(date '+%Y-%m-%d %H:%M:%S')"
+ uci set client-guardian.$section.last_seen="$(date '+%Y-%m-%d %H:%M:%S')"
+ uci set client-guardian.$section.vendor="$vendor"
+
+ if [ "$auto_approve" = "1" ]; then
+ uci set client-guardian.$section.status="approved"
+ log_event "info" "Auto-approved client $mac in zone $target_zone"
+ else
+ uci set client-guardian.$section.status="unknown"
+ fi
+
+ uci commit client-guardian
+
+ # Apply firewall rules
+ apply_client_rules "$mac" "$target_zone"
+
+ return 0
+}
+
# Get all clients (known + detected)
get_clients() {
json_init
@@ -148,15 +544,42 @@ get_clients() {
json_add_string "last_seen" "$(uci -q get client-guardian.$found_section.last_seen)"
json_add_string "notes" "$(uci -q get client-guardian.$found_section.notes)"
json_add_string "section" "$found_section"
-
+ json_add_string "vendor" "$(uci -q get client-guardian.$found_section.vendor || echo 'Unknown')"
+
# Update last seen
uci set client-guardian.$found_section.last_seen="$(date '+%Y-%m-%d %H:%M:%S')"
else
- 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')"
+ # New client detected - apply auto-zoning if enabled
+ if apply_auto_zoning "$mac" "$hostname" "$ip"; then
+ # Auto-zoning succeeded, reload and get the new section
+ config_load client-guardian
+ config_foreach find_client_by_mac client "$mac"
+
+ if [ -n "$found_section" ]; then
+ json_add_boolean "known" 1
+ json_add_string "name" "$(uci -q get client-guardian.$found_section.name)"
+ json_add_string "zone" "$(uci -q get client-guardian.$found_section.zone)"
+ json_add_string "status" "$(uci -q get client-guardian.$found_section.status)"
+ json_add_string "first_seen" "$(uci -q get client-guardian.$found_section.first_seen)"
+ json_add_string "vendor" "$(uci -q get client-guardian.$found_section.vendor || echo 'Unknown')"
+ else
+ # Fallback in case auto-zoning failed
+ json_add_boolean "known" 0
+ json_add_string "name" "$hostname"
+ json_add_string "zone" "quarantine"
+ json_add_string "status" "unknown"
+ json_add_string "first_seen" "$(date '+%Y-%m-%d %H:%M:%S')"
+ json_add_string "vendor" "$(get_vendor_from_mac "$mac")"
+ fi
+ else
+ # Auto-zoning disabled or failed - use default quarantine
+ json_add_boolean "known" 0
+ json_add_string "name" "$hostname"
+ json_add_string "zone" "quarantine"
+ json_add_string "status" "unknown"
+ json_add_string "first_seen" "$(date '+%Y-%m-%d %H:%M:%S')"
+ json_add_string "vendor" "$(get_vendor_from_mac "$mac")"
+ fi
fi
# Get traffic stats if available
@@ -169,7 +592,10 @@ get_clients() {
fi
json_add_int "rx_bytes" "$rx_bytes"
json_add_int "tx_bytes" "$tx_bytes"
-
+
+ # Enrich with threat intelligence
+ enrich_client_with_threats "$ip" "$mac"
+
json_close_object
found_section=""
done << EOF
@@ -221,6 +647,11 @@ add_offline_client() {
json_add_string "section" "$section"
json_add_int "rx_bytes" 0
json_add_int "tx_bytes" 0
+
+ # Enrich with threat intelligence
+ local ip="$(uci -q get client-guardian.$section.static_ip || echo 'N/A')"
+ enrich_client_with_threats "$ip" "$mac"
+
json_close_object
}
@@ -238,7 +669,25 @@ get_zones() {
output_zone() {
local section="$1"
-
+
+ # Helper to convert true/false to 1/0
+ local internet_val=$(uci -q get client-guardian.$section.internet_access || echo "0")
+ [ "$internet_val" = "true" ] && internet_val="1"
+ [ "$internet_val" = "false" ] && internet_val="0"
+
+ local local_val=$(uci -q get client-guardian.$section.local_access || echo "0")
+ [ "$local_val" = "true" ] && local_val="1"
+ [ "$local_val" = "false" ] && local_val="0"
+
+ local inter_val=$(uci -q get client-guardian.$section.inter_client || echo "0")
+ [ "$inter_val" = "true" ] && inter_val="1"
+ [ "$inter_val" = "false" ] && inter_val="0"
+
+ local time_val=$(uci -q get client-guardian.$section.time_restrictions || echo "0")
+ [ "$time_val" = "true" ] && time_val="1"
+ [ "$time_val" = "false" ] && time_val="0"
+
+
json_add_object
json_add_string "id" "$section"
json_add_string "name" "$(uci -q get client-guardian.$section.name)"
@@ -246,19 +695,18 @@ output_zone() {
json_add_string "network" "$(uci -q get client-guardian.$section.network)"
json_add_string "color" "$(uci -q get client-guardian.$section.color)"
json_add_string "icon" "$(uci -q get client-guardian.$section.icon)"
- json_add_boolean "internet_access" "$(uci -q get client-guardian.$section.internet_access || echo 0)"
- json_add_boolean "local_access" "$(uci -q get client-guardian.$section.local_access || echo 0)"
- json_add_boolean "inter_client" "$(uci -q get client-guardian.$section.inter_client || echo 0)"
+ json_add_boolean "internet_access" "$internet_val"
+ json_add_boolean "local_access" "$local_val"
+ json_add_boolean "inter_client" "$inter_val"
json_add_int "bandwidth_limit" "$(uci -q get client-guardian.$section.bandwidth_limit || echo 0)"
- json_add_boolean "time_restrictions" "$(uci -q get client-guardian.$section.time_restrictions || echo 0)"
+ json_add_boolean "time_restrictions" "$time_val"
json_add_string "content_filter" "$(uci -q get client-guardian.$section.content_filter)"
- json_add_boolean "portal_required" "$(uci -q get client-guardian.$section.portal_required || echo 0)"
-
+
# Count clients in zone
local count=0
config_foreach count_zone_clients client "$section"
json_add_int "client_count" "$count"
-
+
json_close_object
}
@@ -319,29 +767,6 @@ output_schedule() {
json_close_object
}
-# Get portal configuration
-get_portal() {
- json_init
-
- json_add_boolean "enabled" "$(uci -q get client-guardian.portal.enabled || echo 1)"
- json_add_string "title" "$(uci -q get client-guardian.portal.title)"
- json_add_string "subtitle" "$(uci -q get client-guardian.portal.subtitle)"
- json_add_string "logo" "$(uci -q get client-guardian.portal.logo)"
- json_add_string "background_color" "$(uci -q get client-guardian.portal.background_color)"
- json_add_string "accent_color" "$(uci -q get client-guardian.portal.accent_color)"
- json_add_boolean "require_terms" "$(uci -q get client-guardian.portal.require_terms || echo 0)"
- json_add_string "auth_method" "$(uci -q get client-guardian.portal.auth_method)"
- json_add_boolean "allow_registration" "$(uci -q get client-guardian.portal.allow_registration || echo 0)"
- json_add_boolean "registration_approval" "$(uci -q get client-guardian.portal.registration_approval || echo 1)"
- json_add_boolean "show_bandwidth_info" "$(uci -q get client-guardian.portal.show_bandwidth_info || echo 0)"
-
- # Active sessions count
- local sessions=0
- [ -f "/tmp/client-guardian-sessions" ] && sessions=$(wc -l < /tmp/client-guardian-sessions)
- json_add_int "active_sessions" "$sessions"
-
- json_dump
-}
# Get alert configuration
get_alerts() {
@@ -410,6 +835,256 @@ get_logs() {
json_dump
}
+# Profile Management Functions
+
+# List available zone profiles
+list_profiles() {
+ local profiles_file="/etc/client-guardian/profiles.json"
+
+ if [ -f "$profiles_file" ]; then
+ cat "$profiles_file"
+ else
+ echo '{"profiles":[]}'
+ fi
+}
+
+# Apply a zone profile
+apply_profile() {
+ read input
+ json_load "$input"
+ json_get_var profile_id profile_id
+ json_get_var auto_refresh auto_refresh
+ json_get_var refresh_interval refresh_interval
+ json_get_var threat_enabled threat_enabled
+ json_get_var auto_ban_threshold auto_ban_threshold
+ json_get_var auto_quarantine_threshold auto_quarantine_threshold
+
+ json_init
+
+ if [ -z "$profile_id" ]; then
+ json_add_boolean "success" 0
+ json_add_string "error" "Profile ID required"
+ json_dump
+ return
+ fi
+
+ local profiles_file="/etc/client-guardian/profiles.json"
+
+ if [ ! -f "$profiles_file" ]; then
+ json_add_boolean "success" 0
+ json_add_string "error" "Profiles file not found"
+ json_dump
+ return
+ fi
+
+ # Extract profile zones
+ local profile_data=$(cat "$profiles_file" | jsonfilter -e "@.profiles[@.id='$profile_id']")
+
+ if [ -z "$profile_data" ]; then
+ json_add_boolean "success" 0
+ json_add_string "error" "Profile not found: $profile_id"
+ json_dump
+ return
+ fi
+
+ # Remove existing zones (except quarantine and blocked which are system)
+ local existing_zones=$(uci show client-guardian | grep "=zone" | cut -d. -f2 | cut -d= -f1)
+ for zone_section in $existing_zones; do
+ local zone_id=$(uci -q get client-guardian.$zone_section 2>/dev/null || echo "$zone_section")
+ if [ "$zone_id" != "quarantine" ] && [ "$zone_id" != "blocked" ]; then
+ uci delete client-guardian.$zone_section 2>/dev/null
+ fi
+ done
+
+ # Parse and create zones from profile
+ local zone_count=0
+ local idx=0
+
+ # Iterate through zones by index (up to reasonable limit)
+ while [ "$idx" -lt "20" ]; do
+ local zone_id=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].id" 2>/dev/null)
+
+ # Break if no more zones
+ [ -z "$zone_id" ] && break
+
+ local zone_name=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].name")
+ local zone_desc=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].description")
+ local zone_network=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].network")
+ local zone_color=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].color")
+ local zone_icon=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].icon")
+ local internet=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].internet_access")
+ local local_access=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].local_access")
+ local inter_client=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].inter_client")
+ local bandwidth=$(echo "$profile_data" | jsonfilter -e "@.zones[$idx].bandwidth_limit")
+
+ # Create UCI zone section
+ uci set client-guardian.$zone_id=zone 2>/dev/null
+ [ -n "$zone_name" ] && uci set client-guardian.$zone_id.name="$zone_name" 2>/dev/null
+ [ -n "$zone_desc" ] && uci set client-guardian.$zone_id.description="$zone_desc" 2>/dev/null
+ [ -n "$zone_network" ] && uci set client-guardian.$zone_id.network="$zone_network" 2>/dev/null
+ [ -n "$zone_color" ] && uci set client-guardian.$zone_id.color="$zone_color" 2>/dev/null
+ [ -n "$zone_icon" ] && uci set client-guardian.$zone_id.icon="$zone_icon" 2>/dev/null
+ [ -n "$internet" ] && uci set client-guardian.$zone_id.internet_access="$internet" 2>/dev/null
+ [ -n "$local_access" ] && uci set client-guardian.$zone_id.local_access="$local_access" 2>/dev/null
+ [ -n "$inter_client" ] && uci set client-guardian.$zone_id.inter_client="$inter_client" 2>/dev/null
+ uci set client-guardian.$zone_id.bandwidth_limit="${bandwidth:-0}" 2>/dev/null
+
+ zone_count=$((zone_count + 1))
+ idx=$((idx + 1))
+ done
+
+ # 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
+
+ # Apply threat intelligence settings (create section if needed)
+ uci set client-guardian.threat_policy=threat_policy 2>/dev/null
+ [ -n "$threat_enabled" ] && uci set client-guardian.threat_policy.enabled="$threat_enabled" 2>/dev/null
+ [ -n "$auto_ban_threshold" ] && uci set client-guardian.threat_policy.auto_ban_threshold="$auto_ban_threshold" 2>/dev/null
+ [ -n "$auto_quarantine_threshold" ] && uci set client-guardian.threat_policy.auto_quarantine_threshold="$auto_quarantine_threshold" 2>/dev/null
+
+ uci commit client-guardian 2>/dev/null
+
+ # Sync firewall zones
+ sync_firewall_zones
+
+ log_event "info" "Applied profile: $profile_id ($zone_count zones)"
+
+ json_add_boolean "success" 1
+ json_add_string "message" "Profile $profile_id applied successfully"
+ json_add_int "zones_created" "$zone_count"
+ json_dump
+}
+
+# Firewall Zone Synchronization Functions
+
+# Ensure Client Guardian zones exist in firewall
+sync_firewall_zones() {
+ # Check if firewall zones need to be created
+ config_load client-guardian
+ config_foreach create_firewall_zone zone
+}
+
+# Create firewall zone for Client Guardian zone
+create_firewall_zone() {
+ local section="$1"
+ local zone_name=$(uci -q get client-guardian.$section.name)
+ local network=$(uci -q get client-guardian.$section.network)
+ local internet_access=$(uci -q get client-guardian.$section.internet_access)
+ local local_access=$(uci -q get client-guardian.$section.local_access)
+
+ # Skip if no network defined
+ [ -z "$network" ] && return
+
+ # Check if firewall zone exists
+ local fw_zone_exists=$(uci show firewall | grep -c "firewall.*\.name='$network'")
+
+ if [ "$fw_zone_exists" = "0" ]; then
+ # Create firewall zone
+ local fw_section=$(uci add firewall zone)
+ uci set firewall.$fw_section.name="$network"
+ uci set firewall.$fw_section.input="REJECT"
+ uci set firewall.$fw_section.output="ACCEPT"
+ uci set firewall.$fw_section.forward="REJECT"
+ uci add_list firewall.$fw_section.network="$network"
+
+ # Add forwarding rule to WAN if internet access allowed
+ if [ "$internet_access" = "1" ]; then
+ local fwd_section=$(uci add firewall forwarding)
+ uci set firewall.$fwd_section.src="$network"
+ uci set firewall.$fwd_section.dest="wan"
+ fi
+
+ # Add forwarding rule to LAN if local access allowed
+ if [ "$local_access" = "1" ]; then
+ local fwd_section=$(uci add firewall forwarding)
+ uci set firewall.$fwd_section.src="$network"
+ uci set firewall.$fwd_section.dest="lan"
+ fi
+
+ uci commit firewall
+ log_event "info" "Created firewall zone: $network"
+ fi
+}
+
+# Apply MAC-based firewall rules for client
+apply_client_rules() {
+ local mac="$1"
+ local zone="$2"
+
+ # Remove existing rules for this MAC
+ remove_client_rules "$mac"
+
+ # Get zone configuration
+ local zone_network=""
+ local zone_internet=""
+ local zone_local=""
+
+ config_load client-guardian
+ config_foreach check_zone zone "$zone"
+
+ # Apply rules based on zone
+ if [ "$zone" = "blocked" ] || [ "$zone_network" = "null" ]; then
+ # Full block - drop all traffic from this MAC
+ local rule_section=$(uci add firewall rule)
+ uci set firewall.$rule_section.src="*"
+ uci set firewall.$rule_section.src_mac="$mac"
+ uci set firewall.$rule_section.target="DROP"
+ uci set firewall.$rule_section.name="CG_BLOCK_$mac"
+ uci commit firewall
+ log_event "info" "Applied BLOCK rule for MAC: $mac"
+ else
+ # Zone-based access control
+ # Allow DHCP for client
+ local rule_section=$(uci add firewall rule)
+ uci set firewall.$rule_section.src="*"
+ uci set firewall.$rule_section.src_mac="$mac"
+ uci set firewall.$rule_section.proto="udp"
+ uci set firewall.$rule_section.dest_port="67-68"
+ uci set firewall.$rule_section.target="ACCEPT"
+ uci set firewall.$rule_section.name="CG_DHCP_$mac"
+
+ # Allow DNS
+ rule_section=$(uci add firewall rule)
+ uci set firewall.$rule_section.src="*"
+ uci set firewall.$rule_section.src_mac="$mac"
+ uci set firewall.$rule_section.proto="udp"
+ uci set firewall.$rule_section.dest_port="53"
+ uci set firewall.$rule_section.target="ACCEPT"
+ uci set firewall.$rule_section.name="CG_DNS_$mac"
+
+ uci commit firewall
+ log_event "info" "Applied zone rules for MAC: $mac in zone: $zone"
+ fi
+
+ # Reload firewall
+ /etc/init.d/firewall reload >/dev/null 2>&1 &
+}
+
+# Remove firewall rules for client
+remove_client_rules() {
+ local mac="$1"
+ mac=$(echo "$mac" | tr 'a-f' 'A-F') # Firewall rules use uppercase
+
+ # Find and remove rules with this MAC
+ uci show firewall | grep -i "$mac" | cut -d. -f1-2 | sort -u | while read rule; do
+ uci delete "$rule" 2>/dev/null
+ done
+ uci commit firewall 2>/dev/null
+}
+
+# Helper to find zone config
+check_zone() {
+ local section="$1"
+ local target_zone="$2"
+
+ if [ "$section" = "$target_zone" ]; then
+ zone_network=$(uci -q get client-guardian.$section.network)
+ zone_internet=$(uci -q get client-guardian.$section.internet_access)
+ zone_local=$(uci -q get client-guardian.$section.local_access)
+ fi
+}
+
# Approve client
approve_client() {
read input
@@ -506,10 +1181,10 @@ ban_client() {
uci set client-guardian.$section.ban_reason="$reason"
uci set client-guardian.$section.ban_date="$(date '+%Y-%m-%d %H:%M:%S')"
uci commit client-guardian
-
- # Block in firewall
- block_client "$mac"
-
+
+ # Apply firewall block rules
+ apply_client_rules "$mac" "blocked"
+
log_event "warning" "Client banned: $mac - Reason: $reason"
# Send alert
@@ -662,32 +1337,6 @@ update_zone() {
json_dump
}
-# Update portal settings
-update_portal() {
- read input
- json_load "$input"
- json_get_var title title
- json_get_var subtitle subtitle
- json_get_var accent_color accent_color
- json_get_var auth_method auth_method
- json_get_var guest_password guest_password
-
- json_init
-
- [ -n "$title" ] && uci set client-guardian.portal.title="$title"
- [ -n "$subtitle" ] && uci set client-guardian.portal.subtitle="$subtitle"
- [ -n "$accent_color" ] && uci set client-guardian.portal.accent_color="$accent_color"
- [ -n "$auth_method" ] && uci set client-guardian.portal.auth_method="$auth_method"
- [ -n "$guest_password" ] && uci set client-guardian.portal.guest_password="$guest_password"
-
- uci commit client-guardian
-
- log_event "info" "Portal settings updated"
-
- json_add_boolean "success" 1
- json_add_string "message" "Portal updated successfully"
- json_dump
-}
# Helper: Apply client firewall rules
apply_client_rules() {
@@ -752,68 +1401,16 @@ send_alert_internal() {
# Nodogsplash Captive Portal Integration
# ===================================
-# List active captive portal sessions (nodogsplash)
-list_sessions() {
- json_init
- json_add_array "sessions"
-
- # Check if nodogsplash is running
- if pidof nodogsplash >/dev/null; then
- # Get sessions from ndsctl
- ndsctl status 2>/dev/null | grep -A 100 "Client" | while read line; do
- # Parse ndsctl output
- # Format: IP MAC Duration Download Upload
- if echo "$line" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"; then
- local ip=$(echo "$line" | awk '{print $1}')
- local mac=$(echo "$line" | awk '{print $2}' | tr 'A-F' 'a-f')
- local duration=$(echo "$line" | awk '{print $3}')
- local downloaded=$(echo "$line" | awk '{print $4}')
- local uploaded=$(echo "$line" | awk '{print $5}')
-
- # Get hostname
- local hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}')
- [ -z "$hostname" ] && hostname="Unknown"
-
- json_add_object
- json_add_string "ip" "$ip"
- json_add_string "mac" "$mac"
- json_add_string "hostname" "$hostname"
- json_add_int "duration" "$duration"
- json_add_int "downloaded" "$downloaded"
- json_add_int "uploaded" "$uploaded"
- json_add_string "state" "authenticated"
- json_close_object
- fi
- done
- fi
-
- json_close_array
-
- # Add nodogsplash status
- json_add_object "nodogsplash"
- if pidof nodogsplash >/dev/null; then
- json_add_boolean "running" 1
- json_add_string "status" "active"
- else
- json_add_boolean "running" 0
- json_add_string "status" "stopped"
- fi
- json_close_object
-
- json_dump
-}
# Get default policy
get_policy() {
json_init
local policy=$(uci -q get client-guardian.config.default_policy || echo "captive")
- local portal_enabled=$(uci -q get client-guardian.portal.enabled || echo "1")
local auto_approve=$(uci -q get client-guardian.config.auto_approve || echo "0")
local session_timeout=$(uci -q get client-guardian.config.session_timeout || echo "86400")
json_add_string "default_policy" "$policy"
- json_add_boolean "portal_enabled" "$portal_enabled"
json_add_boolean "auto_approve" "$auto_approve"
json_add_int "session_timeout" "$session_timeout"
@@ -831,7 +1428,6 @@ set_policy() {
read input
json_load "$input"
json_get_var policy policy
- json_get_var portal_enabled portal_enabled
json_get_var auto_approve auto_approve
json_get_var session_timeout session_timeout
@@ -857,7 +1453,6 @@ set_policy() {
;;
esac
- [ -n "$portal_enabled" ] && uci set client-guardian.portal.enabled="$portal_enabled"
[ -n "$auto_approve" ] && uci set client-guardian.config.auto_approve="$auto_approve"
[ -n "$session_timeout" ] && uci set client-guardian.config.session_timeout="$session_timeout"
@@ -878,102 +1473,8 @@ set_policy() {
}
# Authorize client via nodogsplash
-authorize_client() {
- read input
- json_load "$input"
- json_get_var mac mac
- json_get_var ip ip
-
- json_init
-
- if [ -z "$mac" ]; then
- json_add_boolean "success" 0
- json_add_string "error" "MAC address required"
- json_dump
- return
- fi
-
- mac=$(echo "$mac" | tr 'A-F' 'a-f')
-
- # Use ndsctl to authorize
- if pidof nodogsplash >/dev/null; then
- if [ -n "$ip" ]; then
- # Authorize by IP if provided
- ndsctl auth "$ip" 2>&1
- else
- # Find IP by MAC
- local client_ip=$(cat /proc/net/arp | grep -i "$mac" | awk '{print $1}' | head -1)
- if [ -n "$client_ip" ]; then
- ndsctl auth "$client_ip" 2>&1
- ip="$client_ip"
- else
- json_add_boolean "success" 0
- json_add_string "error" "Client not found or offline"
- json_dump
- return
- fi
- fi
-
- log_event "info" "Client authorized via nodogsplash: $mac ($ip)"
-
- json_add_boolean "success" 1
- json_add_string "message" "Client $mac authorized"
- json_add_string "ip" "$ip"
- else
- json_add_boolean "success" 0
- json_add_string "error" "Nodogsplash not running"
- fi
-
- json_dump
-}
# Deauthorize client via nodogsplash
-deauthorize_client() {
- read input
- json_load "$input"
- json_get_var mac mac
- json_get_var ip ip
-
- json_init
-
- if [ -z "$mac" ]; then
- json_add_boolean "success" 0
- json_add_string "error" "MAC address required"
- json_dump
- return
- fi
-
- mac=$(echo "$mac" | tr 'A-F' 'a-f')
-
- # Use ndsctl to deauthorize
- if pidof nodogsplash >/dev/null; then
- if [ -n "$ip" ]; then
- ndsctl deauth "$ip" 2>&1
- else
- # Find IP by MAC
- local client_ip=$(cat /proc/net/arp | grep -i "$mac" | awk '{print $1}' | head -1)
- if [ -n "$client_ip" ]; then
- ndsctl deauth "$client_ip" 2>&1
- ip="$client_ip"
- else
- json_add_boolean "success" 0
- json_add_string "error" "Client not found in active sessions"
- json_dump
- return
- fi
- fi
-
- log_event "info" "Client deauthorized via nodogsplash: $mac ($ip)"
-
- json_add_boolean "success" 1
- json_add_string "message" "Client $mac deauthorized"
- else
- json_add_boolean "success" 0
- json_add_string "error" "Nodogsplash not running"
- fi
-
- json_dump
-}
# Get client details
get_client() {
@@ -1034,7 +1535,7 @@ get_client() {
# Main dispatcher
case "$1" in
list)
- echo '{"status":{},"clients":{},"zones":{},"parental":{},"portal":{},"alerts":{},"logs":{"limit":"int","level":"str"},"approve_client":{"mac":"str","name":"str","zone":"str","notes":"str"},"ban_client":{"mac":"str","reason":"str"},"quarantine_client":{"mac":"str"},"update_client":{"section":"str","name":"str","zone":"str","notes":"str","daily_quota":"int","static_ip":"str"},"update_zone":{"id":"str","name":"str","bandwidth_limit":"int","content_filter":"str"},"update_portal":{"title":"str","subtitle":"str","accent_color":"str"},"send_test_alert":{"type":"str"},"list_sessions":{},"get_policy":{},"set_policy":{"policy":"str","portal_enabled":"bool","auto_approve":"bool","session_timeout":"int"},"authorize_client":{"mac":"str","ip":"str"},"deauthorize_client":{"mac":"str","ip":"str"},"get_client":{"mac":"str"}}'
+ echo '{"status":{},"clients":{},"zones":{},"parental":{},"alerts":{},"logs":{"limit":"int","level":"str"},"approve_client":{"mac":"str","name":"str","zone":"str","notes":"str"},"ban_client":{"mac":"str","reason":"str"},"quarantine_client":{"mac":"str"},"update_client":{"section":"str","name":"str","zone":"str","notes":"str","daily_quota":"int","static_ip":"str"},"update_zone":{"id":"str","name":"str","bandwidth_limit":"int","content_filter":"str"},"send_test_alert":{"type":"str"},"get_policy":{},"set_policy":{"policy":"str","auto_approve":"bool","session_timeout":"int"},"get_client":{"mac":"str"},"sync_zones":{},"list_profiles":{},"apply_profile":{"profile_id":"str","auto_refresh":"str","refresh_interval":"str","threat_enabled":"str","auto_ban_threshold":"str","auto_quarantine_threshold":"str"}}'
;;
call)
case "$2" in
@@ -1042,7 +1543,6 @@ case "$1" in
clients) get_clients ;;
zones) get_zones ;;
parental) get_parental ;;
- portal) get_portal ;;
alerts) get_alerts ;;
logs) get_logs ;;
approve_client) approve_client ;;
@@ -1050,14 +1550,19 @@ case "$1" in
quarantine_client) quarantine_client ;;
update_client) update_client ;;
update_zone) update_zone ;;
- update_portal) update_portal ;;
send_test_alert) send_test_alert ;;
- list_sessions) list_sessions ;;
get_policy) get_policy ;;
set_policy) set_policy ;;
- authorize_client) authorize_client ;;
- deauthorize_client) deauthorize_client ;;
get_client) get_client ;;
+ sync_zones)
+ json_init
+ sync_firewall_zones
+ json_add_boolean "success" 1
+ json_add_string "message" "Firewall zones synchronized"
+ json_dump
+ ;;
+ list_profiles) list_profiles ;;
+ apply_profile) apply_profile ;;
*) echo '{"error": "Unknown method"}' ;;
esac
;;
diff --git a/package/secubox/luci-app-client-guardian/root/usr/share/luci/menu.d/luci-app-client-guardian.json b/package/secubox/luci-app-client-guardian/root/usr/share/luci/menu.d/luci-app-client-guardian.json
index 8a03d194..3d0ff564 100644
--- a/package/secubox/luci-app-client-guardian/root/usr/share/luci/menu.d/luci-app-client-guardian.json
+++ b/package/secubox/luci-app-client-guardian/root/usr/share/luci/menu.d/luci-app-client-guardian.json
@@ -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"
+ }
}
}
\ No newline at end of file
diff --git a/package/secubox/luci-app-client-guardian/root/usr/share/rpcd/acl.d/luci-app-client-guardian.json b/package/secubox/luci-app-client-guardian/root/usr/share/rpcd/acl.d/luci-app-client-guardian.json
index 3cb62d70..ade504a6 100644
--- a/package/secubox/luci-app-client-guardian/root/usr/share/rpcd/acl.d/luci-app-client-guardian.json
+++ b/package/secubox/luci-app-client-guardian/root/usr/share/rpcd/acl.d/luci-app-client-guardian.json
@@ -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" ]
}
}
}