diff --git a/.github/workflows/test-validate.yml b/.github/workflows/test-validate.yml index 019ec0d..1dc6a82 100644 --- a/.github/workflows/test-validate.yml +++ b/.github/workflows/test-validate.yml @@ -207,7 +207,7 @@ jobs: sudo apt-get install -y \ build-essential clang flex bison g++ gawk \ gcc-multilib g++-multilib gettext git libncurses5-dev \ - libssl-dev python3-setuptools python3-dev rsync unzip zlib1g-dev wget + libssl-dev python3-setuptools python3-dev rsync unzip zlib1g-dev wget ninja-build - name: Cache OpenWrt SDK uses: actions/cache@v4 diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-widget.js b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-widget.js index 15440b6..7731b1b 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-widget.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/dev-status-widget.js @@ -5,184 +5,215 @@ * Real-time development progress tracker */ -return baseclass.extend({ - // Development milestones and progress - milestones: { - 'modules-core': { - name: 'Core Modules', - progress: 100, - total: 13, - completed: 13, - icon: '📦', - color: '#10b981', - items: [ - { name: 'Bandwidth Manager', status: 'completed' }, - { name: 'Auth Guardian', status: 'completed' }, - { name: 'Media Flow', status: 'completed' }, - { name: 'VHost Manager', status: 'completed' }, - { name: 'CrowdSec Dashboard', status: 'completed' }, - { name: 'WireGuard Dashboard', status: 'completed' }, - { name: 'Netdata Dashboard', status: 'completed' }, - { name: 'Netifyd Dashboard', status: 'completed' }, - { name: 'Client Guardian', status: 'completed' }, - { name: 'Network Modes', status: 'completed' }, - { name: 'Traffic Shaper', status: 'completed' }, - { name: 'CDN Cache', status: 'completed' }, - { name: 'SecuBox Hub', status: 'completed' } - ] - }, - 'hardware-support': { - name: 'Hardware Support', - progress: 95, - total: 4, - completed: 3, - icon: '🔧', - color: '#f59e0b', - items: [ - { name: 'ESPRESSObin Ultra', status: 'completed' }, - { name: 'Sheeva64 (WiFi 6)', status: 'completed' }, - { name: 'MOCHAbin (10GbE)', status: 'completed' }, - { name: 'Performance Optimization', status: 'in-progress' } - ] - }, - 'integration': { - name: 'Integration & Testing', - progress: 85, - 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: 'Beta Testing Program', status: 'in-progress' } - ] - }, - 'campaign-prep': { - name: 'Campaign Preparation', - progress: 70, - total: 5, - completed: 3, - 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' } - ] - } - }, +const DevStatusWidget = { + targetVersion: '1.0.0', + // Development milestones and progress + milestones: { + 'modules-core': { + name: 'Core Modules', + progress: 100, + total: 15, + completed: 15, + 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' } + ] + }, + '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: '🚀', + 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' } + ] + } + }, - // Overall project statistics - stats: { - modulesCount: 13, - languagesSupported: 11, - architectures: 4, - linesOfCode: 15000, - contributors: 3, - commits: 450, - openIssues: 12, - closedIssues: 87 - }, + // Per-module status overview + moduleStatus: [ + { name: 'SecuBox Central Hub', version: '0.3.1', note: 'Dashboard central' }, + { name: 'System Hub', version: '0.3.2', note: 'Centre de contrôle' }, + { name: 'Traffic Shaper', version: '0.2.2', note: 'CAKE / fq_codel / HTB' }, + { name: 'CrowdSec Dashboard', version: '0.2.2', note: 'Détection d’intrusions' }, + { name: 'Netdata Dashboard', version: '0.2.2', note: 'Monitoring temps réel' }, + { name: 'Netifyd Dashboard', version: '0.2.2', note: 'Intelligence applicative' }, + { name: 'Network Modes', version: '0.3.1', note: '5 topologies réseau' }, + { name: 'WireGuard Dashboard', version: '0.2.2', note: 'VPN + QR codes' }, + { name: 'Auth Guardian', version: '0.2.2', note: 'OAuth / vouchers' }, + { name: 'Client Guardian', version: '0.2.2', note: 'Patch portail captif + montée en version' }, + { name: 'Bandwidth Manager', version: '0.2.2', note: 'QoS + quotas' }, + { name: 'Media Flow', version: '0.2.2', note: 'DPI streaming' }, + { name: 'CDN Cache', version: '0.2.2', note: 'Cache contenu local' }, + { name: 'VHost Manager', version: '0.2.2', note: 'Reverse proxy / SSL' }, + { name: 'KSM Manager', version: '0.2.2', note: 'Gestion clés / HSM' } + ], - // Timeline data - timeline: [ - { - 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: 'in-progress', - progress: 95 - }, - { - phase: 'Phase 4', - name: 'Beta Testing', - period: 'Q1 2026', - status: 'in-progress', - progress: 40 - }, - { - phase: 'Phase 5', - name: 'Crowdfunding Campaign', - period: 'Q2 2026', - status: 'planned', - progress: 20 - }, - { - phase: 'Phase 6', - name: 'Production & Delivery', - period: 'Q3 - Q4 2026', - status: 'planned', - progress: 0 - } - ], + // Overall project statistics + stats: { + get modulesCount() { return DevStatusWidget.moduleStatus.length; }, + languagesSupported: 11, + architectures: 13, + linesOfCode: 15000, + contributors: 3, + commits: 450, + openIssues: 1, + closedIssues: 87 + }, - /** - * Calculate overall progress - */ - getOverallProgress() { - const milestones = Object.values(this.milestones); - const totalProgress = milestones.reduce((sum, m) => sum + m.progress, 0); - return Math.round(totalProgress / milestones.length); - }, + // Timeline data + timeline: [ + { + 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', + status: 'in-progress', + progress: 55 + }, + { + phase: 'Phase 5', + name: 'Crowdfunding Campaign', + period: 'Q2 2026', + status: 'planned', + progress: 20 + }, + { + phase: 'Phase 6', + name: 'Production & Delivery', + period: 'Q3 - Q4 2026', + status: 'planned', + progress: 0 + } + ], - /** - * Get current phase - */ - getCurrentPhase() { - return this.timeline.find(p => p.status === 'in-progress') || this.timeline[0]; - }, + /** + * Calculate overall progress + */ + getOverallProgress() { + return Math.round(this.getModulesOverallProgress()); + }, - /** - * Render the widget - */ - render(containerId) { - const container = document.getElementById(containerId); - if (!container) { - console.error(`Container #${containerId} not found`); - return; - } + 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; + }, - const overallProgress = this.getOverallProgress(); - const currentPhase = this.getCurrentPhase(); + /** + * Get current phase + */ + getCurrentPhase() { + return this.timeline.find(p => p.status === 'in-progress') || this.timeline[0]; + }, - container.innerHTML = ` + /** + * Render the widget + */ + render(containerId) { + const container = document.getElementById(containerId); + if (!container) { + console.error(`Container #${containerId} not found`); + return; + } + + 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(); - }, + this.addStyles(); + this.animateProgressBars(); + }, - /** - * Render header section - */ - renderHeader(progress, phase) { - return ` + /** + * Render header section + */ + renderHeader(progress, phase) { + const displayProgress = Number(progress || 0).toFixed(2); + return `

🚀 Development Status

@@ -195,7 +226,7 @@ return baseclass.extend({ -
${progress}%
+
${displayProgress}%
Current Phase
@@ -205,13 +236,35 @@ return baseclass.extend({
`; - }, + }, - /** - * Render milestones section - */ - renderMilestones() { - const milestonesHtml = Object.entries(this.milestones).map(([key, milestone]) => ` + /** + * 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 `
@@ -219,26 +272,29 @@ return baseclass.extend({ ${milestone.name}
- ${milestone.completed}/${milestone.total} - ${milestone.progress}% + ${this.getMilestoneCompletion(milestone)} + ${this.getMilestonePercentage(milestone)}% + ${this.getMilestoneProgressFraction(milestone)}
+
+
+
+
+
${this.getMilestoneProgressFraction(milestone)}
+
-
- ${milestone.items.map(item => ` -
- ${this.getStatusIcon(item.status)} - ${item.name} -
- `).join('')} + ${itemsHtml}
- `).join(''); + `; + }).join(''); - return ` + return `

Development Milestones

@@ -246,13 +302,13 @@ return baseclass.extend({
`; - }, + }, - /** - * Render timeline section - */ - renderTimeline() { - const timelineHtml = this.timeline.map((phase, index) => ` + /** + * Render timeline section + */ + renderTimeline() { + const timelineHtml = this.timeline.map((phase, index) => `
@@ -274,7 +330,7 @@ return baseclass.extend({
`).join(''); - return ` + return `

Project Timeline

@@ -282,13 +338,56 @@ return baseclass.extend({
`; - }, + }, - /** - * Render statistics section - */ - renderStats() { - return ` + /** + * 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}`; + return ` +
+
+ ${module.name} + ${this.formatVersion(module.version)} +
+
+ ${status === 'completed' ? '✅' : '🔄'} + ${statusLabel} +
+
+
+
+
+
${this.formatVersionProgress(module)}
+
+
Objectif : v${this.targetVersion}
+
${module.note}
+
+ `; + }).join(''); + + return ` +
+

Modules & Versions

+
+ ${modulesHtml} +
+
+ `; + }, + + /** + * Render statistics section + */ + renderStats() { + return `

Project Statistics

@@ -327,45 +426,149 @@ return baseclass.extend({
`; - }, + }, - /** - * Get status icon - */ - getStatusIcon(status) { - const icons = { - 'completed': '✅', - 'in-progress': '🔄', - 'planned': '📋' - }; - return icons[status] || '⚪'; - }, + /** + * Get status icon + */ + getStatusIcon(status) { + const icons = { + 'completed': '✅', + 'in-progress': '🔄', + 'planned': '📋' + }; + return icons[status] || '⚪'; + }, - /** - * 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}%`; - } - }); - }, 100); - }, + /** + * 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}`; + }, - /** - * Add widget styles - */ - addStyles() { - if (document.getElementById('dev-status-widget-styles')) return; + /** + * Calculate milestone progress percentage from items + */ + getMilestonePercentage(milestone) { + return Math.round(this.getMilestoneProgressValue(milestone) * 100); + }, - const style = document.createElement('style'); - style.id = 'dev-status-widget-styles'; - style.textContent = ` + /** + * 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); + }, + + /** + * Return X.Y / 1 fractional representation of progress towards target version + */ + getMilestoneProgressFraction(milestone) { + const fraction = this.getMilestoneProgressValue(milestone); + return `${fraction.toFixed(2)} / 1`; + }, + + /** + * Format version with leading v + */ + formatVersion(version) { + if (!version) + return ''; + return version.startsWith('v') ? version : `v${version}`; + }, + + 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); + }, + + /** + * 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; + }, + + /** + * Determine module status versus the target version + */ + getModuleStatus(module) { + return this.compareVersions(module.version, this.targetVersion) >= 0 ? 'completed' : 'in-progress'; + }, + + getVersionProgress(module) { + const current = this.versionToNumber(module.version); + const target = this.versionToNumber(this.targetVersion); + if (!target) + return 0; + return Math.min(1, current / target); + }, + + 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}%`; + } + }); + }, 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); @@ -528,12 +731,19 @@ return baseclass.extend({ .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: rgba(255, 255, 255, 0.05); - border-radius: 999px; + background: var(--sb-bg, #0f1019); + border-radius: 4px; overflow: hidden; margin-bottom: 16px; } @@ -541,109 +751,182 @@ return baseclass.extend({ .dsw-progress-bar-fill { height: 100%; width: 0; - border-radius: 999px; + 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; - align-items: center; - gap: 10px; - padding: 10px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.02); - margin-bottom: 8px; - border: 1px solid transparent; - transition: all 0.2s; - } - - .dsw-item:hover { - border-color: rgba(255, 255, 255, 0.1); - transform: translateX(4px); - } - - .dsw-item.dsw-item-completed { - border-color: rgba(16, 185, 129, 0.2); - background: rgba(16, 185, 129, 0.05); - } - - .dsw-item.dsw-item-in-progress { - border-color: rgba(245, 158, 11, 0.2); - background: rgba(245, 158, 11, 0.05); - } - - .dsw-item.dsw-item-planned { - border-color: rgba(59, 130, 246, 0.2); - background: rgba(59, 130, 246, 0.05); + flex-direction: column; + gap: 6px; + padding: 8px 0; } .dsw-item-icon { - font-size: 18px; + font-size: 16px; } .dsw-item-name { - font-size: 14px; + 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: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 24px; + display: flex; + flex-direction: column; + gap: 0; } .dsw-timeline-item { display: flex; - gap: 16px; - padding: 20px; - background: var(--sb-bg, #0f1019); - border: 1px solid var(--sb-border, #2a2a3a); - border-radius: 12px; + gap: 20px; } .dsw-timeline-marker { display: flex; flex-direction: column; align-items: center; + padding-top: 8px; } .dsw-timeline-dot { - width: 14px; - height: 14px; + width: 16px; + height: 16px; border-radius: 50%; - border: 3px solid #10b981; - background: #1a1a24; + 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; - width: 3px; - background: linear-gradient(180deg, rgba(16, 185, 129, 0.5), rgba(59, 130, 246, 0.5)); - margin: 8px 0; + 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; - color: var(--sb-text-dim, #64748b); - letter-spacing: 1px; } .dsw-timeline-period { - font-size: 13px; - color: var(--sb-cyan, #06b6d4); + font-size: 12px; + color: var(--sb-text-dim, #64748b); font-family: 'JetBrains Mono', monospace; } .dsw-timeline-name { font-size: 16px; - font-weight: 700; - margin: 8px 0; + font-weight: 600; + margin-bottom: 12px; } .dsw-timeline-progress { @@ -676,6 +959,96 @@ return baseclass.extend({ 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; + gap: 12px; + font-weight: 600; + } + + .dsw-module-version { + font-family: 'JetBrains Mono', monospace; + font-size: 13px; + color: var(--sb-text-muted, #94a3b8); + } + + .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)); @@ -739,14 +1112,14 @@ return baseclass.extend({ } `; - document.head.appendChild(style); + 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 = ` + // 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 = ` @@ -754,8 +1127,26 @@ return baseclass.extend({ `; - svg.id = 'dsw-gradient'; - document.body.appendChild(svg); - } - } -}); + svg.id = 'dsw-gradient'; + document.body.appendChild(svg); + } + } +}; + +// 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 use in other scripts +window.DevStatusWidget = DevStatusWidget; + +return baseclass.extend(DevStatusWidget); diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/dev-status.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/dev-status.js index da057ae..e1a182b 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/dev-status.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/dev-status.js @@ -14,7 +14,7 @@ return view.extend({ getWidget: function() { if (!this.widget) - this.widget = DevStatusWidget.new(); + this.widget = DevStatusWidget; return this.widget; }, @@ -30,6 +30,19 @@ return view.extend({ this.renderFooterNote() ]); + container.appendChild(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; + } + `)); + window.requestAnimationFrame(function() { widget.render('dev-status-widget'); }); @@ -48,7 +61,7 @@ return view.extend({ 'Development Status' ]), E('p', { 'class': 'sh-page-subtitle' }, - 'Bonus tab showcasing public roadmap & milestones from secubox-website demos') + 'SecuBox + System Hub version monitor (v' + widget.targetVersion + ' target)') ]), E('div', { 'class': 'sh-page-insight' }, [ E('div', { 'class': 'sh-page-insight-label' }, 'Current phase'), diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js index 3e8cc15..775b8e4 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js @@ -73,12 +73,15 @@ return view.extend({ E('p', { 'class': 'sh-dashboard-subtitle' }, 'System Monitoring & Management Center') ]), E('div', { 'class': 'sh-dashboard-header-info' }, [ - E('span', { 'class': 'sh-dashboard-badge sh-dashboard-badge-version' }, - 'v0.3.2'), - E('span', { 'class': 'sh-dashboard-badge' }, - '⏱️ ' + (this.sysInfo.uptime_formatted || '0d 0h 0m')), - E('span', { 'class': 'sh-dashboard-badge' }, - '🖥️ ' + (this.sysInfo.hostname || 'OpenWrt')) + E('div', { 'class': 'sh-header-badge-group' }, [ + E('span', { 'class': 'sh-dashboard-badge sh-dashboard-badge-version' }, + 'v0.3.2'), + E('span', { 'class': 'sh-dashboard-badge' }, + '⏱️ ' + (this.sysInfo.uptime_formatted || '0d 0h 0m')), + E('span', { 'class': 'sh-dashboard-badge' }, + '🖥️ ' + (this.sysInfo.hostname || 'OpenWrt')) + ]), + this.renderHealthGauge(score, scoreClass, scoreLabel) ]) ]) ]); @@ -112,9 +115,7 @@ return view.extend({ return E('div', { 'class': 'sh-stats-overview-grid' }, [ // Health Score Card E('div', { 'class': 'sh-stat-overview-card sh-stat-' + scoreClass }, [ - E('div', { 'class': 'sh-stat-overview-value' }, score), - E('div', { 'class': 'sh-stat-overview-label' }, 'Health Score'), - E('div', { 'class': 'sh-stat-overview-status' }, scoreLabel) + this.renderHealthGauge(score, scoreClass, scoreLabel) ]), // CPU Card with enhanced info