mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 19:45:13 +08:00
🦄 Sync 2026-07-16 04:24:09
This commit is contained in:
1 parent
58b9b357a3
commit
ddc3d78236
70 files changed
+2113
-1956
No files matched your search
+3
-3
@@ -5,12 +5,12 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=cloudreve
|
PKG_NAME:=cloudreve
|
||||||
PKG_VERSION:=4.17.0
|
PKG_VERSION:=4.18.0
|
||||||
PKG_RELEASE:=13
|
PKG_RELEASE:=14
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/cloudreve/Cloudreve.git
|
PKG_SOURCE_URL:=https://github.com/cloudreve/Cloudreve.git
|
||||||
PKG_SOURCE_VERSION:=d4a145c3ac68f08b9071dec0f2ac025c6dc7362d
|
PKG_SOURCE_VERSION:=0bb0ab833571d380153edd3529e01a7957b8b4ce
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_LICENSE:=GPL-3.0-only
|
PKG_LICENSE:=GPL-3.0-only
|
||||||
|
|||||||
@@ -461,25 +461,26 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
o.depends('type', 'tuic');
|
o.depends('type', 'tuic');
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
/* Hysteria2 fields */
|
/* Brutal fields */
|
||||||
o = s.taboption('field_general', form.Value, 'hysteria_up_mbps', _('Max upload speed'),
|
o = s.taboption('field_general', form.Value, 'brutal_up_mbps', _('Max upload speed'),
|
||||||
_('In Mbps.'));
|
_('In Mbps.'));
|
||||||
o.datatype = 'uinteger';
|
o.datatype = 'uinteger';
|
||||||
o.depends('type', 'hysteria2');
|
o.depends({type: /^(hysteria2|shadowquic)$/});
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'hysteria_down_mbps', _('Max download speed'),
|
o = s.taboption('field_general', form.Value, 'brutal_down_mbps', _('Max download speed'),
|
||||||
_('In Mbps.'));
|
_('In Mbps.'));
|
||||||
o.datatype = 'uinteger';
|
o.datatype = 'uinteger';
|
||||||
o.depends('type', 'hysteria2');
|
o.depends({type: /^(hysteria2|shadowquic)$/});
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Flag, 'hysteria_ignore_client_bandwidth', _('Ignore client bandwidth'),
|
o = s.taboption('field_general', form.Flag, 'brutal_ignore_client_bandwidth', _('Ignore client bandwidth'),
|
||||||
_('Tell the client to use the BBR flow control algorithm instead of Hysteria CC.'));
|
_('Tell the client to use the BBR flow control algorithm instead of Hysteria CC.'));
|
||||||
o.default = o.disabled;
|
o.default = o.disabled;
|
||||||
o.depends({type: 'hysteria2', hysteria_up_mbps: '', hysteria_down_mbps: ''});
|
o.depends({type: /^(hysteria2|shadowquic)$/, brutal_up_mbps: '', brutal_down_mbps: ''});
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
|
/* Hysteria2 fields */
|
||||||
o = s.taboption('field_general', form.ListValue, 'hysteria_obfs_type', _('Obfuscate type'));
|
o = s.taboption('field_general', form.ListValue, 'hysteria_obfs_type', _('Obfuscate type'));
|
||||||
o.value('', _('Disable'));
|
o.value('', _('Disable'));
|
||||||
o.value('salamander', _('Salamander'));
|
o.value('salamander', _('Salamander'));
|
||||||
@@ -638,6 +639,7 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
o = s.taboption('field_general', form.Flag, 'plugin', _('Plugin'));
|
o = s.taboption('field_general', form.Flag, 'plugin', _('Plugin'));
|
||||||
o.default = o.disabled;
|
o.default = o.disabled;
|
||||||
o.depends({type: /^(shadowsocks|snell)$/});
|
o.depends({type: /^(shadowsocks|snell)$/});
|
||||||
|
o.depends({type: /^(vmess|vless|trojan|anytls)$/});
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
o = s.taboption('field_plugin', form.ListValue, 'plugin_type', _('Plugin type'));
|
o = s.taboption('field_plugin', form.ListValue, 'plugin_type', _('Plugin type'));
|
||||||
@@ -651,9 +653,12 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
|
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
|
||||||
return _('Expecting: only support %s.').format(_('obfs-simple') +
|
return _('Expecting: Only support %s.').format(_('obfs-simple') +
|
||||||
' / ' + _('ShadowTLS'));
|
' / ' + _('ShadowTLS'));
|
||||||
}
|
}
|
||||||
|
if (['vmess', 'vless', 'trojan', 'anytls'].includes(type) && !['jls'].includes(value)) {
|
||||||
|
return _('Expecting: Only support %s.').format(_('JLS'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1045,6 +1050,10 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'shadowsocks':
|
case 'shadowsocks':
|
||||||
|
case 'vmess':
|
||||||
|
case 'vless':
|
||||||
|
case 'trojan':
|
||||||
|
case 'anytls':
|
||||||
def_alpn = ['h2', 'http/1.1']; // when plugin_type in ['jls']
|
def_alpn = ['h2', 'http/1.1']; // when plugin_type in ['jls']
|
||||||
break;
|
break;
|
||||||
case 'tuic':
|
case 'tuic':
|
||||||
@@ -1079,8 +1088,21 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
o = s.taboption('field_tls', form.Value, 'tls_cert_path', _('Certificate path'),
|
o = s.taboption('field_tls', form.Value, 'tls_cert_path', _('Certificate path'),
|
||||||
_('The %s public key, in PEM format.').format(_('Server')));
|
_('The %s public key, in PEM format.').format(_('Server')));
|
||||||
o.value('/etc/fchomo/certs/server_publickey.pem');
|
o.value('/etc/fchomo/certs/server_publickey.pem');
|
||||||
o.depends({tls: '1', tls_reality: '0', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2|hysteria2-realm|trusttunnel)$/});
|
o.validate = function(section_id, value) {
|
||||||
o.rmempty = false;
|
const plugin_type = this.section.getOption('plugin_type').formvalue(section_id);
|
||||||
|
const tls_reality = this.section.getOption('tls_reality').formvalue(section_id);
|
||||||
|
|
||||||
|
if (plugin_type === 'jls' || tls_reality == 1) {
|
||||||
|
if (value)
|
||||||
|
return _('Expecting: Keep empty when %s is enabled.').format(_('JLS') +
|
||||||
|
' / ' + _('REALITY'));
|
||||||
|
} else if (!value) {
|
||||||
|
return _('Expecting: Cannot be empty.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
o.depends({tls: '1', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2|hysteria2-realm|trusttunnel)$/});
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.Button, '_upload_cert', _('Upload certificate'),
|
o = s.taboption('field_tls', form.Button, '_upload_cert', _('Upload certificate'),
|
||||||
@@ -1181,6 +1203,15 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
// uTLS fields
|
// uTLS fields
|
||||||
o = s.taboption('field_tls', form.Flag, 'tls_reality', _('REALITY'));
|
o = s.taboption('field_tls', form.Flag, 'tls_reality', _('REALITY'));
|
||||||
o.default = o.disabled;
|
o.default = o.disabled;
|
||||||
|
o.validate = function(section_id, value) {
|
||||||
|
const plugin_type = this.section.getOption('plugin_type').formvalue(section_id);
|
||||||
|
value = this.formvalue(section_id);
|
||||||
|
|
||||||
|
if (value == 1 && plugin_type === 'jls')
|
||||||
|
return _('Expecting: Cannot be enabled when %s is enabled.').format(_('JLS'));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
o.depends('tls', '1');
|
o.depends('tls', '1');
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
@@ -1243,14 +1274,14 @@ function renderListeners(s, uciconfig, isClient) {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'vless':
|
case 'vless':
|
||||||
if (!['grpc', 'ws', 'xhttp'].includes(value))
|
if (!['grpc', 'ws', 'xhttp'].includes(value))
|
||||||
return _('Expecting: only support %s.').format(_('gRPC') +
|
return _('Expecting: Only support %s.').format(_('gRPC') +
|
||||||
' / ' + _('WebSocket') +
|
' / ' + _('WebSocket') +
|
||||||
' / ' + _('XHTTP'));
|
' / ' + _('XHTTP'));
|
||||||
break;
|
break;
|
||||||
case 'vmess':
|
case 'vmess':
|
||||||
case 'trojan':
|
case 'trojan':
|
||||||
if (!['grpc', 'ws'].includes(value))
|
if (!['grpc', 'ws'].includes(value))
|
||||||
return _('Expecting: only support %s.').format(_('gRPC') +
|
return _('Expecting: Only support %s.').format(_('gRPC') +
|
||||||
' / ' + _('WebSocket'));
|
' / ' + _('WebSocket'));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -667,6 +667,19 @@ return view.extend({
|
|||||||
so.depends('type', 'tuic');
|
so.depends('type', 'tuic');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
|
/* Brutal fields */
|
||||||
|
so = ss.taboption('field_general', form.Value, 'brutal_up_mbps', _('Max upload speed'),
|
||||||
|
_('In Mbps.'));
|
||||||
|
so.datatype = 'uinteger';
|
||||||
|
so.depends({type: /^(hysteria|hysteria2|shadowquic)$/});
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
|
so = ss.taboption('field_general', form.Value, 'brutal_down_mbps', _('Max download speed'),
|
||||||
|
_('In Mbps.'));
|
||||||
|
so.datatype = 'uinteger';
|
||||||
|
so.depends({type: /^(hysteria|hysteria2|shadowquic)$/});
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
/* Hysteria / Hysteria2 fields */
|
/* Hysteria / Hysteria2 fields */
|
||||||
so = ss.taboption('field_general', form.DynamicList, 'hysteria_ports', _('Ports pool'));
|
so = ss.taboption('field_general', form.DynamicList, 'hysteria_ports', _('Ports pool'));
|
||||||
so.datatype = 'or(port, portrange)';
|
so.datatype = 'or(port, portrange)';
|
||||||
@@ -679,18 +692,6 @@ return view.extend({
|
|||||||
so.depends('type', 'hysteria2');
|
so.depends('type', 'hysteria2');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.Value, 'hysteria_up_mbps', _('Max upload speed'),
|
|
||||||
_('In Mbps.'));
|
|
||||||
so.datatype = 'uinteger';
|
|
||||||
so.depends({type: /^(hysteria|hysteria2)$/});
|
|
||||||
so.modalonly = true;
|
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.Value, 'hysteria_down_mbps', _('Max download speed'),
|
|
||||||
_('In Mbps.'));
|
|
||||||
so.datatype = 'uinteger';
|
|
||||||
so.depends({type: /^(hysteria|hysteria2)$/});
|
|
||||||
so.modalonly = true;
|
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.ListValue, 'hysteria_obfs_type', _('Obfuscate type'));
|
so = ss.taboption('field_general', form.ListValue, 'hysteria_obfs_type', _('Obfuscate type'));
|
||||||
so.value('', _('Disable'));
|
so.value('', _('Disable'));
|
||||||
so.value('salamander', _('Salamander'));
|
so.value('salamander', _('Salamander'));
|
||||||
@@ -716,17 +717,6 @@ return view.extend({
|
|||||||
so.depends('hysteria_obfs_type', 'gecko');
|
so.depends('hysteria_obfs_type', 'gecko');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
/* TrustTunnel fields */
|
|
||||||
so = ss.taboption('field_general', form.Flag, 'trusttunnel_health_check', _('Health check'));
|
|
||||||
so.default = so.enabled;
|
|
||||||
so.depends('type', 'trusttunnel');
|
|
||||||
so.modalonly = true;
|
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.Flag, 'trusttunnel_quic', _('QUIC'));
|
|
||||||
so.default = so.disabled;
|
|
||||||
so.depends('type', 'trusttunnel');
|
|
||||||
so.modalonly = true;
|
|
||||||
|
|
||||||
/* ShadowQUIC fields */
|
/* ShadowQUIC fields */
|
||||||
so = ss.taboption('field_general', form.DynamicList, 'shadowquic_quic_versions', _('QUIC versions'),
|
so = ss.taboption('field_general', form.DynamicList, 'shadowquic_quic_versions', _('QUIC versions'),
|
||||||
_('Support %s, default %s.').format('v1/v2', 'v1'));
|
_('Support %s, default %s.').format('v1/v2', 'v1'));
|
||||||
@@ -752,6 +742,17 @@ return view.extend({
|
|||||||
so.depends('type', 'shadowquic');
|
so.depends('type', 'shadowquic');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
|
/* TrustTunnel fields */
|
||||||
|
so = ss.taboption('field_general', form.Flag, 'trusttunnel_health_check', _('Health check'));
|
||||||
|
so.default = so.enabled;
|
||||||
|
so.depends('type', 'trusttunnel');
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
|
so = ss.taboption('field_general', form.Flag, 'trusttunnel_quic', _('QUIC'));
|
||||||
|
so.default = so.disabled;
|
||||||
|
so.depends('type', 'trusttunnel');
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
/* WireGuard fields */
|
/* WireGuard fields */
|
||||||
so = ss.taboption('field_general', form.Value, 'wireguard_ip', _('Local address'),
|
so = ss.taboption('field_general', form.Value, 'wireguard_ip', _('Local address'),
|
||||||
_('The %s address used by local machine in the Wireguard network.').format('IPv4'));
|
_('The %s address used by local machine in the Wireguard network.').format('IPv4'));
|
||||||
@@ -982,7 +983,7 @@ return view.extend({
|
|||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
|
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
|
||||||
return _('Expecting: only support %s.').format(_('obfs-simple') +
|
return _('Expecting: Only support %s.').format(_('obfs-simple') +
|
||||||
' / ' + _('ShadowTLS'));
|
' / ' + _('ShadowTLS'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1166,6 +1167,14 @@ return view.extend({
|
|||||||
|
|
||||||
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
|
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
|
||||||
_('Hostname that the client attempts to connect to at the start of the TLS handshake process.'));
|
_('Hostname that the client attempts to connect to at the start of the TLS handshake process.'));
|
||||||
|
so.validate = function(section_id, value) {
|
||||||
|
const tls_jls = this.section.getOption('tls_jls').formvalue(section_id);
|
||||||
|
|
||||||
|
if (tls_jls == 1 && !value)
|
||||||
|
return _('Expecting: %s cannot be empty when %s is enabled.').format(_('TLS SNI'), _('JLS'));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2|shadowquic|trusttunnel|masque)$/});
|
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2|shadowquic|trusttunnel|masque)$/});
|
||||||
so.depends({tls: '1', type: /^(tuic)$/, tls_disable_sni: '0'});
|
so.depends({tls: '1', type: /^(tuic)$/, tls_disable_sni: '0'});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
@@ -1300,9 +1309,24 @@ return view.extend({
|
|||||||
so.depends({type: 'ss', plugin_type: 'jls'});
|
so.depends({type: 'ss', plugin_type: 'jls'});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
|
so = ss.taboption('field_tls', form.Flag, 'tls_jls', _('JLS'));
|
||||||
|
so.default = so.disabled;
|
||||||
|
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls)$/});
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
|
so = ss.taboption('field_tls', form.Value, 'tls_jls_username', _('JLS username'));
|
||||||
|
so.rmempty = false;
|
||||||
|
so.depends('tls_jls', '1');
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
|
so = ss.taboption('field_tls', form.Value, 'tls_jls_password', _('JLS password'));
|
||||||
|
so.rmempty = false;
|
||||||
|
so.depends('tls_jls', '1');
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_tls', form.Flag, 'tls_reality', _('REALITY'));
|
so = ss.taboption('field_tls', form.Flag, 'tls_reality', _('REALITY'));
|
||||||
so.default = so.disabled;
|
so.default = so.disabled;
|
||||||
so.depends({tls: '1', type: /^(vmess|vless|trojan)$/});
|
so.depends({tls: '1', tls_jls: '0', type: /^(vmess|vless|trojan)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_tls', form.Value, 'tls_reality_public_key', _('REALITY public key'));
|
so = ss.taboption('field_tls', form.Value, 'tls_reality_public_key', _('REALITY public key'));
|
||||||
@@ -1344,14 +1368,14 @@ return view.extend({
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'vmess':
|
case 'vmess':
|
||||||
if (!['http', 'h2', 'grpc', 'ws'].includes(value))
|
if (!['http', 'h2', 'grpc', 'ws'].includes(value))
|
||||||
return _('Expecting: only support %s.').format(_('HTTP') +
|
return _('Expecting: Only support %s.').format(_('HTTP') +
|
||||||
' / ' + _('HTTPUpgrade') +
|
' / ' + _('HTTPUpgrade') +
|
||||||
' / ' + _('gRPC') +
|
' / ' + _('gRPC') +
|
||||||
' / ' + _('WebSocket'));
|
' / ' + _('WebSocket'));
|
||||||
break;
|
break;
|
||||||
case 'vless':
|
case 'vless':
|
||||||
if (!['http', 'h2', 'grpc', 'ws', 'xhttp'].includes(value))
|
if (!['http', 'h2', 'grpc', 'ws', 'xhttp'].includes(value))
|
||||||
return _('Expecting: only support %s.').format(_('HTTP') +
|
return _('Expecting: Only support %s.').format(_('HTTP') +
|
||||||
' / ' + _('HTTPUpgrade') +
|
' / ' + _('HTTPUpgrade') +
|
||||||
' / ' + _('gRPC') +
|
' / ' + _('gRPC') +
|
||||||
' / ' + _('WebSocket') +
|
' / ' + _('WebSocket') +
|
||||||
@@ -1359,7 +1383,7 @@ return view.extend({
|
|||||||
break;
|
break;
|
||||||
case 'trojan':
|
case 'trojan':
|
||||||
if (!['grpc', 'ws'].includes(value))
|
if (!['grpc', 'ws'].includes(value))
|
||||||
return _('Expecting: only support %s.').format(_('gRPC') +
|
return _('Expecting: Only support %s.').format(_('gRPC') +
|
||||||
' / ' + _('WebSocket'));
|
' / ' + _('WebSocket'));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -25,6 +25,30 @@ migrate() {
|
|||||||
uci_set "$CONF" "$1" max_idle_time "$tuic_max_idle_time"
|
uci_set "$CONF" "$1" max_idle_time "$tuic_max_idle_time"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# hysteria_up_mbps -> brutal_up_mbps
|
||||||
|
if isDefined "$1" hysteria_up_mbps; then
|
||||||
|
local hysteria_up_mbps
|
||||||
|
config_get hysteria_up_mbps "$1" hysteria_up_mbps ""
|
||||||
|
uci_remove "$CONF" "$1" hysteria_up_mbps
|
||||||
|
uci_set "$CONF" "$1" brutal_up_mbps "$hysteria_up_mbps"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# hysteria_down_mbps -> brutal_down_mbps
|
||||||
|
if isDefined "$1" hysteria_down_mbps; then
|
||||||
|
local hysteria_down_mbps
|
||||||
|
config_get hysteria_down_mbps "$1" hysteria_down_mbps ""
|
||||||
|
uci_remove "$CONF" "$1" hysteria_down_mbps
|
||||||
|
uci_set "$CONF" "$1" brutal_down_mbps "$hysteria_down_mbps"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# hysteria_ignore_client_bandwidth -> brutal_ignore_client_bandwidth
|
||||||
|
if isDefined "$1" hysteria_ignore_client_bandwidth; then
|
||||||
|
local hysteria_ignore_client_bandwidth
|
||||||
|
config_get hysteria_ignore_client_bandwidth "$1" hysteria_ignore_client_bandwidth ""
|
||||||
|
uci_remove "$CONF" "$1" hysteria_ignore_client_bandwidth
|
||||||
|
uci_set "$CONF" "$1" brutal_ignore_client_bandwidth "$hysteria_ignore_client_bandwidth"
|
||||||
|
fi
|
||||||
|
|
||||||
# plugin -> plugin_type
|
# plugin -> plugin_type
|
||||||
if isDefined "$1" plugin; then
|
if isDefined "$1" plugin; then
|
||||||
local plugin
|
local plugin
|
||||||
|
|||||||
@@ -59,6 +59,22 @@ migrate() {
|
|||||||
uci_set "$CONF" "$1" max_open_streams "$tuic_max_open_streams"
|
uci_set "$CONF" "$1" max_open_streams "$tuic_max_open_streams"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# hysteria_up_mbps -> brutal_up_mbps
|
||||||
|
if isDefined "$1" hysteria_up_mbps; then
|
||||||
|
local hysteria_up_mbps
|
||||||
|
config_get hysteria_up_mbps "$1" hysteria_up_mbps ""
|
||||||
|
uci_remove "$CONF" "$1" hysteria_up_mbps
|
||||||
|
uci_set "$CONF" "$1" brutal_up_mbps "$hysteria_up_mbps"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# hysteria_down_mbps -> brutal_down_mbps
|
||||||
|
if isDefined "$1" hysteria_down_mbps; then
|
||||||
|
local hysteria_down_mbps
|
||||||
|
config_get hysteria_down_mbps "$1" hysteria_down_mbps ""
|
||||||
|
uci_remove "$CONF" "$1" hysteria_down_mbps
|
||||||
|
uci_set "$CONF" "$1" brutal_down_mbps "$hysteria_down_mbps"
|
||||||
|
fi
|
||||||
|
|
||||||
# plugin -> plugin_type
|
# plugin -> plugin_type
|
||||||
if isDefined "$1" plugin; then
|
if isDefined "$1" plugin; then
|
||||||
local plugin
|
local plugin
|
||||||
|
|||||||
@@ -33,6 +33,30 @@ migrate() {
|
|||||||
uci_set "$CONF" "$1" max_idle_time "$tuic_max_idle_time"
|
uci_set "$CONF" "$1" max_idle_time "$tuic_max_idle_time"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# hysteria_up_mbps -> brutal_up_mbps
|
||||||
|
if isDefined "$1" hysteria_up_mbps; then
|
||||||
|
local hysteria_up_mbps
|
||||||
|
config_get hysteria_up_mbps "$1" hysteria_up_mbps ""
|
||||||
|
uci_remove "$CONF" "$1" hysteria_up_mbps
|
||||||
|
uci_set "$CONF" "$1" brutal_up_mbps "$hysteria_up_mbps"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# hysteria_down_mbps -> brutal_down_mbps
|
||||||
|
if isDefined "$1" hysteria_down_mbps; then
|
||||||
|
local hysteria_down_mbps
|
||||||
|
config_get hysteria_down_mbps "$1" hysteria_down_mbps ""
|
||||||
|
uci_remove "$CONF" "$1" hysteria_down_mbps
|
||||||
|
uci_set "$CONF" "$1" brutal_down_mbps "$hysteria_down_mbps"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# hysteria_ignore_client_bandwidth -> brutal_ignore_client_bandwidth
|
||||||
|
if isDefined "$1" hysteria_ignore_client_bandwidth; then
|
||||||
|
local hysteria_ignore_client_bandwidth
|
||||||
|
config_get hysteria_ignore_client_bandwidth "$1" hysteria_ignore_client_bandwidth ""
|
||||||
|
uci_remove "$CONF" "$1" hysteria_ignore_client_bandwidth
|
||||||
|
uci_set "$CONF" "$1" brutal_ignore_client_bandwidth "$hysteria_ignore_client_bandwidth"
|
||||||
|
fi
|
||||||
|
|
||||||
# plugin -> plugin_type
|
# plugin -> plugin_type
|
||||||
if isDefined "$1" plugin; then
|
if isDefined "$1" plugin; then
|
||||||
local plugin
|
local plugin
|
||||||
|
|||||||
@@ -573,11 +573,13 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
|||||||
"reduce-rtt": strToBool(cfg.tuic_reduce_rtt),
|
"reduce-rtt": strToBool(cfg.tuic_reduce_rtt),
|
||||||
"request-timeout": strToInt(cfg.tuic_request_timeout) || null,
|
"request-timeout": strToInt(cfg.tuic_request_timeout) || null,
|
||||||
|
|
||||||
|
/* Brutal */
|
||||||
|
up: strToInt(cfg.brutal_up_mbps),
|
||||||
|
down: strToInt(cfg.brutal_down_mbps),
|
||||||
|
|
||||||
/* Hysteria / Hysteria2 */
|
/* Hysteria / Hysteria2 */
|
||||||
ports: isEmpty(cfg.hysteria_ports) ? null : join(',', cfg.hysteria_ports),
|
ports: isEmpty(cfg.hysteria_ports) ? null : join(',', cfg.hysteria_ports),
|
||||||
"hop-interval": strToInt(cfg.hysteria_hop_interval), // @DEBUG ERROR data type *utils.IntRanges[uint16]
|
"hop-interval": strToInt(cfg.hysteria_hop_interval), // @DEBUG ERROR data type *utils.IntRanges[uint16]
|
||||||
up: cfg.hysteria_up_mbps ? cfg.hysteria_up_mbps + ' Mbps' : null,
|
|
||||||
down: cfg.hysteria_down_mbps ? cfg.hysteria_down_mbps + ' Mbps' : null,
|
|
||||||
obfs: cfg.hysteria_obfs_type,
|
obfs: cfg.hysteria_obfs_type,
|
||||||
"obfs-password": cfg.hysteria_obfs_password,
|
"obfs-password": cfg.hysteria_obfs_password,
|
||||||
"obfs-min-packet-size": strToInt(cfg.hysteria_obfs_min_packet_size),
|
"obfs-min-packet-size": strToInt(cfg.hysteria_obfs_min_packet_size),
|
||||||
@@ -682,6 +684,10 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
|||||||
config: cfg.tls_ech_config,
|
config: cfg.tls_ech_config,
|
||||||
"query-server-name": cfg.tls_ech_query_server_name
|
"query-server-name": cfg.tls_ech_query_server_name
|
||||||
} : null,
|
} : null,
|
||||||
|
"jls-opts": cfg.tls_jls === '1' ? {
|
||||||
|
"username": cfg.tls_jls_username,
|
||||||
|
"password": cfg.tls_jls_password
|
||||||
|
} : null,
|
||||||
"reality-opts": cfg.tls_reality === '1' ? {
|
"reality-opts": cfg.tls_reality === '1' ? {
|
||||||
"public-key": cfg.tls_reality_public_key,
|
"public-key": cfg.tls_reality_public_key,
|
||||||
"short-id": cfg.tls_reality_short_id,
|
"short-id": cfg.tls_reality_short_id,
|
||||||
|
|||||||
@@ -292,10 +292,12 @@ export function parseListener(cfg) {
|
|||||||
"authentication-timeout": durationToSecond(cfg.tuic_authentication_timeout),
|
"authentication-timeout": durationToSecond(cfg.tuic_authentication_timeout),
|
||||||
"max-udp-relay-packet-size": strToInt(cfg.tuic_max_udp_relay_packet_size),
|
"max-udp-relay-packet-size": strToInt(cfg.tuic_max_udp_relay_packet_size),
|
||||||
|
|
||||||
|
/* Brutal */
|
||||||
|
up: strToInt(cfg.brutal_up_mbps),
|
||||||
|
down: strToInt(cfg.brutal_down_mbps),
|
||||||
|
"ignore-client-bandwidth": strToBool(cfg.brutal_ignore_client_bandwidth),
|
||||||
|
|
||||||
/* Hysteria2 */
|
/* Hysteria2 */
|
||||||
up: strToInt(cfg.hysteria_up_mbps),
|
|
||||||
down: strToInt(cfg.hysteria_down_mbps),
|
|
||||||
"ignore-client-bandwidth": strToBool(cfg.hysteria_ignore_client_bandwidth),
|
|
||||||
obfs: cfg.hysteria_obfs_type,
|
obfs: cfg.hysteria_obfs_type,
|
||||||
"obfs-password": cfg.hysteria_obfs_password,
|
"obfs-password": cfg.hysteria_obfs_password,
|
||||||
"obfs-min-packet-size": strToInt(cfg.hysteria_obfs_min_packet_size),
|
"obfs-min-packet-size": strToInt(cfg.hysteria_obfs_min_packet_size),
|
||||||
@@ -417,7 +419,7 @@ export function parseListener(cfg) {
|
|||||||
|
|
||||||
/* TLS fields */
|
/* TLS fields */
|
||||||
...(cfg.allow_insecure === '1' ? { "allow-insecure": true } : cfg.tls === '1' ? {
|
...(cfg.allow_insecure === '1' ? { "allow-insecure": true } : cfg.tls === '1' ? {
|
||||||
alpn: cfg.tls_alpn,
|
alpn: cfg.plugin_type in ['jls'] ? null : cfg.tls_alpn,
|
||||||
...(cfg.tls_reality === '1' ? {
|
...(cfg.tls_reality === '1' ? {
|
||||||
"reality-config": {
|
"reality-config": {
|
||||||
dest: cfg.tls_reality_dest,
|
dest: cfg.tls_reality_dest,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-passwall
|
PKG_NAME:=luci-app-passwall
|
||||||
PKG_VERSION:=26.7.1
|
PKG_VERSION:=26.7.15
|
||||||
PKG_RELEASE:=179
|
PKG_RELEASE:=180
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -441,24 +441,15 @@ o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
|
|||||||
o:depends({ [_n("tls")] = true })
|
o:depends({ [_n("tls")] = true })
|
||||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||||
|
|
||||||
if api.compare_versions(os.date("%Y.%m.%d"), "<", "3026.6.1") then
|
o = s:option(Value, _n("tls_pinSHA256"), translate("TLS Chain Fingerprint (SHA256)"))
|
||||||
o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
|
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||||
o.default = "0"
|
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
|
||||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertSha256(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
|
||||||
end
|
|
||||||
|
|
||||||
if api.compare_versions(xray_version, ">=", "26.1.31") then
|
o = s:option(Value, _n("tls_CertByName"), translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
|
||||||
o = s:option(Value, _n("tls_pinSHA256"), translate("TLS Chain Fingerprint (SHA256)"))
|
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
|
||||||
o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
|
|
||||||
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertSha256(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
|
|
||||||
|
|
||||||
o = s:option(Value, _n("tls_CertByName"), translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
|
|
||||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
|
||||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
|
||||||
end
|
|
||||||
|
|
||||||
o = s:option(Flag, _n("tls_certificate"), translate("TLS Certificate (PEM)"))
|
o = s:option(Flag, _n("tls_certificate"), translate("TLS Certificate (PEM)"))
|
||||||
o.default = "0"
|
o.default = "0"
|
||||||
|
|||||||
@@ -152,21 +152,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
security = node.stream_security,
|
security = node.stream_security,
|
||||||
tlsSettings = (node.stream_security == "tls") and {
|
tlsSettings = (node.stream_security == "tls") and {
|
||||||
serverName = node.tls_serverName,
|
serverName = node.tls_serverName,
|
||||||
allowInsecure = (function()
|
|
||||||
if node.tls_pinSHA256 and node.tls_pinSHA256 ~= "" then return nil end
|
|
||||||
if node.tls_allowInsecure == "1" then return true end
|
|
||||||
end)(),
|
|
||||||
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
|
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
|
||||||
pinnedPeerCertSha256 = (function()
|
pinnedPeerCertSha256 = node.tls_pinSHA256,
|
||||||
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
|
verifyPeerCertByName = node.tls_CertByName,
|
||||||
if not node.tls_pinSHA256 then return "" end
|
|
||||||
return node.tls_pinSHA256
|
|
||||||
end)(),
|
|
||||||
verifyPeerCertByName = (function()
|
|
||||||
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
|
|
||||||
if not node.tls_CertByName then return "" end
|
|
||||||
return node.tls_CertByName
|
|
||||||
end)(),
|
|
||||||
echConfigList = (node.ech == "1") and node.ech_config or nil,
|
echConfigList = (node.ech == "1") and node.ech_config or nil,
|
||||||
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
|
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
|
||||||
certificate = api.split(node.tls_certificate_pem, "\n"),
|
certificate = api.split(node.tls_certificate_pem, "\n"),
|
||||||
@@ -820,7 +808,8 @@ function gen_config_server(node)
|
|||||||
serverNames = node.reality_serverNames or {},
|
serverNames = node.reality_serverNames or {},
|
||||||
privateKey = node.reality_private_key,
|
privateKey = node.reality_private_key,
|
||||||
shortIds = node.reality_shortId or "",
|
shortIds = node.reality_shortId or "",
|
||||||
mldsa65Seed = (node.use_mldsa65Seed == "1") and node.reality_mldsa65Seed or nil
|
mldsa65Seed = (node.use_mldsa65Seed == "1") and node.reality_mldsa65Seed or nil,
|
||||||
|
minClientVer = "1.0.0"
|
||||||
} or nil
|
} or nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1313,6 +1313,8 @@ local current_node = map:get(section)
|
|||||||
opt.set(dom_prefix + 'utls', true);
|
opt.set(dom_prefix + 'utls', true);
|
||||||
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
|
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
|
||||||
}
|
}
|
||||||
|
opt.set(dom_prefix + 'ech', !!queryParam.ech);
|
||||||
|
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
|
||||||
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
|
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
|
||||||
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
|
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
|
||||||
opt.set(
|
opt.set(
|
||||||
|
|||||||
@@ -1069,6 +1069,10 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
result.reality_publicKey = params.pbk or nil
|
result.reality_publicKey = params.pbk or nil
|
||||||
result.reality_shortId = params.sid or nil
|
result.reality_shortId = params.sid or nil
|
||||||
end
|
end
|
||||||
|
if params.ech and params.ech ~= "" then
|
||||||
|
result.ech = "1"
|
||||||
|
result.ech_config = params.ech
|
||||||
|
end
|
||||||
result.tls_pinSHA256 = params.pcs
|
result.tls_pinSHA256 = params.pcs
|
||||||
result.tls_CertByName = params.vcn
|
result.tls_CertByName = params.vcn
|
||||||
result.tls_allowInsecure = params.allowinsecure or params.allowInsecure or params.insecure
|
result.tls_allowInsecure = params.allowinsecure or params.allowInsecure or params.insecure
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-passwall2
|
PKG_NAME:=luci-app-passwall2
|
||||||
PKG_VERSION:=26.7.12
|
PKG_VERSION:=26.7.16
|
||||||
PKG_RELEASE:=62
|
PKG_RELEASE:=63
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
function is_json(str) {
|
||||||
|
try {
|
||||||
|
const result = JSON.parse(str);
|
||||||
|
return typeof result === 'object' && result !== null;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_timehhmm(str) {
|
||||||
|
const match = String(str).match(/^(\d?\d):(\d\d)$/);
|
||||||
|
if (match) {
|
||||||
|
const hour = parseInt(match[1], 10);
|
||||||
|
const minute = parseInt(match[2], 10);
|
||||||
|
if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof cbi_validators !== 'undefined' && cbi_validators !== null) {
|
||||||
|
cbi_validators['json'] = function() {
|
||||||
|
return is_json(this);
|
||||||
|
}
|
||||||
|
cbi_validators['timehhmm'] = function() {
|
||||||
|
return is_timehhmm(this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
L.require('validation').then(function(validation) {
|
||||||
|
validation.types['json'] = function() {
|
||||||
|
return this.assert(is_json(this.value), _('Must be JSON text!'));
|
||||||
|
}
|
||||||
|
validation.types['timehhmm'] = function() {
|
||||||
|
return this.assert(is_timehhmm(this.value), _('valid time (hh:mm)'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -114,6 +114,8 @@ function index()
|
|||||||
|
|
||||||
--[[geoview]]
|
--[[geoview]]
|
||||||
entry({"admin", "services", appname, "geo_view"}, call("geo_view")).leaf = true
|
entry({"admin", "services", appname, "geo_view"}, call("geo_view")).leaf = true
|
||||||
|
|
||||||
|
entry({"admin", "services", appname, "fetch_certsha256"}, call("fetch_certsha256")).leaf = true
|
||||||
end
|
end
|
||||||
|
|
||||||
function check_site(host, port)
|
function check_site(host, port)
|
||||||
@@ -995,3 +997,17 @@ function flush_set()
|
|||||||
http.redirect(api.url("log"))
|
http.redirect(api.url("log"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function fetch_certsha256()
|
||||||
|
local id = http.formvalue("id") or ""
|
||||||
|
local address = (id ~= "") and uci:get(appname, id, "address") or ""
|
||||||
|
local port = (id ~= "") and uci:get(appname, id, "port") or 0
|
||||||
|
local sni = (id ~= "") and uci:get(appname, id, "tls_serverName") or ""
|
||||||
|
sni = (sni ~= "") and sni or address
|
||||||
|
if address == "" or port == 0 then
|
||||||
|
http_write_json_error()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local data = api.fetch_cert_sha256(address, port, sni, 5)
|
||||||
|
http_write_json(data ~= "" and { code = 1, data = data } or { code = 0 })
|
||||||
|
end
|
||||||
@@ -2,6 +2,8 @@ local api = require "luci.passwall2.api"
|
|||||||
local appname = api.appname
|
local appname = api.appname
|
||||||
local sys = api.sys
|
local sys = api.sys
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -13,7 +15,8 @@ o.rmempty = false
|
|||||||
o.default = false
|
o.default = false
|
||||||
|
|
||||||
-- [[ ACLs Settings ]]--
|
-- [[ ACLs Settings ]]--
|
||||||
s = m:section(TypedSection, "acl_rule")
|
local cfgname = "acl_rule"
|
||||||
|
s = m:section(TypedSection, cfgname)
|
||||||
s.template = "cbi/tblsection"
|
s.template = "cbi/tblsection"
|
||||||
s.sortable = true
|
s.sortable = true
|
||||||
s.anonymous = true
|
s.anonymous = true
|
||||||
@@ -65,4 +68,9 @@ i.cfgvalue = function(t, n)
|
|||||||
return v
|
return v
|
||||||
end
|
end
|
||||||
|
|
||||||
return m
|
local sortable = Template(appname .. "/cbi/sortable")
|
||||||
|
sortable.api = api
|
||||||
|
sortable.target_cfgname = cfgname
|
||||||
|
m:append(sortable)
|
||||||
|
|
||||||
|
return api.return_map(m)
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
local api = require "luci.passwall2.api"
|
local api = require "luci.passwall2.api"
|
||||||
local appname = api.appname
|
local appname = api.appname
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -379,4 +381,4 @@ for k, v in pairs(nodes_table) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -2,6 +2,8 @@ local api = require "luci.passwall2.api"
|
|||||||
local com = require "luci.passwall2.com"
|
local com = require "luci.passwall2.com"
|
||||||
local appname = api.appname
|
local appname = api.appname
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -28,4 +30,4 @@ o.cfgvalue = function(t, n)
|
|||||||
return string.format('<font color="red">%s</font>', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."))
|
return string.format('<font color="red">%s</font>', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."))
|
||||||
end
|
end
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
local api = require "luci.passwall2.api"
|
local api = require "luci.passwall2.api"
|
||||||
local appname = "passwall2"
|
local appname = api.appname
|
||||||
local fs = api.fs
|
local fs = api.fs
|
||||||
local uci = api.uci
|
local uci = api.uci
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ datatypes = api.datatypes
|
|||||||
has_singbox = api.finded_com("sing-box")
|
has_singbox = api.finded_com("sing-box")
|
||||||
has_xray = api.finded_com("xray")
|
has_xray = api.finded_com("xray")
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -387,4 +389,4 @@ footer.shunt_list = api.jsonc.stringify(shunt_list)
|
|||||||
m:append(footer)
|
m:append(footer)
|
||||||
m:append(Template(appname .. "/global/status_bottom"))
|
m:append(Template(appname .. "/global/status_bottom"))
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -15,6 +15,8 @@ for k, e in ipairs(api.get_valid_nodes()) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -100,7 +102,8 @@ end
|
|||||||
o:depends("health_check_type", "passwall_logic")
|
o:depends("health_check_type", "passwall_logic")
|
||||||
|
|
||||||
-- [[ Balancing Settings ]]--
|
-- [[ Balancing Settings ]]--
|
||||||
s = m:section(TypedSection, "haproxy_config", translate("Node List"))
|
local cfgname = "haproxy_config"
|
||||||
|
s = m:section(TypedSection, cfgname, translate("Node List"))
|
||||||
s.description = "<font color='red'>" ..
|
s.description = "<font color='red'>" ..
|
||||||
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") .. "<br>" ..
|
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") .. "<br>" ..
|
||||||
translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") .. "</font>"
|
translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") .. "</font>"
|
||||||
@@ -176,6 +179,11 @@ o:value(0, translate("Primary"))
|
|||||||
o:value(1, translate("Standby"))
|
o:value(1, translate("Standby"))
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
|
|
||||||
|
local sortable = Template(appname .. "/cbi/sortable")
|
||||||
|
sortable.api = api
|
||||||
|
sortable.target_cfgname = cfgname
|
||||||
|
m:append(sortable)
|
||||||
|
|
||||||
m:append(Template(appname .. "/haproxy/js"))
|
m:append(Template(appname .. "/haproxy/js"))
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
api = require "luci.passwall2.api"
|
api = require "luci.passwall2.api"
|
||||||
appname = api.appname
|
appname = api.appname
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname, translate("Node Config"))
|
m = Map(appname, translate("Node Config"))
|
||||||
m.redirect = api.url()
|
m.redirect = api.url()
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
@@ -94,7 +96,6 @@ local footer = Template(appname .. "/node_config/footer")
|
|||||||
footer.api = api
|
footer.api = api
|
||||||
footer.config = m.config
|
footer.config = m.config
|
||||||
footer.section = arg[1]
|
footer.section = arg[1]
|
||||||
|
|
||||||
m:append(footer)
|
m:append(footer)
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -3,6 +3,8 @@ local appname = api.appname
|
|||||||
local datatypes = api.datatypes
|
local datatypes = api.datatypes
|
||||||
local sys = api.sys
|
local sys = api.sys
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -33,4 +35,4 @@ s:append(Template(appname .. "/node_list/link_add_node"))
|
|||||||
|
|
||||||
m:append(Template(appname .. "/node_list/node_list"))
|
m:append(Template(appname .. "/node_list/node_list"))
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -38,6 +38,8 @@ if has_hysteria2 then
|
|||||||
table.insert(hysteria2_type, s)
|
table.insert(hysteria2_type, s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -124,7 +126,8 @@ o.cfgvalue = function(self, section)
|
|||||||
translate("Manual subscription All"))
|
translate("Manual subscription All"))
|
||||||
end
|
end
|
||||||
|
|
||||||
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
|
local cfgname = "subscribe_list"
|
||||||
|
s = m:section(TypedSection, cfgname, "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
|
||||||
s.addremove = true
|
s.addremove = true
|
||||||
s.anonymous = true
|
s.anonymous = true
|
||||||
s.sortable = true
|
s.sortable = true
|
||||||
@@ -144,7 +147,7 @@ o.validate = function(self, value, section)
|
|||||||
return nil, translate("Remark cannot be empty.")
|
return nil, translate("Remark cannot be empty.")
|
||||||
end
|
end
|
||||||
local duplicate = false
|
local duplicate = false
|
||||||
m.uci:foreach(appname, "subscribe_list", function(e)
|
m.uci:foreach(appname, cfgname, function(e)
|
||||||
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
|
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
|
||||||
duplicate = true
|
duplicate = true
|
||||||
return false
|
return false
|
||||||
@@ -219,6 +222,11 @@ o.cfgvalue = function(self, section)
|
|||||||
section, translate("Manual subscription"))
|
section, translate("Manual subscription"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local sortable = Template(appname .. "/cbi/sortable")
|
||||||
|
sortable.api = api
|
||||||
|
sortable.target_cfgname = cfgname
|
||||||
|
m:append(sortable)
|
||||||
|
|
||||||
m:append(Template(appname .. "/node_subscribe/js"))
|
m:append(Template(appname .. "/node_subscribe/js"))
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
local api = require "luci.passwall2.api"
|
local api = require "luci.passwall2.api"
|
||||||
local appname = api.appname
|
local appname = api.appname
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
m.redirect = api.url("node_subscribe")
|
m.redirect = api.url("node_subscribe")
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
@@ -255,13 +257,7 @@ for t = 0, 23 do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
o.default = "0:00"
|
o.default = "0:00"
|
||||||
o.validate = function(self, value)
|
o.datatype = "timehhmm"
|
||||||
local b = api.is_timehhmm(value)
|
|
||||||
if b then
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
o:depends("update_week_mode", "0")
|
o:depends("update_week_mode", "0")
|
||||||
o:depends("update_week_mode", "1")
|
o:depends("update_week_mode", "1")
|
||||||
o:depends("update_week_mode", "2")
|
o:depends("update_week_mode", "2")
|
||||||
@@ -336,4 +332,4 @@ o.description = translate("Some subscription providers may require a hardware id
|
|||||||
translate("Enabling this option will send a hashed HWID when updating subscriptions.") .. "<br>" ..
|
translate("Enabling this option will send a hashed HWID when updating subscriptions.") .. "<br>" ..
|
||||||
translate("This may reveal limited hardware information. Enable only if you trust your provider.")
|
translate("This may reveal limited hardware information. Enable only if you trust your provider.")
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -8,6 +8,8 @@ local port_validate = function(self, value, t)
|
|||||||
return value:gsub("-", ":")
|
return value:gsub("-", ":")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -53,13 +55,7 @@ for index, value in ipairs({"stop", "start", "restart"}) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
o.default = "0:00"
|
o.default = "0:00"
|
||||||
o.validate = function(self, value)
|
o.datatype = "timehhmm"
|
||||||
local b = api.is_timehhmm(value)
|
|
||||||
if b then
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
o:depends(value .. "_week_mode", "0")
|
o:depends(value .. "_week_mode", "0")
|
||||||
o:depends(value .. "_week_mode", "1")
|
o:depends(value .. "_week_mode", "1")
|
||||||
o:depends(value .. "_week_mode", "2")
|
o:depends(value .. "_week_mode", "2")
|
||||||
@@ -312,4 +308,4 @@ if has_singbox then
|
|||||||
o.default = 0
|
o.default = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
local api = require "luci.passwall2.api"
|
local api = require "luci.passwall2.api"
|
||||||
local appname = api.appname
|
local appname = api.appname
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -59,13 +61,7 @@ for t = 0, 23 do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
o.default = "0:00"
|
o.default = "0:00"
|
||||||
o.validate = function(self, value)
|
o.datatype = "timehhmm"
|
||||||
local b = api.is_timehhmm(value)
|
|
||||||
if b then
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
o:depends("update_week_mode", "0")
|
o:depends("update_week_mode", "0")
|
||||||
o:depends("update_week_mode", "1")
|
o:depends("update_week_mode", "1")
|
||||||
o:depends("update_week_mode", "2")
|
o:depends("update_week_mode", "2")
|
||||||
@@ -92,7 +88,8 @@ end
|
|||||||
|
|
||||||
s:append(Template(appname .. "/rule/rule_version"))
|
s:append(Template(appname .. "/rule/rule_version"))
|
||||||
|
|
||||||
s = m:section(TypedSection, "shunt_rules", "Sing-Box/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
|
local cfgname = "shunt_rules"
|
||||||
|
s = m:section(TypedSection, cfgname, "Sing-Box/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
|
||||||
s.template = "cbi/tblsection"
|
s.template = "cbi/tblsection"
|
||||||
s.anonymous = false
|
s.anonymous = false
|
||||||
s.addremove = true
|
s.addremove = true
|
||||||
@@ -113,4 +110,9 @@ end
|
|||||||
|
|
||||||
o = s:option(DummyValue, "remarks", translate("Remarks"))
|
o = s:option(DummyValue, "remarks", translate("Remarks"))
|
||||||
|
|
||||||
return m
|
local sortable = Template(appname .. "/cbi/sortable")
|
||||||
|
sortable.api = api
|
||||||
|
sortable.target_cfgname = cfgname
|
||||||
|
m:append(sortable)
|
||||||
|
|
||||||
|
return api.return_map(m)
|
||||||
@@ -2,6 +2,8 @@ local api = require "luci.passwall2.api"
|
|||||||
local appname = api.appname
|
local appname = api.appname
|
||||||
local datatypes = api.datatypes
|
local datatypes = api.datatypes
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule"))
|
m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule"))
|
||||||
m.redirect = api.url("rule")
|
m.redirect = api.url("rule")
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
@@ -248,4 +250,4 @@ ip_list.description = "<br /><ul>"
|
|||||||
|
|
||||||
o = s:option(Flag, "invert", "invert", translate("Invert match result.") .. " " .. translate("Only support Sing-Box."))
|
o = s:option(Flag, "invert", "invert", translate("Invert match result.") .. " " .. translate("Only support Sing-Box."))
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
local api = require "luci.passwall2.api"
|
local api = require "luci.passwall2.api"
|
||||||
local appname = api.appname
|
local appname = api.appname
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map(appname)
|
m = Map(appname)
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
|
|
||||||
@@ -80,23 +82,14 @@ o = s:option(Flag, "enable_autoswitch", translate("Auto Switch"))
|
|||||||
o.default = 0
|
o.default = 0
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
|
|
||||||
o = s:option(Value, "autoswitch_testing_time", translate("How often to test"), translate("Units:seconds"))
|
o = s:option(ListValue, "backup_node_add_mode", translate("Backup Node Addition Method"))
|
||||||
o.datatype = "min(10)"
|
|
||||||
o.default = 30
|
|
||||||
o:depends("enable_autoswitch", true)
|
o:depends("enable_autoswitch", true)
|
||||||
|
o.default = "manual"
|
||||||
|
o:value("manual", translate("Manual"))
|
||||||
|
o:value("batch", translate("Batch"))
|
||||||
|
|
||||||
o = s:option(Value, "autoswitch_connect_timeout", translate("Timeout seconds"), translate("Units:seconds"))
|
|
||||||
o.datatype = "min(1)"
|
|
||||||
o.default = 3
|
|
||||||
o:depends("enable_autoswitch", true)
|
|
||||||
|
|
||||||
o = s:option(Value, "autoswitch_retry_num", translate("Timeout retry num"))
|
|
||||||
o.datatype = "min(1)"
|
|
||||||
o.default = 1
|
|
||||||
o:depends("enable_autoswitch", true)
|
|
||||||
|
|
||||||
o = s:option(DynamicList, "autoswitch_backup_node", translate("List of backup nodes"))
|
o = s:option(DynamicList, "autoswitch_backup_node", translate("List of backup nodes"))
|
||||||
o:depends("enable_autoswitch", true)
|
o:depends("backup_node_add_mode", "manual")
|
||||||
o.template = appname .. "/cbi/nodes_dynamiclist"
|
o.template = appname .. "/cbi/nodes_dynamiclist"
|
||||||
o.group = {}
|
o.group = {}
|
||||||
o.write = function(self, section, value)
|
o.write = function(self, section, value)
|
||||||
@@ -118,6 +111,44 @@ for i, v in pairs(nodes_table) do
|
|||||||
socks_node.group[#socks_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
socks_node.group[#socks_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
o = s:option(MultiValue, "backup_node_group", translate("Select Group"))
|
||||||
|
o:depends("backup_node_add_mode", "batch")
|
||||||
|
o.widget = "checkbox"
|
||||||
|
o:value("default", translate("default"))
|
||||||
|
local groups = {}
|
||||||
|
m.uci:foreach(appname, "nodes", function(s)
|
||||||
|
if s.group and s.group ~= "" then
|
||||||
|
groups[s.group] = true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
for k, v in pairs(groups) do
|
||||||
|
o:value(api.UrlEncode(k), k)
|
||||||
|
end
|
||||||
|
|
||||||
|
o = s:option(Value, "backup_node_match_rule", translate("Node Matching Rules"))
|
||||||
|
o:depends("backup_node_add_mode", "batch")
|
||||||
|
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
|
||||||
|
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
|
||||||
|
descrStr = descrStr .. "Conditions are joined by <code>&&</code> (AND), and their order does not affect the result.<br>"
|
||||||
|
descrStr = descrStr .. "Multiple groups can be separated by <code>||</code> (OR), matching succeeds if any group matches.<br>"
|
||||||
|
descrStr = descrStr .. "Example: <code>A && B || C && D</code> means (A AND B) OR (C AND D)."
|
||||||
|
o.description = translate(descrStr)
|
||||||
|
|
||||||
|
o = s:option(Value, "autoswitch_testing_time", translate("How often to test"), translate("Units:seconds"))
|
||||||
|
o.datatype = "min(10)"
|
||||||
|
o.default = 30
|
||||||
|
o:depends("enable_autoswitch", true)
|
||||||
|
|
||||||
|
o = s:option(Value, "autoswitch_connect_timeout", translate("Timeout seconds"), translate("Units:seconds"))
|
||||||
|
o.datatype = "min(1)"
|
||||||
|
o.default = 3
|
||||||
|
o:depends("enable_autoswitch", true)
|
||||||
|
|
||||||
|
o = s:option(Value, "autoswitch_retry_num", translate("Timeout retry num"))
|
||||||
|
o.datatype = "min(1)"
|
||||||
|
o.default = 1
|
||||||
|
o:depends("enable_autoswitch", true)
|
||||||
|
|
||||||
o = s:option(Flag, "autoswitch_restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
|
o = s:option(Flag, "autoswitch_restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
|
||||||
o:depends("enable_autoswitch", true)
|
o:depends("enable_autoswitch", true)
|
||||||
|
|
||||||
@@ -134,6 +165,6 @@ o:depends("enable_autoswitch", true)
|
|||||||
|
|
||||||
o = s:option(DummyValue, "btn")
|
o = s:option(DummyValue, "btn")
|
||||||
o.template = appname .. "/socks_auto_switch/btn"
|
o.template = appname .. "/socks_auto_switch/btn"
|
||||||
o:depends("enable_autoswitch", true)
|
o:depends("backup_node_add_mode", "manual")
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -442,22 +442,15 @@ o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
|
|||||||
o:depends({ [_n("tls")] = true })
|
o:depends({ [_n("tls")] = true })
|
||||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||||
|
|
||||||
if api.compare_versions(os.date("%Y.%m.%d"), "<", "3026.6.1") then
|
o = s:option(Value, _n("tls_pinSHA256"), translate("TLS Chain Fingerprint (SHA256)"))
|
||||||
o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
|
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||||
o.default = "0"
|
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
|
||||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertSha256(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
|
||||||
end
|
|
||||||
|
|
||||||
if api.compare_versions(xray_version, ">=", "26.1.31") then
|
o = s:option(Value, _n("tls_CertByName"), translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
|
||||||
o = s:option(Value, _n("tls_pinSHA256"), translate("TLS Chain Fingerprint (SHA256)"))
|
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
|
||||||
|
|
||||||
o = s:option(Value, _n("tls_CertByName"), translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
|
|
||||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
|
||||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
|
||||||
end
|
|
||||||
|
|
||||||
o = s:option(Flag, _n("tls_certificate"), translate("TLS Certificate (PEM)"))
|
o = s:option(Flag, _n("tls_certificate"), translate("TLS Certificate (PEM)"))
|
||||||
o.default = "0"
|
o.default = "0"
|
||||||
@@ -671,6 +664,14 @@ o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in J
|
|||||||
o:depends({ [_n("use_xhttp_extra")] = true })
|
o:depends({ [_n("use_xhttp_extra")] = true })
|
||||||
o.rows = 10
|
o.rows = 10
|
||||||
o.wrap = "off"
|
o.wrap = "off"
|
||||||
|
o.datatype = "json"
|
||||||
|
local o_validate = o.validate
|
||||||
|
o.validate = function(self, value)
|
||||||
|
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||||
|
local v = o_validate(self, value)
|
||||||
|
if v then return v end
|
||||||
|
return nil, "XHTTP Extra " .. translate("Must be JSON text!")
|
||||||
|
end
|
||||||
o.custom_cfgvalue = function(self, section, value)
|
o.custom_cfgvalue = function(self, section, value)
|
||||||
local raw = m:get(section, "xhttp_extra")
|
local raw = m:get(section, "xhttp_extra")
|
||||||
if raw then
|
if raw then
|
||||||
@@ -693,14 +694,6 @@ o.custom_write = function(self, section, value)
|
|||||||
m:del(section, "download_address")
|
m:del(section, "download_address")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
o.validate = function(self, value)
|
|
||||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
|
||||||
if api.jsonc.parse(value) then
|
|
||||||
return value
|
|
||||||
else
|
|
||||||
return nil, "XHTTP Extra " .. translate("Must be JSON text!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
o.custom_remove = function(self, section, value)
|
o.custom_remove = function(self, section, value)
|
||||||
m:del(section, "xhttp_extra")
|
m:del(section, "xhttp_extra")
|
||||||
m:del(section, "download_address")
|
m:del(section, "download_address")
|
||||||
@@ -758,6 +751,14 @@ o.rows = 10
|
|||||||
o.wrap = "off"
|
o.wrap = "off"
|
||||||
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
|
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
|
||||||
translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
|
translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
|
||||||
|
o.datatype = "json"
|
||||||
|
local o_validate = o.validate
|
||||||
|
o.validate = function(self, value)
|
||||||
|
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||||
|
local v = o_validate(self, value)
|
||||||
|
if v then return v end
|
||||||
|
return nil, "FinalMask " .. translate("Must be JSON text!")
|
||||||
|
end
|
||||||
o.custom_cfgvalue = function(self, section, value)
|
o.custom_cfgvalue = function(self, section, value)
|
||||||
local raw = m:get(section, "finalmask")
|
local raw = m:get(section, "finalmask")
|
||||||
if raw then
|
if raw then
|
||||||
@@ -767,14 +768,6 @@ end
|
|||||||
o.custom_write = function(self, section, value)
|
o.custom_write = function(self, section, value)
|
||||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||||
end
|
end
|
||||||
o.validate = function(self, value)
|
|
||||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
|
||||||
if api.jsonc.parse(value) then
|
|
||||||
return value
|
|
||||||
else
|
|
||||||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[Fast Open]]
|
--[[Fast Open]]
|
||||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
local api = require "luci.passwall2.api"
|
local api = require "luci.passwall2.api"
|
||||||
|
local appname = api.appname
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map("passwall2_server", translate("Server-Side"))
|
m = Map("passwall2_server", translate("Server-Side"))
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
@@ -10,7 +13,8 @@ t.addremove = false
|
|||||||
e = t:option(Flag, "enable", translate("Enable"))
|
e = t:option(Flag, "enable", translate("Enable"))
|
||||||
e.rmempty = false
|
e.rmempty = false
|
||||||
|
|
||||||
t = m:section(TypedSection, "user", translate("Users Manager"))
|
local cfgname = "user"
|
||||||
|
t = m:section(TypedSection, cfgname, translate("Users Manager"))
|
||||||
t.anonymous = true
|
t.anonymous = true
|
||||||
t.addremove = true
|
t.addremove = true
|
||||||
t.sortable = true
|
t.sortable = true
|
||||||
@@ -85,8 +89,14 @@ e = t:option(Flag, "log", translate("Log"))
|
|||||||
e.default = "1"
|
e.default = "1"
|
||||||
e.rmempty = false
|
e.rmempty = false
|
||||||
|
|
||||||
m:append(Template("passwall2/server/log"))
|
local sortable = Template(appname .. "/cbi/sortable")
|
||||||
|
sortable.api = api
|
||||||
|
sortable.appname = m.config
|
||||||
|
sortable.target_cfgname = cfgname
|
||||||
|
m:append(sortable)
|
||||||
|
|
||||||
m:append(Template("passwall2/server/users_list_status"))
|
m:append(Template(appname .. "/server/log"))
|
||||||
return m
|
|
||||||
|
|
||||||
|
m:append(Template(appname .. "/server/users_list_status"))
|
||||||
|
|
||||||
|
return api.return_map(m)
|
||||||
@@ -129,12 +129,12 @@ o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
|||||||
o.rows = 10
|
o.rows = 10
|
||||||
o.wrap = "off"
|
o.wrap = "off"
|
||||||
o:depends({ [_n("custom")] = true })
|
o:depends({ [_n("custom")] = true })
|
||||||
o.validate = function(self, value, t)
|
o.datatype = "json"
|
||||||
if value and api.jsonc.parse(value) then
|
local o_validate = o.validate
|
||||||
return value
|
o.validate = function(self, value)
|
||||||
else
|
local v = o_validate(self, value)
|
||||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
if v then return v end
|
||||||
end
|
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||||
end
|
end
|
||||||
o.custom_cfgvalue = function(self, section, value)
|
o.custom_cfgvalue = function(self, section, value)
|
||||||
local config_str = m:get(section, "config_str")
|
local config_str = m:get(section, "config_str")
|
||||||
|
|||||||
@@ -412,6 +412,7 @@ o = s:option(TextValue, _n("finalmask"), "FinalMask JSON")
|
|||||||
o:depends({ [_n("use_finalmask")] = true })
|
o:depends({ [_n("use_finalmask")] = true })
|
||||||
o.rows = 10
|
o.rows = 10
|
||||||
o.wrap = "off"
|
o.wrap = "off"
|
||||||
|
o.datatype = "json"
|
||||||
o.custom_cfgvalue = function(self, section, value)
|
o.custom_cfgvalue = function(self, section, value)
|
||||||
local raw = m:get(section, "finalmask")
|
local raw = m:get(section, "finalmask")
|
||||||
if raw then
|
if raw then
|
||||||
@@ -421,14 +422,6 @@ end
|
|||||||
o.custom_write = function(self, section, value)
|
o.custom_write = function(self, section, value)
|
||||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||||
end
|
end
|
||||||
o.validate = function(self, value)
|
|
||||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
|
||||||
if api.jsonc.parse(value) then
|
|
||||||
return value
|
|
||||||
else
|
|
||||||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[acceptProxyProtocol]]
|
--[[acceptProxyProtocol]]
|
||||||
o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
|
o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
|
||||||
@@ -523,16 +516,16 @@ for _, d in ipairs(netdev_list) do
|
|||||||
o:value(d.name, d.label)
|
o:value(d.name, d.label)
|
||||||
end
|
end
|
||||||
|
|
||||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||||
o.rows = 10
|
o.rows = 10
|
||||||
o.wrap = "off"
|
o.wrap = "off"
|
||||||
o:depends({ [_n("custom")] = true })
|
o:depends({ [_n("custom")] = true })
|
||||||
o.validate = function(self, value, t)
|
o.datatype = "json"
|
||||||
if value and api.jsonc.parse(value) then
|
local o_validate = o.validate
|
||||||
return value
|
o.validate = function(self, value)
|
||||||
else
|
local v = o_validate(self, value)
|
||||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
if v then return v end
|
||||||
end
|
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||||
end
|
end
|
||||||
o.custom_cfgvalue = function(self, section, value)
|
o.custom_cfgvalue = function(self, section, value)
|
||||||
local config_str = m:get(section, "config_str")
|
local config_str = m:get(section, "config_str")
|
||||||
|
|||||||
@@ -489,16 +489,16 @@ for _, d in ipairs(netdev_list) do
|
|||||||
o:value(d.name, d.label)
|
o:value(d.name, d.label)
|
||||||
end
|
end
|
||||||
|
|
||||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||||
o.rows = 10
|
o.rows = 10
|
||||||
o.wrap = "off"
|
o.wrap = "off"
|
||||||
o:depends({ [_n("custom")] = true })
|
o:depends({ [_n("custom")] = true })
|
||||||
o.validate = function(self, value, t)
|
o.datatype = "json"
|
||||||
if value and api.jsonc.parse(value) then
|
local o_validate = o.validate
|
||||||
return value
|
o.validate = function(self, value)
|
||||||
else
|
local v = o_validate(self, value)
|
||||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
if v then return v end
|
||||||
end
|
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||||
end
|
end
|
||||||
o.custom_cfgvalue = function(self, section, value)
|
o.custom_cfgvalue = function(self, section, value)
|
||||||
local config_str = m:get(section, "config_str")
|
local config_str = m:get(section, "config_str")
|
||||||
|
|||||||
@@ -49,16 +49,16 @@ o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
|||||||
o.default = "0"
|
o.default = "0"
|
||||||
o:depends({ [_n("custom")] = false })
|
o:depends({ [_n("custom")] = false })
|
||||||
|
|
||||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||||
o.rows = 10
|
o.rows = 10
|
||||||
o.wrap = "off"
|
o.wrap = "off"
|
||||||
o:depends({ [_n("custom")] = true })
|
o:depends({ [_n("custom")] = true })
|
||||||
o.validate = function(self, value, t)
|
o.datatype = "json"
|
||||||
if value and api.jsonc.parse(value) then
|
local o_validate = o.validate
|
||||||
return value
|
o.validate = function(self, value)
|
||||||
else
|
local v = o_validate(self, value)
|
||||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
if v then return v end
|
||||||
end
|
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||||
end
|
end
|
||||||
o.custom_cfgvalue = function(self, section, value)
|
o.custom_cfgvalue = function(self, section, value)
|
||||||
local config_str = m:get(section, "config_str")
|
local config_str = m:get(section, "config_str")
|
||||||
|
|||||||
@@ -76,16 +76,16 @@ o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
|||||||
o.default = "0"
|
o.default = "0"
|
||||||
o:depends({ [_n("custom")] = false })
|
o:depends({ [_n("custom")] = false })
|
||||||
|
|
||||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||||
o.rows = 10
|
o.rows = 10
|
||||||
o.wrap = "off"
|
o.wrap = "off"
|
||||||
o:depends({ [_n("custom")] = true })
|
o:depends({ [_n("custom")] = true })
|
||||||
o.validate = function(self, value, t)
|
o.datatype = "json"
|
||||||
if value and api.jsonc.parse(value) then
|
local o_validate = o.validate
|
||||||
return value
|
o.validate = function(self, value)
|
||||||
else
|
local v = o_validate(self, value)
|
||||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
if v then return v end
|
||||||
end
|
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||||
end
|
end
|
||||||
o.custom_cfgvalue = function(self, section, value)
|
o.custom_cfgvalue = function(self, section, value)
|
||||||
local config_str = m:get(section, "config_str")
|
local config_str = m:get(section, "config_str")
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ api = require "luci.passwall2.api"
|
|||||||
appname = api.appname
|
appname = api.appname
|
||||||
fs = api.fs
|
fs = api.fs
|
||||||
|
|
||||||
|
api.set_default_cbi()
|
||||||
|
|
||||||
m = Map("passwall2_server", translate("Server Config"))
|
m = Map("passwall2_server", translate("Server Config"))
|
||||||
m.redirect = api.url("server")
|
m.redirect = api.url("server")
|
||||||
api.set_apply_on_parse(m)
|
api.set_apply_on_parse(m)
|
||||||
@@ -54,4 +56,4 @@ footer.config = m.config
|
|||||||
footer.section = arg[1]
|
footer.section = arg[1]
|
||||||
m:append(footer)
|
m:append(footer)
|
||||||
|
|
||||||
return m
|
return api.return_map(m)
|
||||||
@@ -367,8 +367,16 @@ function strToTable(str)
|
|||||||
return loadstring("return " .. str)()
|
return loadstring("return " .. str)()
|
||||||
end
|
end
|
||||||
|
|
||||||
function is_timehhmm(timeStr)
|
function is_json(str)
|
||||||
local hour, minute = string.match(timeStr, "^(%d?%d):(%d%d)$")
|
if str and jsonc.parse(str) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
datatypes.json = is_json
|
||||||
|
|
||||||
|
function is_timehhmm(str)
|
||||||
|
local hour, minute = string.match(str, "^(%d?%d):(%d%d)$")
|
||||||
if hour and minute then
|
if hour and minute then
|
||||||
hour = tonumber(hour)
|
hour = tonumber(hour)
|
||||||
minute = tonumber(minute)
|
minute = tonumber(minute)
|
||||||
@@ -378,6 +386,7 @@ function is_timehhmm(timeStr)
|
|||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
datatypes.timehhmm = is_timehhmm
|
||||||
|
|
||||||
function is_normal_node(e)
|
function is_normal_node(e)
|
||||||
if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
|
if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
|
||||||
@@ -1380,6 +1389,40 @@ function set_apply_on_parse(map)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function set_default_cbi()
|
||||||
|
local cbi = require "luci.cbi"
|
||||||
|
if true then
|
||||||
|
--TextValue
|
||||||
|
local TextValue = cbi.TextValue
|
||||||
|
local original_init = TextValue.__init__
|
||||||
|
function TextValue.__init__(self, ...)
|
||||||
|
original_init(self, ...)
|
||||||
|
self.template = appname .. "/cbi/tvalue"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function return_map(map)
|
||||||
|
local cbi = require "luci.cbi"
|
||||||
|
local api = require "luci.passwall2.api"
|
||||||
|
if true then
|
||||||
|
-- header
|
||||||
|
local header = cbi.Template(appname .. "/cbi/header")
|
||||||
|
header.api = api
|
||||||
|
header.config = map.config
|
||||||
|
table.insert(map.children, 1, header)
|
||||||
|
end
|
||||||
|
if true then
|
||||||
|
-- footer
|
||||||
|
local footer = cbi.Template(appname .. "/cbi/footer")
|
||||||
|
footer.api = api
|
||||||
|
footer.config = map.config
|
||||||
|
map:append(footer)
|
||||||
|
end
|
||||||
|
|
||||||
|
return map
|
||||||
|
end
|
||||||
|
|
||||||
function luci_types(id, m, s, type_name, option_prefix)
|
function luci_types(id, m, s, type_name, option_prefix)
|
||||||
local fv_type
|
local fv_type
|
||||||
local field_type = s.fields["type"]
|
local field_type = s.fields["type"]
|
||||||
@@ -1585,6 +1628,56 @@ function match_node_rule(name, rule)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local normal_nodes = {}
|
||||||
|
function get_batch_nodes(node)
|
||||||
|
if #normal_nodes == 0 then
|
||||||
|
for k, e in ipairs(get_valid_nodes()) do
|
||||||
|
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
|
||||||
|
normal_nodes[#normal_nodes + 1] = {
|
||||||
|
id = e[".name"],
|
||||||
|
remarks = e["remarks"],
|
||||||
|
group = e["group"]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not node.node_group or node.node_group == "" then return {} end
|
||||||
|
local nodes = {}
|
||||||
|
for g in node.node_group:gmatch("%S+") do
|
||||||
|
g = UrlDecode(g)
|
||||||
|
for k, v in pairs(normal_nodes) do
|
||||||
|
local gn = (v.group and v.group ~= "") and v.group or "default"
|
||||||
|
if gn:lower() == g:lower() and match_node_rule(v.remarks, node.node_match_rule) then
|
||||||
|
nodes[#nodes + 1] = v.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_socks_backup_nodes(id)
|
||||||
|
id = trim(id)
|
||||||
|
if id == "" then return "" end
|
||||||
|
local socks = uci:get_all(appname, id)
|
||||||
|
local nodes
|
||||||
|
if socks.backup_node_add_mode and socks.backup_node_add_mode == "batch" then
|
||||||
|
local node = {}
|
||||||
|
node.node_group = socks.backup_node_group
|
||||||
|
node.node_match_rule = socks.backup_node_match_rule
|
||||||
|
nodes = get_batch_nodes(node)
|
||||||
|
else
|
||||||
|
nodes = socks.autoswitch_backup_node
|
||||||
|
end
|
||||||
|
local backup_nodes, seen = {}, {}
|
||||||
|
for _, v in ipairs(nodes or {}) do
|
||||||
|
if v ~= socks.node and not seen[v] then
|
||||||
|
seen[v] = true
|
||||||
|
table.insert(backup_nodes, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(backup_nodes, " ")
|
||||||
|
end
|
||||||
|
|
||||||
function get_core(field, candidates)
|
function get_core(field, candidates)
|
||||||
local v = uci:get(appname, "@global_subscribe[0]", field)
|
local v = uci:get(appname, "@global_subscribe[0]", field)
|
||||||
if v and v ~= "" then
|
if v and v ~= "" then
|
||||||
@@ -1610,6 +1703,26 @@ function cleanEmptyTables(t)
|
|||||||
return next(t) and t or nil
|
return next(t) and t or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function fetch_cert_sha256(host, port, sni, timeout)
|
||||||
|
if not host then return "" end
|
||||||
|
port = tonumber(port) or 443
|
||||||
|
sni = sni or host
|
||||||
|
timeout = tonumber(timeout) or 5
|
||||||
|
local cmd = string.format(
|
||||||
|
"timeout %d openssl s_client -connect %s:%d -servername %s -showcerts </dev/null 2>/dev/null " ..
|
||||||
|
"| awk 'BEGIN{c=0}/BEGIN CERT/{c++} c==1{print} /END CERT/{if(c==1)exit}' " ..
|
||||||
|
"| openssl x509 -outform der 2>/dev/null " ..
|
||||||
|
"| sha256sum 2>/dev/null",
|
||||||
|
timeout, host, port, sni
|
||||||
|
)
|
||||||
|
local out = trim(sys.exec(cmd))
|
||||||
|
local fp = out:match("^([0-9a-fA-F]+)")
|
||||||
|
if not fp or fp:lower():match("^e3b0c44298fc1c149afbf4c8996fb924") then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
return fp:upper()
|
||||||
|
end
|
||||||
|
|
||||||
function get_dnsmasq_server_domain()
|
function get_dnsmasq_server_domain()
|
||||||
local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server")
|
local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server")
|
||||||
local dnsmasq_server_t = {}
|
local dnsmasq_server_t = {}
|
||||||
|
|||||||
@@ -1329,33 +1329,6 @@ function gen_config(var)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
local nodes_list = {}
|
|
||||||
function get_urltest_batch_nodes(_node)
|
|
||||||
if #nodes_list == 0 then
|
|
||||||
for k, e in ipairs(api.get_valid_nodes()) do
|
|
||||||
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
|
|
||||||
nodes_list[#nodes_list + 1] = {
|
|
||||||
id = e[".name"],
|
|
||||||
remarks = e["remarks"],
|
|
||||||
group = e["group"]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not _node.node_group or _node.node_group == "" then return {} end
|
|
||||||
local nodes = {}
|
|
||||||
for g in _node.node_group:gmatch("%S+") do
|
|
||||||
g = api.UrlDecode(g)
|
|
||||||
for k, v in pairs(nodes_list) do
|
|
||||||
local gn = (v.group and v.group ~= "") and v.group or "default"
|
|
||||||
if gn:lower() == g:lower() and api.match_node_rule(v.remarks, _node.node_match_rule) then
|
|
||||||
nodes[#nodes + 1] = v.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nodes
|
|
||||||
end
|
|
||||||
|
|
||||||
function get_node_by_id(node_id)
|
function get_node_by_id(node_id)
|
||||||
if not node_id or node_id == "" or node_id == "nil" then return nil end
|
if not node_id or node_id == "" or node_id == "nil" then return nil end
|
||||||
if node_id:find("Socks_") then
|
if node_id:find("Socks_") then
|
||||||
@@ -1377,7 +1350,7 @@ function gen_config(var)
|
|||||||
-- new urltest
|
-- new urltest
|
||||||
local ut_nodes
|
local ut_nodes
|
||||||
if _node.node_add_mode and _node.node_add_mode == "batch" then
|
if _node.node_add_mode and _node.node_add_mode == "batch" then
|
||||||
ut_nodes = get_urltest_batch_nodes(_node)
|
ut_nodes = api.get_batch_nodes(_node)
|
||||||
else
|
else
|
||||||
ut_nodes = _node.urltest_node
|
ut_nodes = _node.urltest_node
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -176,21 +176,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
security = node.stream_security,
|
security = node.stream_security,
|
||||||
tlsSettings = (node.stream_security == "tls") and {
|
tlsSettings = (node.stream_security == "tls") and {
|
||||||
serverName = node.tls_serverName,
|
serverName = node.tls_serverName,
|
||||||
allowInsecure = (function()
|
|
||||||
if node.tls_pinSHA256 and node.tls_pinSHA256 ~= "" then return nil end
|
|
||||||
if node.tls_allowInsecure == "1" then return true end
|
|
||||||
end)(),
|
|
||||||
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
|
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
|
||||||
pinnedPeerCertSha256 = (function()
|
pinnedPeerCertSha256 = node.tls_pinSHA256,
|
||||||
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
|
verifyPeerCertByName = node.tls_CertByName,
|
||||||
if not node.tls_pinSHA256 then return "" end
|
|
||||||
return node.tls_pinSHA256
|
|
||||||
end)(),
|
|
||||||
verifyPeerCertByName = (function()
|
|
||||||
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
|
|
||||||
if not node.tls_CertByName then return "" end
|
|
||||||
return node.tls_CertByName
|
|
||||||
end)(),
|
|
||||||
echConfigList = (node.ech == "1") and node.ech_config or nil,
|
echConfigList = (node.ech == "1") and node.ech_config or nil,
|
||||||
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
|
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
|
||||||
certificate = api.split(node.tls_certificate_pem, "\n"),
|
certificate = api.split(node.tls_certificate_pem, "\n"),
|
||||||
@@ -839,7 +827,8 @@ function gen_config_server(node)
|
|||||||
serverNames = node.reality_serverNames or {},
|
serverNames = node.reality_serverNames or {},
|
||||||
privateKey = node.reality_private_key,
|
privateKey = node.reality_private_key,
|
||||||
shortIds = node.reality_shortId or "",
|
shortIds = node.reality_shortId or "",
|
||||||
mldsa65Seed = (node.use_mldsa65Seed == "1") and node.reality_mldsa65Seed or nil
|
mldsa65Seed = (node.use_mldsa65Seed == "1") and node.reality_mldsa65Seed or nil,
|
||||||
|
minClientVer = "1.0.0"
|
||||||
} or nil
|
} or nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1032,33 +1021,6 @@ function gen_config(var)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local nodes_list = {}
|
|
||||||
function get_balancer_batch_nodes(_node)
|
|
||||||
if #nodes_list == 0 then
|
|
||||||
for k, e in ipairs(api.get_valid_nodes()) do
|
|
||||||
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
|
|
||||||
nodes_list[#nodes_list + 1] = {
|
|
||||||
id = e[".name"],
|
|
||||||
remarks = e["remarks"],
|
|
||||||
group = e["group"]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not _node.node_group or _node.node_group == "" then return {} end
|
|
||||||
local nodes = {}
|
|
||||||
for g in _node.node_group:gmatch("%S+") do
|
|
||||||
g = api.UrlDecode(g)
|
|
||||||
for k, v in pairs(nodes_list) do
|
|
||||||
local gn = (v.group and v.group ~= "") and v.group or "default"
|
|
||||||
if gn:lower() == g:lower() and api.match_node_rule(v.remarks, _node.node_match_rule) then
|
|
||||||
nodes[#nodes + 1] = v.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nodes
|
|
||||||
end
|
|
||||||
|
|
||||||
function gen_loopback(outbound_tag, loopback_dst)
|
function gen_loopback(outbound_tag, loopback_dst)
|
||||||
if not outbound_tag or outbound_tag == "" then return nil end
|
if not outbound_tag or outbound_tag == "" then return nil end
|
||||||
local inbound_tag = loopback_dst and "lo-to-" .. loopback_dst or outbound_tag .. "-lo"
|
local inbound_tag = loopback_dst and "lo-to-" .. loopback_dst or outbound_tag .. "-lo"
|
||||||
@@ -1090,7 +1052,7 @@ function gen_config(var)
|
|||||||
-- new balancer
|
-- new balancer
|
||||||
local blc_nodes
|
local blc_nodes
|
||||||
if _node.node_add_mode and _node.node_add_mode == "batch" then
|
if _node.node_add_mode and _node.node_add_mode == "batch" then
|
||||||
blc_nodes = get_balancer_batch_nodes(_node)
|
blc_nodes = api.get_batch_nodes(_node)
|
||||||
else
|
else
|
||||||
blc_nodes = _node.balancing_node
|
blc_nodes = _node.balancing_node
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<!-- map footer -->
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<!-- map header -->
|
||||||
|
<script src="<%=resource%>/view/<%=self.api.appname%>/cbi.js"></script>
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<%
|
||||||
|
local api = self.api
|
||||||
|
local appname = self.appname or api.appname
|
||||||
|
local target_cfgname = self.target_cfgname
|
||||||
|
-%>
|
||||||
|
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
|
||||||
|
<style>
|
||||||
|
table .cbi-button-up,
|
||||||
|
table .cbi-button-down,
|
||||||
|
.td.cbi-section-actions .cbi-button-up,
|
||||||
|
.td.cbi-section-actions .cbi-button-down {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle {
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: grab !important;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 100;
|
||||||
|
padding: 0 !important;
|
||||||
|
line-height: inherit;
|
||||||
|
user-select: none;
|
||||||
|
align-self: stretch;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragging-row {
|
||||||
|
background-color: rgba(131, 191, 255, 0.7) !important;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1152px) {
|
||||||
|
#cbi-<%=appname%>-<%=target_cfgname%> .cbi-section-table-titles {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
//<![CDATA[
|
||||||
|
//Add drag-and-drop sorting
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
function initSortableForTable() {
|
||||||
|
var section = document.getElementById("cbi-<%=appname%>-<%=target_cfgname%>");
|
||||||
|
if (!section) return;
|
||||||
|
|
||||||
|
// === insert drag handle ===
|
||||||
|
var rows = section.querySelectorAll("tr");
|
||||||
|
rows.forEach(function(row) {
|
||||||
|
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
||||||
|
if (!btn) return;
|
||||||
|
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
||||||
|
var handle = document.createElement("span");
|
||||||
|
handle.className = "drag-handle center";
|
||||||
|
handle.title = "<%:Drag to reorder%>";
|
||||||
|
handle.innerHTML = "⠿";
|
||||||
|
btn.after(handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
// === init Sortable ===
|
||||||
|
var table = section.getElementsByTagName("table")[0];
|
||||||
|
if (!table) return;
|
||||||
|
var root = table.tBodies[0] || table;
|
||||||
|
if (root._sortable_initialized) return root._sortable_instance;
|
||||||
|
root._sortable_initialized = true;
|
||||||
|
|
||||||
|
// save origin order
|
||||||
|
root._origOrder = getCurrentOrder(root);
|
||||||
|
|
||||||
|
try {
|
||||||
|
root._sortable_instance = Sortable.create(root, {
|
||||||
|
handle: ".drag-handle",
|
||||||
|
draggable: "tr.cbi-section-table-row",
|
||||||
|
animation: 150,
|
||||||
|
ghostClass: "dragging-row",
|
||||||
|
fallbackOnBody: true,
|
||||||
|
forceFallback: false,
|
||||||
|
swapThreshold: 0.65,
|
||||||
|
onEnd: function (evt) {
|
||||||
|
updateHiddenInput(root, section);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return root._sortable_instance;
|
||||||
|
} catch (e) {
|
||||||
|
root._sortable_initialized = false;
|
||||||
|
console.error("Sortable init failed:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get table current line order
|
||||||
|
function getCurrentOrder(root) {
|
||||||
|
var order = [];
|
||||||
|
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
||||||
|
rows.forEach(function (tr) {
|
||||||
|
var id = tr.id || "";
|
||||||
|
if (id.startsWith("cbi-<%=appname%>-"))
|
||||||
|
id = id.replace("cbi-<%=appname%>-", "");
|
||||||
|
order.push(id);
|
||||||
|
});
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update after drag and drop hidden input
|
||||||
|
function updateHiddenInput(root, section) {
|
||||||
|
var newOrder = getCurrentOrder(root);
|
||||||
|
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
||||||
|
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
||||||
|
if (hiddenInput) {
|
||||||
|
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide lua luci version up/down
|
||||||
|
function hideSortColumn(section) {
|
||||||
|
var table = section.getElementsByTagName("table")[0];
|
||||||
|
if(!table) return;
|
||||||
|
var rows = table.querySelectorAll("tr.cbi-section-table-row");
|
||||||
|
if(!rows.length) return;
|
||||||
|
var colCount = rows[0].children.length, sortCol = -1;
|
||||||
|
for(var col=0; col<colCount; col++){
|
||||||
|
var hasSort=false, invalid=false;
|
||||||
|
rows.forEach(function(tr){
|
||||||
|
var td=tr.children[col]; if(!td) return;
|
||||||
|
if(td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid=true;
|
||||||
|
if(td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort=true;
|
||||||
|
});
|
||||||
|
if(!invalid && hasSort){ sortCol=col; break; }
|
||||||
|
}
|
||||||
|
if(sortCol===-1) return;
|
||||||
|
table.querySelectorAll("tr").forEach(function(tr){
|
||||||
|
var c=tr.children[sortCol]; if(c) c.style.display="none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// === wait TypedSection ===
|
||||||
|
(function waitStable() {
|
||||||
|
var last = 0, stable = 0;
|
||||||
|
var THRESHOLD = 5;
|
||||||
|
function tick() {
|
||||||
|
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
||||||
|
if (count && count === last) stable++;
|
||||||
|
else stable = 0;
|
||||||
|
|
||||||
|
last = count;
|
||||||
|
if (stable >= THRESHOLD)
|
||||||
|
setTimeout(initSortableForTable, 200);
|
||||||
|
else
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
tick();
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<%+cbi/valueheader%>
|
||||||
|
<textarea class="cbi-input-textarea" <% if not self.size then %> style="width: 100%"<% else %> cols="<%=self.size%>"<% end %>
|
||||||
|
data-update="change"<%=
|
||||||
|
attr("name", cbid) ..
|
||||||
|
attr("id", cbid) ..
|
||||||
|
ifattr(self.rows, "rows") ..
|
||||||
|
ifattr(self.wrap, "wrap") ..
|
||||||
|
ifattr(self.readonly, "readonly") ..
|
||||||
|
ifattr(self.datatype, "data-type", self.datatype)
|
||||||
|
%>>
|
||||||
|
<%-=pcdata(self:cfgvalue(section) or self.default)-%>
|
||||||
|
</textarea>
|
||||||
|
<%+cbi/valuefooter%>
|
||||||
@@ -2,46 +2,8 @@
|
|||||||
local api = require "luci.passwall2.api"
|
local api = require "luci.passwall2.api"
|
||||||
local appname = api.appname
|
local appname = api.appname
|
||||||
-%>
|
-%>
|
||||||
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js"></script>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
table .cbi-button-up,
|
|
||||||
table .cbi-button-down,
|
|
||||||
.td.cbi-section-actions .cbi-button-up,
|
|
||||||
.td.cbi-section-actions .cbi-button-down {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle {
|
|
||||||
vertical-align: middle;
|
|
||||||
cursor: grab !important;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 100;
|
|
||||||
padding: 0 !important;
|
|
||||||
line-height: inherit;
|
|
||||||
user-select: none;
|
|
||||||
align-self: stretch;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle:hover {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragging-row {
|
|
||||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 1152px) {
|
|
||||||
#cbi-<%=appname%>-haproxy_config .cbi-section-table-titles {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1152px) {
|
@media screen and (min-width: 1152px) {
|
||||||
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".haproxy_port"],
|
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".haproxy_port"],
|
||||||
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".lbweight"] {
|
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".lbweight"] {
|
||||||
@@ -110,129 +72,5 @@ table .cbi-button-down,
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
//节点列表添加拖拽排序
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
function initSortableForTable() {
|
|
||||||
var section = document.getElementById("cbi-<%=appname%>-haproxy_config");
|
|
||||||
if (!section) return;
|
|
||||||
|
|
||||||
hideSortColumn(section);
|
|
||||||
|
|
||||||
// === 插入 drag handle ===
|
|
||||||
var rows = section.querySelectorAll("tr");
|
|
||||||
rows.forEach(function(row) {
|
|
||||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
|
||||||
if (!btn) return;
|
|
||||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
|
||||||
var handle = document.createElement("span");
|
|
||||||
handle.className = "drag-handle center";
|
|
||||||
handle.title = "<%:Drag to reorder%>";
|
|
||||||
handle.innerHTML = "⠿";
|
|
||||||
btn.after(handle);
|
|
||||||
});
|
|
||||||
|
|
||||||
// === 初始化 Sortable ===
|
|
||||||
var table = section.getElementsByTagName("table")[0];
|
|
||||||
if (!table) return;
|
|
||||||
var root = table.tBodies[0] || table;
|
|
||||||
if (root._sortable_initialized) return root._sortable_instance;
|
|
||||||
root._sortable_initialized = true;
|
|
||||||
|
|
||||||
// 保存原始顺序
|
|
||||||
root._origOrder = getCurrentOrder(root);
|
|
||||||
|
|
||||||
try {
|
|
||||||
root._sortable_instance = Sortable.create(root, {
|
|
||||||
handle: ".drag-handle",
|
|
||||||
draggable: "tr.cbi-section-table-row",
|
|
||||||
animation: 150,
|
|
||||||
ghostClass: "dragging-row",
|
|
||||||
fallbackOnBody: true,
|
|
||||||
forceFallback: false,
|
|
||||||
swapThreshold: 0.65,
|
|
||||||
onEnd: function (evt) {
|
|
||||||
updateHiddenInput(root, section);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return root._sortable_instance;
|
|
||||||
} catch (e) {
|
|
||||||
root._sortable_initialized = false;
|
|
||||||
console.error("Sortable init failed:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取 table 当前行顺序
|
|
||||||
function getCurrentOrder(root) {
|
|
||||||
var order = [];
|
|
||||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
|
||||||
rows.forEach(function (tr) {
|
|
||||||
var id = tr.id || "";
|
|
||||||
if (id.startsWith("cbi-<%=appname%>-"))
|
|
||||||
id = id.replace("cbi-<%=appname%>-", "");
|
|
||||||
order.push(id);
|
|
||||||
});
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 拖拽完成后更新 hidden input
|
|
||||||
function updateHiddenInput(root, section) {
|
|
||||||
var newOrder = getCurrentOrder(root);
|
|
||||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
|
||||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
|
||||||
if (hiddenInput) {
|
|
||||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏18.06 up/down 列
|
|
||||||
function hideSortColumn(section) {
|
|
||||||
var table = section.querySelector("table");
|
|
||||||
if (!table) return;
|
|
||||||
var ths = Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-titles > th"));
|
|
||||||
var dataRows = table.querySelectorAll("tr.cbi-section-table-row");
|
|
||||||
if (!ths.length || !dataRows.length) return;
|
|
||||||
var sortCol = -1;
|
|
||||||
for (var i = 0; i < ths.length; i++) {
|
|
||||||
var hasSort = false, invalid = false;
|
|
||||||
dataRows.forEach(function(tr) {
|
|
||||||
var td = tr.querySelectorAll(":scope > td")[i];
|
|
||||||
if (!td) return;
|
|
||||||
if (td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid = true;
|
|
||||||
if (td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort = true;
|
|
||||||
});
|
|
||||||
if (!invalid && hasSort) { sortCol = i; break; }
|
|
||||||
}
|
|
||||||
if (sortCol === -1) return;
|
|
||||||
var rows = [table.querySelector("tr.cbi-section-table-titles")].concat(
|
|
||||||
Array.prototype.slice.call(dataRows),
|
|
||||||
Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-descr"))
|
|
||||||
);
|
|
||||||
rows.forEach(function(tr) {
|
|
||||||
var cells = Array.prototype.filter.call(tr.children, function(el) {
|
|
||||||
return el.tagName === "TH" || el.tagName === "TD";
|
|
||||||
});
|
|
||||||
if (cells[sortCol]) cells[sortCol].style.display = "none";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 等待 TypedSection 行稳定 ===
|
|
||||||
(function waitStable() {
|
|
||||||
var last = 0, stable = 0;
|
|
||||||
var THRESHOLD = 5;
|
|
||||||
function tick() {
|
|
||||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
|
||||||
if (count && count === last) stable++;
|
|
||||||
else stable = 0;
|
|
||||||
|
|
||||||
last = count;
|
|
||||||
if (stable >= THRESHOLD)
|
|
||||||
setTimeout(initSortableForTable, 200);
|
|
||||||
else
|
|
||||||
requestAnimationFrame(tick);
|
|
||||||
}
|
|
||||||
tick();
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
//]]>
|
//]]>
|
||||||
</script>
|
</script>
|
||||||
@@ -52,5 +52,29 @@ local api = self.api
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fetchCertSha256(el) {
|
||||||
|
if (el.getAttribute("data-loading") === "1") return;
|
||||||
|
el.setAttribute("data-loading", "1");
|
||||||
|
el.style.pointerEvents = "none";
|
||||||
|
el.style.color = "red";
|
||||||
|
XHR.get('<%=api.url("fetch_certsha256")%>', { id: node_id }, function(x, res) {
|
||||||
|
el.setAttribute("data-loading", "0");
|
||||||
|
el.style.pointerEvents = "";
|
||||||
|
el.style.color = "";
|
||||||
|
if (!x || x.status !== 200) {
|
||||||
|
alert("<%:Error%>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!res || res.code !== 1 || !res.data) {
|
||||||
|
alert(res && res.msg ? res.msg : "<%:Fetch Failed%> !");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const inputs = document.querySelectorAll('input[id$=".xray_tls_pinSHA256"]');
|
||||||
|
inputs.forEach(function(input) {
|
||||||
|
input.value = res.data;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
//]]>
|
//]]>
|
||||||
</script>
|
</script>
|
||||||
@@ -1314,6 +1314,8 @@ local current_node = map:get(section)
|
|||||||
opt.set(dom_prefix + 'utls', true);
|
opt.set(dom_prefix + 'utls', true);
|
||||||
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
|
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
|
||||||
}
|
}
|
||||||
|
opt.set(dom_prefix + 'ech', !!queryParam.ech);
|
||||||
|
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
|
||||||
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
|
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
|
||||||
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
|
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
|
||||||
opt.set(
|
opt.set(
|
||||||
|
|||||||
@@ -635,11 +635,15 @@ table td, .table .td {
|
|||||||
if (cacheData && cacheData.savetime && (now - cacheData.timestamp) < cacheData.savetime) {
|
if (cacheData && cacheData.savetime && (now - cacheData.timestamp) < cacheData.savetime) {
|
||||||
const a = ping_value[i].firstElementChild;
|
const a = ping_value[i].firstElementChild;
|
||||||
let color = "red";
|
let color = "red";
|
||||||
if (cacheData.value < 100)
|
if (cacheData.value >= 0) {
|
||||||
color = "green";
|
a.innerText = cacheData.value + " ms";
|
||||||
else if (cacheData.value < 200)
|
if (cacheData.value < 100)
|
||||||
color = "#fb9a05";
|
color = "green";
|
||||||
a.innerText = cacheData.value + " ms";
|
else if (cacheData.value < 200)
|
||||||
|
color = "#fb9a05";
|
||||||
|
} else {
|
||||||
|
a.innerText = "<%:Timeout%>";
|
||||||
|
}
|
||||||
a.style.color = color;
|
a.style.color = color;
|
||||||
a.onclick = function () {
|
a.onclick = function () {
|
||||||
ping_node(cbi_id, this, auto_detection_time);
|
ping_node(cbi_id, this, auto_detection_time);
|
||||||
@@ -679,15 +683,23 @@ table td, .table .td {
|
|||||||
const cache_data = {
|
const cache_data = {
|
||||||
dom_id: strs[i],
|
dom_id: strs[i],
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
savetime: 60 * 1000,
|
savetime: 150 * 1000,
|
||||||
value: ping
|
value: ping
|
||||||
};
|
};
|
||||||
text = result.ping + " ms";
|
|
||||||
localStorage.setItem(auto_detection_time + ":" + dom.address + ":" + dom.port, JSON.stringify(cache_data));
|
localStorage.setItem(auto_detection_time + ":" + dom.address + ":" + dom.port, JSON.stringify(cache_data));
|
||||||
|
text = result.ping + " ms";
|
||||||
if (ping < 100)
|
if (ping < 100)
|
||||||
color = "green";
|
color = "green";
|
||||||
else if (ping < 200)
|
else if (ping < 200)
|
||||||
color = "#fb9a05";
|
color = "#fb9a05";
|
||||||
|
} else {
|
||||||
|
const cache_data = {
|
||||||
|
dom_id: strs[i],
|
||||||
|
timestamp: Date.now(),
|
||||||
|
savetime: 60 * 1000,
|
||||||
|
value: -1
|
||||||
|
};
|
||||||
|
localStorage.setItem(auto_detection_time + ":" + dom.address + ":" + dom.port, JSON.stringify(cache_data));
|
||||||
}
|
}
|
||||||
a.innerText = text;
|
a.innerText = text;
|
||||||
a.style.color = color;
|
a.style.color = color;
|
||||||
@@ -1101,6 +1113,15 @@ table td, .table .td {
|
|||||||
onChange("auto_detection_time", auto_detection_time);
|
onChange("auto_detection_time", auto_detection_time);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const links = document.querySelectorAll('a');
|
||||||
|
links.forEach(link => {
|
||||||
|
link.addEventListener('click', (e) => {
|
||||||
|
if (link.getAttribute('target') !== '_blank') {
|
||||||
|
ajaxAbortAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
//]]>
|
//]]>
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,46 +1,6 @@
|
|||||||
<%
|
<%
|
||||||
local api = require "luci.passwall2.api"
|
local api = require "luci.passwall2.api"
|
||||||
-%>
|
-%>
|
||||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
table .cbi-button-up,
|
|
||||||
table .cbi-button-down,
|
|
||||||
.td.cbi-section-actions .cbi-button-up,
|
|
||||||
.td.cbi-section-actions .cbi-button-down {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle {
|
|
||||||
vertical-align: middle;
|
|
||||||
cursor: grab !important;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 100;
|
|
||||||
padding: 0 !important;
|
|
||||||
line-height: inherit;
|
|
||||||
user-select: none;
|
|
||||||
align-self: stretch;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle:hover {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragging-row {
|
|
||||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 1152px) {
|
|
||||||
#cbi-<%=api.appname%>-subscribe_list .cbi-section-table-titles {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
//<![CDATA[
|
//<![CDATA[
|
||||||
@@ -148,129 +108,5 @@ table .cbi-button-down,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//订阅列表添加拖拽排序
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
function initSortableForTable() {
|
|
||||||
var section = document.getElementById("cbi-<%=api.appname%>-subscribe_list");
|
|
||||||
if (!section) return;
|
|
||||||
|
|
||||||
hideSortColumn(section);
|
|
||||||
|
|
||||||
// === 插入 drag handle ===
|
|
||||||
var rows = section.querySelectorAll("tr");
|
|
||||||
rows.forEach(function(row) {
|
|
||||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
|
||||||
if (!btn) return;
|
|
||||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
|
||||||
var handle = document.createElement("span");
|
|
||||||
handle.className = "drag-handle center";
|
|
||||||
handle.title = "<%:Drag to reorder%>";
|
|
||||||
handle.innerHTML = "⠿";
|
|
||||||
btn.after(handle);
|
|
||||||
});
|
|
||||||
|
|
||||||
// === 初始化 Sortable ===
|
|
||||||
var table = section.getElementsByTagName("table")[0];
|
|
||||||
if (!table) return;
|
|
||||||
var root = table.tBodies[0] || table;
|
|
||||||
if (root._sortable_initialized) return root._sortable_instance;
|
|
||||||
root._sortable_initialized = true;
|
|
||||||
|
|
||||||
// 保存原始顺序
|
|
||||||
root._origOrder = getCurrentOrder(root);
|
|
||||||
|
|
||||||
try {
|
|
||||||
root._sortable_instance = Sortable.create(root, {
|
|
||||||
handle: ".drag-handle",
|
|
||||||
draggable: "tr.cbi-section-table-row",
|
|
||||||
animation: 150,
|
|
||||||
ghostClass: "dragging-row",
|
|
||||||
fallbackOnBody: true,
|
|
||||||
forceFallback: false,
|
|
||||||
swapThreshold: 0.65,
|
|
||||||
onEnd: function (evt) {
|
|
||||||
updateHiddenInput(root, section);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return root._sortable_instance;
|
|
||||||
} catch (e) {
|
|
||||||
root._sortable_initialized = false;
|
|
||||||
console.error("Sortable init failed:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取 table 当前行顺序
|
|
||||||
function getCurrentOrder(root) {
|
|
||||||
var order = [];
|
|
||||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
|
||||||
rows.forEach(function (tr) {
|
|
||||||
var id = tr.id || "";
|
|
||||||
if (id.startsWith("cbi-<%=api.appname%>-"))
|
|
||||||
id = id.replace("cbi-<%=api.appname%>-", "");
|
|
||||||
order.push(id);
|
|
||||||
});
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 拖拽完成后更新 hidden input
|
|
||||||
function updateHiddenInput(root, section) {
|
|
||||||
var newOrder = getCurrentOrder(root);
|
|
||||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
|
||||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
|
||||||
if (hiddenInput) {
|
|
||||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏18.06 up/down 列
|
|
||||||
function hideSortColumn(section) {
|
|
||||||
var table = section.querySelector("table");
|
|
||||||
if (!table) return;
|
|
||||||
var ths = Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-titles > th"));
|
|
||||||
var dataRows = table.querySelectorAll("tr.cbi-section-table-row");
|
|
||||||
if (!ths.length || !dataRows.length) return;
|
|
||||||
var sortCol = -1;
|
|
||||||
for (var i = 0; i < ths.length; i++) {
|
|
||||||
var hasSort = false, invalid = false;
|
|
||||||
dataRows.forEach(function(tr) {
|
|
||||||
var td = tr.querySelectorAll(":scope > td")[i];
|
|
||||||
if (!td) return;
|
|
||||||
if (td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid = true;
|
|
||||||
if (td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort = true;
|
|
||||||
});
|
|
||||||
if (!invalid && hasSort) { sortCol = i; break; }
|
|
||||||
}
|
|
||||||
if (sortCol === -1) return;
|
|
||||||
var rows = [table.querySelector("tr.cbi-section-table-titles")].concat(
|
|
||||||
Array.prototype.slice.call(dataRows),
|
|
||||||
Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-descr"))
|
|
||||||
);
|
|
||||||
rows.forEach(function(tr) {
|
|
||||||
var cells = Array.prototype.filter.call(tr.children, function(el) {
|
|
||||||
return el.tagName === "TH" || el.tagName === "TD";
|
|
||||||
});
|
|
||||||
if (cells[sortCol]) cells[sortCol].style.display = "none";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 等待 TypedSection 行稳定 ===
|
|
||||||
(function waitStable() {
|
|
||||||
var last = 0, stable = 0;
|
|
||||||
var THRESHOLD = 5;
|
|
||||||
function tick() {
|
|
||||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
|
||||||
if (count && count === last) stable++;
|
|
||||||
else stable = 0;
|
|
||||||
|
|
||||||
last = count;
|
|
||||||
if (stable >= THRESHOLD)
|
|
||||||
setTimeout(initSortableForTable, 200);
|
|
||||||
else
|
|
||||||
requestAnimationFrame(tick);
|
|
||||||
}
|
|
||||||
tick();
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
//]]>
|
//]]>
|
||||||
</script>
|
</script>
|
||||||
@@ -4,44 +4,12 @@ local fs = api.fs
|
|||||||
local has_old_geoip = fs.access("/tmp/bak_v2ray/geoip.dat")
|
local has_old_geoip = fs.access("/tmp/bak_v2ray/geoip.dat")
|
||||||
local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
||||||
-%>
|
-%>
|
||||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
table .cbi-button-up,
|
|
||||||
table .cbi-button-down,
|
|
||||||
.td.cbi-section-actions .cbi-button-up,
|
|
||||||
.td.cbi-section-actions .cbi-button-down {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.cbi-value[id$="-geoip_update"],
|
div.cbi-value[id$="-geoip_update"],
|
||||||
div.cbi-value[id$="-geosite_update"] {
|
div.cbi-value[id$="-geosite_update"] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag-handle {
|
|
||||||
vertical-align: middle;
|
|
||||||
cursor: grab !important;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 100;
|
|
||||||
padding: 0 !important;
|
|
||||||
line-height: inherit;
|
|
||||||
user-select: none;
|
|
||||||
align-self: stretch;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drag-handle:hover {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragging-row {
|
|
||||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="cbi-value" id="_rule_div">
|
<div class="cbi-value" id="_rule_div">
|
||||||
@@ -157,118 +125,5 @@ local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//分流规则添加拖拽排序
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
function initSortableForTable() {
|
|
||||||
var section = document.getElementById("cbi-<%=api.appname%>-shunt_rules");
|
|
||||||
if (!section) return;
|
|
||||||
|
|
||||||
// === 插入 drag handle ===
|
|
||||||
var rows = section.querySelectorAll("tr");
|
|
||||||
rows.forEach(function(row) {
|
|
||||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
|
||||||
if (!btn) return;
|
|
||||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
|
||||||
var handle = document.createElement("span");
|
|
||||||
handle.className = "drag-handle center";
|
|
||||||
handle.title = "<%:Drag to reorder%>";
|
|
||||||
handle.innerHTML = "⠿";
|
|
||||||
btn.after(handle);
|
|
||||||
});
|
|
||||||
|
|
||||||
// === 初始化 Sortable ===
|
|
||||||
var table = section.getElementsByTagName("table")[0];
|
|
||||||
if (!table) return;
|
|
||||||
var root = table.tBodies[0] || table;
|
|
||||||
if (root._sortable_initialized) return root._sortable_instance;
|
|
||||||
root._sortable_initialized = true;
|
|
||||||
|
|
||||||
// 保存原始顺序
|
|
||||||
root._origOrder = getCurrentOrder(root);
|
|
||||||
|
|
||||||
try {
|
|
||||||
root._sortable_instance = Sortable.create(root, {
|
|
||||||
handle: ".drag-handle",
|
|
||||||
draggable: "tr.cbi-section-table-row",
|
|
||||||
animation: 150,
|
|
||||||
ghostClass: "dragging-row",
|
|
||||||
fallbackOnBody: true,
|
|
||||||
forceFallback: false,
|
|
||||||
swapThreshold: 0.65,
|
|
||||||
onEnd: function (evt) {
|
|
||||||
updateHiddenInput(root, section);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return root._sortable_instance;
|
|
||||||
} catch (e) {
|
|
||||||
root._sortable_initialized = false;
|
|
||||||
console.error("Sortable init failed:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取 table 当前行顺序
|
|
||||||
function getCurrentOrder(root) {
|
|
||||||
var order = [];
|
|
||||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
|
||||||
rows.forEach(function (tr) {
|
|
||||||
var id = tr.id || "";
|
|
||||||
if (id.startsWith("cbi-<%=api.appname%>-"))
|
|
||||||
id = id.replace("cbi-<%=api.appname%>-", "");
|
|
||||||
order.push(id);
|
|
||||||
});
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 拖拽完成后更新 hidden input
|
|
||||||
function updateHiddenInput(root, section) {
|
|
||||||
var newOrder = getCurrentOrder(root);
|
|
||||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
|
||||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
|
||||||
if (hiddenInput) {
|
|
||||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏18.06 up/down 列
|
|
||||||
function hideSortColumn(section) {
|
|
||||||
var table = section.getElementsByTagName("table")[0];
|
|
||||||
if(!table) return;
|
|
||||||
var rows = table.querySelectorAll("tr.cbi-section-table-row");
|
|
||||||
if(!rows.length) return;
|
|
||||||
var colCount = rows[0].children.length, sortCol = -1;
|
|
||||||
for(var col=0; col<colCount; col++){
|
|
||||||
var hasSort=false, invalid=false;
|
|
||||||
rows.forEach(function(tr){
|
|
||||||
var td=tr.children[col]; if(!td) return;
|
|
||||||
if(td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid=true;
|
|
||||||
if(td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort=true;
|
|
||||||
});
|
|
||||||
if(!invalid && hasSort){ sortCol=col; break; }
|
|
||||||
}
|
|
||||||
if(sortCol===-1) return;
|
|
||||||
table.querySelectorAll("tr").forEach(function(tr){
|
|
||||||
var c=tr.children[sortCol]; if(c) c.style.display="none";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 等待 TypedSection 行稳定 ===
|
|
||||||
(function waitStable() {
|
|
||||||
var last = 0, stable = 0;
|
|
||||||
var THRESHOLD = 5;
|
|
||||||
function tick() {
|
|
||||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
|
||||||
if (count && count === last) stable++;
|
|
||||||
else stable = 0;
|
|
||||||
|
|
||||||
last = count;
|
|
||||||
if (stable >= THRESHOLD)
|
|
||||||
setTimeout(initSortableForTable, 200);
|
|
||||||
else
|
|
||||||
requestAnimationFrame(tick);
|
|
||||||
}
|
|
||||||
tick();
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
//]]>
|
//]]>
|
||||||
</script>
|
</script>
|
||||||
@@ -379,6 +379,9 @@ msgstr "توزیع بار"
|
|||||||
msgid "Node Addition Method"
|
msgid "Node Addition Method"
|
||||||
msgstr "روش افزودن گره"
|
msgstr "روش افزودن گره"
|
||||||
|
|
||||||
|
msgid "Backup Node Addition Method"
|
||||||
|
msgstr "روش افزودن گره پشتیبان"
|
||||||
|
|
||||||
msgid "Manual"
|
msgid "Manual"
|
||||||
msgstr "دستی"
|
msgstr "دستی"
|
||||||
|
|
||||||
@@ -1651,6 +1654,12 @@ msgstr "گواهی TLS (PEM)"
|
|||||||
msgid "Full certificate (chain), PEM format."
|
msgid "Full certificate (chain), PEM format."
|
||||||
msgstr "گواهی کامل (زنجیرهای)، فرمت PEM."
|
msgstr "گواهی کامل (زنجیرهای)، فرمت PEM."
|
||||||
|
|
||||||
|
msgid "Fetch Manually"
|
||||||
|
msgstr "دریافت دستی"
|
||||||
|
|
||||||
|
msgid "Fetch Failed"
|
||||||
|
msgstr "واکشی ناموفق"
|
||||||
|
|
||||||
msgid "ECH Config"
|
msgid "ECH Config"
|
||||||
msgstr "پیکربندی ECH"
|
msgstr "پیکربندی ECH"
|
||||||
|
|
||||||
@@ -2400,4 +2409,7 @@ msgid "Gecko Packet Size (min)"
|
|||||||
msgstr "اندازه بسته Gecko (دقیقه)"
|
msgstr "اندازه بسته Gecko (دقیقه)"
|
||||||
|
|
||||||
msgid "Gecko Packet Size (max)"
|
msgid "Gecko Packet Size (max)"
|
||||||
msgstr "اندازه بسته Gecko (حداکثر)"
|
msgstr "اندازه بسته Gecko (حداکثر)"
|
||||||
|
|
||||||
|
msgid "valid time (hh:mm)"
|
||||||
|
msgstr "زمان معتبر (ساعت:میلیمتر)"
|
||||||
@@ -376,6 +376,9 @@ msgstr "Балансировка"
|
|||||||
msgid "Node Addition Method"
|
msgid "Node Addition Method"
|
||||||
msgstr "Способ добавления узлов"
|
msgstr "Способ добавления узлов"
|
||||||
|
|
||||||
|
msgid "Backup Node Addition Method"
|
||||||
|
msgstr "Метод добавления резервного узла"
|
||||||
|
|
||||||
msgid "Manual"
|
msgid "Manual"
|
||||||
msgstr "Вручную"
|
msgstr "Вручную"
|
||||||
|
|
||||||
@@ -1652,6 +1655,12 @@ msgstr "Сертификат TLS (PEM)"
|
|||||||
msgid "Full certificate (chain), PEM format."
|
msgid "Full certificate (chain), PEM format."
|
||||||
msgstr "Полный сертификат (цепочка), формат PEM."
|
msgstr "Полный сертификат (цепочка), формат PEM."
|
||||||
|
|
||||||
|
msgid "Fetch Manually"
|
||||||
|
msgstr "Получить вручную"
|
||||||
|
|
||||||
|
msgid "Fetch Failed"
|
||||||
|
msgstr "Не удалось получить данные."
|
||||||
|
|
||||||
msgid "ECH Config"
|
msgid "ECH Config"
|
||||||
msgstr "Конфигурация ECH"
|
msgstr "Конфигурация ECH"
|
||||||
|
|
||||||
@@ -2399,3 +2408,6 @@ msgstr "Размер упаковки Gecko (мин)"
|
|||||||
|
|
||||||
msgid "Gecko Packet Size (max)"
|
msgid "Gecko Packet Size (max)"
|
||||||
msgstr "Размер упаковки Gecko (макс)"
|
msgstr "Размер упаковки Gecko (макс)"
|
||||||
|
|
||||||
|
msgid "valid time (hh:mm)"
|
||||||
|
msgstr "Время действия (чч:мм)"
|
||||||
@@ -376,6 +376,9 @@ msgstr "负载均衡"
|
|||||||
msgid "Node Addition Method"
|
msgid "Node Addition Method"
|
||||||
msgstr "节点添加方式"
|
msgstr "节点添加方式"
|
||||||
|
|
||||||
|
msgid "Backup Node Addition Method"
|
||||||
|
msgstr "后备节点添加方式"
|
||||||
|
|
||||||
msgid "Manual"
|
msgid "Manual"
|
||||||
msgstr "手动"
|
msgstr "手动"
|
||||||
|
|
||||||
@@ -1643,6 +1646,12 @@ msgstr "TLS 证书(PEM)"
|
|||||||
msgid "Full certificate (chain), PEM format."
|
msgid "Full certificate (chain), PEM format."
|
||||||
msgstr "完整证书(链),PEM 格式。"
|
msgstr "完整证书(链),PEM 格式。"
|
||||||
|
|
||||||
|
msgid "Fetch Manually"
|
||||||
|
msgstr "手动获取"
|
||||||
|
|
||||||
|
msgid "Fetch Failed"
|
||||||
|
msgstr "获取失败"
|
||||||
|
|
||||||
msgid "ECH Config"
|
msgid "ECH Config"
|
||||||
msgstr "ECH 配置"
|
msgstr "ECH 配置"
|
||||||
|
|
||||||
@@ -2387,3 +2396,6 @@ msgstr "Gecko 包大小(最小)"
|
|||||||
|
|
||||||
msgid "Gecko Packet Size (max)"
|
msgid "Gecko Packet Size (max)"
|
||||||
msgstr "Gecko 包大小(最大)"
|
msgstr "Gecko 包大小(最大)"
|
||||||
|
|
||||||
|
msgid "valid time (hh:mm)"
|
||||||
|
msgstr "有效时间(hh:mm)"
|
||||||
@@ -376,6 +376,9 @@ msgstr "負載均衡"
|
|||||||
msgid "Node Addition Method"
|
msgid "Node Addition Method"
|
||||||
msgstr "節點新增方式"
|
msgstr "節點新增方式"
|
||||||
|
|
||||||
|
msgid "Backup Node Addition Method"
|
||||||
|
msgstr "備用節點新增方式"
|
||||||
|
|
||||||
msgid "Manual"
|
msgid "Manual"
|
||||||
msgstr "手動"
|
msgstr "手動"
|
||||||
|
|
||||||
@@ -1643,6 +1646,12 @@ msgstr "TLS 憑證(PEM)"
|
|||||||
msgid "Full certificate (chain), PEM format."
|
msgid "Full certificate (chain), PEM format."
|
||||||
msgstr "完整憑證(鏈),PEM 格式。"
|
msgstr "完整憑證(鏈),PEM 格式。"
|
||||||
|
|
||||||
|
msgid "Fetch Manually"
|
||||||
|
msgstr "手動獲取"
|
||||||
|
|
||||||
|
msgid "Fetch Failed"
|
||||||
|
msgstr "獲取失敗"
|
||||||
|
|
||||||
msgid "ECH Config"
|
msgid "ECH Config"
|
||||||
msgstr "ECH 配置"
|
msgstr "ECH 配置"
|
||||||
|
|
||||||
@@ -2393,3 +2402,6 @@ msgstr "Gecko 包大小(最小)"
|
|||||||
|
|
||||||
msgid "Gecko Packet Size (max)"
|
msgid "Gecko Packet Size (max)"
|
||||||
msgstr "Gecko 包大小(最大)"
|
msgstr "Gecko 包大小(最大)"
|
||||||
|
|
||||||
|
msgid "valid time (hh:mm)"
|
||||||
|
msgstr "有效時間(hh:mm)"
|
||||||
@@ -62,6 +62,10 @@ chmod +x /usr/share/passwall2/*.sh
|
|||||||
[ "$(uci -q get passwall2.@global_xray[0].sniffing)" == "1" ] && [ "$(uci -q get passwall2.@global_xray[0].route_only)" != "1" ] && uci -q set passwall2.@global_xray[0].sniffing_override_dest=1
|
[ "$(uci -q get passwall2.@global_xray[0].sniffing)" == "1" ] && [ "$(uci -q get passwall2.@global_xray[0].route_only)" != "1" ] && uci -q set passwall2.@global_xray[0].sniffing_override_dest=1
|
||||||
uci -q delete passwall2.@global_xray[0].sniffing
|
uci -q delete passwall2.@global_xray[0].sniffing
|
||||||
uci -q delete passwall2.@global_xray[0].route_only
|
uci -q delete passwall2.@global_xray[0].route_only
|
||||||
|
frag_len=$(uci -q get passwall2.@global_xray[0].fragment_length)
|
||||||
|
[ -n "$frag_len" ] && uci -q set passwall2.@global_xray[0].fragment_lengths="$frag_len" && uci -q delete passwall2.@global_xray[0].fragment_length
|
||||||
|
frag_dly=$(uci -q get passwall2.@global_xray[0].fragment_delay)
|
||||||
|
[ -n "$frag_dly" ] && uci -q set passwall2.@global_xray[0].fragment_delays="$frag_dly" && uci -q delete passwall2.@global_xray[0].fragment_delay
|
||||||
uci -q commit passwall2
|
uci -q commit passwall2
|
||||||
|
|
||||||
sed -i "s#add_from#group#g" /etc/config/passwall2 2>/dev/null
|
sed -i "s#add_from#group#g" /etc/config/passwall2 2>/dev/null
|
||||||
|
|||||||
@@ -166,9 +166,8 @@ start() {
|
|||||||
retry_num=$(config_n_get $id autoswitch_retry_num 1)
|
retry_num=$(config_n_get $id autoswitch_retry_num 1)
|
||||||
restore_switch=$(config_n_get $id autoswitch_restore_switch 0)
|
restore_switch=$(config_n_get $id autoswitch_restore_switch 0)
|
||||||
probe_url=$(config_n_get $id autoswitch_probe_url "https://www.google.com/generate_204")
|
probe_url=$(config_n_get $id autoswitch_probe_url "https://www.google.com/generate_204")
|
||||||
backup_node=$(config_n_get $id autoswitch_backup_node)
|
backup_node=$(lua_api "get_socks_backup_nodes(\"${id}\")")
|
||||||
if [ -n "$backup_node" ]; then
|
if [ -n "$backup_node" ]; then
|
||||||
backup_node=$(echo "$backup_node" | tr -s ' ' '\n' | uniq | tr -s '\n' ' ')
|
|
||||||
backup_node_num=$(printf "%s\n" "$backup_node" | wc -w)
|
backup_node_num=$(printf "%s\n" "$backup_node" | wc -w)
|
||||||
if [ "$backup_node_num" -eq 1 ]; then
|
if [ "$backup_node_num" -eq 1 ]; then
|
||||||
[ "$main_node" = "$backup_node" ] && return
|
[ "$main_node" = "$backup_node" ] && return
|
||||||
|
|||||||
@@ -1368,6 +1368,10 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
|||||||
result.reality_publicKey = params.pbk or nil
|
result.reality_publicKey = params.pbk or nil
|
||||||
result.reality_shortId = params.sid or nil
|
result.reality_shortId = params.sid or nil
|
||||||
end
|
end
|
||||||
|
if params.ech and params.ech ~= "" then
|
||||||
|
result.ech = "1"
|
||||||
|
result.ech_config = params.ech
|
||||||
|
end
|
||||||
result.tls_pinSHA256 = params.pcs
|
result.tls_pinSHA256 = params.pcs
|
||||||
result.tls_CertByName = params.vcn
|
result.tls_CertByName = params.vcn
|
||||||
local insecure = params.allowinsecure or params.allowInsecure or params.insecure
|
local insecure = params.allowinsecure or params.allowInsecure or params.insecure
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
|
|||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
PKG_NAME:=luci-app-ssr-plus
|
PKG_NAME:=luci-app-ssr-plus
|
||||||
PKG_VERSION:=196
|
PKG_VERSION:=196
|
||||||
PKG_RELEASE:=44
|
PKG_RELEASE:=45
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||||
|
|||||||
@@ -499,7 +499,10 @@ Xray.outbounds = {
|
|||||||
fingerprint = server.fingerprint,
|
fingerprint = server.fingerprint,
|
||||||
allowInsecure = (function()
|
allowInsecure = (function()
|
||||||
if server.tls_CertSha and server.tls_CertSha ~= "" then return nil end
|
if server.tls_CertSha and server.tls_CertSha ~= "" then return nil end
|
||||||
return server.insecure == "1"
|
if os.date("%Y.%m.%d") < "2026.06.01" then
|
||||||
|
return server.insecure == "1"
|
||||||
|
end
|
||||||
|
return nil
|
||||||
end)(),
|
end)(),
|
||||||
serverName = server.tls_host,
|
serverName = server.tls_host,
|
||||||
certificates = server.certificate and {
|
certificates = server.certificate and {
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=netdata
|
PKG_NAME:=netdata
|
||||||
PKG_VERSION:=2.10.3
|
PKG_VERSION:=2.10.4
|
||||||
PKG_RELEASE:=13
|
PKG_RELEASE:=14
|
||||||
|
|
||||||
PKG_MAINTAINER:=Josef Schlehofer <pepe.schlehofer@gmail.com>, Daniel Engberg <daniel.engberg.lists@pyret.net>
|
PKG_MAINTAINER:=Josef Schlehofer <pepe.schlehofer@gmail.com>, Daniel Engberg <daniel.engberg.lists@pyret.net>
|
||||||
PKG_LICENSE:=GPL-3.0-or-later
|
PKG_LICENSE:=GPL-3.0-or-later
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ PKG_NAME:=alwaysonline
|
|||||||
PKG_UPSTREAM_VERSION:=1.2.1
|
PKG_UPSTREAM_VERSION:=1.2.1
|
||||||
PKG_UPSTREAM_GITHASH:=206292ca68e4d6c81b215163a7bbd0cd2eb36860
|
PKG_UPSTREAM_GITHASH:=206292ca68e4d6c81b215163a7bbd0cd2eb36860
|
||||||
PKG_VERSION:=$(PKG_UPSTREAM_VERSION)~$(call version_abbrev,$(PKG_UPSTREAM_GITHASH))
|
PKG_VERSION:=$(PKG_UPSTREAM_VERSION)~$(call version_abbrev,$(PKG_UPSTREAM_GITHASH))
|
||||||
PKG_RELEASE:=8
|
PKG_RELEASE:=9
|
||||||
UCI_VERSION:=0.2025.01.25
|
UCI_VERSION:=0.2025.01.25
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/Jamesits/alwaysonline.git
|
PKG_SOURCE_URL:=https://github.com/Jamesits/alwaysonline.git
|
||||||
PKG_SOURCE_VERSION:=0b7050013224cea1e9d4843817f9baa12b0bfe19
|
PKG_SOURCE_VERSION:=e5f20a72c8cf6a9ce0c5901e84dff18e96df5812
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_UPSTREAM_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_UPSTREAM_VERSION)
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ include $(TOPDIR)/rules.mk
|
|||||||
PKG_NAME:=tcptraceroute
|
PKG_NAME:=tcptraceroute
|
||||||
PKG_REALVERSION:=1.5beta7
|
PKG_REALVERSION:=1.5beta7
|
||||||
PKG_VERSION:=1.5_beta7
|
PKG_VERSION:=1.5_beta7
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_VERSION:=tcptraceroute-$(PKG_REALVERSION)
|
PKG_SOURCE_VERSION:=tcptraceroute-$(PKG_REALVERSION)
|
||||||
PKG_SOURCE_URL:=https://github.com/mct/tcptraceroute.git
|
PKG_SOURCE_URL:=https://github.com/mct/tcptraceroute.git
|
||||||
|
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
define Package/tcptraceroute
|
define Package/tcptraceroute
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=Xray-core
|
PKG_NAME:=Xray-core
|
||||||
PKG_VERSION:=26.7.11
|
PKG_VERSION:=26.7.11
|
||||||
PKG_RELEASE:=19
|
PKG_RELEASE:=20
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/XTLS/Xray-core/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/XTLS/Xray-core/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
--- a/infra/conf/transport_internet.go
|
|
||||||
+++ b/infra/conf/transport_internet.go
|
|
||||||
@@ -14,7 +14,6 @@ import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
- "time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
@@ -699,12 +698,7 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
|
||||||
config.MasterKeyLog = c.MasterKeyLog
|
|
||||||
|
|
||||||
if c.AllowInsecure {
|
|
||||||
- if time.Now().After(time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)) {
|
|
||||||
- return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`)
|
|
||||||
- } else {
|
|
||||||
- errors.LogWarning(context.Background(), `"allowInsecure" will be removed automatically after 2026-06-01, please use "pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn) instead, PLEASE CONTACT YOUR SERVICE PROVIDER (AIRPORT)`)
|
|
||||||
- config.AllowInsecure = true
|
|
||||||
- }
|
|
||||||
+ config.AllowInsecure = true
|
|
||||||
}
|
|
||||||
if c.PinnedPeerCertSha256 != "" {
|
|
||||||
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
|
|
||||||
+1
-1
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=z8102
|
PKG_NAME:=z8102
|
||||||
PKG_VERSION:=0.2
|
PKG_VERSION:=0.2
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
PKG_MAINTAINER:=Yannick Chabanois <contact@openmptcprouter.com>
|
PKG_MAINTAINER:=Yannick Chabanois <contact@openmptcprouter.com>
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ log() {
|
|||||||
modem_reset()
|
modem_reset()
|
||||||
{
|
{
|
||||||
modemnb=$1
|
modemnb=$1
|
||||||
# sim
|
if [ -z "$(cat /etc/board.json | grep z8802)" ]; then
|
||||||
if [ -e /sys/class/gpio/sim${modemnb}/value ]; then
|
# sim
|
||||||
echo "0" > /sys/class/gpio/sim${modemnb}/value
|
if [ -e /sys/class/gpio/sim${modemnb}/value ]; then
|
||||||
elif [ -f /usr/bin/gpiofind ]; then
|
echo "0" > /sys/class/gpio/sim${modemnb}/value
|
||||||
gpioset `gpiofind "sim=${modemnb}"`=0
|
elif [ -f /usr/bin/gpiofind ]; then
|
||||||
else
|
gpioset `gpiofind "sim=${modemnb}"`=0
|
||||||
gpioset -t0 sim${modemnb}=0
|
else
|
||||||
|
gpioset -t0 sim${modemnb}=0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# stop modem
|
# stop modem
|
||||||
|
|||||||
Reference in new issue
Block a user