secubox-openwrt/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/devstatus.js
CyberMind-FR 1bbd345cee refactor(luci): Mass KissTheme UI rework across all LuCI apps
Convert 90+ LuCI view files from legacy cbi-button-* classes to
KissTheme kiss-btn-* classes for consistent dark theme styling.

Pattern conversions applied:
- cbi-button-positive → kiss-btn-green
- cbi-button-negative/remove → kiss-btn-red
- cbi-button-apply → kiss-btn-cyan
- cbi-button-action → kiss-btn-blue
- cbi-button (plain) → kiss-btn

Also replaced hardcoded colors (#080, #c00, #888, etc.) with
CSS variables (--kiss-green, --kiss-red, --kiss-muted, etc.)
for proper dark theme compatibility.

Apps updated include: ai-gateway, auth-guardian, bandwidth-manager,
cloner, config-advisor, crowdsec-dashboard, dns-provider, exposure,
glances, haproxy, hexojs, iot-guard, jellyfin, ksm-manager,
mac-guardian, magicmirror2, master-link, meshname-dns, metablogizer,
metabolizer, mqtt-bridge, netdata-dashboard, picobrew, routes-status,
secubox-admin, secubox-mirror, secubox-p2p, secubox-security-threats,
service-registry, simplex, streamlit, system-hub, tor-shield,
traffic-shaper, vhost-manager, vortex-dns, vortex-firewall,
webradio, wireguard-dashboard, zigbee2mqtt, zkp, and more.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-12 11:09:34 +01:00

307 lines
11 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require dom';
'require poll';
'require secubox-p2p/api as P2PAPI';
'require secubox/kiss-theme';
/**
* SecuBox Development Status Widget
* Generative and dynamic dashboard showing:
* - Gitea commit activity
* - MirrorBox App Store stats
* - Progress toward v1.0 (0-100%)
*/
return view.extend({
// State
commits: [],
repos: [],
packages: { local: 0, peer: 0, unique: 0, sources: 0 },
giteaConnected: false,
loading: true,
refreshInterval: 30000,
// v1.0 Milestones - features required for 1.0 release
milestones: [
{ id: 'core', name: 'Core Framework', target: 15, current: 15, complete: true },
{ id: 'security', name: 'Security Suite', target: 10, current: 10, complete: true },
{ id: 'network', name: 'Network Tools', target: 8, current: 8, complete: true },
{ id: 'p2p', name: 'P2P Mesh', target: 12, current: 12, complete: true },
{ id: 'apps', name: 'App Ecosystem', target: 25, current: 20, complete: false },
{ id: 'media', name: 'Media Services', target: 5, current: 4, complete: false },
{ id: 'remote', name: 'Remote Access', target: 3, current: 2, complete: false },
{ id: 'docs', name: 'Documentation', target: 10, current: 7, complete: false }
],
load: function() {
var self = this;
self.loading = true;
return Promise.all([
P2PAPI.getGiteaCommits(20).catch(function() { return { commits: [] }; }),
P2PAPI.listGiteaRepos().catch(function() { return { repos: [] }; }),
P2PAPI.getAllPackages().catch(function() { return { sources: [] }; })
]).then(function(results) {
// Gitea commits
var commitResult = results[0] || {};
self.commits = commitResult.commits || [];
self.giteaConnected = commitResult.success !== false && self.commits.length > 0;
// Gitea repos
var repoResult = results[1] || {};
self.repos = repoResult.repos || [];
// Package stats
var pkgResult = results[2] || {};
var sources = pkgResult.sources || [];
var localCount = 0, peerCount = 0, uniqueNames = new Set();
sources.forEach(function(source) {
var pkgs = source.packages || [];
pkgs.forEach(function(p) { uniqueNames.add(p.name); });
if (source.type === 'local') {
localCount = source.package_count || pkgs.length;
} else {
peerCount += source.package_count || pkgs.length;
}
});
self.packages = {
local: localCount,
peer: peerCount,
unique: uniqueNames.size,
sources: sources.length
};
// Update milestones based on real data
self.updateMilestones();
self.loading = false;
});
},
updateMilestones: function() {
// Dynamically update milestones based on actual data
var pkgCount = this.packages.local;
// Apps milestone: based on package count
var appsMilestone = this.milestones.find(function(m) { return m.id === 'apps'; });
if (appsMilestone) {
appsMilestone.current = Math.min(pkgCount, appsMilestone.target);
appsMilestone.complete = appsMilestone.current >= appsMilestone.target;
}
},
calculateProgress: function() {
var totalTarget = 0, totalCurrent = 0;
this.milestones.forEach(function(m) {
totalTarget += m.target;
totalCurrent += Math.min(m.current, m.target);
});
return totalTarget > 0 ? Math.round((totalCurrent / totalTarget) * 100) : 0;
},
formatTimeAgo: function(dateStr) {
if (!dateStr) return 'Unknown';
var date = new Date(dateStr);
var now = new Date();
var diff = Math.floor((now - date) / 1000);
if (diff < 60) return diff + 's ago';
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
if (diff < 604800) return Math.floor(diff / 86400) + 'd ago';
return date.toLocaleDateString();
},
renderProgressBar: function(percent, color) {
return E('div', { 'style': 'background: var(--kiss-bg2); border-radius: 10px; height: 20px; overflow: hidden; position: relative;' }, [
E('div', {
'style': 'background: ' + color + '; height: 100%; width: ' + percent + '%; transition: width 0.5s ease;'
}),
E('span', {
'style': 'position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-weight: bold; font-size: 12px; color: var(--kiss-text);'
}, percent + '%')
]);
},
renderStats: function() {
var c = KissTheme.colors;
return [
KissTheme.stat(this.packages.local, 'Local Packages', c.green),
KissTheme.stat(this.packages.peer, 'Peer Packages', c.blue),
KissTheme.stat(this.repos.length, 'Gitea Repos', c.purple),
KissTheme.stat(this.commits.length, 'Recent Commits', c.orange)
];
},
renderMilestoneProgress: function() {
var self = this;
var progress = this.calculateProgress();
var c = KissTheme.colors;
var progressColor = progress < 50 ? c.red : (progress < 80 ? c.orange : c.green);
return KissTheme.card(
E('div', { 'style': 'display: flex; align-items: center; gap: 12px;' }, [
E('span', {}, 'Progress to v1.0'),
KissTheme.badge(progress + '%', progress < 50 ? 'red' : (progress < 80 ? 'orange' : 'green'))
]),
E('div', {}, [
this.renderProgressBar(progress, progressColor),
E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'margin-top: 16px;' },
this.milestones.map(function(m) {
var pct = m.target > 0 ? Math.round((Math.min(m.current, m.target) / m.target) * 100) : 0;
var color = m.complete ? c.green : (pct > 50 ? c.orange : c.blue);
return E('div', {
'style': 'background: var(--kiss-bg2); padding: 12px; border-radius: 8px; border-left: 4px solid ' + color + ';'
}, [
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
E('span', { 'style': 'font-weight: 500; color: var(--kiss-text);' }, m.name),
m.complete ?
E('span', { 'style': 'color: ' + c.green + '; font-size: 14px;' }, '✓') :
E('span', { 'style': 'color: var(--kiss-muted); font-size: 12px;' }, m.current + '/' + m.target)
]),
E('div', { 'style': 'margin-top: 8px;' }, self.renderProgressBar(pct, color))
]);
})
)
])
);
},
renderCommitLog: function() {
var self = this;
var c = KissTheme.colors;
if (!this.giteaConnected) {
return E('div', { 'style': 'text-align: center; padding: 32px; color: var(--kiss-muted);' }, [
E('div', { 'style': 'font-size: 48px; margin-bottom: 8px;' }, '🔌'),
E('p', {}, _('Gitea not connected')),
E('p', { 'style': 'font-size: 12px;' }, _('Configure Gitea in MirrorBox > Hub settings'))
]);
}
if (this.commits.length === 0) {
return E('p', { 'style': 'color: var(--kiss-muted); text-align: center;' }, _('No recent commits'));
}
return E('div', { 'style': 'max-height: 400px; overflow-y: auto;' },
this.commits.slice(0, 15).map(function(commit) {
var msg = commit.message || commit.commit_message || '';
var shortMsg = msg.split('\n')[0];
if (shortMsg.length > 60) shortMsg = shortMsg.substring(0, 57) + '...';
var author = commit.author || commit.committer || {};
var authorName = author.name || author.login || 'Unknown';
var date = commit.created || commit.committed_date || commit.date;
return E('div', {
'style': 'padding: 12px; border-bottom: 1px solid var(--kiss-line); display: flex; gap: 12px;'
}, [
E('div', {
'style': 'width: 40px; height: 40px; background: ' + c.blue + '; border-radius: 50%; display: flex; ' +
'align-items: center; justify-content: center; color: white; font-weight: bold; flex-shrink: 0;'
}, authorName.charAt(0).toUpperCase()),
E('div', { 'style': 'flex: 1; min-width: 0;' }, [
E('div', { 'style': 'font-weight: 500; margin-bottom: 4px; color: var(--kiss-text);' }, shortMsg),
E('div', { 'style': 'color: var(--kiss-muted); font-size: 12px;' }, [
E('span', {}, authorName),
E('span', { 'style': 'margin: 0 8px;' }, '•'),
E('span', {}, self.formatTimeAgo(date))
])
])
]);
})
);
},
renderRepoList: function() {
if (this.repos.length === 0) {
return E('p', { 'style': 'color: var(--kiss-muted); text-align: center;' }, _('No repositories'));
}
return E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 8px;' },
this.repos.slice(0, 10).map(function(repo) {
var name = repo.name || repo.full_name || 'Unknown';
return E('span', {
'style': 'background: var(--kiss-bg2); padding: 6px 12px; border-radius: 16px; font-size: 13px; color: var(--kiss-text);'
}, name);
})
);
},
render: function() {
var self = this;
if (this.loading) {
var content = [
E('div', { 'style': 'margin-bottom: 24px;' }, [
E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0;' }, 'Development Status'),
E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' }, 'Loading development data...')
]),
E('div', { 'style': 'text-align: center; padding: 60px;' }, [
E('p', { 'class': 'spinning' }, _('Loading...'))
])
];
return KissTheme.wrap(content, 'admin/secubox/p2p/devstatus');
}
// Start polling
poll.add(function() {
return self.load().then(function() {
dom.content(document.getElementById('devstatus-content'), self.renderContent());
});
}, this.refreshInterval);
var content = [
// Header
E('div', { 'style': 'margin-bottom: 24px;' }, [
E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [
E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0;' }, 'Development Status'),
KissTheme.badge('SecuBox v0.17.0 → v1.0', 'blue')
]),
E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' },
'Real-time development progress from Gitea and MirrorBox App Store')
]),
E('div', { 'id': 'devstatus-content' }, this.renderContent())
];
return KissTheme.wrap(content, 'admin/secubox/p2p/devstatus');
},
renderContent: function() {
return E('div', {}, [
// Stats
E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'margin-bottom: 20px;' },
this.renderStats()),
// Milestone Progress
this.renderMilestoneProgress(),
// Two column layout
E('div', { 'class': 'kiss-grid kiss-grid-2', 'style': 'margin-top: 20px;' }, [
KissTheme.card('Recent Commits', this.renderCommitLog()),
KissTheme.card(
'Repositories',
E('div', {}, [
this.renderRepoList(),
E('hr', { 'style': 'margin: 16px 0; border: none; border-top: 1px solid var(--kiss-line);' }),
E('h4', { 'style': 'margin: 0 0 8px 0; color: var(--kiss-text);' }, _('Feed Sources')),
E('p', { 'style': 'color: var(--kiss-muted); margin: 0;' },
this.packages.sources + ' sources providing ' + this.packages.unique + ' unique packages'
)
])
)
])
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});