secubox-openwrt/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/overview.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

385 lines
13 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require ui';
'require network-modes.api as api';
'require network-modes.helpers as helpers';
'require secubox/help as Help';
'require secubox-theme/theme as Theme';
// Initialize global theme respecting LuCI preferences
var nmLang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: nmLang });
return view.extend({
title: _('Network Modes'),
load: function() {
return api.getAllData();
},
handleModeSwitch: function(mode) {
var self = this;
ui.showModal(_('Switch Mode'), [
E('p', {}, _('Are you sure you want to switch to ') + mode + _(' mode?')),
E('p', { 'class': 'nm-alert nm-alert-warning' }, [
E('span', { 'class': 'nm-alert-icon' }, '⚠️'),
E('div', {}, [
E('div', { 'class': 'nm-alert-title' }, _('Warning')),
E('div', { 'class': 'nm-alert-text' }, _('This will change network configuration. A backup will be created.'))
])
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Cancel')),
' ',
E('button', {
'class': 'btn cbi-button-positive',
'click': function() {
ui.hideModal();
return api.applyMode(mode).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', {}, result.message), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', {}, 'Error: ' + result.error), 'error');
}
});
}
}, _('Switch Mode'))
])
]);
},
render: function(data) {
var self = this;
var status = data.status || {};
var modesData = (data.modes || {}).modes || [];
var currentMode = status.current_mode || 'router';
// Build a full mode map using backend data + fallbacks
var baseOrder = ['router', 'doublenat', 'multiwan', 'vpnrelay', 'bridge', 'accesspoint', 'relay', 'travel', 'sniffer'];
var modeInfos = {};
// Prime with RPC payload so description/icon/features stay in sync
modesData.forEach(function(mode) {
var fallback = api.getModeInfo(mode.id || '');
modeInfos[mode.id] = Object.assign({}, fallback, {
id: mode.id,
name: mode.name || (fallback && fallback.name) || mode.id,
icon: mode.icon || (fallback && fallback.icon) || '🌐',
description: mode.description || (fallback && fallback.description) || '',
features: Array.isArray(mode.features) && mode.features.length
? mode.features
: (fallback && fallback.features) || []
});
});
// Ensure every known mode has a definition, even if RPC omitted it
baseOrder.concat(['sniffer']).forEach(function(mode) {
if (!modeInfos[mode]) {
modeInfos[mode] = api.getModeInfo(mode);
}
});
// Preserve RPC ordering but guarantee canonical fallback + sniffer tab
var modeOrder = modesData.map(function(mode) { return mode.id; });
baseOrder.forEach(function(mode) {
if (modeOrder.indexOf(mode) === -1)
modeOrder.push(mode);
});
if (modeOrder.indexOf('sniffer') === -1)
modeOrder.push('sniffer');
var currentModeInfo = modeInfos[currentMode] || api.getModeInfo(currentMode);
var view = E('div', { 'class': 'network-modes-dashboard' }, [
// Load global theme CSS
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/help.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('network-modes/dashboard.css') }),
helpers.createNavigationTabs('overview'),
// Header
E('div', { 'class': 'nm-header' }, [
E('div', { 'class': 'nm-logo' }, [
E('div', { 'class': 'nm-logo-icon' }, '🌐'),
E('div', { 'class': 'nm-logo-text' }, ['Network ', E('span', {}, 'Configuration')])
]),
E('div', { 'class': 'nm-mode-badge ' + currentMode }, [
E('span', { 'class': 'nm-mode-dot' }),
currentModeInfo ? currentModeInfo.name : currentMode
]),
Help.createHelpButton('network-modes', 'header', {
icon: '📖',
label: _('Help')
})
]),
// Current Mode Display Card
E('div', { 'class': 'nm-current-mode-card' }, [
E('div', { 'class': 'nm-current-mode-header' }, [
E('div', { 'class': 'nm-current-mode-icon' }, currentModeInfo ? currentModeInfo.icon : '🌐'),
E('div', { 'class': 'nm-current-mode-info' }, [
E('div', { 'class': 'nm-current-mode-label' }, 'Current Network Mode'),
E('h2', { 'class': 'nm-current-mode-name' }, currentModeInfo ? currentModeInfo.name : currentMode)
])
]),
E('div', { 'class': 'nm-current-mode-description' },
currentModeInfo ? currentModeInfo.description : 'Unknown mode'),
E('div', { 'class': 'nm-current-mode-config' }, [
E('div', { 'class': 'nm-config-item' }, [
E('span', { 'class': 'nm-config-label' }, 'WAN IP:'),
E('span', { 'class': 'nm-config-value' }, status.wan_ip || 'N/A')
]),
E('div', { 'class': 'nm-config-item' }, [
E('span', { 'class': 'nm-config-label' }, 'LAN IP:'),
E('span', { 'class': 'nm-config-value' }, status.lan_ip || 'N/A')
]),
E('div', { 'class': 'nm-config-item' }, [
E('span', { 'class': 'nm-config-label' }, 'DHCP Server:'),
E('span', { 'class': 'nm-config-value' }, status.dhcp_enabled ? 'Enabled' : 'Disabled')
])
]),
E('button', {
'class': 'nm-change-mode-btn',
'click': function() {
window.location.hash = '#admin/secubox/network/network-modes/wizard';
}
}, '🔄 Change Mode')
]),
// Mode Comparison Table
E('div', { 'class': 'nm-comparison-card' }, [
E('h3', { 'class': 'nm-comparison-title' }, 'Mode Comparison Table'),
E('div', { 'class': 'nm-comparison-table-wrapper' }, [
E('table', { 'class': 'nm-comparison-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, 'Feature')
].concat(baseOrder.map(function(modeId) {
var info = modeInfos[modeId] || api.getModeInfo(modeId);
return E('th', {
'class': currentMode === modeId ? 'active-mode' : ''
}, (info.icon || '') + ' ' + (info.name || modeId));
})))
]),
E('tbody', {}, (function() {
var comparisonRows = [
{
label: 'Use Case',
values: {
router: 'Home/Office',
doublenat: 'Behind ISP box',
multiwan: 'Dual uplinks',
vpnrelay: 'VPN gateway',
bridge: 'Layer 2 passthrough',
accesspoint: 'WiFi Hotspot',
relay: 'WiFi Extender',
travel: 'Hotel / Travel kit',
sniffer: 'Packet capture / TAP'
}
},
{
label: 'WAN Ports',
values: {
router: '1 port',
doublenat: 'WAN DHCP',
multiwan: '2 uplinks',
vpnrelay: 'VPN tunnel',
bridge: 'All bridged',
accesspoint: '1 uplink',
relay: 'WiFi uplink',
travel: 'WiFi or USB',
sniffer: 'Monitor source port'
}
},
{
label: 'LAN Ports',
values: {
router: 'Multiple',
doublenat: 'LAN + Guest',
multiwan: 'All ports',
vpnrelay: 'Policy-based',
bridge: 'All ports',
accesspoint: 'All ports',
relay: 'All ports',
travel: 'All ports',
sniffer: 'Mirror to capture'
}
},
{
label: 'WiFi Role',
values: {
router: 'Access Point',
doublenat: 'Router',
multiwan: 'Router',
vpnrelay: 'Router',
bridge: 'Optional AP',
accesspoint: 'AP only',
relay: 'Client + AP',
travel: 'Client + AP',
sniffer: 'Monitor mode'
}
},
{
label: 'DHCP Server',
values: {
router: 'Yes',
doublenat: 'Yes',
multiwan: 'Yes',
vpnrelay: 'Optional',
bridge: 'No',
accesspoint: 'No',
relay: 'Yes',
travel: 'Yes',
sniffer: 'No'
}
},
{
label: 'NAT',
values: {
router: 'Enabled',
doublenat: 'Double layer',
multiwan: 'Enabled',
vpnrelay: 'VPN NAT',
bridge: 'Disabled',
accesspoint: 'Disabled',
relay: 'Enabled',
travel: 'Enabled',
sniffer: 'Disabled'
}
}
];
return comparisonRows.map(function(row) {
return E('tr', {}, [
E('td', { 'class': 'feature-label' }, row.label)
].concat(baseOrder.map(function(modeId) {
return E('td', {
'class': currentMode === modeId ? 'active-mode' : ''
}, row.values[modeId] || '—');
})));
});
})())
])
])
]),
// Mode Selection Grid
E('div', { 'class': 'nm-modes-grid' },
modeOrder.map(function(modeId) {
var info = modeInfos[modeId];
if (!info)
return null;
var isActive = modeId === currentMode;
return E('div', {
'class': 'nm-mode-card ' + modeId + (isActive ? ' active' : ''),
'click': function() {
if (!isActive) {
self.handleModeSwitch(modeId);
}
}
}, [
isActive ? E('div', { 'class': 'nm-mode-active-indicator' }, 'Active') : '',
E('div', { 'class': 'nm-mode-header' }, [
E('div', { 'class': 'nm-mode-icon' }, info.icon),
E('div', { 'class': 'nm-mode-title' }, [
E('h3', {}, info.name),
E('p', {}, modeId.charAt(0).toUpperCase() + modeId.slice(1) + ' Mode')
])
]),
E('div', { 'class': 'nm-mode-description' }, info.description),
E('div', { 'class': 'nm-mode-features' },
(info.features || []).map(function(f) {
return E('span', { 'class': 'nm-mode-feature' }, [
E('span', { 'class': 'nm-mode-feature-icon' }, '✓'),
f
]);
})
)
]);
}).filter(function(card) { return !!card; })
),
// Interfaces Status
E('div', { 'class': 'nm-card' }, [
E('div', { 'class': 'nm-card-header' }, [
E('div', { 'class': 'nm-card-title' }, [
E('span', { 'class': 'nm-card-title-icon' }, '🔌'),
'Network Interfaces'
]),
E('div', { 'class': 'nm-card-badge' }, (status.interfaces || []).length + ' interfaces')
]),
E('div', { 'class': 'nm-card-body' }, [
E('div', { 'class': 'nm-interfaces-grid' },
(status.interfaces || []).map(function(iface) {
var icon = '🔌';
if (iface.name.startsWith('wlan') || iface.name.startsWith('wl')) icon = '📶';
else if (iface.name.startsWith('wg')) icon = '🔐';
else if (iface.name.startsWith('br')) icon = '🌉';
else if (iface.name.startsWith('eth')) icon = '🔗';
return E('div', { 'class': 'nm-interface-card' }, [
E('div', { 'class': 'nm-interface-icon' }, icon),
E('div', { 'class': 'nm-interface-info' }, [
E('div', { 'class': 'nm-interface-name' }, iface.name),
E('div', { 'class': 'nm-interface-ip' }, iface.ip || 'No IP')
]),
E('div', { 'class': 'nm-interface-status ' + iface.state })
]);
})
)
])
]),
// Services Status
E('div', { 'class': 'nm-card' }, [
E('div', { 'class': 'nm-card-header' }, [
E('div', { 'class': 'nm-card-title' }, [
E('span', { 'class': 'nm-card-title-icon' }, '🔧'),
'Services Status'
])
]),
E('div', { 'class': 'nm-card-body' }, [
E('div', { 'class': 'nm-interfaces-grid' },
[
{ name: 'Firewall', key: 'firewall', icon: '🛡️' },
{ name: 'DHCP/DNS', key: 'dnsmasq', icon: '📡' },
{ name: 'Netifyd', key: 'netifyd', icon: '🔍' },
{ name: 'Nginx', key: 'nginx', icon: '🌐' },
{ name: 'Squid', key: 'squid', icon: '🦑' }
].map(function(svc) {
var running = status.services && status.services[svc.key];
return E('div', { 'class': 'nm-interface-card' }, [
E('div', { 'class': 'nm-interface-icon' }, svc.icon),
E('div', { 'class': 'nm-interface-info' }, [
E('div', { 'class': 'nm-interface-name' }, svc.name),
E('div', { 'class': 'nm-interface-ip' }, running ? 'Running' : 'Stopped')
]),
E('div', { 'class': 'nm-interface-status ' + (running ? 'up' : 'down') })
]);
})
)
])
])
]);
// Include CSS
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('network-modes/dashboard.css') });
document.head.appendChild(cssLink);
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});