secubox-openwrt/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/health.js
CyberMind-FR 5902ac500a release: v0.1.1 - Unified Theme System with Dark/Light Mode Support
Major Features:
• Centralized theme system across SecuBox and System Hub
• Three theme modes: dark (default), light, and system (auto-detect)
• Single theme setting in SecuBox controls both plugins
• Real-time theme switching with OS preference detection

SecuBox Changes:
• Added theme.js manager for centralized theme control
• Implemented CSS variables for dark/light mode (secubox.css)
• Fixed hardcoded colors in dashboard.css, alerts.css, monitoring.css
• Integrated theme.js in all 7 views (dashboard, modules, alerts, monitoring, settings, etc.)
• Added get_theme RPC method to luci.secubox backend
• Updated ACL permissions to include get_theme (read access)
• Version updated to 0.1.1

System Hub Changes:
• Added theme.js manager using SecuBox theme API
• Implemented CSS variables for dark/light mode (dashboard.css)
• Integrated theme.js in all 9 views (overview, health, services, logs, backup, components, remote, settings, diagnostics)
• Version updated to 0.1.1
• README updated with maintainer info

Theme System Architecture:
• Configuration: /etc/config/secubox (option theme: dark|light|system)
• RPCD Backend: luci.secubox/get_theme method
• Frontend: theme.js modules (secubox/theme.js, system-hub/theme.js)
• CSS Variables: --sb-bg, --sb-bg-card, --sb-border, --sb-text, --sb-text-muted, --sb-shadow
• Auto-detection: prefers-color-scheme media query for system mode

Documentation:
• Added LUCI_DEVELOPMENT_REFERENCE.md with comprehensive LuCI development patterns
• Documented ubus/RPC types, baseclass.extend() patterns, ACL structure
• Common errors and solutions from implementation experience

Bug Fixes:
• Fixed SecuBox theme not applying visually (CSS variables now used)
• Fixed missing secubox.css in view imports
• Fixed ACL access denied for get_theme method
• Fixed hardcoded colors preventing theme switching

Testing:
• Verified theme switching works in all SecuBox tabs
• Verified theme switching works in all System Hub tabs
• Verified dark/light/system modes function correctly
• Verified single setting controls both plugins

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 16:09:15 +01:00

142 lines
5.8 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require ui';
'require system-hub/api as API';
'require system-hub/theme as Theme';
// Load CSS
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('system-hub/dashboard.css')
}));
// Initialize theme
Theme.init();
// Helper: Get health status info based on score
function getHealthStatus(score) {
if (score >= 90) return { status: 'excellent', label: 'Excellent', color: '#22c55e' };
if (score >= 75) return { status: 'good', label: 'Bon', color: '#3b82f6' };
if (score >= 50) return { status: 'warning', label: 'Attention', color: '#f59e0b' };
return { status: 'critical', label: 'Critique', color: '#ef4444' };
}
// Helper: Format bytes to human-readable size
function formatBytes(bytes) {
if (!bytes) return '0 B';
var k = 1024;
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
}
return view.extend({
load: function() {
return API.getHealth();
},
render: function(data) {
var health = data;
var healthInfo = getHealthStatus(health.score || 0);
var self = this;
var view = E('div', { 'class': 'system-hub-dashboard' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }),
// Global Score
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '💚'), 'Score de Santé Global' ]),
E('div', { 'class': 'sh-card-badge' }, (health.score || 0) + '/100')
]),
E('div', { 'class': 'sh-card-body', 'style': 'text-align: center; padding: 40px;' }, [
E('div', {
'class': 'sh-score-circle ' + healthInfo.status,
'style': 'width: 120px; height: 120px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; font-size: 40px; font-weight: 800;'
}, (health.score || 0).toString()),
E('div', { 'style': 'font-size: 20px; font-weight: 700; margin-bottom: 8px;' }, healthInfo.label),
E('div', { 'style': 'color: #707080;' }, 'Dernière vérification : ' + (health.timestamp || 'N/A'))
])
]),
// Detailed Metrics
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '📊'), 'Métriques Détaillées' ])
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', { 'class': 'sh-health-grid' }, [
this.renderDetailedMetric('🔲', 'CPU', health.cpu?.usage || 0, health.cpu?.status, 'Load: ' + (health.cpu?.load_1m || 'N/A')),
this.renderDetailedMetric('💾', 'Mémoire', health.memory?.usage || 0, health.memory?.status, formatBytes((health.memory?.used_kb || 0) * 1024) + ' utilisés'),
this.renderDetailedMetric('💿', 'Stockage', health.disk?.usage || 0, health.disk?.status, formatBytes((health.disk?.used_kb || 0) * 1024) + ' utilisés'),
this.renderDetailedMetric('🌡️', 'Température', health.temperature?.value || 0, health.temperature?.status, 'Zone 0: CPU'),
this.renderDetailedMetric('🌐', 'Réseau WAN', health.network?.wan_up ? 100 : 0, health.network?.status, health.network?.wan_up ? 'Connecté' : 'Déconnecté'),
this.renderDetailedMetric('⚙️', 'Services', ((health.services?.running || 0) / ((health.services?.running || 0) + (health.services?.failed || 0)) * 100) || 0,
health.services?.failed > 0 ? 'warning' : 'ok',
(health.services?.running || 0) + '/' + ((health.services?.running || 0) + (health.services?.failed || 0)) + ' actifs')
])
])
]),
// Recommendations
health.recommendations && health.recommendations.length > 0 ? E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '💡'), 'Recommandations' ])
]),
E('div', { 'class': 'sh-card-body' },
health.recommendations.map(function(rec) {
return E('div', { 'style': 'display: flex; gap: 12px; align-items: flex-start; padding: 14px; background: rgba(245, 158, 11, 0.1); border-radius: 10px; border-left: 3px solid #f59e0b; margin-bottom: 10px;' }, [
E('span', { 'style': 'font-size: 24px;' }, '⚠️'),
E('div', {}, rec)
]);
})
)
]) : E('span'),
// Actions
E('div', { 'class': 'sh-btn-group' }, [
E('button', {
'class': 'sh-btn sh-btn-primary',
'click': L.bind(this.generateReport, this)
}, [ '📋 Générer Rapport' ]),
E('button', { 'class': 'sh-btn' }, [ '📧 Envoyer par Email' ]),
E('button', { 'class': 'sh-btn' }, [ '📥 Télécharger PDF' ])
])
]);
return view;
},
renderDetailedMetric: function(icon, label, value, status, detail) {
return E('div', { 'class': 'sh-health-metric' }, [
E('div', { 'class': 'sh-metric-header' }, [
E('div', { 'class': 'sh-metric-title' }, [ E('span', { 'class': 'sh-metric-icon' }, icon), label ]),
E('div', { 'class': 'sh-metric-value ' + (status || 'ok') }, value + (label === 'Température' ? '°C' : '%'))
]),
E('div', { 'class': 'sh-progress-bar' }, [
E('div', { 'class': 'sh-progress-fill ' + (status || 'ok'), 'style': 'width: ' + Math.min(value, 100) + '%' })
]),
E('div', { 'style': 'font-size: 10px; color: #707080; margin-top: 8px;' }, detail)
]);
},
generateReport: function() {
ui.showModal(_('Génération Rapport'), [
E('p', {}, 'Génération du rapport de santé...'),
E('div', { 'class': 'spinning' })
]);
// Stub: Report generation not yet implemented
setTimeout(function() {
ui.hideModal();
ui.addNotification(null, E('p', {}, '⚠️ Report generation feature coming soon'), 'info');
}, 1000);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});