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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-30 13:40:01 +01:00
parent a68d8b638f
commit 55ef43aa54
3 changed files with 1299 additions and 11 deletions

View File

@ -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
});

View File

@ -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
});

View File

@ -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": {