secubox-openwrt/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js
2025-12-29 09:03:49 +01:00

536 lines
17 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require dom';
'require poll';
'require secubox/api as API';
'require secubox/theme as Theme';
'require secubox/nav as SecuNav';
// Load theme resources once
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox-theme/secubox-theme.css')
}));
var secuLang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: secuLang });
return view.extend({
dashboardData: null,
healthData: null,
alertsData: null,
modulesList: [],
activeCategory: 'all',
load: function() {
return this.refreshData();
},
refreshData: function() {
var self = this;
return Promise.all([
API.getDashboardData(),
API.getSystemHealth(),
API.getAlerts(),
API.getModules()
]).then(function(data) {
self.dashboardData = data[0] || {};
self.healthData = data[1] || {};
self.alertsData = data[2] || {};
self.modulesList = (data[3] && data[3].modules) || [];
return data;
});
},
render: function() {
var container = E('div', { 'class': 'secubox-dashboard' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/dashboard.css') }),
SecuNav.renderTabs('dashboard'),
this.renderHeader(),
this.renderStatsGrid(),
this.renderMainLayout()
]);
var self = this;
poll.add(function() {
return self.refreshData().then(function() {
self.updateDynamicSections();
});
}, 30);
return container;
},
renderHeader: function() {
var status = this.dashboardData.status || {};
var counts = this.dashboardData.counts || {};
var moduleStats = this.getModuleStats();
var alertsCount = (this.alertsData.alerts || []).length;
var healthScore = (this.healthData.overall && this.healthData.overall.score) || 0;
var version = status.version || _('Unknown');
var stats = [
{ icon: '🏷️', label: _('Version'), value: version },
{ icon: '📦', label: _('Modules'), value: counts.total || moduleStats.total || 0 },
{ icon: '🟢', label: _('Running'), value: counts.running || moduleStats.running || 0, tone: (counts.running || moduleStats.running) > 0 ? 'success' : '' },
{ icon: '⚠️', label: _('Alerts'), value: alertsCount, tone: alertsCount ? 'warn' : '' },
{ icon: '❤️', label: _('Health'), value: healthScore + '/100' }
];
return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '🚀'),
_('SecuBox Control Center')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('Security · Network · System automation'))
]),
E('div', { 'class': 'sh-header-meta' }, stats.map(this.renderHeaderChip, this))
]);
},
renderHeaderChip: function(stat) {
return E('div', { 'class': 'sh-header-chip' + (stat.tone ? ' ' + stat.tone : '') }, [
E('span', { 'class': 'sh-chip-icon' }, stat.icon || '•'),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, stat.label),
E('strong', {}, stat.value.toString())
])
]);
},
renderStatsGrid: function() {
var moduleStats = this.getModuleStats();
var counts = this.dashboardData.counts || {};
var alertsCount = (this.alertsData.alerts || []).length;
var healthScore = (this.healthData.overall && this.healthData.overall.score) || 0;
var stats = [
{ label: _('Total Modules'), value: counts.total || moduleStats.total, icon: '📦', id: 'sb-stat-total' },
{ label: _('Installed'), value: counts.installed || moduleStats.installed, icon: '💾', id: 'sb-stat-installed' },
{ label: _('Active Services'), value: counts.running || moduleStats.running, icon: '🟢', id: 'sb-stat-running' },
{ label: _('System Health'), value: healthScore + '/100', icon: '❤️', id: 'sb-stat-health' },
{ label: _('Alerts'), value: alertsCount, icon: '⚠️', id: 'sb-stat-alerts' }
];
return E('section', { 'class': 'sb-stats-grid' },
stats.map(function(stat) {
return E('div', { 'class': 'sb-stat-card' }, [
E('div', { 'class': 'sb-stat-icon' }, stat.icon),
E('div', { 'class': 'sb-stat-content' }, [
E('div', { 'class': 'sb-stat-value', 'id': stat.id }, stat.value),
E('div', { 'class': 'sb-stat-label' }, stat.label)
])
]);
})
);
},
renderMainLayout: function() {
return E('div', { 'class': 'sb-main-grid' }, [
E('div', { 'class': 'sb-grid-left' }, [
this.renderModulesSection(),
this.renderSystemHealthPanel()
]),
E('div', { 'class': 'sb-grid-right' }, [
this.renderQuickActions(),
this.renderAlertsTimeline()
])
]);
},
renderModulesSection: function() {
var self = this;
var filters = [
{ id: 'all', label: _('All Modules') },
{ id: 'security', label: _('Security') },
{ id: 'network', label: _('Network') },
{ id: 'services', label: _('Services') },
{ id: 'system', label: _('System') }
];
return E('section', { 'class': 'sb-card' }, [
E('div', { 'class': 'sb-card-header' }, [
E('div', {}, [
E('h2', {}, _('Module Overview')),
E('p', { 'class': 'sb-card-subtitle' }, _('Monitor, configure, and access every SecuBox module'))
]),
E('div', { 'class': 'cyber-tablist cyber-tablist--pills sb-module-tabs' }, filters.map(function(filter) {
return E('button', {
'class': 'cyber-tab cyber-tab--pill' + (self.activeCategory === filter.id ? ' is-active' : ''),
'type': 'button',
'data-category': filter.id,
'click': function() {
self.activeCategory = filter.id;
self.updateModuleGrid();
self.syncModuleFilterTabs();
}
}, [
E('span', { 'class': 'cyber-tab-label' }, filter.label)
]);
}))
]),
E('div', { 'class': 'sb-module-grid', 'id': 'sb-module-grid' },
this.getFilteredModules().map(this.renderModuleCard, this))
]);
},
getFilteredModules: function() {
var modules = (this.modulesList && this.modulesList.length)
? this.modulesList
: (this.dashboardData.modules || []);
if (this.activeCategory === 'all')
return modules;
return modules.filter(function(module) {
return (module.category || '').toLowerCase() === this.activeCategory;
}, this);
},
getModuleVersion: function(module) {
if (!module)
return '—';
var candidates = [
module.version,
module.pkg_version,
module.package_version,
module.packageVersion,
module.Version
];
for (var i = 0; i < candidates.length; i++) {
var value = candidates[i];
if (typeof value === 'number')
return String(value);
if (typeof value === 'string' && value.trim())
return value.trim();
}
return '—';
},
renderModuleCard: function(module) {
var status = module.status || 'unknown';
var statusLabel = status === 'active' ? _('Running') :
status === 'error' ? _('Error') :
status === 'disabled' ? _('Disabled') : _('Unknown');
return E('div', { 'class': 'sb-module-card sb-status-' + status }, [
E('div', { 'class': 'sb-module-header' }, [
E('div', { 'class': 'sb-module-icon' }, module.icon || '📦'),
E('div', {}, [
E('h3', {}, module.name || module.id),
E('p', {}, module.description || '')
]),
E('span', { 'class': 'sb-status-pill' }, statusLabel)
]),
E('div', { 'class': 'sb-module-meta' }, [
E('span', {}, _('Version: ') + this.getModuleVersion(module)),
E('span', {}, _('Category: ') + (module.category || 'other'))
]),
E('div', { 'class': 'sb-module-actions' }, [
E('button', {
'class': 'sb-btn sb-btn-ghost',
'type': 'button',
'click': function() {
window.location.href = L.url('admin/secubox/modules');
}
}, _('View Details')),
E('button', {
'class': 'sb-btn sb-btn-primary',
'type': 'button',
'click': function() {
if (module.route) {
window.location.href = L.url(module.route);
}
}
}, _('Configure'))
])
]);
},
renderSystemHealthPanel: function() {
var self = this;
var health = this.healthData || {};
var cpu = health.cpu || {};
var memory = health.memory || {};
var disk = health.disk || {};
var network = health.network || {};
var metrics = [
{
id: 'cpu',
label: _('CPU Usage'),
value: cpu.percent || 0,
detail: _('Load: ') + (cpu.load_1min || '0.00'),
icon: '🔥'
},
{
id: 'memory',
label: _('Memory'),
value: memory.percent || 0,
detail: API.formatBytes((memory.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((memory.total_kb || 0) * 1024),
icon: '💾'
},
{
id: 'disk',
label: _('Storage'),
value: disk.percent || 0,
detail: API.formatBytes((disk.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((disk.total_kb || 0) * 1024),
icon: '💿'
},
{
id: 'network',
label: _('Network'),
value: network.wan_up ? 100 : 0,
detail: network.wan_up ? _('WAN online') : _('WAN offline'),
icon: '🌐'
}
];
return E('section', { 'class': 'sb-card' }, [
E('div', { 'class': 'sb-card-header' }, [
E('h2', {}, _('System Health Dashboard')),
E('p', { 'class': 'sb-card-subtitle' }, _('Real-time telemetry for CPU, RAM, Disk, Network'))
]),
E('div', { 'class': 'sb-health-grid' },
metrics.map(function(metric) {
return self.renderHealthMetric(metric);
}))
]);
},
renderHealthMetric: function(metric) {
var percent = Math.min(100, Math.max(0, Math.round(metric.value)));
var severity = percent < 70 ? 'ok' : percent < 90 ? 'warn' : 'danger';
return E('div', { 'class': 'sb-health-metric' }, [
E('div', { 'class': 'sb-health-icon' }, metric.icon),
E('div', { 'class': 'sb-health-info' }, [
E('div', { 'class': 'sb-health-label' }, metric.label),
E('div', { 'class': 'sb-health-detail', 'id': 'sb-health-detail-' + metric.id }, metric.detail)
]),
E('div', { 'class': 'sb-health-bar' }, [
E('div', {
'class': 'sb-health-progress sb-' + severity,
'id': 'sb-health-bar-' + metric.id,
'style': 'width:' + percent + '%'
})
]),
E('span', { 'class': 'sb-health-percent', 'id': 'sb-health-percent-' + metric.id }, percent + '%')
]);
},
renderQuickActions: function() {
var self = this;
var actions = [
{ id: 'restart_services', label: _('Restart All Services'), icon: '🔄', variant: 'orange' },
{ id: 'update_packages', label: _('Update Packages'), icon: '⬆️', variant: 'blue' },
{ id: 'view_logs', label: _('View System Logs'), icon: '📜', variant: 'indigo' },
{ id: 'export_config', label: _('Export Configuration'), icon: '📦', variant: 'green' }
];
return E('section', { 'class': 'sb-card' }, [
E('div', { 'class': 'sb-card-header' }, [
E('h2', {}, _('Quick Actions')),
E('p', { 'class': 'sb-card-subtitle' }, _('Frequently performed maintenance tasks'))
]),
E('div', { 'class': 'sb-actions-grid' },
actions.map(function(action) {
return E('button', {
'class': 'sb-action-btn sb-' + action.variant,
'type': 'button',
'click': function() {
self.runQuickAction(action.id);
}
}, [
E('span', { 'class': 'sb-action-icon' }, action.icon),
E('span', { 'class': 'sb-action-label' }, action.label)
]);
}))
]);
},
runQuickAction: function(actionId) {
ui.showModal(_('Executing action...'), [
E('p', { 'class': 'spinning' }, _('Running ') + actionId + ' ...')
]);
return API.quickAction(actionId).then(function(result) {
ui.hideModal();
if (result && result.success === false) {
ui.addNotification(null, E('p', {}, result.message || _('Action failed')), 'error');
} else {
ui.addNotification(null, E('p', {}, _('Action completed successfully')), 'info');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
},
renderAlertsTimeline: function() {
var alerts = (this.alertsData.alerts || []).slice(0, 10);
return E('section', { 'class': 'sb-card' }, [
E('div', { 'class': 'sb-card-header' }, [
E('h2', {}, _('Alert Timeline')),
E('p', { 'class': 'sb-card-subtitle' }, _('Latest security and system events'))
]),
alerts.length === 0 ?
E('div', { 'class': 'sb-empty-state' }, [
E('div', { 'class': 'sb-empty-icon' }, '🎉'),
E('p', {}, _('No alerts in the last 24 hours'))
]) :
E('div', { 'class': 'sb-alert-list', 'id': 'sb-alert-list' },
alerts.map(this.renderAlertItem, this))
]);
},
renderAlertItem: function(alert) {
var severity = (alert.severity || 'info').toLowerCase();
var timestamp = alert.timestamp || alert.time || '';
return E('div', { 'class': 'sb-alert-item sb-' + severity }, [
E('div', { 'class': 'sb-alert-time' }, timestamp),
E('div', { 'class': 'sb-alert-details' }, [
E('strong', {}, alert.title || alert.message || _('Alert')),
alert.description ? E('p', {}, alert.description) : '',
alert.source ? E('span', { 'class': 'sb-alert-meta' }, alert.source) : ''
])
]);
},
getSystemVersion: function() {
if (this.dashboardData && this.dashboardData.status && this.dashboardData.status.version)
return this.dashboardData.status.version;
if (this.dashboardData && this.dashboardData.release)
return this.dashboardData.release;
return '0.0.0';
},
getSystemUptime: function() {
if (this.dashboardData && this.dashboardData.status && this.dashboardData.status.uptime)
return this.dashboardData.status.uptime;
if (this.healthData && typeof this.healthData.uptime === 'number')
return this.healthData.uptime;
return 0;
},
getModuleStats: function() {
var modules = (this.modulesList && this.modulesList.length)
? this.modulesList
: (this.dashboardData.modules || []);
var installed = modules.filter(function(mod) {
return mod.installed || mod.in_uci === 1 || mod.enabled === 1 || mod.status === 'active';
}).length;
var running = modules.filter(function(mod) {
return mod.running || mod.status === 'active';
}).length;
return {
list: modules,
total: modules.length,
installed: installed,
running: running
};
},
updateDynamicSections: function() {
this.updateStats();
this.updateModuleGrid();
this.updateHealthMetrics();
this.updateAlerts();
},
updateStats: function() {
var moduleStats = this.getModuleStats();
var counts = this.dashboardData.counts || {};
var alertsCount = (this.alertsData.alerts || []).length;
var healthScore = (this.healthData.overall && this.healthData.overall.score) || 0;
var stats = {
'sb-stat-total': counts.total || moduleStats.total,
'sb-stat-installed': counts.installed || moduleStats.installed,
'sb-stat-running': counts.running || moduleStats.running,
'sb-stat-health': healthScore + '/100',
'sb-stat-alerts': alertsCount
};
Object.keys(stats).forEach(function(id) {
var node = document.getElementById(id);
if (node) node.textContent = stats[id];
});
},
updateModuleGrid: function() {
var grid = document.getElementById('sb-module-grid');
if (!grid) return;
dom.content(grid, this.getFilteredModules().map(this.renderModuleCard, this));
this.syncModuleFilterTabs();
},
syncModuleFilterTabs: function() {
var tabs = document.querySelectorAll('.sb-module-tabs .cyber-tab');
tabs.forEach(function(tab) {
var match = tab.getAttribute('data-category') === this.activeCategory;
tab.classList.toggle('is-active', match);
}, this);
},
updateHealthMetrics: function() {
var health = this.healthData || {};
var cpu = health.cpu || {};
var memory = health.memory || {};
var disk = health.disk || {};
var network = health.network || {};
var map = {
cpu: { value: cpu.percent || 0, detail: _('Load: ') + (cpu.load_1min || '0.00') },
memory: {
value: memory.percent || 0,
detail: API.formatBytes((memory.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((memory.total_kb || 0) * 1024)
},
disk: {
value: disk.percent || 0,
detail: API.formatBytes((disk.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((disk.total_kb || 0) * 1024)
},
network: { value: network.wan_up ? 100 : 0, detail: network.wan_up ? _('WAN online') : _('WAN offline') }
};
Object.keys(map).forEach(function(key) {
var percent = Math.min(100, Math.max(0, Math.round(map[key].value)));
var severity = percent < 70 ? 'ok' : percent < 90 ? 'warn' : 'danger';
var bar = document.getElementById('sb-health-bar-' + key);
var pct = document.getElementById('sb-health-percent-' + key);
var detail = document.getElementById('sb-health-detail-' + key);
if (bar) {
bar.style.width = percent + '%';
bar.className = 'sb-health-progress sb-' + severity;
}
if (pct) pct.textContent = percent + '%';
if (detail) detail.textContent = map[key].detail;
});
},
updateAlerts: function() {
var list = document.getElementById('sb-alert-list');
if (!list) return;
var alerts = (this.alertsData.alerts || []).slice(0, 10);
dom.content(list, alerts.map(this.renderAlertItem, this));
}
});