update 2025-01-17 20:23:38

This commit is contained in:
actions-user 2025-01-17 20:23:38 +08:00
parent 8424782934
commit 09a2824d0a
6 changed files with 1075 additions and 1000 deletions

View File

@ -7,6 +7,7 @@
'require ui';
'require validation';
/* Member */
const rulesetdoc = 'data:text/html;base64,' + 'cmxzdHBsYWNlaG9sZGVy';
const sharktaikogif = function() {
@ -25,47 +26,34 @@ const monospacefonts = [
'monospace'
];
const routing_port_type = [
['all', _('All ports')],
['common_tcpport', _('Common ports only (bypass P2P traffic)')],
['common_udpport', _('Common ports only (bypass P2P traffic)')],
['stun_port', _('STUN ports')],
['turn_port', _('TURN ports')],
];
return baseclass.extend({
rulesetdoc,
sharktaikogif,
monospacefonts,
dashrepos: [
const dashrepos = [
['zephyruso/zashboard', _('zashboard')],
['metacubex/metacubexd', _('metacubexd')],
['metacubex/yacd-meta', _('yacd-meta')],
['metacubex/razord-meta', _('razord-meta')]
],
];
dashrepos_urlparams: {
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'
},
};
checkurls: [
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')]
],
];
health_checkurls: [
const health_checkurls = [
['https://cp.cloudflare.com'],
['https://www.gstatic.com/generate_204']
],
];
inbound_type: [
const inbound_type = [
['http', _('HTTP')],
['socks', _('SOCKS')],
['mixed', _('Mixed')],
@ -74,24 +62,24 @@ return baseclass.extend({
['tuic', _('TUIC')],
['hysteria2', _('Hysteria2')],
//['tunnel', _('Tunnel')]
],
];
ip_version: [
const ip_version = [
['', _('Keep default')],
['dual', _('Dual stack')],
['ipv4', _('IPv4 only')],
['ipv6', _('IPv6 only')],
['ipv4-prefer', _('Prefer IPv4')],
['ipv6-prefer', _('Prefer IPv6')]
],
];
load_balance_strategy: [
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')]
],
];
outbound_type: [
const outbound_type = [
['direct', _('DIRECT')],
['http', _('HTTP')],
['socks5', _('SOCKS5')],
@ -107,9 +95,9 @@ return baseclass.extend({
['tuic', _('TUIC')],
['wireguard', _('WireGuard')],
['ssh', _('SSH')]
],
];
preset_outbound: {
const preset_outbound = {
full: [
['DIRECT'],
['REJECT'],
@ -125,19 +113,25 @@ return baseclass.extend({
['', 'RULES'],
['DIRECT']
]
},
};
proxy_group_type: [
const proxy_group_type = [
['select', _('Select')],
['fallback', _('Fallback')],
['url-test', _('URL test')],
['load-balance', _('Load balance')],
//['relay', _('Relay')], // Deprecated
],
];
routing_port_type,
const routing_port_type = [
['all', _('All ports')],
['common_tcpport', _('Common ports only (bypass P2P traffic)')],
['common_udpport', _('Common ports only (bypass P2P traffic)')],
['stun_port', _('STUN ports')],
['turn_port', _('TURN ports')],
];
rules_type: [
const rules_type = [
['DOMAIN'],
['DOMAIN-SUFFIX'],
['DOMAIN-KEYWORD'],
@ -175,23 +169,23 @@ return baseclass.extend({
['RULE-SET'],
['MATCH']
],
];
rules_logical_type: [
const rules_logical_type = [
['AND'],
['OR'],
['NOT'],
//['SUB-RULE'],
],
];
rules_logical_payload_count: {
const rules_logical_payload_count = {
'AND': { low: 2, high: undefined },
'OR': { low: 2, high: undefined },
'NOT': { low: 1, high: 1 },
//'SUB-RULE': 0,
},
};
shadowsocks_cipher_methods: [
const shadowsocks_cipher_methods = [
/* Stream */
['none', _('none')],
/* AEAD */
@ -204,9 +198,9 @@ return baseclass.extend({
['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')]
],
];
shadowsocks_cipher_length: {
const shadowsocks_cipher_length = {
/* AEAD */
'aes-128-gcm': 0,
'aes-192-gcm': 0,
@ -217,18 +211,18 @@ return baseclass.extend({
'2022-blake3-aes-128-gcm': 16,
'2022-blake3-aes-256-gcm': 32,
'2022-blake3-chacha20-poly1305': 32
},
};
stunserver: [
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']
],
];
tls_client_fingerprints: [
const tls_client_fingerprints = [
['chrome'],
['firefox'],
['safari'],
@ -238,9 +232,29 @@ return baseclass.extend({
['360'],
['qq'],
['random']
],
];
CBIListValue: form.ListValue.extend({
/* Prototype */
const CBIGenValue = form.Value.extend({
__name__: 'CBI.GenValue',
renderWidget() {
let node = form.Value.prototype.renderWidget.apply(this, arguments);
if (!this.password)
node.classList.add('control-group');
(node.querySelector('.control-group') || node).appendChild(E('button', {
'class': 'cbi-button cbi-button-add',
'title': _('Generate'),
'click': ui.createHandlerFn(this, handleGenKey, this.option)
}, [ _('Generate') ]));
return node;
}
});
const CBIListValue = form.ListValue.extend({
renderWidget(/* ... */) {
let frameEl = form.ListValue.prototype.renderWidget.apply(this, arguments);
@ -248,9 +262,9 @@ return baseclass.extend({
return frameEl;
}
}),
});
CBIStaticList: form.DynamicList.extend({
const CBIStaticList = form.DynamicList.extend({
__name__: 'CBI.StaticList',
renderWidget(/* ... */) {
@ -260,9 +274,9 @@ return baseclass.extend({
return El;
}
}),
});
CBITextValue: form.TextValue.extend({
const CBITextValue = form.TextValue.extend({
renderWidget(/* ... */) {
let frameEl = form.TextValue.prototype.renderWidget.apply(this, arguments);
@ -270,10 +284,11 @@ return baseclass.extend({
return frameEl;
}
}),
});
/* Method */
// thanks to homeproxy
calcStringMD5(e) {
function calcStringMD5(e) {
/* Thanks to https://stackoverflow.com/a/41602636 */
function h(a, b) {
var c, d, e, f, g;
@ -352,10 +367,10 @@ return baseclass.extend({
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
decodeBase64Str(str) {
function decodeBase64Str(str) {
if (!str)
return null;
@ -368,9 +383,9 @@ return baseclass.extend({
return decodeURIComponent(Array.prototype.map.call(atob(str), (c) =>
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
).join(''));
},
}
generateRand(type, length) {
function generateRand(type, length) {
let byteArr;
if (['base64', 'hex'].includes(type))
byteArr = crypto.getRandomValues(new Uint8Array(length));
@ -391,9 +406,9 @@ return baseclass.extend({
default:
return null;
};
},
}
isEmpty(res) {
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') {
@ -401,27 +416,27 @@ return baseclass.extend({
return Object.keys(res).length === 0; // empty Object
}
return false;
},
}
removeBlankAttrs(self, res) {
function removeBlankAttrs(res) {
if (Array.isArray(res)) {
return res
.filter(item => !self.isEmpty(item))
.map(item => self.removeBlankAttrs(self, item));
.filter(item => !isEmpty(item))
.map(item => removeBlankAttrs(item));
}
if (res !== null && typeof res === 'object') {
const obj = {};
for (const key in res) {
const val = self.removeBlankAttrs(self, res[key]);
if (!self.isEmpty(val))
const val = removeBlankAttrs(res[key]);
if (!isEmpty(val))
obj[key] = val;
}
return obj;
}
return res;
},
}
getFeatures() {
function getFeatures() {
const callGetFeatures = rpc.declare({
object: 'luci.fchomo',
method: 'get_features',
@ -429,9 +444,9 @@ return baseclass.extend({
});
return L.resolveDefault(callGetFeatures(), {});
},
}
getServiceStatus(instance) {
function getServiceStatus(instance) {
var conf = 'fchomo';
const callServiceList = rpc.declare({
object: 'service',
@ -448,9 +463,9 @@ return baseclass.extend({
} catch (e) {}
return isRunning;
});
},
}
getClashAPI(instance) {
function getClashAPI(instance) {
const callGetClashAPI = rpc.declare({
object: 'luci.fchomo',
method: 'get_clash_api',
@ -459,10 +474,10 @@ return baseclass.extend({
});
return L.resolveDefault(callGetClashAPI(instance), {});
},
}
// thanks to homeproxy
loadDefaultLabel(section_id) {
function loadDefaultLabel(section_id) {
const label = uci.get(this.config, section_id, 'label');
if (label) {
return label;
@ -470,15 +485,15 @@ return baseclass.extend({
uci.set(this.config, section_id, 'label', section_id);
return section_id;
}
},
}
// thanks to homeproxy
loadModalTitle(title, addtitle, section_id) {
function loadModalTitle(title, addtitle, section_id) {
const label = uci.get(this.config, section_id, 'label');
return label ? title + ' » ' + label : addtitle;
},
}
loadProxyGroupLabel(preadds, section_id) {
function loadProxyGroupLabel(preadds, section_id) {
delete this.keylist;
delete this.vallist;
@ -491,9 +506,9 @@ return baseclass.extend({
});
return this.super('load', section_id);
},
}
loadNodeLabel(preadds, section_id) {
function loadNodeLabel(preadds, section_id) {
delete this.keylist;
delete this.vallist;
@ -506,9 +521,9 @@ return baseclass.extend({
});
return this.super('load', section_id);
},
}
loadProviderLabel(preadds, section_id) {
function loadProviderLabel(preadds, section_id) {
delete this.keylist;
delete this.vallist;
@ -521,9 +536,9 @@ return baseclass.extend({
});
return this.super('load', section_id);
},
}
loadRulesetLabel(preadds, behaviors, section_id) {
function loadRulesetLabel(preadds, behaviors, section_id) {
delete this.keylist;
delete this.vallist;
@ -537,9 +552,9 @@ return baseclass.extend({
});
return this.super('load', section_id);
},
}
loadSubRuleGroup(preadds, section_id) {
function loadSubRuleGroup(preadds, section_id) {
delete this.keylist;
delete this.vallist;
@ -556,45 +571,45 @@ return baseclass.extend({
});
return this.super('load', section_id);
},
}
renderStatus(self, ElId, isRunning, instance, noGlobal) {
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, self.handleReload, instance)
'click': ui.createHandlerFn(this, handleReload, instance)
}, [ _('Reload') ]),
self.updateStatus(self, E('span', { id: ElId, style: 'border: unset; font-style: italic; font-weight: bold' }), isRunning ? true : false),
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 ? self.getDashURL(self, isRunning) : '',
'href': visible ? getDashURL(isRunning) : '',
'target': '_blank',
'rel': 'noreferrer noopener'
}, [ _('Open Dashboard') ])
]);
},
updateStatus(self, El, isRunning, instance, noGlobal) {
}
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')
self.getClashAPI(instance).then((res) => {
getClashAPI(instance).then((res) => {
let visible = isRunning && (res.http || res.https);
if (visible) {
El.nextSibling.classList.remove('hidden');
} else
El.nextSibling.classList.add('hidden');
El.nextSibling.href = visible ? self.getDashURL(self, Object.assign(res, isRunning)) : '';
El.nextSibling.href = visible ? getDashURL(Object.assign(res, isRunning)) : '';
});
}
return El;
},
getDashURL(self, isRunning) {
}
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();
@ -602,10 +617,10 @@ return baseclass.extend({
const repo = isRunning.dashboard_repo;
return 'http%s://%s:%s/ui/'.format(tls, host, port) +
String.format(self.dashrepos_urlparams[repo] || '', host, port, secret)
},
String.format(dashrepos_urlparams[repo] || '', host, port, secret)
}
renderResDownload(self, section_id) {
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');
@ -617,7 +632,7 @@ return baseclass.extend({
disabled: (type !== 'http') || null,
click: ui.createHandlerFn(this, function(section_type, section_id, type, url, header) {
if (type === 'http') {
return self.downloadFile(section_type, section_id, url, header).then((res) => {
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)));
@ -629,9 +644,9 @@ return baseclass.extend({
]);
return El;
},
}
renderSectionAdd(prefmt, LC, extra_class) {
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) => {
@ -658,31 +673,65 @@ return baseclass.extend({
}, 'blur', 'keyup');
return el;
},
}
handleAdd(prefmt, ev, name) {
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 ]);
},
}
handleReload(instance, ev, section_id) {
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)))
})
},
}
handleRemoveIdles(self) {
function handleRemoveIdles() {
const section_type = this.sectiontype;
let loaded = [];
uci.sections(this.config, section_type, (section, sid) => loaded.push(sid));
return self.lsDir(section_type).then((res) => {
return lsDir(section_type).then((res) => {
let sectionEl = E('div', { class: 'cbi-section' }, []);
res.filter(e => !loaded.includes(e)).forEach((filename) => {
@ -696,7 +745,7 @@ return baseclass.extend({
class: 'cbi-button cbi-button-negative important',
id: 'rmidles.' + filename + '.button',
click: ui.createHandlerFn(this, function(filename) {
return self.removeFile(section_type, filename).then((res) => {
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');
@ -718,41 +767,41 @@ return baseclass.extend({
])
]);
});
},
}
textvalue2Value(section_id) {
function textvalue2Value(section_id) {
let cval = this.cfgvalue(section_id);
let i = this.keylist.indexOf(cval);
return this.vallist[i];
},
}
validateAuth(section_id, value) {
function validateAuth(section_id, value) {
if (!value)
return true;
if (!value.match(/^[\w-]{3,}:[^:]+$/))
return _('Expecting: %s').format('[A-Za-z0-9_-]{3,}:[^:]+');
return true;
},
validateAuthUsername(section_id, value) {
}
function validateAuthUsername(section_id, value) {
if (!value)
return true;
if (!value.match(/^[\w-]{3,}$/))
return _('Expecting: %s').format('[A-Za-z0-9_-]{3,}');
return true;
},
validateAuthPassword(section_id, value) {
}
function validateAuthPassword(section_id, value) {
if (!value)
return true;
if (!value.match(/^[^:]+$/))
return _('Expecting: %s').format('[^:]+');
return true;
},
}
validateCommonPort(section_id, value) {
function validateCommonPort(section_id, value) {
// thanks to homeproxy
let stubValidator = {
factory: validation,
@ -789,9 +838,9 @@ return baseclass.extend({
}
return true;
},
}
validateJson(section_id, value) {
function validateJson(section_id, value) {
if (!value)
return true;
@ -805,23 +854,23 @@ return baseclass.extend({
}
return true;
},
}
validateBase64Key(length, section_id, value) {
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));
return true;
},
}
validateShadowsocksPassword(self, encmode, section_id, value) {
let length = self.shadowsocks_cipher_length[encmode];
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 self.validateBase64Key(length, section_id, value);
return validateBase64Key(length, section_id, value);
} else {
if (length === 0 && !value)
return _('Expecting: %s').format(_('non-empty value'));
@ -832,9 +881,9 @@ return baseclass.extend({
return true;
return true;
},
}
validateBytesize(section_id, value) {
function validateBytesize(section_id, value) {
if (!value)
return true;
@ -842,8 +891,8 @@ return baseclass.extend({
return _('Expecting: %s').format('^(\\d+)(k|m|g)?b?$');
return true;
},
validateTimeDuration(section_id, value) {
}
function validateTimeDuration(section_id, value) {
if (!value)
return true;
@ -851,9 +900,9 @@ return baseclass.extend({
return _('Expecting: %s').format('^(\\d+)(s|m|h|d)?$');
return true;
},
}
validateUniqueValue(section_id, value) {
function validateUniqueValue(section_id, value) {
if (!value)
return _('Expecting: %s').format(_('non-empty value'));
@ -867,9 +916,9 @@ return baseclass.extend({
return _('Expecting: %s').format(_('unique value'));
return true;
},
}
validateUrl(section_id, value) {
function validateUrl(section_id, value) {
if (!value)
return true;
@ -883,18 +932,18 @@ return baseclass.extend({
}
return true;
},
}
validateUUID(section_id, value) {
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;
},
}
lsDir(type) {
function lsDir(type) {
const callLsDir = rpc.declare({
object: 'luci.fchomo',
method: 'dir_ls',
@ -908,9 +957,9 @@ return baseclass.extend({
} else
throw res.error || 'unknown error';
});
},
}
readFile(type, filename) {
function readFile(type, filename) {
const callReadFile = rpc.declare({
object: 'luci.fchomo',
method: 'file_read',
@ -924,9 +973,9 @@ return baseclass.extend({
} else
throw res.error || 'unknown error';
});
},
}
writeFile(type, filename, content) {
function writeFile(type, filename, content) {
const callWriteFile = rpc.declare({
object: 'luci.fchomo',
method: 'file_write',
@ -940,9 +989,9 @@ return baseclass.extend({
} else
throw res.error || 'unknown error';
});
},
}
downloadFile(type, filename, url, header) {
function downloadFile(type, filename, url, header) {
const callDownloadFile = rpc.declare({
object: 'luci.fchomo',
method: 'file_download',
@ -956,9 +1005,9 @@ return baseclass.extend({
} else
throw res.error || 'unknown error';
});
},
}
removeFile(type, filename) {
function removeFile(type, filename) {
const callRemoveFile = rpc.declare({
object: 'luci.fchomo',
method: 'file_remove',
@ -972,10 +1021,10 @@ return baseclass.extend({
} else
throw res.error || 'unknown error';
});
},
}
// thanks to homeproxy
uploadCertificate(type, filename, ev) {
function uploadCertificate(type, filename, ev) {
const callWriteCertificate = rpc.declare({
object: 'luci.fchomo',
method: 'certificate_write',
@ -993,8 +1042,8 @@ return baseclass.extend({
});
}, this, ev.target))
.catch((e) => { ui.addNotification(null, E('p', e.message)) });
},
uploadInitialPack(ev, section_id) {
}
function uploadInitialPack(ev, section_id) {
const callWriteInitialPack = rpc.declare({
object: 'luci.fchomo',
method: 'initialpack_write',
@ -1013,4 +1062,80 @@ return baseclass.extend({
}, this, ev.target))
.catch((e) => { ui.addNotification(null, E('p', e.message)) });
}
return baseclass.extend({
/* Member */
rulesetdoc,
sharktaikogif,
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 */
GenValue: CBIGenValue,
ListValue: CBIListValue,
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,
});

View File

@ -165,7 +165,7 @@ class RulesEntry {
factor = [{type: 'MATCH'}];
}
rule = hm.removeBlankAttrs(hm, {
rule = hm.removeBlankAttrs({
type: this.type,
payload: factor,
detour: detour || null,
@ -366,7 +366,7 @@ function renderPayload(s, total, uciconfig) {
Object.keys(extenbox).forEach((n) => {
prefix = `payload${n}_`;
o = s.option(hm.CBIStaticList, prefix + 'type', _('Type') + ' ++');
o = s.option(hm.StaticList, prefix + 'type', _('Type') + ' ++');
o.default = hm.rules_type[0][0];
hm.rules_type.forEach((res) => {
o.value.apply(o, res);
@ -411,7 +411,7 @@ function renderPayload(s, total, uciconfig) {
return new RulesEntry(uci.get(uciconfig, section_id, 'entry')).getPayloads().slice(n).map(e => e[key] ?? '');
}, o, n, 'factor', uciconfig)
o = s.option(hm.CBIStaticList, prefix + 'NOTs', _('NOT') + ' ++',
o = s.option(hm.StaticList, prefix + 'NOTs', _('NOT') + ' ++',
_('<code>0</code> or <code>1</code> only.'));
o.value('0');
o.value('1');
@ -508,7 +508,7 @@ function renderRules(s, uciconfig) {
renderPayload(s, Math.max(...Object.values(hm.rules_logical_payload_count).map(e => e.low)), uciconfig);
o = s.option(hm.CBIListValue, 'detour', _('Proxy group'));
o = s.option(hm.ListValue, 'detour', _('Proxy group'));
o.load = function(section_id) {
hm.loadProxyGroupLabel.call(this, hm.preset_outbound.full, section_id);
@ -579,13 +579,13 @@ return view.extend({
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);
hm.updateStatus(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)
hm.renderStatus('_client_bar', false, 'mihomo-c', true)
])
]);
}
@ -1010,7 +1010,7 @@ return view.extend({
so.rmempty = false;
so.modalonly = true;
so = ss.option(hm.CBIListValue, 'detour', _('Proxy group'));
so = ss.option(hm.ListValue, 'detour', _('Proxy group'));
so.load = function(section_id) {
hm.loadProxyGroupLabel.call(this, hm.preset_outbound.dns, section_id);
@ -1142,7 +1142,7 @@ return view.extend({
so.rmempty = false;
so.editable = true;
so = ss.option(hm.CBIListValue, 'proxy', _('Proxy group override'),
so = ss.option(hm.ListValue, 'proxy', _('Proxy group override'),
_('Override the Proxy group of DNS server.'));
so.default = hm.preset_outbound.direct[0][0];
hm.preset_outbound.direct.forEach((res) => {

View File

@ -195,18 +195,18 @@ return view.extend({
}
so = ss.option(form.DummyValue, '_client_status', _('Client status'));
so.cfgvalue = function() { return hm.renderStatus(hm, '_client_bar', CisRunning ? { ...CclashAPI, dashboard_repo: dashboard_repo } : false, 'mihomo-c') }
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(hm, document.getElementById('_client_bar'), isRunning ? { dashboard_repo: dashboard_repo } : false, 'mihomo-c');
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(hm, '_server_bar', SisRunning ? { ...SclashAPI, dashboard_repo: dashboard_repo } : false, 'mihomo-s') }
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(hm, document.getElementById('_server_bar'), isRunning ? { dashboard_repo: dashboard_repo } : false, 'mihomo-s');
hm.updateStatus(document.getElementById('_server_bar'), isRunning ? { dashboard_repo: dashboard_repo } : false, 'mihomo-s');
});
})
@ -774,7 +774,7 @@ return view.extend({
/* Custom Direct list */
ss.tab('direct_list', _('Custom Direct List'));
so = ss.taboption('direct_list', hm.CBITextValue, 'direct_list.yaml', null);
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";
@ -792,7 +792,7 @@ return view.extend({
/* Custom Proxy list */
ss.tab('proxy_list', _('Custom Proxy List'));
so = ss.taboption('proxy_list', hm.CBITextValue, 'proxy_list.yaml', null);
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";

View File

@ -81,7 +81,7 @@ return view.extend({
so.depends({type: /^(http|socks5|mieru|trojan|hysteria2|tuic|ssh)$/});
so.modalonly = true;
so = ss.taboption('field_general', hm.CBITextValue, 'headers', _('HTTP header'));
so = ss.taboption('field_general', hm.TextValue, 'headers', _('HTTP header'));
so.placeholder = '{\n "User-Agent": [\n "Clash/v1.18.0",\n "mihomo/1.18.3"\n ],\n "Authorization": [\n //"token 1231231"\n ]\n}';
so.validate = L.bind(hm.validateJson, so);
so.depends('type', 'http');
@ -152,7 +152,7 @@ return view.extend({
so.password = true;
so.validate = function(section_id, value) {
const encmode = this.section.getOption('shadowsocks_chipher').formvalue(section_id);
return hm.validateShadowsocksPassword.call(this, hm, encmode, section_id, value);
return hm.validateShadowsocksPassword.call(this, encmode, section_id, value);
}
so.depends({type: 'ss', shadowsocks_chipher: /.+/});
so.modalonly = true;
@ -279,7 +279,7 @@ return view.extend({
so.password = true;
so.validate = function(section_id, value) {
const encmode = this.section.getOption('trojan_ss_chipher').formvalue(section_id);
return hm.validateShadowsocksPassword.call(this, hm, encmode, section_id, value);
return hm.validateShadowsocksPassword.call(this, encmode, section_id, value);
}
so.depends({type: 'trojan', trojan_ss_enabled: '1'});
so.modalonly = true;
@ -639,7 +639,7 @@ return view.extend({
so.depends({transport_enabled: '1', transport_type: /^(h2|ws)$/});
so.modalonly = true;
so = ss.taboption('field_transport', hm.CBITextValue, 'transport_http_headers', _('HTTP header'));
so = ss.taboption('field_transport', hm.TextValue, 'transport_http_headers', _('HTTP header'));
so.placeholder = '{\n "Host": "example.com",\n "Connection": [\n "keep-alive"\n ]\n}';
so.validate = L.bind(hm.validateJson, so);
so.depends({transport_enabled: '1', transport_type: /^(http|ws)$/});
@ -798,7 +798,7 @@ return view.extend({
el.appendChild(E('button', {
'class': 'cbi-button cbi-button-add',
'title': _('Remove idles'),
'click': ui.createHandlerFn(this, hm.handleRemoveIdles, hm)
'click': ui.createHandlerFn(this, hm.handleRemoveIdles)
}, [ _('Remove idles') ]));
return el;
@ -843,7 +843,7 @@ return view.extend({
}
so.modalonly = false;
so = ss.taboption('field_general', hm.CBITextValue, '_editer', _('Editer'),
so = ss.taboption('field_general', hm.TextValue, '_editer', _('Editer'),
_('Please type <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/proxy-providers/content/', _('Contents')));
so.placeholder = _('Content will not be verified, Please make sure you enter it correctly.');
@ -857,7 +857,7 @@ return view.extend({
so.depends('type', 'file');
so.modalonly = true;
so = ss.taboption('field_general', hm.CBITextValue, 'payload', 'payload:',
so = ss.taboption('field_general', hm.TextValue, 'payload', 'payload:',
_('Please type <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/proxy-providers/content/', _('Payload')));
so.placeholder = '- name: "ss1"\n type: ss\n server: server\n port: 443\n cipher: chacha20-ietf-poly1305\n password: "password"\n# ' + _('Content will not be verified, Please make sure you enter it correctly.');
@ -894,7 +894,7 @@ return view.extend({
//so.editable = true;
so.depends('type', 'http');
so = ss.taboption('field_general', hm.CBITextValue, 'header', _('HTTP header'),
so = ss.taboption('field_general', hm.TextValue, 'header', _('HTTP header'),
_('Custom HTTP header.'));
so.placeholder = '{\n "User-Agent": [\n "Clash/v1.18.0",\n "mihomo/1.18.3"\n ],\n "Accept": [\n //"application/vnd.github.v3.raw"\n ],\n "Authorization": [\n //"token 1231231"\n ]\n}';
so.validate = L.bind(hm.validateJson, so);
@ -1041,7 +1041,7 @@ return view.extend({
so.modalonly = true;
so = ss.option(form.DummyValue, '_update');
so.cfgvalue = L.bind(hm.renderResDownload, so, hm);
so.cfgvalue = L.bind(hm.renderResDownload, so);
so.editable = true;
so.modalonly = false;
/* Provider END */

View File

@ -183,7 +183,7 @@ return view.extend({
el.appendChild(E('button', {
'class': 'cbi-button cbi-button-add',
'title': _('Remove idles'),
'click': ui.createHandlerFn(this, hm.handleRemoveIdles, hm)
'click': ui.createHandlerFn(this, hm.handleRemoveIdles)
}, [ _('Remove idles') ]));
return el;
@ -260,7 +260,7 @@ return view.extend({
}
o.modalonly = false;
o = s.option(hm.CBITextValue, '_editer', _('Editer'),
o = s.option(hm.TextValue, '_editer', _('Editer'),
_('Please type <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/rule-providers/content/', _('Contents')));
o.placeholder = _('Content will not be verified, Please make sure you enter it correctly.');
@ -274,7 +274,7 @@ return view.extend({
o.depends({'type': 'file', 'format': /^(text|yaml)$/});
o.modalonly = true;
o = s.option(hm.CBITextValue, 'payload', 'payload:',
o = s.option(hm.TextValue, 'payload', 'payload:',
_('Please type <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/rule-providers/content/', _('Payload')));
o.placeholder = '- DOMAIN-SUFFIX,google.com\n# ' + _('Content will not be verified, Please make sure you enter it correctly.');
@ -312,7 +312,7 @@ return view.extend({
o.depends('type', 'http');
o = s.option(form.DummyValue, '_update');
o.cfgvalue = L.bind(hm.renderResDownload, o, hm);
o.cfgvalue = L.bind(hm.renderResDownload, o);
o.editable = true;
o.modalonly = false;
/* Rule set END */

View File

@ -7,56 +7,6 @@
'require fchomo as hm';
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 = hm.generateRand('uuid');
break;
/* DEFAULT */
default:
password = hm.generateRand('hex', 16);
break;
}
/* AEAD */
(function(length) {
if (length && length > 0)
password = hm.generateRand('base64', length);
}(hm.shadowsocks_cipher_length[required_method]));
return widget.value = password;
}
const CBIPWGenValue = form.Value.extend({
__name__: 'CBI.PWGenValue',
renderWidget() {
let node = form.Value.prototype.renderWidget.apply(this, arguments);
(node.querySelector('.control-group') || node).appendChild(E('button', {
'class': 'cbi-button cbi-button-add',
'title': _('Generate'),
'click': ui.createHandlerFn(this, handleGenKey, this.option)
}, [ _('Generate') ]));
return node;
}
});
return view.extend({
load() {
return Promise.all([
@ -78,13 +28,13 @@ return view.extend({
s.render = function () {
poll.add(function () {
return hm.getServiceStatus('mihomo-s').then((isRunning) => {
hm.updateStatus(hm, document.getElementById('_server_bar'), isRunning ? { dashboard_repo: dashboard_repo } : false, 'mihomo-s', true);
hm.updateStatus(document.getElementById('_server_bar'), isRunning ? { dashboard_repo: dashboard_repo } : false, 'mihomo-s', true);
});
});
return E('div', { class: 'cbi-section' }, [
E('p', [
hm.renderStatus(hm, '_server_bar', false, 'mihomo-s', true)
hm.renderStatus('_server_bar', false, 'mihomo-s', true)
])
]);
}
@ -151,7 +101,7 @@ return view.extend({
o.depends({type: /^(http|socks|mixed|hysteria2)$/});
o.modalonly = true;
o = s.option(CBIPWGenValue, 'password', _('Password'));
o = s.option(hm.GenValue, 'password', _('Password'));
o.password = true;
o.validate = L.bind(hm.validateAuthPassword, o);
o.rmempty = false;
@ -184,7 +134,7 @@ return view.extend({
o.depends('type', 'hysteria2');
o.modalonly = true;
o = s.option(CBIPWGenValue, 'hysteria_obfs_password', _('Obfuscate password'),
o = s.option(hm.GenValue, 'hysteria_obfs_password', _('Obfuscate password'),
_('Enabling obfuscation will make the server incompatible with standard QUIC connections, losing the ability to masquerade with HTTP/3.'));
o.password = true;
o.rmempty = false;
@ -207,17 +157,17 @@ return view.extend({
o.depends('type', 'shadowsocks');
o.modalonly = true;
o = s.option(CBIPWGenValue, 'shadowsocks_password', _('Password'));
o = s.option(hm.GenValue, 'shadowsocks_password', _('Password'));
o.password = true;
o.validate = function(section_id, value) {
const encmode = this.section.getOption('shadowsocks_chipher').formvalue(section_id);
return hm.validateShadowsocksPassword.call(this, hm, encmode, section_id, value);
return hm.validateShadowsocksPassword.call(this, encmode, section_id, value);
}
o.depends({type: 'shadowsocks', shadowsocks_chipher: /.+/});
o.modalonly = true;
/* Tuic fields */
o = s.option(CBIPWGenValue, 'uuid', _('UUID'));
o = s.option(hm.GenValue, 'uuid', _('UUID'));
o.rmempty = false;
o.validate = L.bind(hm.validateUUID, o);
o.depends('type', 'tuic');
@ -253,7 +203,7 @@ return view.extend({
o.modalonly = true;
/* VMess fields */
o = s.option(CBIPWGenValue, 'vmess_uuid', _('UUID'));
o = s.option(hm.GenValue, 'vmess_uuid', _('UUID'));
o.rmempty = false;
o.validate = L.bind(hm.validateUUID, o);
o.depends('type', 'vmess');