'use strict';
'require form';
'require fs';
'require network';
'require poll';
'require rpc';
'require uci';
'require ui';
'require view';
'require fchomo as hm';
'require tools.firewall as fwtool';
'require tools.widgets as widgets';
const callResVersion = rpc.declare({
object: 'luci.fchomo',
method: 'resources_get_version',
params: ['type', 'repo'],
expect: { '': {} }
});
const callCrondSet = rpc.declare({
object: 'luci.fchomo',
method: 'crond_set',
params: ['type', 'expr'],
expect: { '': {} }
});
function getRandom(min, max) {
const floatRandom = Math.random()
const difference = max - min
// A random number between 0 and the difference
const random = Math.round(difference * floatRandom)
return random + min
}
function handleResUpdate(type, repo) {
const callResUpdate = rpc.declare({
object: 'luci.fchomo',
method: 'resources_update',
params: ['type', 'repo'],
expect: { '': {} }
});
// Dynamic repo
let label;
if (repo) {
const section_id = this.section.section;
let weight = document.getElementById(this.cbid(section_id));
if (weight)
repo = weight.firstChild.value,
label = weight.firstChild.selectedOptions[0].label;
}
return L.resolveDefault(callResUpdate(type, repo), {}).then((res) => {
switch (res.status) {
case 0:
this.description = (repo ? label + ' ' : '') + _('Successfully updated.');
break;
case 1:
this.description = (repo ? label + ' ' : '') + _('Update failed.');
break;
case 2:
this.description = (repo ? label + ' ' : '') + _('Already in updating.');
break;
case 3:
this.description = (repo ? label + ' ' : '') + _('Already at the latest version.');
break;
default:
this.description = (repo ? label + ' ' : '') + _('Unknown error.');
break;
}
return this.map.reset();
});
}
function renderResVersion(El, type, repo) {
return L.resolveDefault(callResVersion(type, repo), {}).then((res) => {
let resEl = E([
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': ui.createHandlerFn(this, handleResUpdate, type, repo)
}, [ _('Check update') ]),
updateResVersion(E('span', { style: 'border: unset; font-weight: bold; align-items: center' }), res.version)
]);
if (El) {
El.appendChild(resEl);
El.lastChild.style.display = 'flex';
} else
El = resEl;
return El;
});
}
function updateResVersion(El, version) {
if (El) {
El.style.color = version ? 'green' : 'red';
El.innerHTML = ' %s'.format(version || _('not found'));
}
return El;
}
return view.extend({
load() {
return Promise.all([
uci.load('fchomo'),
hm.getFeatures(),
network.getHostHints(),
hm.getServiceStatus('mihomo-c'),
hm.getClashAPI('mihomo-c'),
hm.getServiceStatus('mihomo-s'),
hm.getClashAPI('mihomo-s'),
callResVersion('geoip').then((res) => { return res.version }),
callResVersion('geosite').then((res) => { return res.version })
]);
},
render(data) {
const features = data[1];
const hosts = data[2]?.hosts;
const CisRunning = data[3];
const CclashAPI = data[4];
const SisRunning = data[5];
const SclashAPI = data[6];
const res_ver_geoip = data[7];
const res_ver_geosite = data[8];
const dashboard_repo = uci.get(data[0], 'api', 'dashboard_repo');
let m, s, o, ss, so;
m = new form.Map('fchomo', _('FullCombo Mihomo'),
'
');
s = m.section(form.NamedSection, 'config', 'fchomo');
/* Overview START */
s.tab('status', _('Overview'));
/* Service status */
o = s.taboption('status', form.SectionValue, '_status', form.NamedSection, 'config', 'fchomo', _('Service status'));
ss = o.subsection;
so = ss.option(form.DummyValue, '_core_version', _('Core version'));
so.cfgvalue = function() {
return E('strong', [features.core_version || _('Unknown')]);
}
so = ss.option(form.DummyValue, '_luciapp_version', _('Application version'));
so.cfgvalue = function() {
return E('strong', [features.luciapp_version || _('Unknown')]);
}
so = ss.option(form.DummyValue, '_client_status', _('Client status'));
so.cfgvalue = function() { return hm.renderStatus('_client_bar', CisRunning ? { ...CclashAPI, dashboard_repo: dashboard_repo } : false, 'mihomo-c') }
poll.add(function() {
return hm.getServiceStatus('mihomo-c').then((isRunning) => {
hm.updateStatus(document.getElementById('_client_bar'), isRunning ? { dashboard_repo: dashboard_repo } : false, 'mihomo-c');
});
})
so = ss.option(form.DummyValue, '_server_status', _('Server status'));
so.cfgvalue = function() { return hm.renderStatus('_server_bar', SisRunning ? { ...SclashAPI, dashboard_repo: dashboard_repo } : false, 'mihomo-s') }
poll.add(function() {
return hm.getServiceStatus('mihomo-s').then((isRunning) => {
hm.updateStatus(document.getElementById('_server_bar'), isRunning ? { dashboard_repo: dashboard_repo } : false, 'mihomo-s');
});
})
so = ss.option(form.Button, '_reload', _('Reload All'));
so.inputtitle = _('Reload');
so.inputstyle = 'apply';
so.onclick = L.bind(hm.handleReload, so, null);
so = ss.option(form.DummyValue, '_conn_check', _('Connection check'));
so.cfgvalue = function() {
const callConnStat = rpc.declare({
object: 'luci.fchomo',
method: 'connection_check',
params: ['url'],
expect: { '': {} }
});
const ElId = '_connection_check_results';
return E([
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': ui.createHandlerFn(this, function() {
let weight = document.getElementById(ElId);
weight.innerHTML = '';
return hm.checkurls.forEach((site) => {
L.resolveDefault(callConnStat(site[0]), {}).then((res) => {
weight.innerHTML += ' %s'.format((res.httpcode && res.httpcode.match(/^20\d$/)) ? 'green' : 'red', site[1]);
});
});
})
}, [ _('Check') ]),
E('strong', { id: ElId }, [
E('span', { style: 'color:gray' }, ' ' + _('unchecked'))
])
]);
}
so = ss.option(form.Value, '_nattest', _('Check routerself NAT Behavior'));
so.default = `udp://${hm.stunserver[0][0]}`;
hm.stunserver.forEach((res) => {
so.value.apply(so, res);
})
so.rmempty = false;
if (!features.hm_has_stunclient) {
so.description = _('To check NAT Behavior you need to install stuntman-client first')
.format('https://github.com/muink/openwrt-stuntman');
so.readonly = true;
} else {
so.renderWidget = function(section_id, option_index, cfgvalue) {
const cval = new URL(cfgvalue || this.default);
//console.info(cval.toString());
let El = form.Value.prototype.renderWidget.call(this, section_id, option_index, cval.host);
let resEl = E('div', { 'class': 'control-group' }, [
E('select', {
'id': '_status_nattest_l4proto',
'class': 'cbi-input-select',
'style': 'width: 5em'
}, [
...[
['udp', 'UDP'], // default
['tcp', 'TCP']
].map(res => E('option', {
value: res[0],
selected: (cval.protocol === `${res[0]}:`) ? "" : null
}, res[1]))
]),
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': ui.createHandlerFn(this, function() {
const stun = this.formvalue(this.section.section);
const l4proto = document.getElementById('_status_nattest_l4proto').value;
return fs.exec_direct('/etc/fchomo/scripts/natcheck.sh', [stun, l4proto, getRandom(32768, 61000)]).then((stdout) => {
this.description = '' + _('Expand/Collapse result') + '
' + stdout + ' ';
return this.map.reset().then((res) => {
});
});
})
}, [ _('Check') ])
]);
ui.addValidator(resEl.querySelector('#_status_nattest_l4proto'), 'string', false, (v) => {
const section_id = this.section.section;
const stun = this.formvalue(section_id);
this.onchange.call(this, {}, section_id, stun);
return true;
}, 'change');
let newEl = E('div', { style: 'font-weight: bold; align-items: center; display: flex' }, []);
if (El) {
newEl.appendChild(E([El, resEl]));
} else
newEl.appendChild(resEl);
return newEl;
}
}
so.onchange = function(ev, section_id, value) {
const l4proto = document.getElementById('_status_nattest_l4proto').value;
this.default = `${l4proto}://${value}`;
}
so.write = function() {};
so.remove = function() {};
/* Resources management */
o = s.taboption('status', form.SectionValue, '_config', form.NamedSection, 'resources', 'fchomo', _('Resources management'));
ss = o.subsection;
if (!res_ver_geoip || !res_ver_geosite) {
so = ss.option(form.Button, '_upload_initia', _('Upload initial package'));
so.inputstyle = 'action';
so.inputtitle = _('Upload...');
so.onclick = L.bind(hm.uploadInitialPack, so);
}
so = ss.option(form.Flag, 'auto_update', _('Auto update'),
_('Auto update resources.'));
so.default = so.disabled;
so.rmempty = false;
so.write = function(section_id, formvalue) {
if (formvalue == 1) {
callCrondSet('resources', uci.get(data[0], section_id, 'auto_update_expr'));
} else
callCrondSet('resources');
return this.super('write', section_id, formvalue);
}
so = ss.option(form.Value, 'auto_update_expr', _('Cron expression'),
_('The default value is 2:00 every day.'));
so.default = '0 2 * * *';
so.placeholder = '0 2 * * *';
so.rmempty = false;
so.retain = true;
so.depends('auto_update', '1');
so.write = function(section_id, formvalue) {
callCrondSet('resources', formvalue);
return this.super('write', section_id, formvalue);
};
so.remove = function(section_id) {
callCrondSet('resources');
return this.super('remove', section_id);
};
so = ss.option(form.ListValue, '_dashboard_version', _('Dashboard version'));
so.default = hm.dashrepos[0][0];
hm.dashrepos.forEach((repo) => {
so.value.apply(so, repo);
})
so.renderWidget = function(/* ... */) {
let El = form.ListValue.prototype.renderWidget.apply(this, arguments);
El.classList.add('control-group');
El.firstChild.style.width = '10em';
return renderResVersion.call(this, El, 'dashboard', this.default);
}
so.onchange = function(ev, section_id, value) {
this.default = value;
let weight = ev.target;
if (weight)
return L.resolveDefault(callResVersion('dashboard', value), {}).then((res) => {
updateResVersion(weight.lastChild, res.version);
});
}
so.write = function() {};
so = ss.option(form.DummyValue, '_geoip_version', _('GeoIP version'));
so.cfgvalue = function() { return renderResVersion.call(this, null, 'geoip') };
so = ss.option(form.DummyValue, '_geosite_version', _('GeoSite version'));
so.cfgvalue = function() { return renderResVersion.call(this, null, 'geosite') };
so = ss.option(form.DummyValue, '_asn_version', _('ASN version'));
so.cfgvalue = function() { return renderResVersion.call(this, null, 'asn') };
so = ss.option(form.DummyValue, '_china_ip4_version', _('China IPv4 list version'));
so.cfgvalue = function() { return renderResVersion.call(this, null, 'china_ip4') };
so = ss.option(form.DummyValue, '_china_ip6_version', _('China IPv6 list version'));
so.cfgvalue = function() { return renderResVersion.call(this, null, 'china_ip6') };
so = ss.option(form.DummyValue, '_gfw_list_version', _('GFW list version'));
so.cfgvalue = function() { return renderResVersion.call(this, null, 'gfw_list') };
so = ss.option(form.DummyValue, '_china_list_version', _('China list version'));
so.cfgvalue = function() { return renderResVersion.call(this, null, 'china_list') };
/* Overview END */
/* General START */
s.tab('general', _('General'));
/* General settings */
o = s.taboption('general', form.SectionValue, '_global', form.NamedSection, 'global', 'fchomo', _('General settings'));
ss = o.subsection;
so = ss.option(form.ListValue, 'mode', _('Operation mode'));
so.value('direct', _('Direct'));
so.value('rule', _('Rule'));
so.value('global', _('Global'));
so.default = 'rule';
so = ss.option(form.ListValue, 'find_process_mode', _('Process matching mode'));
so.value('always', _('Enable'));
so.value('strict', _('Auto'));
so.value('off', _('Disable'));
so.default = 'off';
so = ss.option(form.ListValue, 'log_level', _('Log level'));
so.value('silent', _('Silent'));
so.value('error', _('Error'));
so.value('warning', _('Warning'));
so.value('info', _('Info'));
so.value('debug', _('Debug'));
so.default = 'warning';
so = ss.option(form.Flag, 'etag_support', _('ETag support'));
so.default = so.enabled;
so = ss.option(form.Flag, 'ipv6', _('IPv6 support'));
so.default = so.enabled;
so = ss.option(form.Flag, 'unified_delay', _('Unified delay'));
so.default = so.disabled;
so = ss.option(form.Flag, 'tcp_concurrent', _('TCP concurrency'));
so.default = so.disabled;
so = ss.option(form.Value, 'keep_alive_interval', _('TCP-Keep-Alive interval'),
_('In seconds. %s
will be used if empty.').format('30'));
so.placeholder = '30';
so.validate = L.bind(hm.validateTimeDuration, so);
so = ss.option(form.Value, 'keep_alive_idle', _('TCP-Keep-Alive idle timeout'),
_('In seconds. %s
will be used if empty.').format('600'));
so.placeholder = '600';
so.validate = L.bind(hm.validateTimeDuration, so);
/* Global Authentication */
o = s.taboption('general', form.SectionValue, '_global', form.NamedSection, 'global', 'fchomo', _('Global Authentication'));
ss = o.subsection;
so = ss.option(form.DynamicList, 'authentication', _('User Authentication'));
so.datatype = 'list(string)';
so.placeholder = 'user1:pass1';
so.validate = L.bind(hm.validateAuth, so);
so = ss.option(form.DynamicList, 'skip_auth_prefixes', _('No Authentication IP ranges'));
so.datatype = 'list(cidr)';
so.placeholder = '127.0.0.1/8';
/* General END */
/* Inbound START */
s.tab('inbound', _('Inbound'));
/* Listen ports */
o = s.taboption('inbound', form.SectionValue, '_inbound', form.NamedSection, 'inbound', 'fchomo', _('Listen ports'));
ss = o.subsection;
so = ss.option(form.Value, 'mixed_port', _('Mixed port'));
so.datatype = 'port';
so.placeholder = '7890';
so.rmempty = false;
so = ss.option(form.Value, 'redir_port', _('Redir port'));
so.datatype = 'port';
so.placeholder = '7891';
so.rmempty = false;
so = ss.option(form.Value, 'tproxy_port', _('Tproxy port'));
so.datatype = 'port';
so.placeholder = '7892';
so.rmempty = false;
so = ss.option(form.Value, 'tunnel_port', _('DNS port'));
so.datatype = 'port';
so.placeholder = '7893';
so.rmempty = false;
so = ss.option(form.ListValue, 'proxy_mode', _('Proxy mode'));
so.value('redir', _('Redirect TCP'));
if (features.hm_has_tproxy)
so.value('redir_tproxy', _('Redirect TCP + TProxy UDP'));
if (features.hm_has_ip_full && features.hm_has_tun) {
so.value('redir_tun', _('Redirect TCP + Tun UDP'));
so.value('tun', _('Tun TCP/UDP'));
} else
so.description = _('To enable Tun support, you need to install ip-full
and kmod-tun
');
so.default = 'redir_tproxy';
so.rmempty = false;
/* Tun settings */
o = s.taboption('inbound', form.SectionValue, '_inbound', form.NamedSection, 'inbound', 'fchomo', _('Tun settings'));
ss = o.subsection;
so = ss.option(form.RichListValue || form.ListValue, 'tun_stack', _('Stack'), // less_24_10
_('Tun stack.'));
so.value('system', _('System'), _('Less compatibility and sometimes better performance.'));
if (features.with_gvisor) {
so.value('gvisor', _('gVisor'), _('Based on google/gvisor.'));
so.value('mixed', _('Mixed'), _('Mixed system
TCP stack and gVisor
UDP stack.'));
}
so.default = 'system';
so.rmempty = false;
if (hm.less_24_10)
so.onchange = function(ev, section_id, value) {
var desc = ev.target.nextSibling;
if (value === 'mixed')
desc.innerHTML = _('Mixed system
TCP stack and gVisor
UDP stack.');
else if (value === 'gvisor')
desc.innerHTML = _('Based on google/gvisor.');
else if (value === 'system')
desc.innerHTML = _('Less compatibility and sometimes better performance.');
}
so = ss.option(form.Value, 'tun_mtu', _('MTU'));
so.datatype = 'uinteger';
so.placeholder = '9000';
so = ss.option(form.Flag, 'tun_gso', _('Generic segmentation offload'));
so.default = so.disabled;
so = ss.option(form.Value, 'tun_gso_max_size', _('Segment maximum size'));
so.datatype = 'uinteger';
so.placeholder = '65536';
so = ss.option(form.Value, 'tun_udp_timeout', _('UDP NAT expiration time'),
_('In seconds. %s
will be used if empty.').format('300'));
so.placeholder = '300';
so.validate = L.bind(hm.validateTimeDuration, so);
so = ss.option(form.Flag, 'tun_endpoint_independent_nat', _('Endpoint-Independent NAT'),
_('Performance may degrade slightly, so it is not recommended to enable on when it is not needed.'));
so.default = so.disabled;
/* Inbound END */
/* TLS START */
s.tab('tls', _('TLS'));
/* TLS settings */
o = s.taboption('tls', form.SectionValue, '_tls', form.NamedSection, 'tls', 'fchomo', null);
ss = o.subsection;
so = ss.option(form.ListValue, 'global_client_fingerprint', _('Global client fingerprint'));
so.default = hm.tls_client_fingerprints[0][0];
hm.tls_client_fingerprints.forEach((res) => {
so.value.apply(so, res);
})
so = ss.option(form.Value, 'tls_cert_path', _('API TLS certificate path'));
so.datatype = 'file';
so.value('/etc/ssl/acme/example.crt');
so = ss.option(form.Value, 'tls_key_path', _('API TLS private key path'));
so.datatype = 'file';
so.value('/etc/ssl/acme/example.key');
/* TLS END */
/* API START */
s.tab('api', _('API'));
/* API settings */
o = s.taboption('api', form.SectionValue, '_api', form.NamedSection, 'api', 'fchomo', null);
ss = o.subsection;
so = ss.option(form.ListValue, 'dashboard_repo', _('Select Dashboard'));
so.default = hm.dashrepos[0][0];
so.load = function(section_id) {
delete this.keylist;
delete this.vallist;
hm.dashrepos.forEach((repo) => {
L.resolveDefault(callResVersion('dashboard', repo[0]), {}).then((res) => {
this.value(repo[0], repo[1] + ' - ' + (res.version || _('Not Installed')));
});
});
return this.super('load', section_id);
}
so.rmempty = false;
so = ss.option(form.DynamicList, 'external_controller_cors_allow_origins', _('CORS Allow origins'),
_('CORS allowed origins, *
will be used if empty.'));
so.placeholder = 'https://yacd.metacubex.one';
so = ss.option(form.Flag, 'external_controller_cors_allow_private_network', _('CORS Allow private network'),
_('Allow access from private network.' +
'To access the API on a private network from a public website, it must be enabled.'));
so.default = so.enabled;
so = ss.option(form.Value, 'external_controller_port', _('API HTTP port'));
so.datatype = 'port';
so.placeholder = '9090';
so = ss.option(form.Value, 'external_controller_tls_port', _('API HTTPS port'));
so.datatype = 'port';
so.placeholder = '9443';
so.depends({'fchomo.tls.tls_cert_path': /^\/.+/, 'fchomo.tls.tls_key_path': /^\/.+/});
so = ss.option(form.Value, 'external_doh_server', _('API DoH service'));
so.placeholder = '/dns-query';
so.depends({'external_controller_tls_port': /\d+/});
so = ss.option(form.Value, 'secret', _('API secret'),
_('Random will be used if empty.'));
so.password = true;
/* API END */
/* Sniffer START */
s.tab('sniffer', _('Sniffer'));
/* Sniffer settings */
o = s.taboption('sniffer', form.SectionValue, '_sniffer', form.NamedSection, 'sniffer', 'fchomo', _('Sniffer settings'));
ss = o.subsection;
so = ss.option(form.Flag, 'override_destination', _('Override destination'),
_('Override the connection destination address with the sniffed domain.'));
so.default = so.enabled;
so = ss.option(form.DynamicList, 'force_domain', _('Forced sniffing domain'));
so.datatype = 'list(string)';
so = ss.option(form.DynamicList, 'skip_domain', _('Skiped sniffing domain'));
so.datatype = 'list(string)';
so = ss.option(form.DynamicList, 'skip_src_address', _('Skiped sniffing src address'));
so.datatype = 'list(cidr)';
so = ss.option(form.DynamicList, 'skip_dst_address', _('Skiped sniffing dst address'));
so.datatype = 'list(cidr)';
/* Sniff protocol settings */
o = s.taboption('sniffer', form.SectionValue, '_sniffer_sniff', form.GridSection, 'sniff', _('Sniff protocol'));
ss = o.subsection;
ss.anonymous = true;
ss.addremove = false;
ss.rowcolors = true;
ss.sortable = true;
ss.nodescriptions = true;
so = ss.option(form.Flag, 'enabled', _('Enable'));
so.default = so.enabled;
so.editable = true;
so = ss.option(form.ListValue, 'protocol', _('Protocol'));
so.value('HTTP');
so.value('TLS');
so.value('QUIC');
so.readonly = true;
so = ss.option(form.DynamicList, 'ports', _('Ports'));
so.datatype = 'list(or(port, portrange))';
so = ss.option(form.Flag, 'override_destination', _('Override destination'));
so.default = so.enabled;
so.editable = true;
/* Sniffer END */
/* Experimental START */
s.tab('experimental', _('Experimental'));
/* Experimental settings */
o = s.taboption('experimental', form.SectionValue, '_experimental', form.NamedSection, 'experimental', 'fchomo', null);
ss = o.subsection;
so = ss.option(form.Flag, 'quic_go_disable_gso', _('Disable GSO of quic-go'));
so.default = so.disabled;
so = ss.option(form.Flag, 'quic_go_disable_ecn', _('Disable ECN of quic-go'));
so.default = so.disabled;
so = ss.option(form.Flag, 'dialer_ip4p_convert', _('Enable IP4P conversion for outbound connections')
.format('https://github.com/heiher/natmap/wiki/faq#%E5%9F%9F%E5%90%8D%E8%AE%BF%E9%97%AE%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E7%9A%84'));
so.default = so.disabled;
/* Experimental END */
/* ACL START */
s.tab('control', _('Access Control'));
/* Access Control settings */
o = s.taboption('control', form.SectionValue, '_control', form.NamedSection, 'routing', 'fchomo', null);
ss = o.subsection;
/* Interface control */
ss.tab('interface', _('Interface Control'));
so = ss.taboption('interface', widgets.DeviceSelect, 'listen_interfaces', _('Listen interfaces'),
_('Only process traffic from specific interfaces. Leave empty for all.'));
so.multiple = true;
so.noaliases = true;
so = ss.taboption('interface', widgets.DeviceSelect, 'bind_interface', _('Bind interface'),
_('Bind outbound traffic to specific interface. Leave empty to auto detect.') +
_('Priority: Proxy Node > Proxy Group > Global.'));
so.multiple = false;
so.noaliases = true;
so = ss.taboption('interface', form.Value, 'route_table_id', _('Routing table ID'));
so.ucisection = 'config';
so.datatype = 'uinteger';
so.placeholder = '2022';
so.rmempty = false;
so = ss.taboption('interface', form.Value, 'route_rule_pref', _('Routing rule priority'));
so.ucisection = 'config';
so.datatype = 'uinteger';
so.placeholder = '9000';
so.rmempty = false;
so = ss.taboption('interface', form.Value, 'self_mark', _('Routing mark'),
_('Priority: Proxy Node > Proxy Group > Global.'));
so.ucisection = 'config';
so.datatype = 'uinteger';
so.placeholder = '200';
so.rmempty = false;
so = ss.taboption('interface', form.Value, 'tproxy_mark', _('Tproxy Fwmark'));
so.ucisection = 'config';
so.placeholder = '201 or 0xc9/0xff';
so.rmempty = false;
so = ss.taboption('interface', form.Value, 'tun_mark', _('Tun Fwmark'));
so.ucisection = 'config';
so.placeholder = '202 or 0xca/0xff';
so.rmempty = false;
/* Access control */
ss.tab('access_control', _('Access Control'));
so = ss.taboption('access_control', form.ListValue, 'lan_filter', _('Users filter mode'));
so.value('', _('All allowed'));
so.value('white_list', _('White list'));
so.value('black_list', _('Black list'));
so = fwtool.addIPOption(ss, 'access_control', 'lan_direct_ipv4_ips', _('Direct IPv4 IP-s'), null, 'ipv4', hosts, true);
so.depends('lan_filter', 'black_list');
so = fwtool.addIPOption(ss, 'access_control', 'lan_direct_ipv6_ips', _('Direct IPv6 IP-s'), null, 'ipv6', hosts, true);
so.depends({'lan_filter': 'black_list', 'fchomo.global.ipv6': '1'});
so = fwtool.addMACOption(ss, 'access_control', 'lan_direct_mac_addrs', _('Direct MAC-s'), null, hosts);
so.depends('lan_filter', 'black_list');
so = fwtool.addIPOption(ss, 'access_control', 'lan_proxy_ipv4_ips', _('Proxy IPv4 IP-s'), null, 'ipv4', hosts, true);
so.depends('lan_filter', 'white_list');
so = fwtool.addIPOption(ss, 'access_control', 'lan_proxy_ipv6_ips', _('Proxy IPv6 IP-s'), null, 'ipv6', hosts, true);
so.depends({'lan_filter': 'white_list', 'fchomo.global.ipv6': '1'});
so = fwtool.addMACOption(ss, 'access_control', 'lan_proxy_mac_addrs', _('Proxy MAC-s'), null, hosts);
so.depends('lan_filter', 'white_list');
so = ss.taboption('access_control', form.Flag, 'proxy_router', _('Proxy routerself'));
so.default = so.enabled;
/* Routing control */
ss.tab('routing_control', _('Routing Control'));
so = ss.taboption('routing_control', hm.RichMultiValue, 'routing_tcpport', _('Routing ports') + ' (TCP)',
_('Specify target ports to be proxied. Multiple ports must be separated by commas.'));
so.create = true;
hm.routing_port_type.forEach((res) => {
if (res[0] !== 'common_udpport')
so.value.apply(so, res);
})
so.validate = L.bind(hm.validateCommonPort, so);
so = ss.taboption('routing_control', hm.RichMultiValue, 'routing_udpport', _('Routing ports') + ' (UDP)',
_('Specify target ports to be proxied. Multiple ports must be separated by commas.'));
so.create = true;
hm.routing_port_type.forEach((res) => {
if (res[0] !== 'common_tcpport')
so.value.apply(so, res);
})
so.validate = L.bind(hm.validateCommonPort, so);
so = ss.taboption('routing_control', form.ListValue, 'routing_mode', _('Routing mode'),
_('Routing mode of the traffic enters mihomo via firewall rules.'));
so.value('', _('All allowed'));
so.value('bypass_cn', _('Bypass CN'));
so.value('routing_gfw', _('Routing GFW'));
so = ss.taboption('routing_control', form.Flag, 'routing_domain', _('Handle domain'),
_('Routing mode will be handle domain.'));
so.default = so.disabled;
if (!features.hm_has_dnsmasq_full) {
so.description = _('To enable, you need to install dnsmasq-full
.');
so.readonly = true;
uci.set(data[0], so.section.section, so.option, '');
uci.save();
}
so.depends('routing_mode', 'bypass_cn');
so.depends('routing_mode', 'routing_gfw');
so = ss.taboption('routing_control', form.ListValue, 'routing_dscp_mode', _('Routing DSCP'));
so.value('', _('All allowed'));
so.value('bypass_dscp', _('Bypass DSCP'));
so.value('routing_dscp', _('Routing DSCP'));
so = ss.taboption('routing_control', form.Value, 'routing_dscp_list', _('DSCP list'));
so.placeholder = '0,10,12,14,63';
so.validate = function(section_id, value) {
if (!value)
return true;
else if (value.match('^(6[0-3]|[1-5]?[0-9])(,(6[0-3]|[1-5]?[0-9]))*$') === null)
return _('Expecting: %s').format(_('One or more numbers in the range 0-63 separated by commas'));
return true;
}
so.rmempty = false;
so.depends('routing_dscp_mode', 'bypass_dscp');
so.depends('routing_dscp_mode', 'routing_dscp');
/* Custom Direct list */
ss.tab('direct_list', _('Custom Direct List'));
so = ss.taboption('direct_list', hm.TextValue, 'direct_list.yaml', null);
so.rows = 20;
so.default = 'FQDN:\nIPCIDR:\nIPCIDR6:\n';
so.placeholder = "FQDN:\n- mask.icloud.com\n- mask-h2.icloud.com\n- mask.apple-dns.net\nIPCIDR:\n- '223.0.0.0/12'\nIPCIDR6:\n- '2400:3200::/32'\n";
so.load = function(section_id) {
return L.resolveDefault(hm.readFile('resources', this.option), '');
}
so.write = function(section_id, formvalue) {
return hm.writeFile('resources', this.option, formvalue);
}
so.remove = function(section_id) {
return hm.writeFile('resources', this.option);
}
so.rmempty = false;
/* Custom Proxy list */
ss.tab('proxy_list', _('Custom Proxy List'));
so = ss.taboption('proxy_list', hm.TextValue, 'proxy_list.yaml', null);
so.rows = 20;
so.default = 'FQDN:\nIPCIDR:\nIPCIDR6:\n';
so.placeholder = "FQDN:\n- www.google.com\nIPCIDR:\n- '91.105.192.0/23'\nIPCIDR6:\n- '2001:67c:4e8::/48'\n";
so.load = function(section_id) {
return L.resolveDefault(hm.readFile('resources', this.option), '');
}
so.write = function(section_id, formvalue) {
return hm.writeFile('resources', this.option, formvalue);
}
so.remove = function(section_id) {
return hm.writeFile('resources', this.option);
}
so.rmempty = false;
/* ACL END */
return m.render();
}
});