secubox-openwrt/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/health.js
CyberMind-FR 66aa12d6b6 feat: Add SecuBox portal header to all System Hub views
Add unified SecuBox header navigation to all 10 System Hub views
for consistent portal integration when accessed from SecuBox Portal:
- overview.js, health.js, services.js, diagnostics.js
- logs.js, backup.js, components.js, settings.js
- dev-status.js, remote.js

Pattern: Wrap view content with secubox-page-wrapper and prepend
SbHeader.render() to hide LuCI sidebar when in portal context.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:46:48 +01:00

191 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require dom';
'require ui';
'require poll';
'require system-hub/api as API';
'require secubox-theme/theme as Theme';
'require system-hub/theme-assets as ThemeAssets';
'require system-hub/nav as HubNav';
'require secubox-portal/header as SbHeader';
var shLang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: shLang });
return view.extend({
healthData: null,
load: function() {
return API.getHealth();
},
render: function(data) {
this.healthData = data || {};
var container = E('div', { 'class': 'system-hub-dashboard sh-health-view' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
ThemeAssets.stylesheet('common.css'),
ThemeAssets.stylesheet('dashboard.css'),
ThemeAssets.stylesheet('health.css'),
HubNav.renderTabs('health'),
this.renderHero(),
this.renderMetricGrid(),
this.renderSummaryPanels(),
this.renderRecommendations(),
this.renderActions()
]);
var self = this;
poll.add(function() {
return API.getHealth().then(function(fresh) {
self.healthData = fresh || {};
self.updateWidgets();
});
}, 30);
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
wrapper.appendChild(SbHeader.render());
wrapper.appendChild(container);
return wrapper;
},
renderHero: function() {
var score = this.healthData.score || 0;
var state = score >= 80 ? 'healthy' : score >= 60 ? 'good' : score >= 40 ? 'warning' : 'critical';
return E('section', { 'class': 'sh-health-hero' }, [
E('div', {}, [
E('span', { 'class': 'sh-hero-eyebrow' }, _('System Health Monitor')),
E('h1', {}, _('Real-time health score') + ': ' + score + '/100'),
E('p', {}, _('CPU, RAM, storage, temperature, network, and services health summarized.'))
]),
E('div', { 'class': 'sh-health-score sh-' + state }, [
E('div', { 'id': 'sh-health-score' }, score),
E('span', {}, state.toUpperCase())
])
]);
},
renderMetricGrid: function() {
var cpu = this.healthData.cpu || {};
var mem = this.healthData.memory || {};
var disk = this.healthData.disk || {};
var temp = this.healthData.temperature || {};
return E('section', { 'class': 'sh-health-grid', 'id': 'sh-health-grid' }, [
this.renderMetricCard('cpu', '🔥', _('CPU Usage'), (cpu.usage || 0) + '%', cpu),
this.renderMetricCard('mem', '💾', _('Memory'), (mem.usage || 0) + '%', mem),
this.renderMetricCard('disk', '💿', _('Storage'), (disk.usage || 0) + '%', disk),
this.renderMetricCard('temp', '🌡', _('Temperature'), (temp.value || 0) + '°C', temp)
]);
},
renderMetricCard: function(id, icon, label, value, data) {
var percent = data.usage || data.value || 0;
var status = data.status || 'ok';
return E('div', { 'class': 'sh-health-card sh-' + status, 'data-id': id }, [
E('div', { 'class': 'sh-card-header' }, [
E('span', { 'class': 'sh-card-title-icon' }, icon),
E('div', { 'class': 'sh-card-title' }, label)
]),
E('div', { 'class': 'sh-health-value', 'id': 'sh-health-value-' + id }, value),
E('div', { 'class': 'sh-health-bar' }, [
E('div', {
'class': 'sh-health-bar-fill',
'id': 'sh-health-bar-' + id,
'style': 'width:' + Math.min(100, percent) + '%'
})
])
]);
},
renderSummaryPanels: function() {
return E('section', { 'class': 'sh-summary-grid' }, [
this.renderNetworkPanel(),
this.renderServicesPanel()
]);
},
renderNetworkPanel: function() {
var net = this.healthData.network || {};
var status = net.wan_up ? _('Connected') : _('Disconnected');
return E('div', { 'class': 'sh-summary-card' }, [
E('h3', {}, _('Network Summary')),
E('ul', {}, [
E('li', {}, _('WAN: ') + status),
E('li', {}, _('RX: ') + API.formatBytes(net.rx_bytes || 0)),
E('li', {}, _('TX: ') + API.formatBytes(net.tx_bytes || 0))
])
]);
},
renderServicesPanel: function() {
var svc = this.healthData.services || {};
return E('div', { 'class': 'sh-summary-card' }, [
E('h3', {}, _('Services Health')),
E('ul', {}, [
E('li', {}, _('Running: ') + (svc.running || 0)),
E('li', {}, _('Failed: ') + (svc.failed || 0))
])
]);
},
renderRecommendations: function() {
var recos = this.healthData.recommendations || [];
if (!recos.length) return E('section', { 'class': 'sh-card' }, []);
return E('section', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [E('span', { 'class': 'sh-card-title-icon' }, '📋'), _('Recommendations')])
]),
E('ul', { 'class': 'sh-reco-list', 'id': 'sh-reco-list' },
recos.map(function(text) {
return E('li', {}, text);
})
)
]);
},
renderActions: function() {
return E('section', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [E('span', { 'class': 'sh-card-title-icon' }, '🔧'), _('Maintenance Actions')])
]),
E('div', { 'class': 'sh-card-body sh-btn-group' }, [
E('button', {
'class': 'sh-btn sh-btn-primary',
'click': ui.createHandlerFn(this, 'runHealthCheck')
}, _('Run full health check')),
E('button', {
'class': 'sh-btn',
'click': function() { window.location.hash = '#admin/secubox/network/modes/overview'; }
}, _('Open Network Modes'))
])
]);
},
updateWidgets: function() {
var scoreNode = document.getElementById('sh-health-score');
if (scoreNode) scoreNode.textContent = this.healthData.score || 0;
['cpu','mem','disk','temp'].forEach(function(id) {
var bar = document.getElementById('sh-health-bar-' + id);
var data = this.healthData[id === 'cpu' ? 'cpu' : id === 'mem' ? 'memory' : id === 'disk' ? 'disk' : 'temperature'] || {};
var percent = data.usage || data.value || 0;
if (bar) bar.style.width = Math.min(100, percent) + '%';
var valNode = document.getElementById('sh-health-value-' + id);
if (valNode) valNode.textContent = (id === 'temp') ? (data.value || 0) + '°C' : (percent || 0) + '%';
}, this);
var list = document.getElementById('sh-reco-list');
if (list) {
dom.content(list, (this.healthData.recommendations || []).map(function(text) {
return E('li', {}, text);
}));
}
},
runHealthCheck: function() {
ui.addNotification(null, E('p', {}, _('Full health check started (see alerts).')), 'info');
}
});