secubox-openwrt/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js
CyberMind-FR de40c8e533 feat: Release v0.4.3 - Dual menu access and enhanced permissions
This release adds dual menu access for Network Modes (both SecuBox and
LuCI Network menus) and significantly expands RPCD permissions for all
mode configuration operations.

## Network Modes - Dual Menu Access (2 files)
- Added Network Modes to standard LuCI Network menu (admin/network/modes)
- Maintains existing SecuBox menu location (admin/secubox/network/modes)
- Users can now access Network Modes from both locations
- Menu order: 60 in Network menu, 10 in SecuBox Network category

## Network Modes - Enhanced Permissions (1 file)
Added 13+ new RPCD methods to ACL for complete mode management:

Read permissions:
- preview_changes
- sniffer_config, ap_config, relay_config, router_config
- travel_config, doublenat_config, multiwan_config, vpnrelay_config
- travel_scan_networks

Write permissions:
- apply_mode, confirm_mode, rollback
- update_settings
- generate_wireguard_keys, apply_wireguard_config
- apply_mtu_clamping, enable_tcp_bbr
- add_vhost, generate_config

## Network Modes - View Updates (11 files)
Updated all mode views for consistency:
- helpers.js: 28 lines refactored
- overview.js: Enhanced view structure
- All mode views: wizard, router, multiwan, doublenat, accesspoint,
  relay, vpnrelay, travel, sniffer

## Theme Enhancements (1 file)
- theme.js: 89 lines added
- Enhanced theme initialization and configuration
- Improved component styling support

## SecuBox Dashboard (2 files)
- Updated dashboard.js and modules.js
- Improved view rendering and integration

## System Hub (3 files)
- Enhanced logs.js, overview.js, services.js
- Better view consistency and functionality

Summary:
- 19 files changed (+282, -36)
- Dual menu access for Network Modes
- 13+ new RPCD permission methods
- All network mode views updated
- Theme significantly enhanced

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-28 19:24:40 +01:00

449 lines
14 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require dom';
'require poll';
'require secubox/api as API';
'require secubox-theme/theme as Theme';
// 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,
activeCategory: 'all',
load: function() {
return this.refreshData();
},
refreshData: function() {
var self = this;
return Promise.all([
API.getDashboardData(),
API.getSystemHealth(),
API.getAlerts()
]).then(function(data) {
self.dashboardData = data[0] || {};
self.healthData = data[1] || {};
self.alertsData = data[2] || {};
return data;
});
},
render: function() {
var container = E('div', { 'class': 'secubox-dashboard' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/dashboard.css') }),
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 || {};
return E('header', { 'class': 'sb-header' }, [
E('div', { 'class': 'sb-header-info' }, [
E('div', { 'class': 'sb-header-icon' }, '🚀'),
E('div', {}, [
E('h1', { 'class': 'sb-title' }, 'SecuBox Control Center'),
E('p', { 'class': 'sb-subtitle' }, 'Security · Network · System Automation')
])
]),
E('div', { 'class': 'sb-header-meta' }, [
this.renderBadge('v' + (status.version || '0.0.0')),
this.renderBadge('⏱ ' + API.formatUptime(status.uptime)),
this.renderBadge('🖥 ' + (status.hostname || 'SecuBox'), 'sb-badge-ghost')
])
]);
},
renderBadge: function(text, extraClass) {
return E('span', { 'class': 'sb-badge ' + (extraClass || '') }, text);
},
renderStatsGrid: function() {
var modules = this.dashboardData.modules || [];
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 || modules.length, icon: '📦', id: 'sb-stat-total' },
{ label: _('Active Services'), value: counts.running || 0, 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') }
];
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.dashboardData.modules || [];
if (this.activeCategory === 'all')
return modules;
return modules.filter(function(module) {
return (module.category || '').toLowerCase() === this.activeCategory;
}, this);
},
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: ') + (module.version || '0.0.0')),
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) : ''
])
]);
},
updateDynamicSections: function() {
this.updateStats();
this.updateModuleGrid();
this.updateHealthMetrics();
this.updateAlerts();
},
updateStats: function() {
var modules = this.dashboardData.modules || [];
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 || modules.length,
'sb-stat-running': counts.running || 0,
'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));
}
});