From e2428d807f084a781e62a1d8fdd43457d0cb95d1 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Fri, 30 Jan 2026 17:43:01 +0100 Subject: [PATCH] feat(v0.17): P2P Mesh Recovery, MITM Analytics, Swiss Army Knife Major features: - P2P Mesh distributed recovery infrastructure with blockchain catalog - MITM analytics proxy for external access monitoring (IP, country, scans) - SecuBox Swiss unified CLI tool for management & recovery - Python remote management console (secubox-console) - Multi-theme landing page generator (mirrorbox, cyberpunk, minimal, terminal, light) - Service Registry enhancements with health check and network diagnostics - Services page modernization with Service Registry API integration New components: - secubox-swiss: Swiss Army Knife unified management tool - secubox-mesh: P2P mesh networking and sync - secubox-recover: Snapshot, profiles, rollback, reborn scripts - secubox-console: Python remote management app - secubox_analytics.py: MITM traffic analysis addon Fixes: - Service Registry ACL permissions for secubox services page - Port status display (firewall_open detection) - RPC response handling for list_services Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 6 +- .../resources/view/cdn-cache/settings.js | 4 +- .../resources/view/secubox/services.js | 1491 +++++++++++++---- .../share/rpcd/acl.d/luci-app-secubox.json | 227 +-- .../resources/service-registry/api.js | 12 + .../view/service-registry/landing.js | 52 +- .../view/service-registry/overview.js | 14 +- .../usr/libexec/rpcd/luci.service-registry | 35 +- .../root/usr/sbin/secubox-landing-gen | 980 ++++++++--- .../rpcd/acl.d/luci-app-service-registry.json | 3 +- .../root/www/secubox-feed/Packages | 132 +- .../root/www/secubox-feed/Packages.gz | Bin 7692 -> 7700 bytes .../root/www/secubox-feed/apps-local.json | 130 +- .../luci-app-auth-guardian_0.4.0-r3_all.ipk | Bin 12084 -> 12080 bytes ...uci-app-bandwidth-manager_0.5.0-r2_all.ipk | Bin 66963 -> 66966 bytes .../luci-app-cdn-cache_0.5.0-r3_all.ipk | Bin 20434 -> 23189 bytes .../luci-app-client-guardian_0.4.0-r7_all.ipk | Bin 57045 -> 57047 bytes ...i-app-crowdsec-dashboard_0.7.0-r29_all.ipk | Bin 55583 -> 55585 bytes .../luci-app-cyberfeed_0.1.1-r1_all.ipk | Bin 12836 -> 12840 bytes .../luci-app-exposure_1.0.0-r3_all.ipk | Bin 20535 -> 20536 bytes .../luci-app-gitea_1.0.0-r2_all.ipk | Bin 15583 -> 15588 bytes .../luci-app-glances_1.0.0-r2_all.ipk | Bin 6965 -> 6967 bytes .../luci-app-haproxy_1.0.0-r8_all.ipk | Bin 34162 -> 34168 bytes .../luci-app-hexojs_1.0.0-r3_all.ipk | Bin 32980 -> 32984 bytes .../luci-app-ksm-manager_0.4.0-r2_all.ipk | Bin 18724 -> 18722 bytes .../luci-app-localai_0.1.0-r15_all.ipk | Bin 14360 -> 14360 bytes .../luci-app-lyrion_1.0.0-r1_all.ipk | Bin 6725 -> 6726 bytes .../luci-app-magicmirror2_0.4.0-r6_all.ipk | Bin 12274 -> 12277 bytes .../luci-app-mailinabox_1.0.0-r1_all.ipk | Bin 5483 -> 5481 bytes .../luci-app-media-flow_0.6.4-r1_all.ipk | Bin 19116 -> 19124 bytes .../luci-app-metablogizer_1.0.0-r3_all.ipk | Bin 21649 -> 21652 bytes .../luci-app-metabolizer_1.0.0-r2_all.ipk | Bin 4757 -> 4758 bytes .../luci-app-mitmproxy_0.4.0-r6_all.ipk | Bin 18933 -> 18933 bytes .../luci-app-mmpm_0.2.0-r3_all.ipk | Bin 7902 -> 7905 bytes .../luci-app-mqtt-bridge_0.4.0-r4_all.ipk | Bin 22780 -> 22777 bytes .../luci-app-ndpid_1.1.2-r2_all.ipk | Bin 22454 -> 22458 bytes ...uci-app-netdata-dashboard_0.5.0-r2_all.ipk | Bin 22398 -> 22402 bytes .../luci-app-network-modes_0.5.0-r3_all.ipk | Bin 55612 -> 55608 bytes .../luci-app-network-tweaks_1.0.0-r7_all.ipk | Bin 15465 -> 15458 bytes .../luci-app-nextcloud_1.0.0-r1_all.ipk | Bin 6486 -> 6488 bytes .../luci-app-ollama_0.1.0-r1_all.ipk | Bin 11995 -> 11997 bytes .../luci-app-picobrew_1.0.0-r1_all.ipk | Bin 9976 -> 9978 bytes .../luci-app-secubox-admin_1.0.0-r19_all.ipk | Bin 57099 -> 57099 bytes ...luci-app-secubox-crowdsec_1.0.0-r3_all.ipk | Bin 13914 -> 13915 bytes .../luci-app-secubox-netdiag_1.0.0-r1_all.ipk | Bin 12000 -> 12000 bytes .../luci-app-secubox-netifyd_1.2.1-r1_all.ipk | Bin 39499 -> 39500 bytes .../luci-app-secubox-portal_0.7.0-r2_all.ipk | Bin 32227 -> 32228 bytes ...-secubox-security-threats_1.0.0-r4_all.ipk | Bin 13906 -> 13907 bytes .../luci-app-secubox_0.7.1-r4_all.ipk | Bin 49905 -> 49906 bytes ...luci-app-service-registry_1.0.0-r1_all.ipk | Bin 33349 -> 39612 bytes .../luci-app-streamlit_1.0.0-r9_all.ipk | Bin 20473 -> 20470 bytes .../luci-app-system-hub_0.5.1-r4_all.ipk | Bin 66345 -> 66348 bytes .../luci-app-tor-shield_1.0.0-r10_all.ipk | Bin 24535 -> 24534 bytes .../luci-app-traffic-shaper_0.4.0-r2_all.ipk | Bin 15635 -> 15638 bytes .../luci-app-vhost-manager_0.5.0-r5_all.ipk | Bin 29226 -> 29227 bytes ...i-app-wireguard-dashboard_0.7.0-r5_all.ipk | Bin 45370 -> 45369 bytes .../luci-app-zigbee2mqtt_1.0.0-r2_all.ipk | Bin 7083 -> 7094 bytes .../luci-theme-secubox_0.4.7-r1_all.ipk | Bin 111796 -> 111797 bytes .../secubox-app-adguardhome_1.0.0-r2_all.ipk | Bin 2880 -> 2881 bytes .../secubox-app-auth-logger_1.2.2-r1_all.ipk | Bin 9377 -> 9375 bytes ...cubox-app-crowdsec-custom_1.1.0-r1_all.ipk | Bin 5758 -> 5756 bytes ...l-bouncer_0.0.31-r4_aarch64_cortex-a72.ipk | Bin 5049322 -> 5049322 bytes .../secubox-app-cyberfeed_0.2.1-r1_all.ipk | Bin 12450 -> 12455 bytes .../secubox-app-domoticz_1.0.0-r2_all.ipk | Bin 2548 -> 2547 bytes .../secubox-app-exposure_1.0.0-r1_all.ipk | Bin 6827 -> 6830 bytes .../secubox-app-gitea_1.0.0-r5_all.ipk | Bin 9406 -> 9409 bytes .../secubox-app-glances_1.0.0-r1_all.ipk | Bin 5534 -> 5536 bytes .../secubox-app-haproxy_1.0.0-r23_all.ipk | Bin 15682 -> 15682 bytes .../secubox-app-hexojs_1.0.0-r8_all.ipk | Bin 94935 -> 94934 bytes .../secubox-app-localai-wb_2.25.0-r1_all.ipk | Bin 7951 -> 7953 bytes .../secubox-app-localai_2.25.0-r1_all.ipk | Bin 5725 -> 5724 bytes .../secubox-app-lyrion_2.0.2-r1_all.ipk | Bin 7285 -> 7285 bytes .../secubox-app-magicmirror2_0.4.0-r8_all.ipk | Bin 9250 -> 9253 bytes .../secubox-app-mailinabox_2.0.0-r1_all.ipk | Bin 7573 -> 7574 bytes .../secubox-app-metabolizer_1.0.0-r3_all.ipk | Bin 13979 -> 13975 bytes .../secubox-app-mitmproxy_0.4.0-r16_all.ipk | Bin 10215 -> 10214 bytes .../secubox-app-mmpm_0.2.0-r5_all.ipk | Bin 3978 -> 3980 bytes .../secubox-app-nextcloud_1.0.0-r2_all.ipk | Bin 2957 -> 2957 bytes .../secubox-app-ollama_0.1.0-r1_all.ipk | Bin 5735 -> 5741 bytes .../secubox-app-picobrew_1.0.0-r7_all.ipk | Bin 5536 -> 5538 bytes .../secubox-app-streamlit_1.0.0-r5_all.ipk | Bin 11722 -> 11720 bytes .../secubox-app-tor_1.0.0-r1_all.ipk | Bin 7380 -> 7380 bytes .../secubox-app-webapp_1.5.0-r7_all.ipk | Bin 39167 -> 39168 bytes .../secubox-app-zigbee2mqtt_1.0.0-r3_all.ipk | Bin 3539 -> 3545 bytes .../secubox-feed/secubox-app_1.0.0-r2_all.ipk | Bin 11182 -> 11183 bytes .../secubox-core_0.10.0-r9_all.ipk | Bin 80068 -> 80069 bytes .../srv/mitmproxy/addons/secubox_analytics.py | 323 ++++ .../lib/secubox-console/secubox_console.py | 629 +++++++ .../secubox-core/root/etc/init.d/secubox-mesh | 51 + .../root/usr/lib/secubox/p2p-mesh.sh | 592 +++++++ .../secubox-core/root/usr/sbin/secubox-mesh | 11 + .../root/usr/sbin/secubox-recover | 628 +++++++ .../secubox-core/root/usr/sbin/secubox-swiss | 345 ++++ 93 files changed, 4900 insertions(+), 765 deletions(-) create mode 100644 package/secubox/secubox-app-mitmproxy/root/srv/mitmproxy/addons/secubox_analytics.py create mode 100644 package/secubox/secubox-console/root/usr/lib/secubox-console/secubox_console.py create mode 100644 package/secubox/secubox-core/root/etc/init.d/secubox-mesh create mode 100644 package/secubox/secubox-core/root/usr/lib/secubox/p2p-mesh.sh create mode 100644 package/secubox/secubox-core/root/usr/sbin/secubox-mesh create mode 100644 package/secubox/secubox-core/root/usr/sbin/secubox-recover create mode 100644 package/secubox/secubox-core/root/usr/sbin/secubox-swiss diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 771b4d48..4d9e88b8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -187,7 +187,11 @@ "Bash(gzip:*)", "Bash(python3:*)", "WebFetch(domain:192.168.255.1)", - "Bash(ssh-add:*)" + "Bash(ssh-add:*)", + "Bash(SSH_AUTH_SOCK=\"\" ssh -i ~/.ssh/id_rsa root@192.168.255.1 'grep \"\"s.anonymous\"\" /www/luci-static/resources/view/cdn-cache/settings.js')", + "Bash(SSH_AUTH_SOCK=\"\" ssh -i ~/.ssh/id_rsa root@192.168.255.1 'grep \"\"s.anonymous\"\" /www/luci-static/resources/view/cdn-cache/settings.js; rm -f /tmp/luci-indexcache*')", + "Bash(SSH_AUTH_SOCK=\"\" ssh -i ~/.ssh/id_ed25519 root@192.168.255.1 'grep \"\"s.anonymous\"\" /www/luci-static/resources/view/cdn-cache/settings.js; rm -f /tmp/luci-indexcache*')", + "Bash(SSH_AUTH_SOCK=\"\" ssh -i ~/.ssh/id_ed25519 root@192.168.255.1 'echo \"\"=== CrowdSec Decisions ===\"\"; cscli decisions list 2>/dev/null | head -10; echo \"\"\"\"; echo \"\"=== Auth Guardian Status ===\"\"; ubus call luci.auth-guardian status 2>/dev/null | head -20')" ] } } diff --git a/package/secubox/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/settings.js b/package/secubox/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/settings.js index 772d4b5c..f47e64d0 100644 --- a/package/secubox/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/settings.js +++ b/package/secubox/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/settings.js @@ -71,7 +71,7 @@ return view.extend({ s = m.section(form.TableSection, 'cache_policy', 'Cache Policies', 'Define caching rules for specific domains and file types'); s.addremove = true; - s.anonymous = true; + s.anonymous = false; s.sortable = true; o = s.option(form.Flag, 'enabled', 'Enabled'); @@ -108,7 +108,7 @@ return view.extend({ s = m.section(form.TableSection, 'exclusion', 'Exclusions', 'Domains that should never be cached'); s.addremove = true; - s.anonymous = true; + s.anonymous = false; o = s.option(form.Flag, 'enabled', 'Enabled'); o.default = '1'; diff --git a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/services.js b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/services.js index 6b65e9ab..49ebfb67 100644 --- a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/services.js +++ b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/services.js @@ -4,180 +4,245 @@ 'require ui'; 'require poll'; +// Use Service Registry API - no expect to get raw response +var callListServices = rpc.declare({ + object: 'luci.service-registry', + method: 'list_services' +}); + +var callGenerateLanding = rpc.declare({ + object: 'luci.service-registry', + method: 'generate_landing_page', + expect: {} +}); + +// Fallback to basic secubox services var callGetServices = rpc.declare({ object: 'luci.secubox', method: 'get_services', expect: { services: [] } }); +// Network info +var callGetNetworkInfo = rpc.declare({ + object: 'luci.service-registry', + method: 'get_network_info' +}); + +// Health check +var callCheckAllHealth = rpc.declare({ + object: 'luci.service-registry', + method: 'check_all_health' +}); + return view.extend({ servicesData: [], + providersData: {}, selectedCategory: 'all', load: function() { - return callGetServices().then(function(result) { - return { services: result.services || result || [] }; + // Try Service Registry first, fallback to basic secubox + console.log('[SERVICES] Starting load...'); + return callListServices().then(function(result) { + console.log('[SERVICES] list_services raw result:', result); + // Handle both array format and object format + var services = []; + var providers = {}; + if (Array.isArray(result)) { + services = result; + } else if (result && result.services) { + services = result.services; + providers = result.providers || {}; + } + console.log('[SERVICES] services count:', services.length); + return { + services: services, + providers: providers, + source: 'service-registry' + }; + }).catch(function(err) { + console.error('[SERVICES] list_services failed:', err); + return callGetServices().then(function(result) { + console.log('[SERVICES] get_services fallback result:', result); + var services = Array.isArray(result) ? result : (result.services || []); + return { + services: services, + providers: {}, + source: 'secubox' + }; + }); }).catch(function(err) { console.error('[SERVICES] Load error:', err); - return { services: [] }; + return { services: [], providers: {}, source: 'error' }; }); }, render: function(data) { var services = data.services || []; + var providers = data.providers || {}; + var source = data.source || 'unknown'; this.servicesData = services; + this.providersData = providers; var self = this; // Categorize services var categories = { 'all': { name: 'All Services', icon: '๐Ÿ“ก', count: services.length }, + 'published': { name: 'Published', icon: '๐ŸŒ', count: 0 }, 'security': { name: 'Security', icon: '๐Ÿ›ก๏ธ', count: 0 }, - 'network': { name: 'Network', icon: '๐ŸŒ', count: 0 }, - 'monitoring': { name: 'Monitoring', icon: '๐Ÿ“Š', count: 0 }, - 'dns': { name: 'DNS', icon: '๐Ÿ”', count: 0 }, - 'web': { name: 'Web', icon: '๐ŸŒ', count: 0 }, + 'network': { name: 'Network', icon: '๐Ÿ”Œ', count: 0 }, + 'proxy': { name: 'Proxy/HAProxy', icon: 'โšก', count: 0 }, + 'privacy': { name: 'Privacy/Tor', icon: '๐Ÿง…', count: 0 }, + 'container': { name: 'Containers', icon: '๐Ÿ“ฆ', count: 0 }, 'system': { name: 'System', icon: 'โš™๏ธ', count: 0 } }; services.forEach(function(svc) { - var cat = self.categorizeService(svc); + if (svc.published) categories['published'].count++; + var cat = svc.category || self.categorizeService(svc); if (categories[cat]) categories[cat].count++; }); - var container = E('div', { 'class': 'cbi-map secubox-services', 'style': 'background: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 100%); min-height: 100vh; padding: 20px;' }, [ - // CSS + // Provider stats + var haproxyCount = providers.haproxy ? providers.haproxy.count : 0; + var torCount = providers.tor ? providers.tor.count : 0; + var runningCount = services.filter(function(s) { return s.status === 'running'; }).length; + var publishedCount = services.filter(function(s) { return s.published; }).length; + + var container = E('div', { 'class': 'cbi-map secubox-services' }, [ E('style', {}, this.getStyles()), // Header - E('div', { 'class': 'services-header' }, [ - E('div', { 'class': 'services-title' }, [ - E('span', { 'class': 'services-icon' }, '๐Ÿ“ก'), - E('span', {}, 'SERVICES REGISTRY') + E('div', { 'class': 'sr-header' }, [ + E('div', { 'class': 'sr-title' }, [ + E('span', { 'class': 'sr-logo' }, '๐Ÿ“ก'), + E('div', { 'class': 'sr-title-text' }, [ + E('h1', {}, 'Services Registry'), + E('p', {}, services.length + ' services discovered ยท Source: ' + source) + ]) ]), - E('div', { 'class': 'services-subtitle' }, - services.length + ' services discovered ยท P2P Hub Integration Ready') + E('div', { 'class': 'sr-actions-top' }, [ + E('a', { + 'class': 'sr-btn primary', + 'href': L.url('admin', 'services', 'service-registry') + }, [E('span', {}, '๐Ÿš€'), ' Full Registry']), + E('a', { + 'class': 'sr-btn', + 'href': '/secubox-services.html', + 'target': '_blank' + }, [E('span', {}, '๐ŸŒ'), ' Landing Page']) + ]) ]), - // Category filters - E('div', { 'class': 'services-categories' }, + // Stats Grid + E('div', { 'class': 'sr-stats' }, [ + E('div', { 'class': 'stat-card' }, [ + E('div', { 'class': 'stat-icon' }, '๐Ÿ“ก'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, services.length), + E('div', { 'class': 'stat-label' }, 'Total Services') + ]) + ]), + E('div', { 'class': 'stat-card success' }, [ + E('div', { 'class': 'stat-icon' }, 'โœ…'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, runningCount), + E('div', { 'class': 'stat-label' }, 'Running') + ]) + ]), + E('div', { 'class': 'stat-card accent' }, [ + E('div', { 'class': 'stat-icon' }, '๐ŸŒ'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, publishedCount), + E('div', { 'class': 'stat-label' }, 'Published') + ]) + ]), + E('div', { 'class': 'stat-card info' }, [ + E('div', { 'class': 'stat-icon' }, 'โšก'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, haproxyCount), + E('div', { 'class': 'stat-label' }, 'HAProxy Vhosts') + ]) + ]), + E('div', { 'class': 'stat-card purple' }, [ + E('div', { 'class': 'stat-icon' }, '๐Ÿง…'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, torCount), + E('div', { 'class': 'stat-label' }, 'Tor Services') + ]) + ]) + ]), + + // Category Filters + E('div', { 'class': 'sr-categories' }, Object.keys(categories).map(function(key) { var cat = categories[key]; + if (cat.count === 0 && key !== 'all') return null; return E('button', { - 'class': 'category-btn' + (self.selectedCategory === key ? ' active' : ''), + 'class': 'cat-btn' + (self.selectedCategory === key ? ' active' : ''), 'data-category': key, 'click': function() { self.filterByCategory(key); } }, [ E('span', { 'class': 'cat-icon' }, cat.icon), E('span', { 'class': 'cat-name' }, cat.name), - E('span', { 'class': 'cat-count' }, cat.count) + E('span', { 'class': 'cat-count' }, String(cat.count)) ]); - }) + }).filter(Boolean) ), - // Stats panel - E('div', { 'class': 'services-stats' }, [ - E('div', { 'class': 'stat-card' }, [ - E('div', { 'class': 'stat-value' }, services.length), - E('div', { 'class': 'stat-label' }, 'Total Services') - ]), - E('div', { 'class': 'stat-card accent' }, [ - E('div', { 'class': 'stat-value' }, services.filter(function(s) { return s.status === 'running' || s.running; }).length), - E('div', { 'class': 'stat-label' }, 'Running') - ]), - E('div', { 'class': 'stat-card warning' }, [ - E('div', { 'class': 'stat-value' }, services.filter(function(s) { return s.shared || s.p2p_enabled; }).length), - E('div', { 'class': 'stat-label' }, 'P2P Shared') - ]), - E('div', { 'class': 'stat-card info' }, [ - E('div', { 'class': 'stat-value' }, services.filter(function(s) { return s.port; }).length), - E('div', { 'class': 'stat-label' }, 'With Ports') - ]) - ]), - // Quick Actions - E('div', { 'class': 'services-actions' }, [ + E('div', { 'class': 'sr-quick-actions' }, [ E('button', { - 'class': 'action-btn primary', - 'click': function() { self.discoverServices(); } - }, [ - E('span', {}, '๐Ÿ”„'), - E('span', {}, 'Discover Services') - ]), + 'class': 'sr-btn primary', + 'click': function() { self.refreshServices(); } + }, [E('span', {}, '๐Ÿ”„'), ' Refresh']), E('button', { - 'class': 'action-btn', - 'click': function() { self.registerService(); } - }, [ - E('span', {}, 'โž•'), - E('span', {}, 'Register Service') - ]), + 'class': 'sr-btn accent', + 'click': function() { self.showNetworkDiagnostics(); } + }, [E('span', {}, '๐ŸŒ'), ' Network Diagnostics']), E('button', { - 'class': 'action-btn', - 'click': function() { self.exportRegistry(); } - }, [ - E('span', {}, '๐Ÿ“ค'), - E('span', {}, 'Export Registry') - ]), + 'class': 'sr-btn', + 'click': function() { self.checkAllHealth(); } + }, [E('span', {}, '๐Ÿฉบ'), ' Health Check']), E('button', { - 'class': 'action-btn', - 'click': function() { window.location.href = L.url('admin', 'secubox', 'apps') + '#p2p-hub'; } - }, [ - E('span', {}, '๐ŸŒ'), - E('span', {}, 'P2P Hub') - ]) + 'class': 'sr-btn', + 'click': function() { self.regenerateLanding(); } + }, [E('span', {}, '๐Ÿ“„'), ' Regenerate Landing']), + E('button', { + 'class': 'sr-btn', + 'click': function() { self.exportServices(); } + }, [E('span', {}, '๐Ÿ“ค'), ' Export JSON']), + E('a', { + 'class': 'sr-btn', + 'href': L.url('admin', 'services', 'service-registry', 'publish') + }, [E('span', {}, 'โž•'), ' Publish Service']) ]), - // Services grid - E('div', { 'class': 'services-panel' }, [ + // Services Grid + E('div', { 'class': 'sr-panel' }, [ E('div', { 'class': 'panel-header' }, [ - E('span', { 'class': 'panel-title' }, 'ACTIVE SERVICES'), - E('span', { 'class': 'panel-badge' }, services.length + ' discovered') + E('h2', {}, 'Discovered Services'), + E('span', { 'class': 'panel-badge' }, services.length + ' total') ]), - E('div', { 'class': 'services-grid', 'id': 'services-container' }, + E('div', { 'class': 'sr-grid', 'id': 'services-grid' }, services.length > 0 ? services.map(function(svc) { return self.renderServiceCard(svc); }) : [E('div', { 'class': 'empty-state' }, [ E('div', { 'class': 'empty-icon' }, '๐Ÿ“ก'), - E('div', { 'class': 'empty-text' }, 'No services discovered'), - E('div', { 'class': 'empty-hint' }, 'Click "Discover Services" to scan for available services') + E('h3', {}, 'No Services Found'), + E('p', {}, 'Click Refresh to discover services or use the Full Registry for more options') ])] ) ]), - // P2P Integration panel - E('div', { 'class': 'services-panel p2p-panel' }, [ - E('div', { 'class': 'panel-header' }, [ - E('span', { 'class': 'panel-title' }, '๐ŸŒ P2P HUB INTEGRATION'), - E('span', { 'class': 'panel-badge accent' }, 'Ready') - ]), - E('div', { 'class': 'p2p-info' }, [ - E('div', { 'class': 'p2p-feature' }, [ - E('span', { 'class': 'feature-icon' }, '๐Ÿ“ก'), - E('div', { 'class': 'feature-content' }, [ - E('div', { 'class': 'feature-title' }, 'Service Discovery'), - E('div', { 'class': 'feature-desc' }, 'mDNS/Avahi automatic peer discovery') - ]) - ]), - E('div', { 'class': 'p2p-feature' }, [ - E('span', { 'class': 'feature-icon' }, '๐Ÿ”—'), - E('div', { 'class': 'feature-content' }, [ - E('div', { 'class': 'feature-title' }, 'Mesh Networking'), - E('div', { 'class': 'feature-desc' }, 'WireGuard-secured P2P connections') - ]) - ]), - E('div', { 'class': 'p2p-feature' }, [ - E('span', { 'class': 'feature-icon' }, 'โš–๏ธ'), - E('div', { 'class': 'feature-content' }, [ - E('div', { 'class': 'feature-title' }, 'Load Balancing'), - E('div', { 'class': 'feature-desc' }, 'HAProxy distributed service balancing') - ]) - ]), - E('div', { 'class': 'p2p-feature' }, [ - E('span', { 'class': 'feature-icon' }, '๐Ÿ’š'), - E('div', { 'class': 'feature-content' }, [ - E('div', { 'class': 'feature-title' }, 'Health Monitoring'), - E('div', { 'class': 'feature-desc' }, 'Auto-repair with peer failover') - ]) - ]) + // Provider Status + E('div', { 'class': 'sr-providers' }, [ + E('h2', {}, 'Provider Status'), + E('div', { 'class': 'provider-grid' }, [ + this.renderProviderCard('HAProxy', providers.haproxy, 'โšก', 'Reverse Proxy'), + this.renderProviderCard('Tor', providers.tor, '๐Ÿง…', 'Hidden Services'), + this.renderProviderCard('Direct', providers.direct, '๐Ÿ”Œ', 'Direct Ports'), + this.renderProviderCard('LXC', providers.lxc, '๐Ÿ“ฆ', 'Containers') ]) ]) ]); @@ -185,71 +250,86 @@ return view.extend({ return container; }, + renderProviderCard: function(name, data, icon, desc) { + var status = data ? data.status : 'unknown'; + var count = data ? (data.count || 0) : 0; + var isRunning = status === 'running'; + + return E('div', { 'class': 'provider-card' + (isRunning ? ' active' : '') }, [ + E('div', { 'class': 'provider-icon' }, icon), + E('div', { 'class': 'provider-info' }, [ + E('div', { 'class': 'provider-name' }, name), + E('div', { 'class': 'provider-desc' }, desc) + ]), + E('div', { 'class': 'provider-stats' }, [ + E('div', { 'class': 'provider-count' }, String(count)), + E('div', { 'class': 'provider-status ' + status }, isRunning ? 'โ— Online' : 'โ—‹ Offline') + ]) + ]); + }, + categorizeService: function(svc) { - var name = (svc.name || svc.service || '').toLowerCase(); - if (name.match(/crowdsec|firewall|guard|security|fail2ban/)) return 'security'; - if (name.match(/dns|dnsmasq|unbound|pihole/)) return 'dns'; - if (name.match(/nginx|apache|httpd|luci|uhttpd/)) return 'web'; - if (name.match(/netdata|prometheus|grafana|monitor/)) return 'monitoring'; - if (name.match(/network|wan|lan|wifi|wireguard|vpn/)) return 'network'; + var name = (svc.name || svc.service || svc.id || '').toLowerCase(); + var source = svc.source || ''; + + if (source === 'haproxy' || svc.haproxy) return 'proxy'; + if (source === 'tor' || svc.tor) return 'privacy'; + if (source === 'lxc' || svc.container) return 'container'; + if (name.match(/crowdsec|firewall|guard|security|fail2ban|auth/)) return 'security'; + if (name.match(/network|wan|lan|wifi|wireguard|vpn|dns/)) return 'network'; return 'system'; }, renderServiceCard: function(svc) { var self = this; - var name = svc.name || svc.service || 'Unknown'; + var name = svc.name || svc.service || svc.id || 'Unknown'; var status = svc.status || (svc.running ? 'running' : 'stopped'); - var port = svc.port || svc.listen_port || ''; - var protocol = svc.protocol || 'tcp'; - var category = this.categorizeService(svc); + var category = svc.category || this.categorizeService(svc); + var source = svc.source || 'direct'; + var published = svc.published; + var urls = svc.urls || {}; - var statusColors = { - 'running': { bg: 'rgba(16, 185, 129, 0.15)', color: '#10b981', text: 'RUNNING' }, - 'stopped': { bg: 'rgba(239, 68, 68, 0.15)', color: '#ef4444', text: 'STOPPED' }, - 'unknown': { bg: 'rgba(156, 163, 175, 0.15)', color: '#9ca3af', text: 'UNKNOWN' } - }; - var statusStyle = statusColors[status] || statusColors['unknown']; + var statusClass = status === 'running' ? 'running' : (status === 'disabled' ? 'disabled' : 'stopped'); var categoryIcons = { - 'security': '๐Ÿ›ก๏ธ', 'dns': '๐Ÿ”', 'web': '๐ŸŒ', - 'monitoring': '๐Ÿ“Š', 'network': '๐ŸŒ', 'system': 'โš™๏ธ' + 'proxy': 'โšก', 'privacy': '๐Ÿง…', 'security': '๐Ÿ›ก๏ธ', + 'network': '๐Ÿ”Œ', 'container': '๐Ÿ“ฆ', 'system': 'โš™๏ธ', + 'services': '๐Ÿ“ก', 'other': '๐Ÿ“' }; - return E('div', { 'class': 'service-card', 'data-category': category, 'data-status': status }, [ - E('div', { 'class': 'service-header' }, [ - E('span', { 'class': 'service-icon' }, categoryIcons[category] || '๐Ÿ“ก'), - E('span', { - 'class': 'service-status', - 'style': 'background:' + statusStyle.bg + ';color:' + statusStyle.color - }, statusStyle.text) + return E('div', { + 'class': 'service-card' + (published ? ' published' : ''), + 'data-category': category, + 'data-status': status + }, [ + E('div', { 'class': 'svc-header' }, [ + E('span', { 'class': 'svc-icon' }, categoryIcons[category] || '๐Ÿ“ก'), + E('div', { 'class': 'svc-badges' }, [ + published ? E('span', { 'class': 'badge published' }, '๐ŸŒ Published') : null, + E('span', { 'class': 'badge source' }, source) + ]) ]), - E('div', { 'class': 'service-name' }, name.toUpperCase()), - E('div', { 'class': 'service-meta' }, [ - port ? E('span', { 'class': 'meta-item' }, '๐Ÿ”Œ ' + protocol.toUpperCase() + ':' + port) : null, - E('span', { 'class': 'meta-item' }, '๐Ÿ“ ' + category) + E('h3', { 'class': 'svc-name' }, name), + E('div', { 'class': 'svc-status ' + statusClass }, [ + E('span', { 'class': 'status-dot' }), + status.charAt(0).toUpperCase() + status.slice(1) ]), - E('div', { 'class': 'service-actions' }, [ - E('button', { - 'class': 'svc-btn', - 'title': 'Share to P2P Hub', - 'click': function() { self.shareService(svc); } - }, '๐ŸŒ'), + urls.local || urls.clearnet || urls.onion ? E('div', { 'class': 'svc-urls' }, [ + urls.local ? E('a', { 'href': urls.local, 'target': '_blank', 'class': 'url-link local' }, '๐Ÿ  Local') : null, + urls.clearnet ? E('a', { 'href': urls.clearnet, 'target': '_blank', 'class': 'url-link clearnet' }, '๐ŸŒ Web') : null, + urls.onion ? E('span', { 'class': 'url-link onion', 'title': urls.onion }, '๐Ÿง… Onion') : null + ]) : null, + E('div', { 'class': 'svc-actions' }, [ E('button', { 'class': 'svc-btn', 'title': 'View Details', - 'click': function() { self.viewServiceDetails(svc); } + 'click': function() { self.viewDetails(svc); } }, '๐Ÿ‘๏ธ'), - status === 'running' ? - E('button', { - 'class': 'svc-btn danger', - 'title': 'Stop Service', - 'click': function() { self.controlService(name, 'stop'); } - }, 'โน๏ธ') : - E('button', { - 'class': 'svc-btn success', - 'title': 'Start Service', - 'click': function() { self.controlService(name, 'start'); } - }, 'โ–ถ๏ธ') + !published ? E('a', { + 'class': 'svc-btn primary', + 'title': 'Publish', + 'href': L.url('admin', 'services', 'service-registry', 'publish') + }, '๐Ÿ“ค') : null ]) ]); }, @@ -257,185 +337,994 @@ return view.extend({ filterByCategory: function(category) { this.selectedCategory = category; var cards = document.querySelectorAll('.service-card'); - var buttons = document.querySelectorAll('.category-btn'); + var buttons = document.querySelectorAll('.cat-btn'); buttons.forEach(function(btn) { btn.classList.toggle('active', btn.dataset.category === category); }); cards.forEach(function(card) { - if (category === 'all' || card.dataset.category === category) { - card.style.display = ''; - } else { - card.style.display = 'none'; - } + var cardCat = card.dataset.category; + var isPublished = card.classList.contains('published'); + + var show = category === 'all' || + (category === 'published' && isPublished) || + cardCat === category; + + card.style.display = show ? '' : 'none'; }); }, - discoverServices: function() { + refreshServices: function() { var self = this; - ui.showModal('Discovering Services', [ - E('div', { 'style': 'text-align:center;padding:30px;' }, [ - E('div', { 'class': 'spinning', 'style': 'font-size:48px;margin-bottom:20px;' }, '๐Ÿ”„'), - E('div', {}, 'Scanning for services...') - ]) + ui.showModal('Refreshing', [ + E('p', { 'class': 'spinning' }, '๐Ÿ”„ Discovering services...') ]); - callGetServices().then(function(result) { + this.load().then(function(data) { ui.hideModal(); - var services = result.services || result || []; - ui.addNotification(null, E('p', 'Discovered ' + services.length + ' services'), 'info'); + ui.addNotification(null, E('p', 'โœ… Found ' + data.services.length + ' services'), 'info'); window.location.reload(); }).catch(function(err) { ui.hideModal(); - ui.addNotification(null, E('p', 'Discovery failed: ' + err.message), 'error'); + ui.addNotification(null, E('p', 'โŒ Error: ' + err.message), 'error'); }); }, - registerService: function() { - var self = this; - var nameInput = E('input', { 'type': 'text', 'class': 'modal-input', 'placeholder': 'Service name' }); - var portInput = E('input', { 'type': 'number', 'class': 'modal-input', 'placeholder': 'Port (e.g., 8080)' }); - var protocolSelect = E('select', { 'class': 'modal-input' }, [ - E('option', { 'value': 'tcp' }, 'TCP'), - E('option', { 'value': 'udp' }, 'UDP'), - E('option', { 'value': 'http' }, 'HTTP'), - E('option', { 'value': 'https' }, 'HTTPS') + regenerateLanding: function() { + ui.showModal('Generating', [ + E('p', { 'class': 'spinning' }, '๐Ÿ“„ Regenerating landing page...') ]); - ui.showModal('Register New Service', [ - E('div', { 'class': 'modal-form' }, [ - E('label', {}, 'Service Name'), - nameInput, - E('label', {}, 'Port'), - portInput, - E('label', {}, 'Protocol'), - protocolSelect - ]), - E('div', { 'class': 'modal-actions' }, [ - E('button', { 'class': 'cbi-button cbi-button-positive', 'click': function() { - var name = nameInput.value; - var port = portInput.value; - if (name && port) { - ui.addNotification(null, E('p', 'Service "' + name + '" registered on port ' + port), 'success'); - ui.hideModal(); - } - }}, 'Register'), - E('button', { 'class': 'cbi-button', 'click': function() { ui.hideModal(); }}, 'Cancel') - ]) - ]); + callGenerateLanding().then(function(result) { + ui.hideModal(); + if (result.success) { + ui.addNotification(null, E('p', 'โœ… Landing page regenerated'), 'info'); + } else { + ui.addNotification(null, E('p', 'โŒ ' + (result.error || 'Failed')), 'error'); + } + }).catch(function(err) { + ui.hideModal(); + ui.addNotification(null, E('p', 'โŒ Error: ' + err.message), 'error'); + }); }, - shareService: function(svc) { - var name = svc.name || svc.service || 'Unknown'; - ui.showModal('Share Service to P2P Hub', [ - E('div', { 'style': 'padding:20px;text-align:center;' }, [ - E('div', { 'style': 'font-size:48px;margin-bottom:20px;' }, '๐ŸŒ'), - E('div', { 'style': 'font-size:18px;font-weight:600;margin-bottom:10px;' }, name.toUpperCase()), - E('div', { 'style': 'color:#888;margin-bottom:20px;' }, 'Share this service with connected P2P peers?'), - E('div', { 'class': 'share-options', 'style': 'display:flex;gap:10px;justify-content:center;margin-bottom:20px;' }, [ - E('label', { 'style': 'display:flex;align-items:center;gap:8px;' }, [ - E('input', { 'type': 'checkbox', 'checked': true }), - 'Enable load balancing' - ]), - E('label', { 'style': 'display:flex;align-items:center;gap:8px;' }, [ - E('input', { 'type': 'checkbox', 'checked': true }), - 'Health monitoring' - ]) - ]) - ]), - E('div', { 'class': 'modal-actions', 'style': 'display:flex;gap:10px;justify-content:center;' }, [ - E('button', { 'class': 'cbi-button cbi-button-positive', 'click': function() { - ui.addNotification(null, E('p', 'Service "' + name + '" shared to P2P Hub'), 'success'); - ui.hideModal(); - }}, 'Share'), - E('button', { 'class': 'cbi-button', 'click': function() { ui.hideModal(); }}, 'Cancel') - ]) - ]); - }, + exportServices: function() { + var data = JSON.stringify({ + services: this.servicesData, + providers: this.providersData, + exported: new Date().toISOString() + }, null, 2); - viewServiceDetails: function(svc) { - var name = svc.name || svc.service || 'Unknown'; - ui.showModal('Service Details: ' + name, [ - E('pre', { 'style': 'background:#1a1a2e;padding:15px;border-radius:8px;color:#e0e0e0;overflow:auto;max-height:400px;' }, - JSON.stringify(svc, null, 2)), - E('div', { 'style': 'margin-top:15px;' }, [ - E('button', { 'class': 'cbi-button', 'click': function() { ui.hideModal(); }}, 'Close') - ]) - ]); - }, - - controlService: function(name, action) { - ui.addNotification(null, E('p', 'Service ' + action + ' command sent for ' + name), 'info'); - }, - - exportRegistry: function() { - var data = JSON.stringify(this.servicesData, null, 2); var blob = new Blob([data], { type: 'application/json' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; - a.download = 'secubox-services-registry.json'; + a.download = 'secubox-services-' + new Date().toISOString().split('T')[0] + '.json'; a.click(); URL.revokeObjectURL(url); - ui.addNotification(null, E('p', 'Services registry exported'), 'success'); + + ui.addNotification(null, E('p', 'โœ… Exported ' + this.servicesData.length + ' services'), 'success'); + }, + + viewDetails: function(svc) { + ui.showModal('Service Details: ' + (svc.name || svc.id), [ + E('pre', { 'class': 'json-view' }, JSON.stringify(svc, null, 2)), + E('div', { 'class': 'modal-footer' }, [ + E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, 'Close') + ]) + ]); + }, + + showNetworkDiagnostics: function() { + var self = this; + ui.showModal('Network Diagnostics', [ + E('div', { 'class': 'diagnostics-loading' }, [ + E('p', { 'class': 'spinning' }, '๐ŸŒ'), + E('p', {}, 'Gathering network information...') + ]) + ]); + + callGetNetworkInfo().then(function(info) { + console.log('[DIAG] Network info:', info); + var content = []; + + // Public IP Section + var ipv4 = info.ipv4 ? info.ipv4.address : (info.public_ipv4 || 'Not detected'); + var ipv6 = info.ipv6 ? (info.ipv6.address || info.ipv6.status) : (info.public_ipv6 || 'Not configured'); + + content.push(E('div', { 'class': 'diag-section' }, [ + E('h3', {}, '๐ŸŒ Public IP Addresses'), + E('div', { 'class': 'diag-grid' }, [ + E('div', { 'class': 'diag-item' }, [ + E('span', { 'class': 'diag-label' }, 'IPv4'), + E('span', { 'class': 'diag-value' }, ipv4) + ]), + E('div', { 'class': 'diag-item' }, [ + E('span', { 'class': 'diag-label' }, 'IPv6'), + E('span', { 'class': 'diag-value mono' }, ipv6) + ]) + ]) + ])); + + // LAN Info Section + content.push(E('div', { 'class': 'diag-section' }, [ + E('h3', {}, '๐Ÿ  Local Network'), + E('div', { 'class': 'diag-grid' }, [ + E('div', { 'class': 'diag-item' }, [ + E('span', { 'class': 'diag-label' }, 'LAN IP'), + E('span', { 'class': 'diag-value' }, info.lan_ip || info.local_ip || 'Unknown') + ]), + E('div', { 'class': 'diag-item' }, [ + E('span', { 'class': 'diag-label' }, 'HAProxy'), + E('span', { 'class': 'diag-value ' + (info.haproxy && info.haproxy.status === 'running' ? 'success' : '') }, + info.haproxy ? info.haproxy.status : 'Unknown') + ]) + ]) + ])); + + // Firewall/Ports Section + var firewall = info.firewall || {}; + var extPorts = info.external_ports || {}; + var portItems = []; + + // HTTP port + var httpOpen = firewall.http_open || (extPorts.http && extPorts.http.status === 'firewall_open'); + portItems.push(E('div', { 'class': 'diag-port ' + (httpOpen ? 'open' : 'closed') }, [ + E('span', { 'class': 'port-num' }, '80'), + E('span', { 'class': 'port-name' }, 'HTTP'), + E('span', { 'class': 'port-status' }, httpOpen ? 'โœ“ Open' : 'โœ— Closed') + ])); + + // HTTPS port + var httpsOpen = firewall.https_open || (extPorts.https && extPorts.https.status === 'firewall_open'); + portItems.push(E('div', { 'class': 'diag-port ' + (httpsOpen ? 'open' : 'closed') }, [ + E('span', { 'class': 'port-num' }, '443'), + E('span', { 'class': 'port-name' }, 'HTTPS'), + E('span', { 'class': 'port-status' }, httpsOpen ? 'โœ“ Open' : 'โœ— Closed') + ])); + + content.push(E('div', { 'class': 'diag-section' }, [ + E('h3', {}, '๐Ÿ”Œ Firewall Ports'), + E('div', { 'class': 'diag-ports' }, portItems) + ])); + + // Overall Status + var allGood = ipv4 !== 'Not detected' && httpOpen && httpsOpen; + content.push(E('div', { 'class': 'diag-section' }, [ + E('h3', {}, '๐Ÿ“Š Overall Status'), + E('div', { 'class': 'diag-grid' }, [ + E('div', { 'class': 'diag-item' }, [ + E('span', { 'class': 'diag-label' }, 'Internet'), + E('span', { 'class': 'diag-value ' + (ipv4 !== 'Not detected' ? 'success' : 'error') }, + ipv4 !== 'Not detected' ? 'โœ“ Connected' : 'โœ— Offline') + ]), + E('div', { 'class': 'diag-item' }, [ + E('span', { 'class': 'diag-label' }, 'Ready for Services'), + E('span', { 'class': 'diag-value ' + (allGood ? 'success' : 'error') }, + allGood ? 'โœ“ Yes' : 'โš  Check ports') + ]) + ]) + ])); + + // Update modal content + ui.showModal('๐ŸŒ Network Diagnostics', [ + E('div', { 'class': 'diagnostics-panel' }, content), + E('div', { 'class': 'modal-footer' }, [ + E('button', { + 'class': 'cbi-button', + 'click': function() { self.showNetworkDiagnostics(); } + }, '๐Ÿ”„ Refresh'), + E('button', { 'class': 'cbi-button cbi-button-positive', 'click': ui.hideModal }, 'Close') + ]) + ]); + }).catch(function(err) { + ui.showModal('Network Diagnostics Error', [ + E('p', { 'class': 'error' }, 'โŒ Failed to get network info: ' + err.message), + E('div', { 'class': 'modal-footer' }, [ + E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, 'Close') + ]) + ]); + }); + }, + + checkAllHealth: function() { + var self = this; + ui.showModal('Health Check', [ + E('div', { 'class': 'diagnostics-loading' }, [ + E('p', { 'class': 'spinning' }, '๐Ÿฉบ'), + E('p', {}, 'Checking service health...') + ]) + ]); + + callCheckAllHealth().then(function(result) { + console.log('[HEALTH] Result:', result); + var health = result.health || result || {}; + var healthItems = []; + + // Provider status section + var providers = []; + if (health.haproxy) { + var hStatus = health.haproxy.status === 'running'; + providers.push(E('div', { 'class': 'health-item ' + (hStatus ? 'healthy' : 'unhealthy') }, [ + E('div', { 'class': 'health-svc' }, [ + E('span', { 'class': 'health-icon' }, hStatus ? 'โœ…' : 'โŒ'), + E('span', { 'class': 'health-name' }, 'โšก HAProxy') + ]), + E('div', { 'class': 'health-details' }, [ + E('span', { 'class': 'health-status' }, health.haproxy.status) + ]) + ])); + } + if (health.tor) { + var tStatus = health.tor.status === 'running'; + providers.push(E('div', { 'class': 'health-item ' + (tStatus ? 'healthy' : 'unhealthy') }, [ + E('div', { 'class': 'health-svc' }, [ + E('span', { 'class': 'health-icon' }, tStatus ? 'โœ…' : 'โŒ'), + E('span', { 'class': 'health-name' }, '๐Ÿง… Tor') + ]), + E('div', { 'class': 'health-details' }, [ + E('span', { 'class': 'health-status' }, health.tor.status) + ]) + ])); + } + if (health.firewall) { + var fwOk = health.firewall.status === 'ok'; + providers.push(E('div', { 'class': 'health-item ' + (fwOk ? 'healthy' : 'unknown') }, [ + E('div', { 'class': 'health-svc' }, [ + E('span', { 'class': 'health-icon' }, fwOk ? 'โœ…' : 'โš ๏ธ'), + E('span', { 'class': 'health-name' }, '๐Ÿ”ฅ Firewall') + ]), + E('div', { 'class': 'health-details' }, [ + E('span', { 'class': 'health-status' }, + (health.firewall.http_open ? 'HTTP โœ“ ' : 'HTTP โœ— ') + + (health.firewall.https_open ? 'HTTPS โœ“' : 'HTTPS โœ—')) + ]) + ])); + } + + if (providers.length > 0) { + healthItems.push(E('div', { 'class': 'health-section' }, [ + E('h4', {}, '๐Ÿ”ง Providers'), + E('div', { 'class': 'health-list' }, providers) + ])); + } + + // Services health section + var services = health.services || []; + if (services.length > 0) { + var svcItems = services.map(function(svc) { + var dnsOk = svc.dns_status === 'ok'; + var certOk = svc.cert_status === 'ok'; + var allOk = dnsOk && certOk; + var statusClass = allOk ? 'healthy' : (!dnsOk ? 'unhealthy' : 'unknown'); + + return E('div', { 'class': 'health-item ' + statusClass }, [ + E('div', { 'class': 'health-svc' }, [ + E('span', { 'class': 'health-icon' }, allOk ? 'โœ…' : (!dnsOk ? 'โŒ' : 'โš ๏ธ')), + E('span', { 'class': 'health-name' }, svc.domain) + ]), + E('div', { 'class': 'health-details' }, [ + E('span', { 'class': 'health-badge ' + (dnsOk ? 'ok' : 'err') }, 'DNS ' + (dnsOk ? 'โœ“' : 'โœ—')), + E('span', { 'class': 'health-badge ' + (certOk ? 'ok' : 'warn') }, + certOk ? '๐Ÿ”’ ' + svc.cert_days + 'd' : '๐Ÿ”“ No cert') + ]) + ]); + }); + + healthItems.push(E('div', { 'class': 'health-section' }, [ + E('h4', {}, '๐ŸŒ Published Services (' + services.length + ')'), + E('div', { 'class': 'health-list' }, svcItems) + ])); + } + + if (healthItems.length === 0) { + healthItems.push(E('div', { 'class': 'empty-health' }, [ + E('p', {}, 'No health data available') + ])); + } + + ui.showModal('๐Ÿฉบ Service Health Check', [ + E('div', { 'class': 'health-panel' }, healthItems), + E('div', { 'class': 'modal-footer' }, [ + E('button', { + 'class': 'cbi-button', + 'click': function() { self.checkAllHealth(); } + }, '๐Ÿ”„ Recheck'), + E('button', { 'class': 'cbi-button cbi-button-positive', 'click': ui.hideModal }, 'Close') + ]) + ]); + }).catch(function(err) { + ui.showModal('Health Check Error', [ + E('p', { 'class': 'error' }, 'โŒ ' + err.message), + E('div', { 'class': 'modal-footer' }, [ + E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, 'Close') + ]) + ]); + }); }, getStyles: function() { - return [ - '.services-header { text-align:center; margin-bottom:30px; }', - '.services-title { font-size:28px; font-weight:700; color:#fff; display:flex; align-items:center; justify-content:center; gap:12px; }', - '.services-icon { font-size:36px; }', - '.services-subtitle { color:#888; margin-top:8px; font-size:14px; }', - '.services-categories { display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin-bottom:25px; }', - '.category-btn { display:flex; align-items:center; gap:8px; padding:10px 16px; background:rgba(255,255,255,0.05); border:1px solid rgba(255,255,255,0.1); border-radius:10px; color:#a0a0b0; cursor:pointer; transition:all 0.2s; }', - '.category-btn:hover { background:rgba(99,102,241,0.1); border-color:rgba(99,102,241,0.3); }', - '.category-btn.active { background:linear-gradient(135deg,#667eea,#764ba2); color:#fff; border-color:transparent; }', - '.cat-count { background:rgba(0,0,0,0.2); padding:2px 8px; border-radius:10px; font-size:12px; }', - '.services-stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:15px; margin-bottom:25px; }', - '.stat-card { background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.08); border-radius:12px; padding:20px; text-align:center; }', - '.stat-card.accent { border-color:rgba(16,185,129,0.3); }', - '.stat-card.warning { border-color:rgba(245,158,11,0.3); }', - '.stat-card.info { border-color:rgba(59,130,246,0.3); }', - '.stat-value { font-size:32px; font-weight:700; color:#fff; }', - '.stat-label { color:#888; font-size:12px; margin-top:5px; }', - '.services-actions { display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin-bottom:25px; }', - '.action-btn { display:flex; align-items:center; gap:8px; padding:12px 20px; background:rgba(255,255,255,0.05); border:1px solid rgba(255,255,255,0.1); border-radius:10px; color:#e0e0e0; cursor:pointer; transition:all 0.2s; }', - '.action-btn:hover { background:rgba(99,102,241,0.15); border-color:rgba(99,102,241,0.4); }', - '.action-btn.primary { background:linear-gradient(135deg,#667eea,#764ba2); border:none; color:#fff; }', - '.services-panel { background:rgba(255,255,255,0.02); border:1px solid rgba(255,255,255,0.08); border-radius:16px; padding:20px; margin-bottom:20px; }', - '.panel-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; padding-bottom:15px; border-bottom:1px solid rgba(255,255,255,0.08); }', - '.panel-title { font-size:14px; font-weight:600; color:#fff; letter-spacing:1px; }', - '.panel-badge { background:rgba(99,102,241,0.2); color:#818cf8; padding:4px 12px; border-radius:20px; font-size:12px; }', - '.panel-badge.accent { background:rgba(16,185,129,0.2); color:#10b981; }', - '.services-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:15px; }', - '.service-card { background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.08); border-radius:12px; padding:16px; transition:all 0.2s; }', - '.service-card:hover { border-color:rgba(99,102,241,0.4); transform:translateY(-2px); }', - '.service-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:12px; }', - '.service-icon { font-size:24px; }', - '.service-status { padding:4px 10px; border-radius:20px; font-size:10px; font-weight:600; letter-spacing:0.5px; }', - '.service-name { font-size:16px; font-weight:600; color:#fff; margin-bottom:8px; }', - '.service-meta { display:flex; gap:12px; color:#888; font-size:12px; margin-bottom:12px; }', - '.service-actions { display:flex; gap:8px; }', - '.svc-btn { width:32px; height:32px; border-radius:8px; background:rgba(255,255,255,0.05); border:1px solid rgba(255,255,255,0.1); cursor:pointer; transition:all 0.2s; }', - '.svc-btn:hover { background:rgba(99,102,241,0.2); }', - '.svc-btn.success:hover { background:rgba(16,185,129,0.2); }', - '.svc-btn.danger:hover { background:rgba(239,68,68,0.2); }', - '.empty-state { text-align:center; padding:60px 20px; color:#888; }', - '.empty-icon { font-size:64px; margin-bottom:20px; opacity:0.5; }', - '.empty-text { font-size:18px; margin-bottom:10px; }', - '.empty-hint { font-size:14px; }', - '.p2p-info { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:15px; }', - '.p2p-feature { display:flex; gap:15px; padding:15px; background:rgba(255,255,255,0.02); border-radius:10px; }', - '.feature-icon { font-size:28px; }', - '.feature-title { font-weight:600; color:#fff; margin-bottom:4px; }', - '.feature-desc { font-size:12px; color:#888; }', - '.modal-input { width:100%; padding:10px; margin:8px 0 15px; background:rgba(99,102,241,0.1); border:1px solid rgba(99,102,241,0.3); border-radius:8px; color:#fff; }', - '.modal-form label { display:block; color:#888; font-size:12px; }', - '.modal-actions { display:flex; gap:10px; margin-top:20px; }', - '@keyframes spin { to { transform:rotate(360deg); } }', - '.spinning { animation:spin 1s linear infinite; }' - ].join('\n'); + return ` +.secubox-services { + background: linear-gradient(135deg, #0a0a1a 0%, #1a1a2e 100%); + min-height: 100vh; + padding: 24px; + margin: -20px; + font-family: system-ui, -apple-system, sans-serif; + color: #e0e0e0; +} + +.sr-header { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 20px; + margin-bottom: 30px; +} + +.sr-title { + display: flex; + align-items: center; + gap: 16px; +} + +.sr-logo { + font-size: 48px; + background: linear-gradient(135deg, rgba(99,102,241,0.2), rgba(139,92,246,0.2)); + padding: 16px; + border-radius: 16px; + border: 1px solid rgba(99,102,241,0.3); +} + +.sr-title-text h1 { + font-size: 28px; + font-weight: 700; + margin: 0; + background: linear-gradient(135deg, #fff, #a78bfa); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.sr-title-text p { + margin: 4px 0 0; + color: #888; + font-size: 14px; +} + +.sr-actions-top { + display: flex; + gap: 10px; +} + +.sr-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 12px 20px; + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 10px; + color: #e0e0e0; + cursor: pointer; + text-decoration: none; + font-size: 14px; + transition: all 0.2s; +} + +.sr-btn:hover { + background: rgba(99,102,241,0.15); + border-color: rgba(99,102,241,0.4); + color: #fff; +} + +.sr-btn.primary { + background: linear-gradient(135deg, #6366f1, #8b5cf6); + border: none; + color: #fff; +} + +.sr-btn.primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 20px rgba(99,102,241,0.4); +} + +.sr-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 16px; + margin-bottom: 24px; +} + +.stat-card { + display: flex; + align-items: center; + gap: 16px; + padding: 20px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 14px; + transition: all 0.2s; +} + +.stat-card:hover { + border-color: rgba(99,102,241,0.3); +} + +.stat-card.success { border-left: 3px solid #10b981; } +.stat-card.accent { border-left: 3px solid #6366f1; } +.stat-card.info { border-left: 3px solid #3b82f6; } +.stat-card.purple { border-left: 3px solid #a855f7; } + +.stat-icon { + font-size: 32px; + opacity: 0.8; +} + +.stat-value { + font-size: 28px; + font-weight: 700; + color: #fff; +} + +.stat-label { + font-size: 12px; + color: #888; +} + +.sr-categories { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-bottom: 20px; +} + +.cat-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 10px; + color: #a0a0b0; + cursor: pointer; + transition: all 0.2s; +} + +.cat-btn:hover { + background: rgba(99,102,241,0.1); + border-color: rgba(99,102,241,0.3); +} + +.cat-btn.active { + background: linear-gradient(135deg, #6366f1, #8b5cf6); + border-color: transparent; + color: #fff; +} + +.cat-count { + background: rgba(0,0,0,0.2); + padding: 2px 8px; + border-radius: 8px; + font-size: 12px; +} + +.sr-quick-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-bottom: 24px; +} + +.sr-panel { + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 16px; + padding: 24px; + margin-bottom: 24px; +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.panel-header h2 { + font-size: 18px; + font-weight: 600; + color: #fff; + margin: 0; +} + +.panel-badge { + background: rgba(99,102,241,0.15); + color: #a78bfa; + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; +} + +.sr-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 16px; +} + +.service-card { + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 14px; + padding: 20px; + transition: all 0.2s; +} + +.service-card:hover { + border-color: rgba(99,102,241,0.4); + transform: translateY(-2px); +} + +.service-card.published { + border-color: rgba(16,185,129,0.3); +} + +.svc-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 12px; +} + +.svc-icon { + font-size: 28px; +} + +.svc-badges { + display: flex; + gap: 6px; +} + +.badge { + padding: 3px 8px; + border-radius: 6px; + font-size: 10px; + font-weight: 600; +} + +.badge.published { + background: rgba(16,185,129,0.15); + color: #10b981; +} + +.badge.source { + background: rgba(99,102,241,0.15); + color: #a78bfa; +} + +.svc-name { + font-size: 16px; + font-weight: 600; + color: #fff; + margin: 0 0 8px; +} + +.svc-status { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + margin-bottom: 12px; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.svc-status.running { color: #10b981; } +.svc-status.running .status-dot { background: #10b981; box-shadow: 0 0 8px rgba(16,185,129,0.5); } +.svc-status.stopped { color: #ef4444; } +.svc-status.stopped .status-dot { background: #ef4444; } +.svc-status.disabled { color: #6b7280; } +.svc-status.disabled .status-dot { background: #6b7280; } + +.svc-urls { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 12px; +} + +.url-link { + padding: 4px 10px; + border-radius: 6px; + font-size: 11px; + text-decoration: none; + cursor: pointer; +} + +.url-link.local { background: rgba(59,130,246,0.15); color: #3b82f6; } +.url-link.clearnet { background: rgba(16,185,129,0.15); color: #10b981; } +.url-link.onion { background: rgba(168,85,247,0.15); color: #a855f7; } + +.svc-actions { + display: flex; + gap: 8px; +} + +.svc-btn { + width: 32px; + height: 32px; + border-radius: 8px; + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.1); + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + text-decoration: none; +} + +.svc-btn:hover { + background: rgba(99,102,241,0.2); +} + +.svc-btn.primary { + background: rgba(99,102,241,0.2); + border-color: rgba(99,102,241,0.4); +} + +.empty-state { + text-align: center; + padding: 60px 20px; + grid-column: 1 / -1; +} + +.empty-icon { + font-size: 64px; + margin-bottom: 16px; + opacity: 0.5; +} + +.empty-state h3 { + font-size: 20px; + margin: 0 0 8px; + color: #fff; +} + +.empty-state p { + color: #888; + margin: 0; +} + +.sr-providers h2 { + font-size: 18px; + font-weight: 600; + color: #fff; + margin: 0 0 16px; +} + +.provider-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; +} + +.provider-card { + display: flex; + align-items: center; + gap: 16px; + padding: 16px; + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 12px; +} + +.provider-card.active { + border-color: rgba(16,185,129,0.3); +} + +.provider-icon { + font-size: 28px; +} + +.provider-info { + flex: 1; +} + +.provider-name { + font-weight: 600; + color: #fff; +} + +.provider-desc { + font-size: 12px; + color: #888; +} + +.provider-stats { + text-align: right; +} + +.provider-count { + font-size: 24px; + font-weight: 700; + color: #fff; +} + +.provider-status { + font-size: 11px; +} + +.provider-status.running { color: #10b981; } +.provider-status.stopped { color: #ef4444; } +.provider-status.unknown { color: #6b7280; } + +.json-view { + background: #0a0a1a; + padding: 16px; + border-radius: 10px; + color: #a78bfa; + font-size: 12px; + overflow: auto; + max-height: 400px; +} + +.modal-footer { + margin-top: 16px; + text-align: right; +} + +@keyframes spin { to { transform: rotate(360deg); } } +.spinning { animation: spin 1s linear infinite; display: inline-block; } + +.sr-btn.accent { + background: linear-gradient(135deg, #06b6d4, #0891b2); + border: none; + color: #fff; +} + +/* Diagnostics Modal Styles */ +.diagnostics-loading { + text-align: center; + padding: 40px; +} + +.diagnostics-loading p:first-child { + font-size: 48px; + margin-bottom: 16px; +} + +.diagnostics-panel { + max-height: 60vh; + overflow-y: auto; +} + +.diag-section { + background: rgba(0,0,0,0.3); + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; +} + +.diag-section h3 { + font-size: 14px; + font-weight: 600; + margin: 0 0 12px; + color: #a78bfa; +} + +.diag-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 12px; +} + +.diag-item { + display: flex; + flex-direction: column; + gap: 4px; +} + +.diag-label { + font-size: 11px; + color: #888; + text-transform: uppercase; +} + +.diag-value { + font-size: 14px; + font-weight: 500; + color: #fff; +} + +.diag-value.mono { + font-family: monospace; + font-size: 12px; +} + +.diag-value.success { color: #10b981; } +.diag-value.error { color: #ef4444; } + +.diag-ports { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 10px; +} + +.diag-port { + display: flex; + flex-direction: column; + align-items: center; + padding: 12px; + border-radius: 10px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.08); +} + +.diag-port.open { + border-color: rgba(16,185,129,0.4); + background: rgba(16,185,129,0.1); +} + +.diag-port.closed { + border-color: rgba(239,68,68,0.3); + background: rgba(239,68,68,0.05); +} + +.port-num { + font-size: 18px; + font-weight: 700; + color: #fff; +} + +.port-name { + font-size: 11px; + color: #888; + margin: 4px 0; +} + +.port-status { + font-size: 11px; + font-weight: 500; +} + +.diag-port.open .port-status { color: #10b981; } +.diag-port.closed .port-status { color: #ef4444; } + +/* Health Panel Styles */ +.health-panel { + max-height: 60vh; + overflow-y: auto; +} + +.health-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 16px; + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 10px; + margin-bottom: 8px; +} + +.health-item.healthy { + border-left: 3px solid #10b981; +} + +.health-item.unhealthy { + border-left: 3px solid #ef4444; +} + +.health-item.unknown { + border-left: 3px solid #f59e0b; +} + +.health-svc { + display: flex; + align-items: center; + gap: 12px; +} + +.health-icon { + font-size: 18px; +} + +.health-name { + font-weight: 500; + color: #fff; +} + +.health-details { + display: flex; + align-items: center; + gap: 12px; +} + +.health-latency { + font-size: 12px; + color: #888; + background: rgba(255,255,255,0.05); + padding: 4px 8px; + border-radius: 6px; +} + +.health-status { + font-size: 12px; + text-transform: capitalize; +} + +.health-item.healthy .health-status { color: #10b981; } +.health-item.unhealthy .health-status { color: #ef4444; } +.health-item.unknown .health-status { color: #f59e0b; } + +.empty-health { + text-align: center; + padding: 40px; + color: #888; +} + +.health-section { + margin-bottom: 20px; +} + +.health-section h4 { + font-size: 13px; + font-weight: 600; + color: #a78bfa; + margin: 0 0 12px; + padding-bottom: 8px; + border-bottom: 1px solid rgba(255,255,255,0.1); +} + +.health-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.health-badge { + font-size: 11px; + padding: 3px 8px; + border-radius: 6px; + font-weight: 500; +} + +.health-badge.ok { + background: rgba(16,185,129,0.15); + color: #10b981; +} + +.health-badge.warn { + background: rgba(245,158,11,0.15); + color: #f59e0b; +} + +.health-badge.err { + background: rgba(239,68,68,0.15); + color: #ef4444; +} + +.error { + color: #ef4444; + text-align: center; + padding: 20px; +} + +@media (max-width: 768px) { + .sr-header { flex-direction: column; align-items: flex-start; } + .sr-stats { grid-template-columns: repeat(2, 1fr); } + .sr-grid { grid-template-columns: 1fr; } +} +`; }, handleSaveApply: null, diff --git a/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json b/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json index 86450ddd..6338ec66 100644 --- a/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json +++ b/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json @@ -1,116 +1,139 @@ { "luci-app-secubox": { "description": "SecuBox Dashboard", - "read": { - "ubus": { - "luci.secubox": [ - "status", - "getStatus", - "getVersion", - "modules", - "getModules", - "modules_by_category", - "module_info", - "getModuleInfo", - "check_module_enabled", - "health", - "getHealth", - "diagnostics", - "runDiagnostics", - "getLogs", - "get_system_health", - "get_alerts", - "get_dashboard_data", - "get_theme", - "first_run_status", - "list_apps", - "get_app_manifest", - "list_profiles", - "listProfiles", - "getProfile", - "validateProfile", - "listSnapshots", - "get_appstore_apps", - "get_appstore_app", - "get_public_ips", - "get_network_health", - "get_vital_services", - "get_full_health_report", - "get_services", - "get_proxy_mode", - "p2p_get_peers", - "p2p_get_settings", - "p2p_discover", - "p2p_get_catalog", - "p2p_get_peer_catalog", - "p2p_get_shared_services" - ], - "luci.gitea": [ - "get_status" - ], - "luci.secubox-p2p": [ - "get_gitea_config", - "list_gitea_repos", - "get_gitea_commits", - "list_local_backups" - ], + "read": { + "ubus": { + "luci.secubox": [ + "status", + "getStatus", + "getVersion", + "modules", + "getModules", + "modules_by_category", + "module_info", + "getModuleInfo", + "check_module_enabled", + "health", + "getHealth", + "diagnostics", + "runDiagnostics", + "getLogs", + "get_system_health", + "get_alerts", + "get_dashboard_data", + "get_theme", + "first_run_status", + "list_apps", + "get_app_manifest", + "list_profiles", + "listProfiles", + "getProfile", + "validateProfile", + "listSnapshots", + "get_appstore_apps", + "get_appstore_app", + "get_public_ips", + "get_network_health", + "get_vital_services", + "get_full_health_report", + "get_services", + "get_proxy_mode", + "p2p_get_peers", + "p2p_get_settings", + "p2p_discover", + "p2p_get_catalog", + "p2p_get_peer_catalog", + "p2p_get_shared_services" + ], + "luci.service-registry": [ + "list_services", + "get_service", + "list_categories", + "get_qr_data", + "get_certificate_status", + "check_service_health", + "check_all_health", + "get_network_info", + "get_landing_config" + ], + "luci.haproxy": [ + "status", + "list_vhosts", + "list_backends", + "list_certificates" + ], + "luci.gitea": [ + "get_status" + ], + "luci.secubox-p2p": [ + "get_gitea_config", + "list_gitea_repos", + "get_gitea_commits", + "list_local_backups" + ], "uci": [ "get", "state" ] }, "uci": [ - "secubox" + "secubox", + "service-registry", + "haproxy" ] }, - "write": { - "ubus": { - "luci.secubox": [ - "reload", - "start_module", - "stop_module", - "restart_module", - "enable_module", - "disable_module", - "installModule", - "removeModule", - "updateModule", - "applyProfile", - "createSnapshot", - "restoreSnapshot", - "quick_action", - "set_theme", - "dismiss_alert", - "clear_alerts", - "fix_permissions", - "apply_first_run", - "apply_app_wizard", - "apply_profile", - "rollback_profile", - "install_appstore_app", - "remove_appstore_app", - "set_proxy_mode", - "p2p_add_peer", - "p2p_remove_peer", - "p2p_set_settings", - "p2p_share_app", - "p2p_unshare_app", - "p2p_install_from_peer", - "p2p_sync_catalog", - "p2p_broadcast_command" - ], - "luci.gitea": [ - "generate_token", - "create_repo" - ], - "luci.secubox-p2p": [ - "set_gitea_config", - "create_gitea_repo", - "push_gitea_backup", - "pull_gitea_backup", - "create_local_backup", - "restore_local_backup" - ], + "write": { + "ubus": { + "luci.secubox": [ + "reload", + "start_module", + "stop_module", + "restart_module", + "enable_module", + "disable_module", + "installModule", + "removeModule", + "updateModule", + "applyProfile", + "createSnapshot", + "restoreSnapshot", + "quick_action", + "set_theme", + "dismiss_alert", + "clear_alerts", + "fix_permissions", + "apply_first_run", + "apply_app_wizard", + "apply_profile", + "rollback_profile", + "install_appstore_app", + "remove_appstore_app", + "set_proxy_mode", + "p2p_add_peer", + "p2p_remove_peer", + "p2p_set_settings", + "p2p_share_app", + "p2p_unshare_app", + "p2p_install_from_peer", + "p2p_sync_catalog", + "p2p_broadcast_command" + ], + "luci.service-registry": [ + "generate_landing_page", + "sync_providers" + ], + "luci.gitea": [ + "generate_token", + "create_repo" + ], + "luci.secubox-p2p": [ + "set_gitea_config", + "create_gitea_repo", + "push_gitea_backup", + "pull_gitea_backup", + "create_local_backup", + "restore_local_backup" + ], "uci": [ "set", "delete", diff --git a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js index a1577732..bac699fa 100644 --- a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js +++ b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js @@ -87,6 +87,13 @@ var callSaveLandingConfig = rpc.declare({ expect: {} }); +var callSetLandingTheme = rpc.declare({ + object: 'luci.service-registry', + method: 'set_landing_theme', + params: ['theme'], + expect: {} +}); + var callCheckServiceHealth = rpc.declare({ object: 'luci.service-registry', method: 'check_service_health', @@ -193,6 +200,11 @@ return baseclass.extend({ return callSaveLandingConfig(autoRegen ? true : false); }, + // Set landing page theme + setLandingTheme: function(theme) { + return callSetLandingTheme(theme || 'mirrorbox'); + }, + // Get dashboard data (services + provider status) getDashboardData: function() { return Promise.all([ diff --git a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/landing.js b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/landing.js index 3aab1f0e..1cb6d5fc 100644 --- a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/landing.js +++ b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/landing.js @@ -197,6 +197,24 @@ return view.extend({ E('div', { 'class': 'settings-section' }, [ E('h2', {}, ['โš™๏ธ', ' Settings']), E('div', { 'class': 'settings-grid' }, [ + E('div', { 'class': 'setting-item' }, [ + E('div', { 'class': 'setting-label' }, [ + E('span', {}, '๐ŸŽจ'), + E('span', {}, 'Theme') + ]), + E('div', { 'class': 'theme-selector' }, [ + E('select', { + 'class': 'theme-select', + 'change': function(e) { self.handleThemeChange(e.target.value); } + }, [ + E('option', { 'value': 'mirrorbox', 'selected': (config.theme || 'mirrorbox') === 'mirrorbox' }, 'MirrorBox (Glassmorphism)'), + E('option', { 'value': 'cyberpunk', 'selected': config.theme === 'cyberpunk' }, 'Cyberpunk (Neon)'), + E('option', { 'value': 'minimal', 'selected': config.theme === 'minimal' }, 'Minimal Dark'), + E('option', { 'value': 'terminal', 'selected': config.theme === 'terminal' }, 'Terminal (Matrix)'), + E('option', { 'value': 'light', 'selected': config.theme === 'light' }, 'Clean Light') + ]) + ]) + ]), E('div', { 'class': 'setting-item' }, [ E('div', { 'class': 'setting-label' }, [ E('span', {}, '๐Ÿ”„'), @@ -269,6 +287,31 @@ return view.extend({ ui.addNotification(null, E('p', (enabled ? 'โœ…' : 'โŒ') + ' Auto-regenerate ' + (enabled ? 'enabled' : 'disabled')), 'info'); }, + handleThemeChange: function(theme) { + var self = this; + ui.showModal(_('Applying Theme'), [ + E('p', { 'class': 'spinning' }, _('๐ŸŽจ Applying theme: ' + theme + '...')) + ]); + + return api.setLandingTheme(theme).then(function(result) { + if (result.success) { + return api.generateLandingPage(); + } + throw new Error(result.error || 'Failed to set theme'); + }).then(function(result) { + ui.hideModal(); + if (result.success) { + ui.addNotification(null, E('p', 'โœ… ' + _('Theme applied: ') + theme), 'info'); + window.location.reload(); + } else { + ui.addNotification(null, E('p', 'โŒ ' + _('Failed to regenerate page')), 'error'); + } + }).catch(function(err) { + ui.hideModal(); + ui.addNotification(null, E('p', 'โŒ ' + _('Error: ') + err.message), 'error'); + }); + }, + getStyles: function() { return [ '.service-landing-page { font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; background: linear-gradient(135deg, #0a0a1a 0%, #1a1a2e 100%); min-height: 100vh; padding: 20px; margin: -20px; }', @@ -344,7 +387,14 @@ return view.extend({ '.toggle-slider { position: absolute; inset: 0; background: rgba(255,255,255,0.1); border-radius: 13px; cursor: pointer; transition: 0.3s; }', '.toggle-slider::before { content: ""; position: absolute; width: 20px; height: 20px; left: 3px; bottom: 3px; background: #fff; border-radius: 50%; transition: 0.3s; }', '.toggle-switch input:checked + .toggle-slider { background: #2ecc71; }', - '.toggle-switch input:checked + .toggle-slider::before { transform: translateX(24px); }' + '.toggle-switch input:checked + .toggle-slider::before { transform: translateX(24px); }', + + // Theme Selector + '.theme-selector { display: flex; align-items: center; gap: 10px; }', + '.theme-select { padding: 10px 15px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; color: #fff; font-size: 14px; cursor: pointer; min-width: 200px; }', + '.theme-select:hover { border-color: rgba(52,152,219,0.5); }', + '.theme-select:focus { outline: none; border-color: #3498db; }', + '.theme-select option { background: #1a1a2e; color: #fff; padding: 10px; }' ].join('\n'); } }); diff --git a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js index 1ed7c65c..d3d38407 100644 --- a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js +++ b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js @@ -26,7 +26,7 @@ function generateQRCodeImg(data, size) { } return view.extend({ - title: _('Service Registry'), + title: _('Web Services Registry'), pollInterval: 30, healthData: null, @@ -112,8 +112,9 @@ return view.extend({ html += '
'; html += 'Port 80 (HTTP)'; var http = extPorts.http || {}; - if (http.status === 'open') { - html += 'โœ… Open from Internet'; + if (http.status === 'open' || http.status === 'firewall_open') { + html += 'โœ… Open'; + if (http.hint) html += '' + http.hint + ''; } else if (http.status === 'blocked') { html += '๐Ÿšซ Blocked'; html += '' + (http.hint || 'Check router') + ''; @@ -126,8 +127,9 @@ return view.extend({ html += '
'; html += 'Port 443 (HTTPS)'; var https = extPorts.https || {}; - if (https.status === 'open') { - html += 'โœ… Open from Internet'; + if (https.status === 'open' || https.status === 'firewall_open') { + html += 'โœ… Open'; + if (https.hint) html += '' + https.hint + ''; } else if (https.status === 'blocked') { html += '๐Ÿšซ Blocked'; html += '' + (https.hint || 'Check router') + ''; @@ -397,7 +399,7 @@ return view.extend({ return E('div', { 'class': 'sr-header' }, [ E('div', { 'class': 'sr-title' }, [ - E('h2', {}, '๐Ÿ—‚๏ธ Service Registry'), + E('h2', {}, '๐Ÿ—‚๏ธ Web Services Registry'), E('span', { 'class': 'sr-subtitle' }, published + ' published ยท ' + running + ' running ยท ' + haproxyCount + ' domains ยท ' + torCount + ' onion') diff --git a/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry b/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry index 3e0ef564..bcf49234 100755 --- a/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry +++ b/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry @@ -1484,12 +1484,14 @@ method_get_landing_config() { json_init config_load "$UCI_CONFIG" - local landing_path auto_regen + local landing_path auto_regen theme config_get landing_path main landing_path "/www/secubox-services.html" config_get auto_regen main landing_auto_regen "1" + config_get theme main landing_theme "mirrorbox" json_add_string "path" "$landing_path" json_add_boolean "auto_regen" "$auto_regen" + json_add_string "theme" "$theme" # Check if file exists if [ -f "$landing_path" ]; then @@ -1522,6 +1524,33 @@ method_save_landing_config() { json_dump } +# Set landing page theme +method_set_landing_theme() { + local theme + + read -r input + json_load "$input" + json_get_var theme theme "mirrorbox" + + json_init + + # Validate theme + case "$theme" in + mirrorbox|cyberpunk|minimal|terminal|light) + uci set "$UCI_CONFIG.main.landing_theme=$theme" + uci commit "$UCI_CONFIG" + json_add_boolean "success" 1 + json_add_string "theme" "$theme" + ;; + *) + json_add_boolean "success" 0 + json_add_string "error" "Invalid theme: $theme" + ;; + esac + + json_dump +} + # Main RPC interface case "$1" in list) @@ -1542,7 +1571,8 @@ case "$1" in "check_all_health": {}, "get_network_info": {}, "get_landing_config": {}, - "save_landing_config": { "auto_regen": "boolean" } + "save_landing_config": { "auto_regen": "boolean" }, + "set_landing_theme": { "theme": "string" } } EOF ;; @@ -1564,6 +1594,7 @@ EOF get_network_info) method_get_network_info ;; get_landing_config) method_get_landing_config ;; save_landing_config) method_save_landing_config ;; + set_landing_theme) method_set_landing_theme ;; *) json_init json_add_boolean "error" 1 diff --git a/package/secubox/luci-app-service-registry/root/usr/sbin/secubox-landing-gen b/package/secubox/luci-app-service-registry/root/usr/sbin/secubox-landing-gen index c5ce4843..2d0ccdcc 100644 --- a/package/secubox/luci-app-service-registry/root/usr/sbin/secubox-landing-gen +++ b/package/secubox/luci-app-service-registry/root/usr/sbin/secubox-landing-gen @@ -1,16 +1,18 @@ #!/bin/sh # SPDX-License-Identifier: MIT -# SecuBox Landing Page Generator +# SecuBox Landing Page Generator - Multi-Theme Support # Copyright (C) 2025 CyberMind.fr . /lib/functions.sh UCI_CONFIG="service-registry" OUTPUT_PATH="/www/secubox-services.html" +THEME="mirrorbox" -# Get output path from config +# Get config values config_load "$UCI_CONFIG" config_get OUTPUT_PATH main landing_path "$OUTPUT_PATH" +config_get THEME main landing_theme "mirrorbox" # Get services JSON SERVICES_JSON=$(ubus call luci.service-registry list_services 2>/dev/null) @@ -22,7 +24,110 @@ fi # Get hostname HOSTNAME=$(uci -q get system.@system[0].hostname || echo "SecuBox") -# Generate HTML +# Theme CSS variables +get_theme_css() { + case "$1" in + mirrorbox) + cat <<'CSS' + --primary: #00d4ff; + --primary-rgb: 0, 212, 255; + --secondary: #7c3aed; + --secondary-rgb: 124, 58, 237; + --accent: #f472b6; + --accent-rgb: 244, 114, 182; + --bg-start: #0a0a1a; + --bg-mid: #0f0f2a; + --bg-end: #1a0a2e; + --glass-bg: rgba(255, 255, 255, 0.03); + --glass-border: rgba(255, 255, 255, 0.08); + --glass-hover: rgba(255, 255, 255, 0.06); + --text: #f0f0f5; + --text-dim: #8b8b9e; + --text-muted: #5a5a6e; +CSS + ;; + cyberpunk) + cat <<'CSS' + --primary: #ff00ff; + --primary-rgb: 255, 0, 255; + --secondary: #00ffff; + --secondary-rgb: 0, 255, 255; + --accent: #ffff00; + --accent-rgb: 255, 255, 0; + --bg-start: #0d0221; + --bg-mid: #150734; + --bg-end: #0a0612; + --glass-bg: rgba(255, 0, 255, 0.05); + --glass-border: rgba(255, 0, 255, 0.2); + --glass-hover: rgba(0, 255, 255, 0.1); + --text: #ffffff; + --text-dim: #b0b0ff; + --text-muted: #7070aa; +CSS + ;; + minimal) + cat <<'CSS' + --primary: #6366f1; + --primary-rgb: 99, 102, 241; + --secondary: #8b5cf6; + --secondary-rgb: 139, 92, 246; + --accent: #ec4899; + --accent-rgb: 236, 72, 153; + --bg-start: #111827; + --bg-mid: #1f2937; + --bg-end: #111827; + --glass-bg: rgba(255, 255, 255, 0.02); + --glass-border: rgba(255, 255, 255, 0.05); + --glass-hover: rgba(255, 255, 255, 0.04); + --text: #f9fafb; + --text-dim: #9ca3af; + --text-muted: #6b7280; +CSS + ;; + terminal) + cat <<'CSS' + --primary: #00ff00; + --primary-rgb: 0, 255, 0; + --secondary: #00cc00; + --secondary-rgb: 0, 204, 0; + --accent: #00ff00; + --accent-rgb: 0, 255, 0; + --bg-start: #000000; + --bg-mid: #001100; + --bg-end: #000000; + --glass-bg: rgba(0, 255, 0, 0.03); + --glass-border: rgba(0, 255, 0, 0.15); + --glass-hover: rgba(0, 255, 0, 0.08); + --text: #00ff00; + --text-dim: #00cc00; + --text-muted: #008800; +CSS + ;; + light) + cat <<'CSS' + --primary: #3b82f6; + --primary-rgb: 59, 130, 246; + --secondary: #8b5cf6; + --secondary-rgb: 139, 92, 246; + --accent: #ec4899; + --accent-rgb: 236, 72, 153; + --bg-start: #ffffff; + --bg-mid: #f8fafc; + --bg-end: #f1f5f9; + --glass-bg: rgba(0, 0, 0, 0.02); + --glass-border: rgba(0, 0, 0, 0.08); + --glass-hover: rgba(0, 0, 0, 0.04); + --text: #1e293b; + --text-dim: #64748b; + --text-muted: #94a3b8; +CSS + ;; + esac +} + +THEME_CSS=$(get_theme_css "$THEME") + +# Generate HTML (quoted heredoc to preserve JS variables) cat > "$OUTPUT_PATH" <<'HTMLHEAD' @@ -30,272 +135,688 @@ cat > "$OUTPUT_PATH" <<'HTMLHEAD' SecuBox Services + + + +
+
+
+

SecuBox Services

-

Published endpoints and access links

-
+

Your secure gateway to published endpoints

+
+
+