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

1054 lines
35 KiB
JavaScript
Raw Normal View History

2024-10-26 10:27:23 +08:00
'use strict';
'require form';
'require poll';
'require uci';
'require view';
'require fchomo as hm';
'require tools.widgets as widgets';
function loadDNSServerLabel(section_id) {
delete this.keylist;
delete this.vallist;
this.value('default-dns', _('Default DNS (issued by WAN)'));
this.value('system-dns', _('System DNS'));
this.value('block-dns', _('Block DNS queries'));
uci.sections(this.config, 'dns_server', (res) => {
if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
return this.super('load', section_id);
}
function validateNameserver(section_id, value) {
const arr = value.trim().split(' ');
if (arr.length > 1 && arr.includes('block-dns'))
return _('Expecting: %s').format(_('If Block is selected, uncheck others'));
return true;
}
class DNSAddress {
constructor(address) {
this.input = address || '';
[this.addr, this.rawparams] = this.input.split('#');
if (this.rawparams) {
if (this.rawparams.match(/^[^=&]+(&|$)/))
this.rawparams = 'detour=' + this.rawparams
} else
this.rawparams = '';
this.params = new URLSearchParams(this.rawparams);
}
parseParam(param) {
return this.params.has(param) ? decodeURI(this.params.get(param)) : null;
}
setParam(param, value) {
if (value) {
this.params.set(param, value);
} else
this.params.delete(param);
return this
}
toString() {
return this.addr + (this.params.size === 0 ? '' : '#' +
['detour', 'h3', 'ecs', 'ecs-override'].map((k) => {
return this.params.has(k) ? '%s=%s'.format(k, encodeURI(this.params.get(k))) : null;
}).filter(v => v).join('&')
);
}
}
class RulesEntry {
constructor(entry) {
this.input = entry || '';
2024-11-06 00:26:14 +08:00
var content = this.input;
this.subrule = content.split(',');
if (this.subrule.shift() === 'SUB-RULE') {
var subrule_payload = this.subrule.join(',').match(/^\(.*\)/);
if (subrule_payload) {
content = subrule_payload[0].slice(1, -1);
this.subrule = this.subrule.pop() || ' ';
} else {
content = this.subrule.join(',');
this.subrule = ' ';
}
} else
this.subrule = false;
this.rawparams = content.split(',');
2024-10-26 10:27:23 +08:00
this.type = this.rawparams.shift() || '';
var logical_payload, rawfactor;
(function(rawparams_typecuted) {
logical_payload = rawparams_typecuted.match(/^\(.*\)/);
if (logical_payload) {
rawfactor = logical_payload[0];
this.rawparams = rawparams_typecuted.replace(/^\(.*\),?/, '').split(',');
} else
rawfactor = this.rawparams.shift() || '';
}.call(this, this.rawparams.join(',')));
this.detour = this.rawparams.shift() || '';
2024-11-06 00:26:14 +08:00
if (this.type === 'MATCH')
this.detour = rawfactor;
2024-10-26 10:27:23 +08:00
this.payload = [];
if (logical_payload) { // ႇ ❟
if (rawfactor.match(/^\(.*\)$/)) // LOGICAL_TPYE,()
rawfactor.slice(1, -1).split('').forEach((payload) => { // U+A4F9
if (payload.match(/^\(.*\)$/)) { // (payload)
let arr = payload.slice(1, -1).split(''); // U+201A
this.payload.push({ type: arr[0] || '', factor: arr[1] || '' });
}
});
} else
this.payload[0] = { type: this.type, factor: rawfactor };
this.params = {};
if (this.rawparams.length > 0) {
this.rawparams.forEach((k) => {
this.params[k] = 'true';
});
}
this.rawparams = this.rawparams.join(',');
}
setKey(key, value) {
this[key] = value;
return this
}
getPayload(n) {
return this.payload[n] || {};
}
setPayload(n, obj) {
this.payload[n] ||= {};
Object.keys(obj).forEach((key) => {
this.payload[n][key] = obj[key] || null;
});
return this
}
getParam(param) {
return this.params[param] || null;
}
setParam(param, value) {
if (value) {
this.params[param] = value;
} else
this.params[param] = null;
return this
}
toString() {
var logical = hm.rules_logical_type.map(e => e[0] || e).includes(this.type),
factor = '';
if (logical) {
let n = hm.rules_logical_payload_count[this.type] || 0;
factor = '(%s)'.format(this.payload.slice(0, n).map((payload) => {
return '(%s%s)'.format(payload.type || '', payload.factor || '');
}).join(''));
} else
factor = this.payload[0].factor;
2024-11-06 00:26:14 +08:00
if (this.subrule) {
return 'SUB-RULE,(%s),%s'.format([this.type, factor].join(','), this.subrule);
} else
if (this.type === 'MATCH') {
return [this.type, this.detour].join(',');
} else
return [this.type, factor, this.detour].concat(
['no-resolve', 'src'].filter(k => this.params[k])
).join(',');
2024-10-26 10:27:23 +08:00
}
}
function strToFlag(string) {
if (!string)
return null;
switch(string) {
case 'true':
return '1';
case 'false':
return '0';
default:
return null;
}
}
function flagToStr(flag) {
if (!flag)
return null;
switch(flag) {
case '1':
return 'true';
default:
return null;
}
}
function renderPayload(s, total, uciconfig) {
// common payload
var initPayload = function(o, n, key, uciconfig) {
o.load = L.bind(function(n, key, uciconfig, section_id) {
return new RulesEntry(uci.get(uciconfig, section_id, 'entry')).getPayload(n)[key];
}, o, n, key, uciconfig);
o.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'entry');
let n = this.option.match(/^payload(\d+)_/)[1];
var newvalue = new RulesEntry(UIEl.getValue()).setPayload(n, {factor: value}).toString();
UIEl.node.previousSibling.innerText = newvalue;
return UIEl.setValue(newvalue);
}
o.write = function() {};
o.rmempty = false;
o.modalonly = true;
}
var o, prefix;
for (var n=0; n<total; n++) {
prefix = `payload${n}_`;
o = s.option(form.ListValue, prefix + 'type', _('Type') + ` ${n+1}`);
o.default = hm.rules_type[0][0];
hm.rules_type.forEach((res) => {
o.value.apply(o, res);
})
Object.keys(hm.rules_logical_payload_count).forEach((key) => {
if (n < hm.rules_logical_payload_count[key])
o.depends('type', key);
})
initPayload(o, n, 'type', uciconfig);
o.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'entry');
let n = this.option.match(/^payload(\d+)_/)[1];
var newvalue = new RulesEntry(UIEl.getValue()).setPayload(n, {type: value}).toString();
UIEl.node.previousSibling.innerText = newvalue;
return UIEl.setValue(newvalue);
}
o = s.option(form.Value, prefix + 'general', _('Factor') + ` ${n+1}`);
if (n === 0) {
o.depends({type: /\bDOMAIN\b/});
o.depends({type: /\bGEO(SITE|IP)\b/});
2024-11-06 00:26:14 +08:00
o.depends({type: /\bASN\b/});
2024-10-26 10:27:23 +08:00
o.depends({type: /\bPROCESS\b/});
}
o.depends(Object.fromEntries([[prefix + 'type', /\bDOMAIN\b/]]));
o.depends(Object.fromEntries([[prefix + 'type', /\bGEO(SITE|IP)\b/]]));
2024-11-06 00:26:14 +08:00
o.depends(Object.fromEntries([[prefix + 'type', /\bASN\b/]]));
2024-10-26 10:27:23 +08:00
o.depends(Object.fromEntries([[prefix + 'type', /\bPROCESS\b/]]));
initPayload(o, n, 'factor', uciconfig);
o = s.option(form.Value, prefix + 'ip', _('Factor') + ` ${n+1}`);
o.datatype = 'cidr';
2024-11-06 00:26:14 +08:00
if (n === 0) {
o.depends({type: /\b(CIDR|CIDR6)\b/});
o.depends({type: /\bIP-SUFFIX\b/});
}
o.depends(Object.fromEntries([[prefix + 'type', /\b(CIDR|CIDR6)\b/]]));
o.depends(Object.fromEntries([[prefix + 'type', /\bIP-SUFFIX\b/]]));
2024-10-26 10:27:23 +08:00
initPayload(o, n, 'factor', uciconfig);
o = s.option(form.Value, prefix + 'port', _('Factor') + ` ${n+1}`);
o.datatype = 'or(port, portrange)';
if (n === 0)
o.depends({type: /\bPORT\b/});
o.depends(Object.fromEntries([[prefix + 'type', /\bPORT\b/]]));
initPayload(o, n, 'factor', uciconfig);
o = s.option(form.ListValue, prefix + 'l4', _('Factor') + ` ${n+1}`);
o.value('udp', _('UDP'));
o.value('tcp', _('TCP'));
if (n === 0)
o.depends('type', 'NETWORK');
o.depends(prefix + 'type', 'NETWORK');
initPayload(o, n, 'factor', uciconfig);
2024-12-02 11:01:20 +08:00
o = s.option(form.Value, prefix + 'dscp', _('Factor') + ` ${n+1}`);
o.datatype = 'range(0, 63)';
if (n === 0)
o.depends('type', 'DSCP');
o.depends(prefix + 'type', 'DSCP');
initPayload(o, n, 'factor', uciconfig);
2024-10-26 10:27:23 +08:00
o = s.option(form.ListValue, prefix + 'rule_set', _('Factor') + ` ${n+1}`);
o.value('', _('-- Please choose --'));
if (n === 0)
o.depends('type', 'RULE-SET');
o.depends(prefix + 'type', 'RULE-SET');
initPayload(o, n, 'factor', uciconfig);
o.load = L.bind(function(n, key, uciconfig, section_id) {
hm.loadRulesetLabel.call(this, null, section_id);
return new RulesEntry(uci.get(uciconfig, section_id, 'entry')).getPayload(n)[key];
}, o, n, 'factor', uciconfig)
}
}
2024-11-06 00:26:14 +08:00
function renderRules(s, uciconfig) {
var o;
o = s.option(form.DummyValue, 'entry', _('Entry'));
o.load = function(section_id) {
return form.DummyValue.prototype.load.call(this, section_id) || '%s,%s,%s'.format(hm.rules_type[0][0], '', hm.preset_outbound.full[0][0]);
}
o.write = L.bind(form.AbstractValue.prototype.write, o);
o.remove = L.bind(form.AbstractValue.prototype.remove, o);
o.editable = true;
o = s.option(form.ListValue, 'type', _('Type'));
o.default = hm.rules_type[0][0];
[...hm.rules_type, ...hm.rules_logical_type].forEach((res) => {
o.value.apply(o, res);
})
o.load = function(section_id) {
return new RulesEntry(uci.get(uciconfig, section_id, 'entry')).type;
}
o.validate = function(section_id, value) {
// params only available for types other than
// https://github.com/muink/mihomo/blob/43f21c0b412b7a8701fe7a2ea6510c5b985a53d6/config/config.go#L1050
// https://github.com/muink/mihomo/blob/43f21c0b412b7a8701fe7a2ea6510c5b985a53d6/rules/parser.go#L12
if (['GEOIP', 'IP-ASN', 'IP-CIDR', 'IP-CIDR6', 'IP-SUFFIX', 'RULE-SET'].includes(value)) {
['no-resolve', 'src'].forEach((opt) => {
let UIEl = this.section.getUIElement(section_id, opt);
UIEl.node.querySelector('input').disabled = null;
});
} else {
['no-resolve', 'src'].forEach((opt) => {
let UIEl = this.section.getUIElement(section_id, opt);
UIEl.setValue('');
UIEl.node.querySelector('input').disabled = 'true';
});
var UIEl = this.section.getUIElement(section_id, 'entry');
var newvalue = new RulesEntry(UIEl.getValue()).setParam('no-resolve').setParam('src').toString();
UIEl.node.previousSibling.innerText = newvalue;
UIEl.setValue(newvalue);
}
return true;
}
o.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'entry');
var newvalue = new RulesEntry(UIEl.getValue()).setKey('type', value).toString();
UIEl.node.previousSibling.innerText = newvalue;
return UIEl.setValue(newvalue);
}
o.write = function() {};
o.rmempty = false;
o.modalonly = true;
renderPayload(s, Math.max(...Object.values(hm.rules_logical_payload_count)), uciconfig);
o = s.option(form.ListValue, 'detour', _('Proxy group'));
o.load = function(section_id) {
hm.loadProxyGroupLabel.call(this, hm.preset_outbound.full, section_id);
return new RulesEntry(uci.get(uciconfig, section_id, 'entry')).detour;
}
o.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'entry');
var newvalue = new RulesEntry(UIEl.getValue()).setKey('detour', value).toString();
UIEl.node.previousSibling.innerText = newvalue;
return UIEl.setValue(newvalue);
}
o.write = function() {};
//o.depends('SUB-RULE', '0');
o.editable = true;
o = s.option(form.Flag, 'src', _('src'));
o.default = o.disabled;
o.load = function(section_id) {
return strToFlag(new RulesEntry(uci.get(uciconfig, section_id, 'entry')).getParam('src'));
}
o.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'entry');
var newvalue = new RulesEntry(UIEl.getValue()).setParam('src', flagToStr(value)).toString();
UIEl.node.previousSibling.innerText = newvalue;
UIEl.setValue(newvalue);
}
o.write = function() {};
o.depends('SUB-RULE', '0');
o.modalonly = true;
o = s.option(form.Flag, 'no-resolve', _('no-resolve'));
o.default = o.disabled;
o.load = function(section_id) {
return strToFlag(new RulesEntry(uci.get(uciconfig, section_id, 'entry')).getParam('no-resolve'));
}
o.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'entry');
var newvalue = new RulesEntry(UIEl.getValue()).setParam('no-resolve', flagToStr(value)).toString();
UIEl.node.previousSibling.innerText = newvalue;
UIEl.setValue(newvalue);
}
o.write = function() {};
o.depends('SUB-RULE', '0');
o.modalonly = true;
}
2024-10-26 10:27:23 +08:00
return view.extend({
load: function() {
return Promise.all([
uci.load('fchomo')
]);
},
render: function(data) {
var dashboard_repo = uci.get(data[0], 'api', 'dashboard_repo');
2024-12-10 16:28:20 +08:00
let m, s, o, ss, so;
2024-10-26 10:27:23 +08:00
m = new form.Map('fchomo', _('Mihomo client'));
s = m.section(form.TypedSection);
s.render = function () {
poll.add(function () {
return hm.getServiceStatus('mihomo-c').then((isRunning) => {
hm.updateStatus(hm, document.getElementById('_client_bar'), isRunning ? { dashboard_repo: dashboard_repo } : false, 'mihomo-c', true);
});
});
return E('div', { class: 'cbi-section' }, [
E('p', [
hm.renderStatus(hm, '_client_bar', false, 'mihomo-c', true)
])
]);
}
2024-11-03 20:36:08 +08:00
s = m.section(form.NamedSection, 'routing', 'fchomo', null);
2024-10-26 10:27:23 +08:00
/* Proxy Group START */
s.tab('group', _('Proxy Group'));
/* Client switch */
o = s.taboption('group', form.Button, '_reload_client', _('Quick Reload'));
o.inputtitle = _('Reload');
o.inputstyle = 'apply';
o.onclick = L.bind(hm.handleReload, o, 'mihomo-c');
2024-11-06 00:26:14 +08:00
o = s.taboption('group', form.Flag, 'client_enabled', _('Enable'));
o.default = o.disabled;
2024-10-26 10:27:23 +08:00
/* Proxy Group */
o = s.taboption('group', form.SectionValue, '_group', form.GridSection, 'proxy_group', null);
ss = o.subsection;
var prefmt = { 'prefix': 'group_', 'suffix': '' };
ss.addremove = true;
ss.rowcolors = true;
ss.sortable = true;
ss.nodescriptions = true;
ss.modaltitle = L.bind(hm.loadModalTitle, ss, _('Proxy Group'), _('Add a proxy group'));
ss.sectiontitle = L.bind(hm.loadDefaultLabel, ss);
ss.renderSectionAdd = L.bind(hm.renderSectionAdd, ss, prefmt, true);
ss.handleAdd = L.bind(hm.handleAdd, ss, prefmt);
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 = function(section_id, value) {
if (value.match(/[,]/))
return _('Expecting: %s').format(_('not included ","'));
return hm.validateUniqueValue.call(this, section_id, value);
}
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.proxy_group_type[0][0];
hm.proxy_group_type.forEach((res) => {
so.value.apply(so, res);
})
so = ss.taboption('field_general', form.MultiValue, 'groups', _('Group'));
hm.preset_outbound.full.forEach((res) => {
so.value.apply(so, res);
})
so.load = L.bind(hm.loadProxyGroupLabel, so, hm.preset_outbound.full);
so.editable = true;
so = ss.taboption('field_general', form.MultiValue, 'proxies', _('Node'));
so.value('', _('-- Please choose --'));
so.load = L.bind(hm.loadNodeLabel, so);
so.validate = function(section_id, value) {
if (this.section.getOption('include_all').formvalue(section_id) === '1' ||
this.section.getOption('include_all_proxies').formvalue(section_id) === '1')
this.getUIElement(section_id, this.option).node.setAttribute('disabled', '');
else
this.getUIElement(section_id, this.option).node.removeAttribute('disabled');
return true;
}
so.editable = true;
so = ss.taboption('field_general', form.MultiValue, 'use', _('Provider'));
so.value('', _('-- Please choose --'));
so.load = L.bind(hm.loadProviderLabel, so);
so.validate = function(section_id, value) {
if (this.section.getOption('include_all').formvalue(section_id) === '1' ||
this.section.getOption('include_all_providers').formvalue(section_id) === '1')
this.getUIElement(section_id, this.option).node.setAttribute('disabled', '');
else
this.getUIElement(section_id, this.option).node.removeAttribute('disabled');
return true;
}
so.editable = true;
so = ss.taboption('field_general', form.Flag, 'include_all', _('Include all'),
_('Includes all Proxy Node and Provider.'));
so.default = so.disabled;
so.editable = true;
so = ss.taboption('field_general', form.Flag, 'include_all_proxies', _('Include all node'),
_('Includes all Proxy Node.'));
so.default = so.disabled;
so.editable = true;
so = ss.taboption('field_general', form.Flag, 'include_all_providers', _('Include all provider'),
_('Includes all Provider.'));
so.default = so.disabled;
so.editable = true;
/* Override fields */
so = ss.taboption('field_override', form.Flag, 'disable_udp', _('Disable UDP'));
so.default = so.disabled;
so.modalonly = true;
so = ss.taboption('field_override', widgets.DeviceSelect, 'interface_name', _('Bind interface'),
_('Bind outbound interface.</br>') +
_('Priority: Proxy Node > Proxy Group > Global.'));
so.multiple = false;
so.noaliases = true;
so.modalonly = true;
so = ss.taboption('field_override', form.Value, 'routing_mark', _('Routing mark'),
_('Priority: Proxy Node > Proxy Group > Global.'));
so.datatype = 'uinteger';
so.modalonly = true;
/* Health fields */
/* Url-test/Fallback/Load-balance */
so = ss.taboption('field_health', form.Value, '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.depends({type: 'select', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Value, '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: 'select', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Value, '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: 'select', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Flag, 'lazy', _('Lazy'),
_('No testing is performed when this provider node is not in use.'));
so.default = so.enabled;
so.depends({type: 'select', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Value, '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: 'select', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_health', form.Value, 'max_failed_times', _('Max count of failures'),
_('Exceeding this triggers a forced health check. <code>5</code> will be used if empty.'));
so.datatype = 'uinteger';
so.placeholder = '5';
so.depends({type: 'select', '!reverse': true});
so.modalonly = true;
/* Url-test fields */
so = ss.taboption('field_general', form.Value, 'tolerance', _('Node switch tolerance'),
_('In millisecond. <code>%s</code> will be used if empty.').format('150'));
so.datatype = 'uinteger';
so.placeholder = '150';
so.depends('type', 'url-test');
so.modalonly = true;
/* Load-balance fields */
so = ss.taboption('field_general', form.ListValue, 'strategy', _('Strategy'),
_('For details, see <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/proxy-groups/load-balance/#strategy', _('Strategy')));
so.default = hm.load_balance_strategy[0][0];
hm.load_balance_strategy.forEach((res) => {
so.value.apply(so, res);
})
so.depends('type', 'load-balance');
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.modalonly = true;
so = ss.taboption('field_general', form.DynamicList, 'exclude_filter', _('Node exclude filter'),
_('Exclude nodes that meet keywords or regexps.'));
so.placeholder = 'xxx';
so.modalonly = true;
so = ss.taboption('field_general', form.DynamicList, 'exclude_type', _('Node exclude type'),
2024-11-27 20:41:04 +08:00
_('Exclude matched node types. Available types see <a target="_blank" href="%s" rel="noreferrer noopener">here</a>.')
.format('https://wiki.metacubex.one/config/proxy-groups/#exclude-type'));
so.placeholder = 'Shadowsocks|Trojan';
2024-10-26 10:27:23 +08:00
so.modalonly = true;
/* Proxy Group END */
/* Routing rules START */
s.tab('rules', _('Routing rule'));
/* Routing rules */
o = s.taboption('rules', form.SectionValue, '_rules', form.GridSection, 'rules', null);
ss = o.subsection;
var prefmt = { 'prefix': '', 'suffix': '_host' };
ss.addremove = true;
ss.rowcolors = true;
ss.sortable = true;
ss.nodescriptions = true;
ss.modaltitle = L.bind(hm.loadModalTitle, ss, _('Routing rule'), _('Add a routing rule'));
ss.sectiontitle = L.bind(hm.loadDefaultLabel, ss);
ss.renderSectionAdd = L.bind(hm.renderSectionAdd, ss, prefmt, false);
ss.handleAdd = L.bind(hm.handleAdd, ss, prefmt);
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;
2024-11-06 00:26:14 +08:00
renderRules(ss, data[0]);
2024-10-26 10:27:23 +08:00
2024-11-06 00:26:14 +08:00
so = ss.option(form.Flag, 'SUB-RULE', _('SUB-RULE'));
so.default = so.disabled;
2024-10-26 10:27:23 +08:00
so.load = function(section_id) {
2024-11-06 00:26:14 +08:00
return strToFlag(new RulesEntry(uci.get(data[0], section_id, 'entry')).subrule ? 'true' : 'false');
2024-10-26 10:27:23 +08:00
}
so.validate = function(section_id, value) {
2024-11-06 00:26:14 +08:00
value = this.formvalue(section_id);
2024-10-26 10:27:23 +08:00
2024-11-06 00:26:14 +08:00
this.section.getUIElement(section_id, 'detour').node.querySelector('select').disabled = (value === '1') ? 'true' : null;
2024-10-26 10:27:23 +08:00
return true;
}
so.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'entry');
2024-11-06 00:26:14 +08:00
var newvalue = new RulesEntry(UIEl.getValue()).setKey('subrule', value === '1' ? ' ' : false).toString();
2024-10-26 10:27:23 +08:00
UIEl.node.previousSibling.innerText = newvalue;
return UIEl.setValue(newvalue);
}
so.write = function() {};
so.modalonly = true;
2024-11-06 00:26:14 +08:00
so = ss.option(form.ListValue, 'sub_rule', _('Sub rule'));
2024-10-26 10:27:23 +08:00
so.load = function(section_id) {
2024-11-06 00:26:14 +08:00
hm.loadSubRuleGroup.call(this, section_id);
2024-10-26 10:27:23 +08:00
2024-11-06 00:26:14 +08:00
return new RulesEntry(uci.get(data[0], section_id, 'entry')).subrule || '';
2024-10-26 10:27:23 +08:00
}
so.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'entry');
2024-11-06 00:26:14 +08:00
var newvalue = new RulesEntry(UIEl.getValue()).setKey('subrule', value).toString();
2024-10-26 10:27:23 +08:00
UIEl.node.previousSibling.innerText = newvalue;
return UIEl.setValue(newvalue);
}
2024-11-06 00:26:14 +08:00
so.rmempty = false;
2024-10-26 10:27:23 +08:00
so.write = function() {};
2024-11-06 00:26:14 +08:00
so.depends('SUB-RULE', '1');
so.modalonly = true;
/* Routing rules END */
2024-10-26 10:27:23 +08:00
2024-11-06 00:26:14 +08:00
/* Sub rules START */
s.tab('subrules', _('Sub rule'));
2024-10-26 10:27:23 +08:00
2024-11-06 00:26:14 +08:00
/* Sub rules */
o = s.taboption('subrules', form.SectionValue, '_subrules', form.GridSection, 'subrules', null);
ss = o.subsection;
var prefmt = { 'prefix': '', 'suffix': '_subhost' };
ss.addremove = true;
ss.rowcolors = true;
ss.sortable = true;
ss.nodescriptions = true;
ss.modaltitle = L.bind(hm.loadModalTitle, ss, _('Sub rule'), _('Add a sub rule'));
ss.sectiontitle = L.bind(hm.loadDefaultLabel, ss);
ss.renderSectionAdd = L.bind(hm.renderSectionAdd, ss, prefmt, false);
ss.handleAdd = L.bind(hm.handleAdd, ss, prefmt);
2024-10-26 10:27:23 +08:00
2024-11-06 00:26:14 +08:00
so = ss.option(form.Value, 'label', _('Label'));
so.load = L.bind(hm.loadDefaultLabel, so);
so.validate = L.bind(hm.validateUniqueValue, so);
2024-10-26 10:27:23 +08:00
so.modalonly = true;
2024-11-06 00:26:14 +08:00
so = ss.option(form.Flag, 'enabled', _('Enable'));
so.default = so.enabled;
so.editable = true;
2024-10-26 10:27:23 +08:00
2024-11-06 00:26:14 +08:00
so = ss.option(form.Value, 'group', _('Sub rule group'));
so.value('sub-rule1');
so.rmempty = false;
so.validate = L.bind(hm.validateAuthUsername, so);
so.editable = true;
2024-10-26 10:27:23 +08:00
2024-11-06 00:26:14 +08:00
renderRules(ss, data[0]);
/* Sub rules END */
2024-10-26 10:27:23 +08:00
/* DNS settings START */
s.tab('dns', _('DNS settings'));
/* DNS settings */
o = s.taboption('dns', form.SectionValue, '_dns', form.NamedSection, 'dns', 'fchomo', null);
ss = o.subsection;
so = ss.option(form.Value, 'port', _('Listen port'));
so.datatype = 'port'
so.placeholder = '7853';
so.rmempty = false;
so = ss.option(form.Flag, 'ipv6', _('IPv6 support'));
so.default = so.enabled;
so = ss.option(form.MultiValue, 'boot_server', _('Boot DNS server'),
_('Used to resolve the domain of the DNS server. Must be IP.'));
so.default = 'default-dns';
so.load = L.bind(loadDNSServerLabel, so);
so.validate = L.bind(validateNameserver, so);
so.rmempty = false;
so = ss.option(form.MultiValue, 'bootnode_server', _('Boot DNS server (Node)'),
_('Used to resolve the domain of the Proxy node.'));
so.default = 'default-dns';
so.load = L.bind(loadDNSServerLabel, so);
so.validate = L.bind(validateNameserver, so);
so.rmempty = false;
so = ss.option(form.MultiValue, 'default_server', _('Default DNS server'));
so.description = uci.get(data[0], so.section.section, 'fallback_server') ? _('Final DNS server (Used to Domestic-IP response)') : _('Final DNS server');
so.default = 'default-dns';
so.load = L.bind(loadDNSServerLabel, so);
so.validate = L.bind(validateNameserver, so);
so.rmempty = false;
so = ss.option(form.MultiValue, 'fallback_server', _('Fallback DNS server'));
so.description = uci.get(data[0], so.section.section, 'fallback_server') ? _('Final DNS server (Used to Overseas-IP response)') : _('Fallback DNS server');
so.load = L.bind(loadDNSServerLabel, so);
so.validate = L.bind(validateNameserver, so);
so.onchange = function(ev, section_id, value) {
var ddesc = this.section.getUIElement(section_id, 'default_server').node.nextSibling;
var fdesc = ev.target.nextSibling;
if (value.length > 0) {
ddesc.innerHTML = _('Final DNS server (Used to Domestic-IP response)');
fdesc.innerHTML = _('Final DNS server (Used to Overseas-IP response)');
} else {
ddesc.innerHTML = _('Final DNS server');
fdesc.innerHTML = _('Fallback DNS server');
}
}
/* DNS settings END */
/* DNS server START */
s.tab('dns_server', _('DNS server'));
/* DNS server */
o = s.taboption('dns_server', form.SectionValue, '_dns_server', form.GridSection, 'dns_server', null);
ss = o.subsection;
var prefmt = { 'prefix': 'dns_', 'suffix': '' };
ss.addremove = true;
ss.rowcolors = true;
ss.sortable = true;
ss.nodescriptions = true;
ss.modaltitle = L.bind(hm.loadModalTitle, ss, _('DNS server'), _('Add a DNS server'));
ss.sectiontitle = L.bind(hm.loadDefaultLabel, ss);
ss.renderSectionAdd = L.bind(hm.renderSectionAdd, ss, prefmt, true);
ss.handleAdd = L.bind(hm.handleAdd, ss, prefmt);
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.DummyValue, 'address', _('Address'));
so.write = L.bind(form.AbstractValue.prototype.write, so);
so.remove = L.bind(form.AbstractValue.prototype.remove, so);
so.editable = true;
so = ss.option(form.Value, 'addr', _('Address'));
so.load = function(section_id) {
return new DNSAddress(uci.get(data[0], section_id, 'address')).addr;
}
so.validate = function(section_id, value) {
if (value.match('#'))
return _('Expecting: %s').format(_('No add\'l params'));
// params only available on DoH
// https://github.com/muink/mihomo/blob/43f21c0b412b7a8701fe7a2ea6510c5b985a53d6/config/config.go#L1211C8-L1211C14
if (value.match(/^https?:\/\//)){
this.section.getUIElement(section_id, 'h3').node.querySelector('input').disabled = null;
this.section.getUIElement(section_id, 'ecs').node.querySelector('input').disabled = null;
this.section.getUIElement(section_id, 'ecs-override').node.querySelector('input').disabled = null;
} else {
var UIEl = this.section.getUIElement(section_id, 'address');
var newvalue = new DNSAddress(UIEl.getValue()).setParam('h3').setParam('ecs').setParam('ecs-override').toString();
UIEl.node.previousSibling.innerText = newvalue;
UIEl.setValue(newvalue);
['h3', 'ecs', 'ecs-override'].forEach((opt) => {
let UIEl = this.section.getUIElement(section_id, opt);
UIEl.setValue('');
UIEl.node.querySelector('input').disabled = 'true';
});
}
return true;
}
so.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'address');
var newvalue = ('N' + UIEl.getValue()).replace(/^[^#]+/, value);
UIEl.node.previousSibling.innerText = newvalue;
return UIEl.setValue(newvalue);
}
so.write = function() {};
so.rmempty = false;
so.modalonly = true;
so = ss.option(form.ListValue, 'detour', _('Proxy group'));
so.load = function(section_id) {
hm.loadProxyGroupLabel.call(this, hm.preset_outbound.dns, section_id);
return new DNSAddress(uci.get(data[0], section_id, 'address')).parseParam('detour');
}
so.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'address');
var newvalue = new DNSAddress(UIEl.getValue()).setParam('detour', value).toString();
UIEl.node.previousSibling.innerText = newvalue;
return UIEl.setValue(newvalue);
}
so.write = function() {};
so.editable = true;
so = ss.option(form.Flag, 'h3', _('HTTP/3'));
so.default = so.disabled;
so.load = function(section_id) {
return strToFlag(new DNSAddress(uci.get(data[0], section_id, 'address')).parseParam('h3'));
}
so.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'address');
var newvalue = new DNSAddress(UIEl.getValue()).setParam('h3', flagToStr(value)).toString();
UIEl.node.previousSibling.innerText = newvalue;
return UIEl.setValue(newvalue);
}
so.write = function() {};
so.modalonly = true;
so = ss.option(form.Value, 'ecs', _('EDNS Client Subnet'));
so.datatype = 'cidr';
so.load = function(section_id) {
return new DNSAddress(uci.get(data[0], section_id, 'address')).parseParam('ecs');
}
so.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'address');
var newvalue = new DNSAddress(UIEl.getValue()).setParam('ecs', value).toString();
UIEl.node.previousSibling.innerText = newvalue;
UIEl.setValue(newvalue);
}
so.write = function() {};
so.modalonly = true;
so = ss.option(form.Flag, 'ecs-override', _('ECS override'),
_('Override ECS in original request.'));
so.default = so.disabled;
so.load = function(section_id) {
return strToFlag(new DNSAddress(uci.get(data[0], section_id, 'address')).parseParam('ecs-override'));
}
so.onchange = function(ev, section_id, value) {
var UIEl = this.section.getUIElement(section_id, 'address');
var newvalue = new DNSAddress(UIEl.getValue()).setParam('ecs-override', flagToStr(value)).toString();
UIEl.node.previousSibling.innerText = newvalue;
UIEl.setValue(newvalue);
}
so.write = function() {};
so.depends({'ecs': /.+/});
so.modalonly = true;
/* DNS server END */
/* DNS policy START */
s.tab('dns_policy', _('DNS policy'));
/* DNS policy */
o = s.taboption('dns_policy', form.SectionValue, '_dns_policy', form.GridSection, 'dns_policy', null);
ss = o.subsection;
var prefmt = { 'prefix': '', 'suffix': '_domain' };
ss.addremove = true;
ss.rowcolors = true;
ss.sortable = true;
ss.nodescriptions = true;
ss.modaltitle = L.bind(hm.loadModalTitle, ss, _('DNS policy'), _('Add a DNS policy'));
ss.sectiontitle = L.bind(hm.loadDefaultLabel, ss);
ss.renderSectionAdd = L.bind(hm.renderSectionAdd, ss, prefmt, false);
ss.handleAdd = L.bind(hm.handleAdd, ss, prefmt);
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('domain', _('Domain'));
so.value('geosite', _('Geosite'));
so.value('rule_set', _('Rule set'));
so.default = 'domain';
so = ss.option(form.DynamicList, 'domain', _('Domain'),
_('Match domain. Support wildcards.'));
so.depends('type', 'domain');
so.modalonly = true;
so = ss.option(form.DynamicList, 'geosite', _('Geosite'),
_('Match geosite.'));
so.depends('type', 'geosite');
so.modalonly = true;
so = ss.option(form.MultiValue, 'rule_set', _('Rule set'),
_('Match rule set.'));
so.value('', _('-- Please choose --'));
so.load = L.bind(hm.loadRulesetLabel, so, ['domain', 'classical']);
so.depends('type', 'rule_set');
so.modalonly = true;
so = ss.option(form.DummyValue, '_entry', _('Entry'));
so.load = function(section_id) {
var option = uci.get(data[0], section_id, 'type');
return uci.get(data[0], section_id, option)?.join(',');
}
so.modalonly = false;
so = ss.option(form.MultiValue, 'server', _('DNS server'));
so.value('default-dns');
so.default = 'default-dns';
so.load = L.bind(loadDNSServerLabel, so);
so.validate = L.bind(validateNameserver, so);
so.rmempty = false;
so.editable = true;
so = ss.option(form.ListValue, 'proxy', _('Proxy group'),
_('Override the Proxy group of DNS server.'));
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.editable = true;
/* DNS policy END */
/* Fallback filter START */
s.tab('fallback_filter', _('Fallback filter'));
/* Fallback filter */
o = s.taboption('fallback_filter', form.SectionValue, '_fallback_filter', form.NamedSection, 'dns', 'fchomo', null);
o.depends({'fchomo.dns.fallback_server': /.+/});
ss = o.subsection;
so = ss.option(form.Flag, 'fallback_filter_geoip', _('Geoip enable'));
so.default = so.enabled;
so = ss.option(form.Value, 'fallback_filter_geoip_code', _('Geoip code'),
_('Match response with geoip.</br>') +
_('The matching <code>%s</code> will be deemed as not-poisoned.').format(_('IP')));
so.default = 'cn';
so.placeholder = 'cn';
so.rmempty = false;
so.retain = true;
so.depends('fallback_filter_geoip', '1');
so = ss.option(form.DynamicList, 'fallback_filter_geosite', _('Geosite'),
_('Match geosite.</br>') +
_('The matching <code>%s</code> will be deemed as poisoned.').format(_('Domain')));
so = ss.option(form.DynamicList, 'fallback_filter_ipcidr', _('IP CIDR'),
_('Match response with ipcidr.</br>') +
_('The matching <code>%s</code> will be deemed as poisoned.').format(_('IP')));
so.datatype = 'list(cidr)';
so = ss.option(form.DynamicList, 'fallback_filter_domain', _('Domain'),
_('Match domain. Support wildcards.</br>') +
_('The matching <code>%s</code> will be deemed as poisoned.').format(_('Domain')));
/* Fallback filter END */
return m.render();
}
});