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 `
`;
- },
+ },
- /**
- * 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 ? `
+
+ ` : ''}
+
+ `;
+ }).join('');
+
+ return `
+
+
+
${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 `
+
+
+
+ ${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