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