360 lines
13 KiB
JavaScript
360 lines
13 KiB
JavaScript
'use strict';
|
||
'require view';
|
||
'require dom';
|
||
'require ui';
|
||
'require network-modes.api as api';
|
||
|
||
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 view = E('div', { 'class': 'network-modes-dashboard' }, [
|
||
// Header
|
||
E('div', { 'class': 'nm-header' }, [
|
||
E('div', { 'class': 'nm-logo' }, [
|
||
E('div', { 'class': 'nm-logo-icon' }, '🌐'),
|
||
E('div', { 'class': 'nm-logo-text' }, ['Router ', E('span', {}, 'Mode')])
|
||
])
|
||
]),
|
||
|
||
// Description
|
||
E('div', { 'class': 'nm-alert nm-alert-info' }, [
|
||
E('span', { 'class': 'nm-alert-icon' }, '🌐'),
|
||
E('div', {}, [
|
||
E('div', { 'class': 'nm-alert-title' }, 'Full Router with Advanced Features'),
|
||
E('div', { 'class': 'nm-alert-text' },
|
||
'Complete router functionality with WAN management, NAT, firewall, ' +
|
||
'web proxy, and HTTPS reverse proxy for multiple domains.')
|
||
])
|
||
]),
|
||
|
||
// WAN Configuration
|
||
E('div', { 'class': 'nm-card' }, [
|
||
E('div', { 'class': 'nm-card-header' }, [
|
||
E('div', { 'class': 'nm-card-title' }, [
|
||
E('span', { 'class': 'nm-card-title-icon' }, '🌍'),
|
||
'WAN Configuration'
|
||
])
|
||
]),
|
||
E('div', { 'class': 'nm-card-body' }, [
|
||
E('div', { 'class': 'nm-form-group' }, [
|
||
E('label', { 'class': 'nm-form-label' }, 'WAN Interface'),
|
||
E('input', {
|
||
'class': 'nm-input',
|
||
'type': 'text',
|
||
'value': wanConfig.interface || 'eth1',
|
||
'id': 'wan-interface'
|
||
})
|
||
]),
|
||
|
||
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', { 'class': 'nm-toggle' }, [
|
||
E('div', { 'class': 'nm-toggle-info' }, [
|
||
E('span', { 'class': 'nm-toggle-icon' }, '🔄'),
|
||
E('div', {}, [
|
||
E('div', { 'class': 'nm-toggle-label' }, 'NAT / Masquerade'),
|
||
E('div', { 'class': 'nm-toggle-desc' }, 'Network Address Translation for LAN')
|
||
])
|
||
]),
|
||
E('div', {
|
||
'class': 'nm-toggle-switch' + (wanConfig.nat_enabled !== false ? ' active' : ''),
|
||
'id': 'toggle-nat'
|
||
})
|
||
])
|
||
])
|
||
]),
|
||
|
||
// Firewall
|
||
E('div', { 'class': 'nm-card' }, [
|
||
E('div', { 'class': 'nm-card-header' }, [
|
||
E('div', { 'class': 'nm-card-title' }, [
|
||
E('span', { 'class': 'nm-card-title-icon' }, '🛡️'),
|
||
'Firewall'
|
||
]),
|
||
E('div', { 'class': 'nm-card-badge' }, fwConfig.enabled ? 'Enabled' : 'Disabled')
|
||
]),
|
||
E('div', { 'class': 'nm-card-body' }, [
|
||
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' }, 'Enable Firewall'),
|
||
E('div', { 'class': 'nm-toggle-desc' }, 'Protect network with firewall rules')
|
||
])
|
||
]),
|
||
E('div', {
|
||
'class': 'nm-toggle-switch' + (fwConfig.enabled !== false ? ' active' : ''),
|
||
'id': 'toggle-firewall'
|
||
})
|
||
]),
|
||
|
||
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' }, 'SYN Flood Protection'),
|
||
E('div', { 'class': 'nm-toggle-desc' }, 'Prevent SYN flood DoS attacks')
|
||
])
|
||
]),
|
||
E('div', {
|
||
'class': 'nm-toggle-switch' + (fwConfig.syn_flood ? ' active' : ''),
|
||
'id': 'toggle-synflood'
|
||
})
|
||
]),
|
||
|
||
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', 'style': 'font-size: 14px' }, 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', 'style': 'font-size: 14px' }, fwConfig.output || 'ACCEPT')
|
||
]),
|
||
E('div', { 'class': 'nm-wifi-setting' }, [
|
||
E('div', { 'class': 'nm-wifi-setting-label' }, 'WAN Forward'),
|
||
E('div', { 'class': 'nm-wifi-setting-value', 'style': 'font-size: 14px' }, fwConfig.forward || 'REJECT')
|
||
])
|
||
])
|
||
])
|
||
]),
|
||
|
||
// Proxy Configuration
|
||
E('div', { 'class': 'nm-card' }, [
|
||
E('div', { 'class': 'nm-card-header' }, [
|
||
E('div', { 'class': 'nm-card-title' }, [
|
||
E('span', { 'class': 'nm-card-title-icon' }, '🦑'),
|
||
'Web Proxy'
|
||
]),
|
||
E('div', { 'class': 'nm-card-badge' }, proxyConfig.enabled ? 'Active' : 'Disabled')
|
||
]),
|
||
E('div', { 'class': 'nm-card-body' }, [
|
||
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' }, 'Enable Proxy'),
|
||
E('div', { 'class': 'nm-toggle-desc' }, 'HTTP/HTTPS caching proxy')
|
||
])
|
||
]),
|
||
E('div', {
|
||
'class': 'nm-toggle-switch' + (proxyConfig.enabled ? ' active' : ''),
|
||
'id': 'toggle-proxy'
|
||
})
|
||
]),
|
||
|
||
E('div', { 'class': 'nm-form-group', 'style': 'margin-top: 16px' }, [
|
||
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',
|
||
'value': proxyConfig.port || 3128,
|
||
'id': 'proxy-port'
|
||
})
|
||
]),
|
||
|
||
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' }, 'Transparent Proxy'),
|
||
E('div', { 'class': 'nm-toggle-desc' }, 'Intercept traffic without client config')
|
||
])
|
||
]),
|
||
E('div', {
|
||
'class': 'nm-toggle-switch' + (proxyConfig.transparent ? ' active' : ''),
|
||
'id': 'toggle-transparent'
|
||
})
|
||
]),
|
||
|
||
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' }, 'DNS over HTTPS'),
|
||
E('div', { 'class': 'nm-toggle-desc' }, 'Encrypt DNS queries')
|
||
])
|
||
]),
|
||
E('div', {
|
||
'class': 'nm-toggle-switch' + (proxyConfig.dns_over_https ? ' active' : ''),
|
||
'id': 'toggle-doh'
|
||
})
|
||
])
|
||
])
|
||
]),
|
||
|
||
// HTTPS Frontend / Reverse Proxy
|
||
E('div', { 'class': 'nm-card' }, [
|
||
E('div', { 'class': 'nm-card-header' }, [
|
||
E('div', { 'class': 'nm-card-title' }, [
|
||
E('span', { 'class': 'nm-card-title-icon' }, '🔐'),
|
||
'HTTPS Reverse Proxy'
|
||
]),
|
||
E('div', { 'class': 'nm-card-badge' }, vhosts.length + ' virtual hosts')
|
||
]),
|
||
E('div', { 'class': 'nm-card-body' }, [
|
||
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' }, 'Enable HTTPS Frontend'),
|
||
E('div', { 'class': 'nm-toggle-desc' }, 'Reverse proxy for multiple domains')
|
||
])
|
||
]),
|
||
E('div', {
|
||
'class': 'nm-toggle-switch' + (frontendConfig.enabled ? ' active' : ''),
|
||
'id': 'toggle-frontend'
|
||
})
|
||
]),
|
||
|
||
E('div', { 'class': 'nm-form-group', 'style': 'margin-top: 16px' }, [
|
||
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-toggle' }, [
|
||
E('div', { 'class': 'nm-toggle-info' }, [
|
||
E('span', { 'class': 'nm-toggle-icon' }, '📜'),
|
||
E('div', {}, [
|
||
E('div', { 'class': 'nm-toggle-label' }, 'Let\'s Encrypt'),
|
||
E('div', { 'class': 'nm-toggle-desc' }, 'Automatic SSL certificates')
|
||
])
|
||
]),
|
||
E('div', {
|
||
'class': 'nm-toggle-switch' + (frontendConfig.letsencrypt ? ' active' : ''),
|
||
'id': 'toggle-letsencrypt'
|
||
})
|
||
]),
|
||
|
||
// Virtual Hosts Table
|
||
vhosts.length > 0 ?
|
||
E('div', { 'style': 'margin-top: 20px' }, [
|
||
E('h4', { 'style': 'margin: 0 0 12px 0; font-size: 14px' }, 'Virtual Hosts'),
|
||
E('table', { 'class': 'nm-vhost-table' }, [
|
||
E('thead', {}, [
|
||
E('tr', {}, [
|
||
E('th', {}, 'Domain'),
|
||
E('th', {}, 'Backend'),
|
||
E('th', {}, 'Port'),
|
||
E('th', {}, 'SSL'),
|
||
E('th', {}, 'Actions')
|
||
])
|
||
]),
|
||
E('tbody', {},
|
||
vhosts.map(function(vhost) {
|
||
return E('tr', {}, [
|
||
E('td', { 'class': 'domain' }, vhost.domain),
|
||
E('td', { 'class': 'mono' }, vhost.backend),
|
||
E('td', { 'class': 'mono' }, vhost.port || 80),
|
||
E('td', {}, [
|
||
E('span', { 'class': 'nm-ssl-badge ' + (vhost.ssl ? 'enabled' : 'disabled') },
|
||
vhost.ssl ? '🔒 HTTPS' : '⚠️ HTTP')
|
||
]),
|
||
E('td', {}, [
|
||
E('button', { 'class': 'nm-btn', 'style': 'padding: 4px 8px; font-size: 12px' }, '🗑️')
|
||
])
|
||
]);
|
||
})
|
||
)
|
||
])
|
||
]) :
|
||
E('p', { 'style': 'color: var(--nm-text-muted); font-size: 13px; margin-top: 16px' },
|
||
'No virtual hosts configured. Add domains below.'),
|
||
|
||
// Add Virtual Host
|
||
E('div', { 'style': 'margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--nm-border)' }, [
|
||
E('h4', { 'style': 'margin: 0 0 12px 0; font-size: 14px' }, 'Add Virtual Host'),
|
||
E('div', { 'style': 'display: grid; grid-template-columns: 2fr 2fr 1fr auto; gap: 12px; align-items: end' }, [
|
||
E('div', { 'class': 'nm-form-group', 'style': 'margin: 0' }, [
|
||
E('label', { 'class': 'nm-form-label' }, 'Domain'),
|
||
E('input', { 'class': 'nm-input', 'type': 'text', 'placeholder': 'example.com', 'id': 'new-domain' })
|
||
]),
|
||
E('div', { 'class': 'nm-form-group', 'style': 'margin: 0' }, [
|
||
E('label', { 'class': 'nm-form-label' }, 'Backend'),
|
||
E('input', { 'class': 'nm-input', 'type': 'text', 'placeholder': '127.0.0.1:8080', 'id': 'new-backend' })
|
||
]),
|
||
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', 'style': 'height: 46px' }, '➕ Add')
|
||
])
|
||
])
|
||
])
|
||
]),
|
||
|
||
// Actions
|
||
E('div', { 'class': 'nm-btn-group' }, [
|
||
E('button', { 'class': 'nm-btn nm-btn-primary' }, [
|
||
E('span', {}, '💾'),
|
||
'Save Settings'
|
||
]),
|
||
E('button', { 'class': 'nm-btn' }, [
|
||
E('span', {}, '🔄'),
|
||
'Apply & Restart'
|
||
]),
|
||
E('button', { 'class': 'nm-btn' }, [
|
||
E('span', {}, '📝'),
|
||
'Generate Config'
|
||
])
|
||
])
|
||
]);
|
||
|
||
// Toggle handlers
|
||
view.querySelectorAll('.nm-toggle-switch').forEach(function(toggle) {
|
||
toggle.addEventListener('click', function() {
|
||
this.classList.toggle('active');
|
||
});
|
||
});
|
||
|
||
// 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
|
||
});
|