secubox-openwrt/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/health.js
CyberMind-FR 8e53825ad5 release: v0.2.2 - Design System v0.3.0 & Comprehensive Documentation
🎨 Design System v0.3.0 (Demo-inspired)
- New dark palette: #0a0a0f, #6366f1→#8b5cf6 gradients
- Typography: Inter + JetBrains Mono
- Compact stats grid (130px min)
- Gradient text effects with background-clip
- Sticky navigation tabs
- Enhanced card borders and hover effects

📚 Comprehensive Documentation Suite
- DEVELOPMENT-GUIDELINES.md (33KB, 900+ lines)
  - 9 major sections: Design, Architecture, RPCD, ACL, JS, CSS, Errors, Validation, Deployment
  - Complete code templates and best practices
  - Common error diagnostics and solutions
- QUICK-START.md (6.4KB)
  - 8 critical rules for immediate reference
  - Quick code templates
  - Error quick fixes table
- deploy-module-template.sh (8.1KB)
  - Standardized deployment with automatic backup
  - Permission fixes, cache clearing, verification
- Updated CLAUDE.md, README.md with documentation index
- Updated .claude/README.md to v2.0

🔄 Version Updates
- luci-app-secubox: 0.1.2 → 0.2.2
- luci-app-system-hub: 0.1.1 → 0.2.2
- Updated all version strings (api.js, overview.js, CSS files)

🎯 CSS Enhancements
- common.css: Complete rewrite with demo palette
- overview.css: Dashboard header with gradient
- services.css: Updated version to 0.2.2
- components.css: Updated version to 0.2.2

🔧 Critical Rules Documented
1. RPCD naming: file = ubus object (luci. prefix)
2. Menu path = view file location
3. Permissions: 755 (RPCD), 644 (CSS/JS)
4. ALWAYS run validate-modules.sh
5. CSS variables only (no hardcode)
6. Dark mode mandatory
7. Typography: Inter + JetBrains Mono
8. Gradients: --sh-primary → --sh-primary-end

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 18:55:19 +01:00

346 lines
10 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require ui';
'require poll';
'require system-hub/api as API';
'require system-hub/theme as Theme';
return view.extend({
healthData: null,
load: function() {
return Promise.all([
API.getHealth(),
Theme.getTheme()
]);
},
render: function(data) {
this.healthData = data[0] || {};
var theme = data[1];
var container = E('div', { 'class': 'system-hub-health' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/overview.css') }),
// Header with health score
this.renderHeader(),
// System Metrics Grid
E('div', { 'class': 'sh-metrics-grid' }, [
this.renderMetricCard('CPU', this.healthData.cpu),
this.renderMetricCard('Memory', this.healthData.memory),
this.renderMetricCard('Disk', this.healthData.disk),
this.renderMetricCard('Temperature', this.healthData.temperature)
]),
// Network & Services Info
E('div', { 'class': 'sh-info-grid' }, [
this.renderNetworkCard(),
this.renderServicesCard()
]),
// Recommendations
this.healthData.recommendations && this.healthData.recommendations.length > 0
? this.renderRecommendationsCard()
: E('div'),
// Actions
this.renderActionsCard()
]);
// Setup auto-refresh
poll.add(L.bind(function() {
return API.getHealth().then(L.bind(function(refreshData) {
this.healthData = refreshData || {};
this.updateDashboard();
}, this));
}, this), 30);
return container;
},
renderHeader: function() {
var score = this.healthData.score || 0;
var scoreClass = score >= 80 ? 'excellent' : (score >= 60 ? 'good' : (score >= 40 ? 'warning' : 'critical'));
var scoreLabel = score >= 80 ? 'Excellent' : (score >= 60 ? 'Bon' : (score >= 40 ? 'Attention' : 'Critique'));
return E('div', { 'class': 'sh-overview-header' }, [
E('div', { 'class': 'sh-overview-title' }, [
E('h2', {}, [
E('span', { 'class': 'sh-title-icon' }, '💚'),
' Health Monitor'
]),
E('p', { 'class': 'sh-overview-subtitle' },
'Surveillance en temps réel de la santé du système')
]),
E('div', { 'class': 'sh-overview-score' }, [
E('div', { 'class': 'sh-score-circle sh-score-' + scoreClass }, [
E('div', { 'class': 'sh-score-value' }, score),
E('div', { 'class': 'sh-score-label' }, scoreLabel)
])
])
]);
},
renderMetricCard: function(type, data) {
if (!data) return E('div');
var config = this.getMetricConfig(type, data);
return E('div', { 'class': 'sh-metric-card sh-metric-' + config.status }, [
E('div', { 'class': 'sh-metric-header' }, [
E('span', { 'class': 'sh-metric-icon' }, config.icon),
E('span', { 'class': 'sh-metric-title' }, config.title)
]),
E('div', { 'class': 'sh-metric-value' }, config.value),
E('div', { 'class': 'sh-metric-progress' }, [
E('div', {
'class': 'sh-metric-progress-bar',
'style': 'width: ' + config.percentage + '%; background: ' + config.color
})
]),
E('div', { 'class': 'sh-metric-details' }, config.details)
]);
},
getMetricConfig: function(type, data) {
switch(type) {
case 'CPU':
return {
icon: '🔥',
title: 'CPU Usage',
value: (data.usage || 0) + '%',
percentage: data.usage || 0,
status: data.status || 'ok',
color: this.getStatusColor(data.usage || 0),
details: 'Load: ' + (data.load_1m || '0') + ' • ' + (data.cores || 0) + ' cores'
};
case 'Memory':
var usedMB = ((data.used_kb || 0) / 1024).toFixed(0);
var totalMB = ((data.total_kb || 0) / 1024).toFixed(0);
return {
icon: '💾',
title: 'Memory',
value: (data.usage || 0) + '%',
percentage: data.usage || 0,
status: data.status || 'ok',
color: this.getStatusColor(data.usage || 0),
details: usedMB + ' MB / ' + totalMB + ' MB used'
};
case 'Disk':
var usedGB = ((data.used_kb || 0) / 1024 / 1024).toFixed(1);
var totalGB = ((data.total_kb || 0) / 1024 / 1024).toFixed(1);
return {
icon: '💿',
title: 'Disk Space',
value: (data.usage || 0) + '%',
percentage: data.usage || 0,
status: data.status || 'ok',
color: this.getStatusColor(data.usage || 0),
details: usedGB + ' GB / ' + totalGB + ' GB used'
};
case 'Temperature':
return {
icon: '🌡️',
title: 'Temperature',
value: (data.value || 0) + '°C',
percentage: Math.min((data.value || 0), 100),
status: data.status || 'ok',
color: this.getTempColor(data.value || 0),
details: 'Status: ' + (data.status || 'unknown')
};
default:
return {
icon: '📊',
title: type,
value: 'N/A',
percentage: 0,
status: 'unknown',
color: '#64748b',
details: 'No data'
};
}
},
getStatusColor: function(usage) {
if (usage >= 90) return '#ef4444';
if (usage >= 75) return '#f59e0b';
if (usage >= 50) return '#3b82f6';
return '#22c55e';
},
getTempColor: function(temp) {
if (temp >= 80) return '#ef4444';
if (temp >= 70) return '#f59e0b';
if (temp >= 60) return '#3b82f6';
return '#22c55e';
},
renderNetworkCard: function() {
var wan_status = this.healthData.network ? this.healthData.network.wan_up : false;
return E('div', { 'class': 'sh-info-card' }, [
E('div', { 'class': 'sh-info-card-header' }, [
E('h3', {}, [
E('span', { 'style': 'margin-right: 8px;' }, '🌐'),
'Network Status'
])
]),
E('div', { 'class': 'sh-info-card-body' }, [
E('div', { 'class': 'sh-info-list' }, [
this.renderInfoRow('📡', 'WAN Connection',
E('span', {
'class': 'sh-status-badge sh-status-' + (wan_status ? 'ok' : 'error')
}, wan_status ? '✓ Connected' : '✗ Disconnected')
),
this.renderInfoRow('🔌', 'Network Mode',
this.healthData.network ? this.healthData.network.status : 'unknown'
)
])
])
]);
},
renderServicesCard: function() {
var running = this.healthData.services ? this.healthData.services.running : 0;
var failed = this.healthData.services ? this.healthData.services.failed : 0;
var total = running + failed;
return E('div', { 'class': 'sh-info-card' }, [
E('div', { 'class': 'sh-info-card-header' }, [
E('h3', {}, [
E('span', { 'style': 'margin-right: 8px;' }, '⚙️'),
'System Services'
])
]),
E('div', { 'class': 'sh-info-card-body' }, [
E('div', { 'class': 'sh-info-list' }, [
this.renderInfoRow('▶️', 'Running',
E('span', { 'class': 'sh-status-badge sh-status-ok' }, running + ' services')
),
this.renderInfoRow('⏹️', 'Failed',
failed > 0
? E('span', { 'class': 'sh-status-badge sh-status-error' }, failed + ' services')
: E('span', { 'class': 'sh-status-badge sh-status-ok' }, 'None')
),
this.renderInfoRow('📊', 'Health Rate',
total > 0 ? ((running / total * 100).toFixed(0) + '%') : 'N/A'
)
])
])
]);
},
renderRecommendationsCard: function() {
return E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('h3', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '💡'),
'Recommendations'
]),
E('div', { 'class': 'sh-card-badge', 'style': 'background: #f59e0b;' },
this.healthData.recommendations.length)
]),
E('div', { 'class': 'sh-card-body' },
this.healthData.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', { 'style': 'flex: 1; color: var(--sh-text-primary);' }, rec)
]);
})
)
]);
},
renderActionsCard: function() {
return E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('h3', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '🛠️'),
'Actions'
])
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', { 'style': 'display: flex; gap: 12px; flex-wrap: wrap;' }, [
E('button', {
'class': 'sh-btn sh-btn-primary',
'click': L.bind(this.generateReport, this)
}, [
E('span', {}, '📋'),
E('span', {}, 'Generate Report')
]),
E('button', {
'class': 'sh-btn sh-btn-secondary',
'click': function() {
ui.addNotification(null, E('p', '⚠️ Email feature coming soon'), 'info');
}
}, [
E('span', {}, '📧'),
E('span', {}, 'Send Email')
]),
E('button', {
'class': 'sh-btn sh-btn-secondary',
'click': function() {
ui.addNotification(null, E('p', '⚠️ PDF export coming soon'), 'info');
}
}, [
E('span', {}, '📥'),
E('span', {}, 'Download PDF')
])
])
])
]);
},
renderInfoRow: function(icon, label, value) {
return E('div', { 'class': 'sh-info-row' }, [
E('span', { 'class': 'sh-info-icon' }, icon),
E('span', { 'class': 'sh-info-label' }, label),
E('span', { 'class': 'sh-info-value' }, value)
]);
},
updateDashboard: function() {
var metricsGrid = document.querySelector('.sh-metrics-grid');
if (metricsGrid) {
dom.content(metricsGrid, [
this.renderMetricCard('CPU', this.healthData.cpu),
this.renderMetricCard('Memory', this.healthData.memory),
this.renderMetricCard('Disk', this.healthData.disk),
this.renderMetricCard('Temperature', this.healthData.temperature)
]);
}
// Update health score
var scoreValue = document.querySelector('.sh-score-value');
var scoreCircle = document.querySelector('.sh-score-circle');
if (scoreValue && scoreCircle) {
var score = this.healthData.score || 0;
var scoreClass = score >= 80 ? 'excellent' : (score >= 60 ? 'good' : (score >= 40 ? 'warning' : 'critical'));
scoreValue.textContent = score;
scoreCircle.className = 'sh-score-circle sh-score-' + scoreClass;
}
},
generateReport: function() {
ui.showModal(_('Generating Report'), [
E('p', {}, 'Generating health report...'),
E('div', { 'class': 'spinning' })
]);
setTimeout(function() {
ui.hideModal();
ui.addNotification(null, E('p', '⚠️ Report generation feature coming soon'), 'info');
}, 1000);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});