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>
169 lines
6.3 KiB
JavaScript
169 lines
6.3 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require ui';
|
|
'require network-modes.api as api';
|
|
'require network-modes.helpers as helpers';
|
|
'require secubox-theme/theme as Theme';
|
|
|
|
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: _('VPN Relay Mode'),
|
|
|
|
load: function() {
|
|
return api.getVpnRelayConfig();
|
|
},
|
|
|
|
render: function(data) {
|
|
var cfg = data || {};
|
|
var vpn = cfg.vpn || {};
|
|
var policy = cfg.policy || {};
|
|
|
|
var container = E('div', { 'class': 'network-modes-dashboard vpnrelay-mode' }, [
|
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('network-modes/dashboard.css') }),
|
|
helpers.createNavigationTabs('vpnrelay'),
|
|
helpers.createHero({
|
|
icon: '🛡️',
|
|
title: _('VPN Relay'),
|
|
subtitle: _('Route LAN traffic through WireGuard/OpenVPN tunnels with policy routing and kill-switch.'),
|
|
actions: [
|
|
E('button', {
|
|
'class': 'nm-btn nm-btn-primary',
|
|
'data-action': 'vpnrelay-save',
|
|
'type': 'button'
|
|
}, '💾 ' + _('Save Settings')),
|
|
E('button', {
|
|
'class': 'nm-btn',
|
|
'type': 'button',
|
|
'click': ui.createHandlerFn(helpers, helpers.showGeneratedConfig, 'vpnrelay')
|
|
}, '📝 ' + _('Preview Config'))
|
|
]
|
|
}),
|
|
this.renderVpnSection(vpn, cfg),
|
|
this.renderPolicySection(policy)
|
|
]);
|
|
|
|
container.querySelectorAll('.nm-toggle-switch').forEach(function(toggle) {
|
|
toggle.addEventListener('click', function() {
|
|
this.classList.toggle('active');
|
|
});
|
|
});
|
|
|
|
this.bindActions(container);
|
|
return container;
|
|
},
|
|
|
|
renderVpnSection: function(vpn, cfg) {
|
|
var wgList = cfg.wireguard_interfaces || [];
|
|
var ovpnList = cfg.openvpn_profiles || [];
|
|
var ifaceList = cfg.available_interfaces || [];
|
|
|
|
return helpers.createSection({
|
|
icon: '🧩',
|
|
title: _('VPN Transport'),
|
|
body: [
|
|
E('div', { 'class': 'nm-form-grid' }, [
|
|
E('div', { 'class': 'nm-form-group' }, [
|
|
E('label', { 'class': 'nm-form-label' }, _('VPN Type')),
|
|
E('select', { 'class': 'nm-select', 'id': 'vpn-type' }, [
|
|
E('option', { 'value': 'wireguard', 'selected': (vpn.type || 'wireguard') === 'wireguard' }, 'WireGuard'),
|
|
E('option', { 'value': 'openvpn', 'selected': vpn.type === 'openvpn' }, 'OpenVPN')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'nm-form-group' }, [
|
|
E('label', { 'class': 'nm-form-label' }, _('VPN Provider Label')),
|
|
E('input', {
|
|
'class': 'nm-input',
|
|
'id': 'vpn-provider',
|
|
'value': vpn.provider || ''
|
|
})
|
|
]),
|
|
E('div', { 'class': 'nm-form-group' }, [
|
|
E('label', { 'class': 'nm-form-label' }, _('WireGuard Interface')),
|
|
E('select', { 'class': 'nm-select', 'id': 'vpn-wg-interface' },
|
|
(wgList.length ? wgList : ['wg0']).map(function(iface) {
|
|
return E('option', { 'value': iface, 'selected': (vpn.wg_interface || 'wg0') === iface }, iface);
|
|
}))
|
|
]),
|
|
E('div', { 'class': 'nm-form-group' }, [
|
|
E('label', { 'class': 'nm-form-label' }, _('OpenVPN Profile')),
|
|
E('select', { 'class': 'nm-select', 'id': 'vpn-ovpn-profile' }, [
|
|
E('option', { 'value': '' }, _('Select profile'))
|
|
].concat((ovpnList || []).map(function(profile) {
|
|
return E('option', { 'value': profile, 'selected': vpn.openvpn_profile === profile }, profile);
|
|
})))
|
|
]),
|
|
E('div', { 'class': 'nm-form-group' }, [
|
|
E('label', { 'class': 'nm-form-label' }, _('Upstream Interface')),
|
|
E('select', { 'class': 'nm-select', 'id': 'vpn-upstream' },
|
|
(ifaceList.length ? ifaceList : ['wan']).map(function(iface) {
|
|
return E('option', { 'value': iface, 'selected': (vpn.upstream_interface || 'wan') === iface }, iface);
|
|
}))
|
|
])
|
|
])
|
|
]
|
|
});
|
|
},
|
|
|
|
renderPolicySection: function(policy) {
|
|
return helpers.createSection({
|
|
icon: '🧭',
|
|
title: _('Routing & Security'),
|
|
body: [
|
|
E('div', { 'class': 'nm-toggle-list' }, [
|
|
this.renderToggle(_('Policy routing / split tunnel'), 'Allow per-subnet/per-device rules', 'vpn-policy-routing', policy.policy_routing !== 0 && policy.policy_routing !== '0'),
|
|
this.renderToggle(_('Force VPN DNS servers'), 'Override LAN DNS while VPN is active', 'vpn-dns-override', policy.dns_override !== 0 && policy.dns_override !== '0'),
|
|
this.renderToggle(_('VPN kill switch'), 'Cut WAN access if tunnel drops', 'vpn-kill-switch', policy.kill_switch !== 0 && policy.kill_switch !== '0'),
|
|
this.renderToggle(_('Allow LAN bypass'), 'Permit selected subnets to use WAN directly', 'vpn-lan-bypass', policy.lan_bypass === 1 || policy.lan_bypass === '1')
|
|
])
|
|
]
|
|
});
|
|
},
|
|
|
|
renderToggle: function(label, desc, id, active) {
|
|
return E('div', { 'class': 'nm-toggle' }, [
|
|
E('div', { 'class': 'nm-toggle-info' }, [
|
|
E('span', { 'class': 'nm-toggle-icon' }, '⚙️'),
|
|
E('div', {}, [
|
|
E('div', { 'class': 'nm-toggle-label' }, label),
|
|
E('div', { 'class': 'nm-toggle-desc' }, desc)
|
|
])
|
|
]),
|
|
E('div', {
|
|
'class': 'nm-toggle-switch' + (active ? ' active' : ''),
|
|
'id': id
|
|
})
|
|
]);
|
|
},
|
|
|
|
bindActions: function(container) {
|
|
var saveBtn = container.querySelector('[data-action="vpnrelay-save"]');
|
|
if (saveBtn)
|
|
saveBtn.addEventListener('click', ui.createHandlerFn(this, 'saveVpnRelaySettings', container));
|
|
},
|
|
|
|
saveVpnRelaySettings: function(container) {
|
|
var payload = {
|
|
vpn_type: (container.querySelector('#vpn-type') || {}).value,
|
|
vpn_provider: (container.querySelector('#vpn-provider') || {}).value,
|
|
wg_interface: (container.querySelector('#vpn-wg-interface') || {}).value,
|
|
openvpn_profile: (container.querySelector('#vpn-ovpn-profile') || {}).value,
|
|
upstream_interface: (container.querySelector('#vpn-upstream') || {}).value,
|
|
policy_routing: helpers.isToggleActive(container.querySelector('#vpn-policy-routing')) ? 1 : 0,
|
|
dns_override: helpers.isToggleActive(container.querySelector('#vpn-dns-override')) ? 1 : 0,
|
|
kill_switch: helpers.isToggleActive(container.querySelector('#vpn-kill-switch')) ? 1 : 0,
|
|
lan_bypass: helpers.isToggleActive(container.querySelector('#vpn-lan-bypass')) ? 1 : 0
|
|
};
|
|
|
|
return helpers.persistSettings('vpnrelay', payload);
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|