mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-05 01:44:16 +08:00
340 lines
11 KiB
JavaScript
340 lines
11 KiB
JavaScript
/*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Copyright (C) 2022-2025 ImmortalWrt.org
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
'require baseclass';
|
|
'require form';
|
|
'require uci';
|
|
|
|
'require homeproxy-pro as hp';
|
|
'require tools.widgets as widgets';
|
|
|
|
/* Routing nodes sub-grid (the per-outbound entries that routing_rule
|
|
* actions dispatch to). Called after routing.render() so the SectionValue
|
|
* grid nesting is intact. */
|
|
function renderRoutingNodes(ctx) {
|
|
const { s, proxy_nodes } = ctx;
|
|
let o, ss, so;
|
|
|
|
s.tab('routing_node', _('Routing Nodes'));
|
|
o = s.taboption('routing_node', form.SectionValue, '_routing_node', form.GridSection, 'routing_node');
|
|
o.depends('routing_mode', 'custom');
|
|
|
|
ss = o.subsection;
|
|
ss.addremove = true;
|
|
ss.rowcolors = true;
|
|
ss.sortable = true;
|
|
ss.nodescriptions = true;
|
|
ss.modaltitle = L.bind(hp.loadModalTitle, hp, _('Routing node'), _('Add a routing node'), 'homeproxy-pro');
|
|
ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
|
/* Plain wrapper, not L.bind(): see hp.renderSectionAdd() for why the bound
|
|
* form hands a call site's button factory to the wrong argument. */
|
|
ss.renderSectionAdd = function(extra_class) {
|
|
return hp.renderSectionAdd(ss, extra_class);
|
|
};
|
|
|
|
so = ss.option(form.Value, 'label', _('Label'));
|
|
so.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
|
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'routing_node', 'label');
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.Flag, 'enabled', _('Enable'));
|
|
so.default = so.enabled;
|
|
so.rmempty = false;
|
|
so.editable = true;
|
|
|
|
so = ss.option(form.ListValue, 'node', _('Node'),
|
|
_('Outbound node'));
|
|
so.value('urltest', _('URLTest'));
|
|
for (let i in proxy_nodes)
|
|
so.value(i, proxy_nodes[i]);
|
|
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'routing_node', 'node');
|
|
so.editable = true;
|
|
|
|
so = ss.option(form.ListValue, 'domain_resolver', _('Domain resolver'),
|
|
_('For resolving domain name in the server address.'));
|
|
so.load = function(section_id) {
|
|
delete this.keylist;
|
|
delete this.vallist;
|
|
|
|
this.value('', _('Default'));
|
|
this.value('default-dns', _('Default DNS (issued by WAN)'));
|
|
this.value('system-dns', _('System DNS'));
|
|
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
|
if (res.enabled === '1')
|
|
this.value(res['.name'], res.label);
|
|
});
|
|
|
|
return this.super('load', section_id);
|
|
}
|
|
so.depends({'node': 'urltest', '!reverse': true});
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.ListValue, 'domain_strategy', _('Domain strategy'),
|
|
_('The domain strategy for resolving the domain name in the address.'));
|
|
for (let i in hp.dns_strategy)
|
|
so.value(i, hp.dns_strategy[i]);
|
|
so.depends({'node': 'urltest', '!reverse': true});
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(widgets.DeviceSelect, 'bind_interface', _('Bind interface'),
|
|
_('The network interface to bind to.'));
|
|
so.multiple = false;
|
|
so.noaliases = true;
|
|
so.depends({'outbound': '', 'node': /^((?!urltest$).)+$/});
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.ListValue, 'outbound', _('Outbound'),
|
|
_('The tag of the upstream outbound.<br/>Other dial fields will be ignored when enabled.'));
|
|
so.load = function(section_id) {
|
|
delete this.keylist;
|
|
delete this.vallist;
|
|
|
|
this.value('', _('Direct'));
|
|
uci.sections('homeproxy-pro', 'routing_node', (res) => {
|
|
if (res['.name'] !== section_id && res.enabled === '1')
|
|
this.value(res['.name'], res.label);
|
|
});
|
|
|
|
return this.super('load', section_id);
|
|
}
|
|
so.validate = function(section_id, value) {
|
|
if (section_id && value) {
|
|
let node = this.section.formvalue(section_id, 'node');
|
|
|
|
let conflict = false;
|
|
uci.sections('homeproxy-pro', 'routing_node', (res) => {
|
|
if (res['.name'] !== section_id) {
|
|
if (res.outbound === section_id && res['.name'] == value)
|
|
conflict = true;
|
|
else if (res.node === 'urltest' && res.urltest_nodes?.includes(node) && res['.name'] == value)
|
|
conflict = true;
|
|
}
|
|
});
|
|
if (conflict)
|
|
return _('Recursive outbound detected!');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
so.depends({'node': 'urltest', '!reverse': true});
|
|
so.editable = true;
|
|
|
|
so = ss.option(hp.CBIStaticList, 'urltest_nodes', _('URLTest nodes'),
|
|
_('List of nodes to test.'));
|
|
for (let i in proxy_nodes)
|
|
so.value(i, proxy_nodes[i]);
|
|
so.depends('node', 'urltest');
|
|
/* The repository's other validators take (section_id, value) and check
|
|
* `value` before dereferencing it. This one re-read the value through
|
|
* `this.section.formvalue()`, which returns null when the widget is not
|
|
* in the DOM, so `.length` threw a TypeError - the save aborted with a
|
|
* console error instead of the "non-empty value" message. The framework
|
|
* already hands the current value in (getValidator is
|
|
* L.bind(this.validate, this, section_id)), so use it. */
|
|
so.validate = function(section_id, value) {
|
|
if (section_id && value && !value.length)
|
|
return _('Expecting: %s').format(_('non-empty value'));
|
|
|
|
return true;
|
|
}
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.Value, 'urltest_url', _('Test URL'),
|
|
_('The URL to test.'));
|
|
so.placeholder = 'https://www.gstatic.com/generate_204';
|
|
so.validate = function(section_id, value) {
|
|
if (section_id && value) {
|
|
try {
|
|
let url = new URL(value);
|
|
if (!url.hostname)
|
|
return _('Expecting: %s').format(_('valid URL'));
|
|
}
|
|
catch(e) {
|
|
return _('Expecting: %s').format(_('valid URL'));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
so.depends('node', 'urltest');
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.Value, 'urltest_interval', _('Test interval'),
|
|
_('The test interval in seconds.'));
|
|
so.datatype = 'uinteger';
|
|
so.placeholder = '180';
|
|
so.validate = function(section_id, value) {
|
|
if (section_id && value) {
|
|
let idle_timeout = this.section.formvalue(section_id, 'urltest_idle_timeout') || '1800';
|
|
if (parseInt(value) > parseInt(idle_timeout))
|
|
return _('Test interval must be less or equal than idle timeout.');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
so.depends('node', 'urltest');
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.Value, 'urltest_tolerance', _('Test tolerance'),
|
|
_('The test tolerance in milliseconds.'));
|
|
so.datatype = 'uinteger';
|
|
so.placeholder = '50';
|
|
so.depends('node', 'urltest');
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.Value, 'urltest_idle_timeout', _('Idle timeout'),
|
|
_('The idle timeout in seconds.'));
|
|
so.datatype = 'uinteger';
|
|
so.placeholder = '1800';
|
|
so.depends('node', 'urltest');
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.Flag, 'urltest_interrupt_exist_connections', _('Interrupt existing connections'),
|
|
_('Interrupt existing connections when the selected outbound has changed.'));
|
|
so.depends('node', 'urltest');
|
|
so.modalonly = true;
|
|
/* Routing nodes end */
|
|
|
|
}
|
|
|
|
/* DNS servers sub-grid (per-server entries that dns_rule 'route'/'evaluate'
|
|
* actions can target). Called after dns.renderDnsSettings() so the dns
|
|
* NamedSection is in place. */
|
|
function renderDnsServers(ctx) {
|
|
const { s } = ctx;
|
|
let o, ss, so;
|
|
|
|
s.tab('dns_server', _('DNS Servers'));
|
|
o = s.taboption('dns_server', form.SectionValue, '_dns_server', form.GridSection, 'dns_server');
|
|
o.depends('routing_mode', 'custom');
|
|
|
|
ss = o.subsection;
|
|
ss.addremove = true;
|
|
ss.rowcolors = true;
|
|
ss.sortable = true;
|
|
ss.nodescriptions = true;
|
|
ss.modaltitle = L.bind(hp.loadModalTitle, hp, _('DNS server'), _('Add a DNS server'), 'homeproxy-pro');
|
|
ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
|
/* Plain wrapper, not L.bind(): see hp.renderSectionAdd() for why the bound
|
|
* form hands a call site's button factory to the wrong argument. */
|
|
ss.renderSectionAdd = function(extra_class) {
|
|
return hp.renderSectionAdd(ss, extra_class);
|
|
};
|
|
|
|
so = ss.option(form.Value, 'label', _('Label'));
|
|
so.load = L.bind(hp.loadDefaultLabel, this, 'homeproxy-pro');
|
|
so.validate = L.bind(hp.validateUniqueValue, this, 'homeproxy-pro', 'dns_server', 'label');
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.Flag, 'enabled', _('Enable'));
|
|
so.default = so.enabled;
|
|
so.rmempty = false;
|
|
so.editable = true;
|
|
|
|
so = ss.option(form.ListValue, 'type', _('Type'));
|
|
so.value('udp', _('UDP'));
|
|
so.value('tcp', _('TCP'));
|
|
so.value('tls', _('TLS'));
|
|
so.value('https', _('HTTPS'));
|
|
so.value('h3', _('HTTP/3'));
|
|
so.value('quic', _('QUIC'));
|
|
so.default = 'udp';
|
|
so.rmempty = false;
|
|
|
|
so = ss.option(form.Value, 'server', _('Address'),
|
|
_('The address of the dns server.'));
|
|
so.datatype = 'or(hostname, ipaddr)';
|
|
so.rmempty = false;
|
|
|
|
so = ss.option(form.Value, 'server_port', _('Port'),
|
|
_('The port of the DNS server.'));
|
|
so.placeholder = 'auto';
|
|
so.datatype = 'port';
|
|
|
|
so = ss.option(form.Value, 'path', _('Path'),
|
|
_('The path of the DNS server.'));
|
|
so.placeholder = '/dns-query';
|
|
so.depends('type', 'https');
|
|
so.depends('type', 'h3');
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.DynamicList, 'headers', _('Headers'),
|
|
_('Additional headers to be sent to the DNS server.'));
|
|
so.depends('type', 'https');
|
|
so.depends('type', 'h3');
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.Value, 'tls_sni', _('TLS SNI'),
|
|
_('Used to verify the hostname on the returned certificates.'));
|
|
so.depends('type', 'tls');
|
|
so.depends('type', 'https');
|
|
so.depends('type', 'h3');
|
|
so.depends('type', 'quic');
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.ListValue, 'address_resolver', _('Address resolver'),
|
|
_('Tag of a another server to resolve the domain name in the address. Required if address contains domain.'));
|
|
so.load = function(section_id) {
|
|
delete this.keylist;
|
|
delete this.vallist;
|
|
|
|
this.value('', _('None'));
|
|
this.value('default-dns', _('Default DNS (issued by WAN)'));
|
|
this.value('system-dns', _('System DNS'));
|
|
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
|
if (res['.name'] !== section_id && res.enabled === '1')
|
|
this.value(res['.name'], res.label);
|
|
});
|
|
|
|
return this.super('load', section_id);
|
|
}
|
|
so.validate = function(section_id, value) {
|
|
if (section_id && value) {
|
|
let conflict = false;
|
|
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
|
if (res['.name'] !== section_id)
|
|
if (res.address_resolver === section_id && res['.name'] == value)
|
|
conflict = true;
|
|
});
|
|
if (conflict)
|
|
return _('Recursive resolver detected!');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.ListValue, 'address_strategy', _('Address strategy'),
|
|
_('The domain strategy for resolving the domain name in the address.'));
|
|
for (let i in hp.dns_strategy)
|
|
so.value(i, hp.dns_strategy[i]);
|
|
so.depends({'address_resolver': '', '!reverse': true});
|
|
so.modalonly = true;
|
|
|
|
so = ss.option(form.ListValue, 'outbound', _('Outbound'),
|
|
_('Tag of an outbound for connecting to the dns server.'));
|
|
so.load = function(section_id) {
|
|
delete this.keylist;
|
|
delete this.vallist;
|
|
|
|
this.value('direct-out', _('Direct'));
|
|
uci.sections('homeproxy-pro', 'routing_node', (res) => {
|
|
if (res.enabled === '1')
|
|
this.value(res['.name'], res.label);
|
|
});
|
|
|
|
return this.super('load', section_id);
|
|
}
|
|
so.default = 'direct-out';
|
|
so.rmempty = false;
|
|
so.editable = true;
|
|
/* DNS servers end */
|
|
}
|
|
|
|
return baseclass.extend({ renderRoutingNodes, renderDnsServers });
|