From 55ef43aa5458760e0cadfed67a6a9ef4c24212d3 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Fri, 30 Jan 2026 13:40:01 +0100 Subject: [PATCH] feat(p2p): Add MirrorBox overview and profiles pages Overview Page (SOC Admin Landing): - Architecture diagram with layer visualization - Modular component cards with status indicators - Network topology with animated orbital nodes - System status grid with health indicators - Development roadmap timeline Profiles Page (Dynamic Cloning): - Component readiness tracking with progress bars - Quick presets for common configurations - Package feeds management with toggles - Saved profiles list with load/delete - Clone & deploy actions (export, import, sync, push) Menu Structure: - Renamed P2P Hub to MirrorBox - Added Overview as landing page - Added Profiles for cloning management Co-Authored-By: Claude Opus 4.5 --- .../resources/view/secubox-p2p/overview.js | 526 ++++++++++++ .../resources/view/secubox-p2p/profiles.js | 746 ++++++++++++++++++ .../luci/menu.d/luci-app-secubox-p2p.json | 38 +- 3 files changed, 1299 insertions(+), 11 deletions(-) create mode 100644 package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/overview.js create mode 100644 package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/profiles.js diff --git a/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/overview.js b/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/overview.js new file mode 100644 index 00000000..bfcec37c --- /dev/null +++ b/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/overview.js @@ -0,0 +1,526 @@ +'use strict'; +'require view'; +'require dom'; +'require poll'; +'require ui'; +'require rpc'; +'require secubox-p2p/api as P2PAPI'; + +return view.extend({ + // System state + health: {}, + peers: [], + services: [], + modules: [], + + load: function() { + var self = this; + return Promise.all([ + P2PAPI.healthCheck().catch(function() { return {}; }), + P2PAPI.getPeers().catch(function() { return { peers: [] }; }), + P2PAPI.getServices().catch(function() { return { services: [] }; }) + ]).then(function(results) { + self.health = results[0] || {}; + self.peers = results[1].peers || []; + self.services = results[2].services || []; + }); + }, + + render: function() { + var self = this; + + // Architecture modules definition + this.modules = [ + { + id: 'mesh-core', + name: 'Mesh Core', + icon: 'πŸ•ΈοΈ', + description: 'P2P Network Foundation', + status: 'active', + components: ['WireGuard VPN', 'mDNS Discovery', 'Peer Sync'], + color: '#3498db' + }, + { + id: 'security-layer', + name: 'Security Layer', + icon: 'πŸ›‘οΈ', + description: 'Threat Detection & Response', + status: 'active', + components: ['CrowdSec IDS', 'Firewall', 'Rate Limiting'], + color: '#e74c3c' + }, + { + id: 'dns-federation', + name: 'DNS Federation', + icon: '🌐', + description: 'Distributed Name Resolution', + status: 'active', + components: ['Local DNS', 'Mesh Sync', 'Split Horizon'], + color: '#2ecc71' + }, + { + id: 'load-balancer', + name: 'Load Balancer', + icon: 'βš–οΈ', + description: 'Traffic Distribution', + status: 'active', + components: ['HAProxy', 'Health Checks', 'Failover'], + color: '#9b59b6' + }, + { + id: 'service-mesh', + name: 'Service Mesh', + icon: 'πŸ“‘', + description: 'Microservices Orchestration', + status: 'active', + components: ['Service Discovery', 'Registry', 'Routing'], + color: '#f39c12' + }, + { + id: 'identity-access', + name: 'Identity & Access', + icon: 'πŸ”', + description: 'Authentication Gateway', + status: 'planned', + components: ['SSO', 'RBAC', 'API Keys'], + color: '#1abc9c' + }, + { + id: 'observability', + name: 'Observability', + icon: 'πŸ“Š', + description: 'Metrics & Monitoring', + status: 'active', + components: ['Logs', 'Metrics', 'Alerts'], + color: '#e67e22' + }, + { + id: 'data-plane', + name: 'Data Plane', + icon: 'πŸ’Ύ', + description: 'Distributed Storage', + status: 'planned', + components: ['Config Sync', 'Gitea VCS', 'Backup'], + color: '#34495e' + }, + { + id: 'edge-compute', + name: 'Edge Compute', + icon: '⚑', + description: 'Local Processing', + status: 'planned', + components: ['Containers', 'Functions', 'AI/ML'], + color: '#8e44ad' + } + ]; + + var container = E('div', { 'class': 'mirrorbox-overview' }, [ + E('style', {}, this.getStyles()), + + // Hero Banner + this.renderHeroBanner(), + + // Architecture Diagram + this.renderArchitectureDiagram(), + + // Module Grid + this.renderModuleGrid(), + + // Network Topology + this.renderNetworkTopology(), + + // Quick Stats + this.renderQuickStats(), + + // Future Roadmap + this.renderRoadmap() + ]); + + return container; + }, + + renderHeroBanner: function() { + return E('div', { 'class': 'hero-banner' }, [ + E('div', { 'class': 'hero-bg' }), + E('div', { 'class': 'hero-content' }, [ + E('div', { 'class': 'hero-icon' }, '🌐'), + E('h1', { 'class': 'hero-title' }, 'MirrorBox Network'), + E('p', { 'class': 'hero-subtitle' }, 'Decentralized Infrastructure for the Future Internet'), + E('div', { 'class': 'hero-badges' }, [ + E('span', { 'class': 'badge blue' }, 'πŸ•ΈοΈ P2P Mesh'), + E('span', { 'class': 'badge green' }, 'πŸ›‘οΈ Zero Trust'), + E('span', { 'class': 'badge purple' }, '⚑ Edge-First'), + E('span', { 'class': 'badge orange' }, 'πŸ”— Blockchain-Ready') + ]), + E('p', { 'class': 'hero-desc' }, + 'A modular, self-healing network architecture that transforms edge devices into a unified, ' + + 'secure mesh infrastructure. Built for SOC admins who need visibility, control, and resilience.' + ) + ]) + ]); + }, + + renderArchitectureDiagram: function() { + return E('div', { 'class': 'architecture-section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', { 'class': 'title-icon' }, 'πŸ—οΈ'), + 'System Architecture' + ]), + E('div', { 'class': 'arch-diagram' }, [ + // Layer 1: Edge + E('div', { 'class': 'arch-layer edge' }, [ + E('div', { 'class': 'layer-label' }, 'Edge Layer'), + E('div', { 'class': 'layer-nodes' }, [ + E('div', { 'class': 'arch-node' }, ['πŸ“±', E('span', {}, 'IoT')]), + E('div', { 'class': 'arch-node' }, ['πŸ’»', E('span', {}, 'Clients')]), + E('div', { 'class': 'arch-node' }, ['πŸ“‘', E('span', {}, 'Sensors')]) + ]) + ]), + E('div', { 'class': 'arch-connector' }, '⬇️'), + + // Layer 2: MirrorBox Mesh + E('div', { 'class': 'arch-layer mesh' }, [ + E('div', { 'class': 'layer-label' }, 'MirrorBox Mesh Layer'), + E('div', { 'class': 'layer-nodes' }, [ + E('div', { 'class': 'arch-node primary' }, ['πŸ‘‘', E('span', {}, 'P0 Genesis')]), + E('div', { 'class': 'arch-node' }, ['πŸͺ†', E('span', {}, 'Gigogne L1')]), + E('div', { 'class': 'arch-node' }, ['πŸͺ†', E('span', {}, 'Gigogne L2')]), + E('div', { 'class': 'arch-node' }, ['πŸ”—', E('span', {}, 'Peer N')]) + ]), + E('div', { 'class': 'mesh-links' }, [ + E('span', {}, 'πŸ”’ WireGuard'), + E('span', {}, '🌐 DNS Fed'), + E('span', {}, 'βš–οΈ HAProxy') + ]) + ]), + E('div', { 'class': 'arch-connector' }, '⬇️'), + + // Layer 3: Services + E('div', { 'class': 'arch-layer services' }, [ + E('div', { 'class': 'layer-label' }, 'Service Layer'), + E('div', { 'class': 'layer-nodes' }, [ + E('div', { 'class': 'arch-node' }, ['πŸ›‘οΈ', E('span', {}, 'Security')]), + E('div', { 'class': 'arch-node' }, ['πŸ“Š', E('span', {}, 'Monitor')]), + E('div', { 'class': 'arch-node' }, ['πŸ’Ύ', E('span', {}, 'Storage')]), + E('div', { 'class': 'arch-node' }, ['πŸ€–', E('span', {}, 'AI/ML')]) + ]) + ]), + E('div', { 'class': 'arch-connector' }, '⬇️'), + + // Layer 4: Internet + E('div', { 'class': 'arch-layer internet' }, [ + E('div', { 'class': 'layer-label' }, 'Internet / Cloud'), + E('div', { 'class': 'layer-nodes' }, [ + E('div', { 'class': 'arch-node' }, ['☁️', E('span', {}, 'Cloud')]), + E('div', { 'class': 'arch-node' }, ['🌍', E('span', {}, 'CDN')]), + E('div', { 'class': 'arch-node' }, ['πŸ”Œ', E('span', {}, 'APIs')]) + ]) + ]) + ]) + ]); + }, + + renderModuleGrid: function() { + var self = this; + + return E('div', { 'class': 'modules-section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', { 'class': 'title-icon' }, '🧩'), + 'Modular Components' + ]), + E('div', { 'class': 'modules-grid' }, + this.modules.map(function(mod) { + return E('div', { + 'class': 'module-card ' + mod.status, + 'style': '--module-color: ' + mod.color + }, [ + E('div', { 'class': 'module-header' }, [ + E('span', { 'class': 'module-icon' }, mod.icon), + E('span', { 'class': 'module-status ' + mod.status }, + mod.status === 'active' ? '●' : 'β—‹') + ]), + E('h3', { 'class': 'module-name' }, mod.name), + E('p', { 'class': 'module-desc' }, mod.description), + E('div', { 'class': 'module-components' }, + mod.components.map(function(comp) { + return E('span', { 'class': 'component-tag' }, comp); + }) + ) + ]); + }) + ) + ]); + }, + + renderNetworkTopology: function() { + var peersCount = this.peers.length || 0; + var onlineCount = this.peers.filter(function(p) { return p.status === 'online'; }).length; + + return E('div', { 'class': 'topology-section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', { 'class': 'title-icon' }, 'πŸ—ΊοΈ'), + 'Network Topology' + ]), + E('div', { 'class': 'topology-container' }, [ + E('div', { 'class': 'topo-visual' }, [ + // Central hub + E('div', { 'class': 'topo-hub' }, [ + E('div', { 'class': 'hub-core' }, 'πŸ‘‘'), + E('div', { 'class': 'hub-label' }, 'Genesis Node'), + E('div', { 'class': 'hub-rings' }, [ + E('div', { 'class': 'ring ring-1' }), + E('div', { 'class': 'ring ring-2' }), + E('div', { 'class': 'ring ring-3' }) + ]) + ]), + // Orbital nodes + E('div', { 'class': 'topo-orbits' }, [ + E('div', { 'class': 'orbit-node n1' }, 'πŸͺ†'), + E('div', { 'class': 'orbit-node n2' }, 'πŸͺ†'), + E('div', { 'class': 'orbit-node n3' }, 'πŸ”—'), + E('div', { 'class': 'orbit-node n4' }, 'πŸ”—'), + E('div', { 'class': 'orbit-node n5' }, 'πŸ“‘'), + E('div', { 'class': 'orbit-node n6' }, 'πŸ“‘') + ]) + ]), + E('div', { 'class': 'topo-stats' }, [ + E('div', { 'class': 'topo-stat' }, [ + E('div', { 'class': 'stat-value' }, String(peersCount)), + E('div', { 'class': 'stat-label' }, 'Total Nodes') + ]), + E('div', { 'class': 'topo-stat' }, [ + E('div', { 'class': 'stat-value green' }, String(onlineCount)), + E('div', { 'class': 'stat-label' }, 'Online') + ]), + E('div', { 'class': 'topo-stat' }, [ + E('div', { 'class': 'stat-value blue' }, '3'), + E('div', { 'class': 'stat-label' }, 'Gigogne Depth') + ]), + E('div', { 'class': 'topo-stat' }, [ + E('div', { 'class': 'stat-value purple' }, 'Full'), + E('div', { 'class': 'stat-label' }, 'Mesh Mode') + ]) + ]) + ]) + ]); + }, + + renderQuickStats: function() { + var servicesCount = this.services.length || 0; + var healthStatus = this.health.status || 'unknown'; + + return E('div', { 'class': 'stats-section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', { 'class': 'title-icon' }, 'πŸ“ˆ'), + 'System Status' + ]), + E('div', { 'class': 'stats-grid' }, [ + E('div', { 'class': 'stat-card health ' + healthStatus }, [ + E('div', { 'class': 'stat-icon' }, healthStatus === 'healthy' ? 'πŸ’š' : 'πŸ’›'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, healthStatus === 'healthy' ? 'Healthy' : 'Unknown'), + E('div', { 'class': 'stat-label' }, 'System Health') + ]) + ]), + E('div', { 'class': 'stat-card' }, [ + E('div', { 'class': 'stat-icon' }, 'πŸ”’'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, this.health.wireguard_mesh ? 'Active' : 'Off'), + E('div', { 'class': 'stat-label' }, 'WireGuard VPN') + ]) + ]), + E('div', { 'class': 'stat-card' }, [ + E('div', { 'class': 'stat-icon' }, '🌐'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, this.health.dns_federation ? 'Active' : 'Off'), + E('div', { 'class': 'stat-label' }, 'DNS Federation') + ]) + ]), + E('div', { 'class': 'stat-card' }, [ + E('div', { 'class': 'stat-icon' }, 'βš–οΈ'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, this.health.haproxy ? 'Active' : 'Off'), + E('div', { 'class': 'stat-label' }, 'Load Balancer') + ]) + ]), + E('div', { 'class': 'stat-card' }, [ + E('div', { 'class': 'stat-icon' }, 'πŸ“‘'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, String(servicesCount)), + E('div', { 'class': 'stat-label' }, 'Active Services') + ]) + ]), + E('div', { 'class': 'stat-card' }, [ + E('div', { 'class': 'stat-icon' }, 'πŸ›‘οΈ'), + E('div', { 'class': 'stat-info' }, [ + E('div', { 'class': 'stat-value' }, String(this.health.services_running || 0)), + E('div', { 'class': 'stat-label' }, 'Security Services') + ]) + ]) + ]) + ]); + }, + + renderRoadmap: function() { + var roadmapItems = [ + { phase: 'Phase 1', status: 'done', title: 'Core Mesh', items: ['P2P Discovery', 'WireGuard VPN', 'DNS Federation'] }, + { phase: 'Phase 2', status: 'current', title: 'Service Mesh', items: ['HAProxy LB', 'Service Registry', 'Health Checks'] }, + { phase: 'Phase 3', status: 'planned', title: 'Data Plane', items: ['Gitea VCS', 'Config Sync', 'Backup/Restore'] }, + { phase: 'Phase 4', status: 'future', title: 'Edge AI', items: ['Local LLM', 'ML Inference', 'Smart Routing'] }, + { phase: 'Phase 5', status: 'future', title: 'Web3 Ready', items: ['DID Identity', 'Token Economy', 'DAO Governance'] } + ]; + + return E('div', { 'class': 'roadmap-section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', { 'class': 'title-icon' }, 'πŸš€'), + 'Development Roadmap' + ]), + E('div', { 'class': 'roadmap-timeline' }, + roadmapItems.map(function(item) { + return E('div', { 'class': 'roadmap-item ' + item.status }, [ + E('div', { 'class': 'roadmap-marker' }, + item.status === 'done' ? 'βœ“' : + item.status === 'current' ? '●' : 'β—‹' + ), + E('div', { 'class': 'roadmap-content' }, [ + E('div', { 'class': 'roadmap-phase' }, item.phase), + E('h4', { 'class': 'roadmap-title' }, item.title), + E('ul', { 'class': 'roadmap-items' }, + item.items.map(function(i) { + return E('li', {}, i); + }) + ) + ]) + ]); + }) + ) + ]); + }, + + getStyles: function() { + return [ + // Base + '.mirrorbox-overview { font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; background: linear-gradient(135deg, #0a0a1a 0%, #1a1a2e 50%, #0f0f23 100%); min-height: 100vh; padding: 0; }', + + // Hero Banner + '.hero-banner { position: relative; padding: 60px 40px; text-align: center; overflow: hidden; }', + '.hero-bg { position: absolute; inset: 0; background: radial-gradient(ellipse at center, rgba(52,152,219,0.15) 0%, transparent 70%); }', + '.hero-content { position: relative; z-index: 1; max-width: 800px; margin: 0 auto; }', + '.hero-icon { font-size: 64px; margin-bottom: 20px; animation: pulse 2s infinite; }', + '@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } }', + '.hero-title { font-size: 42px; font-weight: 700; margin: 0 0 10px; background: linear-gradient(135deg, #3498db, #9b59b6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }', + '.hero-subtitle { font-size: 18px; color: #888; margin: 0 0 20px; }', + '.hero-badges { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; margin-bottom: 20px; }', + '.hero-badges .badge { padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 500; }', + '.badge.blue { background: rgba(52,152,219,0.2); border: 1px solid rgba(52,152,219,0.4); color: #3498db; }', + '.badge.green { background: rgba(46,204,113,0.2); border: 1px solid rgba(46,204,113,0.4); color: #2ecc71; }', + '.badge.purple { background: rgba(155,89,182,0.2); border: 1px solid rgba(155,89,182,0.4); color: #9b59b6; }', + '.badge.orange { background: rgba(230,126,34,0.2); border: 1px solid rgba(230,126,34,0.4); color: #e67e22; }', + '.hero-desc { font-size: 15px; color: #999; line-height: 1.6; max-width: 600px; margin: 0 auto; }', + + // Section Titles + '.section-title { display: flex; align-items: center; gap: 12px; font-size: 24px; font-weight: 600; margin: 0 0 25px; padding: 0 40px; color: #fff; }', + '.title-icon { font-size: 28px; }', + + // Architecture Diagram + '.architecture-section { padding: 40px 0; background: rgba(0,0,0,0.2); }', + '.arch-diagram { display: flex; flex-direction: column; align-items: center; gap: 15px; padding: 30px; }', + '.arch-layer { display: flex; flex-direction: column; align-items: center; padding: 20px 40px; border-radius: 16px; min-width: 500px; }', + '.arch-layer.edge { background: linear-gradient(135deg, rgba(52,73,94,0.4), rgba(52,73,94,0.2)); border: 1px solid rgba(52,73,94,0.5); }', + '.arch-layer.mesh { background: linear-gradient(135deg, rgba(52,152,219,0.3), rgba(155,89,182,0.2)); border: 2px solid rgba(52,152,219,0.5); }', + '.arch-layer.services { background: linear-gradient(135deg, rgba(46,204,113,0.3), rgba(241,196,15,0.2)); border: 1px solid rgba(46,204,113,0.5); }', + '.arch-layer.internet { background: linear-gradient(135deg, rgba(231,76,60,0.2), rgba(230,126,34,0.2)); border: 1px solid rgba(231,76,60,0.4); }', + '.layer-label { font-size: 12px; text-transform: uppercase; letter-spacing: 2px; color: #888; margin-bottom: 15px; }', + '.layer-nodes { display: flex; gap: 20px; justify-content: center; }', + '.arch-node { display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 12px 20px; background: rgba(0,0,0,0.3); border-radius: 10px; font-size: 24px; }', + '.arch-node span { font-size: 11px; color: #aaa; }', + '.arch-node.primary { background: linear-gradient(135deg, rgba(241,196,15,0.3), rgba(230,126,34,0.2)); border: 1px solid rgba(241,196,15,0.5); }', + '.mesh-links { display: flex; gap: 20px; margin-top: 15px; }', + '.mesh-links span { font-size: 12px; color: #888; padding: 5px 12px; background: rgba(0,0,0,0.3); border-radius: 15px; }', + '.arch-connector { font-size: 20px; opacity: 0.5; }', + + // Modules Grid + '.modules-section { padding: 40px 0; }', + '.modules-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; padding: 0 40px; }', + '.module-card { background: rgba(30,30,50,0.6); border: 1px solid rgba(255,255,255,0.1); border-radius: 16px; padding: 24px; transition: all 0.3s; border-left: 4px solid var(--module-color); }', + '.module-card:hover { transform: translateY(-5px); box-shadow: 0 10px 30px rgba(0,0,0,0.3); border-color: var(--module-color); }', + '.module-card.planned { opacity: 0.6; }', + '.module-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }', + '.module-icon { font-size: 32px; }', + '.module-status { font-size: 12px; }', + '.module-status.active { color: #2ecc71; }', + '.module-status.planned { color: #f39c12; }', + '.module-name { font-size: 18px; font-weight: 600; margin: 0 0 8px; color: #fff; }', + '.module-desc { font-size: 13px; color: #888; margin: 0 0 15px; }', + '.module-components { display: flex; flex-wrap: wrap; gap: 6px; }', + '.component-tag { font-size: 11px; padding: 4px 10px; background: rgba(255,255,255,0.05); border-radius: 12px; color: #aaa; }', + + // Network Topology + '.topology-section { padding: 40px 0; background: rgba(0,0,0,0.2); }', + '.topology-container { display: flex; gap: 40px; padding: 0 40px; align-items: center; justify-content: center; flex-wrap: wrap; }', + '.topo-visual { position: relative; width: 300px; height: 300px; }', + '.topo-hub { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; z-index: 2; }', + '.hub-core { font-size: 48px; animation: pulse 2s infinite; }', + '.hub-label { font-size: 12px; color: #888; margin-top: 5px; }', + '.hub-rings { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }', + '.ring { position: absolute; border: 1px solid rgba(52,152,219,0.3); border-radius: 50%; top: 50%; left: 50%; transform: translate(-50%, -50%); }', + '.ring-1 { width: 120px; height: 120px; animation: ringPulse 3s infinite; }', + '.ring-2 { width: 200px; height: 200px; animation: ringPulse 3s infinite 0.5s; }', + '.ring-3 { width: 280px; height: 280px; animation: ringPulse 3s infinite 1s; }', + '@keyframes ringPulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 0.6; } }', + '.topo-orbits { position: absolute; inset: 0; }', + '.orbit-node { position: absolute; font-size: 24px; animation: orbit 20s linear infinite; }', + '.orbit-node.n1 { top: 10%; left: 50%; }', + '.orbit-node.n2 { top: 30%; right: 10%; }', + '.orbit-node.n3 { bottom: 30%; right: 10%; }', + '.orbit-node.n4 { bottom: 10%; left: 50%; }', + '.orbit-node.n5 { bottom: 30%; left: 10%; }', + '.orbit-node.n6 { top: 30%; left: 10%; }', + '.topo-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; }', + '.topo-stat { background: rgba(30,30,50,0.6); border-radius: 12px; padding: 20px 30px; text-align: center; }', + '.stat-value { font-size: 28px; font-weight: 700; color: #fff; }', + '.stat-value.green { color: #2ecc71; }', + '.stat-value.blue { color: #3498db; }', + '.stat-value.purple { color: #9b59b6; }', + '.stat-label { font-size: 12px; color: #888; margin-top: 5px; }', + + // Quick Stats + '.stats-section { padding: 40px 0; }', + '.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; padding: 0 40px; }', + '.stat-card { display: flex; align-items: center; gap: 15px; background: rgba(30,30,50,0.6); border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; padding: 20px; }', + '.stat-card .stat-icon { font-size: 32px; }', + '.stat-card .stat-value { font-size: 18px; font-weight: 600; color: #fff; }', + '.stat-card .stat-label { font-size: 12px; color: #888; }', + '.stat-card.health.healthy { border-color: rgba(46,204,113,0.5); }', + + // Roadmap + '.roadmap-section { padding: 40px 0; background: rgba(0,0,0,0.2); }', + '.roadmap-timeline { display: flex; gap: 0; padding: 0 40px; overflow-x: auto; }', + '.roadmap-item { flex: 1; min-width: 180px; position: relative; padding: 20px; }', + '.roadmap-item::before { content: ""; position: absolute; top: 32px; left: 0; right: 0; height: 2px; background: rgba(255,255,255,0.1); }', + '.roadmap-item.done::before { background: #2ecc71; }', + '.roadmap-item.current::before { background: linear-gradient(90deg, #3498db, rgba(255,255,255,0.1)); }', + '.roadmap-marker { width: 24px; height: 24px; border-radius: 50%; background: rgba(30,30,50,0.8); border: 2px solid rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 12px; margin-bottom: 15px; position: relative; z-index: 1; }', + '.roadmap-item.done .roadmap-marker { background: #2ecc71; border-color: #2ecc71; color: #fff; }', + '.roadmap-item.current .roadmap-marker { background: #3498db; border-color: #3498db; color: #fff; animation: pulse 2s infinite; }', + '.roadmap-phase { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #888; margin-bottom: 5px; }', + '.roadmap-title { font-size: 16px; font-weight: 600; color: #fff; margin: 0 0 10px; }', + '.roadmap-items { list-style: none; padding: 0; margin: 0; }', + '.roadmap-items li { font-size: 12px; color: #888; padding: 3px 0; }', + '.roadmap-items li::before { content: "β†’ "; color: #555; }', + '.roadmap-item.done .roadmap-items li::before { color: #2ecc71; }', + + // Responsive + '@media (max-width: 768px) {', + ' .hero-title { font-size: 28px; }', + ' .arch-layer { min-width: auto; width: 100%; }', + ' .layer-nodes { flex-wrap: wrap; }', + ' .topology-container { flex-direction: column; }', + '}' + ].join('\n'); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/profiles.js b/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/profiles.js new file mode 100644 index 00000000..eed385b4 --- /dev/null +++ b/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/profiles.js @@ -0,0 +1,746 @@ +'use strict'; +'require view'; +'require dom'; +'require poll'; +'require ui'; +'require rpc'; +'require secubox-p2p/api as P2PAPI'; + +// RPC calls for profiles +var callListProfiles = rpc.declare({ + object: 'luci.secubox', + method: 'list_profiles', + expect: { profiles: [] } +}); + +var callGetProfile = rpc.declare({ + object: 'luci.secubox', + method: 'getProfile', + params: ['name'], + expect: {} +}); + +return view.extend({ + // State + profiles: [], + presets: [], + feeds: [], + components: [], + readinessState: {}, + + load: function() { + var self = this; + return Promise.all([ + P2PAPI.healthCheck().catch(function() { return {}; }), + P2PAPI.getServices().catch(function() { return { services: [] }; }), + P2PAPI.listLocalBackups().catch(function() { return { backups: [] }; }), + callListProfiles().catch(function() { return { profiles: [] }; }) + ]).then(function(results) { + self.health = results[0] || {}; + self.services = results[1].services || []; + self.backups = results[2].backups || []; + self.profiles = results[3].profiles || []; + + // Build component readiness state + self.buildReadinessState(); + self.buildPresets(); + self.buildFeeds(); + }); + }, + + buildReadinessState: function() { + // Define all system components and their readiness criteria + this.components = [ + { + id: 'network', + name: 'Network Core', + icon: '🌐', + category: 'infrastructure', + checks: [ + { name: 'Interfaces', key: 'network_interfaces', required: true }, + { name: 'Firewall', key: 'firewall', required: true }, + { name: 'DHCP', key: 'dhcp', required: false }, + { name: 'DNS', key: 'dns', required: true } + ] + }, + { + id: 'security', + name: 'Security Layer', + icon: 'πŸ›‘οΈ', + category: 'security', + checks: [ + { name: 'CrowdSec', key: 'crowdsec', required: false }, + { name: 'Firewall Rules', key: 'fw_rules', required: true }, + { name: 'SSH Keys', key: 'ssh_keys', required: false }, + { name: 'Certificates', key: 'certs', required: false } + ] + }, + { + id: 'mesh', + name: 'Mesh Network', + icon: 'πŸ•ΈοΈ', + category: 'mesh', + checks: [ + { name: 'WireGuard', key: 'wireguard', required: false }, + { name: 'DNS Federation', key: 'dns_fed', required: false }, + { name: 'HAProxy', key: 'haproxy', required: false }, + { name: 'Peer Discovery', key: 'discovery', required: true } + ] + }, + { + id: 'services', + name: 'Services', + icon: 'πŸ“‘', + category: 'services', + checks: [ + { name: 'Web Server', key: 'uhttpd', required: true }, + { name: 'Gitea', key: 'gitea', required: false }, + { name: 'LocalAI', key: 'localai', required: false }, + { name: 'Custom Apps', key: 'apps', required: false } + ] + }, + { + id: 'storage', + name: 'Data & Storage', + icon: 'πŸ’Ύ', + category: 'data', + checks: [ + { name: 'Config Backup', key: 'config_backup', required: true }, + { name: 'Gitea VCS', key: 'gitea_vcs', required: false }, + { name: 'Local Snapshots', key: 'snapshots', required: false }, + { name: 'External Storage', key: 'external', required: false } + ] + }, + { + id: 'monitoring', + name: 'Monitoring', + icon: 'πŸ“Š', + category: 'observability', + checks: [ + { name: 'System Logs', key: 'logs', required: true }, + { name: 'Metrics', key: 'metrics', required: false }, + { name: 'Alerts', key: 'alerts', required: false }, + { name: 'Health Checks', key: 'health', required: true } + ] + } + ]; + + // Evaluate readiness for each component + var self = this; + this.components.forEach(function(comp) { + var ready = 0; + var total = comp.checks.length; + var requiredMet = true; + + comp.checks.forEach(function(check) { + // Simulate check status (in real implementation, query actual state) + var isReady = self.evaluateCheck(check.key); + check.status = isReady ? 'ready' : 'pending'; + if (isReady) ready++; + if (check.required && !isReady) requiredMet = false; + }); + + comp.readyCount = ready; + comp.totalCount = total; + comp.percentage = Math.round((ready / total) * 100); + comp.status = requiredMet ? (ready === total ? 'complete' : 'partial') : 'blocked'; + }); + + // Overall readiness + var totalReady = this.components.reduce(function(sum, c) { return sum + c.readyCount; }, 0); + var totalChecks = this.components.reduce(function(sum, c) { return sum + c.totalCount; }, 0); + this.readinessState = { + ready: totalReady, + total: totalChecks, + percentage: Math.round((totalReady / totalChecks) * 100) + }; + }, + + evaluateCheck: function(key) { + // Real implementation would check actual system state + var healthyKeys = ['network_interfaces', 'firewall', 'dns', 'fw_rules', 'uhttpd', 'logs', 'health', 'discovery']; + var optionalReady = ['dhcp', 'config_backup']; + + if (this.health.dns_federation) healthyKeys.push('dns_fed'); + if (this.health.wireguard_mesh) healthyKeys.push('wireguard'); + if (this.health.haproxy) healthyKeys.push('haproxy'); + if (this.backups && this.backups.length > 0) healthyKeys.push('snapshots'); + + return healthyKeys.includes(key) || optionalReady.includes(key); + }, + + buildPresets: function() { + this.presets = [ + { + id: 'minimal', + name: 'Minimal Router', + icon: 'πŸ“¦', + description: 'Basic routing and firewall only', + components: ['network', 'security'], + tags: ['lightweight', 'basic'] + }, + { + id: 'security-gateway', + name: 'Security Gateway', + icon: 'πŸ›‘οΈ', + description: 'Full security stack with IDS/IPS', + components: ['network', 'security', 'monitoring'], + tags: ['security', 'enterprise'] + }, + { + id: 'mesh-node', + name: 'Mesh Node', + icon: 'πŸ•ΈοΈ', + description: 'P2P mesh participant with full federation', + components: ['network', 'security', 'mesh', 'services'], + tags: ['p2p', 'distributed'] + }, + { + id: 'full-appliance', + name: 'Full SecuBox Appliance', + icon: '🏠', + description: 'Complete SecuBox with all features', + components: ['network', 'security', 'mesh', 'services', 'storage', 'monitoring'], + tags: ['complete', 'production'] + }, + { + id: 'development', + name: 'Development Node', + icon: 'πŸ§ͺ', + description: 'Testing and development configuration', + components: ['network', 'services', 'storage'], + tags: ['dev', 'testing'] + }, + { + id: 'edge-ai', + name: 'Edge AI Node', + icon: 'πŸ€–', + description: 'LocalAI and ML inference capable', + components: ['network', 'services', 'storage', 'monitoring'], + tags: ['ai', 'ml', 'edge'] + } + ]; + }, + + buildFeeds: function() { + this.feeds = [ + { + id: 'secubox-core', + name: 'SecuBox Core', + url: 'https://feed.secubox.io/core', + enabled: true, + packages: 12, + icon: 'πŸ“¦' + }, + { + id: 'secubox-apps', + name: 'SecuBox Apps', + url: 'https://feed.secubox.io/apps', + enabled: true, + packages: 28, + icon: 'πŸ“±' + }, + { + id: 'secubox-security', + name: 'Security Tools', + url: 'https://feed.secubox.io/security', + enabled: true, + packages: 8, + icon: 'πŸ›‘οΈ' + }, + { + id: 'local-feed', + name: 'Local Development', + url: 'file:///etc/opkg/local-feed', + enabled: false, + packages: 0, + icon: 'πŸ”§' + }, + { + id: 'community', + name: 'Community Packages', + url: 'https://feed.secubox.io/community', + enabled: false, + packages: 45, + icon: 'πŸ‘₯' + } + ]; + }, + + render: function() { + var self = this; + + var container = E('div', { 'class': 'profiles-page' }, [ + E('style', {}, this.getStyles()), + + // Header + this.renderHeader(), + + // Readiness Overview + this.renderReadinessOverview(), + + // Component Grid + this.renderComponentGrid(), + + // Presets Section + this.renderPresetsSection(), + + // Feeds Section + this.renderFeedsSection(), + + // Profiles Section + this.renderProfilesSection(), + + // Clone Actions + this.renderCloneActions() + ]); + + return container; + }, + + renderHeader: function() { + return E('div', { 'class': 'page-header' }, [ + E('div', { 'class': 'header-content' }, [ + E('h1', {}, [ + E('span', { 'class': 'header-icon' }, '🧬'), + 'Profiles & Cloning' + ]), + E('p', { 'class': 'header-desc' }, + 'Manage system profiles, presets, feeds, and component readiness for cloning and deployment' + ) + ]), + E('div', { 'class': 'header-actions' }, [ + E('button', { 'class': 'btn primary', 'click': L.bind(this.createSnapshot, this) }, 'πŸ“Έ Create Snapshot'), + E('button', { 'class': 'btn', 'click': L.bind(this.exportProfile, this) }, 'πŸ“€ Export Profile') + ]) + ]); + }, + + renderReadinessOverview: function() { + var pct = this.readinessState.percentage; + var statusClass = pct >= 80 ? 'good' : pct >= 50 ? 'warning' : 'critical'; + + return E('div', { 'class': 'readiness-overview' }, [ + E('div', { 'class': 'readiness-gauge ' + statusClass }, [ + E('div', { 'class': 'gauge-circle' }, [ + E('svg', { 'viewBox': '0 0 36 36', 'class': 'gauge-svg' }, [ + E('path', { + 'd': 'M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831', + 'fill': 'none', + 'stroke': 'rgba(255,255,255,0.1)', + 'stroke-width': '3' + }), + E('path', { + 'd': 'M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831', + 'fill': 'none', + 'stroke': statusClass === 'good' ? '#2ecc71' : statusClass === 'warning' ? '#f39c12' : '#e74c3c', + 'stroke-width': '3', + 'stroke-dasharray': pct + ', 100' + }) + ]), + E('div', { 'class': 'gauge-value' }, pct + '%') + ]), + E('div', { 'class': 'gauge-label' }, 'System Readiness') + ]), + E('div', { 'class': 'readiness-stats' }, [ + E('div', { 'class': 'rstat' }, [ + E('div', { 'class': 'rstat-value' }, String(this.readinessState.ready)), + E('div', { 'class': 'rstat-label' }, 'Ready') + ]), + E('div', { 'class': 'rstat' }, [ + E('div', { 'class': 'rstat-value' }, String(this.readinessState.total)), + E('div', { 'class': 'rstat-label' }, 'Total Checks') + ]), + E('div', { 'class': 'rstat' }, [ + E('div', { 'class': 'rstat-value' }, String(this.components.filter(function(c) { return c.status === 'complete'; }).length)), + E('div', { 'class': 'rstat-label' }, 'Complete') + ]), + E('div', { 'class': 'rstat' }, [ + E('div', { 'class': 'rstat-value' }, String(this.components.filter(function(c) { return c.status === 'blocked'; }).length)), + E('div', { 'class': 'rstat-label' }, 'Blocked') + ]) + ]) + ]); + }, + + renderComponentGrid: function() { + var self = this; + + return E('div', { 'class': 'section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', {}, '🧩'), + 'Component Readiness' + ]), + E('div', { 'class': 'component-grid' }, + this.components.map(function(comp) { + return E('div', { 'class': 'component-card ' + comp.status }, [ + E('div', { 'class': 'comp-header' }, [ + E('span', { 'class': 'comp-icon' }, comp.icon), + E('span', { 'class': 'comp-status ' + comp.status }, + comp.status === 'complete' ? 'βœ“' : comp.status === 'partial' ? '◐' : 'βœ—' + ) + ]), + E('h3', { 'class': 'comp-name' }, comp.name), + E('div', { 'class': 'comp-progress' }, [ + E('div', { 'class': 'progress-bar' }, [ + E('div', { 'class': 'progress-fill', 'style': 'width: ' + comp.percentage + '%' }) + ]), + E('span', { 'class': 'progress-text' }, comp.readyCount + '/' + comp.totalCount) + ]), + E('div', { 'class': 'comp-checks' }, + comp.checks.map(function(check) { + return E('div', { 'class': 'check-item ' + check.status }, [ + E('span', { 'class': 'check-icon' }, check.status === 'ready' ? 'βœ“' : 'β—‹'), + E('span', { 'class': 'check-name' }, check.name), + check.required ? E('span', { 'class': 'check-req' }, '*') : '' + ]); + }) + ) + ]); + }) + ) + ]); + }, + + renderPresetsSection: function() { + var self = this; + + return E('div', { 'class': 'section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', {}, '⚑'), + 'Quick Presets' + ]), + E('div', { 'class': 'presets-grid' }, + this.presets.map(function(preset) { + var compReady = preset.components.filter(function(cid) { + var comp = self.components.find(function(c) { return c.id === cid; }); + return comp && comp.status !== 'blocked'; + }).length; + var canApply = compReady === preset.components.length; + + return E('div', { 'class': 'preset-card' + (canApply ? '' : ' disabled') }, [ + E('div', { 'class': 'preset-icon' }, preset.icon), + E('h3', { 'class': 'preset-name' }, preset.name), + E('p', { 'class': 'preset-desc' }, preset.description), + E('div', { 'class': 'preset-tags' }, + preset.tags.map(function(tag) { + return E('span', { 'class': 'tag' }, tag); + }) + ), + E('div', { 'class': 'preset-components' }, + preset.components.map(function(cid) { + var comp = self.components.find(function(c) { return c.id === cid; }); + return E('span', { 'class': 'comp-badge ' + (comp ? comp.status : '') }, comp ? comp.icon : '?'); + }) + ), + E('button', { + 'class': 'btn small' + (canApply ? ' primary' : ''), + 'disabled': !canApply, + 'click': function() { self.applyPreset(preset.id); } + }, canApply ? 'Apply' : 'Unavailable') + ]); + }) + ) + ]); + }, + + renderFeedsSection: function() { + var self = this; + + return E('div', { 'class': 'section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', {}, 'πŸ“‘'), + 'Package Feeds' + ]), + E('div', { 'class': 'feeds-list' }, + this.feeds.map(function(feed) { + return E('div', { 'class': 'feed-item' + (feed.enabled ? ' enabled' : '') }, [ + E('div', { 'class': 'feed-icon' }, feed.icon), + E('div', { 'class': 'feed-info' }, [ + E('div', { 'class': 'feed-name' }, feed.name), + E('div', { 'class': 'feed-url' }, feed.url) + ]), + E('div', { 'class': 'feed-packages' }, [ + E('span', { 'class': 'pkg-count' }, String(feed.packages)), + E('span', { 'class': 'pkg-label' }, 'packages') + ]), + E('label', { 'class': 'feed-toggle' }, [ + E('input', { + 'type': 'checkbox', + 'checked': feed.enabled, + 'change': function(e) { self.toggleFeed(feed.id, e.target.checked); } + }), + E('span', { 'class': 'toggle-slider' }) + ]) + ]); + }) + ), + E('div', { 'class': 'feeds-actions' }, [ + E('button', { 'class': 'btn', 'click': L.bind(this.updateFeeds, this) }, 'πŸ”„ Update All'), + E('button', { 'class': 'btn', 'click': L.bind(this.addFeed, this) }, 'βž• Add Feed') + ]) + ]); + }, + + renderProfilesSection: function() { + var self = this; + + return E('div', { 'class': 'section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', {}, 'πŸ‘€'), + 'Saved Profiles' + ]), + E('div', { 'class': 'profiles-list' }, + this.profiles.length > 0 ? + this.profiles.map(function(profile) { + return E('div', { 'class': 'profile-item' }, [ + E('div', { 'class': 'profile-icon' }, 'πŸ“‹'), + E('div', { 'class': 'profile-info' }, [ + E('div', { 'class': 'profile-name' }, profile.name || profile), + E('div', { 'class': 'profile-meta' }, 'Created: ' + (profile.created || 'Unknown')) + ]), + E('div', { 'class': 'profile-actions' }, [ + E('button', { 'class': 'btn small', 'click': function() { self.loadProfile(profile.name || profile); } }, 'Load'), + E('button', { 'class': 'btn small danger', 'click': function() { self.deleteProfile(profile.name || profile); } }, 'πŸ—‘οΈ') + ]) + ]); + }) + : + E('div', { 'class': 'empty-state' }, [ + E('span', { 'class': 'empty-icon' }, 'πŸ“­'), + E('p', {}, 'No saved profiles'), + E('button', { 'class': 'btn primary', 'click': L.bind(this.createProfile, this) }, 'Create Profile') + ]) + ) + ]); + }, + + renderCloneActions: function() { + var self = this; + + return E('div', { 'class': 'section clone-section' }, [ + E('h2', { 'class': 'section-title' }, [ + E('span', {}, '🧬'), + 'Clone & Deploy' + ]), + E('div', { 'class': 'clone-options' }, [ + E('div', { 'class': 'clone-card' }, [ + E('div', { 'class': 'clone-icon' }, 'πŸ“€'), + E('h3', {}, 'Export Bundle'), + E('p', {}, 'Export complete system state as deployable bundle'), + E('button', { 'class': 'btn primary', 'click': L.bind(this.exportBundle, this) }, 'Export') + ]), + E('div', { 'class': 'clone-card' }, [ + E('div', { 'class': 'clone-icon' }, 'πŸ“₯'), + E('h3', {}, 'Import Bundle'), + E('p', {}, 'Import and apply a system bundle from file'), + E('button', { 'class': 'btn', 'click': L.bind(this.importBundle, this) }, 'Import') + ]), + E('div', { 'class': 'clone-card' }, [ + E('div', { 'class': 'clone-icon' }, 'πŸ”„'), + E('h3', {}, 'Sync to Mesh'), + E('p', {}, 'Distribute current profile to all mesh peers'), + E('button', { 'class': 'btn', 'click': L.bind(this.syncToMesh, this) }, 'Sync') + ]), + E('div', { 'class': 'clone-card' }, [ + E('div', { 'class': 'clone-icon' }, '☁️'), + E('h3', {}, 'Push to Gitea'), + E('p', {}, 'Backup profile and state to Gitea repository'), + E('button', { 'class': 'btn', 'click': L.bind(this.pushToGitea, this) }, 'Push') + ]) + ]) + ]); + }, + + // Action handlers + createSnapshot: function() { + var name = 'snapshot-' + new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-'); + P2PAPI.createLocalBackup(name, {}).then(function(result) { + if (result.success) { + ui.addNotification(null, E('p', 'πŸ“Έ Snapshot created: ' + name), 'info'); + } else { + ui.addNotification(null, E('p', '❌ Failed to create snapshot'), 'error'); + } + }); + }, + + exportProfile: function() { + ui.addNotification(null, E('p', 'πŸ“€ Exporting profile...'), 'info'); + }, + + applyPreset: function(presetId) { + ui.addNotification(null, E('p', '⚑ Applying preset: ' + presetId), 'info'); + }, + + toggleFeed: function(feedId, enabled) { + ui.addNotification(null, E('p', (enabled ? 'βœ“' : 'βœ—') + ' Feed ' + feedId + ' ' + (enabled ? 'enabled' : 'disabled')), 'info'); + }, + + updateFeeds: function() { + ui.addNotification(null, E('p', 'πŸ”„ Updating package feeds...'), 'info'); + }, + + addFeed: function() { + ui.addNotification(null, E('p', 'βž• Add feed dialog...'), 'info'); + }, + + loadProfile: function(name) { + ui.addNotification(null, E('p', 'πŸ“‹ Loading profile: ' + name), 'info'); + }, + + deleteProfile: function(name) { + ui.addNotification(null, E('p', 'πŸ—‘οΈ Deleting profile: ' + name), 'info'); + }, + + createProfile: function() { + ui.addNotification(null, E('p', 'βž• Create profile dialog...'), 'info'); + }, + + exportBundle: function() { + ui.addNotification(null, E('p', 'πŸ“€ Exporting system bundle...'), 'info'); + }, + + importBundle: function() { + ui.addNotification(null, E('p', 'πŸ“₯ Import bundle dialog...'), 'info'); + }, + + syncToMesh: function() { + P2PAPI.syncCatalog().then(function() { + ui.addNotification(null, E('p', 'πŸ”„ Profile synced to mesh'), 'info'); + }); + }, + + pushToGitea: function() { + P2PAPI.pushGiteaBackup('Profile sync', {}).then(function(result) { + if (result.success) { + ui.addNotification(null, E('p', '☁️ Pushed to Gitea: ' + result.files_pushed + ' files'), 'info'); + } + }); + }, + + getStyles: function() { + return [ + '.profiles-page { font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; background: linear-gradient(135deg, #0a0a1a 0%, #1a1a2e 100%); min-height: 100vh; padding: 20px; }', + + // Header + '.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; padding: 20px; background: rgba(30,30,50,0.5); border-radius: 16px; }', + '.header-content h1 { display: flex; align-items: center; gap: 12px; font-size: 28px; margin: 0 0 8px; }', + '.header-icon { font-size: 32px; }', + '.header-desc { color: #888; margin: 0; font-size: 14px; }', + '.header-actions { display: flex; gap: 10px; }', + + // Buttons + '.btn { padding: 10px 20px; border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; background: rgba(255,255,255,0.05); color: #fff; cursor: pointer; transition: all 0.2s; }', + '.btn:hover { background: rgba(255,255,255,0.1); }', + '.btn.primary { background: linear-gradient(135deg, #3498db, #2980b9); border-color: #3498db; }', + '.btn.primary:hover { background: linear-gradient(135deg, #2980b9, #1a5276); }', + '.btn.small { padding: 6px 12px; font-size: 12px; }', + '.btn.danger { border-color: #e74c3c; color: #e74c3c; }', + '.btn:disabled { opacity: 0.5; cursor: not-allowed; }', + + // Section + '.section { margin-bottom: 30px; }', + '.section-title { display: flex; align-items: center; gap: 10px; font-size: 20px; margin: 0 0 20px; color: #fff; }', + + // Readiness Overview + '.readiness-overview { display: flex; gap: 40px; align-items: center; padding: 30px; background: rgba(30,30,50,0.5); border-radius: 16px; margin-bottom: 30px; }', + '.readiness-gauge { text-align: center; }', + '.gauge-circle { position: relative; width: 120px; height: 120px; }', + '.gauge-svg { transform: rotate(-90deg); width: 100%; height: 100%; }', + '.gauge-value { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 28px; font-weight: 700; }', + '.readiness-gauge.good .gauge-value { color: #2ecc71; }', + '.readiness-gauge.warning .gauge-value { color: #f39c12; }', + '.readiness-gauge.critical .gauge-value { color: #e74c3c; }', + '.gauge-label { margin-top: 10px; font-size: 14px; color: #888; }', + '.readiness-stats { display: flex; gap: 30px; }', + '.rstat { text-align: center; }', + '.rstat-value { font-size: 32px; font-weight: 700; color: #fff; }', + '.rstat-label { font-size: 12px; color: #888; margin-top: 5px; }', + + // Component Grid + '.component-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }', + '.component-card { background: rgba(30,30,50,0.6); border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; padding: 20px; }', + '.component-card.complete { border-color: rgba(46,204,113,0.5); }', + '.component-card.partial { border-color: rgba(243,156,18,0.5); }', + '.component-card.blocked { border-color: rgba(231,76,60,0.5); }', + '.comp-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }', + '.comp-icon { font-size: 28px; }', + '.comp-status { font-size: 16px; }', + '.comp-status.complete { color: #2ecc71; }', + '.comp-status.partial { color: #f39c12; }', + '.comp-status.blocked { color: #e74c3c; }', + '.comp-name { font-size: 16px; margin: 0 0 12px; color: #fff; }', + '.comp-progress { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; }', + '.progress-bar { flex: 1; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden; }', + '.progress-fill { height: 100%; background: linear-gradient(90deg, #3498db, #2ecc71); transition: width 0.3s; }', + '.progress-text { font-size: 12px; color: #888; }', + '.comp-checks { display: flex; flex-direction: column; gap: 6px; }', + '.check-item { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #888; }', + '.check-item.ready { color: #2ecc71; }', + '.check-icon { width: 14px; }', + '.check-req { color: #e74c3c; font-size: 10px; }', + + // Presets Grid + '.presets-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; }', + '.preset-card { background: rgba(30,30,50,0.6); border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; padding: 20px; text-align: center; }', + '.preset-card.disabled { opacity: 0.5; }', + '.preset-icon { font-size: 40px; margin-bottom: 12px; }', + '.preset-name { font-size: 16px; margin: 0 0 8px; color: #fff; }', + '.preset-desc { font-size: 12px; color: #888; margin: 0 0 12px; }', + '.preset-tags { display: flex; justify-content: center; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }', + '.tag { font-size: 10px; padding: 3px 8px; background: rgba(52,152,219,0.2); border-radius: 10px; color: #3498db; }', + '.preset-components { display: flex; justify-content: center; gap: 6px; margin-bottom: 15px; }', + '.comp-badge { font-size: 16px; padding: 4px; background: rgba(0,0,0,0.3); border-radius: 6px; }', + '.comp-badge.complete { background: rgba(46,204,113,0.2); }', + '.comp-badge.blocked { background: rgba(231,76,60,0.2); }', + + // Feeds + '.feeds-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 15px; }', + '.feed-item { display: flex; align-items: center; gap: 15px; padding: 15px; background: rgba(30,30,50,0.6); border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; }', + '.feed-item.enabled { border-color: rgba(46,204,113,0.5); }', + '.feed-icon { font-size: 24px; }', + '.feed-info { flex: 1; }', + '.feed-name { font-weight: 600; color: #fff; }', + '.feed-url { font-size: 11px; color: #666; font-family: monospace; }', + '.feed-packages { text-align: center; }', + '.pkg-count { font-size: 18px; font-weight: 600; color: #3498db; display: block; }', + '.pkg-label { font-size: 10px; color: #888; }', + '.feed-toggle { position: relative; width: 44px; height: 24px; }', + '.feed-toggle input { opacity: 0; width: 0; height: 0; }', + '.toggle-slider { position: absolute; inset: 0; background: rgba(255,255,255,0.1); border-radius: 12px; cursor: pointer; transition: 0.3s; }', + '.toggle-slider::before { content: ""; position: absolute; width: 18px; height: 18px; left: 3px; bottom: 3px; background: #fff; border-radius: 50%; transition: 0.3s; }', + '.feed-toggle input:checked + .toggle-slider { background: #2ecc71; }', + '.feed-toggle input:checked + .toggle-slider::before { transform: translateX(20px); }', + '.feeds-actions { display: flex; gap: 10px; }', + + // Profiles + '.profiles-list { display: flex; flex-direction: column; gap: 10px; }', + '.profile-item { display: flex; align-items: center; gap: 15px; padding: 15px; background: rgba(30,30,50,0.6); border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; }', + '.profile-icon { font-size: 24px; }', + '.profile-info { flex: 1; }', + '.profile-name { font-weight: 600; color: #fff; }', + '.profile-meta { font-size: 11px; color: #888; }', + '.profile-actions { display: flex; gap: 8px; }', + '.empty-state { text-align: center; padding: 40px; color: #888; }', + '.empty-icon { font-size: 48px; display: block; margin-bottom: 15px; opacity: 0.5; }', + + // Clone Section + '.clone-section { background: rgba(30,30,50,0.3); border-radius: 16px; padding: 25px; }', + '.clone-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; }', + '.clone-card { background: rgba(0,0,0,0.3); border-radius: 12px; padding: 25px; text-align: center; }', + '.clone-icon { font-size: 40px; margin-bottom: 15px; }', + '.clone-card h3 { font-size: 16px; margin: 0 0 10px; color: #fff; }', + '.clone-card p { font-size: 12px; color: #888; margin: 0 0 15px; }', + + // Responsive + '@media (max-width: 768px) {', + ' .page-header { flex-direction: column; gap: 20px; text-align: center; }', + ' .readiness-overview { flex-direction: column; }', + '}' + ].join('\n'); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-secubox-p2p/root/usr/share/luci/menu.d/luci-app-secubox-p2p.json b/package/secubox/luci-app-secubox-p2p/root/usr/share/luci/menu.d/luci-app-secubox-p2p.json index 0c0cfe8d..b30643b4 100644 --- a/package/secubox/luci-app-secubox-p2p/root/usr/share/luci/menu.d/luci-app-secubox-p2p.json +++ b/package/secubox/luci-app-secubox-p2p/root/usr/share/luci/menu.d/luci-app-secubox-p2p.json @@ -1,48 +1,64 @@ { - "admin/secubox/p2p-hub": { - "title": "P2P Hub", + "admin/secubox/mirrorbox": { + "title": "MirrorBox", "order": 15, "action": { "type": "view", - "path": "secubox-p2p/hub" + "path": "secubox-p2p/overview" }, "depends": { "acl": ["luci-app-secubox-p2p"] } }, - "admin/secubox/p2p-hub/overview": { + "admin/secubox/mirrorbox/overview": { "title": "Overview", "order": 10, + "action": { + "type": "view", + "path": "secubox-p2p/overview" + } + }, + "admin/secubox/mirrorbox/hub": { + "title": "P2P Hub", + "order": 20, "action": { "type": "view", "path": "secubox-p2p/hub" } }, - "admin/secubox/p2p-hub/peers": { + "admin/secubox/mirrorbox/peers": { "title": "Peers", - "order": 20, + "order": 30, "action": { "type": "view", "path": "secubox-p2p/peers" } }, - "admin/secubox/p2p-hub/services": { + "admin/secubox/mirrorbox/services": { "title": "Services", - "order": 30, + "order": 40, "action": { "type": "view", "path": "secubox-p2p/services" } }, - "admin/secubox/p2p-hub/mesh": { + "admin/secubox/mirrorbox/profiles": { + "title": "Profiles", + "order": 50, + "action": { + "type": "view", + "path": "secubox-p2p/profiles" + } + }, + "admin/secubox/mirrorbox/mesh": { "title": "Mesh Network", - "order": 40, + "order": 60, "action": { "type": "view", "path": "secubox-p2p/mesh" } }, - "admin/secubox/p2p-hub/settings": { + "admin/secubox/mirrorbox/settings": { "title": "Settings", "order": 90, "action": {