small-package/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js

1346 lines
50 KiB
JavaScript

'use strict';
'require form';
'require uci';
'require ui';
'require view';
'require fchomo as hm';
'require tools.widgets as widgets';
function parseProviderYaml(field, name, cfg) {
if (!cfg.type)
return null;
// key mapping
let config = hm.removeBlankAttrs({
id: cfg.hm_id,
label: cfg.hm_label,
type: cfg.type,
...(cfg.type === 'inline' ? {
//dialer_proxy: cfg["dialer-proxy"],
payload: cfg.payload, // string: array
} : {
url: cfg.url,
size_limit: cfg["size-limit"],
interval: cfg.interval,
proxy: cfg.proxy ? hm.preset_outbound.full.map(([key, label]) => key).includes(cfg.proxy) ? cfg.proxy : this.calcID(hm.glossary["proxy_group"].field, cfg.proxy) : null,
header: cfg.header ? JSON.stringify(cfg.header, null, 2) : null, // string: object
/* Health fields */
health_enable: hm.bool2str(hm.getValue(cfg, "health-check.enable")), // bool
health_url: hm.getValue(cfg, "health-check.url"),
health_interval: hm.getValue(cfg, "health-check.interval"),
health_timeout: hm.getValue(cfg, "health-check.timeout"),
health_lazy: hm.bool2str(hm.getValue(cfg, "health-check.lazy")), // bool
health_expected_status: hm.getValue(cfg, "health-check.expected-status"),
/* Override fields */
override_prefix: hm.getValue(cfg, "override.additional-prefix"),
override_suffix: hm.getValue(cfg, "override.additional-suffix"),
override_replace: (hm.getValue(cfg, "override.proxy-name") || []).map((obj) => JSON.stringify(obj)), // array.string: array.object
// Configuration Items
override_tfo: hm.bool2str(hm.getValue(cfg, "override.tfo")), // bool
override_mptcp: hm.bool2str(hm.getValue(cfg, "override.mptcp")), // bool
override_udp: hm.bool2str(hm.getValue(cfg, "override.udp")), // bool
override_uot: hm.bool2str(hm.getValue(cfg, "override.udp-over-tcp")), // bool
override_up: hm.getValue(cfg, "override.up"),
override_down: hm.getValue(cfg, "override.down"),
override_skip_cert_verify: hm.bool2str(hm.getValue(cfg, "override.skip-cert-verify")), // bool
//override_dialer_proxy: hm.getValue(cfg, "override.dialer-proxy"),
override_interface_name: hm.getValue(cfg, "override.interface-name"),
override_routing_mark: hm.getValue(cfg, "override.routing-mark"),
override_ip_version: hm.getValue(cfg, "override.ip-version"),
/* General fields */
filter: [cfg.filter], // array.string: string
exclude_filter: [cfg["exclude-filter"]], // array.string: string
exclude_type: [cfg["exclude-type"]] // array.string: string
})
});
return config;
}
return view.extend({
load() {
return Promise.all([
uci.load('fchomo')
]);
},
render(data) {
let m, s, o, ss, so;
m = new form.Map('fchomo', _('Edit node'));
s = m.section(form.NamedSection, 'global', 'fchomo');
/* Proxy Node START */
s.tab('node', _('Proxy Node'));
/* Proxy Node */
o = s.taboption('node', form.SectionValue, '_node', hm.GridSection, 'node', null);
ss = o.subsection;
ss.addremove = true;
ss.rowcolors = true;
ss.sortable = true;
ss.nodescriptions = true;
ss.hm_modaltitle = [ _('Node'), _('Add a Node') ];
ss.hm_prefmt = hm.glossary[ss.sectiontype].prefmt;
ss.hm_field = hm.glossary[ss.sectiontype].field;
ss.hm_lowcase_only = true;
ss.tab('field_general', _('General fields'));
ss.tab('field_tls', _('TLS fields'));
ss.tab('field_transport', _('Transport fields'));
ss.tab('field_multiplex', _('Multiplex fields'));
ss.tab('field_dial', _('Dial fields'));
so = ss.taboption('field_general', form.Value, 'label', _('Label'));
so.load = L.bind(hm.loadDefaultLabel, so);
so.validate = L.bind(hm.validateUniqueValue, so);
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'enabled', _('Enable'));
so.default = so.enabled;
so.editable = true;
so = ss.taboption('field_general', form.ListValue, 'type', _('Type'));
so.default = hm.outbound_type[0][0];
hm.outbound_type.forEach((res) => {
so.value.apply(so, res);
})
so = ss.taboption('field_general', form.Value, 'server', _('Server address'));
so.datatype = 'host';
so.rmempty = false;
so.depends({type: 'direct', '!reverse': true});
so = ss.taboption('field_general', form.Value, 'port', _('Port'));
so.datatype = 'port';
so.rmempty = false;
so.depends({type: /^(direct|mieru)$/, '!reverse': true});
/* HTTP / SOCKS fields */
/* hm.validateAuth */
so = ss.taboption('field_general', form.Value, 'username', _('Username'));
so.validate = L.bind(hm.validateAuthUsername, so);
so.depends({type: /^(http|socks5|mieru|ssh)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'password', _('Password'));
so.password = true;
so.validate = L.bind(hm.validateAuthPassword, so);
so.depends({type: /^(http|socks5|mieru|trojan|anytls|hysteria2|tuic|ssh)$/});
so.modalonly = true;
so = ss.taboption('field_general', hm.TextValue, 'headers', _('HTTP header'));
so.placeholder = '{\n "User-Agent": [\n "Clash/v1.18.0",\n "mihomo/1.18.3"\n ],\n "Authorization": [\n //"token 1231231"\n ]\n}';
so.validate = L.bind(hm.validateJson, so);
so.depends('type', 'http');
so.modalonly = true;
/* Hysteria / Hysteria2 fields */
so = ss.taboption('field_general', form.DynamicList, 'hysteria_ports', _('Ports pool'));
so.datatype = 'or(port, portrange)';
so.depends({type: /^(hysteria|hysteria2)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'hysteria_up_mbps', _('Max upload speed'),
_('In Mbps.'));
so.datatype = 'uinteger';
so.depends({type: /^(hysteria|hysteria2)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'hysteria_down_mbps', _('Max download speed'),
_('In Mbps.'));
so.datatype = 'uinteger';
so.depends({type: /^(hysteria|hysteria2)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'hysteria_obfs_type', _('Obfuscate type'));
so.value('', _('Disable'));
so.value('salamander', _('Salamander'));
so.depends('type', 'hysteria2');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'hysteria_obfs_password', _('Obfuscate password'),
_('Enabling obfuscation will make the server incompatible with standard QUIC connections, losing the ability to masquerade with HTTP/3.'));
so.password = true;
so.rmempty = false;
so.depends('type', 'hysteria');
so.depends({type: 'hysteria2', hysteria_obfs_type: /.+/});
so.modalonly = true;
/* SSH fields */
so = ss.taboption('field_general', form.TextValue, 'ssh_priv_key', _('Priv-key'));
so.depends('type', 'ssh');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'ssh_priv_key_passphrase', _('Priv-key passphrase'));
so.password = true;
so.depends({type: 'ssh', ssh_priv_key: /.+/});
so.modalonly = true;
so = ss.taboption('field_general', form.DynamicList, 'ssh_host_key_algorithms', _('Host-key algorithms'));
so.placeholder = 'rsa';
so.depends('type', 'ssh');
so.modalonly = true;
so = ss.taboption('field_general', form.DynamicList, 'ssh_host_key', _('Host-key'));
so.placeholder = 'ssh-rsa AAAAB3NzaC1yc2EAA...';
so.depends({type: 'ssh', ssh_host_key_algorithms: /.+/});
so.modalonly = true;
/* Shadowsocks fields */
so = ss.taboption('field_general', form.ListValue, 'shadowsocks_chipher', _('Chipher'));
so.default = hm.shadowsocks_cipher_methods[1][0];
hm.shadowsocks_cipher_methods.forEach((res) => {
so.value.apply(so, res);
})
so.depends('type', 'ss');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'shadowsocks_password', _('Password'));
so.password = true;
so.validate = function(section_id, value) {
const encmode = this.section.getOption('shadowsocks_chipher').formvalue(section_id);
return hm.validateShadowsocksPassword.call(this, encmode, section_id, value);
}
so.depends({type: 'ss', shadowsocks_chipher: /.+/});
so.modalonly = true;
/* Mieru fields */
so = ss.taboption('field_general', form.Value, 'mieru_port_range', _('Port range'));
so.datatype = 'portrange';
so.depends('type', 'mieru');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'mieru_transport', _('Transport'));
so.value('TCP');
so.default = 'TCP';
so.depends('type', 'mieru');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'mieru_multiplexing', _('Multiplexing'));
so.value('MULTIPLEXING_OFF');
so.value('MULTIPLEXING_LOW');
so.value('MULTIPLEXING_MIDDLE');
so.value('MULTIPLEXING_HIGH');
so.default = 'MULTIPLEXING_LOW';
so.depends('type', 'mieru');
so.modalonly = true;
/* Snell fields */
so = ss.taboption('field_general', form.Value, 'snell_psk', _('Pre-shared key'));
so.password = true;
so.rmempty = false;
so.validate = L.bind(hm.validateAuthPassword, so);
so.depends('type', 'snell');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'snell_version', _('Version'));
so.value('1', _('v1'));
so.value('2', _('v2'));
so.value('3', _('v3'));
so.default = '3';
so.depends('type', 'snell');
so.modalonly = true;
/* TUIC fields */
so = ss.taboption('field_general', form.Value, 'uuid', _('UUID'));
so.rmempty = false;
so.validate = L.bind(hm.validateUUID, so);
so.depends('type', 'tuic');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'tuic_ip', _('IP override'),
_('Override the IP address of the server that DNS response.'));
so.datatype = 'ipaddr(1)';
so.depends('type', 'tuic');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'tuic_congestion_controller', _('Congestion controller'),
_('QUIC congestion controller.'));
so.default = 'cubic';
so.value('cubic', _('cubic'));
so.value('new_reno', _('new_reno'));
so.value('bbr', _('bbr'));
so.depends('type', 'tuic');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'tuic_udp_relay_mode', _('UDP relay mode'),
_('UDP packet relay mode.'));
so.value('', _('Default'));
so.value('native', _('Native'));
so.value('quic', _('QUIC'));
so.depends({type: 'tuic', tuic_udp_over_stream: '0'});
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'tuic_udp_over_stream', _('UDP over stream'),
_('This is the TUIC port of the SUoT protocol, designed to provide a QUIC stream based UDP relay mode that TUIC does not provide.'));
so.default = so.disabled;
so.depends('type', 'tuic');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'tuic_udp_over_stream_version', _('UDP over stream version'));
so.value('1', _('v1'));
so.depends({type: 'tuic', tuic_udp_over_stream: '1'});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'tuic_max_udp_relay_packet_size', _('Max UDP relay packet size'));
so.datatype = 'uinteger';
so.placeholder = '1500';
so.depends('type', 'tuic');
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'tuic_reduce_rtt', _('Enable 0-RTT handshake'),
_('Enable 0-RTT QUIC connection handshake on the client side. This is not impacting much on the performance, as the protocol is fully multiplexed.<br/>' +
'Disabling this is highly recommended, as it is vulnerable to replay attacks.'));
so.default = so.disabled;
so.depends('type', 'tuic');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'tuic_heartbeat', _('Heartbeat interval'),
_('In millisecond.'));
so.datatype = 'uinteger';
so.placeholder = '10000';
so.depends('type', 'tuic');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'tuic_request_timeout', _('Request timeout'),
_('In millisecond.'));
so.datatype = 'uinteger';
so.placeholder = '8000';
so.depends('type', 'tuic');
so.modalonly = true;
/* Trojan fields */
so = ss.taboption('field_general', form.Flag, 'trojan_ss_enabled', _('Shadowsocks encrypt'));
so.default = so.disabled;
so.depends('type', 'trojan');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'trojan_ss_chipher', _('Shadowsocks chipher'));
so.default = hm.trojan_cipher_methods[0][0];
hm.trojan_cipher_methods.forEach((res) => {
so.value.apply(so, res);
})
so.depends({type: 'trojan', trojan_ss_enabled: '1'});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'trojan_ss_password', _('Shadowsocks password'));
so.password = true;
so.validate = function(section_id, value) {
const encmode = this.section.getOption('trojan_ss_chipher').formvalue(section_id);
return hm.validateShadowsocksPassword.call(this, encmode, section_id, value);
}
so.depends({type: 'trojan', trojan_ss_enabled: '1'});
so.modalonly = true;
/* AnyTLS fields */
so = ss.taboption('field_general', form.Value, 'anytls_idle_session_check_interval', _('Idle session check interval'),
_('In seconds.'));
so.placeholder = '30';
so.validate = L.bind(hm.validateTimeDuration, so);
so.depends('type', 'anytls');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'anytls_idle_session_timeout', _('Idle session timeout'),
_('In seconds.'));
so.placeholder = '30';
so.validate = L.bind(hm.validateTimeDuration, so);
so.depends('type', 'anytls');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'anytls_min_idle_session', _('Min of idle sessions to keep'));
so.datatype = 'uinteger';
so.placeholder = '0';
so.depends('type', 'anytls');
so.modalonly = true;
/* VMess / VLESS fields */
so = ss.taboption('field_general', form.Value, 'vmess_uuid', _('UUID'));
so.rmempty = false;
so.validate = L.bind(hm.validateUUID, so);
so.depends({type: /^(vmess|vless)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'vless_flow', _('Flow'));
so.default = hm.vless_flow[0][0];
hm.vless_flow.forEach((res) => {
so.value.apply(so, res);
})
so.depends('type', 'vless');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'vmess_alterid', _('Alter ID'));
so.datatype = 'uinteger';
so.default = '0';
so.depends('type', 'vmess');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'vmess_chipher', _('Chipher'));
so.default = 'auto';
so.value('auto', _('auto'));
so.value('none', _('none'));
so.value('zero', _('zero'));
so.value('aes-128-gcm', _('aes-128-gcm'));
so.value('chacha20-poly1305', _('chacha20-poly1305'));
so.depends('type', 'vmess');
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'vmess_global_padding', _('Global padding'),
_('Protocol parameter. Will waste traffic randomly if enabled (enabled by default in v2ray and cannot be disabled).'));
so.default = so.enabled;
so.depends('type', 'vmess');
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'vmess_authenticated_length', _('Authenticated length'),
_('Protocol parameter. Enable length block encryption.'));
so.default = so.disabled;
so.depends('type', 'vmess');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'vmess_packet_encoding', _('Packet encoding'));
so.value('', _('none'));
so.value('packetaddr', _('packet addr (v2ray-core v5+)'));
so.value('xudp', _('Xudp (Xray-core)'));
so.depends({type: /^(vmess|vless)$/});
so.modalonly = true;
/* WireGuard fields */
so = ss.taboption('field_general', form.Value, 'wireguard_ip', _('Local address'),
_('The %s address used by local machine in the Wireguard network.').format('IPv4'));
so.datatype = 'ip4addr(1)';
so.rmempty = false;
so.depends('type', 'wireguard');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'wireguard_ipv6', _('Local IPv6 address'),
_('The %s address used by local machine in the Wireguard network.').format('IPv6'));
so.datatype = 'ip6addr(1)';
so.depends('type', 'wireguard');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'wireguard_private_key', _('Private key'),
_('WireGuard requires base64-encoded private keys.'));
so.password = true;
so.validate = L.bind(hm.validateBase64Key, so, 44);
so.rmempty = false;
so.depends('type', 'wireguard');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'wireguard_peer_public_key', _('Peer pubkic key'),
_('WireGuard peer public key.'));
so.validate = L.bind(hm.validateBase64Key, so, 44);
so.rmempty = false;
so.depends('type', 'wireguard');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'wireguard_pre_shared_key', _('Pre-shared key'),
_('WireGuard pre-shared key.'));
so.password = true;
so.validate = L.bind(hm.validateBase64Key, so, 44);
so.depends('type', 'wireguard');
so.modalonly = true;
so = ss.taboption('field_general', form.DynamicList, 'wireguard_allowed_ips', _('Allowed IPs'),
_('Destination addresses allowed to be forwarded via Wireguard.'));
so.datatype = 'cidr';
so.placeholder = '0.0.0.0/0';
so.depends('type', 'wireguard');
so.modalonly = true;
so = ss.taboption('field_general', form.DynamicList, 'wireguard_reserved', _('Reserved field bytes'));
so.datatype = 'integer';
so.depends('type', 'wireguard');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'wireguard_mtu', _('MTU'));
so.datatype = 'range(0,9000)';
so.placeholder = '1408';
so.depends('type', 'wireguard');
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'wireguard_remote_dns_resolve', _('Remote DNS resolve'),
_('Force DNS remote resolution.'));
so.default = so.disabled;
so.depends('type', 'wireguard');
so.modalonly = true;
so = ss.taboption('field_general', form.DynamicList, 'wireguard_dns', _('DNS server'));
so.datatype = 'or(host, hostport)';
so.depends('wireguard_remote_dns_resolve', '1');
so.modalonly = true;
/* Plugin fields */
so = ss.taboption('field_general', form.ListValue, 'plugin', _('Plugin'));
so.value('', _('none'));
so.value('obfs', _('obfs-simple'));
//so.value('v2ray-plugin', _('v2ray-plugin'));
//so.value('gost-plugin', _('gost-plugin'));
so.value('shadow-tls', _('shadow-tls'));
so.value('restls', _('restls'));
so.depends('type', 'ss');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'plugin_opts_obfsmode', _('Plugin: ') + _('Obfs Mode'));
so.value('http', _('HTTP'));
so.value('tls', _('TLS'));
so.depends('plugin', 'obfs');
so.depends('type', 'snell');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'plugin_opts_host', _('Plugin: ') + _('Host that supports TLS 1.3'));
so.datatype = 'hostname';
so.placeholder = 'cloud.tencent.com';
so.rmempty = false;
so.depends({plugin: /^(obfs|v2ray-plugin|shadow-tls|restls)$/});
so.depends('type', 'snell');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'plugin_opts_thetlspassword', _('Plugin: ') + _('Password'));
so.password = true;
so.rmempty = false;
so.depends({plugin: /^(shadow-tls|restls)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'plugin_opts_shadowtls_version', _('Plugin: ') + _('Version'));
so.value('1', _('v1'));
so.value('2', _('v2'));
so.value('3', _('v3'));
so.default = '2';
so.depends({plugin: 'shadow-tls'});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'plugin_opts_restls_versionhint', _('Plugin: ') + _('Version hint'));
so.default = 'tls13';
so.rmempty = false;
so.depends({plugin: 'restls'});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'plugin_opts_restls_script', _('Plugin: ') + _('Restls script'));
so.default = '300?100<1,400~100,350~100,600~100,300~200,300~100';
so.rmempty = false;
so.depends({plugin: 'restls'});
so.modalonly = true;
/* Extra fields */
so = ss.taboption('field_general', form.Flag, 'udp', _('UDP'));
so.default = so.disabled;
so.depends({type: /^(direct|socks5|ss|mieru|vmess|vless|trojan|anytls|wireguard)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'uot', _('UoT'),
_('Enable the SUoT protocol, requires server support. Conflict with Multiplex.'));
so.default = so.disabled;
so.depends('type', 'ss');
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'uot_version', _('SUoT version'));
so.value('1', _('v1'));
so.value('2', _('v2'));
so.default = '2';
so.depends('uot', '1');
so.modalonly = true;
/* TLS fields */
so = ss.taboption('field_general', form.Flag, 'tls', _('TLS'));
so.default = so.disabled;
so.validate = function(section_id, value) {
const type = this.section.getOption('type').formvalue(section_id);
let tls = this.section.getUIElement(section_id, 'tls').node.querySelector('input');
// Force enabled
if (['trojan', 'anytls', 'hysteria', 'hysteria2', 'tuic'].includes(type)) {
tls.checked = true;
tls.disabled = true;
} else {
tls.disabled = null;
}
return true;
}
so.depends({type: /^(http|socks5|vmess|vless|trojan|anytls|hysteria|hysteria2|tuic)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.Flag, 'tls_disable_sni', _('Disable SNI'),
_('Donot send server name in ClientHello.'));
so.default = so.disabled;
so.depends({tls: '1', type: /^(tuic)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
_('Used to verify the hostname on the returned certificates.'));
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2)$/});
so.depends({tls: '1', tls_disable_sni: '0', type: /^(tuic)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
_('List of supported application level protocols, in order of preference.'));
so.validate = function(section_id, value) {
const type = this.section.getOption('type').formvalue(section_id);
//const plugin = this.section.getOption('plugin').formvalue(section_id);
let tls_alpn = this.section.getUIElement(section_id, 'tls_alpn');
// Default alpn
if (!`${tls_alpn.getValue()}`) {
let def_alpn;
switch (type) {
case 'ss':
def_alpn = ['h2', 'http/1.1']; // when plugin === 'shadow-tls'
break;
case 'hysteria':
case 'hysteria2':
case 'tuic':
def_alpn = ['h3'];
break;
case 'vmess':
case 'vless':
case 'trojan':
case 'anytls':
def_alpn = ['h2', 'http/1.1'];
break;
default:
def_alpn = [];
}
tls_alpn.setValue(def_alpn);
}
return true;
}
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls|hysteria|hysteria2|tuic)$/});
so.depends({type: 'ss', plugin: 'shadow-tls'});
so.modalonly = true;
so = ss.taboption('field_tls', form.Value, 'tls_fingerprint', _('Cert fingerprint'),
_('Certificate fingerprint. Used to implement SSL Pinning and prevent MitM.'));
so.validate = function(section_id, value) {
if (!value)
return true;
if (!((value.length === 64) && (value.match(/^[0-9a-fA-F]+$/))))
return _('Expecting: %s').format(_('valid SHA256 string with %d characters').format(64));
return true;
}
so.depends({tls: '1', type: /^(http|socks5|vmess|vless|trojan|hysteria|hysteria2)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.Flag, 'tls_skip_cert_verify', _('Skip cert verify'),
_('Donot verifying server certificate.') +
'<br/>' +
_('This is <strong>DANGEROUS</strong>, your traffic is almost like <strong>PLAIN TEXT</strong>! Use at your own risk!'));
so.default = so.disabled;
so.depends({tls: '1', type: /^(http|socks5|vmess|vless|trojan|anytls|hysteria|hysteria2|tuic)$/});
so.modalonly = true;
// uTLS fields
so = ss.taboption('field_tls', form.ListValue, 'tls_client_fingerprint', _('Client fingerprint'));
so.default = hm.tls_client_fingerprints[0][0];
hm.tls_client_fingerprints.forEach((res) => {
so.value.apply(so, res);
})
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls)$/});
so.depends({type: 'ss', plugin: /^(shadow-tls|restls)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.Flag, 'tls_reality', _('REALITY'));
so.default = so.disabled;
so.depends({tls: '1', type: /^(vmess|vless|trojan)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.Value, 'tls_reality_public_key', _('REALITY public key'));
so.rmempty = false;
so.depends('tls_reality', '1');
so.modalonly = true;
so = ss.taboption('field_tls', form.Value, 'tls_reality_short_id', _('REALITY short ID'));
so.rmempty = false;
so.depends('tls_reality', '1');
so.modalonly = true;
/* Transport fields */
so = ss.taboption('field_general', form.Flag, 'transport_enabled', _('Transport'));
so.default = so.disabled;
so.depends({type: /^(vmess|vless|trojan)$/});
so.modalonly = true;
so = ss.taboption('field_transport', form.ListValue, 'transport_type', _('Transport type'));
so.default = 'http';
so.value('http', _('HTTP'));
so.value('h2', _('HTTPUpgrade'));
so.value('grpc', _('gRPC'));
so.value('ws', _('WebSocket'));
so.validate = function(section_id, value) {
const type = this.section.getOption('type').formvalue(section_id);
switch (type) {
case 'vmess':
case 'vless':
if (!['http', 'h2', 'grpc', 'ws'].includes(value))
return _('Expecting: only support %s.').format(_('HTTP') +
' / ' + _('HTTPUpgrade') +
' / ' + _('gRPC') +
' / ' + _('WebSocket'));
break;
case 'trojan':
if (!['grpc', 'ws'].includes(value))
return _('Expecting: only support %s.').format(_('gRPC') +
' / ' + _('WebSocket'));
break;
default:
break;
}
return true;
}
so.depends('transport_enabled', '1');
so.modalonly = true;
so = ss.taboption('field_transport', form.DynamicList, 'transport_hosts', _('Server hostname'));
so.datatype = 'list(hostname)';
so.placeholder = 'example.com';
so.depends({transport_enabled: '1', transport_type: 'h2'});
so.modalonly = true;
so = ss.taboption('field_transport', form.Value, 'transport_http_method', _('HTTP request method'));
so.value('GET', _('GET'));
so.value('POST', _('POST'));
so.value('PUT', _('PUT'));
so.default = 'GET';
so.rmempty = false;
so.depends({transport_enabled: '1', transport_type: 'http'});
so.modalonly = true;
so = ss.taboption('field_transport', form.DynamicList, 'transport_paths', _('Request path'));
so.placeholder = '/video';
so.default = '/';
so.rmempty = false;
so.depends({transport_enabled: '1', transport_type: 'http'});
so.modalonly = true;
so = ss.taboption('field_transport', form.Value, 'transport_path', _('Request path'));
so.placeholder = '/';
so.default = '/';
so.rmempty = false;
so.depends({transport_enabled: '1', transport_type: /^(h2|ws)$/});
so.modalonly = true;
so = ss.taboption('field_transport', hm.TextValue, 'transport_http_headers', _('HTTP header'));
so.placeholder = '{\n "Host": "example.com",\n "Connection": [\n "keep-alive"\n ]\n}';
so.validate = L.bind(hm.validateJson, so);
so.depends({transport_enabled: '1', transport_type: /^(http|ws)$/});
so.modalonly = true;
so = ss.taboption('field_transport', form.Value, 'transport_grpc_servicename', _('gRPC service name'));
so.depends({transport_enabled: '1', transport_type: 'grpc'});
so.modalonly = true;
so = ss.taboption('field_transport', form.Value, 'transport_ws_max_early_data', _('Max Early Data'),
_('Early Data first packet length limit.'));
so.datatype = 'uinteger';
so.value('2048');
so.depends({transport_enabled: '1', transport_type: 'ws'});
so.modalonly = true;
so = ss.taboption('field_transport', form.Value, 'transport_ws_early_data_header', _('Early Data header name'));
so.value('Sec-WebSocket-Protocol');
so.depends({transport_enabled: '1', transport_type: 'ws'});
so.modalonly = true;
so = ss.taboption('field_transport', form.Flag, 'transport_ws_v2ray_http_upgrade', _('V2ray HTTPUpgrade'));
so.default = so.disabled;
so.depends({transport_enabled: '1', transport_type: 'ws'});
so.modalonly = true;
so = ss.taboption('field_transport', form.Flag, 'transport_ws_v2ray_http_upgrade_fast_open', _('V2ray HTTPUpgrade fast open'));
so.default = so.disabled;
so.depends({transport_enabled: '1', transport_type: 'ws', transport_ws_v2ray_http_upgrade: '1'});
so.modalonly = true;
/* Multiplex fields */ // TCP protocol only
so = ss.taboption('field_general', form.Flag, 'smux_enabled', _('Multiplex'));
so.default = so.disabled;
so.depends({type: /^(vmess|vless|trojan)$/});
so.depends({type: 'ss', uot: '0'});
so.modalonly = true;
so = ss.taboption('field_multiplex', form.ListValue, 'smux_protocol', _('Protocol'));
so.default = 'h2mux';
so.value('smux', _('smux'));
so.value('yamux', _('yamux'));
so.value('h2mux', _('h2mux'));
so.depends('smux_enabled', '1');
so.modalonly = true;
so = ss.taboption('field_multiplex', form.Value, 'smux_max_connections', _('Maximum connections'));
so.datatype = 'uinteger';
so.placeholder = '4';
so.depends('smux_enabled', '1');
so.modalonly = true;
so = ss.taboption('field_multiplex', form.Value, 'smux_min_streams', _('Minimum streams'),
_('Minimum multiplexed streams in a connection before opening a new connection.'));
so.datatype = 'uinteger';
so.placeholder = '4';
so.depends('smux_enabled', '1');
so.modalonly = true;
so = ss.taboption('field_multiplex', form.Value, 'smux_max_streams', _('Maximum streams'),
_('Maximum multiplexed streams in a connection before opening a new connection.<br/>' +
'Conflict with <code>%s</code> and <code>%s</code>.')
.format(_('Maximum connections'), _('Minimum streams')));
so.datatype = 'uinteger';
so.placeholder = '0';
so.depends({smux_enabled: '1', smux_max_connections: '', smux_min_streams: ''});
so.modalonly = true;
so = ss.taboption('field_multiplex', form.Flag, 'smux_padding', _('Enable padding'));
so.default = so.disabled;
so.depends('smux_enabled', '1');
so.modalonly = true;
so = ss.taboption('field_multiplex', form.Flag, 'smux_only_tcp', _('TCP only'),
_('Enable multiplexing only for TCP.'));
so.default = so.disabled;
so.depends('smux_enabled', '1');
so.modalonly = true;
so = ss.taboption('field_multiplex', form.Flag, 'smux_statistic', _('Enable statistic'),
_('Show connections in the dashboard for breaking connections easier.'));
so.default = so.disabled;
so.depends('smux_enabled', '1');
so.modalonly = true;
so = ss.taboption('field_multiplex', form.Flag, 'smux_brutal', _('Enable TCP Brutal'),
_('Enable TCP Brutal congestion control algorithm'));
so.default = so.disabled;
so.depends('smux_enabled', '1');
so.modalonly = true;
so = ss.taboption('field_multiplex', form.Value, 'smux_brutal_up', _('Upload bandwidth'),
_('Upload bandwidth in Mbps.'));
so.datatype = 'uinteger';
so.depends('smux_brutal', '1');
so.modalonly = true;
so = ss.taboption('field_multiplex', form.Value, 'smux_brutal_down', _('Download bandwidth'),
_('Download bandwidth in Mbps.'));
so.datatype = 'uinteger';
so.depends('smux_brutal', '1');
so.modalonly = true;
/* Dial fields */
so = ss.taboption('field_dial', form.Flag, 'tfo', _('TFO'));
so.default = so.disabled;
so.modalonly = true;
so = ss.taboption('field_dial', form.Flag, 'mptcp', _('mpTCP'));
so.default = so.disabled;
so.modalonly = true;
/* Features are implemented in proxy chain
so = ss.taboption('field_dial', form.Value, 'dialer_proxy', _('dialer-proxy'));
so.readonly = true;
so.modalonly = true;
*/
so = ss.taboption('field_dial', widgets.DeviceSelect, 'interface_name', _('Bind interface'),
_('Bind outbound interface.</br>') +
_('Priority: Proxy Node > Global.'));
so.multiple = false;
so.noaliases = true;
so.modalonly = true;
so = ss.taboption('field_dial', form.Value, 'routing_mark', _('Routing mark'),
_('Priority: Proxy Node > Global.'));
so.datatype = 'uinteger';
so.modalonly = true;
so = ss.taboption('field_dial', form.ListValue, 'ip_version', _('IP version'));
so.default = hm.ip_version[0][0];
hm.ip_version.forEach((res) => {
so.value.apply(so, res);
})
so.modalonly = true;
/* Proxy Node END */
/* Provider START */
s.tab('provider', _('Provider'));
/* Provider */
o = s.taboption('provider', form.SectionValue, '_provider', hm.GridSection, 'provider', null);
ss = o.subsection;
ss.addremove = true;
ss.rowcolors = true;
ss.sortable = true;
ss.nodescriptions = true;
ss.hm_modaltitle = [ _('Provider'), _('Add a provider') ];
ss.hm_prefmt = hm.glossary[ss.sectiontype].prefmt;
ss.hm_field = hm.glossary[ss.sectiontype].field;
ss.hm_lowcase_only = false;
/* Import mihomo config and Remove idle files start */
ss.handleYamlImport = function() {
const field = this.hm_field;
const o = new hm.HandleImport(this.map, this, _('Import mihomo config'),
_('Please type <code>%s</code> fields of mihomo config.</br>')
.format(field));
o.placeholder = 'proxy-providers:\n' +
' provider1:\n' +
' type: http\n' +
' url: "http://test.com"\n' +
' path: ./proxy_providers/provider1.yaml\n' +
' interval: 3600\n' +
' proxy: DIRECT\n' +
' size-limit: 0\n' +
' header:\n' +
' User-Agent:\n' +
' - "Clash/v1.18.0"\n' +
' - "mihomo/1.18.3"\n' +
' Accept:\n' +
" - 'application/vnd.github.v3.raw'\n" +
' Authorization:\n' +
" - 'token 1231231'\n" +
' health-check:\n' +
' enable: true\n' +
' interval: 600\n' +
' timeout: 5000\n' +
' lazy: true\n' +
' url: https://cp.cloudflare.com/generate_204\n' +
' expected-status: 204\n' +
' override:\n' +
' tfo: false\n' +
' mptcp: false\n' +
' udp: true\n' +
' udp-over-tcp: false\n' +
' down: "50 Mbps"\n' +
' up: "10 Mbps"\n' +
' skip-cert-verify: true\n' +
' dialer-proxy: proxy\n' +
' interface-name: tailscale0\n' +
' routing-mark: 233\n' +
' ip-version: ipv4-prefer\n' +
' additional-prefix: "[provider1]"\n' +
' additional-suffix: "test"\n' +
' proxy-name:\n' +
' - pattern: "test"\n' +
' target: "TEST"\n' +
' - pattern: "IPLC-(.*?)倍"\n' +
' target: "iplc x $1"\n' +
' filter: "(?i)港|hk|hongkong|hong kong"\n' +
' exclude-filter: "xxx"\n' +
' exclude-type: "ss|http"\n' +
' provider2:\n' +
' type: inline\n' +
' dialer-proxy: proxy\n' +
' payload:\n' +
' - name: "ss1"\n' +
' type: ss\n' +
' server: test.server.com\n' +
' port: 443\n' +
' cipher: chacha20-ietf-poly1305\n' +
' password: "password"\n' +
' provider3:\n' +
' type: http\n' +
' url: "http://test.com"\n' +
' path: ./proxy_providers/provider3.yaml\n' +
' proxy: proxy\n' +
' test:\n' +
' type: file\n' +
' path: /test.yaml\n' +
' health-check:\n' +
' enable: true\n' +
' interval: 36000\n' +
' url: https://cp.cloudflare.com/generate_204\n' +
' ...'
o.parseYaml = function(field, name, cfg) {
let config = hm.HandleImport.prototype.parseYaml.call(this, field, name, cfg);
return config ? parseProviderYaml.call(this, field, name, config) : null;
};
return o.render();
}
ss.renderSectionAdd = function(/* ... */) {
let el = hm.GridSection.prototype.renderSectionAdd.apply(this, arguments);
el.appendChild(E('button', {
'class': 'cbi-button cbi-button-add',
'title': _('mihomo config'),
'click': ui.createHandlerFn(this, 'handleYamlImport')
}, [ _('Import mihomo config') ]));
el.appendChild(E('button', {
'class': 'cbi-button cbi-button-add',
'title': _('Remove idles'),
'click': ui.createHandlerFn(this, hm.handleRemoveIdles)
}, [ _('Remove idles') ]));
return el;
}
/* Import mihomo config and Remove idle files end */
ss.tab('field_general', _('General fields'));
ss.tab('field_override', _('Override fields'));
ss.tab('field_health', _('Health fields'));
/* General fields */
so = ss.taboption('field_general', form.Value, 'label', _('Label'));
so.load = L.bind(hm.loadDefaultLabel, so);
so.validate = L.bind(hm.validateUniqueValue, so);
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'enabled', _('Enable'));
so.default = so.enabled;
so.editable = true;
so = ss.taboption('field_general', form.ListValue, 'type', _('Type'));
so.value('file', _('Local'));
so.value('http', _('Remote'));
so.value('inline', _('Inline'));
so.default = 'http';
so = ss.option(form.DummyValue, '_value', _('Value'));
so.load = function(section_id) {
const option = uci.get(data[0], section_id, 'type');
switch (option) {
case 'file':
return uci.get(data[0], section_id, '.name');
case 'http':
return uci.get(data[0], section_id, 'url');
case 'inline':
return uci.get(data[0], section_id, '.name');
default:
return null;
}
}
so.modalonly = false;
so = ss.taboption('field_general', hm.TextValue, '_editer', _('Editer'),
_('Please type <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/proxy-providers/content/', _('Contents')));
so.placeholder = _('Content will not be verified, Please make sure you enter it correctly.');
so.load = function(section_id) {
return L.resolveDefault(hm.readFile(this.section.sectiontype, section_id), '');
}
so.write = L.bind(hm.writeFile, so, so.section.sectiontype);
so.remove = L.bind(hm.writeFile, so, so.section.sectiontype);
so.rmempty = false;
so.retain = true;
so.depends('type', 'file');
so.modalonly = true;
so = ss.taboption('field_general', hm.TextValue, 'payload', 'payload:',
_('Please type <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/proxy-providers/content/', _('Payload')));
so.placeholder = '- name: "ss1"\n type: ss\n server: server\n port: 443\n cipher: chacha20-ietf-poly1305\n password: "password"\n# ' + _('Content will not be verified, Please make sure you enter it correctly.');
so.rmempty = false;
so.depends('type', 'inline');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'url', _('Provider URL'));
so.validate = L.bind(hm.validateUrl, so);
so.rmempty = false;
so.depends('type', 'http');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'size_limit', _('Size limit'),
_('In bytes. <code>%s</code> will be used if empty.').format('0'));
so.placeholder = '0';
so.validate = L.bind(hm.validateBytesize, so);
so.depends('type', 'http');
so = ss.taboption('field_general', form.Value, 'interval', _('Update interval'),
_('In seconds. <code>%s</code> will be used if empty.').format('86400'));
so.placeholder = '86400';
so.validate = L.bind(hm.validateTimeDuration, so);
so.depends('type', 'http');
so = ss.taboption('field_general', form.ListValue, 'proxy', _('Proxy group'),
_('Name of the Proxy group to download provider.'));
so.default = hm.preset_outbound.direct[0][0];
hm.preset_outbound.direct.forEach((res) => {
so.value.apply(so, res);
})
so.load = L.bind(hm.loadProxyGroupLabel, so, hm.preset_outbound.direct);
so.textvalue = L.bind(hm.textvalue2Value, so);
//so.editable = true;
so.depends('type', 'http');
so = ss.taboption('field_general', hm.TextValue, 'header', _('HTTP header'),
_('Custom HTTP header.'));
so.placeholder = '{\n "User-Agent": [\n "Clash/v1.18.0",\n "mihomo/1.18.3"\n ],\n "Accept": [\n //"application/vnd.github.v3.raw"\n ],\n "Authorization": [\n //"token 1231231"\n ]\n}';
so.validate = L.bind(hm.validateJson, so);
so.depends('type', 'http');
so.modalonly = true;
/* Override fields */
// https://github.com/muink/mihomo/blob/43f21c0b412b7a8701fe7a2ea6510c5b985a53d6/adapter/provider/parser.go#L30
so = ss.taboption('field_override', form.Value, 'override_prefix', _('Add prefix'));
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Value, 'override_suffix', _('Add suffix'));
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.DynamicList, 'override_replace', _('Replace name'),
_('Replace node name. ') +
_('For format see <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/proxy-providers/#overrideproxy-name', _('override.proxy-name')));
so.placeholder = '{"pattern": "IPLC-(.*?)倍", "target": "iplc x $1"}';
so.validate = L.bind(hm.validateJson, so);
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.DummyValue, '_config_items', null);
so.load = function() {
return '<a target="_blank" href="%s" rel="noreferrer noopener">%s</a>'
.format('https://wiki.metacubex.one/config/proxy-providers/#_2', _('Configuration Items'));
}
so.rawhtml = true;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Flag, 'override_tfo', _('TFO'));
so.default = so.disabled;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Flag, 'override_mptcp', _('mpTCP'));
so.default = so.disabled;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Flag, 'override_udp', _('UDP'));
so.default = so.enabled;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Flag, 'override_uot', _('UoT'),
_('Enable the SUoT protocol, requires server support. Conflict with Multiplex.'));
so.default = so.disabled;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Value, 'override_up', _('up'),
_('In Mbps.'));
so.datatype = 'uinteger';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Value, 'override_down', _('down'),
_('In Mbps.'));
so.datatype = 'uinteger';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Flag, 'override_skip_cert_verify', _('Skip cert verify'),
_('Donot verifying server certificate.') +
'<br/>' +
_('This is <strong>DANGEROUS</strong>, your traffic is almost like <strong>PLAIN TEXT</strong>! Use at your own risk!'));
so.default = so.disabled;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
/* Features are implemented in proxy chain
so = ss.taboption('field_override', form.Value, 'override_dialer_proxy', _('dialer-proxy'));
so.readonly = true;
so.modalonly = true;
*/
so = ss.taboption('field_override', widgets.DeviceSelect, 'override_interface_name', _('Bind interface'),
_('Bind outbound interface.</br>') +
_('Priority: Proxy Node > Global.'));
so.multiple = false;
so.noaliases = true;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Value, 'override_routing_mark', _('Routing mark'),
_('Priority: Proxy Node > Global.'));
so.datatype = 'uinteger';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.ListValue, 'override_ip_version', _('IP version'));
so.default = hm.ip_version[0][0];
hm.ip_version.forEach((res) => {
so.value.apply(so, res);
})
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
/* Health fields */
so = ss.taboption('field_health', form.Flag, 'health_enable', _('Enable'));
so.default = so.enabled;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Value, 'health_url', _('Health check URL'));
so.default = hm.health_checkurls[0][0];
hm.health_checkurls.forEach((res) => {
so.value.apply(so, res);
})
so.validate = L.bind(hm.validateUrl, so);
so.retain = true;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Value, 'health_interval', _('Health check interval'),
_('In seconds. <code>%s</code> will be used if empty.').format('600'));
so.placeholder = '600';
so.validate = L.bind(hm.validateTimeDuration, so);
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Value, 'health_timeout', _('Health check timeout'),
_('In millisecond. <code>%s</code> will be used if empty.').format('5000'));
so.datatype = 'uinteger';
so.placeholder = '5000';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Flag, 'health_lazy', _('Lazy'),
_('No testing is performed when this provider node is not in use.'));
so.default = so.enabled;
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Value, 'health_expected_status', _('Health check expected status'),
_('Expected HTTP code. <code>204</code> will be used if empty. ') +
_('For format see <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/proxy-groups/#expected-status', _('Expected status')));
so.placeholder = '200/302/400-503';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
/* General fields */
so = ss.taboption('field_general', form.DynamicList, 'filter', _('Node filter'),
_('Filter nodes that meet keywords or regexps.'));
so.placeholder = '(?i)港|hk|hongkong|hong kong';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_general', form.DynamicList, 'exclude_filter', _('Node exclude filter'),
_('Exclude nodes that meet keywords or regexps.'));
so.default = '重置|到期|过期|剩余|套餐 海外用户|回国'
so.placeholder = 'xxx';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_general', form.DynamicList, 'exclude_type', _('Node exclude type'),
_('Exclude matched node types.'));
so.placeholder = 'ss|http';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.option(form.DummyValue, '_update');
so.cfgvalue = L.bind(hm.renderResDownload, so);
so.editable = true;
so.modalonly = false;
/* Provider END */
/* Proxy chain START */
s.tab('dialer_proxy', _('Proxy chain'));
/* Proxy chain */
o = s.taboption('dialer_proxy', form.SectionValue, '_dialer_proxy', hm.GridSection, 'dialer_proxy', null);
ss = o.subsection;
ss.addremove = true;
ss.rowcolors = true;
ss.sortable = true;
ss.nodescriptions = true;
ss.hm_modaltitle = [ _('Proxy chain'), _('Add a proxy chain') ];
ss.hm_prefmt = hm.glossary[ss.sectiontype].prefmt;
ss.hm_field = hm.glossary[ss.sectiontype].field;
ss.hm_lowcase_only = true;
so = ss.option(form.Value, 'label', _('Label'));
so.load = L.bind(hm.loadDefaultLabel, so);
so.validate = L.bind(hm.validateUniqueValue, so);
so.modalonly = true;
so = ss.option(form.Flag, 'enabled', _('Enable'));
so.default = so.enabled;
so.editable = true;
so = ss.option(form.ListValue, 'type', _('Type'));
so.value('node', _('Proxy Node'));
so.value('provider', _('Provider'));
so.default = 'node';
so.textvalue = L.bind(hm.textvalue2Value, so);
so = ss.option(form.DummyValue, '_value', _('Value'));
so.load = function(section_id) {
const type = uci.get(data[0], section_id, 'type');
const detour = uci.get(data[0], section_id, 'chain_tail_group') || uci.get(data[0], section_id, 'chain_tail');
switch (type) {
case 'node':
return '%s » %s'.format(
uci.get(data[0], section_id, 'chain_head'),
detour
);
case 'provider':
return '%s » %s'.format(
uci.get(data[0], section_id, 'chain_head_sub'),
detour
);
default:
return null;
}
}
so.modalonly = false;
so = ss.option(form.ListValue, 'chain_head_sub', _('Chain head'));
so.load = L.bind(hm.loadProviderLabel, so, [['', _('-- Please choose --')]]);
so.rmempty = false;
so.depends('type', 'provider');
so.modalonly = true;
so = ss.option(form.ListValue, 'chain_head', _('Chain head'),
_('Recommended to use UoT node.</br>such as <code>%s</code>.')
.format('ss|ssr|vmess|vless|trojan|tuic'));
so.load = L.bind(hm.loadNodeLabel, so, [['', _('-- Please choose --')]]);
so.rmempty = false;
so.validate = function(section_id, value) {
const chain_tail = this.section.getUIElement(section_id, 'chain_tail').getValue();
if (value === chain_tail)
return _('Expecting: %s').format(_('Different chain head/tail'));
return true;
}
so.depends('type', 'node');
so.modalonly = true;
so = ss.option(form.ListValue, 'chain_tail_group', _('Chain tail'));
so.load = L.bind(hm.loadProxyGroupLabel, so, [['', _('-- Please choose --')]]);
so.rmempty = false;
so.depends({chain_tail: /.+/, '!reverse': true});
so.modalonly = true;
so = ss.option(form.ListValue, 'chain_tail', _('Chain tail'),
_('Recommended to use UoT node.</br>such as <code>%s</code>.')
.format('ss|ssr|vmess|vless|trojan|tuic'));
so.load = L.bind(hm.loadNodeLabel, so, [['', _('-- Please choose --')]]);
so.rmempty = false;
so.validate = function(section_id, value) {
const chain_head = this.section.getUIElement(section_id, 'chain_head').getValue();
if (value === chain_head)
return _('Expecting: %s').format(_('Different chain head/tail'));
return true;
}
so.depends({chain_tail_group: /.+/, '!reverse': true});
so.modalonly = true;
/* Proxy chain END */
return m.render();
}
});