💐 Sync 2026-10-01 01:11:54

This commit is contained in:
github-actions[bot] committed 2026-10-01 01:11:54 +08:00
1 parent fa5e40404d
commit ce81b387d8
44 files changed
+1092 -2287

No files matched your search

@@ -1128,35 +1128,6 @@ return baseclass.extend({
return true;
},
/* A regular expression that does not compile reaches sing-box as-is and
* takes the whole configuration with it, so the reload keeps the previous
* one and the user only sees "my setting did not take". Go's RE2 and this
* engine differ in the corners, but every typo is caught here. */
validateRegex(section_id, value) {
if (section_id && value) {
try {
new RegExp(value);
} catch (e) {
return _('Expecting: %s').format(_('valid regular expression'));
}
}
return true;
},
/* sing-box resolves a DNS query type name through miekg/dns's uppercase
* type table (option/types.go), so "aaaa" is an "unknown DNS query type"
* and takes the configuration down with it. A numeric code up to 65535 is
* valid as-is. */
validateQueryType(section_id, value) {
if (section_id && value) {
if (!((/^[0-9]{1,5}$/.test(value) && parseInt(value, 10) <= 65535) || /^[A-Z][A-Z0-9-]*$/.test(value)))
return _('Expecting: %s').format(_('a numeric code or an uppercase type name (for example AAAA)'));
}
return true;
},
validateUniqueValue(uciconfig, ucisection, ucioption, section_id, value) {
if (section_id) {
if (!value)
@@ -160,12 +160,12 @@ return view.extend({
/* Tab ordering follows the order of these calls: routing,
* routing_node, routing_rule, dns, dns_server, dns_rule, ruleset,
* control, udp_nat, tun_dns. The DNS tab sits before Access Control
* because the pages a preset-routing-mode user actually sees are
* Routing Settings, DNS Settings, Access Control, UDP NAT Settings
* and TUN DNS, in that order. The shared rule-section bodies live in
* common.js and are called out-of-order with respect to their file to
* keep the rendering sequence intact. */
* dns_cache, control, udp_nat, tun_dns. The DNS tab sits before
* Access Control because the pages a preset-routing-mode user
* actually sees are Routing Settings, DNS Settings, Access Control,
* UDP NAT Settings and TUN DNS, in that order. The shared
* rule-section bodies live in common.js and are called out-of-order
* with respect to their file to keep the rendering sequence intact. */
routing.render(ctx);
nodes.renderRoutingNodes(ctx);
routing.renderRoutingRules(ctx);
@@ -173,6 +173,7 @@ return view.extend({
nodes.renderDnsServers(ctx);
dns.renderDnsRules(ctx);
subscription.render(ctx);
dns.renderDnsCache(ctx);
access.render(ctx);
udp_nat.renderUdpNat(ctx);
tun_dns.renderTunDns(ctx);
@@ -44,9 +44,8 @@ function render(ctx) {
/* LAN IP policy start */
ss.tab('lan_ip_policy', _('LAN IP Policy'));
so = ss.taboption('lan_ip_policy', form.ListValue, 'lan_proxy_mode', _('Proxy filter mode'),
_('"No filter" proxies every LAN client, which is the default; the other two values select clients by the lists below.'));
so.value('disabled', _('No filter (proxy every client)'));
so = ss.taboption('lan_ip_policy', form.ListValue, 'lan_proxy_mode', _('Proxy filter mode'));
so.value('disabled', _('Disable'));
so.value('listed_only', _('Proxy listed only'));
so.value('except_listed', _('Proxy all except listed'));
so.default = 'disabled';
@@ -113,7 +113,6 @@ function renderRuleSection(s, kind, self) {
if (is_dns) {
so = ss.taboption('field_other', form.DynamicList, 'query_type', _('Query type'),
_('Match query type.'));
so.validate = hp.validateQueryType;
so.modalonly = true;
so = addNetwork();
@@ -146,7 +145,7 @@ function renderRuleSection(s, kind, self) {
delete this.vallist;
uci.sections('homeproxy-pro', 'ruleset', (res) => {
if (res.enabled !== '0')
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -168,15 +167,12 @@ function renderRuleSection(s, kind, self) {
* (sniff/hijack are emitted by the proxy-mode builder,
* not exposed in the form).
*
* `route-options` is offered for routing rules only. sing-box 1.14's DNS
* rule action list does contain a `route-options`, but it takes a
* different field set (cache / TTL / timeout / client-subnet, and no
* outbound or override), which this shared form does not model - offering
* it here would emit routing fields the DNS schema refuses. Earlier code
* added it to both branches and the depends() guards for dns_disable_cache
* / rewrite_ttl / client_subnet pinned a stale 'route-options' in UCI that
* resurfaced as "unknown field" the next time the user flipped back to
* 'route'. */
* `route-options` is a routing_rule-only action: sing-box 1.14's dns_rule
* schema has no such action, and emitting it would make the whole dns block
* fail with "unknown action". Earlier code added it to both branches and
* the depends() guards for dns_disable_cache / rewrite_ttl / client_subnet
* pinned a stale 'route-options' in the UCI that resurfaced as "unknown
* field" the next time the user flipped back to 'route'. */
so = ss.taboption('field_other', form.ListValue, 'action', _('Action'));
so.value('route', _('Route'));
so.value('reject', _('Reject'));
@@ -201,7 +197,7 @@ function renderRuleSection(s, kind, self) {
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 !== '0')
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -338,7 +334,7 @@ function renderRuleSection(s, kind, self) {
this.value('direct-out', _('Direct'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
if (res.enabled !== '0')
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -352,48 +348,39 @@ function renderRuleSection(s, kind, self) {
_('Override the connection destination address.'));
so.datatype = 'ipaddr';
so.depends('action', 'route');
so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Value, 'override_port', _('Override port'),
_('Override the connection destination port.'));
so.datatype = 'port';
so.depends('action', 'route');
so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Flag, 'udp_disable_domain_unmapping', _('Disable UDP domain unmapping'),
_('If enabled, for UDP proxy requests addressed to a domain, the original packet address will be sent in the response instead of the mapped domain.'));
so.depends('action', 'route');
so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Flag, 'udp_connect', _('connect UDP connections'),
_('If enabled, attempts to connect UDP connection to the destination instead of listen.'));
so.depends('action', 'route');
so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Value, 'udp_timeout', _('UDP timeout'),
_('Timeout for UDP connections.<br/>Setting a larger value than the UDP timeout in inbounds will have no effect.'));
so.datatype = 'uinteger';
so.depends('action', 'route');
so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Flag, 'tls_record_fragment', _('TLS record fragment'),
_('Fragment TLS handshake into multiple TLS records.'));
so.depends('action', 'route');
so.depends('action', 'route-options');
so.modalonly = true;
so.depends({'tls_fragment': '1', '!reverse': true});
so = ss.taboption('field_other', form.Flag, 'tls_fragment', _('TLS fragment'),
_('Fragment TLS handshakes. Due to poor performance, try <code>%s</code> first.').format(
_('TLS record fragment')));
so.depends('action', 'route');
so.depends('action', 'route-options');
so.depends({'tls_record_fragment': '1', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_other', form.Value, 'tls_fragment_fallback_delay', _('Fragment fallback delay'),
@@ -407,7 +394,6 @@ function renderRuleSection(s, kind, self) {
_('Inject a forged TLS ClientHello carrying this SNI before the real one to fool SNI-filtering middleboxes. Requires elevated privileges.'));
so.datatype = 'hostname';
so.depends('action', 'route');
so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.ListValue, 'tls_spoof_method', _('TLS spoof method (1.14)'),
@@ -418,7 +404,6 @@ function renderRuleSection(s, kind, self) {
so.value('wrong-md5', _('wrong-md5'));
so.value('wrong-timestamp', _('wrong-timestamp'));
so.depends('action', 'route');
so.depends('action', 'route-options');
so.depends('tls_spoof', /[\s\S]/);
so.modalonly = true;
@@ -432,7 +417,7 @@ function renderRuleSection(s, kind, self) {
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 !== '0')
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -505,7 +490,6 @@ function renderRuleSection(s, kind, self) {
so = ss.taboption('field_host', form.DynamicList, 'domain_regex', _('Domain regex'),
_('Match domain using regular expression.'));
so.validate = hp.validateRegex;
so.modalonly = true;
so = ss.taboption('field_host', form.DynamicList, 'source_ip_cidr', _('Source IP CIDR'),
@@ -576,7 +560,6 @@ function renderRuleSection(s, kind, self) {
so = ss.taboption('fields_process', form.DynamicList, 'process_path_regex', _('Process path (regex)'),
_('Match process path using regular expression.'));
so.validate = hp.validateRegex;
so.modalonly = true;
}
@@ -13,20 +13,17 @@
'require homeproxy-pro as hp';
'require view.homeproxy-pro.client.common as common';
/* DNS settings: both routing-mode families in one tab.
/* The four cache options exist on the 'dns' NamedSection in both the custom
* mode tab (renderDnsSettings) and the preset mode tab (renderDnsCache).
* Both used to spell them out separately, which registered the same four
* option names twice in one map: LuCI keeps the first registration for writes
* and lookupOption(), so the second copy's dependencies did nothing while its
* description could drift. They are declared here once and rendered from
* wherever the tab needs them, in the original order.
*
* They used to be two tabs with the same title ('dns' and 'dns_cache'), each
* wrapping the same 'dns' NamedSection. Two form.NamedSection instances over
* one UCI section produce identical cbids, and LuCI resolves a cbid by taking
* the first matching element in DOM order - which follows tab registration,
* i.e. the custom-mode tab. The four cache options were registered in both,
* so under a preset routing mode the controls the user could actually see were
* reading and writing an element inside a hidden tab: "Store DNS cache" and
* "DNS query timeout" did not take.
*
* The tab is declared once now and no option name is registered twice, so
* there is no first-match to get wrong. Each option carries the routing mode
* it belongs to as a depends(). */
* The depends() are the custom-mode copy's: with the cache disabled the
* optimistic cache and its persisted file have no effect, so both are hidden
* (disable_cache_expire is itself conditional on disable_cache). */
function renderDnsCacheOptions(ss) {
let so;
@@ -53,23 +50,85 @@ function renderDnsCacheOptions(ss) {
* routing.renderRoutingRules() so the NamedSection chain reads
* config -> routing_node -> routing_rule -> dns from left to right. */
function renderDnsSettings(ctx) {
const { s, stubValidator } = ctx;
const { s } = ctx;
let o, ss, so;
s.tab('dns', _('DNS Settings'));
o = s.taboption('dns', form.SectionValue, '_dns', form.NamedSection, 'dns', 'homeproxy-pro');
o.depends('routing_mode', 'custom');
/* The preset-mode fields. They belong to the 'config' section, not to
* 'dns', so they were never part of the duplicate-cbid problem above -
* they are rendered here because this is the only DNS tab now.
*
* The name says overseas because that is the only thing that belongs
ss = o.subsection;
so = ss.option(form.ListValue, 'default_strategy', _('Default DNS strategy'),
_('The DNS strategy for resolving the domain name in the address.'));
for (let i in hp.dns_strategy)
so.value(i, hp.dns_strategy[i]);
so = ss.option(form.ListValue, 'default_server', _('Default DNS server'));
so.load = function(section_id) {
delete this.keylist;
delete this.vallist;
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.default = 'default-dns';
so.rmempty = false;
so = ss.option(form.Flag, 'disable_cache', _('Disable DNS cache'));
so = ss.option(form.Flag, 'disable_cache_expire', _('Disable cache expire'));
so.depends('disable_cache', '0');
so = ss.option(form.Value, 'client_subnet', _('EDNS Client subnet'),
_('Append a <code>edns0-subnet</code> OPT extra record with the specified IP prefix to every query by default.<br/>' +
'If value is an IP address instead of prefix, <code>/32</code> or <code>/128</code> will be appended automatically.'));
so.datatype = 'or(cidr, ipaddr)';
renderDnsCacheOptions(ss);
/* DNS settings end */
}
/* DNS rules sub-section. Split out from renderDnsSettings() so the
* orchestrator can call it after nodes.renderDnsServers() and preserve
* the original tab ordering (dns, dns_server, dns_rule). */
function renderDnsRules(ctx) {
const { s, self } = ctx;
/* DNS rules start */
common.renderRuleSection(s, 'dns', self);
/* DNS rules end */
}
/* DNS tab for the preset routing modes. Titled 'DNS Settings' exactly like
* the custom-mode tab in renderDnsSettings(): the two are mutually exclusive
* (custom vs the presets), so whichever one a user lands on is called the
* same thing.
*
* The three server options below are config.* (not dns.*) options that used
* to live on the routing tab. They are DNS settings, and the routing page
* only carried them because of the order the old monolithic client.js
* happened to be written in. They stay on the 'config' NamedSection, so
* only the tab they render under changed. */
function renderDnsCache(ctx) {
const { s, stubValidator } = ctx;
let o, ss;
s.tab('dns_cache', _('DNS Settings'));
/* The name says overseas because that is the only thing that belongs
* here: every query this field answers goes to a domain that is not on
* the China path, and a domestic resolver answers those with polluted or
* NXDOMAIN results. The Chinese public resolvers that used to be listed
* below a divider were therefore a footgun with a helpful-looking label -
* the one combination that reliably breaks a working proxy. Only the
* overseas public resolvers are offered now. */
o = s.taboption('dns', form.Value, 'dns_server', _('Overseas DNS server'),
o = s.taboption('dns_cache', form.Value, 'dns_server', _('Overseas DNS server'),
_('Resolves every domain that is not on the China DNS path, through the selected proxy node. ' +
'A domestic resolver answers these queries with polluted or NXDOMAIN results, which is what ' +
'makes a foreign site fail to resolve while the proxy itself works - so only overseas services ' +
@@ -105,13 +164,11 @@ function renderDnsSettings(ctx) {
return true;
}
o = s.taboption('dns', form.Value, 'china_dns_server', _('China DNS server'),
o = s.taboption('dns_cache', form.Value, 'china_dns_server', _('China DNS server'),
_('Resolves the domains on the China path, so it has to be a domestic service to get the ' +
'correct local CDN answers. Keep the default; the upstream may block UDP/53, in which case ' +
'a DoH address (https://dns.alidns.com/dns-query, https://doh.pub/dns-query) also works. ' +
'Support UDP, TCP, DoH, DoQ, DoT. A DoH/DoT address here also sends the bootstrap ' +
'lookup back to the WAN resolver: only a bare IP can resolve the proxy DNS endpoint ' +
'without the ISP.'));
'Support UDP, TCP, DoH, DoQ, DoT.'));
o.value('wan', _('WAN DNS (read from interface)'));
o.value('223.5.5.5', _('Aliyun Public DNS (223.5.5.5)'));
o.value('210.2.4.8', _('CNNIC Public DNS (210.2.4.8)'));
@@ -144,67 +201,16 @@ function renderDnsSettings(ctx) {
return true;
}
o = s.taboption('dns', form.Flag, 'cn_ip_fallback', _('CN-IP DNS fallback (sing-box 1.14)'),
o = s.taboption('dns_cache', form.Flag, 'cn_ip_fallback', _('CN-IP DNS fallback (sing-box 1.14)'),
_('When the main DNS returns a mainland China IP, re-resolve via China DNS using evaluate/match_response.'));
o.depends('routing_mode', 'bypass_mainland_china');
o.rmempty = false;
/* The 'dns' section, registered exactly once: see the file header. The
* custom-only options below keep their own depends() because the section
* is visible in both routing-mode families. */
o = s.taboption('dns', form.SectionValue, '_dns', form.NamedSection, 'dns', 'homeproxy-pro');
o = s.taboption('dns_cache', form.SectionValue, '_dns_cache', form.NamedSection, 'dns', 'homeproxy-pro');
o.depends({'routing_mode': 'custom', '!reverse': true});
ss = o.subsection;
so = ss.option(form.ListValue, 'default_strategy', _('Default DNS strategy'),
_('The DNS 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('routing_mode', 'custom');
so = ss.option(form.ListValue, 'default_server', _('Default DNS server'));
so.load = function(section_id) {
delete this.keylist;
delete this.vallist;
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 !== '0')
this.value(res['.name'], res.label);
});
return this.super('load', section_id);
}
so.default = 'default-dns';
so.rmempty = false;
so.depends('routing_mode', 'custom');
so = ss.option(form.Flag, 'disable_cache', _('Disable DNS cache'));
so = ss.option(form.Flag, 'disable_cache_expire', _('Disable cache expire'));
so.depends('disable_cache', '0');
so = ss.option(form.Value, 'client_subnet', _('EDNS Client subnet'),
_('Append a <code>edns0-subnet</code> OPT extra record with the specified IP prefix to every query by default.<br/>' +
'If value is an IP address instead of prefix, <code>/32</code> or <code>/128</code> will be appended automatically.'));
so.datatype = 'or(cidr, ipaddr)';
so.depends('routing_mode', 'custom');
/* The four cache options are common to both families, so they are
* registered once, with no routing-mode dependency of their own. */
renderDnsCacheOptions(ss);
/* DNS settings end */
}
/* DNS rules sub-section. Split out from renderDnsSettings() so the
* orchestrator can call it after nodes.renderDnsServers() and preserve
* the original tab ordering (dns, dns_server, dns_rule). */
function renderDnsRules(ctx) {
const { s, self } = ctx;
/* DNS rules start */
common.renderRuleSection(s, 'dns', self);
/* DNS rules end */
}
return baseclass.extend({ renderDnsSettings, renderDnsRules });
return baseclass.extend({ renderDnsSettings, renderDnsRules, renderDnsCache });
@@ -65,7 +65,7 @@ function renderRoutingNodes(ctx) {
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 !== '0')
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -96,7 +96,7 @@ function renderRoutingNodes(ctx) {
this.value('', _('Direct'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
if (res['.name'] !== section_id && res.enabled !== '0')
if (res['.name'] !== section_id && res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -264,7 +264,7 @@ function renderDnsServers(ctx) {
so.modalonly = true;
so = ss.option(form.DynamicList, 'headers', _('Headers'),
_('Additional headers to be sent to the DNS server, one <code>Name: Value</code> per line.'));
_('Additional headers to be sent to the DNS server.'));
so.depends('type', 'https');
so.depends('type', 'h3');
so.modalonly = true;
@@ -287,7 +287,7 @@ function renderDnsServers(ctx) {
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 !== '0')
if (res['.name'] !== section_id && res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -324,7 +324,7 @@ function renderDnsServers(ctx) {
this.value('direct-out', _('Direct'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
if (res.enabled !== '0')
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -108,11 +108,7 @@ function render(ctx) {
let ports = [];
for (let i of value.split(',')) {
/* A bare decimal port only: the backend's ports_to_nftarr()
* (firewall_utils.uc) keeps nothing else, so a range like
* "80-90" was accepted here and then dropped without a word -
* the port the user asked for was simply never proxied. */
if (!stubValidator.apply('port', i) || i === '0')
if (!stubValidator.apply('port', i) && !stubValidator.apply('portrange', i))
return _('Expecting: %s').format(_('valid port value'));
if (ports.includes(i))
return _('Port %s alrealy exists!').format(i);
@@ -214,7 +210,7 @@ function render(ctx) {
this.value('direct-out', _('Direct'));
this.value('block-out', _('Block'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
if (res.enabled !== '0')
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -232,7 +228,7 @@ function render(ctx) {
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 !== '0')
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -52,16 +52,11 @@ function render(ctx) {
so.default = 'remote';
so.rmempty = false;
so = ss.option(form.ListValue, 'format', _('Format'),
_('Leave empty to infer it from a <code>.json</code> or <code>.srs</code> path/url; sing-box requires it for any other extension.'));
so.value('', _('Automatic (by extension)'));
so = ss.option(form.ListValue, 'format', _('Format'));
so.value('binary', _('Binary file'));
so.value('source', _('Source file'));
/* Not `binary` by default: sing-box only infers the format when the
* field is absent, and an explicit value wins. Defaulting to binary
* meant every local rule-set pointing at a .json file was parsed as a
* compiled .srs unless the user remembered to change the dropdown. */
so.rmempty = true;
so.default = 'binary';
so.rmempty = false;
so = ss.option(form.Value, 'path', _('Path'));
so.datatype = 'file';
@@ -104,7 +99,7 @@ function render(ctx) {
this.value('', _('Default'));
this.value('direct-out', _('Direct'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
if (res.enabled !== '0')
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
@@ -98,38 +98,6 @@ function getConnStat(o, site) {
]);
}
/* The gfwlist routing mode routes by an nft set that dnsmasq fills through
* `nftset=`. A LAN client with its own DoH/DoT never asks this router's DNS,
* so the set stays empty and the mode silently degrades to direct routing.
* The count is what tells "the mode is working" from "the mode is doing
* nothing", so it sits next to the resource versions. */
function getGfwSetStat(o) {
return hp.rpcCall('gfw_set_counts', [], { expect: { '': {} } }).then((res) => {
let field = E('div', { 'style': 'cbi-value-field' });
let warn = false;
[ [ 'IPv4', res.v4 ], [ 'IPv6', res.v6 ] ].forEach(([ label, count ]) => {
let value;
if (count == null)
value = E('em', {}, _('not installed'));
else if (count === 0) {
value = E('strong', { 'style': 'color:orange' }, '%d'.format(count));
warn = warn || (label === 'IPv4');
} else
value = E('strong', { 'style': 'color:green' }, '%d'.format(count));
field.appendChild(E('span', {}, [ label + ': ', value ]));
field.appendChild(document.createTextNode(' '));
});
if (warn)
field.appendChild(E('em', {}, _('empty: clients may be bypassing this router\'s DNS')));
return field;
});
}
function getResVersion(o, type) {
return hp.rpcCall('resources_get_version', [type],
{ params: ['type'], expect: { '': {} } }).then((res) => {
@@ -306,9 +274,6 @@ return view.extend({
o.cfgvalue = L.bind(getResVersion, this, o, 'gfw_list');
o.rawhtml = true;
o = s.option(form.DummyValue, '_gfw_set_size', _('GFW list addresses'));
o.cfgvalue = L.bind(getGfwSetStat, this, o);
o = s.option(form.Value, 'github_token', _('GitHub token'));
o.password = true;
o.renderWidget = function() {