diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 1887f91c..20c3bfe8 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -288,7 +288,66 @@
"Bash(do scp /home/reepost/CyberMindStudio/_files/secubox-openwrt/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/$f root@192.168.8.191:/www/luci-static/resources/view/client-guardian/)",
"Bash(for f in clients.js overview.js)",
"Bash(for f in htdocs/luci-static/resources/view/client-guardian/settings.js htdocs/luci-static/resources/client-guardian/api.js root/usr/libexec/rpcd/luci.client-guardian root/etc/config/client-guardian)",
- "Bash(do scp \"$f\" root@192.168.8.191:/$f#root/)"
+ "Bash(do scp \"$f\" root@192.168.8.191:/$f#root/)",
+ "Bash(ssh root@192.168.255.1:*)",
+ "Bash(SSH=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\")",
+ "Bash($SSH root@192.168.255.1 \"ubus list | grep secubox; ls -la /usr/libexec/rpcd/luci.secubox* 2>/dev/null; ls -la /www/luci-static/resources/view/secubox/ 2>/dev/null\")",
+ "Bash($SSH root@192.168.255.1 \"cat /usr/share/luci/menu.d/luci-app-secubox.json 2>/dev/null || echo ''NOT FOUND''\")",
+ "Bash($SSH root@192.168.255.1 \"ubus call luci.secubox getModules ''{}'' 2>&1 | head -50\")",
+ "Bash($SSH root@192.168.255.1 \"ubus -v list luci.secubox\")",
+ "Bash($SSH root@192.168.255.1 \"echo ''{\"\"method\"\":\"\"getModules\"\"}'' | /usr/libexec/rpcd/luci.secubox call getModules 2>&1 | head -30\")",
+ "Bash($SSH root@192.168.255.1 \"sed -n ''90,100p'' /usr/sbin/secubox-appstore\")",
+ "Bash(SCP=\"scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\":*)",
+ "Bash(__NEW_LINE__ $SCP /home/reepost/CyberMindStudio/_files/secubox-openwrt/package/secubox/secubox-core/root/usr/sbin/secubox-appstore root@192.168.255.1:/usr/sbin/secubox-appstore)",
+ "Bash($SSH root@192.168.255.1 \"chmod 755 /usr/sbin/secubox-appstore && /etc/init.d/rpcd restart\")",
+ "Bash(SCP=\"scp -O -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\")",
+ "Bash($SSH root@192.168.255.1 \"chmod 755 /usr/sbin/secubox-appstore && /etc/init.d/rpcd restart && echo ''✅ Fixed''\")",
+ "Bash($SSH root@192.168.255.1 \"ubus call luci.secubox getModules ''{}'' 2>&1 | head -30\")",
+ "Bash($SSH root@192.168.255.1 \"grep -A50 ''getModules'' /usr/libexec/rpcd/luci.secubox | head -60\")",
+ "Bash($SSH root@192.168.255.1 \"chmod 755 /usr/sbin/secubox-appstore && /etc/init.d/rpcd restart && ubus call luci.secubox getModules ''{}'' 2>&1 | head -50\")",
+ "Bash($SSH root@192.168.255.1 \"sleep 2 && ubus call luci.secubox getModules ''{}'' 2>&1 | head -80\")",
+ "Bash($SSH root@192.168.255.1 \"cat /usr/share/secubox/plugins/catalog/crowdsec-dashboard.json 2>/dev/null | head -30\")",
+ "Bash($SSH root@192.168.255.1 \"grep -E ''^\\(luci-app-|secubox-\\)'' /tmp/secubox-installed-cache 2>/dev/null | head -20\")",
+ "Bash($SSH root@192.168.255.1 \"chmod 755 /usr/sbin/secubox-appstore && rm -f /tmp/secubox-installed-cache && /etc/init.d/rpcd restart\")",
+ "Bash($SSH root@192.168.255.1 \"sleep 2 && ubus call luci.secubox getModules ''{}'' 2>&1\")",
+ "Bash($SSH root@192.168.255.1 \"chmod 755 /usr/sbin/secubox-appstore && rm -f /tmp/secubox-installed-cache && /etc/init.d/rpcd restart && sleep 2 && ubus call luci.secubox getModules ''{}'' 2>&1 | grep -A12 ''crowdsec-dashboard''\")",
+ "Bash($SSH root@192.168.255.1 \"ubus call luci.secubox getModules ''{}'' 2>&1 | grep -A12 ''netifyd''\")",
+ "Bash($SSH root@192.168.255.1 \"/etc/init.d/netifyd status; ls /etc/rc.d/S*netifyd 2>/dev/null\")",
+ "Bash($SSH root@192.168.255.1 \"chmod 755 /usr/sbin/secubox-appstore && rm -f /tmp/secubox-installed-cache && /etc/init.d/rpcd restart && sleep 2 && ubus call luci.secubox getModules ''{}'' 2>&1 | grep -A12 -E ''\\(netifyd|crowdsec-dashboard\\)''\")",
+ "Bash($SSH root@192.168.255.1 \"cat /etc/config/secubox 2>/dev/null | head -50\")",
+ "Bash($SSH root@192.168.255.1 \"cat /etc/config/firewall | grep -A5 ''luci\\\\|secubox'' | head -30\")",
+ "Bash($SSH root@192.168.255.1 \"/etc/init.d/rpcd restart && sleep 2 && ubus -v list luci.secubox | grep wan\")",
+ "Bash(__NEW_LINE__ $SCP /home/reepost/CyberMindStudio/_files/secubox-openwrt/package/secubox/secubox-core/root/usr/libexec/rpcd/luci.secubox root@192.168.255.1:/usr/libexec/rpcd/luci.secubox)",
+ "Bash($SSH root@192.168.255.1 \"chmod 755 /usr/libexec/rpcd/luci.secubox && /etc/init.d/rpcd restart && sleep 2 && ubus call luci.secubox get_wan_access ''{}''\")",
+ "Bash($SSH root@192.168.255.1 \"/usr/sbin/secubox-wan-access status\")",
+ "Bash($SSH root@192.168.255.1 \"uci show firewall | grep secubox_wan\")",
+ "Bash($SSH root@192.168.255.1 \"nft list ruleset 2>/dev/null | grep -A2 ''secubox\\\\|dport 443'' | head -20\")",
+ "Bash($SSH root@192.168.255.1 \"/etc/init.d/firewall restart && sleep 2 && fw4 print 2>/dev/null | grep -i ''secubox\\\\|443'' | head -10\")",
+ "Bash(SSH=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10\")",
+ "Bash($SSH root@192.168.255.1 \"echo ''connected''\")",
+ "Bash($SSH root@192.168.255.1 \"uci show firewall | grep -E ''=zone$|\\\\.name=''\")",
+ "Bash($SSH root@192.168.255.1 \"uci show firewall | grep -E ''network=|\\\\.input=|\\\\.output=|\\\\.forward=''\")",
+ "Bash(__NEW_LINE__ $SCP /home/reepost/CyberMindStudio/_files/secubox-openwrt/package/secubox/secubox-core/root/usr/sbin/secubox-wan-access root@192.168.255.1:/usr/sbin/secubox-wan-access)",
+ "Bash($SSH root@192.168.255.1 \"chmod 755 /usr/sbin/secubox-wan-access && /usr/sbin/secubox-wan-access apply && /etc/init.d/firewall restart && sleep 2 && /usr/sbin/secubox-wan-access status\")",
+ "Bash($SSH root@192.168.255.1 \"\n# Remove old invalid rule\nuci delete firewall.@rule[0] 2>/dev/null\n\n# Run apply which will create new rules with correct src\n/usr/sbin/secubox-wan-access apply\n\n# Show the new rule\nuci show firewall | grep secubox\n\n# Restart firewall\n/etc/init.d/firewall restart\n\")",
+ "Bash(SSH=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=15\")",
+ "Bash(SSH=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30\")",
+ "Bash($SSH root@192.168.255.1 \"echo connected && uci show firewall | grep secubox\")",
+ "Bash($SSH root@192.168.255.1 \"/etc/init.d/firewall restart && sleep 3 && /usr/sbin/secubox-wan-access status\")",
+ "Bash($SSH root@192.168.255.1 \"fw4 print 2>&1 | grep -i ''secubox\\\\|443'' | head -10\")",
+ "Bash($SSH root@192.168.255.1 \"fw4 print 2>&1 | grep -E ''secubox|tcp dport 443'' | head -10\")",
+ "Bash($SSH root@192.168.255.1 \"cat /usr/share/rpcd/acl.d/luci-app-secubox-admin.json 2>/dev/null || cat /usr/share/rpcd/acl.d/luci-mod-secubox.json 2>/dev/null | head -30\")",
+ "Bash($SSH root@192.168.255.1 \"ls /usr/share/rpcd/acl.d/ | grep -E ''secubox|admin''\")",
+ "Bash($SSH root@192.168.255.1 \"cat /usr/share/rpcd/acl.d/luci-app-secubox-admin.json\")",
+ "Bash($SSH root@192.168.255.1 'cat > /usr/share/rpcd/acl.d/luci-app-secubox-admin.json << ''''EOF''''\n{\n\t\"\"luci-app-secubox-admin\"\": {\n\t\t\"\"description\"\": \"\"SecuBox Admin Control Center\"\",\n\t\t\"\"read\"\": {\n\t\t\t\"\"ubus\"\": {\n\t\t\t\t\"\"luci.secubox\"\": [\n\t\t\t\t\t\"\"get_appstore_apps\"\",\n\t\t\t\t\t\"\"get_appstore_app\"\",\n\t\t\t\t\t\"\"getModules\"\",\n\t\t\t\t\t\"\"getModuleInfo\"\",\n\t\t\t\t\t\"\"get_dashboard_data\"\",\n\t\t\t\t\t\"\"get_system_health\"\",\n\t\t\t\t\t\"\"get_alerts\"\",\n\t\t\t\t\t\"\"getLogs\"\",\n\t\t\t\t\t\"\"listProfiles\"\",\n\t\t\t\t\t\"\"get_catalog_sources\"\",\n\t\t\t\t\t\"\"check_updates\"\",\n\t\t\t\t\t\"\"get_app_versions\"\",\n\t\t\t\t\t\"\"get_changelog\"\",\n\t\t\t\t\t\"\"get_widget_data\"\",\n\t\t\t\t\t\"\"get_wan_access\"\"\n\t\t\t\t]\n\t\t\t},\n\t\t\t\"\"uci\"\": [\n\t\t\t\t\"\"secubox\"\",\n\t\t\t\t\"\"secubox-appstore\"\",\n\t\t\t\t\"\"firewall\"\",\n\t\t\t\t\"\"*\"\"\n\t\t\t]\n\t\t},\n\t\t\"\"write\"\": {\n\t\t\t\"\"ubus\"\": {\n\t\t\t\t\"\"luci.secubox\"\": [\n\t\t\t\t\t\"\"install_appstore_app\"\",\n\t\t\t\t\t\"\"remove_appstore_app\"\",\n\t\t\t\t\t\"\"enable_module\"\",\n\t\t\t\t\t\"\"disable_module\"\",\n\t\t\t\t\t\"\"updateModule\"\",\n\t\t\t\t\t\"\"applyProfile\"\",\n\t\t\t\t\t\"\"set_catalog_source\"\",\n\t\t\t\t\t\"\"sync_catalog\"\",\n\t\t\t\t\t\"\"set_wan_access\"\",\n\t\t\t\t\t\"\"apply_wan_access\"\"\n\t\t\t\t]\n\t\t\t},\n\t\t\t\"\"uci\"\": [\n\t\t\t\t\"\"secubox\"\",\n\t\t\t\t\"\"secubox-appstore\"\",\n\t\t\t\t\"\"firewall\"\",\n\t\t\t\t\"\"*\"\"\n\t\t\t]\n\t\t}\n\t}\n}\nEOF\n/etc/init.d/rpcd restart && echo \"\"ACL updated\"\"')",
+ "Bash($SSH root@192.168.255.1 \"ubus call luci.secubox apply_wan_access ''{}'' 2>&1\")",
+ "Bash($SSH root@192.168.255.1 \"/usr/sbin/secubox-wan-access apply 2>&1\")",
+ "Bash($SSH root@192.168.255.1 \"ubus call luci.secubox apply_wan_access ''{}''\")",
+ "Bash($SSH root@192.168.255.1 \"chmod 755 /usr/libexec/rpcd/luci.secubox && /etc/init.d/rpcd restart && sleep 2 && ubus call luci.secubox apply_wan_access ''{}''\")",
+ "Bash($SSH root@192.168.255.1 \"uci show network | grep -E ''=interface|\\\\.proto=''\")",
+ "Bash($SSH root@192.168.255.1 \"uci show firewall | grep -E ''zone.*name|=forwarding''\")",
+ "Bash(for:*)",
+ "Bash(do sed -i \"s/''require secubox-theme\\\\/theme as Theme'';//g\" \"$f\")"
]
}
}
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/alerts.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/alerts.js
index 7ac558a8..0bacab72 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/alerts.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/alerts.js
@@ -1,6 +1,6 @@
'use strict';
'require view';
-'require secubox-theme/theme as Theme';
+
'require dom';
'require poll';
'require uci';
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 12864f0e..6ea3dab5 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
@@ -1,16 +1,72 @@
'use strict';
'require view';
-'require secubox-theme/theme as Theme';
'require dom';
'require poll';
'require ui';
-'require client-guardian.api as api';
+'require rpc';
+
+var callGetClients = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'clients',
+ expect: { clients: [] }
+});
+
+var callGetZones = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'zones',
+ expect: { zones: [] }
+});
+
+var callApproveClient = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'approve_client',
+ params: ['mac', 'name', 'zone', 'notes']
+});
+
+var callUpdateClient = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'update_client',
+ params: ['section', 'name', 'zone', 'notes', 'daily_quota', 'static_ip']
+});
+
+var callBanClient = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'ban_client',
+ params: ['mac', 'reason']
+});
+
+var callQuarantineClient = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'quarantine_client',
+ params: ['mac']
+});
+
+function formatBytes(bytes) {
+ if (!bytes || bytes === 0) return '0 B';
+ var units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ 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];
+}
+
+function getDeviceIcon(hostname, mac) {
+ hostname = (hostname || '').toLowerCase();
+ mac = (mac || '').toLowerCase();
+ if (hostname.match(/android|iphone|ipad|mobile|phone|samsung|xiaomi|huawei/)) return '📱';
+ if (hostname.match(/pc|laptop|desktop|macbook|imac|windows|linux|ubuntu/)) return '💻';
+ if (hostname.match(/camera|bulb|switch|sensor|thermostat|doorbell|lock/)) return '📷';
+ if (hostname.match(/tv|roku|chromecast|firestick|appletv|media/)) return '📺';
+ if (hostname.match(/playstation|xbox|nintendo|switch|steam/)) return '🎮';
+ if (hostname.match(/router|switch|ap|access[-_]?point|bridge/)) return '🌐';
+ if (hostname.match(/printer|print|hp-|canon-|epson-/)) return '🖨️';
+ return '🔌';
+}
return view.extend({
load: function() {
return Promise.all([
- api.getClients(),
- api.getZones()
+ callGetClients(),
+ callGetZones()
]);
},
@@ -85,7 +141,7 @@ return view.extend({
if (client.status === 'unknown') statusClass += ' quarantine';
if (client.status === 'banned') statusClass += ' banned';
- var deviceIcon = api.getDeviceIcon(client.hostname || client.name, client.mac);
+ var deviceIcon = getDeviceIcon(client.hostname || client.name, client.mac);
var zoneClass = (client.zone || 'unknown').replace('lan_', '');
var self = this;
@@ -109,8 +165,8 @@ return view.extend({
]),
E('span', { 'class': 'cg-client-zone ' + zoneClass }, client.zone || 'unknown'),
E('div', { 'class': 'cg-client-traffic' }, [
- E('div', { 'class': 'cg-client-traffic-value' }, '↓ ' + api.formatBytes(client.rx_bytes || 0)),
- E('div', { 'class': 'cg-client-traffic-label' }, '↑ ' + api.formatBytes(client.tx_bytes || 0))
+ E('div', { 'class': 'cg-client-traffic-value' }, '↓ ' + formatBytes(client.rx_bytes || 0)),
+ E('div', { 'class': 'cg-client-traffic-label' }, '↑ ' + formatBytes(client.tx_bytes || 0))
]),
E('div', { 'class': 'cg-client-actions' }, [
client.status === 'unknown' ? E('div', {
@@ -192,7 +248,7 @@ return view.extend({
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(L.bind(function() {
+ callApproveClient(mac, name, zone, notes).then(L.bind(function() {
ui.hideModal();
ui.addNotification(null, E('p', _('Client approved successfully')), 'success');
this.handleRefresh();
@@ -231,7 +287,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': L.bind(function() {
- api.updateClient(
+ callUpdateClient(
client.section,
document.getElementById('edit-name').value,
document.getElementById('edit-zone').value,
@@ -262,7 +318,7 @@ return view.extend({
E('button', { 'class': 'cg-btn', 'click': ui.hideModal }, _('Annuler')),
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(L.bind(function() {
+ callBanClient(mac, reason).then(L.bind(function() {
ui.hideModal();
ui.addNotification(null, E('p', _('Client banned successfully')), 'info');
this.handleRefresh();
@@ -274,7 +330,7 @@ return view.extend({
handleUnban: function(ev) {
var mac = ev.currentTarget.dataset.mac;
- api.quarantineClient(mac).then(L.bind(function() {
+ callQuarantineClient(mac).then(L.bind(function() {
ui.addNotification(null, E('p', _('Client unbanned successfully')), 'success');
this.handleRefresh();
}, this));
@@ -282,8 +338,8 @@ return view.extend({
handleRefresh: function() {
return Promise.all([
- api.getClients(),
- api.getZones()
+ callGetClients(),
+ callGetZones()
]).then(L.bind(function(data) {
var container = document.querySelector('.client-guardian-dashboard');
if (container) {
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/debug.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/debug.js
index d4948e85..10d0f3b7 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/debug.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/debug.js
@@ -1,6 +1,6 @@
'use strict';
'require view';
-'require secubox-theme/theme as Theme';
+
'require dom';
'require ui';
'require uci';
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/logs.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/logs.js
index c93d2a1f..9b3557b0 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/logs.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/logs.js
@@ -1,6 +1,6 @@
'use strict';
'require view';
-'require secubox-theme/theme as Theme';
+
'require dom';
'require poll';
'require ui';
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 c38383b2..164b39bc 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
@@ -1,18 +1,67 @@
'use strict';
'require view';
-'require secubox-theme/theme as Theme';
'require dom';
'require poll';
'require uci';
'require ui';
-'require client-guardian.api as api';
+'require rpc';
+
+var callGetStatus = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'status'
+});
+
+var callGetClients = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'clients',
+ expect: { clients: [] }
+});
+
+var callGetZones = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'zones',
+ expect: { zones: [] }
+});
+
+var callApproveClient = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'approve_client',
+ params: ['mac', 'name', 'zone', 'notes']
+});
+
+var callBanClient = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'ban_client',
+ params: ['mac', 'reason']
+});
+
+function formatBytes(bytes) {
+ if (!bytes || bytes === 0) return '0 B';
+ var units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ 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];
+}
+
+function getDeviceIcon(hostname, mac) {
+ hostname = (hostname || '').toLowerCase();
+ mac = (mac || '').toLowerCase();
+ if (hostname.match(/android|iphone|ipad|mobile|phone|samsung|xiaomi|huawei/)) return '📱';
+ if (hostname.match(/pc|laptop|desktop|macbook|imac|windows|linux|ubuntu/)) return '💻';
+ if (hostname.match(/camera|bulb|switch|sensor|thermostat|doorbell|lock/)) return '📷';
+ if (hostname.match(/tv|roku|chromecast|firestick|appletv|media/)) return '📺';
+ if (hostname.match(/playstation|xbox|nintendo|switch|steam/)) return '🎮';
+ if (hostname.match(/router|switch|ap|access[-_]?point|bridge/)) return '🌐';
+ if (hostname.match(/printer|print|hp-|canon-|epson-/)) return '🖨️';
+ return '🔌';
+}
return view.extend({
load: function() {
return Promise.all([
- api.getStatus(),
- api.getClients(),
- api.getZones(),
+ callGetStatus(),
+ callGetClients(),
+ callGetZones(),
uci.load('client-guardian')
]);
},
@@ -117,7 +166,7 @@ return view.extend({
if (client.status === 'banned')
statusClass += ' banned';
- var deviceIcon = api.getDeviceIcon(client.hostname || client.name, client.mac);
+ var deviceIcon = getDeviceIcon(client.hostname || client.name, client.mac);
var zoneClass = (client.zone || 'unknown').replace('lan_', '');
var item = E('div', { 'class': 'cg-client-item ' + statusClass }, [
@@ -142,8 +191,8 @@ return view.extend({
]),
E('span', { 'class': 'cg-client-zone ' + zoneClass }, client.zone || 'unknown'),
E('div', { 'class': 'cg-client-traffic' }, [
- E('div', { 'class': 'cg-client-traffic-value' }, '↓ ' + api.formatBytes(client.rx_bytes || 0)),
- E('div', { 'class': 'cg-client-traffic-label' }, '↑ ' + api.formatBytes(client.tx_bytes || 0))
+ E('div', { 'class': 'cg-client-traffic-value' }, '↓ ' + formatBytes(client.rx_bytes || 0)),
+ E('div', { 'class': 'cg-client-traffic-label' }, '↑ ' + formatBytes(client.tx_bytes || 0))
])
]);
@@ -197,7 +246,7 @@ return view.extend({
'class': 'cg-btn cg-btn-success',
'click': L.bind(function() {
var zone = document.getElementById('approve-zone').value;
- api.approveClient(mac, '', zone, '').then(L.bind(function() {
+ callApproveClient(mac, '', zone, '').then(L.bind(function() {
ui.hideModal();
ui.addNotification(null, E('p', _('Client approved successfully')), 'success');
this.handleRefresh();
@@ -222,7 +271,7 @@ return view.extend({
E('button', {
'class': 'cg-btn cg-btn-danger',
'click': L.bind(function() {
- api.banClient(mac, 'Manual ban').then(L.bind(function() {
+ callBanClient(mac, 'Manual ban').then(L.bind(function() {
ui.hideModal();
ui.addNotification(null, E('p', _('Client banned successfully')), 'info');
this.handleRefresh();
@@ -235,24 +284,18 @@ return view.extend({
handleRefresh: function() {
return Promise.all([
- api.getStatus(),
- api.getClients(),
- api.getZones()
+ callGetStatus(),
+ callGetClients(),
+ callGetZones()
]).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');
}
@@ -263,7 +306,6 @@ return view.extend({
},
handleLeave: function() {
- // Stop polling when leaving the view to prevent memory leaks
poll.stop();
},
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/parental.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/parental.js
index 420c6777..2ed02ce9 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/parental.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/parental.js
@@ -1,13 +1,17 @@
'use strict';
'require view';
-'require secubox-theme/theme as Theme';
'require dom';
'require ui';
-'require client-guardian.api as api';
+'require rpc';
+
+var callGetParental = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'parental'
+});
return view.extend({
load: function() {
- return api.getParental();
+ return callGetParental();
},
render: function(data) {
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 4383105f..6e1ea20f 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
@@ -1,6 +1,6 @@
'use strict';
'require view';
-'require secubox-theme/theme as Theme';
+
'require form';
'require ui';
'require uci';
diff --git a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/wizard.js b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/wizard.js
index 12bef323..d5869221 100644
--- a/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/wizard.js
+++ b/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/wizard.js
@@ -1,6 +1,5 @@
'use strict';
'require view';
-'require secubox-theme/theme as Theme';
'require dom';
'require ui';
'require rpc';
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 94000e4c..c48387db 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
@@ -1,13 +1,29 @@
'use strict';
'require view';
-'require secubox-theme/theme as Theme';
'require dom';
'require ui';
-'require client-guardian.api as api';
+'require rpc';
+
+var callGetZones = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'zones',
+ expect: { zones: [] }
+});
+
+var callUpdateZone = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'update_zone',
+ params: ['id', 'name', 'bandwidth_limit', 'content_filter']
+});
+
+var callSyncZones = rpc.declare({
+ object: 'luci.client-guardian',
+ method: 'sync_zones'
+});
return view.extend({
load: function() {
- return api.getZones();
+ return callGetZones();
},
render: function(data) {
@@ -51,17 +67,17 @@ return view.extend({
var features = [];
if (zone.internet_access) features.push({ name: 'Internet', enabled: true });
else features.push({ name: 'Internet', enabled: false });
-
+
if (zone.local_access) features.push({ name: 'Local', enabled: true });
else features.push({ name: 'Local', enabled: false });
-
+
if (zone.inter_client) features.push({ name: 'Inter-client', enabled: true });
-
+
if (zone.time_restrictions) features.push({ name: 'Horaires', enabled: true });
- if (zone.content_filter && zone.content_filter !== 'none')
+ if (zone.content_filter && zone.content_filter !== 'none')
features.push({ name: 'Filtrage', enabled: true });
if (zone.portal_required) features.push({ name: 'Portail', enabled: true });
- if (zone.bandwidth_limit > 0)
+ if (zone.bandwidth_limit > 0)
features.push({ name: zone.bandwidth_limit + ' Mbps', enabled: true });
return E('div', {
@@ -162,13 +178,11 @@ return view.extend({
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': L.bind(function() {
- api.updateZone(
+ callUpdateZone(
zone.id,
zone.name,
parseInt(document.getElementById('zone-bandwidth').value) || 0,
- document.getElementById('zone-filter').value,
- zone.time_restrictions ? document.getElementById('zone-start').value : '',
- zone.time_restrictions ? document.getElementById('zone-end').value : ''
+ document.getElementById('zone-filter').value
).then(L.bind(function() {
ui.hideModal();
ui.addNotification(null, E('p', _('Zone updated successfully')), 'success');
@@ -184,7 +198,7 @@ return view.extend({
btn.disabled = true;
btn.innerHTML = '⏳ Synchronisation...';
- api.syncZones().then(function(result) {
+ callSyncZones().then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', {}, 'Zones firewall synchronisées avec succès'), 'success');
btn.innerHTML = '✅ Synchronisé';
@@ -205,7 +219,7 @@ return view.extend({
},
handleRefresh: function() {
- return api.getZones().then(L.bind(function(data) {
+ return callGetZones().then(L.bind(function(data) {
var container = document.querySelector('.client-guardian-dashboard');
if (container) {
var newView = this.render(data);
diff --git a/package/secubox/luci-app-client-guardian/root/etc/client-guardian/profiles.json b/package/secubox/luci-app-client-guardian/root/etc/client-guardian/profiles.json
index a5134580..dbb5444b 100644
--- a/package/secubox/luci-app-client-guardian/root/etc/client-guardian/profiles.json
+++ b/package/secubox/luci-app-client-guardian/root/etc/client-guardian/profiles.json
@@ -1,5 +1,62 @@
{
"profiles": [
+ {
+ "id": "factory_default",
+ "name": "Configuration Usine",
+ "description": "Zones firewall par défaut OpenWrt - Réinitialisation standard",
+ "icon": "🔄",
+ "is_factory_default": true,
+ "zones": [
+ {
+ "id": "lan",
+ "name": "Réseau Local (LAN)",
+ "description": "Tous les appareils du réseau local",
+ "network": "lan",
+ "color": "#22c55e",
+ "icon": "home",
+ "internet_access": true,
+ "local_access": true,
+ "inter_client": true,
+ "bandwidth_limit": 0,
+ "priority": "normal"
+ },
+ {
+ "id": "wan",
+ "name": "Internet (WAN)",
+ "description": "Connexion vers Internet - trafic sortant autorisé",
+ "network": "wan",
+ "color": "#ef4444",
+ "icon": "globe",
+ "is_wan": true,
+ "internet_access": true,
+ "local_access": false,
+ "inter_client": false,
+ "bandwidth_limit": 0,
+ "priority": "high"
+ },
+ {
+ "id": "guest",
+ "name": "Invités",
+ "description": "Réseau invités isolé (optionnel)",
+ "network": "guest",
+ "color": "#8b5cf6",
+ "icon": "users",
+ "optional": true,
+ "internet_access": true,
+ "local_access": false,
+ "inter_client": false,
+ "bandwidth_limit": 0,
+ "priority": "low"
+ }
+ ],
+ "firewall_defaults": {
+ "input": "ACCEPT",
+ "output": "ACCEPT",
+ "forward": "REJECT",
+ "lan_to_wan": "ACCEPT",
+ "wan_to_lan": "REJECT"
+ }
+ },
{
"id": "family_home",
"name": "Maison Familiale",
diff --git a/package/secubox/luci-app-secubox-admin/htdocs/luci-static/resources/view/secubox-admin/advanced-settings.js b/package/secubox/luci-app-secubox-admin/htdocs/luci-static/resources/view/secubox-admin/advanced-settings.js
index cda840b1..5cc1fde3 100644
--- a/package/secubox/luci-app-secubox-admin/htdocs/luci-static/resources/view/secubox-admin/advanced-settings.js
+++ b/package/secubox/luci-app-secubox-admin/htdocs/luci-static/resources/view/secubox-admin/advanced-settings.js
@@ -6,6 +6,25 @@
'require rpc';
'require form';
+var callGetWanAccess = rpc.declare({
+ object: 'luci.secubox',
+ method: 'get_wan_access',
+ expect: { }
+});
+
+var callSetWanAccess = rpc.declare({
+ object: 'luci.secubox',
+ method: 'set_wan_access',
+ params: ['enabled', 'https_enabled', 'https_port', 'http_enabled', 'http_port', 'ssh_enabled', 'ssh_port'],
+ expect: { success: false }
+});
+
+var callApplyWanAccess = rpc.declare({
+ object: 'luci.secubox',
+ method: 'apply_wan_access',
+ expect: { success: false }
+});
+
var callGetConfigFiles = rpc.declare({
object: 'file',
method: 'list',
@@ -34,12 +53,14 @@ return view.extend({
L.resolveDefault(uci.load('secubox-appstore'), {}),
L.resolveDefault(uci.load('network'), {}),
L.resolveDefault(uci.load('firewall'), {}),
- L.resolveDefault(uci.load('dhcp'), {})
+ L.resolveDefault(uci.load('dhcp'), {}),
+ L.resolveDefault(callGetWanAccess(), {})
]);
},
- render: function() {
+ render: function(data) {
var self = this;
+ var wanAccess = data[5] || {};
var container = E('div', { 'class': 'cyberpunk-mode' }, [
E('link', { 'rel': 'stylesheet', 'type': 'text/css',
@@ -55,6 +76,7 @@ return view.extend({
E('div', { 'class': 'cyber-dual-console' }, [
// Left: Quick Config Sections
E('div', { 'class': 'cyber-console-left' }, [
+ this.renderWanAccessPanel(wanAccess),
this.renderQuickConfigPanel(),
this.renderSystemSubsetsPanel(),
this.renderConfigFilesPanel()
@@ -72,6 +94,171 @@ return view.extend({
return container;
},
+ renderWanAccessPanel: function(wanAccess) {
+ var self = this;
+ var enabled = wanAccess.enabled || false;
+ var services = wanAccess.services || {};
+
+ return E('div', { 'class': 'cyber-panel cyber-scanlines' }, [
+ E('div', { 'class': 'cyber-panel-header' }, [
+ E('div', { 'class': 'cyber-panel-title' }, '🌐 WAN ACCESS'),
+ E('span', {
+ 'class': 'cyber-panel-badge ' + (enabled ? 'success' : 'warning'),
+ 'id': 'wan-access-status'
+ }, enabled ? 'ENABLED' : 'DISABLED')
+ ]),
+ E('div', { 'class': 'cyber-panel-body' }, [
+ // Master toggle
+ E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; padding: 12px; background: rgba(0,255,65,0.05); border-left: 3px solid var(--cyber-primary); margin-bottom: 15px;' }, [
+ E('div', {}, [
+ E('div', { 'style': 'font-weight: bold; font-size: 13px;' }, 'Remote Access'),
+ E('div', { 'style': 'font-size: 10px; color: var(--cyber-text-dim);' }, 'Allow access from WAN/Internet')
+ ]),
+ E('label', { 'class': 'cyber-switch' }, [
+ E('input', {
+ 'type': 'checkbox',
+ 'id': 'wan-access-master',
+ 'checked': enabled,
+ 'change': function(ev) {
+ var masterEnabled = ev.target.checked;
+ document.getElementById('wan-access-status').textContent = masterEnabled ? 'ENABLED' : 'DISABLED';
+ document.getElementById('wan-access-status').className = 'cyber-panel-badge ' + (masterEnabled ? 'success' : 'warning');
+ }
+ }),
+ E('span', { 'class': 'cyber-slider' })
+ ])
+ ]),
+
+ // Service toggles
+ E('div', { 'style': 'display: flex; flex-direction: column; gap: 10px;' }, [
+ // HTTPS
+ E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px; background: rgba(0,255,255,0.05); border-radius: 4px;' }, [
+ E('div', { 'style': 'display: flex; align-items: center; gap: 10px;' }, [
+ E('span', { 'style': 'font-size: 16px;' }, '🔒'),
+ E('div', {}, [
+ E('div', { 'style': 'font-size: 12px; font-weight: bold;' }, 'HTTPS (LuCI)'),
+ E('div', { 'style': 'font-size: 10px; color: var(--cyber-text-dim);' }, 'Secure web interface')
+ ])
+ ]),
+ E('div', { 'style': 'display: flex; align-items: center; gap: 10px;' }, [
+ E('input', {
+ 'type': 'number',
+ 'id': 'wan-https-port',
+ 'value': (services.https && services.https.port) || 443,
+ 'style': 'width: 70px; padding: 5px; background: rgba(0,0,0,0.3); border: 1px solid var(--cyber-border); color: var(--cyber-text); text-align: center;'
+ }),
+ E('label', { 'class': 'cyber-switch' }, [
+ E('input', {
+ 'type': 'checkbox',
+ 'id': 'wan-https-enabled',
+ 'checked': services.https && services.https.enabled
+ }),
+ E('span', { 'class': 'cyber-slider' })
+ ])
+ ])
+ ]),
+
+ // HTTP
+ E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px; background: rgba(255,165,0,0.05); border-radius: 4px;' }, [
+ E('div', { 'style': 'display: flex; align-items: center; gap: 10px;' }, [
+ E('span', { 'style': 'font-size: 16px;' }, '🌐'),
+ E('div', {}, [
+ E('div', { 'style': 'font-size: 12px; font-weight: bold;' }, 'HTTP'),
+ E('div', { 'style': 'font-size: 10px; color: var(--cyber-warning);' }, 'Not recommended')
+ ])
+ ]),
+ E('div', { 'style': 'display: flex; align-items: center; gap: 10px;' }, [
+ E('input', {
+ 'type': 'number',
+ 'id': 'wan-http-port',
+ 'value': (services.http && services.http.port) || 80,
+ 'style': 'width: 70px; padding: 5px; background: rgba(0,0,0,0.3); border: 1px solid var(--cyber-border); color: var(--cyber-text); text-align: center;'
+ }),
+ E('label', { 'class': 'cyber-switch' }, [
+ E('input', {
+ 'type': 'checkbox',
+ 'id': 'wan-http-enabled',
+ 'checked': services.http && services.http.enabled
+ }),
+ E('span', { 'class': 'cyber-slider' })
+ ])
+ ])
+ ]),
+
+ // SSH
+ E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px; background: rgba(255,0,0,0.05); border-radius: 4px;' }, [
+ E('div', { 'style': 'display: flex; align-items: center; gap: 10px;' }, [
+ E('span', { 'style': 'font-size: 16px;' }, '🖥️'),
+ E('div', {}, [
+ E('div', { 'style': 'font-size: 12px; font-weight: bold;' }, 'SSH'),
+ E('div', { 'style': 'font-size: 10px; color: var(--cyber-danger);' }, 'Use with caution')
+ ])
+ ]),
+ E('div', { 'style': 'display: flex; align-items: center; gap: 10px;' }, [
+ E('input', {
+ 'type': 'number',
+ 'id': 'wan-ssh-port',
+ 'value': (services.ssh && services.ssh.port) || 22,
+ 'style': 'width: 70px; padding: 5px; background: rgba(0,0,0,0.3); border: 1px solid var(--cyber-border); color: var(--cyber-text); text-align: center;'
+ }),
+ E('label', { 'class': 'cyber-switch' }, [
+ E('input', {
+ 'type': 'checkbox',
+ 'id': 'wan-ssh-enabled',
+ 'checked': services.ssh && services.ssh.enabled
+ }),
+ E('span', { 'class': 'cyber-slider' })
+ ])
+ ])
+ ])
+ ]),
+
+ // Apply button
+ E('div', { 'style': 'margin-top: 15px;' }, [
+ E('button', {
+ 'class': 'cyber-btn primary',
+ 'style': 'width: 100%;',
+ 'click': function() {
+ var masterEnabled = document.getElementById('wan-access-master').checked;
+ var httpsEnabled = document.getElementById('wan-https-enabled').checked;
+ var httpsPort = parseInt(document.getElementById('wan-https-port').value) || 443;
+ var httpEnabled = document.getElementById('wan-http-enabled').checked;
+ var httpPort = parseInt(document.getElementById('wan-http-port').value) || 80;
+ var sshEnabled = document.getElementById('wan-ssh-enabled').checked;
+ var sshPort = parseInt(document.getElementById('wan-ssh-port').value) || 22;
+
+ ui.showModal(_('Applying'), [
+ E('p', { 'class': 'spinning' }, _('Updating firewall rules...'))
+ ]);
+
+ callSetWanAccess(
+ masterEnabled ? 1 : 0,
+ httpsEnabled ? 1 : 0,
+ httpsPort,
+ httpEnabled ? 1 : 0,
+ httpPort,
+ sshEnabled ? 1 : 0,
+ sshPort
+ ).then(function() {
+ return callApplyWanAccess();
+ }).then(function(result) {
+ ui.hideModal();
+ if (result.success) {
+ ui.addNotification(null, E('p', 'WAN access rules applied successfully'), 'success');
+ } else {
+ ui.addNotification(null, E('p', 'Failed to apply rules'), 'error');
+ }
+ }).catch(function(err) {
+ ui.hideModal();
+ ui.addNotification(null, E('p', 'Error: ' + err), 'error');
+ });
+ }
+ }, '🔄 Apply Firewall Rules')
+ ])
+ ])
+ ]);
+ },
+
renderQuickConfigPanel: function() {
var self = this;
diff --git a/package/secubox/luci-app-secubox-admin/root/usr/share/rpcd/acl.d/luci-app-secubox-admin.json b/package/secubox/luci-app-secubox-admin/root/usr/share/rpcd/acl.d/luci-app-secubox-admin.json
index 93b237c8..209f25cf 100644
--- a/package/secubox/luci-app-secubox-admin/root/usr/share/rpcd/acl.d/luci-app-secubox-admin.json
+++ b/package/secubox/luci-app-secubox-admin/root/usr/share/rpcd/acl.d/luci-app-secubox-admin.json
@@ -17,12 +17,14 @@
"check_updates",
"get_app_versions",
"get_changelog",
- "get_widget_data"
+ "get_widget_data",
+ "get_wan_access"
]
},
"uci": [
"secubox",
"secubox-appstore",
+ "firewall",
"*"
]
},
@@ -36,12 +38,15 @@
"updateModule",
"applyProfile",
"set_catalog_source",
- "sync_catalog"
+ "sync_catalog",
+ "set_wan_access",
+ "apply_wan_access"
]
},
"uci": [
"secubox",
"secubox-appstore",
+ "firewall",
"*"
]
}
diff --git a/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/devices.js b/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/devices.js
index e59ffd30..15cdfca3 100644
--- a/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/devices.js
+++ b/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/devices.js
@@ -3,7 +3,52 @@
'require poll';
'require ui';
'require dom';
-'require secubox-netifyd/api as netifydAPI';
+'require rpc';
+
+var callGetDevices = rpc.declare({
+ object: 'luci.secubox-netifyd',
+ method: 'get_detected_devices'
+});
+
+var callGetStatus = rpc.declare({
+ object: 'luci.secubox-netifyd',
+ method: 'get_service_status'
+});
+
+var callGetDashboard = rpc.declare({
+ object: 'luci.secubox-netifyd',
+ method: 'get_dashboard'
+});
+
+function formatBytes(bytes) {
+ if (!bytes || bytes === 0) return '0 B';
+ var units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ 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];
+}
+
+function formatDuration(seconds) {
+ if (seconds < 60) return seconds + 's';
+ if (seconds < 3600) return Math.floor(seconds / 60) + 'm';
+ if (seconds < 86400) return Math.floor(seconds / 3600) + 'h';
+ return Math.floor(seconds / 86400) + 'd';
+}
+
+function getDeviceIcon(hostname, mac) {
+ hostname = (hostname || '').toLowerCase();
+ if (hostname.match(/android|phone|mobile|samsung|xiaomi|huawei|oppo|vivo/)) return '📱';
+ if (hostname.match(/iphone|ipad|apple|macbook|imac/)) return '🍎';
+ if (hostname.match(/pc|laptop|desktop|windows|linux|ubuntu/)) return '💻';
+ if (hostname.match(/camera|cam|dvr|nvr|hikvision|dahua/)) return '📷';
+ if (hostname.match(/tv|roku|chromecast|firestick|appletv|smart-tv/)) return '📺';
+ if (hostname.match(/playstation|xbox|nintendo|switch|steam/)) return '🎮';
+ if (hostname.match(/router|switch|ap|access[-_]?point|mesh/)) return '📡';
+ if (hostname.match(/printer|print|hp-|canon-|epson-/)) return '🖨️';
+ if (hostname.match(/alexa|echo|google[-_]?home|homepod/)) return '🔊';
+ if (hostname.match(/thermostat|nest|hue|bulb|sensor|iot/)) return '🏠';
+ return '🔌';
+}
return view.extend({
refreshInterval: 5,
@@ -14,9 +59,9 @@ return view.extend({
load: function() {
return Promise.all([
- netifydAPI.getDetectedDevices(),
- netifydAPI.getServiceStatus(),
- netifydAPI.getDashboard()
+ callGetDevices(),
+ callGetStatus(),
+ callGetDashboard()
]);
},
@@ -67,7 +112,7 @@ return view.extend({
},
handleExport: function(ev) {
- var csvContent = 'IP Address,MAC Address,Flows,Bytes Sent,Bytes Received,Total Traffic,Last Seen\n';
+ var csvContent = 'IP Address,MAC Address,Hostname,Flows,Bytes Sent,Bytes Received,Total Traffic,Last Seen\n';
this.devicesData.forEach(function(device) {
var total = (device.bytes_sent || 0) + (device.bytes_received || 0);
@@ -77,6 +122,7 @@ return view.extend({
csvContent += [
'"' + (device.ip || 'N/A') + '"',
'"' + (device.mac || 'N/A') + '"',
+ '"' + (device.hostname || device.name || '') + '"',
device.flows || 0,
device.bytes_sent || 0,
device.bytes_received || 0,
@@ -113,45 +159,46 @@ return view.extend({
{
title: _('Active Devices'),
value: devices.length.toString(),
- icon: 'network-wired',
- gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
+ emoji: '📱',
+ color: '#6366f1',
+ bg: 'linear-gradient(135deg, rgba(99,102,241,0.15) 0%, rgba(139,92,246,0.15) 100%)'
},
{
title: _('Total Flows'),
value: totalFlows.toLocaleString(),
- icon: 'stream',
- gradient: 'linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%)'
+ emoji: '🔄',
+ color: '#10b981',
+ bg: 'linear-gradient(135deg, rgba(16,185,129,0.15) 0%, rgba(52,211,153,0.15) 100%)'
},
{
- title: _('Total Sent'),
- value: netifydAPI.formatBytes(totalBytesSent),
- icon: 'upload',
- gradient: 'linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%)'
+ title: _('Upload'),
+ value: formatBytes(totalBytesSent),
+ emoji: '⬆️',
+ color: '#ef4444',
+ bg: 'linear-gradient(135deg, rgba(239,68,68,0.15) 0%, rgba(248,113,113,0.15) 100%)'
},
{
- title: _('Total Received'),
- value: netifydAPI.formatBytes(totalBytesRecv),
- icon: 'download',
- gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)'
+ title: _('Download'),
+ value: formatBytes(totalBytesRecv),
+ emoji: '⬇️',
+ color: '#22c55e',
+ bg: 'linear-gradient(135deg, rgba(34,197,94,0.15) 0%, rgba(74,222,128,0.15) 100%)'
}
];
return E('div', {
- 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 1.5rem'
+ 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px'
}, cards.map(function(card) {
return E('div', {
- 'style': 'background: ' + card.gradient + '; color: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1)'
+ 'style': 'background: ' + card.bg + '; border: 1px solid rgba(0,0,0,0.08); padding: 20px; border-radius: 16px; transition: transform 0.2s, box-shadow 0.2s;'
}, [
- E('div', { 'style': 'display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1rem' }, [
- E('div', { 'style': 'font-size: 0.9em; opacity: 0.9' }, card.title),
- E('i', {
- 'class': 'fa fa-' + card.icon,
- 'style': 'font-size: 2em; opacity: 0.3'
- })
+ E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px' }, [
+ E('span', { 'style': 'font-size: 28px' }, card.emoji),
+ E('span', { 'style': 'font-size: 12px; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600' }, card.title)
]),
- E('div', { 'style': 'font-size: 2em; font-weight: bold' }, card.value)
+ E('div', { 'style': 'font-size: 28px; font-weight: 700; color: ' + card.color }, card.value)
]);
- }.bind(this)));
+ }));
},
renderDevicesTable: function() {
@@ -161,135 +208,90 @@ return view.extend({
var devices = this.filterDevices(this.devicesData);
var sortedDevices = this.sortDevices(devices, this.sortColumn, this.sortDirection);
- var getSortIcon = function(column) {
- if (this.sortColumn !== column) {
- return E('i', { 'class': 'fa fa-sort', 'style': 'opacity: 0.3' });
- }
- return E('i', {
- 'class': 'fa fa-sort-' + (this.sortDirection === 'asc' ? 'up' : 'down'),
- 'style': 'color: #3b82f6'
- });
- }.bind(this);
-
- dom.content(container, [
- devices.length > 0 ? E('div', { 'class': 'table', 'style': 'font-size: 0.95em' },
- [
- // Header
- E('div', { 'class': 'tr table-titles' }, [
- E('div', {
- 'class': 'th left',
- 'style': 'width: 20%; cursor: pointer',
- 'click': ui.createHandlerFn(this, 'handleSort', 'ip')
- }, [
- _('IP Address'),
- ' ',
- getSortIcon('ip')
- ]),
- E('div', { 'class': 'th left', 'style': 'width: 20%' }, _('MAC Address')),
- E('div', {
- 'class': 'th center',
- 'style': 'width: 10%; cursor: pointer',
- 'click': ui.createHandlerFn(this, 'handleSort', 'flows')
- }, [
- _('Flows'),
- ' ',
- getSortIcon('flows')
- ]),
- E('div', {
- 'class': 'th right',
- 'style': 'width: 15%; cursor: pointer',
- 'click': ui.createHandlerFn(this, 'handleSort', 'bytes_sent')
- }, [
- _('Sent'),
- ' ',
- getSortIcon('bytes_sent')
- ]),
- E('div', {
- 'class': 'th right',
- 'style': 'width: 15%; cursor: pointer',
- 'click': ui.createHandlerFn(this, 'handleSort', 'bytes_received')
- }, [
- _('Received'),
- ' ',
- getSortIcon('bytes_received')
- ]),
- E('div', { 'class': 'th', 'style': 'width: 20%' }, _('Traffic Distribution'))
- ])
- ].concat(
- // Rows
- sortedDevices.map(function(device, idx) {
- var lastSeen = device.last_seen || 0;
- var now = Math.floor(Date.now() / 1000);
- var ago = now - lastSeen;
- var lastSeenStr = 'N/A';
-
- if (lastSeen > 0) {
- if (ago < 60) {
- lastSeenStr = _('Just now');
- } else {
- lastSeenStr = netifydAPI.formatDuration(ago) + ' ' + _('ago');
- }
- }
-
- var totalBytes = (device.bytes_sent || 0) + (device.bytes_received || 0);
- var sentPercent = totalBytes > 0 ? ((device.bytes_sent || 0) / totalBytes * 100) : 50;
- var recvPercent = 100 - sentPercent;
-
- return E('div', {
- 'class': 'tr',
- 'style': idx % 2 === 0 ? 'background: #f9fafb' : ''
- }, [
- E('div', { 'class': 'td left', 'style': 'width: 20%' }, [
- E('code', { 'style': 'font-size: 0.9em' }, device.ip || 'Unknown'),
- E('br'),
- E('small', { 'class': 'text-muted' }, lastSeenStr)
- ]),
- E('div', { 'class': 'td left', 'style': 'width: 20%' }, [
- E('code', { 'style': 'font-size: 0.8em' }, device.mac || 'Unknown')
- ]),
- E('div', { 'class': 'td center', 'style': 'width: 10%' },
- (device.flows || 0).toLocaleString()),
- E('div', { 'class': 'td right', 'style': 'width: 15%' }, [
- E('span', {
- 'class': 'badge',
- 'style': 'background: #ef4444; color: white; padding: 0.25rem 0.5rem; border-radius: 4px'
- }, netifydAPI.formatBytes(device.bytes_sent || 0))
- ]),
- E('div', { 'class': 'td right', 'style': 'width: 15%' }, [
- E('span', {
- 'class': 'badge',
- 'style': 'background: #10b981; color: white; padding: 0.25rem 0.5rem; border-radius: 4px'
- }, netifydAPI.formatBytes(device.bytes_received || 0))
- ]),
- E('div', { 'class': 'td', 'style': 'width: 20%' }, [
- E('div', {
- 'style': 'display: flex; gap: 2px; height: 24px; border-radius: 4px; overflow: hidden'
- }, [
- E('div', {
- 'style': 'background: #ef4444; width: ' + sentPercent + '%; transition: width 0.3s',
- 'title': _('Upload: %s').format(sentPercent.toFixed(1) + '%')
- }),
- E('div', {
- 'style': 'background: #10b981; width: ' + recvPercent + '%; transition: width 0.3s',
- 'title': _('Download: %s').format(recvPercent.toFixed(1) + '%')
- })
- ]),
- E('div', { 'style': 'font-size: 0.75em; color: #6b7280; margin-top: 0.25rem; text-align: center' },
- _('Total: %s').format(netifydAPI.formatBytes(totalBytes)))
- ])
- ]);
- }.bind(this))
- )
- ) : E('div', {
- 'class': 'alert-message info',
- 'style': 'text-align: center; padding: 3rem'
+ if (devices.length === 0) {
+ dom.content(container, E('div', {
+ 'style': 'text-align: center; padding: 48px; background: linear-gradient(135deg, rgba(99,102,241,0.05) 0%, rgba(139,92,246,0.05) 100%); border-radius: 16px; border: 2px dashed rgba(99,102,241,0.2)'
}, [
- E('i', { 'class': 'fa fa-network-wired', 'style': 'font-size: 3em; opacity: 0.3; display: block; margin-bottom: 1rem' }),
- E('h4', _('No Device Data')),
- E('p', { 'class': 'text-muted' }, _('No devices have been detected yet')),
- E('small', _('Data will appear once network traffic is analyzed'))
- ])
- ]);
+ E('div', { 'style': 'font-size: 48px; margin-bottom: 16px; opacity: 0.5' }, '📡'),
+ E('h4', { 'style': 'margin: 0 0 8px 0; color: #374151' }, _('No Devices Detected')),
+ E('p', { 'style': 'color: #6b7280; margin: 0' }, _('Waiting for network traffic...'))
+ ]));
+ return;
+ }
+
+ var deviceCards = sortedDevices.map(function(device) {
+ var lastSeen = device.last_seen || 0;
+ var now = Math.floor(Date.now() / 1000);
+ var ago = now - lastSeen;
+ var lastSeenStr = 'N/A';
+ var isOnline = ago < 120;
+
+ if (lastSeen > 0) {
+ if (ago < 60) lastSeenStr = _('Just now');
+ else lastSeenStr = formatDuration(ago) + ' ' + _('ago');
+ }
+
+ var totalBytes = (device.bytes_sent || 0) + (device.bytes_received || 0);
+ var sentPercent = totalBytes > 0 ? ((device.bytes_sent || 0) / totalBytes * 100) : 50;
+ var recvPercent = 100 - sentPercent;
+ var icon = getDeviceIcon(device.hostname || device.name, device.mac);
+
+ return E('div', {
+ 'style': 'background: white; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px; transition: all 0.2s; ' + (isOnline ? 'box-shadow: 0 2px 8px rgba(34,197,94,0.1); border-left: 4px solid #22c55e;' : 'opacity: 0.7;')
+ }, [
+ // Header row: Icon + IP/MAC + Status
+ E('div', { 'style': 'display: flex; align-items: center; gap: 12px; margin-bottom: 12px' }, [
+ E('div', { 'style': 'font-size: 32px; flex-shrink: 0' }, icon),
+ E('div', { 'style': 'flex: 1; min-width: 0' }, [
+ E('div', { 'style': 'font-weight: 600; color: #111827; font-size: 15px; font-family: monospace' }, device.ip || 'Unknown'),
+ E('div', { 'style': 'font-size: 11px; color: #9ca3af; font-family: monospace' }, device.mac || 'Unknown')
+ ]),
+ E('div', { 'style': 'text-align: right' }, [
+ E('div', {
+ 'style': 'display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; ' + (isOnline ? 'background: rgba(34,197,94,0.15); color: #16a34a' : 'background: rgba(156,163,175,0.15); color: #6b7280')
+ }, [
+ E('span', { 'style': 'width: 6px; height: 6px; border-radius: 50%; background: ' + (isOnline ? '#22c55e' : '#9ca3af') }),
+ isOnline ? _('Online') : lastSeenStr
+ ]),
+ E('div', { 'style': 'font-size: 11px; color: #9ca3af; margin-top: 4px' },
+ (device.flows || 0) + ' ' + _('flows'))
+ ])
+ ]),
+ // Traffic stats row
+ E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px' }, [
+ E('div', { 'style': 'background: rgba(239,68,68,0.08); padding: 8px 12px; border-radius: 8px; display: flex; justify-content: space-between; align-items: center' }, [
+ E('span', { 'style': 'font-size: 12px; color: #6b7280' }, '⬆️ ' + _('Upload')),
+ E('span', { 'style': 'font-weight: 600; color: #dc2626; font-size: 13px' }, formatBytes(device.bytes_sent || 0))
+ ]),
+ E('div', { 'style': 'background: rgba(34,197,94,0.08); padding: 8px 12px; border-radius: 8px; display: flex; justify-content: space-between; align-items: center' }, [
+ E('span', { 'style': 'font-size: 12px; color: #6b7280' }, '⬇️ ' + _('Download')),
+ E('span', { 'style': 'font-weight: 600; color: #16a34a; font-size: 13px' }, formatBytes(device.bytes_received || 0))
+ ])
+ ]),
+ // Traffic bar
+ E('div', { 'style': 'position: relative' }, [
+ E('div', { 'style': 'display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: #f3f4f6' }, [
+ E('div', {
+ 'style': 'background: linear-gradient(90deg, #ef4444, #f87171); width: ' + sentPercent + '%; transition: width 0.3s',
+ 'title': _('Upload: %s').format(sentPercent.toFixed(1) + '%')
+ }),
+ E('div', {
+ 'style': 'background: linear-gradient(90deg, #22c55e, #4ade80); width: ' + recvPercent + '%; transition: width 0.3s',
+ 'title': _('Download: %s').format(recvPercent.toFixed(1) + '%')
+ })
+ ]),
+ E('div', { 'style': 'display: flex; justify-content: space-between; margin-top: 4px; font-size: 10px; color: #9ca3af' }, [
+ E('span', {}, sentPercent.toFixed(0) + '% ↑'),
+ E('span', { 'style': 'font-weight: 500; color: #6b7280' }, _('Total: %s').format(formatBytes(totalBytes))),
+ E('span', {}, recvPercent.toFixed(0) + '% ↓')
+ ])
+ ])
+ ]);
+ });
+
+ dom.content(container, E('div', {
+ 'style': 'display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px'
+ }, deviceCards));
},
render: function(data) {
@@ -302,101 +304,96 @@ return view.extend({
// Set up polling
poll.add(L.bind(function() {
return Promise.all([
- netifydAPI.getDetectedDevices(),
- netifydAPI.getServiceStatus()
+ callGetDevices(),
+ callGetStatus()
]).then(L.bind(function(result) {
this.devicesData = (result[0] || {}).devices || [];
this.renderDevicesTable();
+ // Update summary cards
+ var summaryContainer = document.getElementById('summary-cards-container');
+ if (summaryContainer) {
+ dom.content(summaryContainer, this.renderSummaryCards(this.devicesData).childNodes);
+ }
+ // Update device count badge
+ var countBadge = document.getElementById('device-count-badge');
+ if (countBadge) {
+ countBadge.textContent = this.devicesData.length;
+ }
}, this));
}, this), this.refreshInterval);
var serviceRunning = status.running;
- return E('div', { 'class': 'cbi-map' }, [
- E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem' }, [
- E('h2', { 'name': 'content', 'style': 'margin: 0' }, [
- E('i', { 'class': 'fa fa-network-wired', 'style': 'margin-right: 0.5rem' }),
- _('Detected Devices')
- ]),
- E('span', {
- 'class': 'badge',
- 'style': 'padding: 0.5rem 1rem; font-size: 0.9em; background: ' + (serviceRunning ? '#10b981' : '#ef4444')
- }, [
- E('i', { 'class': 'fa fa-circle', 'style': 'margin-right: 0.5rem' }),
- serviceRunning ? _('Live') : _('Offline')
- ])
- ]),
- E('div', { 'class': 'cbi-map-descr' },
- _('Network devices detected and tracked by Netifyd deep packet inspection. Updates every 5 seconds.')),
-
- E('div', { 'class': 'cbi-section' }, [
- E('div', { 'class': 'cbi-section-node' }, [
- E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem' }, [
- E('h3', { 'style': 'margin: 0' }, [
- E('i', { 'class': 'fa fa-chart-bar', 'style': 'margin-right: 0.5rem' }),
- _('Summary')
- ]),
- E('button', {
- 'class': 'btn btn-primary',
- 'click': ui.createHandlerFn(this, 'handleExport')
- }, [
- E('i', { 'class': 'fa fa-download' }),
- ' ',
- _('Export CSV')
- ])
- ]),
- this.renderSummaryCards(this.devicesData)
- ])
- ]),
-
- E('div', { 'class': 'cbi-section' }, [
- E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem' }, [
- E('h3', { 'style': 'margin: 0' }, [
- E('i', { 'class': 'fa fa-list', 'style': 'margin-right: 0.5rem' }),
- _('Device List'),
- ' ',
- E('span', {
- 'class': 'badge',
- 'style': 'background: #3b82f6; color: white; margin-left: 0.5rem'
- }, this.devicesData.length)
- ]),
- E('div', { 'style': 'display: flex; gap: 0.5rem; align-items: center' }, [
- E('input', {
- 'type': 'text',
- 'class': 'cbi-input-text',
- 'placeholder': _('Search by IP or MAC...'),
- 'style': 'min-width: 250px',
- 'value': this.searchQuery,
- 'keyup': function(ev) {
- self.searchQuery = ev.target.value;
- self.renderDevicesTable();
- }
- })
+ return E('div', { 'style': 'max-width: 1400px; margin: 0 auto; padding: 24px' }, [
+ // Header
+ E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 16px' }, [
+ E('div', { 'style': 'display: flex; align-items: center; gap: 12px' }, [
+ E('div', { 'style': 'font-size: 36px' }, '📡'),
+ E('div', {}, [
+ E('h2', { 'style': 'margin: 0; font-size: 24px; font-weight: 700; color: #111827' }, _('Network Devices')),
+ E('p', { 'style': 'margin: 4px 0 0 0; font-size: 14px; color: #6b7280' }, _('Real-time traffic monitoring via Netifyd DPI'))
])
]),
- E('div', { 'class': 'cbi-section-node' }, [
+ E('div', { 'style': 'display: flex; align-items: center; gap: 12px' }, [
E('div', {
- 'class': 'alert alert-info',
- 'style': 'margin-bottom: 1rem; display: flex; align-items: center; gap: 1rem'
+ 'style': 'display: flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: 24px; font-size: 13px; font-weight: 600; ' + (serviceRunning ? 'background: rgba(34,197,94,0.15); color: #16a34a' : 'background: rgba(239,68,68,0.15); color: #dc2626')
}, [
- E('div', { 'style': 'display: flex; align-items: center; gap: 0.5rem' }, [
- E('div', { 'style': 'width: 12px; height: 12px; background: #ef4444; border-radius: 2px' }),
- E('span', _('Upload'))
- ]),
- E('div', { 'style': 'display: flex; align-items: center; gap: 0.5rem' }, [
- E('div', { 'style': 'width: 12px; height: 12px; background: #10b981; border-radius: 2px' }),
- E('span', _('Download'))
- ]),
- E('span', { 'class': 'text-muted' }, '|'),
- E('span', [
- E('i', { 'class': 'fa fa-sync' }),
- ' ',
- _('Auto-refresh: Every %d seconds').format(this.refreshInterval)
- ])
+ E('span', { 'style': 'width: 8px; height: 8px; border-radius: 50%; background: ' + (serviceRunning ? '#22c55e' : '#ef4444') + '; animation: ' + (serviceRunning ? 'pulse 2s infinite' : 'none') }),
+ serviceRunning ? _('Live') : _('Offline')
]),
- E('div', { 'id': 'devices-table-container' })
+ E('button', {
+ 'style': 'display: flex; align-items: center; gap: 8px; padding: 8px 16px; background: #6366f1; color: white; border: none; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer',
+ 'click': ui.createHandlerFn(this, 'handleExport')
+ }, [
+ '📥',
+ _('Export')
+ ])
])
- ])
+ ]),
+
+ // Summary Cards
+ E('div', { 'id': 'summary-cards-container' }, this.renderSummaryCards(this.devicesData).childNodes),
+
+ // Device List Header
+ E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 12px' }, [
+ E('div', { 'style': 'display: flex; align-items: center; gap: 12px' }, [
+ E('h3', { 'style': 'margin: 0; font-size: 18px; font-weight: 600; color: #374151' }, _('Device List')),
+ E('span', {
+ 'id': 'device-count-badge',
+ 'style': 'background: #6366f1; color: white; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600'
+ }, this.devicesData.length)
+ ]),
+ E('div', { 'style': 'display: flex; gap: 12px; align-items: center' }, [
+ E('div', { 'style': 'display: flex; align-items: center; gap: 16px; font-size: 12px; color: #6b7280' }, [
+ E('span', { 'style': 'display: flex; align-items: center; gap: 4px' }, [
+ E('span', { 'style': 'width: 10px; height: 10px; background: linear-gradient(90deg, #ef4444, #f87171); border-radius: 2px' }),
+ _('Upload')
+ ]),
+ E('span', { 'style': 'display: flex; align-items: center; gap: 4px' }, [
+ E('span', { 'style': 'width: 10px; height: 10px; background: linear-gradient(90deg, #22c55e, #4ade80); border-radius: 2px' }),
+ _('Download')
+ ]),
+ E('span', { 'style': 'color: #9ca3af' }, '|'),
+ E('span', {}, '🔄 ' + _('%ds refresh').format(this.refreshInterval))
+ ]),
+ E('input', {
+ 'type': 'text',
+ 'placeholder': _('🔍 Search IP or MAC...'),
+ 'style': 'padding: 8px 16px; border: 1px solid #e5e7eb; border-radius: 8px; font-size: 13px; min-width: 200px; outline: none',
+ 'value': this.searchQuery,
+ 'keyup': function(ev) {
+ self.searchQuery = ev.target.value;
+ self.renderDevicesTable();
+ }
+ })
+ ])
+ ]),
+
+ // Devices Grid
+ E('div', { 'id': 'devices-table-container' }),
+
+ // CSS for pulse animation
+ E('style', {}, '@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }')
]);
},
diff --git a/package/secubox/secubox-core/root/etc/config/secubox b/package/secubox/secubox-core/root/etc/config/secubox
index fbd1252e..25938d12 100644
--- a/package/secubox/secubox-core/root/etc/config/secubox
+++ b/package/secubox/secubox-core/root/etc/config/secubox
@@ -22,3 +22,14 @@ config diagnostics 'settings'
option health_threshold_cpu '80'
option health_threshold_memory '90'
option health_threshold_storage '85'
+
+config wan_access 'remote'
+ option enabled '1'
+ option https_enabled '1'
+ option https_port '443'
+ option http_enabled '0'
+ option http_port '80'
+ option ssh_enabled '0'
+ option ssh_port '22'
+ option allowed_ips ''
+ option dmz_mode '0'
diff --git a/package/secubox/secubox-core/root/usr/libexec/rpcd/luci.secubox b/package/secubox/secubox-core/root/usr/libexec/rpcd/luci.secubox
index d584a2ea..d38bded7 100755
--- a/package/secubox/secubox-core/root/usr/libexec/rpcd/luci.secubox
+++ b/package/secubox/secubox-core/root/usr/libexec/rpcd/luci.secubox
@@ -194,12 +194,30 @@ case "$1" in
json_add_object "update_component_settings"
json_add_string "component_id" "string"
- json_add_object "settings" "object"
+ json_add_object "settings"
+ json_close_object
json_close_object
json_add_object "sync_component_registry"
json_close_object
+ # WAN Access management
+ json_add_object "get_wan_access"
+ json_close_object
+
+ json_add_object "set_wan_access"
+ json_add_boolean "enabled" "boolean"
+ json_add_boolean "https_enabled" "boolean"
+ json_add_int "https_port" "integer"
+ json_add_boolean "http_enabled" "boolean"
+ json_add_int "http_port" "integer"
+ json_add_boolean "ssh_enabled" "boolean"
+ json_add_int "ssh_port" "integer"
+ json_close_object
+
+ json_add_object "apply_wan_access"
+ json_close_object
+
json_dump
;;
@@ -220,7 +238,7 @@ case "$1" in
reload)
/usr/sbin/secubox-core reload
json_init
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_dump
;;
@@ -587,12 +605,12 @@ case "$1" in
# Use secubox-appstore for installation
if /usr/sbin/secubox-appstore install "$app_id" >/dev/null 2>&1; then
json_init
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_add_string "message" "App installed successfully"
json_dump
else
json_init
- json_add_boolean "success" false
+ json_add_boolean "success" 0
json_add_string "error" "Installation failed"
json_add_string "details" "Check system logs for more information"
json_dump
@@ -607,12 +625,12 @@ case "$1" in
# Use secubox-appstore for removal
if /usr/sbin/secubox-appstore remove "$app_id" >/dev/null 2>&1; then
json_init
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_add_string "message" "App removed successfully"
json_dump
else
json_init
- json_add_boolean "success" false
+ json_add_boolean "success" 0
json_add_string "error" "Removal failed"
json_add_string "details" "Check system logs for more information"
json_dump
@@ -756,19 +774,19 @@ case "$1" in
if uci set "${CONFIG_NAME}.${SECTION_NAME}.force_source=$source" >/dev/null 2>&1 && \
uci commit "$CONFIG_NAME" >/dev/null 2>&1; then
json_init
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_add_string "message" "Catalog source set to: $source"
json_dump
else
json_init
- json_add_boolean "success" false
+ json_add_boolean "success" 0
json_add_string "error" "Failed to update UCI config"
json_dump
fi
else
json_init
- json_add_boolean "success" false
+ json_add_boolean "success" 0
json_add_string "error" "No source specified"
json_dump
fi
@@ -782,13 +800,13 @@ case "$1" in
# Call secubox-catalog-sync (with or without source)
if /usr/sbin/secubox-appstore sync ${source:+"$source"} 2>&1; then
json_init
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_add_string "message" "Catalog synced successfully"
[ -n "$source" ] && json_add_string "source" "$source"
json_dump
else
json_init
- json_add_boolean "success" false
+ json_add_boolean "success" 0
json_add_string "error" "Sync failed"
json_dump
fi
@@ -942,12 +960,12 @@ case "$1" in
json_init
if echo "$result" | grep -q "Success:"; then
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_add_string "message" "$result"
json_add_string "component_id" "$component_id"
json_add_string "new_state" "$new_state"
else
- json_add_boolean "success" false
+ json_add_boolean "success" 0
json_add_string "error" "$result"
fi
json_dump
@@ -981,10 +999,10 @@ case "$1" in
json_init
if echo "$result" | grep -q "Success:"; then
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_add_string "message" "$result"
else
- json_add_boolean "success" false
+ json_add_boolean "success" 0
json_add_string "error" "$result"
fi
json_dump
@@ -998,10 +1016,10 @@ case "$1" in
json_init
if echo "$result" | grep -q "Success:"; then
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_add_string "message" "$result"
else
- json_add_boolean "success" false
+ json_add_boolean "success" 0
json_add_string "error" "$result"
fi
json_dump
@@ -1039,7 +1057,7 @@ case "$1" in
# Extract settings object from input
# This is simplified - full implementation would parse settings JSON
json_init
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_add_string "message" "Settings update functionality available via CLI: secubox-component set-setting"
json_dump
;;
@@ -1049,15 +1067,53 @@ case "$1" in
json_init
if echo "$result" | grep -q "successfully"; then
- json_add_boolean "success" true
+ json_add_boolean "success" 1
json_add_string "message" "$result"
else
- json_add_boolean "success" false
+ json_add_boolean "success" 0
json_add_string "error" "$result"
fi
json_dump
;;
+ get_wan_access)
+ /usr/sbin/secubox-wan-access json
+ ;;
+
+ set_wan_access)
+ read -r input
+ enabled=$(echo "$input" | jsonfilter -e '@.enabled' 2>/dev/null)
+ https_enabled=$(echo "$input" | jsonfilter -e '@.https_enabled' 2>/dev/null)
+ https_port=$(echo "$input" | jsonfilter -e '@.https_port' 2>/dev/null)
+ http_enabled=$(echo "$input" | jsonfilter -e '@.http_enabled' 2>/dev/null)
+ http_port=$(echo "$input" | jsonfilter -e '@.http_port' 2>/dev/null)
+ ssh_enabled=$(echo "$input" | jsonfilter -e '@.ssh_enabled' 2>/dev/null)
+ ssh_port=$(echo "$input" | jsonfilter -e '@.ssh_port' 2>/dev/null)
+
+ # Update UCI settings
+ [ -n "$enabled" ] && uci set secubox.remote.enabled="$enabled"
+ [ -n "$https_enabled" ] && uci set secubox.remote.https_enabled="$https_enabled"
+ [ -n "$https_port" ] && uci set secubox.remote.https_port="$https_port"
+ [ -n "$http_enabled" ] && uci set secubox.remote.http_enabled="$http_enabled"
+ [ -n "$http_port" ] && uci set secubox.remote.http_port="$http_port"
+ [ -n "$ssh_enabled" ] && uci set secubox.remote.ssh_enabled="$ssh_enabled"
+ [ -n "$ssh_port" ] && uci set secubox.remote.ssh_port="$ssh_port"
+ uci commit secubox
+
+ json_init
+ json_add_boolean "success" 1
+ json_add_string "message" "WAN access settings updated"
+ json_dump
+ ;;
+
+ apply_wan_access)
+ /usr/sbin/secubox-wan-access apply >/dev/null 2>&1
+ json_init
+ json_add_boolean "success" 1
+ json_add_string "message" "WAN access rules applied"
+ json_dump
+ ;;
+
*)
json_init
json_add_boolean "error" true
diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox-appstore b/package/secubox/secubox-core/root/usr/sbin/secubox-appstore
index 007d3aeb..f182060a 100755
--- a/package/secubox/secubox-core/root/usr/sbin/secubox-appstore
+++ b/package/secubox/secubox-core/root/usr/sbin/secubox-appstore
@@ -89,6 +89,23 @@ get_active_catalog() {
echo "$MAIN_CATALOG"
}
+# Check if service is enabled (will start on boot)
+is_service_enabled() {
+ local service="$1"
+ [ -z "$service" ] && return 1
+ [ -f "/etc/init.d/$service" ] || return 1
+ # Check if service has symlink in /etc/rc.d/
+ ls /etc/rc.d/S*"$service" >/dev/null 2>&1
+}
+
+# Check if service is running/active
+is_service_active() {
+ local service="$1"
+ [ -z "$service" ] && return 1
+ [ -f "/etc/init.d/$service" ] || return 1
+ /etc/init.d/"$service" status >/dev/null 2>&1
+}
+
# List all modules
list_modules() {
local format="${1:-table}"
@@ -117,15 +134,62 @@ list_modules() {
local module_name=$(jsonfilter -i "$catalog" -e '@.name' 2>/dev/null)
local module_category=$(jsonfilter -i "$catalog" -e '@.category' 2>/dev/null)
local module_version=$(jsonfilter -i "$catalog" -e '@.version' 2>/dev/null)
+ local service_name=$(jsonfilter -i "$catalog" -e '@.procd.service' 2>/dev/null)
- # Check if installed
+ # Auto-detect service name if not defined in catalog
+ if [ -z "$service_name" ]; then
+ case "$module_id" in
+ *crowdsec*) service_name="crowdsec" ;;
+ *netifyd*) service_name="netifyd" ;;
+ *adguardhome*) service_name="AdGuardHome" ;;
+ *wireguard*) service_name="wg-quick" ;;
+ *nginx*|*vhost*) service_name="nginx" ;;
+ *dnscrypt*) service_name="dnscrypt-proxy" ;;
+ *unbound*) service_name="unbound" ;;
+ *stubby*) service_name="stubby" ;;
+ *shadowsocks*) service_name="shadowsocks-libev" ;;
+ *zerotier*) service_name="zerotier" ;;
+ *tailscale*) service_name="tailscale" ;;
+ *docker*|*portainer*) service_name="dockerd" ;;
+ *homeassistant*) service_name="homeassistant" ;;
+ *jellyfin*) service_name="jellyfin" ;;
+ *nextcloud*) service_name="nextcloud" ;;
+ *uptimekuma*|*uptime-kuma*) service_name="uptime-kuma" ;;
+ *vaultwarden*) service_name="vaultwarden" ;;
+ *gitea*) service_name="gitea" ;;
+ *domoticz*) service_name="domoticz" ;;
+ *zigbee2mqtt*) service_name="zigbee2mqtt" ;;
+ *mosquitto*|*mqtt*) service_name="mosquitto" ;;
+ *netdata*) service_name="netdata" ;;
+ esac
+ fi
+
+ # Check if installed (support both packages.required[0] and packages[0] formats)
local packages=$(jsonfilter -i "$catalog" -e '@.packages.required[0]' 2>/dev/null)
+ [ -z "$packages" ] && packages=$(jsonfilter -i "$catalog" -e '@.packages[0]' 2>/dev/null)
local status="available"
local installed_version=""
+ local is_installed=0
+ local is_enabled=0
+ local is_active=0
+
if [ "$cache_ready" -eq 1 ] && [ -n "$packages" ]; then
installed_version=$(get_installed_version_from_cache "$packages")
if [ -n "$installed_version" ]; then
status="installed"
+ is_installed=1
+
+ # Check enabled/active status if service defined
+ if [ -n "$service_name" ]; then
+ if is_service_enabled "$service_name"; then
+ is_enabled=1
+ status="enabled"
+ fi
+ if is_service_active "$service_name"; then
+ is_active=1
+ status="active"
+ fi
+ fi
fi
fi
@@ -137,7 +201,10 @@ list_modules() {
json_add_string "version" "$module_version"
json_add_string "status" "$status"
[ -n "$installed_version" ] && json_add_string "installed_version" "$installed_version"
- json_add_boolean "installed" $([ "$status" = "installed" ] && echo true || echo false)
+ [ -n "$service_name" ] && json_add_string "service" "$service_name"
+ json_add_boolean "installed" "$is_installed"
+ json_add_boolean "enabled" "$is_enabled"
+ json_add_boolean "active" "$is_active"
json_close_object
else
printf "%-20s %-12s %-12s %-10s\n" \
diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox-wan-access b/package/secubox/secubox-core/root/usr/sbin/secubox-wan-access
new file mode 100644
index 00000000..595e7cac
--- /dev/null
+++ b/package/secubox/secubox-core/root/usr/sbin/secubox-wan-access
@@ -0,0 +1,235 @@
+#!/bin/sh
+#
+# SecuBox WAN Access Manager
+# Manages firewall rules for remote access to LuCI/SecuBox
+#
+
+. /lib/functions.sh
+
+RULE_PREFIX="secubox_wan"
+
+# Remove all SecuBox WAN access rules
+remove_rules() {
+ local changed=0
+
+ # Find and remove all secubox_wan rules
+ while uci -q get firewall.@rule[-1] >/dev/null 2>&1; do
+ local name=$(uci -q get firewall.@rule[-1].name)
+ if echo "$name" | grep -q "^${RULE_PREFIX}"; then
+ uci delete firewall.@rule[-1]
+ changed=1
+ else
+ break
+ fi
+ done
+
+ # Iterate through all rules to find secubox ones
+ local i=0
+ while true; do
+ local name=$(uci -q get firewall.@rule[$i].name)
+ [ -z "$name" ] && break
+
+ if echo "$name" | grep -q "^${RULE_PREFIX}"; then
+ uci delete firewall.@rule[$i]
+ changed=1
+ # Don't increment i since indices shift after delete
+ else
+ i=$((i + 1))
+ fi
+ done
+
+ [ "$changed" -eq 1 ] && return 0
+ return 1
+}
+
+# Detect WAN zone name (fallback to '*' for any)
+get_wan_zone() {
+ # Try common WAN zone names
+ for zone in wan WAN external internet; do
+ if uci -q get firewall.@zone[] 2>/dev/null | grep -q "name='$zone'"; then
+ echo "$zone"
+ return
+ fi
+ done
+ # Check for zone with wan/wan6 network
+ local i=0
+ while true; do
+ local name=$(uci -q get firewall.@zone[$i].name 2>/dev/null)
+ [ -z "$name" ] && break
+ local network=$(uci -q get firewall.@zone[$i].network 2>/dev/null)
+ if echo "$network" | grep -qE '^wan|wan6|wan '; then
+ echo "$name"
+ return
+ fi
+ i=$((i + 1))
+ done
+ # No WAN zone found - use '*' for any source
+ echo "*"
+}
+
+# Add a firewall rule
+add_rule() {
+ local name="$1"
+ local port="$2"
+ local proto="${3:-tcp}"
+ local src="${4:-$(get_wan_zone)}"
+
+ uci add firewall rule >/dev/null
+ uci set firewall.@rule[-1].name="$name"
+ uci set firewall.@rule[-1].src="$src"
+ uci set firewall.@rule[-1].dest_port="$port"
+ uci set firewall.@rule[-1].proto="$proto"
+ uci set firewall.@rule[-1].target="ACCEPT"
+ uci set firewall.@rule[-1].enabled="1"
+}
+
+# Apply rules based on secubox config
+apply_rules() {
+ config_load secubox
+
+ local enabled https_enabled https_port http_enabled http_port ssh_enabled ssh_port
+
+ config_get enabled remote enabled "0"
+ config_get https_enabled remote https_enabled "0"
+ config_get https_port remote https_port "443"
+ config_get http_enabled remote http_enabled "0"
+ config_get http_port remote http_port "80"
+ config_get ssh_enabled remote ssh_enabled "0"
+ config_get ssh_port remote ssh_port "22"
+
+ # Remove existing rules first
+ remove_rules
+
+ # Only add rules if WAN access is enabled
+ if [ "$enabled" = "1" ]; then
+ # HTTPS access
+ if [ "$https_enabled" = "1" ]; then
+ add_rule "${RULE_PREFIX}_https" "$https_port" "tcp" "wan"
+ echo "Added HTTPS access rule (port $https_port)"
+ fi
+
+ # HTTP access
+ if [ "$http_enabled" = "1" ]; then
+ add_rule "${RULE_PREFIX}_http" "$http_port" "tcp" "wan"
+ echo "Added HTTP access rule (port $http_port)"
+ fi
+
+ # SSH access
+ if [ "$ssh_enabled" = "1" ]; then
+ add_rule "${RULE_PREFIX}_ssh" "$ssh_port" "tcp" "wan"
+ echo "Added SSH access rule (port $ssh_port)"
+ fi
+ fi
+
+ uci commit firewall
+ /etc/init.d/firewall reload >/dev/null 2>&1
+
+ echo "WAN access rules applied"
+}
+
+# Show current status
+status() {
+ config_load secubox
+
+ local enabled https_enabled https_port http_enabled http_port ssh_enabled ssh_port
+
+ config_get enabled remote enabled "0"
+ config_get https_enabled remote https_enabled "0"
+ config_get https_port remote https_port "443"
+ config_get http_enabled remote http_enabled "0"
+ config_get http_port remote http_port "80"
+ config_get ssh_enabled remote ssh_enabled "0"
+ config_get ssh_port remote ssh_port "22"
+
+ echo "SecuBox WAN Access Status"
+ echo "========================="
+ echo "Master switch: $([ "$enabled" = "1" ] && echo "ENABLED" || echo "DISABLED")"
+ echo ""
+ echo "Services:"
+ echo " HTTPS (port $https_port): $([ "$https_enabled" = "1" ] && echo "ENABLED" || echo "disabled")"
+ echo " HTTP (port $http_port): $([ "$http_enabled" = "1" ] && echo "ENABLED" || echo "disabled")"
+ echo " SSH (port $ssh_port): $([ "$ssh_enabled" = "1" ] && echo "ENABLED" || echo "disabled")"
+ echo ""
+ echo "Active firewall rules:"
+ iptables -L INPUT -n --line-numbers 2>/dev/null | grep -E "dpt:(${https_port}|${http_port}|${ssh_port})" || echo " (none)"
+}
+
+# Enable WAN access
+enable() {
+ uci set secubox.remote.enabled='1'
+ uci commit secubox
+ apply_rules
+ echo "WAN access enabled"
+}
+
+# Disable WAN access
+disable() {
+ uci set secubox.remote.enabled='0'
+ uci commit secubox
+ remove_rules
+ uci commit firewall
+ /etc/init.d/firewall reload >/dev/null 2>&1
+ echo "WAN access disabled"
+}
+
+# JSON output for API
+json_status() {
+ config_load secubox
+
+ local enabled https_enabled https_port http_enabled http_port ssh_enabled ssh_port
+
+ config_get enabled remote enabled "0"
+ config_get https_enabled remote https_enabled "0"
+ config_get https_port remote https_port "443"
+ config_get http_enabled remote http_enabled "0"
+ config_get http_port remote http_port "80"
+ config_get ssh_enabled remote ssh_enabled "0"
+ config_get ssh_port remote ssh_port "22"
+
+ . /usr/share/libubox/jshn.sh
+ json_init
+ json_add_boolean "enabled" "$enabled"
+ json_add_object "services"
+ json_add_object "https"
+ json_add_boolean "enabled" "$https_enabled"
+ json_add_int "port" "$https_port"
+ json_close_object
+ json_add_object "http"
+ json_add_boolean "enabled" "$http_enabled"
+ json_add_int "port" "$http_port"
+ json_close_object
+ json_add_object "ssh"
+ json_add_boolean "enabled" "$ssh_enabled"
+ json_add_int "port" "$ssh_port"
+ json_close_object
+ json_close_object
+ json_dump
+}
+
+case "$1" in
+ apply)
+ apply_rules
+ ;;
+ remove)
+ remove_rules
+ uci commit firewall
+ /etc/init.d/firewall reload >/dev/null 2>&1
+ echo "WAN access rules removed"
+ ;;
+ enable)
+ enable
+ ;;
+ disable)
+ disable
+ ;;
+ status)
+ status
+ ;;
+ json)
+ json_status
+ ;;
+ *)
+ echo "Usage: $0 {apply|remove|enable|disable|status|json}"
+ exit 1
+ ;;
+esac