develstats
This commit is contained in:
parent
81a0448fde
commit
5d10e6c31c
2
.github/workflows/test-validate.yml
vendored
2
.github/workflows/test-validate.yml
vendored
@ -207,7 +207,7 @@ jobs:
|
|||||||
sudo apt-get install -y \
|
sudo apt-get install -y \
|
||||||
build-essential clang flex bison g++ gawk \
|
build-essential clang flex bison g++ gawk \
|
||||||
gcc-multilib g++-multilib gettext git libncurses5-dev \
|
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
|
- name: Cache OpenWrt SDK
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
@ -5,49 +5,53 @@
|
|||||||
* Real-time development progress tracker
|
* Real-time development progress tracker
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return baseclass.extend({
|
const DevStatusWidget = {
|
||||||
|
targetVersion: '1.0.0',
|
||||||
// Development milestones and progress
|
// Development milestones and progress
|
||||||
milestones: {
|
milestones: {
|
||||||
'modules-core': {
|
'modules-core': {
|
||||||
name: 'Core Modules',
|
name: 'Core Modules',
|
||||||
progress: 100,
|
progress: 100,
|
||||||
total: 13,
|
total: 15,
|
||||||
completed: 13,
|
completed: 15,
|
||||||
icon: '📦',
|
icon: '📦',
|
||||||
color: '#10b981',
|
color: '#10b981',
|
||||||
items: [
|
items: [
|
||||||
{ name: 'Bandwidth Manager', status: 'completed' },
|
{ name: 'SecuBox Central Hub', status: 'completed' },
|
||||||
{ name: 'Auth Guardian', status: 'completed' },
|
{ name: 'System Hub', status: 'completed' },
|
||||||
{ name: 'Media Flow', status: 'completed' },
|
{ name: 'Traffic Shaper', status: 'completed' },
|
||||||
{ name: 'VHost Manager', status: 'completed' },
|
|
||||||
{ name: 'CrowdSec Dashboard', status: 'completed' },
|
{ name: 'CrowdSec Dashboard', status: 'completed' },
|
||||||
{ name: 'WireGuard Dashboard', status: 'completed' },
|
|
||||||
{ name: 'Netdata Dashboard', status: 'completed' },
|
{ name: 'Netdata Dashboard', status: 'completed' },
|
||||||
{ name: 'Netifyd Dashboard', status: 'completed' },
|
{ name: 'Netifyd Dashboard', status: 'completed' },
|
||||||
{ name: 'Client Guardian', status: 'completed' },
|
|
||||||
{ name: 'Network Modes', status: 'completed' },
|
{ name: 'Network Modes', status: 'completed' },
|
||||||
{ name: 'Traffic Shaper', 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: 'CDN Cache', status: 'completed' },
|
||||||
{ name: 'SecuBox Hub', status: 'completed' }
|
{ name: 'VHost Manager', status: 'completed' },
|
||||||
|
{ name: 'KSM Manager', status: 'completed' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'hardware-support': {
|
'hardware-support': {
|
||||||
name: 'Hardware Support',
|
name: 'Hardware Support',
|
||||||
progress: 95,
|
progress: 90,
|
||||||
total: 4,
|
total: 5,
|
||||||
completed: 3,
|
completed: 4,
|
||||||
icon: '🔧',
|
icon: '🔧',
|
||||||
color: '#f59e0b',
|
color: '#f59e0b',
|
||||||
items: [
|
items: [
|
||||||
{ name: 'ESPRESSObin Ultra', status: 'completed' },
|
{ name: 'x86-64 Tier 1 (PC / VM)', status: 'completed' },
|
||||||
{ name: 'Sheeva64 (WiFi 6)', status: 'completed' },
|
{ name: 'ARM Cortex-A72 Tier 1 (MOCHAbin / RPi4)', status: 'completed' },
|
||||||
{ name: 'MOCHAbin (10GbE)', status: 'completed' },
|
{ name: 'ARM Cortex-A53 Tier 1 (ESPRESSObin / Sheeva64)', status: 'completed' },
|
||||||
{ name: 'Performance Optimization', status: 'in-progress' }
|
{ name: 'Tier 2 ARM64 / ARM32 Targets', status: 'in-progress' },
|
||||||
|
{ name: 'Tier 2 MIPS Targets', status: 'in-progress' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'integration': {
|
'integration': {
|
||||||
name: 'Integration & Testing',
|
name: 'Integration & Testing',
|
||||||
progress: 85,
|
progress: 88,
|
||||||
total: 6,
|
total: 6,
|
||||||
completed: 5,
|
completed: 5,
|
||||||
icon: '🧪',
|
icon: '🧪',
|
||||||
@ -58,12 +62,12 @@ return baseclass.extend({
|
|||||||
{ name: 'ubus APIs', status: 'completed' },
|
{ name: 'ubus APIs', status: 'completed' },
|
||||||
{ name: 'Multi-platform Build', status: 'completed' },
|
{ name: 'Multi-platform Build', status: 'completed' },
|
||||||
{ name: 'Documentation', status: 'completed' },
|
{ name: 'Documentation', status: 'completed' },
|
||||||
{ name: 'Beta Testing Program', status: 'in-progress' }
|
{ name: 'Hardware Beta Testing', status: 'in-progress' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'campaign-prep': {
|
'campaign-prep': {
|
||||||
name: 'Campaign Preparation',
|
name: 'Campaign Preparation',
|
||||||
progress: 70,
|
progress: 75,
|
||||||
total: 5,
|
total: 5,
|
||||||
completed: 3,
|
completed: 3,
|
||||||
icon: '🚀',
|
icon: '🚀',
|
||||||
@ -78,15 +82,34 @@ return baseclass.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 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' }
|
||||||
|
],
|
||||||
|
|
||||||
// Overall project statistics
|
// Overall project statistics
|
||||||
stats: {
|
stats: {
|
||||||
modulesCount: 13,
|
get modulesCount() { return DevStatusWidget.moduleStatus.length; },
|
||||||
languagesSupported: 11,
|
languagesSupported: 11,
|
||||||
architectures: 4,
|
architectures: 13,
|
||||||
linesOfCode: 15000,
|
linesOfCode: 15000,
|
||||||
contributors: 3,
|
contributors: 3,
|
||||||
commits: 450,
|
commits: 450,
|
||||||
openIssues: 12,
|
openIssues: 1,
|
||||||
closedIssues: 87
|
closedIssues: 87
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -110,15 +133,15 @@ return baseclass.extend({
|
|||||||
phase: 'Phase 3',
|
phase: 'Phase 3',
|
||||||
name: 'Hardware Integration',
|
name: 'Hardware Integration',
|
||||||
period: 'Q2 - Q4 2025',
|
period: 'Q2 - Q4 2025',
|
||||||
status: 'in-progress',
|
status: 'completed',
|
||||||
progress: 95
|
progress: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
phase: 'Phase 4',
|
phase: 'Phase 4',
|
||||||
name: 'Beta Testing',
|
name: 'Beta Testing',
|
||||||
period: 'Q1 2026',
|
period: 'Q1 2026',
|
||||||
status: 'in-progress',
|
status: 'in-progress',
|
||||||
progress: 40
|
progress: 55
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
phase: 'Phase 5',
|
phase: 'Phase 5',
|
||||||
@ -140,9 +163,15 @@ return baseclass.extend({
|
|||||||
* Calculate overall progress
|
* Calculate overall progress
|
||||||
*/
|
*/
|
||||||
getOverallProgress() {
|
getOverallProgress() {
|
||||||
const milestones = Object.values(this.milestones);
|
return Math.round(this.getModulesOverallProgress());
|
||||||
const totalProgress = milestones.reduce((sum, m) => sum + m.progress, 0);
|
},
|
||||||
return Math.round(totalProgress / milestones.length);
|
|
||||||
|
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;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,7 +191,7 @@ return baseclass.extend({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const overallProgress = this.getOverallProgress();
|
const overallProgress = this.getModulesOverallProgress();
|
||||||
const currentPhase = this.getCurrentPhase();
|
const currentPhase = this.getCurrentPhase();
|
||||||
|
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
@ -170,6 +199,7 @@ return baseclass.extend({
|
|||||||
${this.renderHeader(overallProgress, currentPhase)}
|
${this.renderHeader(overallProgress, currentPhase)}
|
||||||
${this.renderMilestones()}
|
${this.renderMilestones()}
|
||||||
${this.renderTimeline()}
|
${this.renderTimeline()}
|
||||||
|
${this.renderModuleStatus()}
|
||||||
${this.renderStats()}
|
${this.renderStats()}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -182,6 +212,7 @@ return baseclass.extend({
|
|||||||
* Render header section
|
* Render header section
|
||||||
*/
|
*/
|
||||||
renderHeader(progress, phase) {
|
renderHeader(progress, phase) {
|
||||||
|
const displayProgress = Number(progress || 0).toFixed(2);
|
||||||
return `
|
return `
|
||||||
<div class="dsw-header">
|
<div class="dsw-header">
|
||||||
<div class="dsw-header-content">
|
<div class="dsw-header-content">
|
||||||
@ -195,7 +226,7 @@ return baseclass.extend({
|
|||||||
<circle class="dsw-progress-bar" cx="60" cy="60" r="54"
|
<circle class="dsw-progress-bar" cx="60" cy="60" r="54"
|
||||||
style="stroke-dashoffset: ${339 - (339 * progress / 100)}" />
|
style="stroke-dashoffset: ${339 - (339 * progress / 100)}" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="dsw-progress-value">${progress}%</div>
|
<div class="dsw-progress-value">${displayProgress}%</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dsw-current-phase">
|
<div class="dsw-current-phase">
|
||||||
<div class="dsw-phase-label">Current Phase</div>
|
<div class="dsw-phase-label">Current Phase</div>
|
||||||
@ -211,7 +242,29 @@ return baseclass.extend({
|
|||||||
* Render milestones section
|
* Render milestones section
|
||||||
*/
|
*/
|
||||||
renderMilestones() {
|
renderMilestones() {
|
||||||
const milestonesHtml = Object.entries(this.milestones).map(([key, milestone]) => `
|
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 `
|
||||||
|
<div class="dsw-item dsw-item-${item.status}">
|
||||||
|
<span class="dsw-item-icon">${this.getStatusIcon(item.status)}</span>
|
||||||
|
<span class="dsw-item-name">${item.name}</span>
|
||||||
|
${progressValue >= 0 ? `
|
||||||
|
<div class="dsw-item-progress">
|
||||||
|
<div class="dsw-item-progress-bar">
|
||||||
|
<div class="dsw-item-progress-fill" data-progress="${progressPercent}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="dsw-item-progress-label">${progressLabel}</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
<div class="dsw-milestone" data-key="${key}">
|
<div class="dsw-milestone" data-key="${key}">
|
||||||
<div class="dsw-milestone-header">
|
<div class="dsw-milestone-header">
|
||||||
<div class="dsw-milestone-info">
|
<div class="dsw-milestone-info">
|
||||||
@ -219,24 +272,27 @@ return baseclass.extend({
|
|||||||
<span class="dsw-milestone-name">${milestone.name}</span>
|
<span class="dsw-milestone-name">${milestone.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dsw-milestone-stats">
|
<div class="dsw-milestone-stats">
|
||||||
<span class="dsw-milestone-count">${milestone.completed}/${milestone.total}</span>
|
<span class="dsw-milestone-count">${this.getMilestoneCompletion(milestone)}</span>
|
||||||
<span class="dsw-milestone-percent" style="color: ${milestone.color}">${milestone.progress}%</span>
|
<span class="dsw-milestone-percent" style="color: ${milestone.color}">${this.getMilestonePercentage(milestone)}%</span>
|
||||||
|
<span class="dsw-milestone-fraction">${this.getMilestoneProgressFraction(milestone)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dsw-milestone-mini-progress">
|
||||||
|
<div class="dsw-milestone-mini-bar">
|
||||||
|
<div class="dsw-milestone-mini-fill" data-progress="${Math.round(this.getMilestoneProgressValue(milestone) * 100)}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="dsw-milestone-mini-label">${this.getMilestoneProgressFraction(milestone)}</div>
|
||||||
|
</div>
|
||||||
<div class="dsw-progress-bar-container">
|
<div class="dsw-progress-bar-container">
|
||||||
<div class="dsw-progress-bar-fill" data-progress="${milestone.progress}"
|
<div class="dsw-progress-bar-fill" data-progress="${Math.round(this.getMilestoneProgressValue(milestone) * 100)}"
|
||||||
style="background: ${milestone.color}"></div>
|
style="background: ${milestone.color}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dsw-milestone-items">
|
<div class="dsw-milestone-items">
|
||||||
${milestone.items.map(item => `
|
${itemsHtml}
|
||||||
<div class="dsw-item dsw-item-${item.status}">
|
|
||||||
<span class="dsw-item-icon">${this.getStatusIcon(item.status)}</span>
|
|
||||||
<span class="dsw-item-name">${item.name}</span>
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="dsw-milestones">
|
<div class="dsw-milestones">
|
||||||
@ -284,6 +340,49 @@ return baseclass.extend({
|
|||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 `
|
||||||
|
<div class="dsw-module-card dsw-module-${status}">
|
||||||
|
<div class="dsw-module-header">
|
||||||
|
<span class="dsw-module-name">${module.name}</span>
|
||||||
|
<span class="dsw-module-version">${this.formatVersion(module.version)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dsw-module-status-row">
|
||||||
|
<span class="dsw-module-status-indicator">${status === 'completed' ? '✅' : '🔄'}</span>
|
||||||
|
<span class="dsw-module-status-label">${statusLabel}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dsw-module-progress">
|
||||||
|
<div class="dsw-module-progress-bar">
|
||||||
|
<div class="dsw-module-progress-fill" data-progress="${progressPercent}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="dsw-module-progress-label">${this.formatVersionProgress(module)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="dsw-module-target">Objectif : v${this.targetVersion}</div>
|
||||||
|
<div class="dsw-module-note">${module.note}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="dsw-modules">
|
||||||
|
<h4 class="dsw-section-title">Modules & Versions</h4>
|
||||||
|
<div class="dsw-modules-grid">
|
||||||
|
${modulesHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render statistics section
|
* Render statistics section
|
||||||
*/
|
*/
|
||||||
@ -341,6 +440,106 @@ return baseclass.extend({
|
|||||||
return icons[status] || '⚪';
|
return icons[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}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate milestone progress percentage from items
|
||||||
|
*/
|
||||||
|
getMilestonePercentage(milestone) {
|
||||||
|
return Math.round(this.getMilestoneProgressValue(milestone) * 100);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Animate progress bars
|
||||||
*/
|
*/
|
||||||
@ -352,6 +551,10 @@ return baseclass.extend({
|
|||||||
element.style.width = `${progress}%`;
|
element.style.width = `${progress}%`;
|
||||||
} else if (element.classList.contains('dsw-timeline-progress-fill')) {
|
} else if (element.classList.contains('dsw-timeline-progress-fill')) {
|
||||||
element.style.width = `${progress}%`;
|
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);
|
}, 100);
|
||||||
@ -528,12 +731,19 @@ return baseclass.extend({
|
|||||||
|
|
||||||
.dsw-milestone-percent {
|
.dsw-milestone-percent {
|
||||||
font-weight: 700;
|
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 {
|
.dsw-progress-bar-container {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: var(--sb-bg, #0f1019);
|
||||||
border-radius: 999px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
@ -541,109 +751,182 @@ return baseclass.extend({
|
|||||||
.dsw-progress-bar-fill {
|
.dsw-progress-bar-fill {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 0;
|
width: 0;
|
||||||
border-radius: 999px;
|
border-radius: 4px;
|
||||||
transition: width 1s ease-out;
|
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 {
|
.dsw-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 6px;
|
||||||
padding: 10px;
|
padding: 8px 0;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dsw-item-icon {
|
.dsw-item-icon {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dsw-item-name {
|
.dsw-item-name {
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
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 {
|
.dsw-timeline {
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dsw-timeline-container {
|
.dsw-timeline-container {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dsw-timeline-item {
|
.dsw-timeline-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 20px;
|
||||||
padding: 20px;
|
|
||||||
background: var(--sb-bg, #0f1019);
|
|
||||||
border: 1px solid var(--sb-border, #2a2a3a);
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dsw-timeline-marker {
|
.dsw-timeline-marker {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dsw-timeline-dot {
|
.dsw-timeline-dot {
|
||||||
width: 14px;
|
width: 16px;
|
||||||
height: 14px;
|
height: 16px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 3px solid #10b981;
|
background: var(--sb-border, #2a2a3a);
|
||||||
background: #1a1a24;
|
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 {
|
.dsw-timeline-line {
|
||||||
|
width: 2px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 3px;
|
background: var(--sb-border, #2a2a3a);
|
||||||
background: linear-gradient(180deg, rgba(16, 185, 129, 0.5), rgba(59, 130, 246, 0.5));
|
margin-top: 4px;
|
||||||
margin: 8px 0;
|
}
|
||||||
|
|
||||||
|
.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 {
|
.dsw-timeline-phase {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
color: var(--sb-cyan, #06b6d4);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--sb-text-dim, #64748b);
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dsw-timeline-period {
|
.dsw-timeline-period {
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
color: var(--sb-cyan, #06b6d4);
|
color: var(--sb-text-dim, #64748b);
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dsw-timeline-name {
|
.dsw-timeline-name {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
margin: 8px 0;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dsw-timeline-progress {
|
.dsw-timeline-progress {
|
||||||
@ -676,6 +959,96 @@ return baseclass.extend({
|
|||||||
text-align: right;
|
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 {
|
.dsw-stats-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||||
@ -758,4 +1131,22 @@ return baseclass.extend({
|
|||||||
document.body.appendChild(svg);
|
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);
|
||||||
|
|||||||
@ -14,7 +14,7 @@ return view.extend({
|
|||||||
|
|
||||||
getWidget: function() {
|
getWidget: function() {
|
||||||
if (!this.widget)
|
if (!this.widget)
|
||||||
this.widget = DevStatusWidget.new();
|
this.widget = DevStatusWidget;
|
||||||
return this.widget;
|
return this.widget;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -30,6 +30,19 @@ return view.extend({
|
|||||||
this.renderFooterNote()
|
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() {
|
window.requestAnimationFrame(function() {
|
||||||
widget.render('dev-status-widget');
|
widget.render('dev-status-widget');
|
||||||
});
|
});
|
||||||
@ -48,7 +61,7 @@ return view.extend({
|
|||||||
'Development Status'
|
'Development Status'
|
||||||
]),
|
]),
|
||||||
E('p', { 'class': 'sh-page-subtitle' },
|
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' }, [
|
||||||
E('div', { 'class': 'sh-page-insight-label' }, 'Current phase'),
|
E('div', { 'class': 'sh-page-insight-label' }, 'Current phase'),
|
||||||
|
|||||||
@ -73,12 +73,15 @@ return view.extend({
|
|||||||
E('p', { 'class': 'sh-dashboard-subtitle' }, 'System Monitoring & Management Center')
|
E('p', { 'class': 'sh-dashboard-subtitle' }, 'System Monitoring & Management Center')
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sh-dashboard-header-info' }, [
|
E('div', { 'class': 'sh-dashboard-header-info' }, [
|
||||||
|
E('div', { 'class': 'sh-header-badge-group' }, [
|
||||||
E('span', { 'class': 'sh-dashboard-badge sh-dashboard-badge-version' },
|
E('span', { 'class': 'sh-dashboard-badge sh-dashboard-badge-version' },
|
||||||
'v0.3.2'),
|
'v0.3.2'),
|
||||||
E('span', { 'class': 'sh-dashboard-badge' },
|
E('span', { 'class': 'sh-dashboard-badge' },
|
||||||
'⏱️ ' + (this.sysInfo.uptime_formatted || '0d 0h 0m')),
|
'⏱️ ' + (this.sysInfo.uptime_formatted || '0d 0h 0m')),
|
||||||
E('span', { 'class': 'sh-dashboard-badge' },
|
E('span', { 'class': 'sh-dashboard-badge' },
|
||||||
'🖥️ ' + (this.sysInfo.hostname || 'OpenWrt'))
|
'🖥️ ' + (this.sysInfo.hostname || 'OpenWrt'))
|
||||||
|
]),
|
||||||
|
this.renderHealthGauge(score, scoreClass, scoreLabel)
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
@ -112,9 +115,7 @@ return view.extend({
|
|||||||
return E('div', { 'class': 'sh-stats-overview-grid' }, [
|
return E('div', { 'class': 'sh-stats-overview-grid' }, [
|
||||||
// Health Score Card
|
// Health Score Card
|
||||||
E('div', { 'class': 'sh-stat-overview-card sh-stat-' + scoreClass }, [
|
E('div', { 'class': 'sh-stat-overview-card sh-stat-' + scoreClass }, [
|
||||||
E('div', { 'class': 'sh-stat-overview-value' }, score),
|
this.renderHealthGauge(score, scoreClass, scoreLabel)
|
||||||
E('div', { 'class': 'sh-stat-overview-label' }, 'Health Score'),
|
|
||||||
E('div', { 'class': 'sh-stat-overview-status' }, scoreLabel)
|
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// CPU Card with enhanced info
|
// CPU Card with enhanced info
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user