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

335 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require dom';
'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 });
function buildToggle(id, icon, title, desc, enabled) {
return E('div', { 'class': 'nm-toggle' }, [
E('div', { 'class': 'nm-toggle-info' }, [
E('span', { 'class': 'nm-toggle-icon' }, icon),
E('div', {}, [
E('div', { 'class': 'nm-toggle-label' }, title),
E('div', { 'class': 'nm-toggle-desc' }, desc)
])
]),
E('div', {
'class': 'nm-toggle-switch' + (enabled ? ' active' : ''),
'id': id
})
]);
}
return view.extend({
title: _('Router Mode'),
load: function() {
return api.getRouterConfig();
},
render: function(data) {
var config = data || {};
var wanConfig = config.wan || {};
var lanConfig = config.lan || {};
var fwConfig = config.firewall || {};
var proxyConfig = config.proxy || {};
var frontendConfig = config.https_frontend || {};
var vhosts = config.virtual_hosts || [];
var heroActions = [
E('button', { 'class': 'nm-btn nm-btn-primary', 'type': 'button', 'data-action': 'router-save' }, ['💾 ', _('Save & Apply')]),
E('button', { 'class': 'nm-btn', 'type': 'button', 'data-action': 'router-config' }, ['📝 ', _('Preview Config')]),
E('button', { 'class': 'nm-btn', 'type': 'button', 'data-action': 'router-wizard' }, ['🧭 ', _('Mode Wizard')])
];
var hero = helpers.createHero({
icon: '🌐',
title: _('Router Mode'),
subtitle: _('Full router stack with NAT, firewall, transparent proxying, HTTPS reverse proxy, and virtual hosts.'),
gradient: 'linear-gradient(135deg,#f97316,#fb923c)',
actions: heroActions
});
var statsRow = E('div', {
'style': 'display:flex;flex-wrap:wrap;gap:12px;margin-bottom:24px;'
}, [
helpers.createStatBadge({ label: _('WAN Protocol'), value: (wanConfig.protocol || 'DHCP').toUpperCase() }),
helpers.createStatBadge({ label: _('LAN Gateway'), value: lanConfig.ip_address || '192.168.1.1' }),
helpers.createStatBadge({ label: _('Firewall'), value: fwConfig.enabled !== false ? _('Active') : _('Disabled') }),
helpers.createStatBadge({ label: _('Proxy'), value: proxyConfig.enabled ? (proxyConfig.type || 'squid') : _('Disabled') })
]);
var wanSection = helpers.createSection({
title: _('WAN Configuration'),
icon: '🌍',
badge: (wanConfig.interface || 'eth1').toUpperCase(),
body: [
E('div', { 'class': 'nm-form-grid' }, [
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('WAN Interface')),
E('input', {
'class': 'nm-input',
'id': 'wan-interface',
'value': wanConfig.interface || 'eth1'
})
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('WAN Protocol')),
E('select', { 'class': 'nm-select', 'id': 'wan-protocol' },
(config.available_wan_protocols || ['dhcp', 'static', 'pppoe']).map(function(proto) {
return E('option', {
'value': proto,
'selected': proto === wanConfig.protocol
}, proto.toUpperCase());
})
)
])
]),
E('div', { 'style': 'margin-top:16px;' }, [
buildToggle('toggle-nat', '🔄', _('NAT / Masquerade'), _('Enable source NAT for LAN subnets'), wanConfig.nat_enabled !== false)
])
]
});
var lanSection = helpers.createSection({
title: _('LAN & DHCP'),
icon: '🏠',
badge: lanConfig.interface || 'br-lan',
body: [
E('div', { 'class': 'nm-form-grid' }, [
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('LAN IP Address')),
E('input', {
'class': 'nm-input',
'id': 'lan-ip',
'value': lanConfig.ip_address || ''
})
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('Netmask')),
E('input', {
'class': 'nm-input',
'id': 'lan-netmask',
'value': lanConfig.netmask || '255.255.255.0'
})
])
]),
E('p', { 'class': 'nm-alert nm-alert-info', 'style': 'margin-top:16px;' }, _('DHCP pools and LAN VLAN splitting configured per mode.'))
]
});
var firewallSection = helpers.createSection({
title: _('Firewall & Security'),
icon: '🛡️',
badge: fwConfig.enabled !== false ? _('Enabled') : _('Disabled'),
body: [
buildToggle('toggle-firewall', '🛡️', _('Enable Firewall'), _('WAN/LAN isolation with automatic rules'), fwConfig.enabled !== false),
buildToggle('toggle-synflood', '🌊', _('SYN Flood Protection'), _('Hardened TCP stack for DoS resilience'), fwConfig.syn_flood),
E('div', { 'class': 'nm-wifi-grid', 'style': 'margin-top:16px;' }, [
E('div', { 'class': 'nm-wifi-setting' }, [
E('div', { 'class': 'nm-wifi-setting-label' }, _('WAN Input')),
E('div', { 'class': 'nm-wifi-setting-value' }, fwConfig.input || 'REJECT')
]),
E('div', { 'class': 'nm-wifi-setting' }, [
E('div', { 'class': 'nm-wifi-setting-label' }, _('WAN Output')),
E('div', { 'class': 'nm-wifi-setting-value' }, fwConfig.output || 'ACCEPT')
]),
E('div', { 'class': 'nm-wifi-setting' }, [
E('div', { 'class': 'nm-wifi-setting-label' }, _('Forwarding')),
E('div', { 'class': 'nm-wifi-setting-value' }, fwConfig.forward || 'REJECT')
])
])
]
});
var proxySection = helpers.createSection({
title: _('Web Proxy & DoH'),
icon: '🦑',
badge: proxyConfig.enabled ? _('Active') : _('Disabled'),
body: [
buildToggle('toggle-proxy', '🦑', _('Enable Proxy'), _('HTTP/HTTPS caching with ACLs'), proxyConfig.enabled),
E('div', { 'class': 'nm-form-grid', 'style': 'margin-top:16px;' }, [
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('Proxy Type')),
E('select', { 'class': 'nm-select', 'id': 'proxy-type' }, [
E('option', { 'value': 'squid', 'selected': proxyConfig.type === 'squid' }, 'Squid'),
E('option', { 'value': 'tinyproxy', 'selected': proxyConfig.type === 'tinyproxy' }, 'TinyProxy'),
E('option', { 'value': 'privoxy', 'selected': proxyConfig.type === 'privoxy' }, 'Privoxy')
])
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('Proxy Port')),
E('input', {
'class': 'nm-input',
'type': 'number',
'id': 'proxy-port',
'value': proxyConfig.port || 3128
})
])
]),
buildToggle('toggle-transparent', '👁️', _('Transparent Proxy'), _('Intercept traffic without client configuration'), proxyConfig.transparent),
buildToggle('toggle-doh', '🔒', _('DNS over HTTPS'), _('Encrypt DNS queries through proxy stack'), proxyConfig.dns_over_https)
]
});
var frontendSection = helpers.createSection({
title: _('HTTPS Frontend & Virtual Hosts'),
icon: '🔐',
badge: vhosts.length + ' ' + _('hosts'),
body: [
buildToggle('toggle-frontend', '🌐', _('Enable HTTPS Frontend'), _('Reverse proxy TLS termination'), frontendConfig.enabled),
E('div', { 'class': 'nm-form-grid', 'style': 'margin-top:16px;' }, [
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('Frontend Type')),
E('select', { 'class': 'nm-select', 'id': 'frontend-type' }, [
E('option', { 'value': 'nginx', 'selected': frontendConfig.type === 'nginx' }, 'Nginx'),
E('option', { 'value': 'haproxy', 'selected': frontendConfig.type === 'haproxy' }, 'HAProxy'),
E('option', { 'value': 'caddy', 'selected': frontendConfig.type === 'caddy' }, 'Caddy')
])
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('Let\'s Encrypt')),
buildToggle('toggle-letsencrypt', '📜', _('Automatic Certificates'), _('Issue/renew via ACME'), frontendConfig.letsencrypt)
])
]),
vhosts.length ? helpers.createList(vhosts.map(function(vhost) {
return {
title: vhost.domain,
description: (vhost.backend || '') + ':' + (vhost.port || 80),
suffix: E('span', { 'class': 'nm-badge' }, vhost.ssl ? '🔒 HTTPS' : 'HTTP')
};
})) : E('p', { 'style': 'color: var(--nm-text-muted); margin: 16px 0;' }, _('No virtual hosts defined.')),
E('div', { 'style': 'display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-top:16px;' }, [
E('div', { 'class': 'nm-form-group', 'style': 'margin:0;' }, [
E('label', { 'class': 'nm-form-label' }, _('Domain')),
E('input', { 'class': 'nm-input', 'id': 'new-domain', 'placeholder': 'vpn.example.com' })
]),
E('div', { 'class': 'nm-form-group', 'style': 'margin:0;' }, [
E('label', { 'class': 'nm-form-label' }, _('Backend')),
E('input', { 'class': 'nm-input', 'id': 'new-backend', 'placeholder': '192.168.1.10' })
]),
E('div', { 'class': 'nm-form-group', 'style': 'margin:0;' }, [
E('label', { 'class': 'nm-form-label' }, _('Port')),
E('input', { 'class': 'nm-input', 'type': 'number', 'min': '1', 'max': '65535', 'id': 'new-port', 'value': '443' })
]),
E('div', { 'class': 'nm-form-group', 'style': 'margin:0;' }, [
E('label', { 'class': 'nm-form-label' }, _('SSL')),
E('select', { 'class': 'nm-select', 'id': 'new-ssl' }, [
E('option', { 'value': '1' }, _('Yes')),
E('option', { 'value': '0' }, _('No'))
])
]),
E('button', { 'class': 'nm-btn nm-btn-primary', 'type': 'button', 'data-action': 'router-add-vhost' }, ' ' + _('Add Host'))
])
]
});
var container = E('div', { 'class': 'network-modes-dashboard router-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('router'),
hero,
statsRow,
wanSection,
lanSection,
firewallSection,
proxySection,
frontendSection
]);
container.querySelectorAll('.nm-toggle-switch').forEach(function(toggle) {
toggle.addEventListener('click', function() {
this.classList.toggle('active');
});
});
this.bindRouterActions(container);
return container;
},
bindRouterActions: function(container) {
var saveBtn = container.querySelector('[data-action="router-save"]');
var wizardBtn = container.querySelector('[data-action="router-wizard"]');
var configBtn = container.querySelector('[data-action="router-config"]');
var addVhostBtn = container.querySelector('[data-action="router-add-vhost"]');
if (saveBtn)
saveBtn.addEventListener('click', ui.createHandlerFn(this, 'saveRouterSettings', container));
if (wizardBtn)
wizardBtn.addEventListener('click', ui.createHandlerFn(this, 'openWizard'));
if (configBtn)
configBtn.addEventListener('click', ui.createHandlerFn(helpers, helpers.showGeneratedConfig, 'router'));
if (addVhostBtn)
addVhostBtn.addEventListener('click', ui.createHandlerFn(this, 'addVirtualHost', container));
},
saveRouterSettings: function(container) {
var payload = {
wan_interface: container.querySelector('#wan-interface') ? container.querySelector('#wan-interface').value : 'eth1',
wan_protocol: container.querySelector('#wan-protocol') ? container.querySelector('#wan-protocol').value : 'dhcp',
nat_enabled: helpers.isToggleActive(container.querySelector('#toggle-nat')) ? 1 : 0,
firewall_enabled: helpers.isToggleActive(container.querySelector('#toggle-firewall')) ? 1 : 0,
proxy_enabled: helpers.isToggleActive(container.querySelector('#toggle-proxy')) ? 1 : 0,
proxy_type: container.querySelector('#proxy-type') ? container.querySelector('#proxy-type').value : 'squid',
proxy_port: container.querySelector('#proxy-port') ? parseInt(container.querySelector('#proxy-port').value, 10) || 3128 : 3128,
transparent_proxy: helpers.isToggleActive(container.querySelector('#toggle-transparent')) ? 1 : 0,
dns_over_https: helpers.isToggleActive(container.querySelector('#toggle-doh')) ? 1 : 0,
https_frontend: helpers.isToggleActive(container.querySelector('#toggle-frontend')) ? 1 : 0,
frontend_type: container.querySelector('#frontend-type') ? container.querySelector('#frontend-type').value : 'nginx',
letsencrypt: helpers.isToggleActive(container.querySelector('#toggle-letsencrypt')) ? 1 : 0
};
return helpers.persistSettings('router', payload);
},
openWizard: function() {
window.location.hash = '#admin/secubox/network/network-modes/wizard';
},
addVirtualHost: function(container) {
var domainInput = container.querySelector('#new-domain');
var backendInput = container.querySelector('#new-backend');
if (!domainInput || !backendInput)
return;
var domain = domainInput.value.trim();
var backend = backendInput.value.trim();
var portValue = parseInt((container.querySelector('#new-port') || { value: '443' }).value, 10);
var sslValue = (container.querySelector('#new-ssl') || { value: '1' }).value === '1' ? 1 : 0;
if (!domain || !backend) {
ui.addNotification(null, E('p', {}, _('Domain and backend are required')), 'error');
return;
}
ui.showModal(_('Adding virtual host...'), [
E('p', { 'class': 'spinning' }, _('Saving virtual host entry'))
]);
return api.addVirtualHost({
domain: domain,
backend: backend,
port: isNaN(portValue) ? 80 : portValue,
ssl: sslValue
}).then(function(result) {
ui.hideModal();
if (result && result.success) {
ui.addNotification(null, E('p', {}, result.message || _('Virtual host added')), 'info');
window.location.reload();
} else {
ui.addNotification(null, E('p', {}, (result && result.error) || _('Failed to add virtual host')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
}
});