diff --git a/.claude/WIP.md b/.claude/WIP.md
index 84607f58..382c751f 100644
--- a/.claude/WIP.md
+++ b/.claude/WIP.md
@@ -1,6 +1,6 @@
# Work In Progress (Claude)
-_Last updated: 2026-03-08 (RTTY Remote Module)_
+_Last updated: 2026-03-09 (Dev Status Widget v2.1)_
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
@@ -8,6 +8,39 @@ _Last updated: 2026-03-08 (RTTY Remote Module)_
## Recently Completed
+### 2026-03-09
+
+- **Dev Status Widget v2.1 (Dynamic Dashboard)**
+ - Complete redesign with 4-layer architecture visualization
+ - 22+ features with dependency tracking (dependsOn/usedBy)
+ - 80+ components with status indicators
+ - Interactive filters: layer, status, category with localStorage persistence
+ - Feature cards: click to expand, show full dependencies/components
+ - Layer cards: click to filter features by layer
+ - Interconnection graph showing feature dependencies
+ - Milestone timeline to v1.0 with progress tracking
+ - Production stats display (185 packages, 226 vhosts, etc.)
+ - Auto-refresh with live RPCD data (60s interval)
+ - ES5 compatible for older browsers
+ - Standalone HTML page: `/dev-status.html` (no auth required)
+ - Files: `dev-status-widget.js`, `dev-status.js`, `dev-status-standalone.html`
+
+- **DNS Zone Configuration Sync**
+ - Fixed BIND zone path mismatch: `/srv/dns/zones/` → `/etc/bind/zones/`
+ - Added ganimed.fr zone declaration to `named.conf.zones`
+ - Synced zone files between LuCI-managed and BIND-loaded paths
+
+- **Mitmproxy WAF Memory Optimization**
+ - Diagnosed memory leak (687MB RSS)
+ - Added flow limits: `--set flow_detail=0 --set hardlimit=500`
+ - Reduced memory to 77MB
+ - Fixed `/srv/mitmproxy-in/haproxy-routes.json` for git.maegia.tv
+
+- **Config Backups Repository**
+ - Created `config-backups/` directory with BIND zones
+ - Created private `secubox-configs` repo on local Gitea
+ - Git remote: `git@git.maegia.tv:reepost/secubox-configs.git`
+
### 2026-03-08
- **RTTY Remote Control Module (Phase 3 - Web Terminal)**
@@ -37,6 +70,14 @@ _Last updated: 2026-03-08 (RTTY Remote Module)_
- Master-link integration for authentication
- Tested: `rttyctl rpc 127.0.0.1 system board` works
+- **lldh360.maegia.tv BIND Zone Fix**
+ - DNS was returning NXDOMAIN despite zone file existing
+ - Root cause: BIND (named) is the authoritative DNS, not dnsmasq
+ - Zone file `/srv/dns/zones/maegia.tv.zone` existed but wasn't registered in BIND
+ - Added zone entry to `/etc/bind/named.conf.zones`
+ - Restarted BIND (named), domain now resolves correctly
+ - Site accessible via HTTPS (HTTP 200)
+
- **HAProxy mitmproxy Port Fix**
- Changed mitmproxy-in WAF port from 8890 to 22222
- Fixed UCI config regeneration issue (was overwriting manual edits)
diff --git a/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-standalone.html b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-standalone.html
new file mode 100644
index 00000000..602091a6
--- /dev/null
+++ b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-standalone.html
@@ -0,0 +1,366 @@
+
+
+
+
+
+ SecuBox Development Status
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-widget.js b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-widget.js
index e1ae469a..11e3f963 100644
--- a/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-widget.js
+++ b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-widget.js
@@ -1,1337 +1,1404 @@
'use strict';
'require baseclass';
/**
- * SecuBox Development Status Widget
- * Real-time development progress tracker
+ * SecuBox Development Status Widget v2.1
+ * Dynamic + Interactive Architecture Dashboard
+ * Features → Components → Submodules + Interconnections
+ * - Live data from RPCD (no auth required for read-only)
+ * - LocalStorage for filter persistence
+ * - Auto-refresh capability
+ * - ES5 compatible for older browsers
+ * Generated from DEV-STATUS.md - 2026-03-09
*/
-const DevStatusWidget = {
+var DevStatusWidget = {
targetVersion: '1.0.0',
- // Development milestones and progress
- milestones: {
- 'modules-core': {
- name: 'Core Modules',
- progress: 100,
- total: 15,
- completed: 15,
- icon: '📦',
+ lastUpdate: '2026-03-09',
+ totalPackages: 185,
+ refreshInterval: null,
+ refreshSeconds: 60,
+ activeFilters: {
+ layer: null,
+ status: null,
+ category: null
+ },
+
+ // ============================================================
+ // ARCHITECTURE: 4 LAYERS
+ // ============================================================
+ layers: {
+ 'core': {
+ id: 'core',
+ name: 'Couche 1: Core Mesh',
+ description: 'Infrastructure fondamentale: reverse proxy, WAF, DNS, containers',
+ progress: 85,
+ icon: '🏗️',
color: '#10b981',
- items: [
- { name: 'SecuBox Central Hub', status: 'completed' },
- { name: 'System Hub', status: 'completed' },
- { name: 'Traffic Shaper', status: 'completed' },
- { name: 'CrowdSec Dashboard', status: 'completed' },
- { name: 'Netdata Dashboard', status: 'completed' },
- { name: 'Netifyd Dashboard', status: 'completed' },
- { name: 'Network Modes', status: 'completed' },
- { name: 'WireGuard Dashboard', status: 'completed' },
- { name: 'Auth Guardian', status: 'completed' },
- { name: 'Client Guardian (Captive Portal v1.0.0)', status: 'completed' },
- { name: 'Bandwidth Manager', status: 'completed' },
- { name: 'Media Flow', status: 'completed' },
- { name: 'CDN Cache', status: 'completed' },
- { name: 'VHost Manager', status: 'completed' },
- { name: 'KSM Manager', status: 'completed' }
- ]
+ order: 1
},
- 'hardware-support': {
- name: 'Hardware Support',
- progress: 90,
- total: 5,
- completed: 4,
- icon: '🔧',
- color: '#f59e0b',
- items: [
- { name: 'x86-64 Tier 1 (PC / VM)', status: 'completed' },
- { name: 'ARM Cortex-A72 Tier 1 (MOCHAbin / RPi4)', status: 'completed' },
- { name: 'ARM Cortex-A53 Tier 1 (ESPRESSObin / Sheeva64)', status: 'completed' },
- { name: 'Tier 2 ARM64 / ARM32 Targets', status: 'in-progress' },
- { name: 'Tier 2 MIPS Targets', status: 'in-progress' }
- ]
- },
- 'integration': {
- name: 'Integration & Testing',
- progress: 88,
- total: 6,
- completed: 5,
- icon: '🧪',
- color: '#3b82f6',
- items: [
- { name: 'LuCI Integration', status: 'completed' },
- { name: 'RPCD Backends', status: 'completed' },
- { name: 'ubus APIs', status: 'completed' },
- { name: 'Multi-platform Build', status: 'completed' },
- { name: 'Documentation', status: 'completed' },
- { name: 'Hardware Beta Testing', status: 'in-progress' }
- ]
- },
- 'campaign-prep': {
- name: 'Campaign Preparation',
- progress: 75,
- total: 5,
- completed: 3,
- icon: '🚀',
+ 'ai': {
+ id: 'ai',
+ name: 'Couche 2: AI Gateway',
+ description: 'Intelligence artificielle: inference, agents, mémoire contextuelle',
+ progress: 60,
+ icon: '🤖',
color: '#8b5cf6',
- items: [
- { name: 'Website Multi-language', status: 'completed' },
- { name: 'Demo Pages', status: 'completed' },
- { name: 'Video Tutorials', status: 'in-progress' },
- { name: 'Marketing Materials', status: 'in-progress' },
- { name: 'Crowdfunding Setup', status: 'planned' }
- ]
+ order: 2
+ },
+ 'mirrornet': {
+ id: 'mirrornet',
+ name: 'Couche 3: MirrorNet P2P',
+ description: 'Réseau maillé: identité, gossip, partage IOC, mirroring',
+ progress: 40,
+ icon: '🌐',
+ color: '#06b6d4',
+ order: 3
+ },
+ 'certification': {
+ id: 'certification',
+ name: 'Couche 4: Certification',
+ description: 'Conformité: ANSSI CSPN, CRA, audit sécurité',
+ progress: 20,
+ icon: '🏆',
+ color: '#f59e0b',
+ order: 4
}
},
- // Per-module status overview (will be populated dynamically)
- moduleStatus: [],
+ // ============================================================
+ // FEATURES: Major functional areas
+ // ============================================================
+ features: {
+ // === SECURITY ===
+ 'intrusion-prevention': {
+ id: 'intrusion-prevention',
+ name: 'Intrusion Prevention',
+ layer: 'core',
+ category: 'security',
+ status: 'production',
+ progress: 95,
+ icon: '🛡️',
+ description: 'Détection et blocage des menaces en temps réel',
+ components: ['crowdsec', 'firewall-bouncer', 'wazuh'],
+ dependsOn: ['network-stack'],
+ usedBy: ['threat-intelligence', 'ai-security']
+ },
+ 'waf': {
+ id: 'waf',
+ name: 'Web Application Firewall',
+ layer: 'core',
+ category: 'security',
+ status: 'production',
+ progress: 90,
+ icon: '🔥',
+ description: 'Inspection HTTP/HTTPS, détection bots, analytics',
+ components: ['mitmproxy', 'haproxy-router', 'analytics'],
+ dependsOn: ['reverse-proxy'],
+ usedBy: ['session-analytics', 'threat-intelligence']
+ },
+ 'dns-firewall': {
+ id: 'dns-firewall',
+ name: 'DNS Firewall',
+ layer: 'core',
+ category: 'security',
+ status: 'production',
+ progress: 85,
+ icon: '🚫',
+ description: 'Blocage DNS (RPZ), threat feeds, mesh DNS',
+ components: ['vortex-dns', 'vortex-firewall', 'rpz-zones'],
+ dependsOn: ['dns-master'],
+ usedBy: ['ai-security', 'mesh-network']
+ },
+ 'access-control': {
+ id: 'access-control',
+ name: 'Access Control',
+ layer: 'core',
+ category: 'security',
+ status: 'production',
+ progress: 90,
+ icon: '🔐',
+ description: 'NAC, portail captif, OAuth2, vouchers',
+ components: ['auth-guardian', 'client-guardian', 'nodogsplash', 'mac-guardian'],
+ dependsOn: ['network-stack'],
+ usedBy: ['user-management']
+ },
- // Static module definitions (fallback if API fails)
- staticModuleStatus: [
- { name: 'SecuBox Central Hub', version: '0.7.0-r6', note: 'Dashboard central + Appstore (5 apps)', id: 'secubox-admin' },
- { name: 'System Hub', version: '0.5.1-r2', note: 'Centre de contrôle', id: 'system-hub' },
- { name: 'Traffic Shaper', version: '0.4.0-r1', note: 'CAKE / fq_codel / HTB', id: 'traffic-shaper' },
- { name: 'CrowdSec Dashboard', version: '0.5.0-r1', note: 'Détection d\'intrusions', id: 'crowdsec' },
- { name: 'Netdata Dashboard', version: '0.5.0-r1', note: 'Monitoring temps réel', id: 'netdata' },
- { name: 'Netifyd Dashboard', version: '0.4.0-r1', note: 'Intelligence applicative', id: 'netifyd' },
- { name: 'Network Modes', version: '0.5.0-r1', note: '5 topologies réseau', id: 'network-modes' },
- { name: 'WireGuard Dashboard', version: '0.4.0-r1', note: 'VPN + QR codes', id: 'wireguard' },
- { name: 'Auth Guardian', version: '0.4.0-r1', note: 'OAuth / vouchers', id: 'auth-guardian' },
- { name: 'Client Guardian', version: '0.4.0-r1', note: 'Portail captif + contrôle d\'accès', id: 'client-guardian' },
- { name: 'Bandwidth Manager', version: '0.4.0-r1', note: 'QoS + quotas', id: 'bandwidth-manager' },
- { name: 'Media Flow', version: '0.4.0-r1', note: 'DPI streaming', id: 'media-flow' },
- { name: 'CDN Cache', version: '0.5.0-r1', note: 'Cache contenu local', id: 'cdn-cache' },
- { name: 'VHost Manager', version: '0.4.1-r3', note: 'Reverse proxy / SSL', id: 'vhost-manager' },
- { name: 'KSM Manager', version: '0.4.0-r1', note: 'Gestion clés / HSM', id: 'ksm-manager' }
- ],
+ // === NETWORK ===
+ 'reverse-proxy': {
+ id: 'reverse-proxy',
+ name: 'Reverse Proxy & SSL',
+ layer: 'core',
+ category: 'network',
+ status: 'production',
+ progress: 95,
+ icon: '🔀',
+ description: 'HAProxy SNI routing, ACME SSL, 226 vhosts',
+ components: ['haproxy', 'acme', 'vhost-manager'],
+ dependsOn: ['dns-master'],
+ usedBy: ['waf', 'service-exposure'],
+ stats: { vhosts: 226, certificates: 92 }
+ },
+ 'dns-master': {
+ id: 'dns-master',
+ name: 'DNS Master',
+ layer: 'core',
+ category: 'network',
+ status: 'production',
+ progress: 90,
+ icon: '🌍',
+ description: 'BIND9 authoritative, zone management, 7 zones',
+ components: ['bind9', 'dns-provider', 'zone-editor'],
+ dependsOn: [],
+ usedBy: ['reverse-proxy', 'dns-firewall', 'mesh-network'],
+ stats: { zones: 7, records: 78 }
+ },
+ 'vpn-mesh': {
+ id: 'vpn-mesh',
+ name: 'VPN & Mesh',
+ layer: 'core',
+ category: 'network',
+ status: 'production',
+ progress: 85,
+ icon: '🔒',
+ description: 'WireGuard tunnels, QR codes, mesh topology',
+ components: ['wireguard', 'mesh-discovery', 'qr-generator'],
+ dependsOn: ['network-stack'],
+ usedBy: ['mirrornet-p2p', 'master-link']
+ },
+ 'bandwidth-qos': {
+ id: 'bandwidth-qos',
+ name: 'Bandwidth & QoS',
+ layer: 'core',
+ category: 'network',
+ status: 'production',
+ progress: 85,
+ icon: '📊',
+ description: 'SQM/CAKE, quotas, traffic shaping',
+ components: ['bandwidth-manager', 'traffic-shaper', 'sqm'],
+ dependsOn: ['network-stack'],
+ usedBy: ['media-services']
+ },
- // Overall project statistics (as of v0.19.0 - 2026-03-08)
- stats: {
- get modulesCount() { return DevStatusWidget.moduleStatus.length || 40; },
- languagesSupported: 12,
- architectures: 13,
- linesOfCode: 52000,
- contributors: 6,
- commits: 1650,
- openIssues: 3,
- closedIssues: 195
+ // === SERVICES ===
+ 'container-platform': {
+ id: 'container-platform',
+ name: 'Container Platform',
+ layer: 'core',
+ category: 'services',
+ status: 'production',
+ progress: 95,
+ icon: '📦',
+ description: 'LXC containers, 18 running, auto-start',
+ components: ['lxc-manager', 'container-networking', 'resource-limits'],
+ dependsOn: ['network-stack'],
+ usedBy: ['media-services', 'communication', 'cloud-services'],
+ stats: { running: 18, total: 25 }
+ },
+ 'media-services': {
+ id: 'media-services',
+ name: 'Media Services',
+ layer: 'core',
+ category: 'services',
+ status: 'production',
+ progress: 90,
+ icon: '🎬',
+ description: 'Streaming, photos, musique',
+ components: ['jellyfin', 'photoprism', 'lyrion', 'peertube'],
+ dependsOn: ['container-platform', 'reverse-proxy'],
+ usedBy: []
+ },
+ 'communication': {
+ id: 'communication',
+ name: 'Communication',
+ layer: 'core',
+ category: 'services',
+ status: 'production',
+ progress: 85,
+ icon: '💬',
+ description: 'Chat, video, federation',
+ components: ['matrix', 'jitsi', 'jabber', 'gotosocial', 'simplex'],
+ dependsOn: ['container-platform', 'reverse-proxy'],
+ usedBy: []
+ },
+ 'cloud-services': {
+ id: 'cloud-services',
+ name: 'Cloud Services',
+ layer: 'core',
+ category: 'services',
+ status: 'production',
+ progress: 85,
+ icon: '☁️',
+ description: 'Files, email, git',
+ components: ['nextcloud', 'mailserver', 'gitea'],
+ dependsOn: ['container-platform', 'reverse-proxy'],
+ usedBy: []
+ },
+ 'remote-access': {
+ id: 'remote-access',
+ name: 'Remote Access',
+ layer: 'core',
+ category: 'services',
+ status: 'production',
+ progress: 80,
+ icon: '🖥️',
+ description: 'Terminal web, RDP, TURN/STUN',
+ components: ['rtty-remote', 'turn-server', 'rustdesk'],
+ dependsOn: ['reverse-proxy'],
+ usedBy: ['master-link']
+ },
+
+ // === AI ===
+ 'ai-inference': {
+ id: 'ai-inference',
+ name: 'AI Inference',
+ layer: 'ai',
+ category: 'ai',
+ status: 'production',
+ progress: 80,
+ icon: '🧠',
+ description: 'LocalAI, Ollama, embeddings, completions',
+ components: ['localai', 'ollama', 'model-manager'],
+ dependsOn: ['container-platform'],
+ usedBy: ['ai-security', 'ai-agents']
+ },
+ 'ai-security': {
+ id: 'ai-security',
+ name: 'AI Security Agents',
+ layer: 'ai',
+ category: 'ai',
+ status: 'beta',
+ progress: 50,
+ icon: '🤖',
+ description: 'Threat analysis, DNS anomaly, network behavior',
+ components: ['threat-analyst', 'dns-guard-ai', 'network-anomaly'],
+ dependsOn: ['ai-inference', 'intrusion-prevention'],
+ usedBy: []
+ },
+ 'ai-memory': {
+ id: 'ai-memory',
+ name: 'AI Memory & Context',
+ layer: 'ai',
+ category: 'ai',
+ status: 'alpha',
+ progress: 30,
+ icon: '💾',
+ description: 'LocalRecall, RAG, conversation history',
+ components: ['localrecall', 'mcp-server', 'embedding-store'],
+ dependsOn: ['ai-inference'],
+ usedBy: ['ai-security']
+ },
+
+ // === MIRRORNET ===
+ 'mesh-network': {
+ id: 'mesh-network',
+ name: 'Mesh Network',
+ layer: 'mirrornet',
+ category: 'p2p',
+ status: 'beta',
+ progress: 50,
+ icon: '🕸️',
+ description: 'P2P mesh, gossip protocol, service discovery',
+ components: ['p2p-core', 'gossip', 'mesh-dns'],
+ dependsOn: ['vpn-mesh', 'dns-master'],
+ usedBy: ['p2p-intel', 'service-mirroring']
+ },
+ 'identity-trust': {
+ id: 'identity-trust',
+ name: 'Identity & Trust',
+ layer: 'mirrornet',
+ category: 'p2p',
+ status: 'alpha',
+ progress: 30,
+ icon: '🪪',
+ description: 'DID identity, reputation, trust hierarchy',
+ components: ['identity-did', 'reputation', 'master-link'],
+ dependsOn: ['mesh-network'],
+ usedBy: ['p2p-intel']
+ },
+ 'p2p-intel': {
+ id: 'p2p-intel',
+ name: 'P2P Intelligence',
+ layer: 'mirrornet',
+ category: 'p2p',
+ status: 'alpha',
+ progress: 25,
+ icon: '🔍',
+ description: 'IOC sharing, signed alerts, collective defense',
+ components: ['p2p-intel-core', 'ioc-signatures', 'alert-propagation'],
+ dependsOn: ['identity-trust', 'intrusion-prevention'],
+ usedBy: []
+ },
+ 'service-exposure': {
+ id: 'service-exposure',
+ name: 'Service Exposure',
+ layer: 'core',
+ category: 'exposure',
+ status: 'production',
+ progress: 80,
+ icon: '🚀',
+ description: 'Peek/Poke/Emancipate, multi-channel exposure',
+ components: ['exposure-engine', 'tor-hidden', 'dns-ssl', 'mesh-publish'],
+ dependsOn: ['reverse-proxy', 'dns-master'],
+ usedBy: ['mesh-network']
+ },
+
+ // === MONITORING ===
+ 'system-monitoring': {
+ id: 'system-monitoring',
+ name: 'System Monitoring',
+ layer: 'core',
+ category: 'monitoring',
+ status: 'production',
+ progress: 90,
+ icon: '📈',
+ description: 'Glances, Netdata, system health',
+ components: ['glances', 'netdata', 'health-checks'],
+ dependsOn: [],
+ usedBy: ['ai-security']
+ },
+ 'network-analytics': {
+ id: 'network-analytics',
+ name: 'Network Analytics',
+ layer: 'core',
+ category: 'monitoring',
+ status: 'production',
+ progress: 85,
+ icon: '🔬',
+ description: 'DPI, flow analysis, application detection',
+ components: ['netifyd', 'ndpid', 'flow-analyzer'],
+ dependsOn: ['network-stack'],
+ usedBy: ['ai-security', 'bandwidth-qos']
+ },
+ 'session-analytics': {
+ id: 'session-analytics',
+ name: 'Session Analytics',
+ layer: 'core',
+ category: 'monitoring',
+ status: 'production',
+ progress: 85,
+ icon: '👁️',
+ description: 'Avatar-Tap recording, cookie tracking, replay',
+ components: ['avatar-tap', 'cookie-tracker', 'session-replay'],
+ dependsOn: ['waf'],
+ usedBy: ['threat-intelligence']
+ },
+ 'threat-intelligence': {
+ id: 'threat-intelligence',
+ name: 'Threat Intelligence',
+ layer: 'core',
+ category: 'monitoring',
+ status: 'production',
+ progress: 80,
+ icon: '🎯',
+ description: 'CVE triage, cyberfeed, device intel',
+ components: ['cve-triage', 'cyberfeed', 'device-intel'],
+ dependsOn: ['intrusion-prevention'],
+ usedBy: ['ai-security']
+ },
+
+ // === ADMIN ===
+ 'config-management': {
+ id: 'config-management',
+ name: 'Configuration Management',
+ layer: 'certification',
+ category: 'admin',
+ status: 'beta',
+ progress: 60,
+ icon: '⚙️',
+ description: 'Backup, restore, config advisor, cloning',
+ components: ['backup', 'config-advisor', 'cloner'],
+ dependsOn: [],
+ usedBy: []
+ },
+ 'compliance': {
+ id: 'compliance',
+ name: 'Compliance & Audit',
+ layer: 'certification',
+ category: 'admin',
+ status: 'alpha',
+ progress: 20,
+ icon: '📋',
+ description: 'ANSSI CSPN, CRA, security audit',
+ components: ['anssi-checker', 'sbom-generator', 'audit-log'],
+ dependsOn: ['config-management'],
+ usedBy: []
+ }
},
- // Timeline data
- timeline: [
+ // ============================================================
+ // COMPONENTS: Building blocks
+ // ============================================================
+ components: {
+ // Security components
+ 'crowdsec': { name: 'CrowdSec', type: 'backend', status: 'production', packages: ['secubox-app-crowdsec', 'luci-app-crowdsec-dashboard'] },
+ 'firewall-bouncer': { name: 'Firewall Bouncer', type: 'backend', status: 'production', packages: ['secubox-app-cs-firewall-bouncer'] },
+ 'wazuh': { name: 'Wazuh SIEM', type: 'backend', status: 'production', packages: ['secubox-app-wazuh', 'luci-app-wazuh'] },
+ 'mitmproxy': { name: 'Mitmproxy WAF', type: 'backend', status: 'production', packages: ['secubox-app-mitmproxy', 'luci-app-mitmproxy'] },
+ 'haproxy-router': { name: 'HAProxy Router', type: 'addon', status: 'production', packages: [] },
+ 'analytics': { name: 'SecuBox Analytics', type: 'addon', status: 'production', packages: [] },
+ 'vortex-dns': { name: 'Vortex DNS', type: 'backend', status: 'production', packages: ['secubox-vortex-dns', 'luci-app-vortex-dns'] },
+ 'vortex-firewall': { name: 'Vortex Firewall', type: 'backend', status: 'production', packages: ['secubox-vortex-firewall', 'luci-app-vortex-firewall'] },
+ 'rpz-zones': { name: 'RPZ Zones', type: 'config', status: 'production', packages: [] },
+ 'auth-guardian': { name: 'Auth Guardian', type: 'luci', status: 'production', packages: ['luci-app-auth-guardian'] },
+ 'client-guardian': { name: 'Client Guardian', type: 'luci', status: 'production', packages: ['luci-app-client-guardian'] },
+ 'nodogsplash': { name: 'Nodogsplash', type: 'backend', status: 'production', packages: ['secubox-app-nodogsplash'] },
+ 'mac-guardian': { name: 'MAC Guardian', type: 'backend', status: 'production', packages: ['secubox-app-mac-guardian', 'luci-app-mac-guardian'] },
+
+ // Network components
+ 'haproxy': { name: 'HAProxy', type: 'backend', status: 'production', packages: ['secubox-app-haproxy', 'luci-app-haproxy'] },
+ 'acme': { name: 'ACME SSL', type: 'backend', status: 'production', packages: [] },
+ 'vhost-manager': { name: 'VHost Manager', type: 'luci', status: 'production', packages: ['secubox-app-vhost-manager', 'luci-app-vhost-manager'] },
+ 'bind9': { name: 'BIND9', type: 'backend', status: 'production', packages: ['secubox-app-dns-master', 'luci-app-dns-master'] },
+ 'dns-provider': { name: 'DNS Provider API', type: 'backend', status: 'beta', packages: ['secubox-app-dns-provider', 'luci-app-dns-provider'] },
+ 'zone-editor': { name: 'Zone Editor', type: 'luci', status: 'production', packages: [] },
+ 'wireguard': { name: 'WireGuard', type: 'backend', status: 'production', packages: ['luci-app-wireguard-dashboard'] },
+ 'mesh-discovery': { name: 'Mesh Discovery', type: 'backend', status: 'beta', packages: ['secubox-app-meshname-dns'] },
+ 'qr-generator': { name: 'QR Generator', type: 'addon', status: 'production', packages: [] },
+ 'bandwidth-manager': { name: 'Bandwidth Manager', type: 'luci', status: 'production', packages: ['luci-app-bandwidth-manager'] },
+ 'traffic-shaper': { name: 'Traffic Shaper', type: 'luci', status: 'production', packages: ['luci-app-traffic-shaper'] },
+ 'sqm': { name: 'SQM/CAKE', type: 'backend', status: 'production', packages: [] },
+
+ // Service components
+ 'lxc-manager': { name: 'LXC Manager', type: 'luci', status: 'production', packages: ['luci-app-vm'] },
+ 'container-networking': { name: 'Container Networking', type: 'backend', status: 'production', packages: [] },
+ 'resource-limits': { name: 'Resource Limits', type: 'config', status: 'production', packages: [] },
+ 'jellyfin': { name: 'Jellyfin', type: 'backend', status: 'production', packages: ['secubox-app-jellyfin', 'luci-app-jellyfin'] },
+ 'photoprism': { name: 'PhotoPrism', type: 'backend', status: 'production', packages: ['secubox-app-photoprism', 'luci-app-photoprism'] },
+ 'lyrion': { name: 'Lyrion Music', type: 'backend', status: 'production', packages: ['secubox-app-lyrion', 'luci-app-lyrion'] },
+ 'peertube': { name: 'PeerTube', type: 'backend', status: 'beta', packages: ['secubox-app-peertube', 'luci-app-peertube'] },
+ 'matrix': { name: 'Matrix', type: 'backend', status: 'production', packages: ['secubox-app-matrix', 'luci-app-matrix'] },
+ 'jitsi': { name: 'Jitsi', type: 'backend', status: 'production', packages: ['secubox-app-jitsi', 'luci-app-jitsi'] },
+ 'jabber': { name: 'Prosody XMPP', type: 'backend', status: 'production', packages: ['secubox-app-jabber', 'luci-app-jabber'] },
+ 'gotosocial': { name: 'GoToSocial', type: 'backend', status: 'production', packages: ['secubox-app-gotosocial', 'luci-app-gotosocial'] },
+ 'simplex': { name: 'SimpleX', type: 'backend', status: 'beta', packages: ['secubox-app-simplex', 'luci-app-simplex'] },
+ 'nextcloud': { name: 'Nextcloud', type: 'backend', status: 'production', packages: ['secubox-app-nextcloud', 'luci-app-nextcloud'] },
+ 'mailserver': { name: 'Mail Server', type: 'backend', status: 'production', packages: ['secubox-app-mailserver', 'luci-app-mailserver'] },
+ 'gitea': { name: 'Gitea', type: 'backend', status: 'production', packages: ['secubox-app-gitea', 'luci-app-gitea'] },
+ 'rtty-remote': { name: 'RTTY Remote', type: 'backend', status: 'production', packages: ['secubox-app-rtty-remote', 'luci-app-rtty-remote'] },
+ 'turn-server': { name: 'TURN Server', type: 'backend', status: 'production', packages: ['secubox-app-turn', 'luci-app-turn'] },
+ 'rustdesk': { name: 'RustDesk', type: 'backend', status: 'beta', packages: ['secubox-app-rustdesk'] },
+
+ // AI components
+ 'localai': { name: 'LocalAI', type: 'backend', status: 'production', packages: ['secubox-app-localai', 'luci-app-localai'] },
+ 'ollama': { name: 'Ollama', type: 'backend', status: 'beta', packages: ['secubox-app-ollama', 'luci-app-ollama'] },
+ 'model-manager': { name: 'Model Manager', type: 'luci', status: 'beta', packages: ['luci-app-ai-gateway'] },
+ 'threat-analyst': { name: 'Threat Analyst', type: 'backend', status: 'beta', packages: ['secubox-threat-analyst', 'luci-app-threat-analyst'] },
+ 'dns-guard-ai': { name: 'DNS Guard AI', type: 'backend', status: 'beta', packages: ['secubox-dns-guard', 'luci-app-dnsguard'] },
+ 'network-anomaly': { name: 'Network Anomaly', type: 'backend', status: 'beta', packages: ['secubox-network-anomaly', 'luci-app-network-anomaly'] },
+ 'localrecall': { name: 'LocalRecall', type: 'backend', status: 'alpha', packages: ['secubox-localrecall', 'luci-app-localrecall'] },
+ 'mcp-server': { name: 'MCP Server', type: 'backend', status: 'beta', packages: ['secubox-mcp-server'] },
+ 'embedding-store': { name: 'Embedding Store', type: 'backend', status: 'alpha', packages: [] },
+
+ // P2P components
+ 'p2p-core': { name: 'P2P Core', type: 'backend', status: 'beta', packages: ['secubox-p2p', 'luci-app-secubox-p2p'] },
+ 'gossip': { name: 'Gossip Protocol', type: 'backend', status: 'beta', packages: [] },
+ 'mesh-dns': { name: 'Mesh DNS', type: 'backend', status: 'beta', packages: ['secubox-app-meshname-dns', 'luci-app-meshname-dns'] },
+ 'identity-did': { name: 'Identity DID', type: 'backend', status: 'alpha', packages: ['secubox-identity'] },
+ 'reputation': { name: 'Reputation System', type: 'backend', status: 'alpha', packages: [] },
+ 'master-link': { name: 'Master Link', type: 'backend', status: 'production', packages: ['secubox-master-link', 'luci-app-master-link'] },
+ 'p2p-intel-core': { name: 'P2P Intel Core', type: 'backend', status: 'alpha', packages: ['secubox-p2p-intel'] },
+ 'ioc-signatures': { name: 'IOC Signatures', type: 'backend', status: 'alpha', packages: [] },
+ 'alert-propagation': { name: 'Alert Propagation', type: 'backend', status: 'alpha', packages: [] },
+ 'exposure-engine': { name: 'Exposure Engine', type: 'backend', status: 'production', packages: ['secubox-app-exposure', 'luci-app-exposure'] },
+ 'tor-hidden': { name: 'Tor Hidden Services', type: 'backend', status: 'production', packages: ['secubox-app-tor', 'luci-app-tor-shield'] },
+ 'dns-ssl': { name: 'DNS/SSL Channel', type: 'backend', status: 'production', packages: [] },
+ 'mesh-publish': { name: 'Mesh Publish', type: 'backend', status: 'beta', packages: [] },
+
+ // Monitoring components
+ 'glances': { name: 'Glances', type: 'backend', status: 'production', packages: ['secubox-app-glances', 'luci-app-glances'] },
+ 'netdata': { name: 'Netdata', type: 'backend', status: 'production', packages: ['luci-app-netdata-dashboard'] },
+ 'health-checks': { name: 'Health Checks', type: 'backend', status: 'production', packages: [] },
+ 'netifyd': { name: 'Netifyd', type: 'backend', status: 'production', packages: ['secubox-app-netifyd', 'luci-app-secubox-netifyd'] },
+ 'ndpid': { name: 'nDPId', type: 'backend', status: 'production', packages: ['secubox-app-ndpid', 'luci-app-ndpid'] },
+ 'flow-analyzer': { name: 'Flow Analyzer', type: 'backend', status: 'production', packages: [] },
+ 'avatar-tap': { name: 'Avatar-Tap', type: 'backend', status: 'production', packages: ['secubox-avatar-tap', 'luci-app-avatar-tap'] },
+ 'cookie-tracker': { name: 'Cookie Tracker', type: 'backend', status: 'production', packages: ['secubox-cookie-tracker', 'luci-app-cookie-tracker'] },
+ 'session-replay': { name: 'Session Replay', type: 'luci', status: 'production', packages: [] },
+ 'cve-triage': { name: 'CVE Triage', type: 'backend', status: 'beta', packages: ['secubox-cve-triage', 'luci-app-cve-triage'] },
+ 'cyberfeed': { name: 'CyberFeed', type: 'backend', status: 'production', packages: ['secubox-app-cyberfeed', 'luci-app-cyberfeed'] },
+ 'device-intel': { name: 'Device Intel', type: 'backend', status: 'production', packages: ['secubox-app-device-intel', 'luci-app-device-intel'] },
+
+ // Admin components
+ 'backup': { name: 'Backup', type: 'backend', status: 'production', packages: ['secubox-app-backup', 'luci-app-backup'] },
+ 'config-advisor': { name: 'Config Advisor', type: 'backend', status: 'beta', packages: ['secubox-config-advisor', 'luci-app-config-advisor'] },
+ 'cloner': { name: 'Station Cloner', type: 'luci', status: 'alpha', packages: ['luci-app-cloner'] },
+ 'anssi-checker': { name: 'ANSSI Checker', type: 'backend', status: 'alpha', packages: [] },
+ 'sbom-generator': { name: 'SBOM Generator', type: 'backend', status: 'planned', packages: [] },
+ 'audit-log': { name: 'Audit Log', type: 'backend', status: 'beta', packages: ['secubox-app-auth-logger'] },
+
+ // Virtual/implicit
+ 'network-stack': { name: 'Network Stack', type: 'system', status: 'production', packages: [] }
+ },
+
+ // ============================================================
+ // MILESTONES: Version targets
+ // ============================================================
+ milestones: [
{
- phase: 'Phase 1',
- name: 'Core Development',
- period: 'Q4 2024 - Q1 2025',
- status: 'completed',
- progress: 100
- },
- {
- phase: 'Phase 2',
- name: 'Advanced Modules',
- period: 'Q1 - Q2 2025',
- status: 'completed',
- progress: 100
- },
- {
- phase: 'Phase 3',
- name: 'Hardware Integration',
- period: 'Q2 - Q4 2025',
- status: 'completed',
- progress: 100
- },
- {
- phase: 'Phase 4',
- name: 'Beta Testing',
- period: 'Q1 2026',
+ version: '0.19',
+ name: 'Core Stability',
+ target: '2026-03-15',
status: 'in-progress',
- progress: 65
+ progress: 85,
+ features: ['rtty-remote', 'dns-master', 'waf-memory-fix'],
+ highlights: ['RTTY Remote Web Terminal', 'DNS Master LuCI sync', 'WAF memory optimization']
},
{
- phase: 'Phase 5',
- name: 'Crowdfunding Campaign',
- period: 'Q2 2026',
+ version: '0.20',
+ name: 'AI Gateway Expansion',
+ target: '2026-03-30',
status: 'planned',
- progress: 20
+ progress: 20,
+ features: ['ai-security', 'threat-analyst', 'localrecall'],
+ highlights: ['Threat Analyst auto-rules', 'DNS Guard AI', 'LocalRecall persistence']
},
{
- phase: 'Phase 6',
- name: 'Production & Delivery',
- period: 'Q3 - Q4 2026',
+ version: '0.21',
+ name: 'MirrorNet Phase 1',
+ target: '2026-04-15',
status: 'planned',
- progress: 0
+ progress: 10,
+ features: ['identity-trust', 'p2p-intel', 'mesh-network'],
+ highlights: ['DID Identity', 'Gossip protocol', 'IOC sharing']
+ },
+ {
+ version: '0.22',
+ name: 'Station Cloning',
+ target: '2026-04-30',
+ status: 'planned',
+ progress: 5,
+ features: ['cloner', 'master-link'],
+ highlights: ['Clone image builder', 'Auto-mesh join', 'First-boot provisioning']
+ },
+ {
+ version: '1.0',
+ name: 'Certification Ready',
+ target: '2026-06-01',
+ status: 'planned',
+ progress: 0,
+ features: ['compliance'],
+ highlights: ['ANSSI CSPN', 'CRA Annex I SBOM', 'Security documentation']
}
],
- /**
- * Fetch and synchronize module versions from system
- */
- async syncModuleVersions() {
+ // ============================================================
+ // PRODUCTION STATS (defaults, updated via RPCD)
+ // ============================================================
+ stats: {
+ totalPackages: 185,
+ luciApps: 89,
+ backends: 96,
+ lxcContainers: 18,
+ haproxyVhosts: 226,
+ sslCertificates: 92,
+ dnsZones: 7,
+ dnsRecords: 78,
+ mitmproxyRoutes: 150,
+ architectures: 13,
+ commits: 1700,
+ modulesCount: 89,
+ lastLiveUpdate: null
+ },
+
+ // ============================================================
+ // DYNAMIC DATA FETCHING
+ // ============================================================
+
+ loadFiltersFromStorage: function() {
try {
- // Try to fetch from secubox via ubus
- const appsData = await L.resolveDefault(
- L.Request.post(L.env.ubus_rpc_session ? '/ubus/' : '/ubus', {
- 'jsonrpc': '2.0',
- 'id': 1,
- 'method': 'call',
- 'params': [
- L.env.ubus_rpc_session,
- 'luci.secubox',
- 'get_apps',
- {}
- ]
- }),
- null
- );
-
- if (!appsData || !appsData.json || !appsData.json().result || !appsData.json().result[1]) {
- console.warn('[DevStatus] API not available, using static data');
- this.moduleStatus = this.staticModuleStatus;
- return;
+ var stored = localStorage.getItem('dsw_filters');
+ if (stored) {
+ var parsed = JSON.parse(stored);
+ this.activeFilters = parsed;
}
+ } catch (e) {
+ // Ignore localStorage errors
+ }
+ },
- const result = appsData.json().result[1];
- const apps = result.apps || [];
+ saveFiltersToStorage: function() {
+ try {
+ localStorage.setItem('dsw_filters', JSON.stringify(this.activeFilters));
+ } catch (e) {
+ // Ignore localStorage errors
+ }
+ },
- // Also get modules status
- const modulesData = await L.resolveDefault(
- L.Request.post(L.env.ubus_rpc_session ? '/ubus/' : '/ubus', {
- 'jsonrpc': '2.0',
- 'id': 2,
- 'method': 'call',
- 'params': [
- L.env.ubus_rpc_session,
- 'luci.secubox',
- 'get_modules',
- {}
- ]
- }),
- null
- );
+ fetchLiveStats: function() {
+ var self = this;
- const modules = modulesData && modulesData.json() && modulesData.json().result && modulesData.json().result[1] ?
- modulesData.json().result[1].modules || {} : {};
+ // Try to fetch from system-hub RPCD (no auth required for read-only)
+ if (typeof L !== 'undefined' && L.rpc) {
+ var rpc = L.rpc.declare({
+ object: 'luci.system-hub',
+ method: 'status',
+ expect: {}
+ });
- // Map apps to module status
- this.moduleStatus = this.staticModuleStatus.map(staticModule => {
- const app = apps.find(a => a.id === staticModule.id || a.name === staticModule.name);
+ rpc().then(function(result) {
+ if (result) {
+ self.updateLiveStats(result);
+ }
+ }).catch(function() {
+ // Silently fail, use static data
+ });
+ } else {
+ // Standalone mode - try direct fetch
+ this.fetchStatsStandalone();
+ }
+ },
- let installed = false;
- let running = false;
-
- if (app && app.packages && app.packages.required && app.packages.required[0]) {
- const pkgName = app.packages.required[0];
- const moduleInfo = modules[pkgName];
- if (moduleInfo) {
- installed = moduleInfo.enabled || false;
- running = moduleInfo.running || false;
+ fetchStatsStandalone: function() {
+ var self = this;
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', '/ubus', true);
+ xhr.setRequestHeader('Content-Type', 'application/json');
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4 && xhr.status === 200) {
+ try {
+ var resp = JSON.parse(xhr.responseText);
+ if (resp && resp.result && resp.result[1]) {
+ self.updateLiveStats(resp.result[1]);
}
+ } catch (e) {
+ // Ignore parse errors
}
-
- if (app) {
- return {
- name: staticModule.name,
- version: app.pkg_version || app.version || staticModule.version,
- note: staticModule.note,
- id: staticModule.id,
- installed: installed,
- running: running
- };
- }
-
- return staticModule;
- });
-
- console.log('[DevStatus] Module versions synchronized:', this.moduleStatus.length, 'modules');
- } catch (error) {
- console.error('[DevStatus] Failed to sync module versions:', error);
- this.moduleStatus = this.staticModuleStatus;
- }
- },
-
- /**
- * Calculate overall progress
- */
- getOverallProgress() {
- return Math.round(this.getModulesOverallProgress());
- },
-
- getModulesOverallProgress() {
- const modules = this.moduleStatus || [];
- if (!modules.length)
- return this.getMilestoneProgressValue(this.milestones['modules-core']) * 100;
- const total = modules.reduce((sum, module) => sum + this.getVersionProgress(module), 0);
- return (total / modules.length) * 100;
- },
-
- /**
- * Get current phase
- */
- getCurrentPhase() {
- return this.timeline.find(p => p.status === 'in-progress') || this.timeline[0];
- },
-
- /**
- * Render the widget
- */
- async render(containerId) {
- const container = document.getElementById(containerId);
- if (!container) {
- console.error(`Container #${containerId} not found`);
- return;
- }
-
- // Show loading state
- container.innerHTML = `
-
- `;
-
- // Fetch and sync module versions
- await this.syncModuleVersions();
-
- // Render with fresh data
- const overallProgress = this.getModulesOverallProgress();
- const currentPhase = this.getCurrentPhase();
-
- container.innerHTML = `
-
- ${this.renderHeader(overallProgress, currentPhase)}
- ${this.renderMilestones()}
- ${this.renderTimeline()}
- ${this.renderModuleStatus()}
- ${this.renderStats()}
-
- `;
-
- this.addStyles();
- this.animateProgressBars();
- },
-
- /**
- * Render header section
- */
- renderHeader(progress, phase) {
- const displayProgress = Number(progress || 0).toFixed(2);
- return `
-
- `;
- },
-
- /**
- * Render milestones section
- */
- renderMilestones() {
- const milestonesHtml = Object.entries(this.milestones).map(([key, milestone]) => {
- const itemsHtml = milestone.items.map(item => {
- const moduleInfo = this.getModuleInfo(item.name);
- const progressValue = this.getItemProgress(item);
- const progressPercent = Math.round(progressValue * 100);
- const progressLabel = moduleInfo ? this.formatVersionProgress(moduleInfo) : (item.status === 'completed' ? '1.00 / 1.00' : (item.status === 'in-progress' ? '0.50 / 1.00' : '0.00 / 1.00'));
- return `
-
-
${this.getStatusIcon(item.status)}
-
${item.name}
- ${progressValue >= 0 ? `
-
- ` : ''}
-
- `;
- }).join('');
-
- return `
-
-
-
-
-
${this.getMilestoneProgressFraction(milestone)}
-
-
-
- ${itemsHtml}
-
-
- `;
- }).join('');
-
- return `
-
-
Development Milestones
-
- ${milestonesHtml}
-
-
- `;
- },
-
- /**
- * Render timeline section
- */
- renderTimeline() {
- const timelineHtml = this.timeline.map((phase, index) => `
-
-
-
- ${index < this.timeline.length - 1 ? '
' : ''}
-
-
-
- `).join('');
-
- return `
-
-
Project Timeline
-
- ${timelineHtml}
-
-
- `;
- },
-
- /**
- * Render per-module status grid
- */
- renderModuleStatus() {
- const modulesWithProgress = [...this.moduleStatus].sort((a, b) => this.getVersionProgress(b) - this.getVersionProgress(a));
- const modulesHtml = modulesWithProgress.map(module => {
- const status = this.getModuleStatus(module);
- const progressPercent = Math.round(this.getVersionProgress(module) * 100);
- const statusLabel = status === 'completed'
- ? `Prêt pour v${this.targetVersion}`
- : `Progression vers v${this.targetVersion}`;
-
- // Runtime status indicators
- const runtimeStatus = module.running ? 'running' : (module.installed ? 'stopped' : 'not-installed');
- const runtimeIcon = module.running ? '🟢' : (module.installed ? '🟠' : '⚫');
- const runtimeLabel = module.running ? 'Running' : (module.installed ? 'Installed' : 'Not Installed');
-
- return `
-
-
-
- ${status === 'completed' ? '✅' : '🔄'}
- ${statusLabel}
-
-
-
-
${this.formatVersionProgress(module)}
-
-
Objectif : v${this.targetVersion}
-
${module.note}
-
- `;
- }).join('');
-
- return `
-
-
Modules & Versions
-
- ${modulesHtml}
-
-
- `;
- },
-
- /**
- * Render statistics section
- */
- renderStats() {
- return `
-
-
Project Statistics
-
-
-
${this.stats.modulesCount}
-
Modules
-
-
-
${this.stats.languagesSupported}
-
Languages
-
-
-
${this.stats.architectures}
-
Architectures
-
-
-
${(this.stats.linesOfCode / 1000).toFixed(1)}k
-
Lines of Code
-
-
-
${this.stats.contributors}
-
Contributors
-
-
-
${this.stats.commits}
-
Commits
-
-
-
${this.stats.openIssues}
-
Open Issues
-
-
-
${this.stats.closedIssues}
-
Closed Issues
-
-
-
- `;
- },
-
- /**
- * Get status icon
- */
- getStatusIcon(status) {
- const icons = {
- 'completed': '✅',
- 'in-progress': '🔄',
- 'planned': '📋'
+ }
};
- return icons[status] || '⚪';
+ xhr.send(JSON.stringify({
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'call',
+ params: ['00000000000000000000000000000000', 'luci.system-hub', 'status', {}]
+ }));
},
- /**
- * Get milestone completion text (completed/total)
- */
- getMilestoneCompletion(milestone) {
- const total = milestone.items.length || milestone.total || 0;
- if (!total)
- return '0/0';
- const completed = milestone.items.filter(item => this.getItemProgress(item) >= 0.999).length;
- return `${completed}/${total}`;
+ updateLiveStats: function(data) {
+ if (data.service_count) {
+ this.stats.backends = data.service_count;
+ }
+ this.stats.lastLiveUpdate = new Date().toISOString();
+
+ // Update display if rendered
+ var statsContainer = document.querySelector('.dsw-stats-grid');
+ if (statsContainer) {
+ this.updateStatsDisplay();
+ }
},
- /**
- * Calculate milestone progress percentage from items
- */
- getMilestonePercentage(milestone) {
- return Math.round(this.getMilestoneProgressValue(milestone) * 100);
+ updateStatsDisplay: function() {
+ var liveIndicator = document.querySelector('.dsw-live-indicator');
+ if (liveIndicator && this.stats.lastLiveUpdate) {
+ liveIndicator.classList.add('dsw-live-active');
+ liveIndicator.title = 'Last update: ' + this.stats.lastLiveUpdate;
+ }
},
- /**
- * Get milestone progress value (0-1)
- */
- getMilestoneProgressValue(milestone) {
- const items = milestone.items || [];
- if (!items.length)
- return 0;
- const sum = items.reduce((acc, item) => acc + this.getItemProgress(item), 0);
- return Math.min(1, sum / items.length);
+ startAutoRefresh: function() {
+ var self = this;
+ if (this.refreshInterval) {
+ clearInterval(this.refreshInterval);
+ }
+ this.refreshInterval = setInterval(function() {
+ self.fetchLiveStats();
+ }, this.refreshSeconds * 1000);
},
- /**
- * Return X.Y / 1 fractional representation of progress towards target version
- */
- getMilestoneProgressFraction(milestone) {
- const fraction = this.getMilestoneProgressValue(milestone);
- return `${fraction.toFixed(2)} / 1`;
+ stopAutoRefresh: function() {
+ if (this.refreshInterval) {
+ clearInterval(this.refreshInterval);
+ this.refreshInterval = null;
+ }
},
- /**
- * Format version with leading v
- */
- formatVersion(version) {
- if (!version)
- return '';
- return version.startsWith('v') ? version : `v${version}`;
- },
+ // ============================================================
+ // RENDER METHODS
+ // ============================================================
- versionToNumber(version) {
- if (!version)
- return 0;
- const parts = version.toString().replace(/^v/, '').split('.');
- const major = parseInt(parts[0], 10) || 0;
- const minor = parseInt(parts[1], 10) || 0;
- const patch = parseInt(parts[2], 10) || 0;
- return major + (minor / 10) + (patch / 100);
- },
+ render: function(containerId) {
+ var self = this;
+ var container = document.getElementById(containerId);
+ if (!container) return;
- /**
- * Compare semantic versions (returns positive if v1 >= v2)
- */
- compareVersions(v1, v2) {
- const diff = this.versionToNumber(v1) - this.versionToNumber(v2);
- if (diff > 0)
- return 1;
- if (diff < 0)
- return -1;
- return 0;
- },
+ // Load saved filters
+ this.loadFiltersFromStorage();
- /**
- * Determine module status versus the target version
- */
- getModuleStatus(module) {
- return this.compareVersions(module.version, this.targetVersion) >= 0 ? 'completed' : 'in-progress';
- },
+ container.innerHTML = this.renderLoading();
- getVersionProgress(module) {
- const current = this.versionToNumber(module.version);
- const target = this.versionToNumber(this.targetVersion);
- if (!target)
- return 0;
- return Math.min(1, current / target);
- },
+ // Small delay for smooth loading animation
+ setTimeout(function() {
+ container.innerHTML = [
+ '',
+ self.renderStyles(),
+ self.renderHeader(),
+ self.renderControls(),
+ self.renderLayersOverview(),
+ self.renderFeaturesGrid(),
+ self.renderDependencyGraph(),
+ self.renderMilestones(),
+ self.renderStats(),
+ '
'
+ ].join('');
- formatVersionProgress(module) {
- return `${this.getVersionProgress(module).toFixed(2)} / 1.00`;
- },
-
- getModuleInfo(name) {
- return this.moduleStatus.find(module => module.name === name);
- },
-
- getItemProgress(item) {
- const module = this.getModuleInfo(item.name);
- if (module)
- return this.getVersionProgress(module);
- if (item.status === 'completed') return 1;
- if (item.status === 'in-progress') return 0.5;
- return 0;
- },
-
- /**
- * Animate progress bars
- */
- animateProgressBars() {
- setTimeout(() => {
- document.querySelectorAll('[data-progress]').forEach(element => {
- const progress = element.getAttribute('data-progress');
- if (element.classList.contains('dsw-progress-bar-fill')) {
- element.style.width = `${progress}%`;
- } else if (element.classList.contains('dsw-timeline-progress-fill')) {
- element.style.width = `${progress}%`;
- } else if (element.classList.contains('dsw-milestone-mini-fill')) {
- element.style.width = `${progress}%`;
- } else if (element.classList.contains('dsw-module-progress-fill')) {
- element.style.width = `${progress}%`;
- }
- });
+ self.initInteractions();
+ self.fetchLiveStats();
+ self.startAutoRefresh();
+ self.applyFilters();
}, 100);
},
- /**
- * Add widget styles
- */
- addStyles() {
- if (document.getElementById('dev-status-widget-styles')) return;
-
- const style = document.createElement('style');
- style.id = 'dev-status-widget-styles';
- style.textContent = `
- .dev-status-widget {
- background: var(--sb-bg-card, #1a1a24);
- border: 1px solid var(--sb-border, #2a2a3a);
- border-radius: 20px;
- padding: 32px;
- color: var(--sb-text, #f1f5f9);
- }
-
- .dsw-loading {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- min-height: 200px;
- gap: 16px;
- }
-
- .dsw-loading .spinner {
- width: 50px;
- height: 50px;
- border: 4px solid rgba(99, 102, 241, 0.1);
- border-top-color: #6366f1;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
-
- .dsw-loading p {
- color: var(--sb-text-muted, #94a3b8);
- font-size: 14px;
- }
-
- @keyframes spin {
- to { transform: rotate(360deg); }
- }
-
- .dsw-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 40px;
- flex-wrap: wrap;
- gap: 24px;
- }
-
- .dsw-title {
- font-size: 28px;
- font-weight: 800;
- margin-bottom: 8px;
- background: linear-gradient(135deg, #10b981, #06b6d4);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- }
-
- .dsw-subtitle {
- color: var(--sb-text-muted, #94a3b8);
- font-size: 14px;
- }
-
- .dsw-overall-progress {
- display: flex;
- align-items: center;
- gap: 24px;
- }
-
- .dsw-progress-circle {
- position: relative;
- width: 120px;
- height: 120px;
- }
-
- .dsw-progress-circle svg {
- transform: rotate(-90deg);
- }
-
- .dsw-progress-bg {
- fill: none;
- stroke: var(--sb-border, #2a2a3a);
- stroke-width: 8;
- }
-
- .dsw-progress-bar {
- fill: none;
- stroke: url(#gradient);
- stroke-width: 8;
- stroke-linecap: round;
- stroke-dasharray: 339;
- stroke-dashoffset: 339;
- transition: stroke-dashoffset 1.5s ease-out;
- }
-
- .dsw-progress-value {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 28px;
- font-weight: 800;
- color: var(--sb-green, #10b981);
- }
-
- .dsw-current-phase {
- text-align: left;
- }
-
- .dsw-phase-label {
- font-size: 11px;
- text-transform: uppercase;
- color: var(--sb-text-dim, #64748b);
- font-weight: 600;
- letter-spacing: 1px;
- }
-
- .dsw-phase-name {
- font-size: 18px;
- font-weight: 700;
- margin: 4px 0;
- }
-
- .dsw-phase-period {
- font-size: 13px;
- color: var(--sb-cyan, #06b6d4);
- font-family: 'JetBrains Mono', monospace;
- }
-
- .dsw-section-title {
- font-size: 20px;
- font-weight: 700;
- margin-bottom: 20px;
- color: var(--sb-text, #f1f5f9);
- }
-
- .dsw-milestones {
- margin-bottom: 40px;
- }
-
- .dsw-milestones-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
- gap: 24px;
- }
-
- .dsw-milestone {
- background: var(--sb-bg, #0f1019);
- border: 1px solid var(--sb-border, #2a2a3a);
- border-radius: 12px;
- padding: 20px;
- transition: all 0.3s;
- }
-
- .dsw-milestone:hover {
- border-color: var(--sb-cyan, #06b6d4);
- transform: translateY(-2px);
- }
-
- .dsw-milestone-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- }
-
- .dsw-milestone-info {
- display: flex;
- align-items: center;
- gap: 10px;
- }
-
- .dsw-milestone-icon {
- font-size: 24px;
- }
-
- .dsw-milestone-name {
- font-size: 16px;
- font-weight: 700;
- }
-
- .dsw-milestone-stats {
- display: flex;
- align-items: center;
- gap: 12px;
- font-size: 14px;
- }
-
- .dsw-milestone-count {
- color: var(--sb-text-muted, #94a3b8);
- }
-
- .dsw-milestone-percent {
- font-weight: 700;
- font-family: 'JetBrains Mono', monospace;
- }
-
- .dsw-milestone-fraction {
- font-family: 'JetBrains Mono', monospace;
- font-size: 13px;
- color: var(--sb-text-muted, #94a3b8);
- }
-
- .dsw-progress-bar-container {
- height: 8px;
- background: var(--sb-bg, #0f1019);
- border-radius: 4px;
- overflow: hidden;
- margin-bottom: 16px;
- }
-
- .dsw-progress-bar-fill {
- height: 100%;
- width: 0;
- border-radius: 4px;
- transition: width 1s ease-out;
- }
-
- .dsw-milestone-mini-progress {
- margin: 8px 0 12px;
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
-
- .dsw-milestone-mini-bar {
- width: 100%;
- height: 4px;
- background: rgba(148, 163, 184, 0.2);
- border-radius: 999px;
- overflow: hidden;
- }
-
- .dsw-milestone-mini-fill {
- height: 100%;
- width: 0;
- border-radius: 999px;
- background: linear-gradient(90deg, var(--sb-green, #10b981), var(--sb-cyan, #06b6d4));
- transition: width 0.8s ease-out;
- }
-
- .dsw-milestone-mini-label {
- font-size: 12px;
- font-family: 'JetBrains Mono', monospace;
- color: var(--sb-text-muted, #94a3b8);
- text-align: right;
- }
-
- .dsw-milestone-items {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
-
- .dsw-item {
- display: flex;
- flex-direction: column;
- gap: 6px;
- padding: 8px 0;
- }
-
- .dsw-item-icon {
- font-size: 16px;
- }
-
- .dsw-item-name {
- font-size: 13px;
- font-weight: 600;
- }
-
- .dsw-item-completed .dsw-item-name {
- color: var(--sb-text-muted, #94a3b8);
- }
-
- .dsw-item-in-progress .dsw-item-name {
- color: var(--sb-orange, #f97316);
- }
-
- .dsw-item-planned .dsw-item-name {
- color: var(--sb-text-dim, #64748b);
- }
-
- .dsw-item-progress {
- width: 100%;
- }
-
- .dsw-item-progress-bar {
- width: 100%;
- height: 4px;
- background: rgba(148, 163, 184, 0.15);
- border-radius: 999px;
- overflow: hidden;
- }
-
- .dsw-item-progress-fill {
- height: 100%;
- width: 0;
- background: linear-gradient(90deg, #10b981, #06b6d4);
- border-radius: 999px;
- transition: width 0.8s ease-out;
- }
-
- .dsw-item-progress-label {
- text-align: right;
- font-size: 11px;
- font-family: 'JetBrains Mono', monospace;
- color: var(--sb-text-muted, #94a3b8);
- margin-top: 2px;
- }
-
- .dsw-timeline {
- margin-bottom: 40px;
- }
-
- .dsw-timeline-container {
- display: flex;
- flex-direction: column;
- gap: 0;
- }
-
- .dsw-timeline-item {
- display: flex;
- gap: 20px;
- }
-
- .dsw-timeline-marker {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding-top: 8px;
- }
-
- .dsw-timeline-dot {
- width: 16px;
- height: 16px;
- border-radius: 50%;
- background: var(--sb-border, #2a2a3a);
- border: 3px solid var(--sb-bg-card, #1a1a24);
- z-index: 1;
- }
-
- .dsw-timeline-completed .dsw-timeline-dot {
- background: var(--sb-green, #10b981);
- }
-
- .dsw-timeline-in-progress .dsw-timeline-dot {
- background: var(--sb-orange, #f97316);
- animation: pulse 2s infinite;
- }
-
- .dsw-timeline-line {
- width: 2px;
- flex: 1;
- background: var(--sb-border, #2a2a3a);
- margin-top: 4px;
- }
-
- .dsw-timeline-completed .dsw-timeline-line {
- background: var(--sb-green, #10b981);
- }
-
- .dsw-timeline-content {
- flex: 1;
- padding-bottom: 32px;
- }
-
- .dsw-timeline-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 4px;
- }
-
- .dsw-timeline-phase {
- font-size: 12px;
- font-weight: 700;
- color: var(--sb-cyan, #06b6d4);
- text-transform: uppercase;
- }
-
- .dsw-timeline-period {
- font-size: 12px;
- color: var(--sb-text-dim, #64748b);
- font-family: 'JetBrains Mono', monospace;
- }
-
- .dsw-timeline-name {
- font-size: 16px;
- font-weight: 600;
- margin-bottom: 12px;
- }
-
- .dsw-timeline-progress {
- display: flex;
- align-items: center;
- gap: 12px;
- }
-
- .dsw-timeline-progress-bar {
- flex: 1;
- height: 6px;
- background: var(--sb-bg, #0f1019);
- border-radius: 3px;
- overflow: hidden;
- }
-
- .dsw-timeline-progress-fill {
- height: 100%;
- width: 0;
- background: linear-gradient(90deg, #10b981, #06b6d4);
- border-radius: 3px;
- transition: width 1s ease-out;
- }
-
- .dsw-timeline-progress-text {
- font-size: 12px;
- font-weight: 600;
- color: var(--sb-text-muted, #94a3b8);
- min-width: 40px;
- text-align: right;
- }
-
- .dsw-modules-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
- gap: 16px;
- }
-
- .dsw-module-card {
- background: var(--sb-bg, #0f1019);
- border: 1px solid var(--sb-border, #2a2a3a);
- border-radius: 12px;
- padding: 18px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
-
- .dsw-module-card:hover {
- border-color: var(--sb-cyan, #06b6d4);
- transform: translateY(-2px);
- }
-
- .dsw-module-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 12px;
- font-weight: 600;
- }
-
- .dsw-module-badges {
- display: flex;
- align-items: center;
- gap: 8px;
- }
-
- .dsw-module-version {
- font-family: 'JetBrains Mono', monospace;
- font-size: 13px;
- color: var(--sb-text-muted, #94a3b8);
- }
-
- .dsw-module-runtime-badge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 20px;
- height: 20px;
- font-size: 12px;
- border-radius: 50%;
- cursor: help;
- transition: transform 0.2s;
- }
-
- .dsw-module-runtime-badge:hover {
- transform: scale(1.2);
- }
-
- .dsw-runtime-running {
- background: rgba(16, 185, 129, 0.15);
- box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.3);
- animation: pulse-green 2s ease-in-out infinite;
- }
-
- .dsw-runtime-stopped {
- background: rgba(245, 158, 11, 0.15);
- box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.3);
- }
-
- .dsw-runtime-not-installed {
- background: rgba(107, 116, 128, 0.15);
- box-shadow: 0 0 0 2px rgba(107, 116, 128, 0.2);
- }
-
- @keyframes pulse-green {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.6; }
- }
-
- .dsw-module-status-row {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 14px;
- font-weight: 600;
- }
-
- .dsw-module-status-indicator {
- font-size: 16px;
- }
-
- .dsw-module-target {
- font-size: 12px;
- color: var(--sb-text-muted, #94a3b8);
- text-transform: uppercase;
- letter-spacing: 0.5px;
- }
-
- .dsw-module-note {
- font-size: 13px;
- color: var(--sb-text-muted, #94a3b8);
- }
-
- .dsw-module-progress {
- margin-top: 4px;
- }
-
- .dsw-module-progress-bar {
- width: 100%;
- height: 6px;
- background: var(--sb-bg, #0f1019);
- border-radius: 3px;
- overflow: hidden;
- }
-
- .dsw-module-progress-fill {
- height: 100%;
- width: 0;
- background: linear-gradient(90deg, #10b981, #06b6d4);
- border-radius: 3px;
- transition: width 0.8s ease-out;
- }
-
- .dsw-module-progress-label {
- margin-top: 4px;
- font-size: 12px;
- font-family: 'JetBrains Mono', monospace;
- color: var(--sb-text-muted, #94a3b8);
- text-align: right;
- }
-
- .dsw-module-card.dsw-module-in-progress {
- border-color: rgba(245, 158, 11, 0.4);
- }
-
- .dsw-stats-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
- gap: 16px;
- }
-
- .dsw-stat {
- background: var(--sb-bg, #0f1019);
- border: 1px solid var(--sb-border, #2a2a3a);
- border-radius: 12px;
- padding: 20px;
- text-align: center;
- transition: all 0.2s;
- }
-
- .dsw-stat:hover {
- border-color: var(--sb-cyan, #06b6d4);
- transform: translateY(-2px);
- }
-
- .dsw-stat-value {
- font-size: 32px;
- font-weight: 800;
- color: var(--sb-green, #10b981);
- font-family: 'JetBrains Mono', monospace;
- margin-bottom: 4px;
- }
-
- .dsw-stat-label {
- font-size: 12px;
- color: var(--sb-text-muted, #94a3b8);
- }
-
- @keyframes pulse {
- 0%, 100% { box-shadow: 0 0 0 0 rgba(249, 115, 22, 0.7); }
- 50% { box-shadow: 0 0 0 10px rgba(249, 115, 22, 0); }
- }
-
- @media (max-width: 768px) {
- .dev-status-widget {
- padding: 20px;
+ renderLoading: function() {
+ return 'Chargement de l\'architecture...
';
+ },
+
+ renderHeader: function() {
+ var overallProgress = this.calculateOverallProgress();
+ return [
+ ''
+ ].join('');
+ },
+
+ renderControls: function() {
+ var self = this;
+ var statusOptions = ['all', 'production', 'beta', 'alpha', 'planned'];
+ var categoryOptions = ['all', 'security', 'network', 'services', 'ai', 'p2p', 'monitoring', 'exposure', 'admin'];
+
+ return [
+ '',
+ '
',
+ '',
+ '',
+ '
',
+ '
',
+ '',
+ '',
+ '
',
+ '
',
+ '',
+ '',
+ '
',
+ '
',
+ '',
+ 'Live',
+ '
',
+ '
'
+ ].join('');
+ },
+
+ renderLayersOverview: function() {
+ var self = this;
+ var sortedLayers = Object.keys(this.layers).map(function(k) { return self.layers[k]; });
+ sortedLayers.sort(function(a, b) { return a.order - b.order; });
+
+ var layersHtml = sortedLayers.map(function(layer) {
+ var featureCount = Object.keys(self.features).filter(function(k) {
+ return self.features[k].layer === layer.id;
+ }).length;
+
+ return [
+ '',
+ '
', layer.icon, '
',
+ '
',
+ '
', layer.name, '
',
+ '
', layer.description, '
',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
', layer.progress, '%',
+ '
',
+ '
', featureCount, ' features',
+ '
',
+ '
'
+ ].join('');
+ }).join('');
+
+ return [
+ '',
+ '
🏛️ Architecture 4 Couches
',
+ '
', layersHtml, '
',
+ '
'
+ ].join('');
+ },
+
+ renderFeaturesGrid: function() {
+ var self = this;
+ var categories = {
+ security: { name: 'Security', icon: '🛡️' },
+ network: { name: 'Network', icon: '🌍' },
+ services: { name: 'Services', icon: '📦' },
+ ai: { name: 'AI', icon: '🤖' },
+ p2p: { name: 'P2P/Mesh', icon: '🕸️' },
+ monitoring: { name: 'Monitoring', icon: '📊' },
+ exposure: { name: 'Exposure', icon: '🚀' },
+ admin: { name: 'Admin', icon: '⚙️' }
+ };
+
+ var html = ['📦 Features & Components
'];
+
+ Object.keys(categories).forEach(function(catId) {
+ var cat = categories[catId];
+ var features = Object.keys(self.features).filter(function(k) {
+ return self.features[k].category === catId;
+ }).map(function(k) { return self.features[k]; });
+
+ if (!features.length) return;
+
+ html.push('
');
+ html.push('
', cat.icon, ' ', cat.name, '
');
+ html.push('
');
+
+ features.forEach(function(feature) {
+ var layer = self.layers[feature.layer];
+ var componentsCount = (feature.components || []).length;
+ var depsCount = ((feature.dependsOn || []).length) + ((feature.usedBy || []).length);
+
+ var componentTags = (feature.components || []).slice(0, 4).map(function(c) {
+ var comp = self.components[c];
+ if (!comp) return '';
+ return '
' + comp.name + '';
+ }).join('');
+
+ var moreCount = (feature.components || []).length - 4;
+ if (moreCount > 0) {
+ componentTags += '
+' + moreCount + '';
}
- .dsw-header {
- flex-direction: column;
- align-items: flex-start;
- }
+ html.push([
+ '
',
+ '',
+ '
', feature.description, '
',
+ '
',
+ '
',
+ '
', feature.progress, '%',
+ '
',
+ '
',
+ '📦 ', componentsCount, '',
+ '🔗 ', depsCount, '',
+ '', layer.icon, '',
+ '
',
+ '
', componentTags, '
',
+ '
',
+ self.renderFeatureDetails(feature),
+ '
',
+ '
'
+ ].join(''));
+ });
- .dsw-overall-progress {
- flex-direction: column;
- width: 100%;
- }
+ html.push('
');
+ });
- .dsw-milestones-grid {
- grid-template-columns: 1fr;
- }
+ html.push('
');
+ return html.join('');
+ },
- .dsw-stats-grid {
- grid-template-columns: repeat(2, 1fr);
- }
- }
- `;
+ renderFeatureDetails: function(feature) {
+ var self = this;
+ var html = [];
- document.head.appendChild(style);
-
- // Add SVG gradient
- if (!document.getElementById('dsw-gradient')) {
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- svg.setAttribute('width', '0');
- svg.setAttribute('height', '0');
- svg.innerHTML = `
-
-
-
-
-
-
- `;
- svg.id = 'dsw-gradient';
- document.body.appendChild(svg);
+ // Dependencies
+ if (feature.dependsOn && feature.dependsOn.length) {
+ html.push('');
+ html.push('Depends on: ');
+ html.push(feature.dependsOn.map(function(d) {
+ var dep = self.features[d];
+ return dep ? (dep.icon + ' ' + dep.name) : d;
+ }).join(', '));
+ html.push('
');
}
+
+ // Used by
+ if (feature.usedBy && feature.usedBy.length) {
+ html.push('');
+ html.push('Used by: ');
+ html.push(feature.usedBy.map(function(u) {
+ var user = self.features[u];
+ return user ? (user.icon + ' ' + user.name) : u;
+ }).join(', '));
+ html.push('
');
+ }
+
+ // All components
+ if (feature.components && feature.components.length) {
+ html.push('');
+ html.push('
All components:');
+ html.push('
');
+ feature.components.forEach(function(c) {
+ var comp = self.components[c];
+ if (comp) {
+ html.push('');
+ html.push(comp.name + ' (' + comp.type + ')');
+ html.push('');
+ }
+ });
+ html.push('
');
+ }
+
+ return html.join('');
+ },
+
+ renderDependencyGraph: function() {
+ var self = this;
+ var deps = [];
+
+ Object.keys(this.features).forEach(function(fid) {
+ var feature = self.features[fid];
+ (feature.dependsOn || []).forEach(function(dep) {
+ if (self.features[dep]) {
+ deps.push({ from: dep, to: fid, type: 'depends' });
+ }
+ });
+ (feature.usedBy || []).forEach(function(user) {
+ if (self.features[user]) {
+ deps.push({ from: fid, to: user, type: 'provides' });
+ }
+ });
+ });
+
+ // Remove duplicates
+ var seen = {};
+ deps = deps.filter(function(d) {
+ var key = d.from + '->' + d.to;
+ if (seen[key]) return false;
+ seen[key] = true;
+ return true;
+ });
+
+ var depsHtml = deps.slice(0, 20).map(function(d) {
+ var from = self.features[d.from];
+ var to = self.features[d.to];
+ return [
+ '',
+ '', (from ? from.icon : ''), ' ', (from ? from.name : d.from), '',
+ '', (d.type === 'depends' ? '→' : '←'), '',
+ '', (to ? to.icon : ''), ' ', (to ? to.name : d.to), '',
+ '
'
+ ].join('');
+ }).join('');
+
+ return [
+ '',
+ '
🔗 Interconnections
',
+ '
',
+ '
',
+ ' Dépend de',
+ ' Utilisé par',
+ '
',
+ '
', depsHtml, '
',
+ '
', deps.length, ' interconnections entre features
',
+ '
',
+ '
'
+ ].join('');
+ },
+
+ renderMilestones: function() {
+ var self = this;
+ var html = this.milestones.map(function(m, i) {
+ var isActive = m.status === 'in-progress';
+ var highlightsHtml = m.highlights.map(function(h) {
+ return '' + h + '';
+ }).join('');
+
+ return [
+ '',
+ '
',
+ '
',
+ (i < self.milestones.length - 1 ? '
' : ''),
+ '
',
+ '
',
+ '',
+ '
',
+ '
',
+ '
', m.progress, '%',
+ '
',
+ '
',
+ '
',
+ '
'
+ ].join('');
+ }).join('');
+
+ return [
+ '',
+ '
🎯 Milestones → v', this.targetVersion, '
',
+ '
', html, '
',
+ '
'
+ ].join('');
+ },
+
+ renderStats: function() {
+ var stats = [
+ { label: 'Packages', value: this.stats.totalPackages, icon: '📦' },
+ { label: 'LuCI Apps', value: this.stats.luciApps, icon: '🖥️' },
+ { label: 'LXC Running', value: this.stats.lxcContainers, icon: '📦' },
+ { label: 'HAProxy Vhosts', value: this.stats.haproxyVhosts, icon: '🔀' },
+ { label: 'SSL Certs', value: this.stats.sslCertificates, icon: '🔒' },
+ { label: 'DNS Zones', value: this.stats.dnsZones, icon: '🌍' },
+ { label: 'WAF Routes', value: this.stats.mitmproxyRoutes, icon: '🔥' },
+ { label: 'Commits', value: this.stats.commits, icon: '📝' }
+ ];
+
+ var statsHtml = stats.map(function(s) {
+ return [
+ '',
+ '', s.icon, '',
+ '', s.value, '',
+ '', s.label, '',
+ '
'
+ ].join('');
+ }).join('');
+
+ return [
+ '',
+ '
📈 Production Stats (C3BOX gk2)
',
+ '
', statsHtml, '
',
+ '
'
+ ].join('');
+ },
+
+ // ============================================================
+ // HELPERS
+ // ============================================================
+
+ calculateOverallProgress: function() {
+ var features = Object.keys(this.features).map(function(k) { return this.features[k]; }, this);
+ var total = features.reduce(function(sum, f) { return sum + f.progress; }, 0);
+ return Math.round(total / features.length);
+ },
+
+ getStatusBadge: function(status) {
+ var badges = {
+ 'production': '✅',
+ 'beta': '🔶',
+ 'alpha': '🔷',
+ 'planned': '⬜'
+ };
+ return badges[status] || '⚪';
+ },
+
+ getCurrentPhase: function() {
+ var current = this.milestones.find(function(m) { return m.status === 'in-progress'; });
+ if (!current) current = this.milestones[0];
+ return {
+ phase: 'v' + current.version,
+ name: current.name,
+ period: current.target,
+ status: current.status
+ };
+ },
+
+ getOverallProgress: function() {
+ return this.calculateOverallProgress();
+ },
+
+ applyFilters: function() {
+ var self = this;
+ var cards = document.querySelectorAll('.dsw-feature-card');
+ var statusFilter = this.activeFilters.status;
+ var categoryFilter = this.activeFilters.category;
+ var layerFilter = this.activeFilters.layer;
+
+ cards.forEach(function(card) {
+ var feature = self.features[card.dataset.feature];
+ if (!feature) return;
+
+ var visible = true;
+
+ if (statusFilter && statusFilter !== 'all' && feature.status !== statusFilter) {
+ visible = false;
+ }
+ if (categoryFilter && categoryFilter !== 'all' && feature.category !== categoryFilter) {
+ visible = false;
+ }
+ if (layerFilter && feature.layer !== layerFilter) {
+ visible = false;
+ }
+
+ card.style.opacity = visible ? '1' : '0.2';
+ card.style.pointerEvents = visible ? 'auto' : 'none';
+ });
+
+ // Update layer cards
+ document.querySelectorAll('.dsw-layer-card').forEach(function(card) {
+ card.classList.toggle('dsw-layer-active', card.dataset.layer === layerFilter);
+ });
+ },
+
+ initInteractions: function() {
+ var self = this;
+
+ // Feature card click to expand
+ document.querySelectorAll('.dsw-feature-card').forEach(function(card) {
+ card.addEventListener('click', function(e) {
+ if (e.target.tagName === 'SELECT' || e.target.tagName === 'BUTTON') return;
+ card.classList.toggle('dsw-expanded');
+ });
+ });
+
+ // Layer filter
+ document.querySelectorAll('.dsw-layer-card').forEach(function(card) {
+ card.addEventListener('click', function() {
+ var layer = card.dataset.layer;
+ if (self.activeFilters.layer === layer) {
+ self.activeFilters.layer = null;
+ } else {
+ self.activeFilters.layer = layer;
+ }
+ self.saveFiltersToStorage();
+ self.applyFilters();
+ });
+ });
+
+ // Status filter dropdown
+ var statusSelect = document.querySelector('.dsw-filter-status');
+ if (statusSelect) {
+ statusSelect.addEventListener('change', function() {
+ self.activeFilters.status = this.value === 'all' ? null : this.value;
+ self.saveFiltersToStorage();
+ self.applyFilters();
+ });
+ }
+
+ // Category filter dropdown
+ var categorySelect = document.querySelector('.dsw-filter-category');
+ if (categorySelect) {
+ categorySelect.addEventListener('change', function() {
+ self.activeFilters.category = this.value === 'all' ? null : this.value;
+ self.saveFiltersToStorage();
+ self.applyFilters();
+ });
+ }
+
+ // Clear filters button
+ var clearBtn = document.querySelector('.dsw-btn-clear');
+ if (clearBtn) {
+ clearBtn.addEventListener('click', function() {
+ self.activeFilters = { layer: null, status: null, category: null };
+ self.saveFiltersToStorage();
+
+ var statusSel = document.querySelector('.dsw-filter-status');
+ var catSel = document.querySelector('.dsw-filter-category');
+ if (statusSel) statusSel.value = 'all';
+ if (catSel) catSel.value = 'all';
+
+ self.applyFilters();
+ });
+ }
+
+ // Refresh button
+ var refreshBtn = document.querySelector('.dsw-btn-refresh');
+ if (refreshBtn) {
+ refreshBtn.addEventListener('click', function() {
+ refreshBtn.classList.add('dsw-spinning');
+ self.fetchLiveStats();
+ setTimeout(function() {
+ refreshBtn.classList.remove('dsw-spinning');
+ }, 1000);
+ });
+ }
+ },
+
+ renderStyles: function() {
+ return [
+ ''
+ ].join('\n');
}
};
-// Auto-initialize if container exists
-if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', () => {
- if (document.getElementById('dev-status-widget')) {
- DevStatusWidget.render('dev-status-widget');
- }
- });
-} else {
- if (document.getElementById('dev-status-widget')) {
- DevStatusWidget.render('dev-status-widget');
- }
+// Export for different module systems
+if (typeof window !== 'undefined') {
+ window.DevStatusWidget = DevStatusWidget;
}
-// Export for use in other scripts
-window.DevStatusWidget = DevStatusWidget;
-
-return baseclass.extend(DevStatusWidget);
+// LuCI baseclass export
+if (typeof baseclass !== 'undefined') {
+ return baseclass.extend(DevStatusWidget);
+}
diff --git a/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/dev-status.js b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/dev-status.js
index fcbfa776..dddc7b4d 100644
--- a/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/dev-status.js
+++ b/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/dev-status.js
@@ -40,13 +40,9 @@ return view.extend({
E('style', {
'type': 'text/css'
}, `
- .sh-dev-status-widget-shell .dsw-milestones,
- .sh-dev-status-widget-shell .dsw-timeline,
- .sh-dev-status-widget-shell .dsw-stats {
- display: none !important;
- }
- .sh-dev-status-widget-shell .dsw-modules {
- margin-top: -10px;
+ /* Widget v2.1 - full display */
+ .sh-dev-status-widget-shell {
+ margin-top: 16px;
}
`)
];
@@ -84,7 +80,7 @@ return view.extend({
var widget = this.getWidget();
var overallProgress = widget.getOverallProgress();
var phase = widget.getCurrentPhase();
- var milestonesCount = Object.keys(widget.milestones || {}).length;
+ var milestonesCount = (widget.milestones || []).length;
return E('div', { 'class': 'sh-stats-grid sh-dev-status-grid' }, [
E('div', { 'class': 'sh-stat-badge' }, [
@@ -96,8 +92,8 @@ return view.extend({
E('div', { 'class': 'sh-stat-label' }, 'Milestone groups')
]),
E('div', { 'class': 'sh-stat-badge' }, [
- E('div', { 'class': 'sh-stat-value' }, widget.stats.modulesCount),
- E('div', { 'class': 'sh-stat-label' }, 'Modules livrés')
+ E('div', { 'class': 'sh-stat-value' }, Object.keys(widget.features || {}).length),
+ E('div', { 'class': 'sh-stat-label' }, 'Features')
]),
E('div', { 'class': 'sh-stat-badge' }, [
E('div', { 'class': 'sh-stat-value' }, (phase.status || '').replace('-', ' ')),