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 = ` -
-
-
-

Loading development status...

-
-
- `; - - // 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 ` -
-
-

🚀 Development Status

-

Real-time project progress tracker

-
-
-
- - - - -
${displayProgress}%
-
-
-
Current Phase
-
${phase.phase}: ${phase.name}
-
${phase.period}
-
-
-
- `; - }, - - /** - * 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 ? ` -
-
-
-
-
${progressLabel}
-
- ` : ''} -
- `; - }).join(''); - - return ` -
-
-
- ${milestone.icon} - ${milestone.name} -
-
- ${this.getMilestoneCompletion(milestone)} - ${this.getMilestonePercentage(milestone)}% - ${this.getMilestoneProgressFraction(milestone)} -
-
-
-
-
-
-
${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 ? '
' : ''} -
-
-
- ${phase.phase} - ${phase.period} -
-
${phase.name}
-
-
-
-
- ${phase.progress}% -
-
-
- `).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 ` -
-
- ${module.name} -
- ${this.formatVersion(module.version)} - - ${runtimeIcon} - -
-
-
- ${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 [ + '
', + '
', + '

SecuBox Development Status

', + '

Architecture: ', this.totalPackages, ' packages · 4 couches · ', Object.keys(this.features).length, ' features

', + '
', + '
', + '
', + '', + '', + '', + '', + '', overallProgress, '%', + '
', + '
', + 'Target', + 'v', this.targetVersion, '', + '
', + '
', + '
' + ].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.icon, '', + '', feature.name, '', + '', self.getStatusBadge(feature.status), '', + '
', + '

', 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 ? '
    ' : ''), + '
    ', + '
    ', + '
    ', + 'v', m.version, '', + '', m.name, '', + '', m.target, '', + '
    ', + '
    ', + '
    ', + '
    ', + '
    ', + '', 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('-', ' ')),