187 lines
6.2 KiB
JavaScript
187 lines
6.2 KiB
JavaScript
'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';
|
|
|
|
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);
|
|
|
|
return container;
|
|
},
|
|
|
|
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');
|
|
}
|
|
});
|