2024-10-26 10:27:23 +08:00
|
|
|
'use strict';
|
|
|
|
'require baseclass';
|
|
|
|
'require form';
|
|
|
|
'require fs';
|
|
|
|
'require rpc';
|
|
|
|
'require uci';
|
|
|
|
'require ui';
|
|
|
|
'require validation';
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
/* Member */
|
2025-01-14 00:26:16 +08:00
|
|
|
const rulesetdoc = 'data:text/html;base64,' + 'cmxzdHBsYWNlaG9sZGVy';
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-14 00:26:16 +08:00
|
|
|
const sharktaikogif = function() {
|
2024-10-26 10:27:23 +08:00
|
|
|
return 'data:image/gif;base64,' +
|
|
|
|
'c2hhcmstdGFpa28uZ2lm'
|
|
|
|
}()
|
|
|
|
|
2025-01-21 16:24:29 +08:00
|
|
|
const less_24_10 = !form.RichListValue;
|
|
|
|
|
|
|
|
const pr7558_merged = false;
|
|
|
|
const pr7574_merged = false;
|
|
|
|
|
2025-01-14 00:26:16 +08:00
|
|
|
const monospacefonts = [
|
|
|
|
'"Cascadia Code"',
|
|
|
|
'"Cascadia Mono"',
|
|
|
|
'Menlo',
|
|
|
|
'Monaco',
|
|
|
|
'Consolas',
|
|
|
|
'"Liberation Mono"',
|
|
|
|
'"Courier New"',
|
|
|
|
'monospace'
|
|
|
|
];
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const dashrepos = [
|
|
|
|
['zephyruso/zashboard', _('zashboard')],
|
|
|
|
['metacubex/metacubexd', _('metacubexd')],
|
|
|
|
['metacubex/yacd-meta', _('yacd-meta')],
|
|
|
|
['metacubex/razord-meta', _('razord-meta')]
|
2025-01-16 14:15:24 +08:00
|
|
|
];
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const dashrepos_urlparams = {
|
|
|
|
'zephyruso/zashboard': '#/setup' + '?hostname=%s&port=%s&secret=%s',
|
|
|
|
'metacubex/metacubexd': '#/setup' + '?hostname=%s&port=%s&secret=%s',
|
|
|
|
'metacubex/yacd-meta': '?hostname=%s&port=%s&secret=%s',
|
|
|
|
'metacubex/razord-meta': '?host=%s&port=%s&secret=%s'
|
|
|
|
};
|
|
|
|
|
|
|
|
const checkurls = [
|
|
|
|
['https://www.baidu.com', _('Baidu')],
|
|
|
|
['https://s1.music.126.net/style/favicon.ico', _('163Music')],
|
|
|
|
['https://www.google.com/generate_204', _('Google')],
|
|
|
|
['https://github.com', _('GitHub')],
|
|
|
|
['https://www.youtube.com', _('YouTube')]
|
|
|
|
];
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const health_checkurls = [
|
|
|
|
['https://cp.cloudflare.com'],
|
|
|
|
['https://www.gstatic.com/generate_204']
|
|
|
|
];
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const inbound_type = [
|
|
|
|
['http', _('HTTP')],
|
|
|
|
['socks', _('SOCKS')],
|
|
|
|
['mixed', _('Mixed')],
|
|
|
|
['shadowsocks', _('Shadowsocks')],
|
|
|
|
['vmess', _('VMess')],
|
|
|
|
['tuic', _('TUIC')],
|
|
|
|
['hysteria2', _('Hysteria2')],
|
|
|
|
//['tunnel', _('Tunnel')]
|
|
|
|
];
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const ip_version = [
|
|
|
|
['', _('Keep default')],
|
|
|
|
['dual', _('Dual stack')],
|
|
|
|
['ipv4', _('IPv4 only')],
|
|
|
|
['ipv6', _('IPv6 only')],
|
|
|
|
['ipv4-prefer', _('Prefer IPv4')],
|
|
|
|
['ipv6-prefer', _('Prefer IPv6')]
|
|
|
|
];
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const load_balance_strategy = [
|
|
|
|
['round-robin', _('Simple round-robin all nodes')],
|
|
|
|
['consistent-hashing', _('Same dstaddr requests. Same node')],
|
|
|
|
['sticky-sessions', _('Same srcaddr and dstaddr requests. Same node')]
|
|
|
|
];
|
2024-11-03 20:36:08 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const outbound_type = [
|
|
|
|
['direct', _('DIRECT')],
|
|
|
|
['http', _('HTTP')],
|
|
|
|
['socks5', _('SOCKS5')],
|
|
|
|
['ss', _('Shadowsocks')],
|
|
|
|
//['ssr', _('ShadowsocksR')], // Deprecated
|
|
|
|
['mieru', _('Mieru')],
|
|
|
|
['snell', _('Snell')],
|
|
|
|
['vmess', _('VMess')],
|
|
|
|
['vless', _('VLESS')],
|
|
|
|
['trojan', _('Trojan')],
|
|
|
|
//['hysteria', _('Hysteria')],
|
|
|
|
['hysteria2', _('Hysteria2')],
|
|
|
|
['tuic', _('TUIC')],
|
|
|
|
['wireguard', _('WireGuard')],
|
|
|
|
['ssh', _('SSH')]
|
|
|
|
];
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const preset_outbound = {
|
|
|
|
full: [
|
|
|
|
['DIRECT'],
|
|
|
|
['REJECT'],
|
|
|
|
['REJECT-DROP'],
|
|
|
|
['PASS'],
|
|
|
|
['COMPATIBLE']
|
2024-10-26 10:27:23 +08:00
|
|
|
],
|
2025-01-17 20:36:29 +08:00
|
|
|
direct: [
|
|
|
|
['', _('null')],
|
|
|
|
['DIRECT']
|
2024-10-26 10:27:23 +08:00
|
|
|
],
|
2025-01-17 20:36:29 +08:00
|
|
|
dns: [
|
|
|
|
['', 'RULES'],
|
|
|
|
['DIRECT']
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
|
|
|
const proxy_group_type = [
|
|
|
|
['select', _('Select')],
|
|
|
|
['fallback', _('Fallback')],
|
|
|
|
['url-test', _('URL test')],
|
|
|
|
['load-balance', _('Load balance')],
|
|
|
|
//['relay', _('Relay')], // Deprecated
|
|
|
|
];
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const routing_port_type = [
|
|
|
|
['all', _('All ports')],
|
2025-01-22 16:25:57 +08:00
|
|
|
['common_tcpport', _('Common ports only (bypass P2P traffic)'), uci.get('fchomo', 'routing', 'common_tcpport') || '20-21,22,53,80,110,143,443,465,853,873,993,995,5222,8080,8443,9418'],
|
|
|
|
['common_udpport', _('Common ports only (bypass P2P traffic)'), uci.get('fchomo', 'routing', 'common_udpport') || '20-21,22,53,80,110,143,443,853,993,995,8080,8443,9418'],
|
|
|
|
['stun_port', _('STUN ports'), uci.get('fchomo', 'routing', 'stun_port') || '3478,19302'],
|
|
|
|
['turn_port', _('TURN ports'), uci.get('fchomo', 'routing', 'turn_port') || '5349'],
|
2025-01-17 20:36:29 +08:00
|
|
|
];
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const rules_type = [
|
|
|
|
['DOMAIN'],
|
|
|
|
['DOMAIN-SUFFIX'],
|
|
|
|
['DOMAIN-KEYWORD'],
|
|
|
|
['DOMAIN-REGEX'],
|
|
|
|
['GEOSITE'],
|
2025-01-16 10:39:55 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
['IP-CIDR'],
|
|
|
|
['IP-CIDR6'],
|
|
|
|
['IP-SUFFIX'],
|
|
|
|
['IP-ASN'],
|
|
|
|
['GEOIP'],
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
['SRC-GEOIP'],
|
|
|
|
['SRC-IP-ASN'],
|
|
|
|
['SRC-IP-CIDR'],
|
|
|
|
['SRC-IP-SUFFIX'],
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
['DST-PORT'],
|
|
|
|
['SRC-PORT'],
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
//['IN-PORT'],
|
|
|
|
//['IN-TYPE'],
|
|
|
|
//['IN-USER'],
|
|
|
|
//['IN-NAME'],
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
['PROCESS-PATH'],
|
|
|
|
['PROCESS-PATH-REGEX'],
|
|
|
|
['PROCESS-NAME'],
|
|
|
|
['PROCESS-NAME-REGEX'],
|
|
|
|
//['UID'],
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
['NETWORK'],
|
|
|
|
['DSCP'],
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
['RULE-SET'],
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
['MATCH']
|
|
|
|
];
|
2024-11-06 00:26:14 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const rules_logical_type = [
|
|
|
|
['AND'],
|
|
|
|
['OR'],
|
|
|
|
['NOT'],
|
|
|
|
//['SUB-RULE'],
|
|
|
|
];
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const rules_logical_payload_count = {
|
|
|
|
'AND': { low: 2, high: undefined },
|
|
|
|
'OR': { low: 2, high: undefined },
|
|
|
|
'NOT': { low: 1, high: 1 },
|
|
|
|
//'SUB-RULE': 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
const shadowsocks_cipher_methods = [
|
|
|
|
/* Stream */
|
|
|
|
['none', _('none')],
|
|
|
|
/* AEAD */
|
|
|
|
['aes-128-gcm', _('aes-128-gcm')],
|
|
|
|
['aes-192-gcm', _('aes-192-gcm')],
|
|
|
|
['aes-256-gcm', _('aes-256-gcm')],
|
|
|
|
['chacha20-ietf-poly1305', _('chacha20-ietf-poly1305')],
|
|
|
|
['xchacha20-ietf-poly1305', _('xchacha20-ietf-poly1305')],
|
|
|
|
/* AEAD 2022 */
|
|
|
|
['2022-blake3-aes-128-gcm', _('2022-blake3-aes-128-gcm')],
|
|
|
|
['2022-blake3-aes-256-gcm', _('2022-blake3-aes-256-gcm')],
|
|
|
|
['2022-blake3-chacha20-poly1305', _('2022-blake3-chacha20-poly1305')]
|
|
|
|
];
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const shadowsocks_cipher_length = {
|
|
|
|
/* AEAD */
|
|
|
|
'aes-128-gcm': 0,
|
|
|
|
'aes-192-gcm': 0,
|
|
|
|
'aes-256-gcm': 0,
|
|
|
|
'chacha20-ietf-poly1305': 0,
|
|
|
|
'xchacha20-ietf-poly1305': 0,
|
|
|
|
/* AEAD 2022 */
|
|
|
|
'2022-blake3-aes-128-gcm': 16,
|
|
|
|
'2022-blake3-aes-256-gcm': 32,
|
|
|
|
'2022-blake3-chacha20-poly1305': 32
|
|
|
|
};
|
|
|
|
|
|
|
|
const stunserver = [
|
|
|
|
['stun.fitauto.ru:3478'],
|
|
|
|
['stun.hot-chilli.net:3478'],
|
|
|
|
['stun.pure-ip.com:3478'],
|
|
|
|
['stun.voipgate.com:3478'],
|
|
|
|
['stun.voipia.net:3478'],
|
|
|
|
['stunserver2024.stunprotocol.org:3478']
|
|
|
|
];
|
2024-11-03 20:36:08 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const tls_client_fingerprints = [
|
|
|
|
['chrome'],
|
|
|
|
['firefox'],
|
|
|
|
['safari'],
|
|
|
|
['iOS'],
|
|
|
|
['android'],
|
|
|
|
['edge'],
|
|
|
|
['360'],
|
|
|
|
['qq'],
|
|
|
|
['random']
|
|
|
|
];
|
2024-12-29 00:22:00 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
/* Prototype */
|
2025-01-21 16:24:29 +08:00
|
|
|
const CBIDynamicList = form.DynamicList.extend({
|
|
|
|
__name__: 'CBI.DynamicList',
|
|
|
|
|
|
|
|
renderWidget(section_id, option_index, cfgvalue) {
|
|
|
|
const value = (cfgvalue != null) ? cfgvalue : this.default;
|
|
|
|
const choices = this.transformChoices();
|
|
|
|
const items = L.toArray(value);
|
|
|
|
|
|
|
|
const widget = new UIDynamicList(items, choices, {
|
|
|
|
id: this.cbid(section_id),
|
|
|
|
sort: this.keylist,
|
|
|
|
allowduplicates: this.allowduplicates,
|
|
|
|
optional: this.optional || this.rmempty,
|
|
|
|
datatype: this.datatype,
|
|
|
|
placeholder: this.placeholder,
|
|
|
|
validate: L.bind(this.validate, this, section_id),
|
|
|
|
disabled: (this.readonly != null) ? this.readonly : this.map.readonly
|
|
|
|
});
|
|
|
|
|
|
|
|
return widget.render();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const CBIGenValue = form.Value.extend({
|
|
|
|
__name__: 'CBI.GenValue',
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
renderWidget() {
|
|
|
|
let node = form.Value.prototype.renderWidget.apply(this, arguments);
|
2025-01-14 00:26:16 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
if (!this.password)
|
|
|
|
node.classList.add('control-group');
|
2025-01-14 00:26:16 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
(node.querySelector('.control-group') || node).appendChild(E('button', {
|
|
|
|
'class': 'cbi-button cbi-button-add',
|
|
|
|
'title': _('Generate'),
|
|
|
|
'click': ui.createHandlerFn(this, handleGenKey, this.option)
|
|
|
|
}, [ _('Generate') ]));
|
2025-01-14 00:26:16 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return node;
|
|
|
|
}
|
|
|
|
});
|
2024-12-01 20:38:03 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const CBIListValue = form.ListValue.extend({
|
|
|
|
renderWidget(/* ... */) {
|
|
|
|
let frameEl = form.ListValue.prototype.renderWidget.apply(this, arguments);
|
2025-01-14 00:26:16 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
frameEl.querySelector('select').style["min-width"] = '10em';
|
2025-01-14 00:26:16 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return frameEl;
|
|
|
|
}
|
|
|
|
});
|
2025-01-14 00:26:16 +08:00
|
|
|
|
2025-01-22 16:25:57 +08:00
|
|
|
const CBIRichMultiValue = form.MultiValue.extend({
|
|
|
|
__name__: 'CBI.RichMultiValue',
|
|
|
|
|
|
|
|
value: (form.RichListValue || form.MultiValue).prototype.value
|
|
|
|
});
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const CBIStaticList = form.DynamicList.extend({
|
|
|
|
__name__: 'CBI.StaticList',
|
2025-01-14 00:26:16 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
renderWidget(/* ... */) {
|
2025-01-21 16:24:29 +08:00
|
|
|
let El = ((less_24_10 || !pr7558_merged) ? CBIDynamicList : form.DynamicList).prototype.renderWidget.apply(this, arguments);
|
2025-01-14 00:26:16 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
El.querySelector('.add-item ul > li[data-value="-"]')?.remove();
|
2024-12-01 20:38:03 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return El;
|
|
|
|
}
|
|
|
|
});
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const CBITextValue = form.TextValue.extend({
|
|
|
|
renderWidget(/* ... */) {
|
2025-01-21 16:24:29 +08:00
|
|
|
if (pr7574_merged)
|
|
|
|
this.monospace = monospacefonts.join(',');
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
let frameEl = form.TextValue.prototype.renderWidget.apply(this, arguments);
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-21 16:24:29 +08:00
|
|
|
if (!pr7574_merged)
|
|
|
|
frameEl.querySelector('textarea').style.fontFamily = monospacefonts.join(',');
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return frameEl;
|
|
|
|
}
|
|
|
|
});
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-21 16:24:29 +08:00
|
|
|
const UIDynamicList = ui.DynamicList.extend({
|
|
|
|
addItem(dl, value, text, flash) {
|
|
|
|
if (this.options.allowduplicates) {
|
|
|
|
const new_item = E('div', { 'class': flash ? 'item flash' : 'item', 'tabindex': 0, 'draggable': !less_24_10 }, [
|
|
|
|
E('span', {}, [ text ?? value ]),
|
|
|
|
E('input', {
|
|
|
|
'type': 'hidden',
|
|
|
|
'name': this.options.name,
|
|
|
|
'value': value })]);
|
|
|
|
|
|
|
|
const ai = dl.querySelector('.add-item');
|
|
|
|
ai.parentNode.insertBefore(new_item, ai);
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.DynamicList.prototype.addItem.call(this, dl, value, text, flash);
|
|
|
|
},
|
|
|
|
|
|
|
|
handleDropdownChange(ev) {
|
|
|
|
ui.DynamicList.prototype.handleDropdownChange.call(this, ev);
|
|
|
|
|
|
|
|
if (this.options.allowduplicates) {
|
|
|
|
const sbVal = ev.detail.value;
|
|
|
|
sbVal?.element.removeAttribute('unselectable');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
/* Method */
|
|
|
|
// thanks to homeproxy
|
|
|
|
function calcStringMD5(e) {
|
|
|
|
/* Thanks to https://stackoverflow.com/a/41602636 */
|
|
|
|
function h(a, b) {
|
|
|
|
var c, d, e, f, g;
|
|
|
|
e = a & 2147483648;
|
|
|
|
f = b & 2147483648;
|
|
|
|
c = a & 1073741824;
|
|
|
|
d = b & 1073741824;
|
|
|
|
g = (a & 1073741823) + (b & 1073741823);
|
|
|
|
return c & d ? g ^ 2147483648 ^ e ^ f : c | d ? g & 1073741824 ? g ^ 3221225472 ^ e ^ f : g ^ 1073741824 ^ e ^ f : g ^ e ^ f;
|
|
|
|
}
|
|
|
|
function k(a, b, c, d, e, f, g) { a = h(a, h(h(b & c | ~b & d, e), g)); return h(a << f | a >>> 32 - f, b); }
|
|
|
|
function l(a, b, c, d, e, f, g) { a = h(a, h(h(b & d | c & ~d, e), g)); return h(a << f | a >>> 32 - f, b); }
|
|
|
|
function m(a, b, d, c, e, f, g) { a = h(a, h(h(b ^ d ^ c, e), g)); return h(a << f | a >>> 32 - f, b); }
|
|
|
|
function n(a, b, d, c, e, f, g) { a = h(a, h(h(d ^ (b | ~c), e), g)); return h(a << f | a >>> 32 - f, b); }
|
|
|
|
function p(a) {
|
|
|
|
var b = '', d = '';
|
|
|
|
for (var c = 0; 3 >= c; c++) d = a >>> 8 * c & 255, d = '0' + d.toString(16), b += d.substr(d.length - 2, 2);
|
|
|
|
return b;
|
|
|
|
}
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
var f = [], q, r, s, t, a, b, c, d;
|
|
|
|
e = function(a) {
|
|
|
|
a = a.replace(/\r\n/g, '\n');
|
|
|
|
for (var b = '', d = 0; d < a.length; d++) {
|
|
|
|
var c = a.charCodeAt(d);
|
|
|
|
128 > c ? b += String.fromCharCode(c) : (127 < c && 2048 > c ? b += String.fromCharCode(c >> 6 | 192) :
|
|
|
|
(b += String.fromCharCode(c >> 12 | 224), b += String.fromCharCode(c >> 6 & 63 | 128)),
|
|
|
|
b += String.fromCharCode(c & 63 | 128))
|
2024-10-26 10:27:23 +08:00
|
|
|
}
|
2025-01-17 20:36:29 +08:00
|
|
|
return b;
|
|
|
|
}(e);
|
|
|
|
f = function(b) {
|
|
|
|
var c = b.length, a = c + 8;
|
|
|
|
for (var d = 16 * ((a - a % 64) / 64 + 1), e = Array(d - 1), f = 0, g = 0; g < c;)
|
|
|
|
a = (g - g % 4) / 4, f = g % 4 * 8, e[a] |= b.charCodeAt(g) << f, g++;
|
|
|
|
a = (g - g % 4) / 4; e[a] |= 128 << g % 4 * 8; e[d - 2] = c << 3; e[d - 1] = c >>> 29;
|
|
|
|
return e;
|
|
|
|
}(e);
|
|
|
|
a = 1732584193;
|
|
|
|
b = 4023233417;
|
|
|
|
c = 2562383102;
|
|
|
|
d = 271733878;
|
|
|
|
|
|
|
|
for (e = 0; e < f.length; e += 16) q = a, r = b, s = c, t = d,
|
|
|
|
a = k(a, b, c, d, f[e + 0], 7, 3614090360), d = k(d, a, b, c, f[e + 1], 12, 3905402710),
|
|
|
|
c = k(c, d, a, b, f[e + 2], 17, 606105819), b = k(b, c, d, a, f[e + 3], 22, 3250441966),
|
|
|
|
a = k(a, b, c, d, f[e + 4], 7, 4118548399), d = k(d, a, b, c, f[e + 5], 12, 1200080426),
|
|
|
|
c = k(c, d, a, b, f[e + 6], 17, 2821735955), b = k(b, c, d, a, f[e + 7], 22, 4249261313),
|
|
|
|
a = k(a, b, c, d, f[e + 8], 7, 1770035416), d = k(d, a, b, c, f[e + 9], 12, 2336552879),
|
|
|
|
c = k(c, d, a, b, f[e + 10], 17, 4294925233), b = k(b, c, d, a, f[e + 11], 22, 2304563134),
|
|
|
|
a = k(a, b, c, d, f[e + 12], 7, 1804603682), d = k(d, a, b, c, f[e + 13], 12, 4254626195),
|
|
|
|
c = k(c, d, a, b, f[e + 14], 17, 2792965006), b = k(b, c, d, a, f[e + 15], 22, 1236535329),
|
|
|
|
a = l(a, b, c, d, f[e + 1], 5, 4129170786), d = l(d, a, b, c, f[e + 6], 9, 3225465664),
|
|
|
|
c = l(c, d, a, b, f[e + 11], 14, 643717713), b = l(b, c, d, a, f[e + 0], 20, 3921069994),
|
|
|
|
a = l(a, b, c, d, f[e + 5], 5, 3593408605), d = l(d, a, b, c, f[e + 10], 9, 38016083),
|
|
|
|
c = l(c, d, a, b, f[e + 15], 14, 3634488961), b = l(b, c, d, a, f[e + 4], 20, 3889429448),
|
|
|
|
a = l(a, b, c, d, f[e + 9], 5, 568446438), d = l(d, a, b, c, f[e + 14], 9, 3275163606),
|
|
|
|
c = l(c, d, a, b, f[e + 3], 14, 4107603335), b = l(b, c, d, a, f[e + 8], 20, 1163531501),
|
|
|
|
a = l(a, b, c, d, f[e + 13], 5, 2850285829), d = l(d, a, b, c, f[e + 2], 9, 4243563512),
|
|
|
|
c = l(c, d, a, b, f[e + 7], 14, 1735328473), b = l(b, c, d, a, f[e + 12], 20, 2368359562),
|
|
|
|
a = m(a, b, c, d, f[e + 5], 4, 4294588738), d = m(d, a, b, c, f[e + 8], 11, 2272392833),
|
|
|
|
c = m(c, d, a, b, f[e + 11], 16, 1839030562), b = m(b, c, d, a, f[e + 14], 23, 4259657740),
|
|
|
|
a = m(a, b, c, d, f[e + 1], 4, 2763975236), d = m(d, a, b, c, f[e + 4], 11, 1272893353),
|
|
|
|
c = m(c, d, a, b, f[e + 7], 16, 4139469664), b = m(b, c, d, a, f[e + 10], 23, 3200236656),
|
|
|
|
a = m(a, b, c, d, f[e + 13], 4, 681279174), d = m(d, a, b, c, f[e + 0], 11, 3936430074),
|
|
|
|
c = m(c, d, a, b, f[e + 3], 16, 3572445317), b = m(b, c, d, a, f[e + 6], 23, 76029189),
|
|
|
|
a = m(a, b, c, d, f[e + 9], 4, 3654602809), d = m(d, a, b, c, f[e + 12], 11, 3873151461),
|
|
|
|
c = m(c, d, a, b, f[e + 15], 16, 530742520), b = m(b, c, d, a, f[e + 2], 23, 3299628645),
|
|
|
|
a = n(a, b, c, d, f[e + 0], 6, 4096336452), d = n(d, a, b, c, f[e + 7], 10, 1126891415),
|
|
|
|
c = n(c, d, a, b, f[e + 14], 15, 2878612391), b = n(b, c, d, a, f[e + 5], 21, 4237533241),
|
|
|
|
a = n(a, b, c, d, f[e + 12], 6, 1700485571), d = n(d, a, b, c, f[e + 3], 10, 2399980690),
|
|
|
|
c = n(c, d, a, b, f[e + 10], 15, 4293915773), b = n(b, c, d, a, f[e + 1], 21, 2240044497),
|
|
|
|
a = n(a, b, c, d, f[e + 8], 6, 1873313359), d = n(d, a, b, c, f[e + 15], 10, 4264355552),
|
|
|
|
c = n(c, d, a, b, f[e + 6], 15, 2734768916), b = n(b, c, d, a, f[e + 13], 21, 1309151649),
|
|
|
|
a = n(a, b, c, d, f[e + 4], 6, 4149444226), d = n(d, a, b, c, f[e + 11], 10, 3174756917),
|
|
|
|
c = n(c, d, a, b, f[e + 2], 15, 718787259), b = n(b, c, d, a, f[e + 9], 21, 3951481745),
|
|
|
|
a = h(a, q), b = h(b, r), c = h(c, s), d = h(d, t);
|
|
|
|
return (p(a) + p(b) + p(c) + p(d)).toLowerCase();
|
|
|
|
}
|
|
|
|
|
|
|
|
// thanks to homeproxy
|
|
|
|
function decodeBase64Str(str) {
|
|
|
|
if (!str)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
/* Thanks to luci-app-ssr-plus */
|
|
|
|
str = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
|
|
let padding = (4 - (str.length % 4)) % 4;
|
|
|
|
if (padding)
|
|
|
|
str = str + Array(padding + 1).join('=');
|
|
|
|
|
|
|
|
return decodeURIComponent(Array.prototype.map.call(atob(str), (c) =>
|
|
|
|
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
|
|
|
).join(''));
|
|
|
|
}
|
|
|
|
|
|
|
|
function generateRand(type, length) {
|
|
|
|
let byteArr;
|
|
|
|
if (['base64', 'hex'].includes(type))
|
|
|
|
byteArr = crypto.getRandomValues(new Uint8Array(length));
|
|
|
|
switch (type) {
|
|
|
|
case 'base64':
|
|
|
|
/* Thanks to https://stackoverflow.com/questions/9267899 */
|
|
|
|
return btoa(String.fromCharCode.apply(null, byteArr));
|
|
|
|
case 'hex':
|
|
|
|
return Array.from(byteArr, (byte) =>
|
|
|
|
(byte & 255).toString(16).padStart(2, '0')
|
|
|
|
).join('');
|
|
|
|
case 'uuid':
|
|
|
|
/* Thanks to https://stackoverflow.com/a/2117523 */
|
|
|
|
return (location.protocol === 'https:') ? crypto.randomUUID() :
|
|
|
|
([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c) =>
|
|
|
|
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
|
|
|
);
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function isEmpty(res) {
|
|
|
|
if (res == null) return true; // null, undefined
|
|
|
|
if (typeof res === 'string' || Array.isArray(res)) return res.length === 0; // empty String/Array
|
|
|
|
if (typeof res === 'object') {
|
|
|
|
if (res instanceof Map || res instanceof Set) return res.size === 0; // empty Map/Set
|
|
|
|
return Object.keys(res).length === 0; // empty Object
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeBlankAttrs(res) {
|
|
|
|
if (Array.isArray(res)) {
|
|
|
|
return res
|
|
|
|
.filter(item => !isEmpty(item))
|
|
|
|
.map(item => removeBlankAttrs(item));
|
|
|
|
}
|
|
|
|
if (res !== null && typeof res === 'object') {
|
|
|
|
const obj = {};
|
|
|
|
for (const key in res) {
|
|
|
|
const val = removeBlankAttrs(res[key]);
|
|
|
|
if (!isEmpty(val))
|
|
|
|
obj[key] = val;
|
|
|
|
}
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getFeatures() {
|
|
|
|
const callGetFeatures = rpc.declare({
|
|
|
|
object: 'luci.fchomo',
|
|
|
|
method: 'get_features',
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return L.resolveDefault(callGetFeatures(), {});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getServiceStatus(instance) {
|
|
|
|
var conf = 'fchomo';
|
|
|
|
const callServiceList = rpc.declare({
|
|
|
|
object: 'service',
|
|
|
|
method: 'list',
|
|
|
|
params: ['name'],
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return L.resolveDefault(callServiceList(conf), {})
|
|
|
|
.then((res) => {
|
|
|
|
let isRunning = false;
|
|
|
|
try {
|
|
|
|
isRunning = res[conf]['instances'][instance].running;
|
|
|
|
} catch (e) {}
|
|
|
|
return isRunning;
|
2025-01-16 00:24:18 +08:00
|
|
|
});
|
2025-01-17 20:36:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function getClashAPI(instance) {
|
|
|
|
const callGetClashAPI = rpc.declare({
|
|
|
|
object: 'luci.fchomo',
|
|
|
|
method: 'get_clash_api',
|
|
|
|
params: ['instance'],
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return L.resolveDefault(callGetClashAPI(instance), {});
|
|
|
|
}
|
|
|
|
|
|
|
|
// thanks to homeproxy
|
|
|
|
function loadDefaultLabel(section_id) {
|
|
|
|
const label = uci.get(this.config, section_id, 'label');
|
|
|
|
if (label) {
|
|
|
|
return label;
|
|
|
|
} else {
|
|
|
|
uci.set(this.config, section_id, 'label', section_id);
|
|
|
|
return section_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// thanks to homeproxy
|
|
|
|
function loadModalTitle(title, addtitle, section_id) {
|
|
|
|
const label = uci.get(this.config, section_id, 'label');
|
|
|
|
return label ? title + ' » ' + label : addtitle;
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadProxyGroupLabel(preadds, section_id) {
|
|
|
|
delete this.keylist;
|
|
|
|
delete this.vallist;
|
|
|
|
|
|
|
|
preadds?.forEach((arr) => {
|
|
|
|
this.value.apply(this, arr);
|
|
|
|
});
|
|
|
|
uci.sections(this.config, 'proxy_group', (res) => {
|
|
|
|
if (res.enabled !== '0')
|
|
|
|
this.value(res['.name'], res.label);
|
|
|
|
});
|
|
|
|
|
|
|
|
return this.super('load', section_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadNodeLabel(preadds, section_id) {
|
|
|
|
delete this.keylist;
|
|
|
|
delete this.vallist;
|
|
|
|
|
|
|
|
preadds?.forEach((arr) => {
|
|
|
|
this.value.apply(this, arr);
|
|
|
|
});
|
|
|
|
uci.sections(this.config, 'node', (res) => {
|
|
|
|
if (res.enabled !== '0')
|
|
|
|
this.value(res['.name'], res.label);
|
|
|
|
});
|
|
|
|
|
|
|
|
return this.super('load', section_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadProviderLabel(preadds, section_id) {
|
|
|
|
delete this.keylist;
|
|
|
|
delete this.vallist;
|
|
|
|
|
|
|
|
preadds?.forEach((arr) => {
|
|
|
|
this.value.apply(this, arr);
|
|
|
|
});
|
|
|
|
uci.sections(this.config, 'provider', (res) => {
|
|
|
|
if (res.enabled !== '0')
|
|
|
|
this.value(res['.name'], res.label);
|
|
|
|
});
|
|
|
|
|
|
|
|
return this.super('load', section_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadRulesetLabel(preadds, behaviors, section_id) {
|
|
|
|
delete this.keylist;
|
|
|
|
delete this.vallist;
|
|
|
|
|
|
|
|
preadds?.forEach((arr) => {
|
|
|
|
this.value.apply(this, arr);
|
|
|
|
});
|
|
|
|
uci.sections(this.config, 'ruleset', (res) => {
|
|
|
|
if (res.enabled !== '0')
|
|
|
|
if (behaviors ? behaviors.includes(res.behavior) : true)
|
2024-10-26 10:27:23 +08:00
|
|
|
this.value(res['.name'], res.label);
|
2025-01-17 20:36:29 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
return this.super('load', section_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadSubRuleGroup(preadds, section_id) {
|
|
|
|
delete this.keylist;
|
|
|
|
delete this.vallist;
|
|
|
|
|
|
|
|
preadds?.forEach((arr) => {
|
|
|
|
this.value.apply(this, arr);
|
|
|
|
});
|
|
|
|
let groups = {};
|
|
|
|
uci.sections(this.config, 'subrules', (res) => {
|
|
|
|
if (res.enabled !== '0')
|
|
|
|
groups[res.group] = res.group;
|
|
|
|
});
|
|
|
|
Object.keys(groups).forEach((group) => {
|
|
|
|
this.value(group, group);
|
|
|
|
});
|
|
|
|
|
|
|
|
return this.super('load', section_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderStatus(ElId, isRunning, instance, noGlobal) {
|
|
|
|
const visible = isRunning && (isRunning.http || isRunning.https);
|
|
|
|
|
|
|
|
return E([
|
|
|
|
E('button', {
|
|
|
|
'class': 'cbi-button cbi-button-apply' + (noGlobal ? ' hidden' : ''),
|
|
|
|
'click': ui.createHandlerFn(this, handleReload, instance)
|
|
|
|
}, [ _('Reload') ]),
|
|
|
|
updateStatus(E('span', { id: ElId, style: 'border: unset; font-style: italic; font-weight: bold' }), isRunning ? true : false),
|
|
|
|
E('a', {
|
|
|
|
'class': 'cbi-button cbi-button-apply %s'.format(visible ? '' : 'hidden'),
|
|
|
|
'href': visible ? getDashURL(isRunning) : '',
|
|
|
|
'target': '_blank',
|
|
|
|
'rel': 'noreferrer noopener'
|
|
|
|
}, [ _('Open Dashboard') ])
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
function updateStatus(El, isRunning, instance, noGlobal) {
|
|
|
|
if (El) {
|
|
|
|
El.style.color = isRunning ? 'green' : 'red';
|
|
|
|
El.innerHTML = ' %s%s '.format(noGlobal ? instance + ' ' : '', isRunning ? _('Running') : _('Not Running'));
|
|
|
|
/* Dashboard button */
|
|
|
|
if (El.nextSibling?.localName === 'a')
|
|
|
|
getClashAPI(instance).then((res) => {
|
|
|
|
let visible = isRunning && (res.http || res.https);
|
|
|
|
if (visible) {
|
|
|
|
El.nextSibling.classList.remove('hidden');
|
|
|
|
} else
|
|
|
|
El.nextSibling.classList.add('hidden');
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
El.nextSibling.href = visible ? getDashURL(Object.assign(res, isRunning)) : '';
|
|
|
|
});
|
|
|
|
}
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return El;
|
|
|
|
}
|
|
|
|
function getDashURL(isRunning) {
|
|
|
|
const tls = isRunning.https ? 's' : '';
|
|
|
|
const host = window.location.hostname;
|
|
|
|
const port = isRunning.https ? isRunning.https.split(':').pop() : isRunning.http.split(':').pop();
|
|
|
|
const secret = isRunning.secret;
|
|
|
|
const repo = isRunning.dashboard_repo;
|
|
|
|
|
|
|
|
return 'http%s://%s:%s/ui/'.format(tls, host, port) +
|
|
|
|
String.format(dashrepos_urlparams[repo] || '', host, port, secret)
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderResDownload(section_id) {
|
|
|
|
const section_type = this.section.sectiontype;
|
|
|
|
const type = uci.get(this.config, section_id, 'type');
|
|
|
|
const url = uci.get(this.config, section_id, 'url');
|
|
|
|
const header = uci.get(this.config, section_id, 'header');
|
|
|
|
|
|
|
|
let El = E([
|
|
|
|
E('button', {
|
|
|
|
class: 'cbi-button cbi-button-add',
|
|
|
|
disabled: (type !== 'http') || null,
|
|
|
|
click: ui.createHandlerFn(this, function(section_type, section_id, type, url, header) {
|
|
|
|
if (type === 'http') {
|
|
|
|
return downloadFile(section_type, section_id, url, header).then((res) => {
|
|
|
|
ui.addNotification(null, E('p', _('Download successful.')));
|
|
|
|
}).catch((e) => {
|
|
|
|
ui.addNotification(null, E('p', _('Download failed: %s').format(e)));
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
return ui.addNotification(null, E('p', _('Unable to download unsupported type: %s').format(type)));
|
|
|
|
}, section_type, section_id, type, url, header)
|
|
|
|
}, [ _('🡇') ]) //🗘
|
|
|
|
]);
|
|
|
|
|
|
|
|
return El;
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderSectionAdd(prefmt, LC, extra_class) {
|
|
|
|
let el = form.GridSection.prototype.renderSectionAdd.apply(this, [ extra_class ]),
|
|
|
|
nameEl = el.querySelector('.cbi-section-create-name');
|
|
|
|
ui.addValidator(nameEl, 'uciname', true, (v) => {
|
|
|
|
let button = el.querySelector('.cbi-section-create > .cbi-button-add');
|
2025-01-15 16:25:40 +08:00
|
|
|
const prefix = prefmt?.prefix ? prefmt.prefix : '';
|
|
|
|
const suffix = prefmt?.suffix ? prefmt.suffix : '';
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
if (!v) {
|
|
|
|
button.disabled = true;
|
|
|
|
return true;
|
|
|
|
} else if (LC && (v !== v.toLowerCase())) {
|
|
|
|
button.disabled = true;
|
|
|
|
return _('Expecting: %s').format(_('Lowercase only'));
|
|
|
|
} else if (uci.get(this.config, v)) {
|
|
|
|
button.disabled = true;
|
|
|
|
return _('Expecting: %s').format(_('unique UCI identifier'));
|
|
|
|
} else if (uci.get(this.config, prefix + v + suffix)) {
|
|
|
|
button.disabled = true;
|
|
|
|
return _('Expecting: %s').format(_('unique identifier'));
|
|
|
|
} else {
|
|
|
|
button.disabled = null;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}, 'blur', 'keyup');
|
|
|
|
|
|
|
|
return el;
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleAdd(prefmt, ev, name) {
|
|
|
|
const prefix = prefmt?.prefix ? prefmt.prefix : '';
|
|
|
|
const suffix = prefmt?.suffix ? prefmt.suffix : '';
|
|
|
|
|
|
|
|
return form.GridSection.prototype.handleAdd.apply(this, [ ev, prefix + name + suffix ]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleGenKey(option) {
|
|
|
|
const section_id = this.section.section;
|
|
|
|
const type = this.section.getOption('type').formvalue(section_id);
|
|
|
|
let widget = this.map.findElement('id', 'widget.cbid.fchomo.%s.%s'.format(section_id, option));
|
|
|
|
let password, required_method;
|
|
|
|
|
|
|
|
if (option === 'uuid' || option.match(/_uuid/))
|
|
|
|
required_method = 'uuid';
|
|
|
|
else if (type === 'shadowsocks')
|
|
|
|
required_method = this.section.getOption('shadowsocks_chipher')?.formvalue(section_id);
|
|
|
|
|
|
|
|
switch (required_method) {
|
|
|
|
/* NONE */
|
|
|
|
case 'none':
|
|
|
|
password = '';
|
|
|
|
break;
|
|
|
|
/* UUID */
|
|
|
|
case 'uuid':
|
|
|
|
password = generateRand('uuid');
|
|
|
|
break;
|
|
|
|
/* DEFAULT */
|
|
|
|
default:
|
|
|
|
password = generateRand('hex', 16);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* AEAD */
|
|
|
|
(function(length) {
|
|
|
|
if (length && length > 0)
|
|
|
|
password = generateRand('base64', length);
|
|
|
|
}(shadowsocks_cipher_length[required_method]));
|
|
|
|
|
|
|
|
return widget.value = password;
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleReload(instance, ev, section_id) {
|
|
|
|
instance = instance || '';
|
|
|
|
return fs.exec('/etc/init.d/fchomo', ['reload', instance])
|
|
|
|
.then((res) => { /* return window.location = window.location.href.split('#')[0] */ })
|
|
|
|
.catch((e) => {
|
|
|
|
ui.addNotification(null, E('p', _('Failed to execute "/etc/init.d/fchomo %s %s" reason: %s').format('reload', instance, e)))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleRemoveIdles() {
|
|
|
|
const section_type = this.sectiontype;
|
|
|
|
|
|
|
|
let loaded = [];
|
|
|
|
uci.sections(this.config, section_type, (section, sid) => loaded.push(sid));
|
|
|
|
|
|
|
|
return lsDir(section_type).then((res) => {
|
|
|
|
let sectionEl = E('div', { class: 'cbi-section' }, []);
|
|
|
|
|
|
|
|
res.filter(e => !loaded.includes(e)).forEach((filename) => {
|
|
|
|
sectionEl.appendChild(E('div', { class: 'cbi-value' }, [
|
|
|
|
E('label', {
|
|
|
|
class: 'cbi-value-title',
|
|
|
|
id: 'rmidles.' + filename + '.label'
|
|
|
|
}, [ filename ]),
|
|
|
|
E('div', { class: 'cbi-value-field' }, [
|
2024-10-26 10:27:23 +08:00
|
|
|
E('button', {
|
2025-01-17 20:36:29 +08:00
|
|
|
class: 'cbi-button cbi-button-negative important',
|
|
|
|
id: 'rmidles.' + filename + '.button',
|
|
|
|
click: ui.createHandlerFn(this, function(filename) {
|
|
|
|
return removeFile(section_type, filename).then((res) => {
|
|
|
|
let node = document.getElementById('rmidles.' + filename + '.label');
|
|
|
|
node.innerHTML = '<s>%s</s>'.format(node.innerHTML);
|
|
|
|
node = document.getElementById('rmidles.' + filename + '.button');
|
|
|
|
node.classList.add('hidden');
|
|
|
|
});
|
|
|
|
}, filename)
|
|
|
|
}, [ _('Remove') ])
|
2024-10-26 10:27:23 +08:00
|
|
|
])
|
2025-01-17 20:36:29 +08:00
|
|
|
]));
|
2024-10-26 10:27:23 +08:00
|
|
|
});
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
ui.showModal(_('Remove idles'), [
|
|
|
|
sectionEl,
|
|
|
|
E('div', { class: 'right' }, [
|
|
|
|
E('button', {
|
|
|
|
class: 'btn cbi-button-action',
|
|
|
|
click: ui.hideModal
|
|
|
|
}, [ _('Complete') ])
|
|
|
|
])
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
}
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function textvalue2Value(section_id) {
|
|
|
|
let cval = this.cfgvalue(section_id);
|
|
|
|
let i = this.keylist.indexOf(cval);
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return this.vallist[i];
|
|
|
|
}
|
2024-11-03 20:36:08 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function validateAuth(section_id, value) {
|
|
|
|
if (!value)
|
2024-11-03 20:36:08 +08:00
|
|
|
return true;
|
2025-01-17 20:36:29 +08:00
|
|
|
if (!value.match(/^[\w-]{3,}:[^:]+$/))
|
|
|
|
return _('Expecting: %s').format('[A-Za-z0-9_-]{3,}:[^:]+');
|
2024-11-03 20:36:08 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
function validateAuthUsername(section_id, value) {
|
|
|
|
if (!value)
|
2024-11-03 20:36:08 +08:00
|
|
|
return true;
|
2025-01-17 20:36:29 +08:00
|
|
|
if (!value.match(/^[\w-]{3,}$/))
|
|
|
|
return _('Expecting: %s').format('[A-Za-z0-9_-]{3,}');
|
2024-11-03 20:36:08 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
function validateAuthPassword(section_id, value) {
|
|
|
|
if (!value)
|
2024-11-03 20:36:08 +08:00
|
|
|
return true;
|
2025-01-17 20:36:29 +08:00
|
|
|
if (!value.match(/^[^:]+$/))
|
|
|
|
return _('Expecting: %s').format('[^:]+');
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function validateCommonPort(section_id, value) {
|
|
|
|
// thanks to homeproxy
|
|
|
|
let stubValidator = {
|
|
|
|
factory: validation,
|
|
|
|
apply(type, value, args) {
|
|
|
|
if (value != null)
|
|
|
|
this.value = value;
|
|
|
|
|
|
|
|
return validation.types[type].apply(this, args);
|
|
|
|
},
|
|
|
|
assert(condition) {
|
|
|
|
return !!condition;
|
2024-10-26 10:27:23 +08:00
|
|
|
}
|
2025-01-17 20:36:29 +08:00
|
|
|
};
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
const arr = value.trim().split(' ');
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
if (arr.length === 0 || arr.includes(''))
|
|
|
|
return _('Expecting: %s').format(_('non-empty value'));
|
2024-11-27 11:29:22 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
if (arr.length > 1 && arr.includes('all'))
|
|
|
|
return _('Expecting: %s').format(_('If All ports is selected, uncheck others'));
|
|
|
|
|
|
|
|
for (let custom of arr) {
|
|
|
|
if (!routing_port_type.map(e => e[0]).includes(custom)) {
|
|
|
|
let ports = [];
|
|
|
|
for (let i of custom.split(',')) {
|
|
|
|
if (!stubValidator.apply('port', i) && !stubValidator.apply('portrange', i))
|
|
|
|
return _('Expecting: %s').format(_('valid port value'));
|
|
|
|
if (ports.includes(i))
|
|
|
|
return _('Port %s alrealy exists!').format(i);
|
|
|
|
ports = ports.concat(i);
|
2024-11-16 20:36:29 +08:00
|
|
|
}
|
2025-01-17 20:36:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2024-11-16 20:36:29 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function validateJson(section_id, value) {
|
|
|
|
if (!value)
|
2024-11-16 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
try {
|
|
|
|
let obj = JSON.parse(value.trim());
|
|
|
|
if (!obj)
|
|
|
|
return _('Expecting: %s').format(_('valid JSON format'));
|
|
|
|
}
|
|
|
|
catch(e) {
|
|
|
|
return _('Expecting: %s').format(_('valid JSON format'));
|
|
|
|
}
|
2024-12-21 14:15:01 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
2024-12-21 14:15:01 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function validateBase64Key(length, section_id, value) {
|
|
|
|
/* Thanks to luci-proto-wireguard */
|
|
|
|
if (value)
|
|
|
|
if (value.length !== length || !value.match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) || value[length-1] !== '=')
|
|
|
|
return _('Expecting: %s').format(_('valid base64 key with %d characters').format(length));
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function validateShadowsocksPassword(encmode, section_id, value) {
|
|
|
|
let length = shadowsocks_cipher_length[encmode];
|
|
|
|
if (typeof length !== 'undefined') {
|
|
|
|
length = Math.ceil(length/3)*4;
|
|
|
|
if (encmode.match(/^2022-/)) {
|
|
|
|
return validateBase64Key(length, section_id, value);
|
|
|
|
} else {
|
|
|
|
if (length === 0 && !value)
|
|
|
|
return _('Expecting: %s').format(_('non-empty value'));
|
|
|
|
if (length !== 0 && value.length !== length)
|
|
|
|
return _('Expecting: %s').format(_('valid key length with %d characters').format(length));
|
|
|
|
}
|
|
|
|
} else
|
2024-10-26 10:27:23 +08:00
|
|
|
return true;
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function validateBytesize(section_id, value) {
|
|
|
|
if (!value)
|
2024-10-26 10:27:23 +08:00
|
|
|
return true;
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
if (!value.match(/^(\d+)(k|m|g)?b?$/))
|
|
|
|
return _('Expecting: %s').format('^(\\d+)(k|m|g)?b?$');
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
function validateTimeDuration(section_id, value) {
|
|
|
|
if (!value)
|
2024-10-26 10:27:23 +08:00
|
|
|
return true;
|
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
if (!value.match(/^(\d+)(s|m|h|d)?$/))
|
|
|
|
return _('Expecting: %s').format('^(\\d+)(s|m|h|d)?$');
|
2024-11-03 20:36:08 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function validateUniqueValue(section_id, value) {
|
|
|
|
if (!value)
|
|
|
|
return _('Expecting: %s').format(_('non-empty value'));
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
let duplicate = false;
|
|
|
|
uci.sections(this.config, this.section.sectiontype, (res) => {
|
|
|
|
if (res['.name'] !== section_id)
|
|
|
|
if (res[this.option] === value)
|
|
|
|
duplicate = true;
|
|
|
|
});
|
|
|
|
if (duplicate)
|
|
|
|
return _('Expecting: %s').format(_('unique value'));
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function validateUrl(section_id, value) {
|
|
|
|
if (!value)
|
|
|
|
return true;
|
2024-10-26 10:27:23 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
try {
|
|
|
|
let url = new URL(value);
|
|
|
|
if (!url.hostname)
|
|
|
|
return _('Expecting: %s').format(_('valid URL'));
|
|
|
|
}
|
|
|
|
catch(e) {
|
|
|
|
return _('Expecting: %s').format(_('valid URL'));
|
|
|
|
}
|
2024-11-03 20:36:08 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return true;
|
|
|
|
}
|
2024-11-03 20:36:08 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
function validateUUID(section_id, value) {
|
|
|
|
if (!value)
|
|
|
|
return true;
|
|
|
|
else if (value.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') === null)
|
|
|
|
return _('Expecting: %s').format(_('valid uuid'));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function lsDir(type) {
|
|
|
|
const callLsDir = rpc.declare({
|
|
|
|
object: 'luci.fchomo',
|
|
|
|
method: 'dir_ls',
|
|
|
|
params: ['type'],
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return L.resolveDefault(callLsDir(type), {}).then((res) => {
|
|
|
|
if (res.result) {
|
|
|
|
return res.result;
|
|
|
|
} else
|
|
|
|
throw res.error || 'unknown error';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function readFile(type, filename) {
|
|
|
|
const callReadFile = rpc.declare({
|
|
|
|
object: 'luci.fchomo',
|
|
|
|
method: 'file_read',
|
|
|
|
params: ['type', 'filename'],
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return L.resolveDefault(callReadFile(type, filename), {}).then((res) => {
|
|
|
|
if (res.content ?? true) {
|
|
|
|
return res.content;
|
|
|
|
} else
|
|
|
|
throw res.error || 'unknown error';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function writeFile(type, filename, content) {
|
|
|
|
const callWriteFile = rpc.declare({
|
|
|
|
object: 'luci.fchomo',
|
|
|
|
method: 'file_write',
|
|
|
|
params: ['type', 'filename', 'content'],
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return L.resolveDefault(callWriteFile(type, filename, content), {}).then((res) => {
|
|
|
|
if (res.result) {
|
|
|
|
return res.result;
|
|
|
|
} else
|
|
|
|
throw res.error || 'unknown error';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function downloadFile(type, filename, url, header) {
|
|
|
|
const callDownloadFile = rpc.declare({
|
|
|
|
object: 'luci.fchomo',
|
|
|
|
method: 'file_download',
|
|
|
|
params: ['type', 'filename', 'url', 'header'],
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return L.resolveDefault(callDownloadFile(type, filename, url, header), {}).then((res) => {
|
|
|
|
if (res.result) {
|
|
|
|
return res.result;
|
|
|
|
} else
|
|
|
|
throw res.error || 'unknown error';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeFile(type, filename) {
|
|
|
|
const callRemoveFile = rpc.declare({
|
|
|
|
object: 'luci.fchomo',
|
|
|
|
method: 'file_remove',
|
|
|
|
params: ['type', 'filename'],
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return L.resolveDefault(callRemoveFile(type, filename), {}).then((res) => {
|
|
|
|
if (res.result) {
|
|
|
|
return res.result;
|
|
|
|
} else
|
|
|
|
throw res.error || 'unknown error';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// thanks to homeproxy
|
|
|
|
function uploadCertificate(type, filename, ev) {
|
|
|
|
const callWriteCertificate = rpc.declare({
|
|
|
|
object: 'luci.fchomo',
|
|
|
|
method: 'certificate_write',
|
|
|
|
params: ['filename'],
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return ui.uploadFile('/tmp/fchomo_certificate.tmp', ev.target)
|
|
|
|
.then(L.bind((btn, res) => {
|
|
|
|
return L.resolveDefault(callWriteCertificate(filename), {}).then((ret) => {
|
|
|
|
if (ret.result === true)
|
|
|
|
ui.addNotification(null, E('p', _('Your %s was successfully uploaded. Size: %sB.').format(type, res.size)));
|
|
|
|
else
|
|
|
|
ui.addNotification(null, E('p', _('Failed to upload %s, error: %s.').format(type, ret.error)));
|
|
|
|
});
|
|
|
|
}, this, ev.target))
|
|
|
|
.catch((e) => { ui.addNotification(null, E('p', e.message)) });
|
|
|
|
}
|
|
|
|
function uploadInitialPack(ev, section_id) {
|
|
|
|
const callWriteInitialPack = rpc.declare({
|
|
|
|
object: 'luci.fchomo',
|
|
|
|
method: 'initialpack_write',
|
|
|
|
expect: { '': {} }
|
|
|
|
});
|
|
|
|
|
|
|
|
return ui.uploadFile('/tmp/fchomo_initialpack.tmp', ev.target)
|
|
|
|
.then(L.bind((btn, res) => {
|
|
|
|
return L.resolveDefault(callWriteInitialPack(), {}).then((ret) => {
|
|
|
|
if (ret.result === true) {
|
|
|
|
ui.addNotification(null, E('p', _('Successfully uploaded.')));
|
|
|
|
return window.location = window.location.href.split('#')[0];
|
|
|
|
} else
|
|
|
|
ui.addNotification(null, E('p', _('Failed to upload, error: %s.').format(ret.error)));
|
2024-11-17 20:36:06 +08:00
|
|
|
});
|
2025-01-17 20:36:29 +08:00
|
|
|
}, this, ev.target))
|
|
|
|
.catch((e) => { ui.addNotification(null, E('p', e.message)) });
|
|
|
|
}
|
2024-11-17 20:36:06 +08:00
|
|
|
|
2025-01-17 20:36:29 +08:00
|
|
|
return baseclass.extend({
|
|
|
|
/* Member */
|
|
|
|
rulesetdoc,
|
|
|
|
sharktaikogif,
|
2025-01-21 16:24:29 +08:00
|
|
|
less_24_10,
|
|
|
|
pr7558_merged,
|
|
|
|
pr7574_merged,
|
2025-01-17 20:36:29 +08:00
|
|
|
monospacefonts,
|
|
|
|
dashrepos,
|
|
|
|
dashrepos_urlparams,
|
|
|
|
checkurls,
|
|
|
|
health_checkurls,
|
|
|
|
inbound_type,
|
|
|
|
ip_version,
|
|
|
|
load_balance_strategy,
|
|
|
|
outbound_type,
|
|
|
|
preset_outbound,
|
|
|
|
proxy_group_type,
|
|
|
|
routing_port_type,
|
|
|
|
rules_type,
|
|
|
|
rules_logical_type,
|
|
|
|
rules_logical_payload_count,
|
|
|
|
shadowsocks_cipher_methods,
|
|
|
|
shadowsocks_cipher_length,
|
|
|
|
stunserver,
|
|
|
|
tls_client_fingerprints,
|
|
|
|
|
|
|
|
/* Prototype */
|
2025-01-21 16:24:29 +08:00
|
|
|
DynamicList: CBIDynamicList,
|
2025-01-17 20:36:29 +08:00
|
|
|
GenValue: CBIGenValue,
|
|
|
|
ListValue: CBIListValue,
|
2025-01-22 16:25:57 +08:00
|
|
|
RichMultiValue: CBIRichMultiValue,
|
2025-01-17 20:36:29 +08:00
|
|
|
StaticList: CBIStaticList,
|
|
|
|
TextValue: CBITextValue,
|
|
|
|
|
|
|
|
/* Method */
|
|
|
|
calcStringMD5,
|
|
|
|
decodeBase64Str,
|
|
|
|
generateRand,
|
|
|
|
isEmpty,
|
|
|
|
removeBlankAttrs,
|
|
|
|
getFeatures,
|
|
|
|
getServiceStatus,
|
|
|
|
getClashAPI,
|
|
|
|
loadDefaultLabel,
|
|
|
|
loadModalTitle,
|
|
|
|
loadProxyGroupLabel,
|
|
|
|
loadNodeLabel,
|
|
|
|
loadProviderLabel,
|
|
|
|
loadRulesetLabel,
|
|
|
|
loadSubRuleGroup,
|
|
|
|
renderStatus,
|
|
|
|
updateStatus,
|
|
|
|
getDashURL,
|
|
|
|
renderResDownload,
|
|
|
|
renderSectionAdd,
|
|
|
|
handleAdd,
|
|
|
|
handleGenKey,
|
|
|
|
handleReload,
|
|
|
|
handleRemoveIdles,
|
|
|
|
textvalue2Value,
|
|
|
|
validateAuth,
|
|
|
|
validateAuthUsername,
|
|
|
|
validateAuthPassword,
|
|
|
|
validateCommonPort,
|
|
|
|
validateJson,
|
|
|
|
validateBase64Key,
|
|
|
|
validateShadowsocksPassword,
|
|
|
|
validateBytesize,
|
|
|
|
validateTimeDuration,
|
|
|
|
validateUniqueValue,
|
|
|
|
validateUrl,
|
|
|
|
validateUUID,
|
|
|
|
lsDir,
|
|
|
|
readFile,
|
|
|
|
writeFile,
|
|
|
|
downloadFile,
|
|
|
|
removeFile,
|
|
|
|
uploadCertificate,
|
|
|
|
uploadInitialPack,
|
2024-10-26 10:27:23 +08:00
|
|
|
});
|