Compare commits

...
42 Commits
Author SHA1 Message Date
action d98dc86e1e update 2026-09-16 00:04:48 2026-09-16 00:04:48 +08:00
action b109228443 update 2026-09-15 17:08:00 2026-09-15 17:08:00 +08:00
action bb8ab7ae16 update 2026-09-15 11:00:48 2026-09-15 11:00:48 +08:00
action c323f3b64e update 2026-09-15 01:28:15 2026-09-15 01:28:15 +08:00
action fcd7d31b29 update 2026-09-14 17:28:54 2026-09-14 17:28:54 +08:00
action 87faec277a update 2026-09-14 10:58:48 2026-09-14 10:58:48 +08:00
action d341a782d4 update 2026-09-14 06:01:57 2026-09-14 06:01:57 +08:00
action 205be274cd update 2026-09-14 02:28:52 2026-09-14 02:28:52 +08:00
action 622dd92021 update 2026-09-13 23:28:56 2026-09-13 23:28:56 +08:00
action 141863e6d4 update 2026-09-13 16:42:22 2026-09-13 16:42:22 +08:00
action 2e55e363ac update 2026-09-13 10:43:38 2026-09-13 10:43:38 +08:00
action 86d911baf4 update 2026-09-13 05:53:41 2026-09-13 05:53:41 +08:00
action 65075cb38f update 2026-09-13 02:01:59 2026-09-13 02:01:59 +08:00
action aff13cf0c6 update 2026-09-12 22:51:39 2026-09-12 22:51:39 +08:00
action 7497dff620 update 2026-09-12 16:17:58 2026-09-12 16:17:58 +08:00
action 639738b05d update 2026-09-12 10:45:42 2026-09-12 10:45:42 +08:00
action 419c580042 update 2026-09-12 06:09:18 2026-09-12 06:09:18 +08:00
action 7a00e14e57 update 2026-09-12 02:55:35 2026-09-12 02:55:35 +08:00
action 8daa9142b3 update 2026-09-11 23:43:33 2026-09-11 23:43:33 +08:00
action 42846bd15f update 2026-09-11 16:29:42 2026-09-11 16:29:42 +08:00
action e964d3fab4 update 2026-09-11 10:36:18 2026-09-11 10:36:18 +08:00
action 7a1c066f8e update 2026-09-11 06:12:18 2026-09-11 06:12:18 +08:00
action 7f998ecef4 update 2026-09-11 02:53:12 2026-09-11 02:53:12 +08:00
action eb846719e6 update 2026-09-10 23:42:03 2026-09-10 23:42:03 +08:00
action 11dbae0135 update 2026-09-10 16:35:06 2026-09-10 16:35:06 +08:00
action 52a65b099b update 2026-09-10 10:43:10 2026-09-10 10:43:10 +08:00
action eda104d43a update 2026-09-10 06:10:14 2026-09-10 06:10:14 +08:00
action be31648233 update 2026-09-10 03:01:51 2026-09-10 03:01:51 +08:00
action 542084a121 update 2026-09-09 23:46:59 2026-09-09 23:47:00 +08:00
action fe313e7d23 update 2026-09-09 16:33:06 2026-09-09 16:33:06 +08:00
action 09f0910b78 update 2026-09-09 03:12:17 2026-09-09 03:12:17 +08:00
action 8778465a55 update 2026-09-08 23:51:28 2026-09-08 23:51:28 +08:00
action b81fb0f1c5 update 2026-09-08 16:30:31 2026-09-08 16:30:31 +08:00
action f12aa6537d update 2026-09-08 10:40:39 2026-09-08 10:40:39 +08:00
action 9e7a5c5caf update 2026-09-08 06:27:12 2026-09-08 06:27:12 +08:00
action 28c0067490 update 2026-09-08 01:05:17 2026-09-08 01:05:17 +08:00
action 2ff8b68fd7 update 2026-09-07 16:50:29 2026-09-07 16:50:29 +08:00
action e7e55d191d update 2026-09-07 10:27:50 2026-09-07 10:27:50 +08:00
action 344aea7ec2 update 2026-09-07 05:54:25 2026-09-07 05:54:25 +08:00
action 4a4dfac1a2 update 2026-09-07 01:57:13 2026-09-07 01:57:13 +08:00
action 7b5d12b8ec update 2026-09-06 22:47:17 2026-09-06 22:47:17 +08:00
action 51ed2622f8 update 2026-09-06 16:17:35 2026-09-06 16:17:35 +08:00
207 changed files with 8780 additions and 4965 deletions
+2 -2
View File
@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=brook PKG_NAME:=brook
PKG_VERSION:=20260101.0 PKG_VERSION:=20270101
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/txthinking/brook/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/txthinking/brook/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=8ddba4ed9ae9d10928e169f8121c6791e0e3c2907fa27d6e0055fe434f6e700e PKG_HASH:=43d8e5476918daa2d35fc63e8b0c94c0c1df8577f1d09103a3ab0f6141f29c0f
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org> PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_LICENSE:=GPL-3.0 PKG_LICENSE:=GPL-3.0
+4 -4
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=clashoo PKG_NAME:=clashoo
PKG_SHORT_SHA:=fd74ecb PKG_SHORT_SHA:=ab405ba
PKG_COMMIT_DATE:=2026.09.05 PKG_COMMIT_DATE:=2026.09.14
PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA) PKG_VERSION:=$(PKG_COMMIT_DATE)~$(PKG_SHORT_SHA)
PKG_RELEASE:=1 PKG_RELEASE:=1
@@ -11,8 +11,8 @@ PKG_SOURCE:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA).tar.gz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-alpha-$(PKG_SHORT_SHA) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-alpha-$(PKG_SHORT_SHA)
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src
PKG_SOURCE_VERSION:=fd74ecb6e22bbdba17a4d7441531fea69ee43036 PKG_SOURCE_VERSION:=ab405bad5beeeac8b003bb01f60f134f6df54471
PKG_HASH:=b88239c01871ba771b1e80caadf247feab42130783f5ab40be0c9fe27a9cc836 PKG_HASH:=bd7a8035d67755720178a8fd2c49a9d82a4578493aae80ae46d5e6d3141cfaee
PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA) PKG_BUILD_VERSION:=alpha-$(PKG_SHORT_SHA)
PKG_LICENSE:=GPL3.0+ PKG_LICENSE:=GPL3.0+
+3
View File
@@ -47,6 +47,9 @@ config clashoo 'config'
option fallback_filter_geoip '0' option fallback_filter_geoip '0'
list fallback_filter_ipcidr '240.0.0.0/4' list fallback_filter_ipcidr '240.0.0.0/4'
option dns_respect_rules '1' option dns_respect_rules '1'
option dns_loopback_compat '0'
list dns_loopback_compat_resolver 'udp://223.5.5.5:53'
list dns_loopback_compat_resolver 'udp://119.29.29.29:53'
option dns_migrated '1' option dns_migrated '1'
option block_quic '0' option block_quic '0'
option singbox_independent_cache '0' option singbox_independent_cache '0'
+113 -6
View File
@@ -790,6 +790,11 @@ _dnsmasq_state_digest() {
"$(uci -q get dhcp.@dnsmasq[0].resolvfile 2>/dev/null)" "$(uci -q get dhcp.@dnsmasq[0].resolvfile 2>/dev/null)"
} }
_reload_dnsmasq() {
/etc/init.d/dnsmasq reload >/dev/null 2>&1 && return 0
/etc/init.d/dnsmasq restart >/dev/null 2>&1
}
revert_dns() { revert_dns() {
#=========================================================================================================================== #===========================================================================================================================
local _before _after local _before _after
@@ -803,7 +808,7 @@ revert_dns() {
return 0 return 0
fi fi
uci commit dhcp uci commit dhcp
/etc/init.d/dnsmasq restart >/dev/null 2>&1 _reload_dnsmasq >/dev/null 2>&1
#=========================================================================================================================== #===========================================================================================================================
} }
@@ -1040,6 +1045,99 @@ _clashoo_dns_force_fields() {
return 0 return 0
} }
_dns_nameserver_inherit_mode() {
local has_nameserver marker prefer_h3 respect_false has_proxy force_respect
command -v yq >/dev/null 2>&1 || return 0
[ -s "$CONFIG_YAML" ] || return 0
has_nameserver="$(yq -M e '(.dns.nameserver // null) | (tag != "!!null" and length > 0)' "$CONFIG_YAML" 2>/dev/null)"
[ "$has_nameserver" = "true" ] || return 0
marker=0
if yq -M e -r '(.dns.nameserver // null) | .. | select(tag == "!!str")' "$CONFIG_YAML" 2>/dev/null |
awk '
{
hash = index($0, "#")
if (!hash) next
fragment = substr($0, hash + 1)
split(fragment, parts, "&")
if (parts[1] !~ /=/) { found = 1; exit }
}
END { exit(found ? 0 : 1) }'; then
marker=1
fi
prefer_h3="$(yq -M e '(.dns."prefer-h3" // false) == true' "$CONFIG_YAML" 2>/dev/null)"
respect_false="$(yq -M e '((.dns // {}) | has("respect-rules")) and ((.dns."respect-rules") == false or (.dns."respect-rules") == "false")' "$CONFIG_YAML" 2>/dev/null)"
has_proxy="$(yq -M e '(.dns // {}) | has("proxy-server-nameserver")' "$CONFIG_YAML" 2>/dev/null)"
force_respect=0
case ",${CLASHOO_DNS_FORCE:-}," in
*,respect-rules,*) force_respect=1 ;;
esac
# skip suppresses both the generated proxy role and automatic respect-rules.
if [ "$marker" = "1" ] ||
{ [ "$prefer_h3" = "true" ] && [ "$force_respect" != "1" ]; } ||
{ [ "$respect_false" = "true" ] && [ "$force_respect" != "1" ]; } ||
[ "$(uci -q get clashoo.config.dns_respect_rules 2>/dev/null)" = "0" ]; then
printf '%s' skip
return 0
fi
case ",${CLASHOO_DNS_FORCE:-}," in
*,proxy-server-nameserver,*) return 0 ;;
esac
[ "$has_proxy" = "true" ] && return 0
printf '%s' inherit
}
_adapt_dns_loopback_resolver() {
local filter listen host host_pattern old_port servers replacement tmp
[ "$(uci -q get clashoo.config.dns_loopback_compat 2>/dev/null)" = "1" ] || return 0
command -v yq >/dev/null 2>&1 || return 0
[ -s "$CONFIG_YAML" ] || return 0
filter="${CLASHOO_DNS_LOOPBACK_FILTER:-/usr/share/clashoo/runtime/dns_loopback_adapter.yq}"
[ -r "$filter" ] || return 0
[ "$(yq -M e '(.dns."proxy-server-nameserver" | type)' "$CONFIG_YAML" 2>/dev/null)" = "!!seq" ] || return 0
listen="$(yq -M e -r '.dns.listen // ""' "$CONFIG_YAML" 2>/dev/null)"
old_port="${listen##*:}"
host="${listen%:*}"
printf '%s' "$old_port" | grep -Eq '^[0-9]+$' || return 0
host="$(printf '%s' "$host" | tr 'A-Z' 'a-z')"
case "$host" in
""|0.0.0.0|'[::]'|::)
host_pattern='(localhost|127(\.[0-9]{1,3}){3}|\[?::1\]?)' ;;
localhost)
host_pattern='localhost' ;;
127.*)
printf '%s' "$host" | grep -Eq '^127(\.[0-9]{1,3}){3}$' || return 0
host_pattern="$(printf '%s' "$host" | sed 's/\./\\./g')" ;;
'[::1]'|::1)
host_pattern='\[?::1\]?' ;;
*)
return 0 ;;
esac
servers="$(yq -M e -r '.dns."proxy-server-nameserver"[]' "$CONFIG_YAML" 2>/dev/null)"
printf '%s\n' "$servers" | grep -Eiq "^([A-Za-z][A-Za-z0-9+.-]*://)?(${host_pattern}):${old_port}($|[/#?])" || return 0
replacement="$(uci -q get clashoo.config.dns_loopback_compat_resolver 2>/dev/null)"
[ -n "$replacement" ] || replacement='udp://223.5.5.5:53 udp://119.29.29.29:53'
tmp="$(mktemp "${CONFIG_YAML}.dns-loopback.XXXXXX" 2>/dev/null)" || return 0
export CLASHOO_DNS_OLD_PORT="$old_port"
export CLASHOO_DNS_HOST_PATTERN="$host_pattern"
export CLASHOO_DNS_REPLACEMENT="$replacement"
if yq -M e --from-file "$filter" "$CONFIG_YAML" > "$tmp" 2>/dev/null &&
mv -f "$tmp" "$CONFIG_YAML"; then
log_msg "Adapted stale loopback proxy DNS" "已适配失效的回环节点 DNS"
else
rm -f "$tmp"
log_msg "Loopback proxy DNS adaptation failed; preserved source settings" "回环节点 DNS 适配失败,已保留原配置"
fi
unset CLASHOO_DNS_OLD_PORT CLASHOO_DNS_HOST_PATTERN CLASHOO_DNS_REPLACEMENT
return 0
}
backfill_custom_sniffer_overrides() { backfill_custom_sniffer_overrides() {
[ "$CLASHOO_KEEP_SNIFFER" = "1" ] || return 0 [ "$CLASHOO_KEEP_SNIFFER" = "1" ] || return 0
[ "$(uci -q get clashoo.config.sniffer_streaming 2>/dev/null)" = "1" ] || return 0 [ "$(uci -q get clashoo.config.sniffer_streaming 2>/dev/null)" = "1" ] || return 0
@@ -1062,8 +1160,15 @@ ensure_quic_fallback_guard() {
yml_change() { yml_change() {
CLASHOO_DNS_FORCE="$(_clashoo_dns_force_fields)" CLASHOO_DNS_FORCE="$(_clashoo_dns_force_fields)"
CLASHOO_DNS_PRESENT="$(_user_dns_fields "$CLASHOO_DNS_FORCE")" CLASHOO_DNS_PRESENT="$(_user_dns_fields "$CLASHOO_DNS_FORCE")"
CLASHOO_DNS_INHERIT_MODE="$(_dns_nameserver_inherit_mode)"
CLASHOO_DNS_INHERIT_NAMESERVER=""
if [ "$CLASHOO_DNS_INHERIT_MODE" = "inherit" ]; then
CLASHOO_DNS_INHERIT_NAMESERVER="$(yq -M e -o=json '(.dns.nameserver // [])' "$CONFIG_YAML" 2>/dev/null)"
fi
export CLASHOO_DNS_FORCE export CLASHOO_DNS_FORCE
export CLASHOO_DNS_PRESENT export CLASHOO_DNS_PRESENT
export CLASHOO_DNS_INHERIT_MODE
export CLASHOO_DNS_INHERIT_NAMESERVER
CLASHOO_KEEP_FAKEIP_FILTER="" CLASHOO_KEEP_FAKEIP_FILTER=""
CLASHOO_KEEP_SNIFFER="" CLASHOO_KEEP_SNIFFER=""
if command -v yq >/dev/null 2>&1 && [ -s "$CONFIG_YAML" ]; then if command -v yq >/dev/null 2>&1 && [ -s "$CONFIG_YAML" ]; then
@@ -1583,8 +1688,7 @@ singbox_dns_change() {
fi fi
if [ "$(_dnsmasq_state_digest)" != "$_sb_dns_before" ]; then if [ "$(_dnsmasq_state_digest)" != "$_sb_dns_before" ]; then
uci commit dhcp >/dev/null 2>&1 uci commit dhcp >/dev/null 2>&1
/etc/init.d/dnsmasq stop >/dev/null 2>&1 _reload_dnsmasq >/dev/null 2>&1
/etc/init.d/dnsmasq start >/dev/null 2>&1
else else
uci -q revert dhcp >/dev/null 2>&1 uci -q revert dhcp >/dev/null 2>&1
fi fi
@@ -1880,6 +1984,7 @@ _start_prepare_config() {
if command -v yq >/dev/null 2>&1 && grep -q '<<[[:space:]]*:' "$CONFIG_YAML" 2>/dev/null; then if command -v yq >/dev/null 2>&1 && grep -q '<<[[:space:]]*:' "$CONFIG_YAML" 2>/dev/null; then
yq e -i 'explode(.)' "$CONFIG_YAML" 2>/dev/null || true yq e -i 'explode(.)' "$CONFIG_YAML" 2>/dev/null || true
fi fi
_adapt_dns_loopback_resolver >/dev/null 2>&1
# stage bundled cn.mrs into the core dir; mihomo rejects rule-set paths outside /etc/clashoo # stage bundled cn.mrs into the core dir; mihomo rejects rule-set paths outside /etc/clashoo
if [ -f /usr/share/clashoo/ruleset/cn.mrs ]; then if [ -f /usr/share/clashoo/ruleset/cn.mrs ]; then
if [ ! -f /etc/clashoo/ruleset/cn.mrs ] || [ /usr/share/clashoo/ruleset/cn.mrs -nt /etc/clashoo/ruleset/cn.mrs ]; then if [ ! -f /etc/clashoo/ruleset/cn.mrs ] || [ /usr/share/clashoo/ruleset/cn.mrs -nt /etc/clashoo/ruleset/cn.mrs ]; then
@@ -1993,8 +2098,7 @@ _start_launch_core() {
if [ "$(_dnsmasq_state_digest)" != "$_dnsmasq_digest_before" ]; then if [ "$(_dnsmasq_state_digest)" != "$_dnsmasq_digest_before" ]; then
log_msg "Reloading DNS resolver" "重启 DNS 解析" log_msg "Reloading DNS resolver" "重启 DNS 解析"
/etc/init.d/dnsmasq stop >/dev/null 2>&1 _reload_dnsmasq >/dev/null 2>&1
/etc/init.d/dnsmasq start >/dev/null 2>&1
fi fi
restore >/dev/null 2>&1 restore >/dev/null 2>&1
@@ -2041,7 +2145,7 @@ boot(){
runtime_state_set "health_status" "starting" runtime_state_set "health_status" "starting"
runtime_state_set "health_detail" "boot_starting" runtime_state_set "health_detail" "boot_starting"
runtime_state_set "updated_at" "$(date +%s)" runtime_state_set "updated_at" "$(date +%s)"
# start_service does mihomo -t dry-run + firewall restart + dnsmasq restart # start_service does mihomo -t dry-run + firewall restart + dnsmasq reload/fallback
# (~20-30s on LXC). Doing this synchronously in boot() blocks rcS S99 stage # (~20-30s on LXC). Doing this synchronously in boot() blocks rcS S99 stage
# and during that window sshd/uhttpd refuse connections (kex reset). Detach # and during that window sshd/uhttpd refuse connections (kex reset). Detach
# to background with a small settle delay so the rcS pipeline finishes and # to background with a small settle delay so the rcS pipeline finishes and
@@ -2283,6 +2387,9 @@ reload_service()
-X PUT -d '{"path":"'"$CONFIG_YAML"'"}' \ -X PUT -d '{"path":"'"$CONFIG_YAML"'"}' \
"http://${_lip}:${_dp}/configs?force=true" >/dev/null 2>&1 "http://${_lip}:${_dp}/configs?force=true" >/dev/null 2>&1
backend_apply_rules 2>/dev/null backend_apply_rules 2>/dev/null
if [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" != "1" ]; then
_reload_dnsmasq >/dev/null 2>&1
fi
runtime_run_health_check_settle >/dev/null 2>&1 runtime_run_health_check_settle >/dev/null 2>&1
fi fi
} }
@@ -6,12 +6,14 @@ set clashoo_china6 {
2001:250::/30, 2001:250::/30,
2001:254::/33, 2001:254::/33,
2001:255::/32, 2001:255::/32,
2001:7fa:5::/48,
2001:c68::/32, 2001:c68::/32,
2001:cc0::/32, 2001:cc0::/32,
2001:da8::/32, 2001:da8::/32,
2001:daa:1::/48, 2001:daa:1::/48,
2001:daa:2::/47, 2001:daa:2::/47,
2001:daa:4::/47, 2001:daa:4::/47,
2001:daa:6::/48,
2001:daa:9::/48, 2001:daa:9::/48,
2001:dc7::/45, 2001:dc7::/45,
2001:dc7:9::/48, 2001:dc7:9::/48,
@@ -117,7 +119,6 @@ set clashoo_china6 {
2400:5280:1400::/38, 2400:5280:1400::/38,
2400:5280:1c00::/38, 2400:5280:1c00::/38,
2400:5280:3400::/38, 2400:5280:3400::/38,
2400:5280:4000::/38,
2400:5280:4c00::/38, 2400:5280:4c00::/38,
2400:5280:5000::/36, 2400:5280:5000::/36,
2400:5280:6000::/35, 2400:5280:6000::/35,
@@ -757,9 +758,7 @@ set clashoo_china6 {
2401:f860:10::/44, 2401:f860:10::/44,
2401:f860:20::/43, 2401:f860:20::/43,
2401:f860:40::/42, 2401:f860:40::/42,
2401:f860:80::/46, 2401:f860:80::/45,
2401:f860:84::/48,
2401:f860:86::/47,
2401:f860:89::/48, 2401:f860:89::/48,
2401:f860:8a::/47, 2401:f860:8a::/47,
2401:f860:8c::/46, 2401:f860:8c::/46,
@@ -781,7 +780,6 @@ set clashoo_china6 {
2401:f860:2000::/35, 2401:f860:2000::/35,
2401:f860:4000::/34, 2401:f860:4000::/34,
2401:f860:8000::/33, 2401:f860:8000::/33,
2401:fa00:40::/48,
2401:fa80::/32, 2401:fa80::/32,
2401:fb80::/32, 2401:fb80::/32,
2401:fc60::/32, 2401:fc60::/32,
@@ -939,7 +937,9 @@ set clashoo_china6 {
2402:7140::/32, 2402:7140::/32,
2402:71c0::/32, 2402:71c0::/32,
2402:7240::/32, 2402:7240::/32,
2402:72a0::/32,
2402:72c0::/32, 2402:72c0::/32,
2402:73e0::/32,
2402:7540::/32, 2402:7540::/32,
2402:75c0::/32, 2402:75c0::/32,
2402:7740::/32, 2402:7740::/32,
@@ -1247,11 +1247,12 @@ set clashoo_china6 {
2403:6280::/32, 2403:6280::/32,
2403:62c0::/32, 2403:62c0::/32,
2403:6380::/42, 2403:6380::/42,
2403:6380:41::/48,
2403:6380:43::/48, 2403:6380:43::/48,
2403:6380:44::/46, 2403:6380:44::/46,
2403:6380:48::/45, 2403:6380:48::/45,
2403:6380:50::/44, 2403:6380:50::/44,
2403:6380:70::/44, 2403:6380:60::/43,
2403:6380:80::/41, 2403:6380:80::/41,
2403:6380:100::/40, 2403:6380:100::/40,
2403:6380:200::/39, 2403:6380:200::/39,
@@ -1458,15 +1459,7 @@ set clashoo_china6 {
2404:1880::/32, 2404:1880::/32,
2404:1c80::/32, 2404:1c80::/32,
2404:1cc0::/32, 2404:1cc0::/32,
2404:1d80::/44, 2404:1d80::/39,
2404:1d80:10::/45,
2404:1d80:18::/47,
2404:1d80:1b::/48,
2404:1d80:1c::/46,
2404:1d80:20::/43,
2404:1d80:40::/42,
2404:1d80:80::/41,
2404:1d80:100::/40,
2404:1d80:200::/44, 2404:1d80:200::/44,
2404:1d80:210::/47, 2404:1d80:210::/47,
2404:1d80:212::/48, 2404:1d80:212::/48,
@@ -1765,10 +1758,9 @@ set clashoo_china6 {
2405:84c0:8008::/45, 2405:84c0:8008::/45,
2405:84c0:8010::/48, 2405:84c0:8010::/48,
2405:84c0:8019::/48, 2405:84c0:8019::/48,
2405:84c0:801b::/48,
2405:84c0:801c::/46, 2405:84c0:801c::/46,
2405:84c0:8020::/47, 2405:84c0:8020::/47,
2405:84c0:8034::/46, 2405:84c0:8036::/47,
2405:84c0:8038::/46, 2405:84c0:8038::/46,
2405:84c0:803c::/47, 2405:84c0:803c::/47,
2405:84c0:803e::/48, 2405:84c0:803e::/48,
@@ -1943,7 +1935,13 @@ set clashoo_china6 {
2406:840:190::/44, 2406:840:190::/44,
2406:840:1a0::/43, 2406:840:1a0::/43,
2406:840:1c0::/42, 2406:840:1c0::/42,
2406:840:200::/41, 2406:840:201::/48,
2406:840:202::/47,
2406:840:204::/46,
2406:840:208::/45,
2406:840:210::/44,
2406:840:220::/43,
2406:840:240::/42,
2406:840:280::/42, 2406:840:280::/42,
2406:840:2c0::/43, 2406:840:2c0::/43,
2406:840:2e1::/48, 2406:840:2e1::/48,
@@ -1952,7 +1950,6 @@ set clashoo_china6 {
2406:840:2e8::/45, 2406:840:2e8::/45,
2406:840:2f0::/44, 2406:840:2f0::/44,
2406:840:300::/41, 2406:840:300::/41,
2406:840:381::/48,
2406:840:382::/47, 2406:840:382::/47,
2406:840:384::/46, 2406:840:384::/46,
2406:840:388::/45, 2406:840:388::/45,
@@ -2041,7 +2038,7 @@ set clashoo_china6 {
2406:840:a18::/45, 2406:840:a18::/45,
2406:840:a20::/44, 2406:840:a20::/44,
2406:840:a30::/48, 2406:840:a30::/48,
2406:840:a32::/47, 2406:840:a33::/48,
2406:840:a34::/46, 2406:840:a34::/46,
2406:840:a38::/45, 2406:840:a38::/45,
2406:840:a40::/42, 2406:840:a40::/42,
@@ -2274,18 +2271,7 @@ set clashoo_china6 {
2406:840:9fc0::/43, 2406:840:9fc0::/43,
2406:840:9fe0::/44, 2406:840:9fe0::/44,
2406:840:a000::/35, 2406:840:a000::/35,
2406:840:c000::/41, 2406:840:c000::/36,
2406:840:c080::/43,
2406:840:c0a0::/45,
2406:840:c0a9::/48,
2406:840:c0aa::/47,
2406:840:c0ac::/46,
2406:840:c0b0::/44,
2406:840:c0c0::/42,
2406:840:c100::/40,
2406:840:c200::/39,
2406:840:c400::/38,
2406:840:c800::/37,
2406:840:e000::/41, 2406:840:e000::/41,
2406:840:e090::/44, 2406:840:e090::/44,
2406:840:e0a0::/43, 2406:840:e0a0::/43,
@@ -2294,7 +2280,7 @@ set clashoo_china6 {
2406:840:e0cc::/47, 2406:840:e0cc::/47,
2406:840:e0ce::/48, 2406:840:e0ce::/48,
2406:840:e0d0::/44, 2406:840:e0d0::/44,
2406:840:e0e6::/47, 2406:840:e0e4::/46,
2406:840:e0e9::/48, 2406:840:e0e9::/48,
2406:840:e0ea::/47, 2406:840:e0ea::/47,
2406:840:e0ec::/46, 2406:840:e0ec::/46,
@@ -2353,7 +2339,10 @@ set clashoo_china6 {
2406:840:e57f::/48, 2406:840:e57f::/48,
2406:840:e580::/41, 2406:840:e580::/41,
2406:840:e610::/44, 2406:840:e610::/44,
2406:840:e620::/43, 2406:840:e622::/47,
2406:840:e624::/46,
2406:840:e628::/45,
2406:840:e630::/44,
2406:840:e640::/43, 2406:840:e640::/43,
2406:840:e660::/46, 2406:840:e660::/46,
2406:840:e664::/47, 2406:840:e664::/47,
@@ -2436,14 +2425,7 @@ set clashoo_china6 {
2406:840:f120::/43, 2406:840:f120::/43,
2406:840:f140::/42, 2406:840:f140::/42,
2406:840:f180::/41, 2406:840:f180::/41,
2406:840:f201::/48, 2406:840:f200::/40,
2406:840:f202::/48,
2406:840:f204::/46,
2406:840:f208::/45,
2406:840:f210::/44,
2406:840:f220::/43,
2406:840:f240::/42,
2406:840:f280::/41,
2406:840:f300::/43, 2406:840:f300::/43,
2406:840:f320::/44, 2406:840:f320::/44,
2406:840:f330::/46, 2406:840:f330::/46,
@@ -2471,6 +2453,10 @@ set clashoo_china6 {
2406:840:f600::/42, 2406:840:f600::/42,
2406:840:f640::/43, 2406:840:f640::/43,
2406:840:f670::/44, 2406:840:f670::/44,
2406:840:f680::/47,
2406:840:f682::/48,
2406:840:f684::/46,
2406:840:f688::/45,
2406:840:f690::/44, 2406:840:f690::/44,
2406:840:f6a0::/43, 2406:840:f6a0::/43,
2406:840:f6c0::/42, 2406:840:f6c0::/42,
@@ -2527,13 +2513,7 @@ set clashoo_china6 {
2406:840:fc68::/45, 2406:840:fc68::/45,
2406:840:fc74::/46, 2406:840:fc74::/46,
2406:840:fc78::/45, 2406:840:fc78::/45,
2406:840:fc80::/48, 2406:840:fc80::/44,
2406:840:fc82::/48,
2406:840:fc84::/48,
2406:840:fc86::/48,
2406:840:fc88::/46,
2406:840:fc8c::/47,
2406:840:fc8e::/48,
2406:840:fc94::/46, 2406:840:fc94::/46,
2406:840:fc98::/45, 2406:840:fc98::/45,
2406:840:fca4::/46, 2406:840:fca4::/46,
@@ -2612,19 +2592,17 @@ set clashoo_china6 {
2406:840:fe52::/47, 2406:840:fe52::/47,
2406:840:fe54::/46, 2406:840:fe54::/46,
2406:840:fe58::/45, 2406:840:fe58::/45,
2406:840:fe60::/44, 2406:840:fe60::/43,
2406:840:fe70::/47,
2406:840:fe72::/48,
2406:840:fe75::/48,
2406:840:fe76::/47,
2406:840:fe78::/45,
2406:840:fe80::/44, 2406:840:fe80::/44,
2406:840:fe95::/48, 2406:840:fe95::/48,
2406:840:fe9c::/48, 2406:840:fe9c::/48,
2406:840:fe9f::/48, 2406:840:fe9f::/48,
2406:840:fea0::/48, 2406:840:fea0::/48,
2406:840:fea9::/48, 2406:840:fea9::/48,
2406:840:feb0::/44, 2406:840:feb0::/47,
2406:840:feb2::/48,
2406:840:feb4::/46,
2406:840:feb8::/45,
2406:840:fec1::/48, 2406:840:fec1::/48,
2406:840:fec6::/47, 2406:840:fec6::/47,
2406:840:fec8::/47, 2406:840:fec8::/47,
@@ -3070,7 +3048,6 @@ set clashoo_china6 {
2408:4008::/29, 2408:4008::/29,
2408:4010::/30, 2408:4010::/30,
2408:4014::/31, 2408:4014::/31,
2408:4016:1::/48,
2408:4016:2::/47, 2408:4016:2::/47,
2408:4016:4::/46, 2408:4016:4::/46,
2408:4016:8::/45, 2408:4016:8::/45,
@@ -3236,7 +3213,6 @@ set clashoo_china6 {
2605:9d80:9042::/48, 2605:9d80:9042::/48,
2605:9d80:9071::/48, 2605:9d80:9071::/48,
2605:9d80:9092::/48, 2605:9d80:9092::/48,
2804:1e48:9001::/48,
2804:1e48:9002::/48, 2804:1e48:9002::/48,
2a03:f900::/29, 2a03:f900::/29,
2a04:3e00:1002::/48, 2a04:3e00:1002::/48,
@@ -3301,10 +3277,7 @@ set clashoo_china6 {
2a0a:d681:e200::/39, 2a0a:d681:e200::/39,
2a0a:d681:e400::/38, 2a0a:d681:e400::/38,
2a0a:d681:e800::/37, 2a0a:d681:e800::/37,
2a0a:d681:f000::/37, 2a0a:d681:f000::/36,
2a0a:d681:f800::/39,
2a0a:d681:fa00::/40,
2a0a:d681:fd00::/40,
2a0a:d682::/33, 2a0a:d682::/33,
2a0a:d682:8000::/34, 2a0a:d682:8000::/34,
2a0a:d682:c000::/36, 2a0a:d682:c000::/36,
@@ -427,7 +427,6 @@ set clashoo_china {
43.240.128.0/21, 43.240.128.0/21,
43.240.136.0/22, 43.240.136.0/22,
43.240.156.0/22, 43.240.156.0/22,
43.240.160.0/19,
43.240.192.0/19, 43.240.192.0/19,
43.240.240.0/20, 43.240.240.0/20,
43.241.0.0/20, 43.241.0.0/20,
@@ -921,7 +920,9 @@ set clashoo_china {
59.153.136.0/22, 59.153.136.0/22,
59.153.152.0/22, 59.153.152.0/22,
59.153.164.0/22, 59.153.164.0/22,
59.153.168.0/21, 59.153.168.0/22,
59.153.173.0/24,
59.153.174.0/23,
59.153.176.0/20, 59.153.176.0/20,
59.153.192.0/22, 59.153.192.0/22,
59.155.0.0/16, 59.155.0.0/16,
@@ -952,6 +953,7 @@ set clashoo_china {
61.29.128.0/18, 61.29.128.0/18,
61.29.192.0/19, 61.29.192.0/19,
61.29.224.0/20, 61.29.224.0/20,
61.29.251.0/24,
61.45.128.0/18, 61.45.128.0/18,
61.45.224.0/20, 61.45.224.0/20,
61.47.128.0/18, 61.47.128.0/18,
@@ -1325,7 +1327,7 @@ set clashoo_china {
103.36.96.0/22, 103.36.96.0/22,
103.36.132.0/22, 103.36.132.0/22,
103.36.136.0/22, 103.36.136.0/22,
103.36.160.0/19, 103.36.160.0/20,
103.36.192.0/19, 103.36.192.0/19,
103.36.224.0/20, 103.36.224.0/20,
103.36.240.0/21, 103.36.240.0/21,
@@ -1630,6 +1632,7 @@ set clashoo_china {
103.70.14.0/23, 103.70.14.0/23,
103.70.148.0/22, 103.70.148.0/22,
103.70.224.0/23, 103.70.224.0/23,
103.70.227.0/24,
103.70.236.0/22, 103.70.236.0/22,
103.70.252.0/22, 103.70.252.0/22,
103.71.0.0/22, 103.71.0.0/22,
@@ -1733,6 +1736,7 @@ set clashoo_china {
103.81.120.0/22, 103.81.120.0/22,
103.81.148.0/22, 103.81.148.0/22,
103.81.164.0/22, 103.81.164.0/22,
103.81.184.0/23,
103.81.200.0/22, 103.81.200.0/22,
103.81.232.0/22, 103.81.232.0/22,
103.82.60.0/22, 103.82.60.0/22,
@@ -1879,6 +1883,7 @@ set clashoo_china {
103.97.144.0/21, 103.97.144.0/21,
103.97.188.0/22, 103.97.188.0/22,
103.97.192.0/22, 103.97.192.0/22,
103.97.229.0/24,
103.98.0.0/23, 103.98.0.0/23,
103.98.40.0/21, 103.98.40.0/21,
103.98.48.0/22, 103.98.48.0/22,
@@ -2184,6 +2189,7 @@ set clashoo_china {
103.143.132.0/22, 103.143.132.0/22,
103.143.174.0/23, 103.143.174.0/23,
103.143.228.0/23, 103.143.228.0/23,
103.144.41.0/24,
103.144.66.0/23, 103.144.66.0/23,
103.144.70.0/23, 103.144.70.0/23,
103.144.72.0/23, 103.144.72.0/23,
@@ -2339,7 +2345,7 @@ set clashoo_china {
103.176.244.0/23, 103.176.244.0/23,
103.177.28.0/23, 103.177.28.0/23,
103.177.44.0/23, 103.177.44.0/23,
103.177.70.0/23, 103.177.71.0/24,
103.177.162.0/23, 103.177.162.0/23,
103.178.240.0/23, 103.178.240.0/23,
103.179.76.0/22, 103.179.76.0/22,
@@ -2752,6 +2758,7 @@ set clashoo_china {
103.238.24.0/21, 103.238.24.0/21,
103.238.32.0/21, 103.238.32.0/21,
103.238.40.0/22, 103.238.40.0/22,
103.238.46.0/23,
103.238.48.0/21, 103.238.48.0/21,
103.238.56.0/22, 103.238.56.0/22,
103.238.88.0/21, 103.238.88.0/21,
@@ -2964,7 +2971,10 @@ set clashoo_china {
110.92.68.0/22, 110.92.68.0/22,
110.93.32.0/19, 110.93.32.0/19,
110.94.0.0/15, 110.94.0.0/15,
110.96.0.0/12, 110.96.0.0/15,
110.98.0.0/16,
110.100.0.0/14,
110.104.0.0/13,
110.112.0.0/13, 110.112.0.0/13,
110.120.0.0/16, 110.120.0.0/16,
110.121.0.0/22, 110.121.0.0/22,
@@ -3219,7 +3229,10 @@ set clashoo_china {
114.112.228.0/24, 114.112.228.0/24,
114.112.230.0/23, 114.112.230.0/23,
114.112.234.0/23, 114.112.234.0/23,
114.112.240.0/20, 114.112.240.0/21,
114.112.248.0/22,
114.112.252.0/23,
114.112.255.0/24,
114.113.0.0/17, 114.113.0.0/17,
114.113.128.0/21, 114.113.128.0/21,
114.113.140.0/22, 114.113.140.0/22,
@@ -3388,10 +3401,10 @@ set clashoo_china {
117.134.0.0/17, 117.134.0.0/17,
117.134.128.0/18, 117.134.128.0/18,
117.134.205.0/24, 117.134.205.0/24,
117.134.206.0/23, 117.134.207.0/24,
117.134.208.0/21, 117.134.208.0/23,
117.134.216.0/22, 117.134.212.0/23,
117.134.220.0/23, 117.134.216.0/21,
117.134.232.0/21, 117.134.232.0/21,
117.134.240.0/20, 117.134.240.0/20,
117.135.0.0/16, 117.135.0.0/16,
@@ -3498,7 +3511,13 @@ set clashoo_china {
119.48.0.0/13, 119.48.0.0/13,
119.57.0.0/16, 119.57.0.0/16,
119.58.0.0/16, 119.58.0.0/16,
119.59.128.0/17, 119.59.128.0/24,
119.59.130.0/24,
119.59.132.0/22,
119.59.136.0/21,
119.59.144.0/20,
119.59.160.0/19,
119.59.192.0/18,
119.60.0.0/15, 119.60.0.0/15,
119.62.0.0/16, 119.62.0.0/16,
119.63.32.0/19, 119.63.32.0/19,
@@ -3737,7 +3756,6 @@ set clashoo_china {
123.49.236.0/24, 123.49.236.0/24,
123.49.240.0/24, 123.49.240.0/24,
123.49.242.0/23, 123.49.242.0/23,
123.49.245.0/24,
123.49.248.0/21, 123.49.248.0/21,
123.50.160.0/19, 123.50.160.0/19,
123.52.0.0/14, 123.52.0.0/14,
@@ -3893,6 +3911,7 @@ set clashoo_china {
129.211.0.0/16, 129.211.0.0/16,
132.232.0.0/16, 132.232.0.0/16,
132.237.134.0/24, 132.237.134.0/24,
134.49.240.0/22,
134.175.0.0/16, 134.175.0.0/16,
137.59.59.0/24, 137.59.59.0/24,
137.59.88.0/22, 137.59.88.0/22,
@@ -3971,7 +3990,14 @@ set clashoo_china {
140.179.0.0/16, 140.179.0.0/16,
140.205.0.0/16, 140.205.0.0/16,
140.206.0.0/15, 140.206.0.0/15,
140.210.0.0/16, 140.210.0.0/20,
140.210.16.0/21,
140.210.24.0/22,
140.210.28.0/23,
140.210.30.0/24,
140.210.32.0/19,
140.210.64.0/18,
140.210.128.0/17,
140.224.0.0/16, 140.224.0.0/16,
140.237.0.0/16, 140.237.0.0/16,
140.240.0.0/16, 140.240.0.0/16,
@@ -4203,6 +4229,8 @@ set clashoo_china {
163.0.0.0/16, 163.0.0.0/16,
163.47.4.0/22, 163.47.4.0/22,
163.52.28.0/23, 163.52.28.0/23,
163.52.76.0/23,
163.52.108.0/23,
163.53.0.0/20, 163.53.0.0/20,
163.53.36.0/22, 163.53.36.0/22,
163.53.40.0/21, 163.53.40.0/21,
@@ -4339,12 +4367,6 @@ set clashoo_china {
180.210.212.0/22, 180.210.212.0/22,
180.212.0.0/15, 180.212.0.0/15,
180.222.224.0/19, 180.222.224.0/19,
180.223.196.0/24,
180.223.200.0/22,
180.223.212.0/24,
180.223.232.0/21,
180.223.240.0/21,
180.223.252.0/22,
180.233.0.0/18, 180.233.0.0/18,
180.233.64.0/19, 180.233.64.0/19,
180.233.144.0/22, 180.233.144.0/22,
@@ -4416,14 +4438,14 @@ set clashoo_china {
183.182.12.0/22, 183.182.12.0/22,
183.182.16.0/23, 183.182.16.0/23,
183.182.23.0/24, 183.182.23.0/24,
183.182.24.0/23, 183.182.24.0/21,
183.182.26.0/24,
183.182.28.0/22,
183.184.0.0/13, 183.184.0.0/13,
183.192.0.0/10, 183.192.0.0/10,
185.2.48.0/24,
185.2.51.0/24, 185.2.51.0/24,
185.75.173.0/24, 185.75.173.0/24,
185.75.174.0/24, 185.75.174.0/24,
185.192.56.0/22,
188.131.128.0/17, 188.131.128.0/17,
192.51.172.0/23, 192.51.172.0/23,
192.55.46.0/23, 192.55.46.0/23,
@@ -4654,7 +4676,9 @@ set clashoo_china {
202.55.4.0/24, 202.55.4.0/24,
202.55.18.0/23, 202.55.18.0/23,
202.55.20.0/22, 202.55.20.0/22,
202.55.24.0/21, 202.55.24.0/22,
202.55.28.0/23,
202.55.31.0/24,
202.57.192.0/20, 202.57.192.0/20,
202.57.212.0/22, 202.57.212.0/22,
202.57.216.0/22, 202.57.216.0/22,
@@ -4789,9 +4813,7 @@ set clashoo_china {
202.130.39.0/24, 202.130.39.0/24,
202.130.224.0/19, 202.130.224.0/19,
202.131.16.0/21, 202.131.16.0/21,
202.131.48.0/22, 202.131.48.0/20,
202.131.54.0/23,
202.131.56.0/21,
202.131.208.0/20, 202.131.208.0/20,
202.133.32.0/20, 202.133.32.0/20,
202.134.58.0/24, 202.134.58.0/24,
@@ -4815,6 +4837,8 @@ set clashoo_china {
202.143.56.0/21, 202.143.56.0/21,
202.143.100.0/22, 202.143.100.0/22,
202.143.104.0/22, 202.143.104.0/22,
202.144.196.0/23,
202.144.198.0/24,
202.146.160.0/20, 202.146.160.0/20,
202.146.184.0/23, 202.146.184.0/23,
202.146.186.0/24, 202.146.186.0/24,
@@ -5021,7 +5045,6 @@ set clashoo_china {
203.8.8.0/24, 203.8.8.0/24,
203.8.23.0/24, 203.8.23.0/24,
203.8.25.0/24, 203.8.25.0/24,
203.8.30.0/24,
203.8.70.0/24, 203.8.70.0/24,
203.8.82.0/24, 203.8.82.0/24,
203.8.86.0/23, 203.8.86.0/23,
@@ -5949,6 +5972,7 @@ set clashoo_china {
203.223.16.0/24, 203.223.16.0/24,
203.223.21.0/24, 203.223.21.0/24,
203.223.22.0/23, 203.223.22.0/23,
206.109.70.0/23,
206.109.75.0/24, 206.109.75.0/24,
210.2.0.0/19, 210.2.0.0/19,
210.5.0.0/19, 210.5.0.0/19,
@@ -5971,8 +5995,6 @@ set clashoo_china {
210.26.0.0/15, 210.26.0.0/15,
210.28.0.0/14, 210.28.0.0/14,
210.32.0.0/12, 210.32.0.0/12,
210.48.140.0/23,
210.48.142.0/24,
210.51.0.0/16, 210.51.0.0/16,
210.52.0.0/15, 210.52.0.0/15,
210.56.192.0/19, 210.56.192.0/19,
@@ -6043,7 +6065,9 @@ set clashoo_china {
218.24.0.0/14, 218.24.0.0/14,
218.28.0.0/15, 218.28.0.0/15,
218.30.0.0/19, 218.30.0.0/19,
218.30.32.0/21, 218.30.32.0/22,
218.30.36.0/23,
218.30.38.0/24,
218.30.40.0/22, 218.30.40.0/22,
218.30.45.0/24, 218.30.45.0/24,
218.30.46.0/23, 218.30.46.0/23,
@@ -6154,7 +6178,6 @@ set clashoo_china {
221.128.224.0/21, 221.128.224.0/21,
221.128.232.0/22, 221.128.232.0/22,
221.128.238.0/23, 221.128.238.0/23,
221.128.244.0/22,
221.129.0.0/16, 221.129.0.0/16,
221.130.0.0/15, 221.130.0.0/15,
221.133.224.0/19, 221.133.224.0/19,
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,17 @@
. as $document |
strenv(CLASHOO_DNS_OLD_PORT) as $old_port |
strenv(CLASHOO_DNS_HOST_PATTERN) as $host_pattern |
(strenv(CLASHOO_DNS_REPLACEMENT) | split(" ") | map(select(length > 0))) as $replacement |
("(?i)^(?:[a-z][a-z0-9+.-]*://)?(?:" + $host_pattern + "):" + $old_port + "(?:$|[/#?])") as $old_loopback |
(
$document |
select((.dns."proxy-server-nameserver" | type) == "!!seq") |
([.dns."proxy-server-nameserver"[] | select(type == "!!str" and test($old_loopback))] | length) as $match_count |
select($match_count > 0) |
.dns."proxy-server-nameserver" =
([
.dns."proxy-server-nameserver"[] |
select((type == "!!str" and test($old_loopback)) | not) |
select(. as $server | ($replacement | contains([$server])) | not)
] + $replacement)
) // $document
@@ -12,10 +12,13 @@ trap 'rm -f "$CUSTOM_RULE_FILE" "$RULE" "$CLASH" "$CLASH_CONFIG"' EXIT
[ -f "$CONFIG_YAML" ] || exit 0 [ -f "$CONFIG_YAML" ] || exit 0
. /usr/share/clashoo/runtime/primary_group.sh
# __PROXY__ placeholder -> primary proxy group (cached by RPC); # __PROXY__ placeholder -> primary proxy group (cached by RPC);
# fallback to GLOBAL (built-in mihomo group) # fallback to GLOBAL (built-in mihomo group)
PRIMARY_GROUP="$(uci -q get clashoo.config.primary_proxy_group)" CACHED_PRIMARY_GROUP="$(uci -q get clashoo.config.primary_proxy_group)"
PRIMARY_GROUP="$(clashoo_resolve_primary_group "$CONFIG_YAML" "$CACHED_PRIMARY_GROUP")"
[ -n "$PRIMARY_GROUP" ] || PRIMARY_GROUP="GLOBAL" [ -n "$PRIMARY_GROUP" ] || PRIMARY_GROUP="GLOBAL"
append=$(uci get clashoo.config.append_rules 2>/dev/null) append=$(uci get clashoo.config.append_rules 2>/dev/null)
@@ -119,6 +119,14 @@ for (let f in split(s(getenv('CLASHOO_DNS_PRESENT'), ''), ','))
let dns_force = {}; let dns_force = {};
for (let f in split(s(getenv('CLASHOO_DNS_FORCE'), ''), ',')) for (let f in split(s(getenv('CLASHOO_DNS_FORCE'), ''), ','))
if (length(trim(f))) dns_force[trim(f)] = true; if (length(trim(f))) dns_force[trim(f)] = true;
let dns_inherit_mode = s(getenv('CLASHOO_DNS_INHERIT_MODE'), '');
let inherited_nameserver = json(s(getenv('CLASHOO_DNS_INHERIT_NAMESERVER'), '[]'));
if (type(inherited_nameserver) != 'array') {
if (inherited_nameserver != null && inherited_nameserver != '')
inherited_nameserver = [inherited_nameserver];
else
inherited_nameserver = [];
}
let dns_role_fields = ['nameserver', 'proxy-server-nameserver', 'direct-nameserver', let dns_role_fields = ['nameserver', 'proxy-server-nameserver', 'direct-nameserver',
'default-nameserver', 'nameserver-policy', 'respect-rules']; 'default-nameserver', 'nameserver-policy', 'respect-rules'];
let had_user_dns_roles = false; let had_user_dns_roles = false;
@@ -158,8 +166,15 @@ uci.foreach('clashoo', 'dnsservers', function(sec) {
if (!dns_roles[role]) dns_roles[role] = []; if (!dns_roles[role]) dns_roles[role] = [];
push(dns_roles[role], srv); push(dns_roles[role], srv);
}); });
for (let role in keys(dns_roles)) for (let role in keys(dns_roles)) {
if (role == 'proxy-server-nameserver' && (dns_inherit_mode == 'inherit' || dns_inherit_mode == 'skip') &&
!dns_present[role] && !dns_force[role]) continue;
if (!dns_present[role] || dns_force[role]) cfg['dns'][role] = dns_roles[role]; if (!dns_present[role] || dns_force[role]) cfg['dns'][role] = dns_roles[role];
}
if (dns_inherit_mode == 'inherit' && length(inherited_nameserver) &&
!dns_present['proxy-server-nameserver'] && !dns_force['proxy-server-nameserver'])
cfg['dns']['proxy-server-nameserver'] = inherited_nameserver;
let bootstrap = a('default_nameserver'); let bootstrap = a('default_nameserver');
if (bootstrap == null) bootstrap = a('defaul_nameserver'); if (bootstrap == null) bootstrap = a('defaul_nameserver');
@@ -182,8 +197,10 @@ if (length(keys(policies)) && (!dns_present['nameserver-policy'] || dns_force['n
cfg['dns']['nameserver-policy'] = policies; cfg['dns']['nameserver-policy'] = policies;
let respect_rules = a('dns_respect_rules') == null ? true : ab('dns_respect_rules'); let respect_rules = a('dns_respect_rules') == null ? true : ab('dns_respect_rules');
if (respect_rules && length(cfg['dns']['proxy-server-nameserver'] || []) if (dns_force['respect-rules'])
&& (!dns_present['respect-rules'] || dns_force['respect-rules']) && !dns_present['prefer-h3']) cfg['dns']['respect-rules'] = respect_rules;
else if (respect_rules && length(cfg['dns']['proxy-server-nameserver'] || [])
&& !dns_present['respect-rules'] && !dns_present['prefer-h3'])
cfg['dns']['respect-rules'] = true; cfg['dns']['respect-rules'] = true;
/* profile */ /* profile */
+49
View File
@@ -0,0 +1,49 @@
#!/bin/sh
clashoo_resolve_primary_group() {
local config_yaml="$1"
local cached_group="$2"
local group_rows
local resolved_group
if [ "$cached_group" = "GLOBAL" ]; then
printf '%s\n' "GLOBAL"
return 0
fi
if [ ! -f "$config_yaml" ] || ! command -v yq >/dev/null 2>&1; then
printf '%s\n' "GLOBAL"
return 0
fi
group_rows="$(yq -M e -r '(."proxy-groups" // [])[] | [.name, .type] | @tsv' "$config_yaml" 2>/dev/null)" || {
printf '%s\n' "GLOBAL"
return 0
}
if [ -n "$cached_group" ] && printf '%s\n' "$group_rows" |
awk -F '\t' -v wanted="$cached_group" '$1 == wanted { found = 1; exit } END { exit !found }'
then
printf '%s\n' "$cached_group"
return 0
fi
resolved_group="$(printf '%s\n' "$group_rows" | awk -F '\t' '
tolower($2) == "select" &&
(index($1, "节点选择") || index($1, "代理") || index($1, "🚀") || tolower($1) ~ /proxy/) {
print $1
exit
}
')"
if [ -z "$resolved_group" ]; then
resolved_group="$(printf '%s\n' "$group_rows" | awk -F '\t' '
tolower($2) == "select" {
print $1
exit
}
')"
fi
printf '%s\n' "${resolved_group:-GLOBAL}"
}
@@ -301,9 +301,10 @@ package_version_from_url() {
case "$file" in case "$file" in
clashoo_*_"$ARCH".ipk) clashoo_*_"$ARCH".ipk)
v="${file#clashoo_}" v="${file#clashoo_}"
printf '%s\n' "${v%_${ARCH}.ipk}" v="${v%_${ARCH}.ipk}"
printf '%s\n' "$v" | sed 's/^\([0-9][0-9][0-9][0-9]\.[0-9][0-9]*\.[0-9][0-9]*\)\./\1~/'
;; ;;
clashoo_*.ipk) printf '%s\n' "$file" | sed -n 's/^clashoo_\(.*\)_[^_][^_]*\.ipk$/\1/p' ;; clashoo_*.ipk) printf '%s\n' "$file" | sed -n 's/^clashoo_\(.*\)_[^_][^_]*\.ipk$/\1/p' | sed 's/^\([0-9][0-9][0-9][0-9]\.[0-9][0-9]*\.[0-9][0-9]*\)\./\1~/' ;;
luci-app-clashoo_*.ipk) printf '%s\n' "$file" | sed -n 's/^luci-app-clashoo_\(.*\)_all\.ipk$/\1/p' ;; luci-app-clashoo_*.ipk) printf '%s\n' "$file" | sed -n 's/^luci-app-clashoo_\(.*\)_all\.ipk$/\1/p' ;;
luci-i18n-clashoo-zh-cn_*.ipk) printf '%s\n' "$file" | sed -n 's/^luci-i18n-clashoo-zh-cn_\(.*\)_all\.ipk$/\1/p' ;; luci-i18n-clashoo-zh-cn_*.ipk) printf '%s\n' "$file" | sed -n 's/^luci-i18n-clashoo-zh-cn_\(.*\)_all\.ipk$/\1/p' ;;
clashoo-*.apk) clashoo-*.apk)
+4 -4
View File
@@ -5,13 +5,13 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=dae PKG_NAME:=dae
PKG_VERSION:=2026.08.28 PKG_VERSION:=2026.09.12
PKG_RELEASE:=4 PKG_RELEASE:=1
PKG_SOURCE:=dae-src-2026.08.28-d6aca8a10b35.tar.gz PKG_SOURCE:=dae-src-2026.09.12-187058462a1f.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=d6aca8a10b3549d28ff29979793fc0e074f183957f516a17b50d6f3961730c30 PKG_HASH:=187058462a1fd9eeb9885f4971ccf4bc81358533e71730d8509bd7968624c1c7
PKG_LICENSE:=AGPL-3.0-only PKG_LICENSE:=AGPL-3.0-only
PKG_LICENSE_FILE:=LICENSE PKG_LICENSE_FILE:=LICENSE
+4 -4
View File
@@ -5,13 +5,13 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=daed PKG_NAME:=daed
PKG_VERSION:=2026.08.28 PKG_VERSION:=2026.09.12
PKG_RELEASE:=4 PKG_RELEASE:=1
PKG_SOURCE:=daed-src-2026.08.28-73f01b995c34.tar.gz PKG_SOURCE:=daed-src-2026.09.12-a0181f729855.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=73f01b995c341586fdf7ab6ee8ed7a4cb52b59fd913ca75b1e61b3ecb6c9a350 PKG_HASH:=a0181f7298552497ed193e683a54b1df77f2d5441524d61989f3e3e3cf6eac51
PKG_LICENSE:=AGPL-3.0-only MIT PKG_LICENSE:=AGPL-3.0-only MIT
PKG_LICENSE_FILES:=LICENSE wing/LICENSE PKG_LICENSE_FILES:=LICENSE wing/LICENSE
+83 -4
View File
@@ -1,8 +1,65 @@
#!/bin/sh #!/bin/sh
# daed-cleanup.sh — reaper for stale daed kernel state.
#
# Removes:
# 1. Processes inside the `daens` netns (SIGTERM, then SIGKILL after 1s)
# 2. The `daens` network namespace itself (ip netns del, with
# umount+rm as fallback for zombie nsfs mounts)
# 3. The `dae0` veth pair in the host netns
#
# daed itself owns TC clsact detach. This opkg-side helper only
# reaps stale daens/dae0 state and never removes /sys/fs/bpf/daed.
# The pin directory is created during normal startup, so it is not
# a reliable leak indicator and must not block service lifecycle.
#
# The function stays sourceable by both init.d/daed and daed-guard.
daed_process_probe() {
if command -v pgrep >/dev/null 2>&1; then
pgrep -f '^/usr/bin/daed([[:space:]]|$)' >/dev/null 2>&1
case "$?" in
0) return 0 ;;
1) return 1 ;;
esac
fi
if command -v pidof >/dev/null 2>&1; then
pidof daed >/dev/null 2>&1
case "$?" in
0) return 0 ;;
1) return 1 ;;
esac
fi
return 2
}
daed_cleanup_runtime() { daed_cleanup_runtime() {
local pid local pid rc=0 probe_rc
# If daed userspace is currently running, do not touch the
# netns, the veth, or the eBPF dataplane. They are in active
# use; removing them would make every connection through dae
# hang.
daed_process_probe
probe_rc=$?
case "$probe_rc" in
0)
if [ "${DAED_GUARD_CLEANUP:-start}" = "start" ]; then
logger -t daed-init "cleanup: pre-start skipped because /usr/bin/daed is still running; refusing a second instance"
return 1
fi
logger -t daed-init "cleanup: post-exit skipped because another /usr/bin/daed instance is still running"
return 0
;;
1) ;;
*)
logger -t daed-init "cleanup: cannot determine whether daed is running; refusing to remove netns/veth"
return 1
;;
esac
# 1. Kill processes inside daens.
for pid in $(ip netns pids daens 2>/dev/null); do for pid in $(ip netns pids daens 2>/dev/null); do
kill "$pid" 2>/dev/null kill "$pid" 2>/dev/null
done done
@@ -14,14 +71,36 @@ daed_cleanup_runtime() {
done done
fi fi
# 2. Remove the daens netns. ip netns del can fail if a
# process still references it via /proc/<pid>/ns/net or
# because the umount has already happened. Try the
# umount/rm fallback.
if ! ip netns del daens 2>/dev/null; then if ! ip netns del daens 2>/dev/null; then
umount -l /run/netns/daens 2>/dev/null umount -l /run/netns/daens 2>/dev/null
rm -f /run/netns/daens if [ -e /run/netns/daens ] && ! rm -f /run/netns/daens 2>/dev/null; then
logger -t daed-init "cleanup: failed to remove /run/netns/daens (resource busy); a reboot may be required"
rc=1
fi
fi fi
ip link del dae0 2>/dev/null || true # 3. Remove the dae0 veth pair.
if ip link show dae0 >/dev/null 2>&1; then
if ! ip link del dae0 2>/dev/null; then
logger -t daed-init "cleanup: failed to remove dae0 veth pair"
rc=1
fi
fi
# 4. The pin root is normal persistent state. Never remove it or
# make its existence change the cleanup result.
if [ -e /sys/fs/bpf/daed ]; then
logger -t daed-init "cleanup: /sys/fs/bpf/daed exists; leaving normal pin root unchanged"
fi
# Final verification covers only netns and veth state.
[ ! -e /run/netns/daens ] || return 1 [ ! -e /run/netns/daens ] || return 1
! ip netns list 2>/dev/null | awk '$1 == "daens" { found=1 } END { exit !found }' || return 1 ! ip netns list 2>/dev/null | grep -Eq '^daens([[:space:]]|$)' || return 1
! ip link show dae0 >/dev/null 2>&1 || return 1 ! ip link show dae0 >/dev/null 2>&1 || return 1
return $rc
} }
+118 -16
View File
@@ -1,29 +1,131 @@
#!/bin/sh #!/bin/sh
# Keep daed as a child so signals can be forwarded and stale netns/veth
# state can be cleaned before start and after exit. daed owns TC detach;
# /sys/fs/bpf/daed is normal persistent state and is never removed here.
. /usr/share/daed/cleanup.sh . /usr/share/daed/cleanup.sh
# Pre-start cleanup refuses to remove runtime state while another
# daed instance is active, and fails closed if that probe is broken.
DAED_GUARD_CLEANUP=start
if ! daed_cleanup_runtime; then if ! daed_cleanup_runtime; then
echo "daed: stale network state could not be removed" >&2 echo "daed: stale /usr/bin/daed or netns state could not be verified or removed; refusing to start. Check process and netns state." >&2
logger -t daed-init "pre-start cleanup failed: refusing to start daed"
exit 1 exit 1
fi fi
# Keep daed as a child so a panic or unexpected exit is followed by an # Keep daed as a child so post-exit cleanup runs before procd can
# immediate teardown of all kernel/runtime state before procd can respawn us. # respawn it.
child_pid= child_pid=
cleanup_child() { pending_signal=
[ -n "$child_pid" ] && kill "$child_pid" 2>/dev/null shutdown_signal=
shutdown_elapsed=0
forced_kill=0
child_term_timeout=20
forward_signal() {
local sig="$1"
if [ -z "$child_pid" ]; then
pending_signal="$sig"
logger -t daed-init "signal $sig received before daed child started; launch cancelled"
return 0
fi
case "$sig" in
TERM|INT|QUIT)
if [ -z "$shutdown_signal" ]; then
shutdown_signal="$sig"
shutdown_elapsed=0
fi
;;
esac
kill -"$sig" "$child_pid" 2>/dev/null
} }
trap cleanup_child TERM INT
/usr/bin/daed "$@" & exit_with_signal() {
child_pid=$! local sig="$1"
wait "$child_pid" trap - TERM INT HUP QUIT
status=$? kill -"$sig" "$$" 2>/dev/null
child_pid= exit 1
trap - TERM INT }
if ! daed_cleanup_runtime; then child_is_running() {
echo "daed: runtime cleanup after exit failed" >&2 local state
status=1 kill -0 "$child_pid" 2>/dev/null || return 1
state=$(awk '{ print $3 }' "/proc/$child_pid/stat" 2>/dev/null)
[ "$state" != "Z" ]
}
trap 'forward_signal TERM' TERM
trap 'forward_signal INT' INT
trap 'forward_signal HUP' HUP
trap 'forward_signal QUIT' QUIT
start_child() {
local sig
# Keep this check inside the function as well as at the call site:
# it closes the ordinary pre-start window, while the pending-signal
# path below handles a signal arriving during the background fork.
[ -z "$pending_signal" ] || return 125
/usr/bin/daed "$@" &
child_pid=$!
if [ -n "$pending_signal" ]; then
sig="$pending_signal"
pending_signal=
forward_signal "$sig"
fi
}
if [ -n "$pending_signal" ]; then
logger -t daed-init "refusing to start daed after pending signal $pending_signal"
exit_with_signal "$pending_signal"
fi fi
exit "$status"
# Best-effort OOM preference; failure must not block startup.
if ! echo -16 > /proc/self/oom_score_adj 2>/dev/null; then
logger -t daed-init "warn: failed to set /proc/self/oom_score_adj; continuing without OOM preference"
fi
if ! start_child "$@"; then
logger -t daed-init "refusing to start daed after pending signal $pending_signal"
if [ -n "$pending_signal" ]; then
exit_with_signal "$pending_signal"
fi
exit 1
fi
status=0
reaped=0
while [ "$reaped" -eq 0 ]; do
if [ -n "$shutdown_signal" ] && child_is_running; then
if [ "$shutdown_elapsed" -ge "$child_term_timeout" ]; then
if [ "$forced_kill" -eq 0 ]; then
logger -t daed-init "daed did not exit within ${child_term_timeout}s after $shutdown_signal; sending KILL"
kill -KILL "$child_pid" 2>/dev/null
forced_kill=1
fi
else
sleep 1
shutdown_elapsed=$((shutdown_elapsed + 1))
continue
fi
sleep 1
continue
fi
wait "$child_pid" 2>/dev/null
status=$?
if ! child_is_running; then
reaped=1
fi
done
child_pid=
trap - TERM INT HUP QUIT
# Post-exit cleanup runs after the child is reaped.
DAED_GUARD_CLEANUP=post-exit
cleanup_status=0
daed_cleanup_runtime || cleanup_status=$?
if [ "$cleanup_status" -ne 0 ]; then
echo "daed: runtime cleanup after exit failed" >&2
logger -t daed-init "post-exit cleanup failed; check ip netns / ip link show"
fi
if [ "$status" -ne 0 ]; then
exit "$status"
fi
exit "$cleanup_status"
+37 -13
View File
@@ -1,5 +1,7 @@
#!/bin/sh /etc/rc.common #!/bin/sh /etc/rc.common
# Copyright (C) 2023 Tianling Shen <cnsztl@immortalwrt.org> # Copyright (C) 2023 Tianling Shen <cnsztl@immortalwrt.org>
# daed-guard handles bounded child shutdown and post-exit netns/veth cleanup.
# Keep the log file and a pre-stop state snapshot for diagnostics.
USE_PROCD=1 USE_PROCD=1
START=99 START=99
@@ -10,18 +12,28 @@ LOG="/var/log/daed/daed.log"
. /usr/share/daed/cleanup.sh . /usr/share/daed/cleanup.sh
log() {
logger -t daed-init "$@"
}
start_service() { start_service() {
log "start: begin"
config_load "$CONF" config_load "$CONF"
local enabled local enabled
config_get_bool enabled "config" "enabled" "0" config_get_bool enabled "config" "enabled" "0"
[ "$enabled" -eq "1" ] || return 1 if [ "$enabled" -ne "1" ]; then
log "start: config disabled, exit"
return 1
fi
local listen_addr log_maxbackups log_maxsize local listen_addr log_maxbackups log_maxsize
config_get listen_addr "config" "listen_addr" "0.0.0.0:2023" config_get listen_addr "config" "listen_addr" "0.0.0.0:2023"
config_get log_maxbackups "config" "log_maxbackups" "1" config_get log_maxbackups "config" "log_maxbackups" "1"
config_get log_maxsize "config" "log_maxsize" "5" config_get log_maxsize "config" "log_maxsize" "5"
log "start: listen=$listen_addr log_maxbackups=$log_maxbackups log_maxsize=$log_maxsize"
procd_open_instance "$CONF" procd_open_instance "$CONF"
procd_set_param env DAE_LOCATION_ASSET="/usr/share/v2ray" TZ="$(uci -q get system.@system[0].zonename)" procd_set_param env DAE_LOCATION_ASSET="/usr/share/v2ray" TZ="$(uci -q get system.@system[0].zonename)"
procd_set_param command "$PROG" run procd_set_param command "$PROG" run
@@ -33,25 +45,37 @@ start_service() {
procd_set_param limits core="unlimited" procd_set_param limits core="unlimited"
procd_set_param limits nofile="1000000 1000000" procd_set_param limits nofile="1000000 1000000"
# Avoid an endless crash/respawn loop which can repeatedly reattach dae's # daed-guard escalates its child after 20 seconds. Leave time for
# data-plane hooks and make the router management plane unreachable. # reap and post-exit cleanup before procd kills the wrapper.
procd_set_param respawn 3600 5 5 procd_set_param term_timeout 30
# procd_set_param respawn: arguments are (threshold, timeout, retry).
# threshold = runtime that resets the short-lived exit counter
# timeout = seconds to wait between retries
# retry = maximum short-lived exits before procd gives up
# Reset the counter after one hour of stable runtime; otherwise retry
# after 5 seconds and stop after 10 failures.
procd_set_param respawn 3600 5 10
# daed-guard sets oom_score_adj before forking; procd has no
# oom_adj/oom_score_adj parameter.
# procd_set_param stdout 1 # procd_set_param stdout 1
procd_set_param stderr 1 procd_set_param stderr 1
procd_close_instance procd_close_instance
log "start: procd_open_instance done"
} }
stop_service() { stop_service() {
rm -f "$LOG" log "stop: begin"
daed_cleanup_runtime # Cleanup runs in daed-guard after its child exits. Record only a
} # pre-stop snapshot here; pin entries do not prove TC attachment.
local pinned="" ns_left=""
restart() { if [ -d /sys/fs/bpf/daed ]; then
stop pinned=$(ls /sys/fs/bpf/daed 2>/dev/null | tr '\n' ' ')
sleep 1 fi
daed_cleanup_runtime if ip netns list 2>/dev/null | grep -q '^daens'; then
start ns_left="daens"
fi
log "stop: pre-stop state — bpf_pin_entries=[${pinned:-none}] netns_left=[${ns_left:-none}]"
} }
service_triggers() { service_triggers() {
+11 -9
View File
@@ -1,8 +1,6 @@
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
# #
# Copyright (C) 2021-2023 sirpdboy <herboy2008@gmail.com> # Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com>
#
# This is free software, licensed under the Apache License, Version 2.0 .
# #
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
@@ -10,12 +8,12 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=ddns-go PKG_NAME:=ddns-go
PKG_VERSION:=6.17.7 PKG_VERSION:=6.17.7
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_VERSION:=6.17.7
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=f7001004e092d9641aad5a94158e0b4cae4a53a7f5c7d96d5c6af3d246c56fcc PKG_HASH:=f7001004e092d9641aad5a94158e0b4cae4a53a7f5c7d96d5c6af3d246c56fcc
PKG_LICENSE:=MIT PKG_HASH:=f7001004e092d9641aad5a94158e0b4cae4a53a7f5c7d96d5c6af3d246c56fcc
PKG_LICENSE_FILES:=LICENSE PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org> PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
@@ -44,14 +42,18 @@ define Package/ddns-go/description
support Alidns Dnspod Cloudflare Hicloud Callback Baiducloud porkbun GoDaddy Google Domains. support Alidns Dnspod Cloudflare Hicloud Callback Baiducloud porkbun GoDaddy Google Domains.
endef endef
define Package/ddns-go/conffiles
/etc/config/ddns-go
/etc/ddns-go/ddns-go-config.yaml
endef
define Package/ddns-go/install define Package/ddns-go/install
$(call GoPackage/Package/Install/Bin,$(1)) $(call GoPackage/Package/Install/Bin,$(1))
$(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) $(CURDIR)/file/ddns-go.init $(1)/etc/init.d/ddns-go $(INSTALL_DIR) $(1)/etc/config
$(INSTALL_BIN) $(CURDIR)/files/ddns-go.init $(1)/etc/init.d/ddns-go
$(INSTALL_DIR) $(1)/etc/uci-defaults $(INSTALL_CONF) $(CURDIR)/files/ddns-go.conf $(1)/etc/config/ddns-go
$(INSTALL_BIN) $(CURDIR)/file/luci-ddns-go.uci-default $(1)/etc/uci-defaults/luci-ddns-go
endef endef
$(eval $(call GoBinPackage,ddns-go)) $(eval $(call GoBinPackage,ddns-go))
-46
View File
@@ -1,46 +0,0 @@
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2021-2023 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/luci-app-ddns-go
#
# This file is part of ddns-go .
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
START=99
USE_PROCD=1
PROG=/usr/bin/ddns-go
CONFDIR=/etc/ddns-go
CONF=$CONFDIR/ddns-go-config.yaml
get_config() {
config_get_bool enabled $1 enabled 1
config_get_bool logger $1 logger 1
config_get port $1 port 9876
config_get time $1 time 300
}
init_yaml(){
[ -d $CONFDIR ] || mkdir -p $CONFDIR 2>/dev/null
cat /usr/share/ddns-go/ddns-go-default.yaml > $CONF
}
start_service() {
config_load ddns-go
config_foreach get_config basic
[ x$enabled == x1 ] || return 1
[ -s ${CONF} ] || init_yaml
logger -t ddns-go -p warn "ddns-go is start."
echo "ddns-go is start."
procd_open_instance
procd_set_param command $PROG -l :$port -f $time -c "$CONF"
[ "x$logger" == x1 ] && procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
}
service_triggers() {
procd_add_reload_trigger "ddns-go"
}
-7
View File
@@ -1,7 +0,0 @@
#!/bin/sh
[ -s "/etc/ddns-go/localtime" ] && mv -f /etc/ddns-go/localtime /etc/localtime
/etc/init.d/ddns-go enable
/etc/init.d/ddns-go start
rm -f /tmp/luci*
exit 0
+9
View File
@@ -0,0 +1,9 @@
config basic 'config'
option enabled '0'
option logger '1'
option port '9876'
option time '300'
option ctimes '5'
option skipverify '0'
option delay '0'
option dns '223.5.5.5'
+85
View File
@@ -0,0 +1,85 @@
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com>
#
# This file is part of ddns-go .
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
START=99
USE_PROCD=1
NAME=ddns-go
PROG=/usr/bin/ddns-go
CONFDIR=/etc/ddns-go
CONF=$CONFDIR/ddns-go-config.yaml
init_yaml() {
[ -d "$CONFDIR" ] || mkdir -p "$CONFDIR"
chown -R ddns-go:ddns-go "$CONFDIR"
chmod 755 "$CONFDIR"
[ -f "$CONF" ] && chmod 644 "$CONF"
}
build_args() {
local cfg="$1"
local args="-c $CONF"
config_get port "$cfg" port '9876'
args="$args -l :$port"
config_get time "$cfg" time '300'
[ -n "$time" ] && args="$args -f $time"
config_get ctimes "$cfg" ctimes '5'
[ -n "$ctimes" ] && args="$args -cacheTimes $ctimes"
config_get dns "$cfg" dns '223.5.5.5'
[ -n "$dns" ] && args="$args -dns $dns"
config_get_bool noweb "$cfg" noweb 0
[ "$noweb" -eq 1 ] && args="$args -noweb"
config_get_bool skipverify "$cfg" skipverify 0
[ "$skipverify" -eq 1 ] && args="$args -skipVerify"
echo "$args"
}
start_instance() {
local cfg="$1"
local logger
config_get_bool enabled "$cfg" enabled 0
[ "$enabled" -eq 0 ] && return 0
config_get delay "$cfg" delay 0
if [ "$delay" -gt 0 ]; then
local uptime=$(awk -F. '{print $1}' /proc/uptime)
[ "$uptime" -lt 120 ] && sleep "$delay"
fi
init_yaml
local args=$(build_args "$cfg")
procd_open_instance
procd_set_param command $PROG $args
config_get_bool logger "$cfg" logger 1
procd_set_param stdout "$logger"
procd_set_param stderr "$logger"
procd_set_param user ddns-go
procd_set_param respawn
procd_close_instance
}
start_service() {
config_load "$NAME"
config_foreach start_instance 'basic'
}
service_triggers() {
procd_add_reload_trigger "$NAME"
}
+23 -16
View File
@@ -1,25 +1,24 @@
#!/bin/sh /etc/rc.common #!/bin/sh /etc/rc.common
USE_PROCD=1
START=99 START=99
STOP=10
CONF="filebrowser-q" CONF="filebrowser-q"
PROG="/usr/bin/filebrowser-q" PROG="/usr/bin/filebrowser-q"
CONF_PATH="/etc/filebrowser-q/config.yaml" CONF_PATH="/etc/filebrowser-q/config.yaml"
PID_FILE="/var/run/filebrowser-q.pid" DB_PATH="/etc/filebrowser-q/database.db"
start() { start_service() {
config_load "$CONF" config_load "$CONF"
local enabled local enabled
config_get_bool enabled "config" "enabled" "0" config_get_bool enabled "config" "enabled" "0"
[ "$enabled" -eq "1" ] || return 1 [ "$enabled" -eq "1" ] || return 1
local listen_port root_path root_name local listen_port root_path root_name
config_get listen_port "config" "listen_port" "8787" config_get listen_port "config" "listen_port" "8787"
config_get root_path "config" "root_path" "/" config_get root_path "config" "root_path" "/"
# 路径为 / 时 name 设为 root,否则留空
root_name="" root_name=""
[ "$root_path" = "/" ] && root_name="root" [ "$root_path" = "/" ] && root_name="root"
@@ -28,7 +27,7 @@ start() {
cat <<EOF > "$CONF_PATH" cat <<EOF > "$CONF_PATH"
server: server:
port: $listen_port port: $listen_port
database: "/etc/filebrowser-q/database.db" database: "$DB_PATH"
sources: sources:
- path: "$root_path" - path: "$root_path"
name: "$root_name" name: "$root_name"
@@ -49,12 +48,20 @@ EOF
-i "$CONF_PATH" -i "$CONF_PATH"
fi fi
echo "Starting filebrowser..." procd_open_instance
start-stop-daemon -S -q -b -m -p "$PID_FILE" -x "$PROG" -- -c "$CONF_PATH" procd_set_param command "$PROG"
procd_append_param command -c "$CONF_PATH"
procd_set_param limits core="unlimited"
procd_set_param limits nofile="1000000 1000000"
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
} }
stop() { service_triggers() {
kill -9 `pidof filebrowser-q | sed "s/$$//g"` 2>/dev/null procd_add_reload_trigger "$CONF"
rm -f "$PID_FILE"
echo "filebrowser stopped"
} }
+3 -3
View File
@@ -9,9 +9,9 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
PKG_SOURCE_DATE:=2026-09-03 PKG_SOURCE_DATE:=2026-09-11
PKG_SOURCE_VERSION:=4f6a76b64b8279e98004f541f8e136307efe5e01 PKG_SOURCE_VERSION:=cfcd774b98f3433e18b722f9a7ff06119825b8eb
PKG_MIRROR_HASH:=933dc8c8d745923437c9e730ab7c2a23a53562144513a4f634ad10381c23be44 PKG_MIRROR_HASH:=d8bea9ac89f9e87f36874601588f0d8d32221bf1ad8488f2fd9775abcddf4860
PKG_LICENSE:=BSD 3-Clause PKG_LICENSE:=BSD 3-Clause
PKG_LICENSE_FILES:=LICENSE PKG_LICENSE_FILES:=LICENSE
+2 -2
View File
@@ -3,7 +3,7 @@
#ifndef OUT_LAST_COMMIT_POSITION_H_ #ifndef OUT_LAST_COMMIT_POSITION_H_
#define OUT_LAST_COMMIT_POSITION_H_ #define OUT_LAST_COMMIT_POSITION_H_
#define LAST_COMMIT_POSITION_NUM 2553 #define LAST_COMMIT_POSITION_NUM 2563
#define LAST_COMMIT_POSITION "2553 (4f6a76b64b82)" #define LAST_COMMIT_POSITION "2563 (cfcd774b98f3)"
#endif // OUT_LAST_COMMIT_POSITION_H_ #endif // OUT_LAST_COMMIT_POSITION_H_
+1 -1
View File
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=automount PKG_NAME:=automount
PKG_VERSION:=1 PKG_VERSION:=1
PKG_RELEASE:=40 PKG_RELEASE:=41
PKG_ARCH:=all PKG_ARCH:=all
include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/package.mk
+3
View File
@@ -6,6 +6,9 @@
blkdev=`dirname $DEVPATH` blkdev=`dirname $DEVPATH`
basename=`basename $blkdev` basename=`basename $blkdev`
device=`basename $DEVPATH` device=`basename $DEVPATH`
grep -qs "^/dev/$device " /proc/mounts && exit 0
skip=`block info | grep -vE '(f2fs|kernel|squashfs)' | sed 's/\(.*\): .*/\1/' | grep -q $device ; echo $?` skip=`block info | grep -vE '(f2fs|kernel|squashfs)' | sed 's/\(.*\): .*/\1/' | grep -q $device ; echo $?`
path=$DEVPATH path=$DEVPATH
-57
View File
@@ -1,57 +0,0 @@
#
# Copyright (C) 2010-2011 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=autosamba
PKG_VERSION:=1
PKG_RELEASE:=15
PKG_ARCH:=all
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_KSMBD \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_SAMBA3 \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_SAMBA4 \
include $(INCLUDE_DIR)/package.mk
define Package/autosamba
TITLE:=Samba autoconfig hotplug script.
MAINTAINER:=Lean
DEPENDS:=+wsdd2 +PACKAGE_$(PKG_NAME)_INCLUDE_KSMBD:luci-app-ksmbd +PACKAGE_$(PKG_NAME)_INCLUDE_SAMBA3:luci-app-samba +PACKAGE_$(PKG_NAME)_INCLUDE_SAMBA4:luci-app-samba4
endef
define Package/autosamba/description
A hotplug script to config Samba share automatically.
endef
define Package/$(PKG_NAME)/config
choice
prompt "Samba Server Selection"
default PACKAGE_$(PKG_NAME)_INCLUDE_KSMBD
config PACKAGE_$(PKG_NAME)_INCLUDE_KSMBD
bool "KSMBD"
config PACKAGE_$(PKG_NAME)_INCLUDE_SAMBA3
bool "SAMBA 3"
config PACKAGE_$(PKG_NAME)_INCLUDE_SAMBA4
bool "SAMBA 4"
endchoice
endef
define Build/Compile
endef
define Package/autosamba/install
$(INSTALL_DIR) $(1)/etc/hotplug.d/block
$(INSTALL_BIN) ./files/20-smb $(1)/etc/hotplug.d/block/20-smb
endef
$(eval $(call BuildPackage,autosamba))
-106
View File
@@ -1,106 +0,0 @@
#!/bin/sh
#
# D-Team Technology Co.,Ltd. ShenZhen
# 作者:Vic
#
# 警告:对着屏幕的哥们,我们允许你使用此脚本,但不允许你抹去作者的信息,请保留这段话。
#
. /lib/functions.sh
. /lib/functions/service.sh
global=0
config_file="/etc/config/samba4"
wait_for_init() {
for i in `seq 30`
do
[ -e /tmp/procd.done ] || {
sleep 1; continue;
}
return
done
}
smb_handle() {
config_get path $1 path
if [ "$path" = "$2" ] ;then
global=1
fi
}
chk_en() {
config_get_bool autoshare $1 autoshare 1
[ $autoshare -eq 0 ] && exit
}
config_load samba4
config_foreach chk_en samba4
device=`basename $DEVPATH`
case "$ACTION" in
add)
case "$device" in
sd*);;
md*);;
hd*);;
mmcblk*);;
*) return;;
esac
path="/dev/$device"
wait_for_init
cat /proc/mounts | grep -v '/boot\|/opt' | while read j
do
str=${j%% *}
if [ "$str" == $path ];then
strr=${j#* }
target=${strr%% *}
global=0
config_foreach smb_handle sambashare $target
name=${target#*/mnt/}
if [ $global -eq 0 ] ;then
echo -e "\n\nconfig sambashare" >> $config_file
echo -e "\toption auto '1'" >> $config_file
echo -e "\toption name '$name'" >> $config_file
echo -e "\toption path '$target'" >> $config_file
echo -e "\toption read_only 'no'" >> $config_file
echo -e "\toption guest_ok 'yes'" >> $config_file
echo -e "\toption create_mask '0666'" >> $config_file
echo -e "\toption dir_mask '0777'" >> $config_file
echo -e "\toption device '$device'" >> $config_file
echo -e "\toption inherit_owner 'yes'" >> $config_file
/etc/init.d/samba4 reload
return
fi
fi
done
;;
remove)
i=0
while true
do
dev=`uci get samba4.@sambashare[$i].device`
[ $? -ne 0 ] && break
[ "$dev" = "$device" ] && {
auto=`uci get samba4.@sambashare[$i].auto`
[ $auto = "1" ] && {
mount_dir=`uci get samba4.@sambashare[$i].name`
uci delete samba4.@sambashare[$i]
uci commit
/etc/init.d/samba4 reload
return
}
}
let i+=1
done
;;
esac
+1
View File
@@ -11,6 +11,7 @@ P4REV:=9
PKG_VERSION:=5.0.4.0 PKG_VERSION:=5.0.4.0
PKG_BUILD_PARALLEL:=1 PKG_BUILD_PARALLEL:=1
PATCH_DIR:=./patches-$(KERNEL_PATCHVER)
# PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME) # PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME)
PKG_KCONFIG:= \ PKG_KCONFIG:= \
+1 -1
View File
@@ -10,7 +10,7 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
PKG_MAINTAINER:=timsaya PKG_MAINTAINER:=timsaya
PKG_VERSION:=0.12.10 PKG_VERSION:=0.12.11
PKG_RELEASE:=1 PKG_RELEASE:=1
include $(TOPDIR)/feeds/luci/luci.mk include $(TOPDIR)/feeds/luci/luci.mk
@@ -470,6 +470,26 @@ var callSetDefaultRateLimit = rpc.declare({
expect: {} expect: {}
}); });
var callGetTrafficQuotas = rpc.declare({
object: 'luci.bandix',
method: 'getTrafficQuotas',
expect: {}
});
var callSetTrafficQuota = rpc.declare({
object: 'luci.bandix',
method: 'setTrafficQuota',
params: ['mac', 'minute_bytes', 'hourly_bytes', 'daily_bytes', 'weekly_bytes', 'monthly_bytes', 'total_bytes'],
expect: {}
});
var callDeleteTrafficQuota = rpc.declare({
object: 'luci.bandix',
method: 'deleteTrafficQuota',
params: ['mac'],
expect: {}
});
return view.extend({ return view.extend({
load: function () { load: function () {
return Promise.all([ return Promise.all([
@@ -1903,6 +1923,74 @@ return view.extend({
.schedule-rules-info { .schedule-rules-info {
} }
.traffic-quota-section {
margin-top: 18px;
padding-top: 16px;
border-top: 1px solid ${scheme === 'dark' ? 'rgba(255,255,255,0.16)' : 'rgba(0,0,0,0.12)'};
}
.traffic-quota-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 6px;
}
.traffic-quota-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 14px;
margin-top: 12px;
}
.traffic-quota-input-row {
display: flex;
gap: 6px;
}
.traffic-quota-input-row .form-input { min-width: 0; flex: 1; }
.traffic-quota-input-row .cbi-input-select { width: 82px; flex-shrink: 0; }
.traffic-quota-usage {
margin-top: 12px;
padding: 10px 12px;
border-radius: 6px;
background: ${scheme === 'dark' ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'};
font-size: 0.8125rem;
}
.traffic-quota-usage-row {
display: flex;
justify-content: space-between;
gap: 12px;
padding: 2px 0;
}
.traffic-quota-status {
font-size: 0.75rem;
font-weight: 600;
border-radius: 999px;
padding: 2px 8px;
color: #fff;
background: #10b981;
}
.traffic-quota-status.blocked { background: #ef4444; }
.traffic-quota-status.unconfigured { background: #6b7280; }
.traffic-quota-cell {
margin-top: 6px;
padding-top: 6px;
border-top: 1px dashed ${scheme === 'dark' ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.14)'};
font-size: 0.75rem;
}
.traffic-quota-blocked-badge {
display: inline-block;
margin-left: 6px;
padding: 1px 6px;
border-radius: 999px;
color: #fff;
background: #ef4444;
font-size: 0.6875rem;
font-weight: 600;
}
@media (max-width: 600px) {
.traffic-quota-grid { grid-template-columns: 1fr; }
}
/* 统计区域样式 */ /* 统计区域样式 */
.traffic-stats-container { .traffic-stats-container {
display: flex; display: flex;
@@ -2904,7 +2992,7 @@ return view.extend({
E('th', {}, _('Device Info')), E('th', {}, _('Device Info')),
E('th', {}, _('LAN Traffic')), E('th', {}, _('LAN Traffic')),
E('th', {}, _('WAN Traffic')), E('th', {}, _('WAN Traffic')),
E('th', {}, _('Rate Limit')), E('th', {}, _('Rules / Quota')),
E('th', {}, _('Actions')) E('th', {}, _('Actions'))
]) ])
]), ]),
@@ -3171,7 +3259,30 @@ return view.extend({
}); });
} }
// 创建限速设置模态框 function createQuotaInput(period, label) {
return E('div', { 'class': 'form-group', 'style': 'margin-bottom: 0;' }, [
E('label', { 'class': 'form-label' }, label),
E('div', { 'class': 'traffic-quota-input-row' }, [
E('input', {
'type': 'number',
'min': '0',
'step': '0.01',
'class': 'form-input',
'id': 'traffic-quota-' + period + '-value',
'placeholder': '0'
}),
E('select', { 'class': 'cbi-input-select', 'id': 'traffic-quota-' + period + '-unit' }, [
E('option', { 'value': '1' }, 'B'),
E('option', { 'value': '1024' }, 'KB'),
E('option', { 'value': '1048576' }, 'MB'),
E('option', { 'value': '1073741824' }, 'GB'),
E('option', { 'value': '1099511627776' }, 'TB')
])
])
]);
}
// 创建设备设置模态框
var bandixModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'rate-limit-bandix-modal' }, [ var bandixModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'rate-limit-bandix-modal' }, [
E('div', { 'class': 'modal-content bandix-modal' }, [ E('div', { 'class': 'modal-content bandix-modal' }, [
E('div', { 'class': 'bandix-modal-body', }, [ E('div', { 'class': 'bandix-modal-body', }, [
@@ -3211,6 +3322,46 @@ return view.extend({
document.body.appendChild(bandixModal); document.body.appendChild(bandixModal);
// 创建独立的流量配额模态框
var trafficQuotaModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'traffic-quota-bandix-modal' }, [
E('div', { 'class': 'modal-content bandix-modal' }, [
E('div', { 'class': 'bandix-modal-header' }, [
E('h3', { 'class': 'bandix-modal-title' }, _('Traffic Quota'))
]),
E('div', { 'class': 'bandix-modal-body' }, [
E('div', { 'class': 'device-summary', 'id': 'traffic-quota-device-summary' }),
E('div', { 'class': 'traffic-quota-section', 'style': 'margin-top: 0; padding-top: 0; border-top: none;' }, [
E('div', { 'class': 'traffic-quota-header' }, [
E('div', { 'style': 'font-weight: 600;' }, _('Quota Status')),
E('span', { 'class': 'traffic-quota-status', 'id': 'traffic-quota-status' }, _('Not configured'))
]),
E('div', { 'style': 'font-size: 0.75rem; opacity: 0.7;' },
_('WAN upload and download are counted together. Enter 0 for unlimited.')),
E('div', { 'class': 'traffic-quota-grid' }, [
createQuotaInput('minute', _('Minute Quota')),
createQuotaInput('hourly', _('Hourly Quota')),
createQuotaInput('daily', _('Daily Quota')),
createQuotaInput('weekly', _('Weekly Quota')),
createQuotaInput('monthly', _('Monthly Quota')),
createQuotaInput('total', _('Lifetime Quota'))
]),
E('div', { 'class': 'traffic-quota-usage', 'id': 'traffic-quota-usage' }, [
E('div', { 'style': 'opacity: 0.7;' }, _('No quota usage data'))
]),
E('div', { 'style': 'display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px;' }, [
E('button', { 'class': 'cbi-button cbi-button-negative', 'id': 'traffic-quota-delete-btn', 'style': 'display: none;' }, _('Delete Quota')),
E('button', { 'class': 'cbi-button cbi-button-positive', 'id': 'traffic-quota-save-btn' }, _('Save Quota'))
])
])
]),
E('div', { 'class': 'bandix-modal-footer' }, [
E('button', { 'class': 'cbi-button cbi-button-reset', 'id': 'traffic-quota-modal-close' }, _('Close'))
])
])
]);
document.body.appendChild(trafficQuotaModal);
// 创建添加规则模态框 // 创建添加规则模态框
var addRuleModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'add-rule-bandix-modal' }, [ var addRuleModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'add-rule-bandix-modal' }, [
E('div', { 'class': 'modal-content bandix-modal' }, [ E('div', { 'class': 'modal-content bandix-modal' }, [
@@ -3946,9 +4097,17 @@ return view.extend({
// 模态框事件处理 // 模态框事件处理
var currentDevice = null; var currentDevice = null;
var currentQuotaDevice = null;
var quotaFormDirty = false;
var editingRule = null; var editingRule = null;
var showRateLimitModal; var showRateLimitModal;
trafficQuotaModal.querySelectorAll('.traffic-quota-input-row input, .traffic-quota-input-row select').forEach(function (input) {
input.addEventListener(input.tagName === 'SELECT' ? 'change' : 'input', function () {
quotaFormDirty = true;
});
});
// 显示模态框 // 显示模态框
showRateLimitModal = function (device) { showRateLimitModal = function (device) {
currentDevice = device; currentDevice = device;
@@ -3994,6 +4153,33 @@ return view.extend({
}, 300); }, 300);
} }
function showTrafficQuotaModal(device) {
currentQuotaDevice = device;
quotaFormDirty = false;
var modal = document.getElementById('traffic-quota-bandix-modal');
var deviceSummary = document.getElementById('traffic-quota-device-summary');
var modalContent = [
E('div', { 'class': 'device-summary-name' }, device.host || device.ip4),
E('div', { 'class': 'device-summary-details' }, device.ip4 + ' (' + device.mac + ')')
];
var modalBadges = buildDeviceUplinkChBadges(device);
if (modalBadges.length) {
modalContent.push(E('div', { 'class': 'device-summary-badges device-uplink-badges-wrap' }, modalBadges));
}
deviceSummary.innerHTML = E('div', {}, modalContent).innerHTML;
renderTrafficQuotaForm(getTrafficQuotaForDevice(device.mac), false);
modal.classList.add('show');
loadTrafficQuotaForm();
}
function hideTrafficQuotaModal() {
document.getElementById('traffic-quota-bandix-modal').classList.remove('show');
setTimeout(function () {
currentQuotaDevice = null;
quotaFormDirty = false;
}, 300);
}
// 加载定时限速规则列表 // 加载定时限速规则列表
function loadScheduleRules() { function loadScheduleRules() {
if (!currentDevice) return; if (!currentDevice) return;
@@ -4163,8 +4349,60 @@ return view.extend({
// 绑定 hostname 保存按钮事件 // 绑定 hostname 保存按钮事件
document.getElementById('hostname-save-btn').addEventListener('click', saveHostname); document.getElementById('hostname-save-btn').addEventListener('click', saveHostname);
document.getElementById('traffic-quota-save-btn').addEventListener('click', function () {
if (!currentQuotaDevice) return;
var mac = currentQuotaDevice.mac;
var values = ['minute', 'hourly', 'daily', 'weekly', 'monthly', 'total'].map(readQuotaInput);
if (values.some(function (value) { return value === null; })) {
ui.addNotification(null, E('p', {}, _('Quota values must be non-negative and within the supported range.')), 'error');
return;
}
var button = this;
button.disabled = true;
setTrafficQuotaFormDisabled(true);
callSetTrafficQuota(mac, values[0], values[1], values[2], values[3], values[4], values[5]).then(function (result) {
if (result && result.success === false) throw new Error(result.error || _('Failed to save traffic quota'));
return fetchAllTrafficQuotas(true);
}).then(function () {
if (currentQuotaDevice && currentQuotaDevice.mac === mac) {
quotaFormDirty = false;
renderTrafficQuotaForm(getTrafficQuotaForDevice(mac), false);
}
updateDeviceData();
}).catch(function (error) {
ui.addNotification(null, E('p', {}, _('Failed to save traffic quota') + ': ' + (error.message || error)), 'error');
}).finally(function () {
button.disabled = false;
setTrafficQuotaFormDisabled(false);
});
});
document.getElementById('traffic-quota-delete-btn').addEventListener('click', function () {
if (!currentQuotaDevice) return;
var mac = currentQuotaDevice.mac;
showConfirmDialog(
_('Delete Traffic Quota'),
_('Delete this quota and all of its accumulated usage?'),
function () {
callDeleteTrafficQuota(mac).then(function (result) {
if (result && result.success === false) throw new Error(result.error || _('Failed to delete traffic quota'));
return fetchAllTrafficQuotas(true);
}).then(function () {
if (currentQuotaDevice && currentQuotaDevice.mac === mac) {
quotaFormDirty = false;
renderTrafficQuotaForm(null, false);
}
updateDeviceData();
}).catch(function (error) {
ui.addNotification(null, E('p', {}, _('Failed to delete traffic quota') + ': ' + (error.message || error)), 'error');
});
}
);
});
// 绑定关闭按钮事件 // 绑定关闭按钮事件
document.getElementById('bandix-modal-close').addEventListener('click', hideRateLimitModal); document.getElementById('bandix-modal-close').addEventListener('click', hideRateLimitModal);
document.getElementById('traffic-quota-modal-close').addEventListener('click', hideTrafficQuotaModal);
// 历史趋势:状态与工具 // 历史趋势:状态与工具
var latestDevices = []; var latestDevices = [];
@@ -4175,6 +4413,154 @@ return view.extend({
var allScheduleRules = []; // 存储所有设备的定时限速规则 var allScheduleRules = []; // 存储所有设备的定时限速规则
var isScheduleRulesLoading = false; // 防止轮询重入 var isScheduleRulesLoading = false; // 防止轮询重入
var allTrafficQuotas = [];
var trafficQuotasRequest = null;
var trafficQuotasLoadError = null;
function fetchAllTrafficQuotas(forceRefresh) {
if (trafficQuotasRequest) {
if (!forceRefresh) return trafficQuotasRequest;
return trafficQuotasRequest.then(function () {
return fetchAllTrafficQuotas(true);
});
}
trafficQuotasRequest = callGetTrafficQuotas().then(function (res) {
if (!res || res.success === false || res.status === 'error') {
throw new Error((res && (res.error || res.message)) || _('Failed to load traffic quotas'));
}
var quotas = [];
if (res && res.data && Array.isArray(res.data.quotas)) {
quotas = res.data.quotas;
} else if (res && Array.isArray(res.quotas)) {
quotas = res.quotas;
}
allTrafficQuotas = quotas;
trafficQuotasLoadError = null;
return quotas;
}).catch(function (error) {
console.error('Failed to fetch traffic quotas:', error);
allTrafficQuotas = [];
trafficQuotasLoadError = error && error.message ? error.message : _('Failed to load traffic quotas');
return [];
}).finally(function () {
trafficQuotasRequest = null;
});
return trafficQuotasRequest;
}
function getTrafficQuotaForDevice(mac) {
for (var i = 0; i < allTrafficQuotas.length; i++) {
if (allTrafficQuotas[i] && allTrafficQuotas[i].mac === mac) {
return allTrafficQuotas[i];
}
}
return null;
}
function quotaPeriods(quota) {
return [
{ key: 'minute', label: _('Minute'), limit: quota.minute_bytes || 0, used: quota.minute_used_bytes || 0 },
{ key: 'hourly', label: _('Hourly'), limit: quota.hourly_bytes || 0, used: quota.hourly_used_bytes || 0 },
{ key: 'daily', label: _('Daily'), limit: quota.daily_bytes || 0, used: quota.daily_used_bytes || 0 },
{ key: 'weekly', label: _('Weekly'), limit: quota.weekly_bytes || 0, used: quota.weekly_used_bytes || 0 },
{ key: 'monthly', label: _('Monthly'), limit: quota.monthly_bytes || 0, used: quota.monthly_used_bytes || 0 },
{ key: 'total', label: _('Lifetime'), limit: quota.total_bytes || 0, used: quota.total_used_bytes || 0 }
];
}
function setQuotaInput(period, bytes) {
var valueInput = document.getElementById('traffic-quota-' + period + '-value');
var unitSelect = document.getElementById('traffic-quota-' + period + '-unit');
if (!valueInput || !unitSelect) return;
var units = [1099511627776, 1073741824, 1048576, 1024, 1];
var unit = period === 'minute' ? 1048576 : 1073741824;
if (bytes > 0) {
for (var i = 0; i < units.length; i++) {
if (bytes >= units[i] && bytes % units[i] === 0) {
unit = units[i];
break;
}
}
}
unitSelect.value = String(unit);
valueInput.value = bytes > 0 ? String(bytes / unit) : '0';
}
function readQuotaInput(period) {
var value = Number(document.getElementById('traffic-quota-' + period + '-value').value || 0);
var unit = Number(document.getElementById('traffic-quota-' + period + '-unit').value || 1);
var bytes = Math.round(value * unit);
if (!Number.isFinite(bytes) || bytes < 0 || !Number.isSafeInteger(bytes)) return null;
return bytes;
}
function setTrafficQuotaFormDisabled(disabled) {
trafficQuotaModal.querySelectorAll('.traffic-quota-input-row input, .traffic-quota-input-row select').forEach(function (input) {
input.disabled = disabled;
});
}
function renderTrafficQuotaForm(quota, preserveInputs) {
if (!preserveInputs) {
var periods = ['minute', 'hourly', 'daily', 'weekly', 'monthly', 'total'];
periods.forEach(function (period) {
setQuotaInput(period, quota ? (quota[period + '_bytes'] || 0) : 0);
});
}
var status = document.getElementById('traffic-quota-status');
var usage = document.getElementById('traffic-quota-usage');
var deleteBtn = document.getElementById('traffic-quota-delete-btn');
if (!status || !usage || !deleteBtn) return;
status.classList.toggle('blocked', !!(quota && quota.blocked));
status.classList.toggle('unconfigured', !quota);
status.textContent = !quota ? (trafficQuotasLoadError ? _('Unavailable') : _('Not configured')) : (quota.blocked ? _('Blocked') : _('Active'));
deleteBtn.style.display = quota ? '' : 'none';
usage.innerHTML = '';
if (!quota) {
usage.appendChild(E('div', { 'style': 'opacity: 0.7;' }, trafficQuotasLoadError || _('Usage starts when a quota is first saved.')));
return;
}
quotaPeriods(quota).forEach(function (period) {
usage.appendChild(E('div', { 'class': 'traffic-quota-usage-row' }, [
E('span', {}, period.label),
E('span', {}, formatSize(period.used) + ' / ' + (period.limit > 0 ? formatSize(period.limit) : _('Unlimited')))
]));
});
}
function loadTrafficQuotaForm() {
if (!currentQuotaDevice) return Promise.resolve();
var mac = currentQuotaDevice.mac;
renderTrafficQuotaForm(getTrafficQuotaForDevice(mac), quotaFormDirty);
return fetchAllTrafficQuotas().then(function () {
if (currentQuotaDevice && currentQuotaDevice.mac === mac) {
renderTrafficQuotaForm(getTrafficQuotaForDevice(mac), quotaFormDirty);
}
});
}
function buildTrafficQuotaCell(mac) {
var quota = getTrafficQuotaForDevice(mac);
if (!quota) return null;
var periods = quotaPeriods(quota).filter(function (period) { return period.limit > 0; });
var mostUsed = null;
periods.forEach(function (period) {
var ratio = period.used / period.limit;
if (!mostUsed || ratio > mostUsed.ratio) mostUsed = { period: period, ratio: ratio };
});
var text = quota.blocked ? _('Quota exceeded') : _('Quota active');
if (mostUsed) {
text = mostUsed.period.label + ': ' + formatSize(mostUsed.period.used) + ' / ' + formatSize(mostUsed.period.limit);
}
return E('div', {
'class': 'traffic-quota-cell',
'style': quota.blocked ? 'color: #ef4444; font-weight: 600;' : '',
'title': quota.blocked ? _('WAN traffic is blocked because a quota was reached.') : _('Traffic Quota')
}, text);
}
// 获取所有定时限速规则 // 获取所有定时限速规则
function fetchAllScheduleRules() { function fetchAllScheduleRules() {
if (isScheduleRulesLoading) return Promise.resolve(); if (isScheduleRulesLoading) return Promise.resolve();
@@ -5862,7 +6248,7 @@ return view.extend({
createSortableHeader(_('Device Info'), 'online'), createSortableHeader(_('Device Info'), 'online'),
createSplitHeader(_('LAN Traffic'), 'lan_speed', 'lan_traffic'), createSplitHeader(_('LAN Traffic'), 'lan_speed', 'lan_traffic'),
createSplitHeader(_('WAN Traffic'), 'wan_speed', 'wan_traffic'), createSplitHeader(_('WAN Traffic'), 'wan_speed', 'wan_traffic'),
E('th', {}, _('Schedule Rules')), E('th', {}, _('Rules / Quota')),
E('th', {}, _('Actions')) E('th', {}, _('Actions'))
]) ])
]), ]),
@@ -5890,9 +6276,9 @@ return view.extend({
// 填充数据 // 填充数据
filteredDevices.forEach(function (device) { filteredDevices.forEach(function (device) {
var isOnline = isDeviceOnline(device); var isOnline = isDeviceOnline(device);
var deviceQuota = getTrafficQuotaForDevice(device.mac);
// 默认使用窄主题样式 var buttonText = 'R';
var buttonText = '⚙';
var actionButton = E('button', { var actionButton = E('button', {
'class': 'cbi-button cbi-button-action', 'class': 'cbi-button cbi-button-action',
@@ -5903,6 +6289,15 @@ return view.extend({
showRateLimitModal(device); showRateLimitModal(device);
}); });
var quotaButton = E('button', {
'class': 'cbi-button cbi-button-neutral',
'title': _('Traffic Quota')
}, 'Q');
quotaButton.addEventListener('click', function () {
showTrafficQuotaModal(device);
});
var deleteButton = E('button', { var deleteButton = E('button', {
'class': 'cbi-button cbi-button-reset', 'class': 'cbi-button cbi-button-reset',
'title': _('Delete Device') 'title': _('Delete Device')
@@ -5935,7 +6330,10 @@ return view.extend({
E('span', { E('span', {
'class': 'device-status ' + (isOnline ? 'online' : 'offline') 'class': 'device-status ' + (isOnline ? 'online' : 'offline')
}), }),
device.host || '-' device.host || '-',
deviceQuota && deviceQuota.blocked
? E('span', { 'class': 'traffic-quota-blocked-badge' }, _('Quota blocked'))
: ''
]), ]),
E('div', { 'class': 'device-ip' }, [ E('div', { 'class': 'device-ip' }, [
device.conn ? E('span', { device.conn ? E('span', {
@@ -6149,12 +6547,14 @@ return view.extend({
}; };
} }
return E('td', {}, rulesInfo); var quotaCell = buildTrafficQuotaCell(device.mac);
return E('td', {}, quotaCell ? [rulesInfo, quotaCell] : rulesInfo);
})(), })(),
// 操作 // 操作
E('td', { 'class': 'device-actions' }, [ E('td', { 'class': 'device-actions' }, [
actionButton, actionButton,
quotaButton,
deleteButton deleteButton
]) ])
]); ]);
@@ -6193,6 +6593,16 @@ return view.extend({
}); });
return cardActionBtn; return cardActionBtn;
})(), })(),
(function () {
var cardQuotaBtn = E('button', {
'class': 'cbi-button cbi-button-neutral',
'title': _('Traffic Quota')
}, 'Q');
cardQuotaBtn.addEventListener('click', function () {
showTrafficQuotaModal(device);
});
return cardQuotaBtn;
})(),
(function () { (function () {
var cardDeleteBtn = E('button', { var cardDeleteBtn = E('button', {
'class': 'cbi-button cbi-button-reset', 'class': 'cbi-button cbi-button-reset',
@@ -6279,6 +6689,14 @@ return view.extend({
rulesContent rulesContent
]); ]);
})(), })(),
(function () {
var quotaCell = buildTrafficQuotaCell(device.mac);
if (!quotaCell) return '';
return E('div', { 'class': 'device-card-section' }, [
E('div', { 'class': 'device-card-section-label' }, _('Traffic Quota')),
quotaCell
]);
})(),
// LAN流量(直接显示,不需要展开/收起) // LAN流量(直接显示,不需要展开/收起)
E('div', { 'class': 'device-card-section', 'style': 'margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(0, 0, 0, 0.1);' }, [ E('div', { 'class': 'device-card-section', 'style': 'margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(0, 0, 0, 0.1);' }, [
E('div', { 'class': 'device-card-section-label' }, _('LAN Traffic')), E('div', { 'class': 'device-card-section-label' }, _('LAN Traffic')),
@@ -7499,12 +7917,25 @@ return view.extend({
}); });
}, 5000); }, 5000);
// 轮询获取流量配额状态(每1秒)
poll.add(function () {
return fetchAllTrafficQuotas().then(function () {
if (window.__bandixRenderTable) window.__bandixRenderTable();
if (currentQuotaDevice) {
renderTrafficQuotaForm(getTrafficQuotaForDevice(currentQuotaDevice.mac), quotaFormDirty);
}
});
}, 1);
// Traffic Statistics 不再自动刷新,改为手动查询 // Traffic Statistics 不再自动刷新,改为手动查询
// 立即执行一次,不等待轮询 // 立即执行一次,不等待轮询
updateDeviceData(); updateDeviceData();
refreshWhitelistStatus(); refreshWhitelistStatus();
fetchAllScheduleRules(); fetchAllScheduleRules();
fetchAllTrafficQuotas().then(function () {
if (window.__bandixRenderTable) window.__bandixRenderTable();
});
// 初始化时间范围查询功能 // 初始化时间范围查询功能
setTimeout(function () { setTimeout(function () {
+90
View File
@@ -1258,3 +1258,93 @@ msgstr "ID de programa ancla de egreso TCX"
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
msgstr "Se usa cuando tc_order es before/after. Debe ser un ID de programa de egreso valido en la misma interfaz." msgstr "Se usa cuando tc_order es before/after. Debe ser un ID de programa de egreso valido en la misma interfaz."
msgid "Traffic Quota"
msgstr "Cuota de tráfico"
msgid "Quota Status"
msgstr "Estado de la cuota"
msgid "Minute Quota"
msgstr "Cuota por minuto"
msgid "Hourly Quota"
msgstr "Cuota por hora"
msgid "Daily Quota"
msgstr "Cuota diaria"
msgid "Weekly Quota"
msgstr "Cuota semanal"
msgid "Monthly Quota"
msgstr "Cuota mensual"
msgid "Lifetime Quota"
msgstr "Cuota total acumulada"
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
msgstr "La subida y la descarga WAN se contabilizan juntas. Introduzca 0 para no establecer límite."
msgid "Not configured"
msgstr "Sin configurar"
msgid "Blocked"
msgstr "Bloqueado"
msgid "Active"
msgstr "Activa"
msgid "No quota usage data"
msgstr "No hay datos de uso de la cuota"
msgid "Delete Quota"
msgstr "Eliminar cuota"
msgid "Save Quota"
msgstr "Guardar cuota"
msgid "Usage starts when a quota is first saved."
msgstr "El uso comienza a contabilizarse al guardar una cuota por primera vez."
msgid "Minute"
msgstr "Minuto"
msgid "Lifetime"
msgstr "Total acumulado"
msgid "Quota exceeded"
msgstr "Cuota excedida"
msgid "Quota active"
msgstr "Cuota activa"
msgid "WAN traffic is blocked because a quota was reached."
msgstr "El tráfico WAN está bloqueado porque se alcanzó una cuota."
msgid "Quota blocked"
msgstr "Bloqueado por cuota"
msgid "Quota values must be non-negative and within the supported range."
msgstr "Los valores de cuota deben ser no negativos y estar dentro del rango admitido."
msgid "Failed to save traffic quota"
msgstr "No se pudo guardar la cuota de tráfico"
msgid "Delete Traffic Quota"
msgstr "Eliminar cuota de tráfico"
msgid "Delete this quota and all of its accumulated usage?"
msgstr "¿Eliminar esta cuota y todo su uso acumulado?"
msgid "Failed to delete traffic quota"
msgstr "No se pudo eliminar la cuota de tráfico"
msgid "Rules / Quota"
msgstr "Reglas / Cuota"
msgid "Failed to load traffic quotas"
msgstr "No se pudieron cargar las cuotas de tráfico"
msgid "Unavailable"
msgstr "No disponible"
+90
View File
@@ -1258,3 +1258,93 @@ msgstr "ID du programme d ancrage egress TCX"
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
msgstr "Utilise lorsque tc_order est before/after. Doit etre un ID de programme egress valide sur la meme interface." msgstr "Utilise lorsque tc_order est before/after. Doit etre un ID de programme egress valide sur la meme interface."
msgid "Traffic Quota"
msgstr "Quota de trafic"
msgid "Quota Status"
msgstr "État du quota"
msgid "Minute Quota"
msgstr "Quota par minute"
msgid "Hourly Quota"
msgstr "Quota par heure"
msgid "Daily Quota"
msgstr "Quota journalier"
msgid "Weekly Quota"
msgstr "Quota hebdomadaire"
msgid "Monthly Quota"
msgstr "Quota mensuel"
msgid "Lifetime Quota"
msgstr "Quota total cumulé"
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
msgstr "Le trafic montant et descendant WAN est comptabilisé ensemble. Saisissez 0 pour un trafic illimité."
msgid "Not configured"
msgstr "Non configuré"
msgid "Blocked"
msgstr "Bloqué"
msgid "Active"
msgstr "Actif"
msgid "No quota usage data"
msgstr "Aucune donnée d'utilisation du quota"
msgid "Delete Quota"
msgstr "Supprimer le quota"
msgid "Save Quota"
msgstr "Enregistrer le quota"
msgid "Usage starts when a quota is first saved."
msgstr "Le suivi de l'utilisation commence lors du premier enregistrement d'un quota."
msgid "Minute"
msgstr "Minute"
msgid "Lifetime"
msgstr "Total cumulé"
msgid "Quota exceeded"
msgstr "Quota dépassé"
msgid "Quota active"
msgstr "Quota actif"
msgid "WAN traffic is blocked because a quota was reached."
msgstr "Le trafic WAN est bloqué car un quota a été atteint."
msgid "Quota blocked"
msgstr "Blocage par quota"
msgid "Quota values must be non-negative and within the supported range."
msgstr "Les valeurs de quota doivent être positives ou nulles et respecter la plage prise en charge."
msgid "Failed to save traffic quota"
msgstr "Impossible d'enregistrer le quota de trafic"
msgid "Delete Traffic Quota"
msgstr "Supprimer le quota de trafic"
msgid "Delete this quota and all of its accumulated usage?"
msgstr "Supprimer ce quota et toutes ses données d'utilisation cumulées ?"
msgid "Failed to delete traffic quota"
msgstr "Impossible de supprimer le quota de trafic"
msgid "Rules / Quota"
msgstr "Règles / Quota"
msgid "Failed to load traffic quotas"
msgstr "Impossible de charger les quotas de trafic"
msgid "Unavailable"
msgstr "Indisponible"
+90
View File
@@ -1258,3 +1258,93 @@ msgstr "TCX egressアンカープログラムID"
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
msgstr "tc_orderがbefore/afterのときに使用します。同じインターフェース上の有効なegressプログラムIDである必要があります。" msgstr "tc_orderがbefore/afterのときに使用します。同じインターフェース上の有効なegressプログラムIDである必要があります。"
msgid "Traffic Quota"
msgstr "トラフィッククォータ"
msgid "Quota Status"
msgstr "クォータの状態"
msgid "Minute Quota"
msgstr "1分あたりのクォータ"
msgid "Hourly Quota"
msgstr "1時間あたりのクォータ"
msgid "Daily Quota"
msgstr "1日あたりのクォータ"
msgid "Weekly Quota"
msgstr "1週間あたりのクォータ"
msgid "Monthly Quota"
msgstr "1か月あたりのクォータ"
msgid "Lifetime Quota"
msgstr "累計クォータ"
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
msgstr "WANのアップロードとダウンロードは合算されます。無制限にするには0を入力してください。"
msgid "Not configured"
msgstr "未設定"
msgid "Blocked"
msgstr "ブロック中"
msgid "Active"
msgstr "有効"
msgid "No quota usage data"
msgstr "クォータ使用量データはありません"
msgid "Delete Quota"
msgstr "クォータを削除"
msgid "Save Quota"
msgstr "クォータを保存"
msgid "Usage starts when a quota is first saved."
msgstr "クォータを初めて保存した時点から使用量の計測を開始します。"
msgid "Minute"
msgstr "1分"
msgid "Lifetime"
msgstr "累計"
msgid "Quota exceeded"
msgstr "クォータ超過"
msgid "Quota active"
msgstr "クォータ有効"
msgid "WAN traffic is blocked because a quota was reached."
msgstr "クォータに達したためWANトラフィックがブロックされています。"
msgid "Quota blocked"
msgstr "クォータによりブロック"
msgid "Quota values must be non-negative and within the supported range."
msgstr "クォータ値は0以上で、サポートされている範囲内である必要があります。"
msgid "Failed to save traffic quota"
msgstr "トラフィッククォータを保存できませんでした"
msgid "Delete Traffic Quota"
msgstr "トラフィッククォータを削除"
msgid "Delete this quota and all of its accumulated usage?"
msgstr "このクォータと累積された使用量をすべて削除しますか?"
msgid "Failed to delete traffic quota"
msgstr "トラフィッククォータを削除できませんでした"
msgid "Rules / Quota"
msgstr "ルール / クォータ"
msgid "Failed to load traffic quotas"
msgstr "トラフィッククォータを読み込めませんでした"
msgid "Unavailable"
msgstr "利用不可"
+90
View File
@@ -1258,3 +1258,93 @@ msgstr "ID programu kotwicy TCX egress"
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
msgstr "Uzywane, gdy tc_order to before/after. Musi to byc prawidlowe ID programu egress na tym samym interfejsie." msgstr "Uzywane, gdy tc_order to before/after. Musi to byc prawidlowe ID programu egress na tym samym interfejsie."
msgid "Traffic Quota"
msgstr "Limit transferu"
msgid "Quota Status"
msgstr "Stan limitu"
msgid "Minute Quota"
msgstr "Limit minutowy"
msgid "Hourly Quota"
msgstr "Limit godzinowy"
msgid "Daily Quota"
msgstr "Limit dzienny"
msgid "Weekly Quota"
msgstr "Limit tygodniowy"
msgid "Monthly Quota"
msgstr "Limit miesięczny"
msgid "Lifetime Quota"
msgstr "Łączny limit"
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
msgstr "Ruch wysyłany i pobierany przez WAN jest sumowany. Wpisz 0, aby wyłączyć limit."
msgid "Not configured"
msgstr "Nie skonfigurowano"
msgid "Blocked"
msgstr "Zablokowano"
msgid "Active"
msgstr "Aktywny"
msgid "No quota usage data"
msgstr "Brak danych o wykorzystaniu limitu"
msgid "Delete Quota"
msgstr "Usuń limit"
msgid "Save Quota"
msgstr "Zapisz limit"
msgid "Usage starts when a quota is first saved."
msgstr "Naliczanie użycia rozpoczyna się po pierwszym zapisaniu limitu."
msgid "Minute"
msgstr "Minuta"
msgid "Lifetime"
msgstr "Łącznie"
msgid "Quota exceeded"
msgstr "Limit przekroczony"
msgid "Quota active"
msgstr "Limit aktywny"
msgid "WAN traffic is blocked because a quota was reached."
msgstr "Ruch WAN jest zablokowany, ponieważ osiągnięto limit."
msgid "Quota blocked"
msgstr "Blokada przez limit"
msgid "Quota values must be non-negative and within the supported range."
msgstr "Wartości limitu muszą być nieujemne i mieścić się w obsługiwanym zakresie."
msgid "Failed to save traffic quota"
msgstr "Nie udało się zapisać limitu transferu"
msgid "Delete Traffic Quota"
msgstr "Usuń limit transferu"
msgid "Delete this quota and all of its accumulated usage?"
msgstr "Usunąć ten limit i wszystkie dane o naliczonym użyciu?"
msgid "Failed to delete traffic quota"
msgstr "Nie udało się usunąć limitu transferu"
msgid "Rules / Quota"
msgstr "Reguły / Limit"
msgid "Failed to load traffic quotas"
msgstr "Nie udało się wczytać limitów transferu"
msgid "Unavailable"
msgstr "Niedostępne"
+90
View File
@@ -1258,3 +1258,93 @@ msgstr "ID якорной программы TCX egress"
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
msgstr "Используется, когда tc_order имеет значение before/after. Должен быть корректный ID egress-программы на том же интерфейсе." msgstr "Используется, когда tc_order имеет значение before/after. Должен быть корректный ID egress-программы на том же интерфейсе."
msgid "Traffic Quota"
msgstr "Квота трафика"
msgid "Quota Status"
msgstr "Состояние квоты"
msgid "Minute Quota"
msgstr "Минутная квота"
msgid "Hourly Quota"
msgstr "Часовая квота"
msgid "Daily Quota"
msgstr "Суточная квота"
msgid "Weekly Quota"
msgstr "Недельная квота"
msgid "Monthly Quota"
msgstr "Месячная квота"
msgid "Lifetime Quota"
msgstr "Общая квота"
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
msgstr "Входящий и исходящий трафик WAN учитываются вместе. Введите 0, чтобы отключить ограничение."
msgid "Not configured"
msgstr "Не настроено"
msgid "Blocked"
msgstr "Заблокировано"
msgid "Active"
msgstr "Активна"
msgid "No quota usage data"
msgstr "Нет данных об использовании квоты"
msgid "Delete Quota"
msgstr "Удалить квоту"
msgid "Save Quota"
msgstr "Сохранить квоту"
msgid "Usage starts when a quota is first saved."
msgstr "Учет трафика начинается после первого сохранения квоты."
msgid "Minute"
msgstr "Минута"
msgid "Lifetime"
msgstr "Всего"
msgid "Quota exceeded"
msgstr "Квота превышена"
msgid "Quota active"
msgstr "Квота активна"
msgid "WAN traffic is blocked because a quota was reached."
msgstr "Трафик WAN заблокирован, так как достигнута квота."
msgid "Quota blocked"
msgstr "Блокировка по квоте"
msgid "Quota values must be non-negative and within the supported range."
msgstr "Значения квоты должны быть неотрицательными и находиться в поддерживаемом диапазоне."
msgid "Failed to save traffic quota"
msgstr "Не удалось сохранить квоту трафика"
msgid "Delete Traffic Quota"
msgstr "Удалить квоту трафика"
msgid "Delete this quota and all of its accumulated usage?"
msgstr "Удалить эту квоту и все накопленные данные об использовании?"
msgid "Failed to delete traffic quota"
msgstr "Не удалось удалить квоту трафика"
msgid "Rules / Quota"
msgstr "Правила / Квота"
msgid "Failed to load traffic quotas"
msgstr "Не удалось загрузить квоты трафика"
msgid "Unavailable"
msgstr "Недоступно"
+90
View File
@@ -1285,3 +1285,93 @@ msgstr "TCX 出站锚点程序 ID"
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
msgstr "当 tc_order 为 before/after 时使用。必须是同一接口上的有效出站程序 ID。" msgstr "当 tc_order 为 before/after 时使用。必须是同一接口上的有效出站程序 ID。"
msgid "Traffic Quota"
msgstr "流量配额"
msgid "Quota Status"
msgstr "配额状态"
msgid "Hourly Quota"
msgstr "每小时配额"
msgid "Daily Quota"
msgstr "每天配额"
msgid "Weekly Quota"
msgstr "每周配额"
msgid "Monthly Quota"
msgstr "每月配额"
msgid "Lifetime Quota"
msgstr "累计总量配额"
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
msgstr "WAN 上传和下载合并计算;输入 0 表示不限量。"
msgid "Not configured"
msgstr "未配置"
msgid "Blocked"
msgstr "已阻断"
msgid "Active"
msgstr "生效中"
msgid "No quota usage data"
msgstr "暂无配额用量数据"
msgid "Delete Quota"
msgstr "删除配额"
msgid "Save Quota"
msgstr "保存配额"
msgid "Usage starts when a quota is first saved."
msgstr "首次保存配额后开始累计用量。"
msgid "Lifetime"
msgstr "累计"
msgid "Quota exceeded"
msgstr "配额已用尽"
msgid "Quota active"
msgstr "配额生效中"
msgid "WAN traffic is blocked because a quota was reached."
msgstr "因达到流量配额,WAN 流量已被阻断。"
msgid "Quota blocked"
msgstr "配额阻断"
msgid "Quota values must be non-negative and within the supported range."
msgstr "配额必须是支持范围内的非负数。"
msgid "Failed to save traffic quota"
msgstr "保存流量配额失败"
msgid "Delete Traffic Quota"
msgstr "删除流量配额"
msgid "Delete this quota and all of its accumulated usage?"
msgstr "确定删除此配额及其全部累计用量吗?"
msgid "Failed to delete traffic quota"
msgstr "删除流量配额失败"
msgid "Rules / Quota"
msgstr "规则 / 配额"
msgid "Failed to load traffic quotas"
msgstr "加载流量配额失败"
msgid "Unavailable"
msgstr "不可用"
msgid "Minute Quota"
msgstr "每分钟配额"
msgid "Minute"
msgstr "每分钟"
+90
View File
@@ -1258,3 +1258,93 @@ msgstr "TCX 出站錨點程式 ID"
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
msgstr "當 tc_order 為 before/after 時使用。必須是同一介面上的有效出站程式 ID。" msgstr "當 tc_order 為 before/after 時使用。必須是同一介面上的有效出站程式 ID。"
msgid "Traffic Quota"
msgstr "流量配額"
msgid "Quota Status"
msgstr "配額狀態"
msgid "Minute Quota"
msgstr "每分鐘配額"
msgid "Hourly Quota"
msgstr "每小時配額"
msgid "Daily Quota"
msgstr "每天配額"
msgid "Weekly Quota"
msgstr "每週配額"
msgid "Monthly Quota"
msgstr "每月配額"
msgid "Lifetime Quota"
msgstr "累計總量配額"
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
msgstr "WAN 上傳和下載合併計算;輸入 0 表示不限量。"
msgid "Not configured"
msgstr "未設定"
msgid "Blocked"
msgstr "已阻斷"
msgid "Active"
msgstr "生效中"
msgid "No quota usage data"
msgstr "暫無配額用量資料"
msgid "Delete Quota"
msgstr "刪除配額"
msgid "Save Quota"
msgstr "儲存配額"
msgid "Usage starts when a quota is first saved."
msgstr "首次儲存配額後開始累計用量。"
msgid "Minute"
msgstr "每分鐘"
msgid "Lifetime"
msgstr "累計"
msgid "Quota exceeded"
msgstr "配額已用盡"
msgid "Quota active"
msgstr "配額生效中"
msgid "WAN traffic is blocked because a quota was reached."
msgstr "因達到流量配額,WAN 流量已被阻斷。"
msgid "Quota blocked"
msgstr "配額阻斷"
msgid "Quota values must be non-negative and within the supported range."
msgstr "配額必須是支援範圍內的非負數。"
msgid "Failed to save traffic quota"
msgstr "儲存流量配額失敗"
msgid "Delete Traffic Quota"
msgstr "刪除流量配額"
msgid "Delete this quota and all of its accumulated usage?"
msgstr "確定刪除此配額及其全部累計用量嗎?"
msgid "Failed to delete traffic quota"
msgstr "刪除流量配額失敗"
msgid "Rules / Quota"
msgstr "規則 / 配額"
msgid "Failed to load traffic quotas"
msgstr "載入流量配額失敗"
msgid "Unavailable"
msgstr "無法使用"
@@ -19,6 +19,7 @@ readonly BANDIX_TRAFFIC_USAGE_INCREMENTS_API="$BANDIX_API_BASE/api/traffic/usage
readonly BANDIX_RATE_LIMIT_WHITELIST_API="$BANDIX_API_BASE/api/traffic/rate_limit/whitelist" readonly BANDIX_RATE_LIMIT_WHITELIST_API="$BANDIX_API_BASE/api/traffic/rate_limit/whitelist"
readonly BANDIX_RATE_LIMIT_WHITELIST_ENABLED_API="$BANDIX_API_BASE/api/traffic/rate_limit/whitelist/enabled" readonly BANDIX_RATE_LIMIT_WHITELIST_ENABLED_API="$BANDIX_API_BASE/api/traffic/rate_limit/whitelist/enabled"
readonly BANDIX_RATE_LIMIT_DEFAULT_API="$BANDIX_API_BASE/api/traffic/rate_limit/default" readonly BANDIX_RATE_LIMIT_DEFAULT_API="$BANDIX_API_BASE/api/traffic/rate_limit/default"
readonly BANDIX_TRAFFIC_QUOTAS_API="$BANDIX_API_BASE/api/traffic/quotas"
# 通用函数:创建简单的JSON响应 # 通用函数:创建简单的JSON响应
make_value() { make_value() {
@@ -134,6 +135,71 @@ set_default_rate_limit() {
echo "$response" echo "$response"
} }
get_traffic_quotas() {
local response=$(curl -s --connect-timeout 2 --max-time 5 -X GET "$BANDIX_TRAFFIC_QUOTAS_API" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$response" ]; then
make_error "Failed to connect to Bandix service"
return
fi
echo "$response"
}
set_traffic_quota() {
local mac="$1"
local minute_bytes="$2"
local hourly_bytes="$3"
local daily_bytes="$4"
local weekly_bytes="$5"
local monthly_bytes="$6"
local total_bytes="$7"
if [ -z "$mac" ]; then
make_error "MAC address is required"
return
fi
for value in "$minute_bytes" "$hourly_bytes" "$daily_bytes" "$weekly_bytes" "$monthly_bytes" "$total_bytes"; do
case "$value" in
''|*[!0-9]*)
make_error "Quota values must be non-negative integers"
return
;;
esac
done
local mac_escaped=$(escape_json_string "$mac")
local request_data="{
\"mac\": \"$mac_escaped\",
\"minute_bytes\": $minute_bytes,
\"hourly_bytes\": $hourly_bytes,
\"daily_bytes\": $daily_bytes,
\"weekly_bytes\": $weekly_bytes,
\"monthly_bytes\": $monthly_bytes,
\"total_bytes\": $total_bytes
}"
local response=$(curl -s --connect-timeout 3 --max-time 10 -X POST -H "Content-Type: application/json" -d "$request_data" "$BANDIX_TRAFFIC_QUOTAS_API" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$response" ] && echo "$response" | grep -q '"status":[[:space:]]*"success"'; then
make_success "Traffic quota saved successfully"
else
make_error "Failed to save traffic quota"
fi
}
delete_traffic_quota() {
local mac="$1"
if [ -z "$mac" ]; then
make_error "MAC address is required"
return
fi
local mac_escaped=$(escape_json_string "$mac")
local request_data="{\"mac\": \"$mac_escaped\"}"
local response=$(curl -s --connect-timeout 3 --max-time 10 -X DELETE -H "Content-Type: application/json" -d "$request_data" "$BANDIX_TRAFFIC_QUOTAS_API" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$response" ] && echo "$response" | grep -q '"status":[[:space:]]*"success"'; then
make_success "Traffic quota deleted successfully"
else
make_error "Failed to delete traffic quota"
fi
}
# 删除设备 # 删除设备
delete_device() { delete_device() {
local mac="$1" local mac="$1"
@@ -1349,6 +1415,23 @@ case "$1" in
json_add_int "wan_tx_rate_limit" json_add_int "wan_tx_rate_limit"
json_close_object json_close_object
json_add_object "getTrafficQuotas"
json_close_object
json_add_object "setTrafficQuota"
json_add_string "mac"
json_add_int "minute_bytes"
json_add_int "hourly_bytes"
json_add_int "daily_bytes"
json_add_int "weekly_bytes"
json_add_int "monthly_bytes"
json_add_int "total_bytes"
json_close_object
json_add_object "deleteTrafficQuota"
json_add_string "mac"
json_close_object
json_dump json_dump
json_cleanup json_cleanup
;; ;;
@@ -1793,6 +1876,57 @@ case "$1" in
fi fi
set_default_rate_limit "$wan_rx_rate_limit" "$wan_tx_rate_limit" set_default_rate_limit "$wan_rx_rate_limit" "$wan_tx_rate_limit"
;; ;;
getTrafficQuotas)
get_traffic_quotas
;;
setTrafficQuota)
mac=""
minute_bytes=""
hourly_bytes=""
daily_bytes=""
weekly_bytes=""
monthly_bytes=""
total_bytes=""
input=""
if read -t 1 -r input; then :; fi
if [ -n "$input" ]; then
mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
[ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)"
minute_bytes="$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null)"
[ -z "$minute_bytes" ] && minute_bytes="$(echo "$input" | jsonfilter -e '$.minute_bytes' 2>/dev/null)"
hourly_bytes="$(echo "$input" | jsonfilter -e '$[2]' 2>/dev/null)"
[ -z "$hourly_bytes" ] && hourly_bytes="$(echo "$input" | jsonfilter -e '$.hourly_bytes' 2>/dev/null)"
daily_bytes="$(echo "$input" | jsonfilter -e '$[3]' 2>/dev/null)"
[ -z "$daily_bytes" ] && daily_bytes="$(echo "$input" | jsonfilter -e '$.daily_bytes' 2>/dev/null)"
weekly_bytes="$(echo "$input" | jsonfilter -e '$[4]' 2>/dev/null)"
[ -z "$weekly_bytes" ] && weekly_bytes="$(echo "$input" | jsonfilter -e '$.weekly_bytes' 2>/dev/null)"
monthly_bytes="$(echo "$input" | jsonfilter -e '$[5]' 2>/dev/null)"
[ -z "$monthly_bytes" ] && monthly_bytes="$(echo "$input" | jsonfilter -e '$.monthly_bytes' 2>/dev/null)"
total_bytes="$(echo "$input" | jsonfilter -e '$[6]' 2>/dev/null)"
[ -z "$total_bytes" ] && total_bytes="$(echo "$input" | jsonfilter -e '$.total_bytes' 2>/dev/null)"
else
mac="$3"
minute_bytes="$4"
hourly_bytes="$5"
daily_bytes="$6"
weekly_bytes="$7"
monthly_bytes="$8"
total_bytes="$9"
fi
set_traffic_quota "$mac" "$minute_bytes" "$hourly_bytes" "$daily_bytes" "$weekly_bytes" "$monthly_bytes" "$total_bytes"
;;
deleteTrafficQuota)
mac=""
input=""
if read -t 1 -r input; then :; fi
if [ -n "$input" ]; then
mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
[ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)"
else
mac="$3"
fi
delete_traffic_quota "$mac"
;;
esac esac
;; ;;
esac esac
@@ -26,7 +26,8 @@
"installUpdate", "installUpdate",
"getTrafficUsageRanking", "getTrafficUsageRanking",
"getTrafficUsageIncrements", "getTrafficUsageIncrements",
"getRateLimitWhitelist" "getRateLimitWhitelist",
"getTrafficQuotas"
] ]
}, },
"uci": [ "uci": [
@@ -64,7 +65,10 @@
"addRateLimitWhitelist", "addRateLimitWhitelist",
"deleteRateLimitWhitelist", "deleteRateLimitWhitelist",
"setDefaultRateLimit", "setDefaultRateLimit",
"updateScheduleLimit" "updateScheduleLimit",
"getTrafficQuotas",
"setTrafficQuota",
"deleteTrafficQuota"
] ]
}, },
"uci": [ "uci": [
+2 -2
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-clashoo PKG_NAME:=luci-app-clashoo
PKG_VERSION:=1.30.0 PKG_VERSION:=1.30.0
PKG_RELEASE:=11 PKG_RELEASE:=15
PKG_MAINTAINER:=kenzok8 PKG_MAINTAINER:=kenzok8
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
@@ -75,7 +75,7 @@ define Package/luci-i18n-clashoo-zh-cn
SECTION:=luci SECTION:=luci
CATEGORY:=LuCI CATEGORY:=LuCI
TITLE:=luci-app-clashoo - Simplified Chinese (zh-cn) TITLE:=luci-app-clashoo - Simplified Chinese (zh-cn)
DEPENDS:=+luci-app-clashoo DEPENDS:=luci-app-clashoo
DEFAULT:=LUCI_LANG_zh_Hans||(ALL&&m) DEFAULT:=LUCI_LANG_zh_Hans||(ALL&&m)
HIDDEN:=1 HIDDEN:=1
PKGARCH:=all PKGARCH:=all
@@ -143,7 +143,7 @@ const callOverview = rpc.declare({ object: 'luci.clashoo', method: 'ove
const callSmartFlushCache = rpc.declare({ object: 'luci.clashoo', method: 'smart_flush_cache', expect: {} }); const callSmartFlushCache = rpc.declare({ object: 'luci.clashoo', method: 'smart_flush_cache', expect: {} });
const callListSingboxProfiles = rpc.declare({ object: 'luci.clashoo', method: 'list_singbox_profiles', expect: {} }); const callListSingboxProfiles = rpc.declare({ object: 'luci.clashoo', method: 'list_singbox_profiles', expect: {} });
const callGetSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'get_singbox_profile', params: ['name'], expect: {} }); const callGetSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'get_singbox_profile', params: ['name'], expect: {} });
const callSaveSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'save_singbox_profile', params: ['name', 'content'], expect: {} }); const callSaveSingboxProfileChunk = rpc.declare({ object: 'luci.clashoo', method: 'save_singbox_profile_chunk', params: ['name', 'content', 'index', 'total'], expect: {} });
const callSetSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'set_singbox_profile', params: ['name'], expect: {} }); const callSetSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'set_singbox_profile', params: ['name'], expect: {} });
const callDeleteSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'delete_singbox_profile', params: ['name'], expect: {} }); const callDeleteSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'delete_singbox_profile', params: ['name'], expect: {} });
const callCreateSingboxConfig = rpc.declare({ object: 'luci.clashoo', method: 'create_singbox_config', params: ['sub_url', 'name'], expect: {} }); const callCreateSingboxConfig = rpc.declare({ object: 'luci.clashoo', method: 'create_singbox_config', params: ['sub_url', 'name'], expect: {} });
@@ -235,7 +235,23 @@ return baseclass.extend({
listSingboxProfiles: function () { return L.resolveDefault(callListSingboxProfiles(), { profiles: [], active: '' }); }, listSingboxProfiles: function () { return L.resolveDefault(callListSingboxProfiles(), { profiles: [], active: '' }); },
getSingboxProfile: function (name) { return L.resolveDefault(callGetSingboxProfile(name), {}); }, getSingboxProfile: function (name) { return L.resolveDefault(callGetSingboxProfile(name), {}); },
saveSingboxProfile: function (name, content){ return L.resolveDefault(callSaveSingboxProfile(name, content), {}); }, saveSingboxProfile: function (name, content) {
var chunkSize = 24576;
var total = Math.max(1, Math.ceil((content || '').length / chunkSize));
var index = 0;
function sendNext() {
var chunk = (content || '').slice(index * chunkSize, (index + 1) * chunkSize);
return L.resolveDefault(callSaveSingboxProfileChunk(name, chunk, String(index), String(total)), {}).then(function (r) {
if (!r || !r.success)
return { success: false, error: (r && r.error) || 'upload_failed', message: (r && (r.message || r.error)) || _('Upload failed') };
index++;
return index < total ? sendNext() : r;
});
}
return sendNext();
},
setSingboxProfile: function (name) { return L.resolveDefault(callSetSingboxProfile(name), {}); }, setSingboxProfile: function (name) { return L.resolveDefault(callSetSingboxProfile(name), {}); },
deleteSingboxProfile: function (name) { return L.resolveDefault(callDeleteSingboxProfile(name), {}); }, deleteSingboxProfile: function (name) { return L.resolveDefault(callDeleteSingboxProfile(name), {}); },
createSingboxConfig: function (url, name) { return L.resolveDefault(callCreateSingboxConfig(url, name), {}); }, createSingboxConfig: function (url, name) { return L.resolveDefault(callCreateSingboxConfig(url, name), {}); },
@@ -1357,6 +1357,14 @@ return view.extend({
o = s.option(form.Flag, 'dns_respect_rules', _("DNS Respect Rules")); o = s.option(form.Flag, 'dns_respect_rules', _("DNS Respect Rules"));
o.default = '1'; o.default = '1';
o.description = _("DNS queries follow the routing rules, so overseas domains resolve through the proxy instead of a polluted result. Requires proxy-server-nameserver."); o.description = _("DNS queries follow the routing rules, so overseas domains resolve through the proxy instead of a polluted result. Requires proxy-server-nameserver.");
o = s.option(form.Flag, 'dns_loopback_compat', _("OpenClash/Nikki Loopback DNS Compatibility"));
o.default = '0';
o.description = _("Optional compatibility for normal managed mode. At runtime, replace only proxy node resolvers that point back to the imported configuration's own DNS listener. Core Only mode stays verbatim; the source configuration and unrelated local DNS services remain unchanged.");
o.depends('enable_dns', '1');
o = s.option(form.DynamicList, 'dns_loopback_compat_resolver', _("Loopback DNS Replacement"));
o.placeholder = 'udp://223.5.5.5:53';
o.description = _("Used only when the compatibility switch detects a stale self-reference. When empty, udp://223.5.5.5:53 and udp://119.29.29.29:53 are used. Plain IP DNS is recommended to avoid bootstrap loops.");
o.depends('dns_loopback_compat', '1');
o = s.option(form.Value, 'dns_ecs', _("ECS Client Subnet")); o = s.option(form.Value, 'dns_ecs', _("ECS Client Subnet"));
o.placeholder = _("Recommended blank"); o.placeholder = _("Recommended blank");
o.description = _("mihomo writes the ecs parameter to DNS URLs; sing-box writes dns.client_subnet. Leave empty to skip."); o.description = _("mihomo writes the ecs parameter to DNS URLs; sing-box writes dns.client_subnet. Leave empty to skip.");
+12
View File
@@ -902,9 +902,18 @@ msgstr ""
msgid "OpenClash is enabled; disable it first to avoid rule conflicts" msgid "OpenClash is enabled; disable it first to avoid rule conflicts"
msgstr "" msgstr ""
msgid "OpenClash/Nikki Loopback DNS Compatibility"
msgstr ""
msgid "Operation failed: " msgid "Operation failed: "
msgstr "" msgstr ""
msgid "Loopback DNS Replacement"
msgstr ""
msgid "Optional compatibility for normal managed mode. At runtime, replace only proxy node resolvers that point back to the imported configuration's own DNS listener. Core Only mode stays verbatim; the source configuration and unrelated local DNS services remain unchanged."
msgstr ""
msgid "Other Configuration Files (upload / overwrite output)" msgid "Other Configuration Files (upload / overwrite output)"
msgstr "" msgstr ""
@@ -1502,6 +1511,9 @@ msgstr ""
msgid "User-Agent (UA)" msgid "User-Agent (UA)"
msgstr "" msgstr ""
msgid "Used only when the compatibility switch detects a stale self-reference. When empty, udp://223.5.5.5:53 and udp://119.29.29.29:53 are used. Plain IP DNS is recommended to avoid bootstrap loops."
msgstr ""
msgid "Username" msgid "Username"
msgstr "" msgstr ""
+12
View File
@@ -909,6 +909,18 @@ msgstr "TCP 或 UDP 任意一个选择 TUN 时,该栈设置才会生效。"
msgid "OpenClash is enabled; disable it first to avoid rule conflicts" msgid "OpenClash is enabled; disable it first to avoid rule conflicts"
msgstr "OpenClash 已启用,请先停用以避免规则冲突" msgstr "OpenClash 已启用,请先停用以避免规则冲突"
msgid "OpenClash/Nikki Loopback DNS Compatibility"
msgstr "OpenClash/Nikki 回环 DNS 兼容"
msgid "Loopback DNS Replacement"
msgstr "回环 DNS 替换地址"
msgid "Optional compatibility for normal managed mode. At runtime, replace only proxy node resolvers that point back to the imported configuration's own DNS listener. Core Only mode stays verbatim; the source configuration and unrelated local DNS services remain unchanged."
msgstr "用于普通接管模式的可选兼容功能。运行时仅替换指回导入配置自身 DNS 监听的节点解析器;仅内核模式保持原样,源配置和其他本地 DNS 服务不受影响。"
msgid "Used only when the compatibility switch detects a stale self-reference. When empty, udp://223.5.5.5:53 and udp://119.29.29.29:53 are used. Plain IP DNS is recommended to avoid bootstrap loops."
msgstr "仅在兼容开关检测到失效自引用时使用。留空则使用 udp://223.5.5.5:53 和 udp://119.29.29.29:53。建议使用纯 IP DNS,避免启动解析死循环。"
msgid "Operation failed: " msgid "Operation failed: "
msgstr "操作失败: " msgstr "操作失败: "
@@ -1135,7 +1135,7 @@ return '_merged_' + s + '__' + t + '.yaml';
} }
function template_search_dirs() { function template_search_dirs() {
return [TEMPLATE_USER_DIR, TEMPLATE_DIR, '/usr/share/clashoo/config/template']; return [TEMPLATE_DIR, '/usr/share/clashoo/config/template'];
} }
function find_template_path(name) { function find_template_path(name) {
@@ -3318,6 +3318,8 @@ call: function(req) {
let tpl_src = trim(req.args?.template_source || ''); let tpl_src = trim(req.args?.template_source || '');
let sub_url = trim(req.args?.sub_url || ''); let sub_url = trim(req.args?.sub_url || '');
let output_name = ensure_yaml_name(req.args?.output_name || ''); let output_name = ensure_yaml_name(req.args?.output_name || '');
if (!output_name) output_name = 'tpl-rewrite.yaml';
let uniq_name = replace(output_name, /\.(yaml|yml)$/, '');
let set_active = (req.args?.set_active || '') == '1'; let set_active = (req.args?.set_active || '') == '1';
if (!tpl_src) if (!tpl_src)
@@ -3361,20 +3363,16 @@ return { success: false, error: 'missing_yq', message: '缺少 yq,无法注入
if (!access(exploded, "r")) { if (!access(exploded, "r")) {
system("cp -f " + shell_quote(tmp_tpl) + " " + shell_quote(exploded)); system("cp -f " + shell_quote(tmp_tpl) + " " + shell_quote(exploded));
} }
/* proxy-providers 和 rule-providers 两处都注入(兼容不同模板格式) */ /* 替换 proxy-providers.url 并把 provider 名改成唯一名(同步 proxy-groups 的 use*/
let inject_cmd = "URL=" + shell_quote(sub_url) let inject_cmd = "URL=" + shell_quote(sub_url) + " NAME=" + shell_quote(uniq_name)
+ " yq e '.[\"proxy-providers\"][].url = env(URL) | .[\"rule-providers\"][].url = env(URL)' " + " yq e '.[\"proxy-providers\"][].url = env(URL) | (.[\"proxy-providers\"] | keys | map({\"key\": ., \"value\": strenv(NAME) + \"-\" + .}) | from_entries) as $rename | .[\"proxy-providers\"] |= with_entries(.key = $rename[.key]) | (.[\"proxy-groups\"][] | select(has(\"use\")) | .use) |= map($rename[.] // .)' "
+ shell_quote(exploded) + " > " + shell_quote(tmp_out); + shell_quote(exploded) + " > " + shell_quote(tmp_out);
if (system(inject_cmd) != 0 || !access(tmp_out, "r")) { if (system(inject_cmd) != 0 || !access(tmp_out, "r")) {
system("rm -f " + shell_quote(exploded)); system("rm -f " + shell_quote(exploded));
return { success: false, error: "inject_failed", message: "订阅 URL 注入失败" }; return { success: false, error: "inject_failed", message: "订阅 URL 注入失败" };
} }
system("rm -f " + shell_quote(exploded)); system("rm -f " + shell_quote(exploded));
if (!output_name) {
output_name = 'tpl-rewrite.yaml';
}
let out_path = out_dir + '/' + output_name; let out_path = out_dir + '/' + output_name;
if (system('mv -f ' + shell_quote(tmp_out) + ' ' + shell_quote(out_path)) != 0) if (system('mv -f ' + shell_quote(tmp_out) + ' ' + shell_quote(out_path)) != 0)
return { success: false, error: 'write_failed', message: '写入结果文件失败' }; return { success: false, error: 'write_failed', message: '写入结果文件失败' };
@@ -3382,16 +3380,16 @@ return { success: false, error: 'write_failed', message: '写入结果文件失
/* 删除 respect-rules(模板可能设了但没提供 proxy-server-nameserver */ /* 删除 respect-rules(模板可能设了但没提供 proxy-server-nameserver */
system("sed -i '/respect-rules/d' " + shell_quote(out_path) + " 2>/dev/null"); system("sed -i '/respect-rules/d' " + shell_quote(out_path) + " 2>/dev/null");
system('rm -f ' + shell_quote(tmp_tpl)); system('rm -f ' + shell_quote(tmp_tpl));
/* 替换 直连→DIRECT(兼容不同模板的命名差异) */ /* 只把独立引用项"直连"转成 DIRECT,组名里的"直连"不动 */
system("sed -i 's/直连/DIRECT/g' " + shell_quote(out_path) + " 2>/dev/null"); system("sed -i 's/^\\( *- *\\)直连$/\\1DIRECT/' " + shell_quote(out_path) + " 2>/dev/null");
system("yq e '(.proxy-groups[].proxies) |= sub(\"直连\", \"DIRECT\")' " + shell_quote(out_path) + " -i 2>/dev/null"); system("yq e '(.proxy-groups[] | select(has(\"proxies\")) | .proxies) |= map(sub(\"^直连$\", \"DIRECT\"))' " + shell_quote(out_path) + " -i 2>/dev/null");
/* 用 mixin.uc + yq merge 叠加 UCI 覆盖(端口、routing-mark、dns 等) */ /* 用 mixin.uc + yq merge 叠加 UCI 覆盖(端口、routing-mark、dns 等) */
let mixin_script = "/usr/share/clashoo/runtime/mixin.uc"; let mixin_script = "/usr/share/clashoo/runtime/mixin.uc";
if (access(mixin_script, "r") && system("command -v yq >/dev/null 2>&1") == 0) { if (access(mixin_script, "r") && system("command -v yq >/dev/null 2>&1") == 0) {
let mixin_yaml = "/tmp/_clashoo_mixin.yaml"; let mixin_yaml = "/tmp/_clashoo_mixin.yaml";
let merged_out = out_path + ".merged"; let merged_out = out_path + ".merged";
system("ucode " + shell_quote(mixin_script) + " 2>/dev/null | yq -M -p json -o yaml > " + shell_quote(mixin_yaml) + " 2>/dev/null"); system("ucode " + shell_quote(mixin_script) + " 2>/dev/null | yq -M -p json -o yaml > " + shell_quote(mixin_yaml) + " 2>/dev/null");
system("yq -M eval-all '. as $item ireduce ({}; . * $item)' " + shell_quote(out_path) + " " + shell_quote(mixin_yaml) + " > " + shell_quote(merged_out) + " 2>/dev/null"); system("yq -M eval-all '(. as $item ireduce ({}; . * $item)) as $m | [\"port\",\"socks-port\",\"redir-port\",\"tproxy-port\",\"mixed-port\",\"allow-lan\",\"bind-address\",\"mode\",\"log-level\",\"ipv6\",\"interface-name\",\"routing-mark\",\"external-controller\",\"external-ui\",\"secret\",\"tun\",\"listeners\",\"tunnels\",\"dns\",\"hosts\",\"profile\",\"geodata-mode\",\"geodata-loader\",\"geox-url\",\"proxy-providers\",\"proxies\",\"proxy-groups\",\"rule-providers\",\"rules\",\"sub-rules\",\"ntp\",\"authentication\",\"sniffer\",\"experimental\"] as $order | ($order | to_entries | map({\"key\": .value, \"value\": .key}) | from_entries) as $rank | $m | to_entries | sort_by(.key as $k | $rank[$k] // 999) | from_entries' " + shell_quote(out_path) + " " + shell_quote(mixin_yaml) + " > " + shell_quote(merged_out) + " 2>/dev/null");
if (access(merged_out, "r")) { if (access(merged_out, "r")) {
system("mv -f " + shell_quote(merged_out) + " " + shell_quote(out_path)); system("mv -f " + shell_quote(merged_out) + " " + shell_quote(out_path));
} }
@@ -3812,6 +3810,50 @@ return { success: true, name };
} }
}, },
save_singbox_profile_chunk: {
args: { name: 'name', content: 'content', index: 'index', total: 'total' },
call: function(req) {
let name = req.args?.name || '';
let content = req.args?.content;
let idx = int(req.args?.index || 0);
let total = int(req.args?.total || 0);
if (!name || index(name, '/') >= 0 || index(name, '..') >= 0)
return { success: false, error: 'invalid name' };
if (!match(name, /\.json$/)) name = name + '.json';
if (type(content) != 'string') return { success: false, error: 'invalid content' };
if (total < 1 || idx < 0 || idx >= total) return { success: false, error: 'invalid chunk' };
if (total == 1 && !content) return { success: false, error: 'empty content' };
let dir = '/usr/share/clashoo/config/singbox';
system('mkdir -p ' + shell_quote(dir) + ' >/dev/null 2>&1');
let tmp = '/tmp/clashoo_sb_upload_' + name;
if (idx == 0) {
if (writefile(tmp, content) === null)
return { success: false, error: 'write_failed', message: '写入文件失败' };
} else {
let fp = open(tmp, 'a');
if (!fp) return { success: false, error: 'write_failed', message: '打开文件失败' };
let ok = fp.write(content);
fp.close();
if (ok === null) return { success: false, error: 'write_failed', message: '写入文件失败' };
}
if (idx + 1 < total)
return { success: true, name, index: idx, total, complete: false };
let binary = find_binary();
if (binary && match(binary, /sing-box/)) {
if (system(shell_quote(binary) + ' check -c ' + shell_quote(tmp) + ' >/dev/null 2>&1') != 0) {
system('rm -f ' + shell_quote(tmp));
return { success: false, error: 'invalid_config', message: 'sing-box 配置验证失败,请检查 JSON 格式' };
}
}
if (system('cp -f ' + shell_quote(tmp) + ' ' + shell_quote(dir + '/' + name) + ' >/dev/null 2>&1') != 0) {
system('rm -f ' + shell_quote(tmp));
return { success: false, error: 'write_failed', message: '写入文件失败' };
}
system('rm -f ' + shell_quote(tmp));
return { success: true, name, index: idx, total, complete: true };
}
},
set_singbox_profile: { set_singbox_profile: {
args: { name: 'name' }, args: { name: 'name' },
call: function(req) { call: function(req) {
+3 -2
View File
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-daede PKG_NAME:=luci-app-daede
PKG_VERSION:=1.14.7 PKG_VERSION:=1.15
PKG_RELEASE:=25 PKG_RELEASE:=2
PKG_MAINTAINER:=kenzok8 PKG_MAINTAINER:=kenzok8
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
@@ -78,6 +78,7 @@ define Package/$(PKG_NAME)/install
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/proxy-check.sh $(1)/usr/share/luci-app-daede/proxy-check.sh $(INSTALL_BIN) ./root/usr/share/luci-app-daede/proxy-check.sh $(1)/usr/share/luci-app-daede/proxy-check.sh
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/fetch-clash-yaml.sh $(1)/usr/share/luci-app-daede/fetch-clash-yaml.sh $(INSTALL_BIN) ./root/usr/share/luci-app-daede/fetch-clash-yaml.sh $(1)/usr/share/luci-app-daede/fetch-clash-yaml.sh
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/config-backup.sh $(1)/usr/share/luci-app-daede/config-backup.sh $(INSTALL_BIN) ./root/usr/share/luci-app-daede/config-backup.sh $(1)/usr/share/luci-app-daede/config-backup.sh
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/snapshot-file.sh $(1)/usr/share/luci-app-daede/snapshot-file.sh
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/config-defaults.sh $(1)/usr/share/luci-app-daede/config-defaults.sh $(INSTALL_BIN) ./root/usr/share/luci-app-daede/config-defaults.sh $(1)/usr/share/luci-app-daede/config-defaults.sh
$(INSTALL_DIR) $(1)/usr/share/luci-app-daede/defaults $(INSTALL_DIR) $(1)/usr/share/luci-app-daede/defaults
$(INSTALL_DATA) $(CURDIR)/../dae/files/dae.config $(1)/usr/share/luci-app-daede/defaults/dae $(INSTALL_DATA) $(CURDIR)/../dae/files/dae.config $(1)/usr/share/luci-app-daede/defaults/dae
@@ -13,6 +13,7 @@
const FETCHER = '/usr/share/luci-app-daede/fetch-clash-yaml.sh'; const FETCHER = '/usr/share/luci-app-daede/fetch-clash-yaml.sh';
const GENERATOR = '/usr/share/luci-app-daede/gen-dae-config.sh'; const GENERATOR = '/usr/share/luci-app-daede/gen-dae-config.sh';
const SNAPSHOT_HELPER = '/usr/share/luci-app-daede/snapshot-file.sh';
const SUB_STAGE = '/tmp/daede-sub.txt'; const SUB_STAGE = '/tmp/daede-sub.txt';
const FETCH_CHUNK_BYTES = 16384; const FETCH_CHUNK_BYTES = 16384;
@@ -651,11 +652,19 @@ return view.extend({
const name = airportName.value.trim(); const name = airportName.value.trim();
const groupName = airportSync.backendGroupName(name, 'daed', airportId); const groupName = airportSync.backendGroupName(name, 'daed', airportId);
const endpoint = daedEndpoint(); const endpoint = daedEndpoint();
// daed can't read file:// — it HTTP-fetches a subscription link. Stage // daed can't read file:// — it HTTP-fetches a subscription link. Persist
// the converted links (base64) and let daed pull them from the // the converted snapshot and let daed pull it from the loopback-only CGI,
// loopback-only CGI, so the import lands as ONE subscription. // so later daed refreshes and configuration backups keep working.
const subToken = airportSync.backendId(airportId) + Date.now().toString(36); // Keep the CGI token opaque, bounded, and unique across imports started in
const stageFile = '/tmp/daede-daedsub-' + subToken; // the same millisecond. The final sanitization also protects the filename
// and the CGI's allow-list if backendId ever changes.
const tokenTime = Date.now().toString(36);
const tokenRandom = Math.random().toString(36).slice(2, 12);
const tokenPrefix = airportSync.backendId(airportId).replace(/[^A-Za-z0-9]/g, '')
.slice(0, Math.max(0, 64 - tokenTime.length - tokenRandom.length));
const subToken = (tokenPrefix + tokenTime + tokenRandom).slice(0, 64);
const snapshotFile = '/etc/daed/daede-sub-' + subToken;
const stageFile = '/etc/daed/.daede-sub-stage-' + subToken;
const subUrl = 'http://127.0.0.1/cgi-bin/daede-sub?t=' + subToken; const subUrl = 'http://127.0.0.1/cgi-bin/daede-sub?t=' + subToken;
const b64 = btoa(items.map(function(item) { return item.link; }).join('\n') + '\n'); const b64 = btoa(items.map(function(item) { return item.link; }).join('\n') + '\n');
@@ -664,15 +673,156 @@ return view.extend({
let createdGroupId = ''; let createdGroupId = '';
const createdNodeIds = []; const createdNodeIds = [];
let groupReady = false; let groupReady = false;
let snapshotCommitted = false;
let snapshotFinalToken = '';
let reuseExistingSnapshot = false;
const oldSubId = existingAirport ? existingAirport.subscription_id : ''; const oldSubId = existingAirport ? existingAirport.subscription_id : '';
const oldNodeIds = existingAirport ? existingAirport.node_ids : []; const oldNodeIds = existingAirport ? existingAirport.node_ids : [];
const dropStage = function() { return fs.exec('/bin/rm', [ '-f', stageFile ]).catch(function() {}); }; const dropSnapshot = function(path) {
if (!path)
return Promise.resolve();
return fs.remove(path).catch(function(error) {
const message = String(error && (error.message || error) || '');
if (/not found|no such file|enoent/i.test(message))
return;
throw error;
});
};
const appendWarning = function(error, warning) {
const result = error instanceof Error ? error : new Error(String(error && (error.message || error) || error));
result.message = result.message + '; ' + warning;
return result;
};
const snapshotAction = function(args) {
return fs.exec(SNAPSHOT_HELPER, args).then(function(res) {
if (!res || res.code !== 0)
throw new Error((res && (res.stderr || res.stdout)) || _('Snapshot operation failed'));
return res;
});
};
const cleanupStage = function() {
return snapshotAction([ 'discard', subToken ]);
};
const querySubscriptions = function() {
if (!token)
return Promise.reject(new Error(_('daed subscription cleanup could not be verified')));
return graphQL(endpoint, 'query SubscriptionLinks{subscriptions{id link}}', {}, token).then(function(value) {
if (!value || !Array.isArray(value.subscriptions))
throw new Error(_('daed returned an invalid subscription list'));
return value.subscriptions;
});
};
const dropSnapshotIfUnreferenced = function() {
if (snapshotFinalToken !== subToken)
return Promise.resolve();
return querySubscriptions().then(function(subscriptions) {
if (subscriptions.some(function(sub) { return String(sub.link || '') === subUrl; }))
throw new Error(_('new daed subscription snapshot is still referenced; it was kept'));
return dropSnapshot(snapshotFile);
});
};
const finishWithSnapshotCleanup = function(result) {
return dropSnapshotIfUnreferenced().then(function() {
return result;
}).catch(function(error) {
const warning = String(error && (error.message || error) || error);
result.warning = result.warning
? result.warning + '; ' + warning
: warning;
return result;
});
};
const updateExistingSubscription = function() {
const old = (((before || {}).subscriptions) || []).find(function(sub) { return sub.id === oldSubId; });
if (!old)
return Promise.reject(new Error(_('Managed subscription no longer exists in daed')));
const oldLink = String(old.link || '');
const localToken = oldLink.match(/^http:\/\/127\.0\.0\.1\/cgi-bin\/daede-sub\?t=([A-Za-z0-9]{1,64})$/);
const refresh = function() {
return graphQL(endpoint, 'mutation UpdateSub($id:ID!){updateSubscription(id:$id){id}}', { id: oldSubId }, token);
};
const currentLink = function() {
return querySubscriptions().then(function(subscriptions) {
const current = subscriptions.find(function(sub) { return sub.id === oldSubId; });
return current ? String(current.link || '') : '';
});
};
const restoreOldLink = function(originalError) {
return graphQL(endpoint, 'mutation RestoreLink($id:ID!,$link:String!){updateSubscriptionLink(id:$id,link:$link)}', {
id: oldSubId,
link: oldLink
}, token).then(refresh).then(currentLink).then(function(link) {
if (link !== oldLink) {
snapshotCommitted = true;
throw appendWarning(originalError, _('rollback was not confirmed; both subscription snapshots were kept'));
}
throw originalError;
}, function(rollbackError) {
snapshotCommitted = true;
throw appendWarning(originalError, _('rollback could not be confirmed; both subscription snapshots were kept: %s').format(String(rollbackError && (rollbackError.message || rollbackError) || rollbackError)));
});
};
const finish = function() {
const tag = old.tag !== groupName
? graphQL(endpoint, 'mutation TagSub($id:ID!,$tag:String!){tagSubscription(id:$id,tag:$tag)}', {
id: oldSubId,
tag: groupName
}, token).catch(function(error) {
return String(error && (error.message || error) || error);
})
: Promise.resolve('');
return tag.then(function(tagWarning) {
writeAirportRecord(existingAirport, {
id: airportId,
backend: 'daed',
name: name,
sourceHash: state.sourceHash,
groupId: existingAirport.group_id,
subscriptionId: oldSubId,
nodeIds: []
});
return applyUciChanges().then(function() {
items.forEach(function(item) { item.duplicate = true; item.selected = false; });
return {
added: items.length,
duplicates: 0,
failed: 0,
warning: tagWarning
};
});
});
};
if (localToken && reuseExistingSnapshot) {
return graphQL(endpoint, 'mutation UpdateSub($id:ID!){updateSubscription(id:$id){id}}', { id: oldSubId }, token)
.then(currentLink).then(function(link) {
if (link !== oldLink)
throw new Error(_('daed did not confirm the existing subscription link after refresh'));
}).catch(function(error) {
throw appendWarning(error, _('daed subscription refresh failed; the new snapshot remains at the existing link and existing nodes were kept'));
}).then(finish);
}
return graphQL(endpoint, 'mutation SetLink($id:ID!,$link:String!){updateSubscriptionLink(id:$id,link:$link)}', {
id: oldSubId,
link: subUrl
}, token).then(refresh).then(function() {
return currentLink().then(function(link) {
if (link !== subUrl)
throw new Error(_('daed did not confirm the new subscription link'));
});
}).catch(restoreOldLink).then(function() {
snapshotCommitted = true;
return finish();
});
};
const loadState = function(forceLogin) { const loadState = function(forceLogin) {
return requestDaedToken(endpoint, forceLogin).then(function(auth) { return requestDaedToken(endpoint, forceLogin).then(function(auth) {
token = auth.token; token = auth.token;
usedCachedToken = auth.cached; usedCachedToken = auth.cached;
return graphQL(endpoint, 'query State{nodes(first:10000){edges{id link tag}} groups{id name nodes{id}}}', {}, token); return graphQL(endpoint, 'query State{nodes(first:10000){edges{id link tag}} groups{id name nodes{id}} subscriptions{id link tag}}', {}, token);
}); });
}; };
const importDirectNodes = function() { const importDirectNodes = function() {
@@ -797,7 +947,7 @@ return view.extend({
}); });
}; };
return fs.write(stageFile, b64).then(function() { return fs.write(stageFile, b64, 384).then(function() {
return loadState(false).catch(function(error) { return loadState(false).catch(function(error) {
if (!usedCachedToken || !daedSession.isAccessDenied(error)) if (!usedCachedToken || !daedSession.isAccessDenied(error))
throw error; throw error;
@@ -806,42 +956,63 @@ return view.extend({
}); });
}).then(function(dataValue) { }).then(function(dataValue) {
before = dataValue; before = dataValue;
// drop this airport's previous subscription first: the new one const old = (before.subscriptions || []).find(function(sub) { return sub.id === oldSubId; });
// reuses the same tag (group name) and daed enforces unique tags. const localToken = old && String(old.link || '').match(/^http:\/\/127\.0\.0\.1\/cgi-bin\/daede-sub\?t=([A-Za-z0-9]{1,64})$/);
if (!oldSubId) const oldLinkReferences = localToken
return null; ? (before.subscriptions || []).filter(function(sub) { return String(sub.link || '') === String(old.link || ''); })
return graphQL(endpoint, 'mutation RmSub($ids:[ID!]!){removeSubscriptions(ids:$ids)}', { ids: [ oldSubId ] }, token).catch(function() {}); : [];
}).then(function() { reuseExistingSnapshot = !!(localToken && oldLinkReferences.length === 1 && String(oldLinkReferences[0].id) === String(oldSubId));
// import the whole converted batch as one subscription const publish = reuseExistingSnapshot
return graphQL(endpoint, ? snapshotAction([ 'replace', localToken[1], subToken ]).then(function() {
'mutation Import($a:ImportArgument!){importSubscription(rollbackError:false,arg:$a){sub{id} nodeImportResult{error}}}', snapshotFinalToken = localToken[1];
{ a: { link: subUrl, tag: groupName } }, token).then(function(result) { snapshotCommitted = true;
const sub = result.importSubscription && result.importSubscription.sub; })
const rows = (result.importSubscription && result.importSubscription.nodeImportResult) || []; : snapshotAction([ 'publish', subToken ]).then(function() {
const usable = rows.length snapshotFinalToken = subToken;
? rows.some(function(r) { return !r.error || r.error === 'node already exists'; })
: !!(sub && sub.id);
if (!sub || !sub.id || !usable) {
const details = rows.map(function(r) { return r.error; }).filter(Boolean).join('; ');
if (sub && sub.id)
newSubId = sub.id;
throw new Error(details || _('No usable nodes were imported'));
}
return result;
}).catch(function(error) {
if (daedSession.isAccessDenied(error))
throw error;
const cleanupSub = newSubId
? graphQL(endpoint, 'mutation Rm($ids:[ID!]!){removeSubscriptions(ids:$ids)}', { ids: [ newSubId ] }, token).catch(function() {})
: Promise.resolve();
newSubId = '';
return cleanupSub.then(importDirectNodes).then(function(result) {
return { directResult: result };
});
}); });
return publish.then(function() {
if (oldSubId)
return updateExistingSubscription().then(function(result) { return { existingResult: result }; });
// import the whole converted batch as one subscription
return graphQL(endpoint,
'mutation Import($a:ImportArgument!){importSubscription(rollbackError:false,arg:$a){sub{id} nodeImportResult{error}}}',
{ a: { link: subUrl, tag: groupName } }, token).then(function(result) {
const sub = result.importSubscription && result.importSubscription.sub;
const rows = (result.importSubscription && result.importSubscription.nodeImportResult) || [];
const usable = rows.length
? rows.some(function(r) { return !r.error || r.error === 'node already exists'; })
: !!(sub && sub.id);
if (!sub || !sub.id || !usable) {
const details = rows.map(function(r) { return r.error; }).filter(Boolean).join('; ');
if (sub && sub.id)
newSubId = sub.id;
throw new Error(details || _('No usable nodes were imported'));
}
return result;
}).catch(function(error) {
if (daedSession.isAccessDenied(error))
throw error;
const cleanupSub = newSubId
? graphQL(endpoint, 'mutation Rm($ids:[ID!]!){removeSubscriptions(ids:$ids)}', { ids: [ newSubId ] }, token).catch(function(cleanupError) {
snapshotCommitted = true;
throw appendWarning(error, _('rollback could not be confirmed; the new subscription snapshot was kept: %s').format(String(cleanupError && (cleanupError.message || cleanupError) || cleanupError)));
})
: Promise.resolve();
newSubId = '';
return cleanupSub.then(function() {
if (oldSubId)
throw error;
return importDirectNodes();
}).then(function(result) {
return { directResult: result };
});
});
});
}).then(function(result) { }).then(function(result) {
if (result.existingResult)
return result.existingResult;
if (result.directResult) if (result.directResult)
return result.directResult; return finishWithSnapshotCleanup(result.directResult);
const sub = result.importSubscription && result.importSubscription.sub; const sub = result.importSubscription && result.importSubscription.sub;
newSubId = sub.id; newSubId = sub.id;
@@ -861,6 +1032,7 @@ return view.extend({
return ensureGroup.then(function(groupId) { return ensureGroup.then(function(groupId) {
return graphQL(endpoint, 'mutation AddSubs($id:ID!,$ids:[ID!]!){groupAddSubscriptions(id:$id,subscriptionIDs:$ids)}', { id: groupId, ids: [ newSubId ] }, token).then(function() { return graphQL(endpoint, 'mutation AddSubs($id:ID!,$ids:[ID!]!){groupAddSubscriptions(id:$id,subscriptionIDs:$ids)}', { id: groupId, ids: [ newSubId ] }, token).then(function() {
groupReady = true; groupReady = true;
snapshotCommitted = true;
const cleanup = []; const cleanup = [];
// Migrate converter-managed airport groups to proxy, but never // Migrate converter-managed airport groups to proxy, but never
// disturb proxy's existing nodes or other subscriptions. // disturb proxy's existing nodes or other subscriptions.
@@ -880,7 +1052,12 @@ return view.extend({
}); });
return applyUciChanges().then(function() { return applyUciChanges().then(function() {
items.forEach(function(item) { item.duplicate = true; item.selected = false; }); items.forEach(function(item) { item.duplicate = true; item.selected = false; });
return { added: items.length, duplicates: 0, failed: failed }; return {
added: items.length,
duplicates: 0,
failed: failed,
warning: ''
};
}); });
}); });
}); });
@@ -892,15 +1069,47 @@ return view.extend({
throw error; throw error;
const cleanup = []; const cleanup = [];
if (newSubId) if (newSubId)
cleanup.push(graphQL(endpoint, 'mutation Rm($ids:[ID!]!){removeSubscriptions(ids:$ids)}', { ids: [ newSubId ] }, token)); cleanup.push(graphQL(endpoint, 'mutation Rm($ids:[ID!]!){removeSubscriptions(ids:$ids)}', { ids: [ newSubId ] }, token).catch(function(cleanupError) {
snapshotCommitted = true;
throw cleanupError;
}));
if (createdNodeIds.length) if (createdNodeIds.length)
cleanup.push(graphQL(endpoint, 'mutation Rm($ids:[ID!]!){removeNodes(ids:$ids)}', { ids: createdNodeIds }, token)); cleanup.push(graphQL(endpoint, 'mutation Rm($ids:[ID!]!){removeNodes(ids:$ids)}', { ids: createdNodeIds }, token));
if (createdGroupId) if (createdGroupId)
cleanup.push(graphQL(endpoint, 'mutation RmG($id:ID!){removeGroup(id:$id)}', { id: createdGroupId }, token)); cleanup.push(graphQL(endpoint, 'mutation RmG($id:ID!){removeGroup(id:$id)}', { id: createdGroupId }, token));
return Promise.all(cleanup).catch(function() {}).then(function() { throw error; }); return Promise.all(cleanup).then(function() {
}).finally(function() { throw error;
token = ''; }, function(cleanupError) {
return dropStage(); snapshotCommitted = true;
throw appendWarning(error, _('rollback could not be confirmed; the new subscription snapshot was kept: %s').format(String(cleanupError && (cleanupError.message || cleanupError) || cleanupError)));
});
}).then(function(result) {
return cleanupStage().then(function() {
token = '';
return result;
}, function(cleanupError) {
token = '';
result.warning = result.warning
? result.warning + '; ' + String(cleanupError && (cleanupError.message || cleanupError) || cleanupError)
: String(cleanupError && (cleanupError.message || cleanupError) || cleanupError);
return result;
});
}, function(error) {
let cleanupWarning = '';
const removeSnapshot = snapshotCommitted
? Promise.resolve()
: dropSnapshotIfUnreferenced().catch(function(cleanupError) {
cleanupWarning = _('rollback could not be confirmed; the new subscription snapshot was kept: %s').format(String(cleanupError && (cleanupError.message || cleanupError) || cleanupError));
});
return removeSnapshot.then(function() {
return cleanupStage().catch(function(stageError) {
const warning = _('snapshot stage cleanup failed: %s').format(String(stageError && (stageError.message || stageError) || stageError));
cleanupWarning = cleanupWarning ? cleanupWarning + '; ' + warning : warning;
});
}).then(function() {
token = '';
throw cleanupWarning ? appendWarning(error, cleanupWarning) : error;
});
}); });
}; };
@@ -924,8 +1133,11 @@ return view.extend({
setImportStatus(_('Importing node group…')); setImportStatus(_('Importing node group…'));
const action = state.target === 'dae' ? importDae(items) : importDaed(items); const action = state.target === 'dae' ? importDae(items) : importDaed(items);
action.then(function(result) { action.then(function(result) {
setImportStatus(_('Node group imported: added %d, reused %d, failed %d') let message = _('Node group imported: added %d, reused %d, failed %d')
.format(result.added, result.duplicates, result.failed), result.failed ? 'err' : 'ok'); .format(result.added, result.duplicates, result.failed);
if (result.warning)
message += ' ' + _('Completed with warning: %s').format(result.warning);
setImportStatus(message, result.failed || result.warning ? 'err' : 'ok');
renderResults(); renderResults();
}).catch(function(e) { }).catch(function(e) {
setImportStatus(_('Node group import failed: %s').format(e.message || e), 'err'); setImportStatus(_('Node group import failed: %s').format(e.message || e), 'err');
+6
View File
@@ -812,6 +812,12 @@ msgstr ""
msgid "Node group imported: added %d, reused %d, failed %d" msgid "Node group imported: added %d, reused %d, failed %d"
msgstr "" msgstr ""
msgid "Completed with warning: %s"
msgstr ""
msgid "Managed subscription no longer exists in daed"
msgstr ""
msgid "Node group import failed: %s" msgid "Node group import failed: %s"
msgstr "" msgstr ""
+6
View File
@@ -1004,6 +1004,12 @@ msgstr "正在导入节点组…"
msgid "Node group imported: added %d, reused %d, failed %d" msgid "Node group imported: added %d, reused %d, failed %d"
msgstr "节点组导入完成:新增 %d,复用 %d,失败 %d" msgstr "节点组导入完成:新增 %d,复用 %d,失败 %d"
msgid "Completed with warning: %s"
msgstr "已完成,但有警告:%s"
msgid "Managed subscription no longer exists in daed"
msgstr "daed 中已不存在该托管订阅"
msgid "Node group import failed: %s" msgid "Node group import failed: %s"
msgstr "节点组导入失败:%s" msgstr "节点组导入失败:%s"
+6
View File
@@ -1004,6 +1004,12 @@ msgstr "正在导入节点组…"
msgid "Node group imported: added %d, reused %d, failed %d" msgid "Node group imported: added %d, reused %d, failed %d"
msgstr "节点组导入完成:新增 %d,复用 %d,失败 %d" msgstr "节点组导入完成:新增 %d,复用 %d,失败 %d"
msgid "Completed with warning: %s"
msgstr "已完成,但有警告:%s"
msgid "Managed subscription no longer exists in daed"
msgstr "daed 中已不存在该托管订阅"
msgid "Node group import failed: %s" msgid "Node group import failed: %s"
msgstr "节点组导入失败:%s" msgstr "节点组导入失败:%s"
@@ -31,6 +31,12 @@ paths() {
case "${name%.sub}" in ''|*[!A-Za-z0-9_]*) fail 'unexpected subscription filename' ;; esac case "${name%.sub}" in ''|*[!A-Za-z0-9_]*) fail 'unexpected subscription filename' ;; esac
printf 'etc/dae/subscriptions/%s\n' "$name" printf 'etc/dae/subscriptions/%s\n' "$name"
done done
for sub in "$1"/etc/daed/daede-sub-*; do
[ -e "$sub" ] || [ -L "$sub" ] || continue
name="${sub##*/daede-sub-}"
case "$name" in ''|*[!A-Za-z0-9]*) fail 'unexpected daed subscription filename' ;; esac
printf 'etc/daed/daede-sub-%s\n' "$name"
done
} }
check_paths() { check_paths() {
for dir in /etc/config /etc/dae /etc/daed /etc/dae/subscriptions; do for dir in /etc/config /etc/dae /etc/daed /etc/dae/subscriptions; do
@@ -42,6 +48,14 @@ check_paths() {
[ ! -e "/$p" ] || [ -f "/$p" ] || fail "not a regular file: /$p" [ ! -e "/$p" ] || [ -f "/$p" ] || fail "not a regular file: /$p"
done < "$WORK/paths" done < "$WORK/paths"
} }
cleanup_stages() {
for stage in /etc/daed/.daede-sub-stage-*; do
[ -e "$stage" ] || [ -L "$stage" ] || continue
[ ! -L "$stage" ] || fail "refusing symlink: $stage"
[ -f "$stage" ] || fail "not a regular file: $stage"
rm -f "$stage" || fail "failed to remove stale snapshot stage: $stage"
done
}
snapshot() { snapshot() {
mkdir -p "$WORK/before" mkdir -p "$WORK/before"
while IFS= read -r p; do while IFS= read -r p; do
@@ -139,6 +153,9 @@ validate_archive() {
etc/dae/subscriptions/*.sub) etc/dae/subscriptions/*.sub)
name="${p#etc/dae/subscriptions/}" name="${p#etc/dae/subscriptions/}"
case "${name%.sub}" in ''|*[!A-Za-z0-9_]*) fail 'invalid subscription path' ;; esac ;; case "${name%.sub}" in ''|*[!A-Za-z0-9_]*) fail 'invalid subscription path' ;; esac ;;
etc/daed/daede-sub-*)
name="${p#etc/daed/daede-sub-}"
case "$name" in ''|*[!A-Za-z0-9]*) fail 'invalid daed subscription path' ;; esac ;;
*) fail 'unexpected archive entry' ;; *) fail 'unexpected archive entry' ;;
esac esac
done < "$WORK/entries" done < "$WORK/entries"
@@ -175,6 +192,7 @@ run_action() {
fi fi
fi fi
stop_backends stop_backends
[ "$ACTION" = reset ] && cleanup_stages
snapshot snapshot
if [ "$ACTION" = export ]; then if [ "$ACTION" = export ]; then
paths "$WORK/before" > "$WORK/candidates" paths "$WORK/before" > "$WORK/candidates"
@@ -0,0 +1,81 @@
#!/bin/sh
# Publish converter snapshots without exposing a partially written final file.
set -eu
DIR=/etc/daed
PREFIX=daede-sub
STAGE_PREFIX=.daede-sub-stage
fail() {
echo "$*" >&2
exit 1
}
valid_token() {
[ "$#" -eq 1 ] || return 1
case "$1" in
''|*[!A-Za-z0-9]*) return 1 ;;
esac
[ "${#1}" -le 64 ]
}
stage_path() { printf '%s/%s-%s\n' "$DIR" "$STAGE_PREFIX" "$1"; }
final_path() { printf '%s/%s-%s\n' "$DIR" "$PREFIX" "$1"; }
regular_file() {
[ -f "$1" ] && [ ! -L "$1" ]
}
safe_dir() {
[ -d "$DIR" ] && [ ! -L "$DIR" ] || fail "invalid snapshot directory"
}
publish() {
[ "$#" -eq 1 ] || fail 'publish expects one token'
valid_token "$1" || fail 'invalid snapshot token'
safe_dir
stage=$(stage_path "$1")
final=$(final_path "$1")
regular_file "$stage" || fail 'snapshot stage is not a regular file'
[ ! -e "$final" ] && [ ! -L "$final" ] || fail 'snapshot already exists'
chmod 600 "$stage" || fail 'cannot secure snapshot stage'
mv -f "$stage" "$final" || fail 'cannot publish snapshot'
}
replace() {
[ "$#" -eq 2 ] || fail 'replace expects existing and stage tokens'
valid_token "$1" || fail 'invalid existing snapshot token'
valid_token "$2" || fail 'invalid stage snapshot token'
safe_dir
[ "$1" != "$2" ] || fail 'existing and stage tokens must differ'
stage=$(stage_path "$2")
final=$(final_path "$1")
regular_file "$stage" || fail 'snapshot stage is not a regular file'
if [ -e "$final" ] || [ -L "$final" ]; then
regular_file "$final" || fail 'existing snapshot is not a regular file'
fi
chmod 600 "$stage" || fail 'cannot secure snapshot stage'
mv -f "$stage" "$final" || fail 'cannot replace snapshot'
}
discard() {
[ "$#" -eq 1 ] || fail 'discard expects one token'
valid_token "$1" || fail 'invalid snapshot token'
safe_dir
stage=$(stage_path "$1")
if [ -L "$stage" ]; then
fail 'refusing symlink snapshot stage'
fi
if [ -e "$stage" ]; then
[ -f "$stage" ] || fail 'snapshot stage is not a regular file'
rm -f "$stage" || fail 'cannot discard snapshot stage'
fi
}
case "${1:-}" in
publish) shift; publish "$@" ;;
replace) shift; replace "$@" ;;
discard) shift; discard "$@" ;;
*) fail 'unknown snapshot action' ;;
esac
@@ -43,9 +43,12 @@
"/tmp/dae-validate.dae": [ "write" ], "/tmp/dae-validate.dae": [ "write" ],
"/tmp/daede-import.b64": [ "write" ], "/tmp/daede-import.b64": [ "write" ],
"/tmp/daede-sub.txt": [ "write" ], "/tmp/daede-sub.txt": [ "write" ],
"/tmp/daede-daedsub-*": [ "write" ], "/etc/daed/.daede-sub-stage-*": [ "write" ],
"/bin/rm -f /tmp/daede-daedsub-*": [ "exec" ], "/etc/daed/daede-sub-*": [ "write" ],
"/usr/share/luci-app-daede/config-backup.sh import": [ "exec" ], "/usr/share/luci-app-daede/config-backup.sh import": [ "exec" ],
"/usr/share/luci-app-daede/snapshot-file.sh publish *": [ "exec" ],
"/usr/share/luci-app-daede/snapshot-file.sh replace * *": [ "exec" ],
"/usr/share/luci-app-daede/snapshot-file.sh discard *": [ "exec" ],
"/usr/share/luci-app-daede/gen-dae-config.sh write-sub *": [ "exec" ], "/usr/share/luci-app-daede/gen-dae-config.sh write-sub *": [ "exec" ],
"/usr/share/luci-app-daede/gen-dae-config.sh delete-sub *": [ "exec" ], "/usr/share/luci-app-daede/gen-dae-config.sh delete-sub *": [ "exec" ],
"/etc/config/daede": [ "write" ], "/etc/config/daede": [ "write" ],
+5 -5
View File
@@ -2,13 +2,13 @@
# daede-sub - serve a converter's staged node list to the local daed only. # daede-sub - serve a converter's staged node list to the local daed only.
# #
# daed (dae-wing) can only build a subscription by HTTP-fetching a link, and it # daed (dae-wing) can only build a subscription by HTTP-fetching a link, and it
# does not read file:// like dae core. So the converter stages the base64 share # does not read file:// like dae core. So the converter stores the base64 share
# links in /tmp/daede-daedsub-<token> and points daed's importSubscription at # links in /etc/daed/daede-sub-<token> and points daed's importSubscription at
# http://127.0.0.1/cgi-bin/daede-sub?t=<token>. This CGI is the read side. # http://127.0.0.1/cgi-bin/daede-sub?t=<token>. This CGI is the read side.
# #
# Locked to loopback (REMOTE_ADDR), so even though uhttpd listens on the LAN no # Locked to loopback (REMOTE_ADDR), so even though uhttpd listens on the LAN no
# remote client can pull the links. The frontend deletes the staged file after # remote client can pull the links. The snapshot remains available for daed's
# the import (success or failure), so the exposure is one local fetch. # own refresh action and is included in daede configuration backups.
deny() { printf 'Status: 403 Forbidden\r\nContent-Type: text/plain\r\n\r\nforbidden\n'; exit 0; } deny() { printf 'Status: 403 Forbidden\r\nContent-Type: text/plain\r\n\r\nforbidden\n'; exit 0; }
@@ -18,7 +18,7 @@ deny() { printf 'Status: 403 Forbidden\r\nContent-Type: text/plain\r\n\r\nforbid
t=$(printf '%s' "$QUERY_STRING" | sed -n 's/^.*\bt=\([A-Za-z0-9]\{1,64\}\).*$/\1/p') t=$(printf '%s' "$QUERY_STRING" | sed -n 's/^.*\bt=\([A-Za-z0-9]\{1,64\}\).*$/\1/p')
[ -n "$t" ] || deny [ -n "$t" ] || deny
f="/tmp/daede-daedsub-$t" f="/etc/daed/daede-sub-$t"
[ -f "$f" ] || deny [ -f "$f" ] || deny
printf 'Content-Type: text/plain\r\n\r\n' printf 'Content-Type: text/plain\r\n\r\n'
@@ -385,9 +385,11 @@ return view.extend({
so.validate = validateServerValue; so.validate = validateServerValue;
so.modalonly = true; so.modalonly = true;
so = ss.option(form.DynamicList, 'upstream', _('Upstream DNS')); so = ss.option(form.DynamicList, 'upstream', _('Upstream DNS'),
_('Allows the use of a specified upstream for specific domain. <a href="%s" target="_blank">Syntax reference</a>.')
.format('https://github.com/AdguardTeam/dnsproxy#specifying-upstreams-for-domains'));
so.rmempty = false; so.rmempty = false;
so.validate = validateServerValue; //so.validate = validateServerValue; // See #11
so.modalonly = true; so.modalonly = true;
so = ss.option(form.DynamicList, 'fallback', _('Fallback DNS')); so = ss.option(form.DynamicList, 'fallback', _('Fallback DNS'));
+7 -1
View File
@@ -11,6 +11,12 @@ msgstr ""
msgid "Add DNS profile" msgid "Add DNS profile"
msgstr "" msgstr ""
#: htdocs/luci-static/resources/view/dnsproxy.js:389
msgid ""
"Allows the use of a specified upstream for specific domain. <a href=\"%s\" "
"target=\"_blank\">Syntax reference</a>."
msgstr ""
#: htdocs/luci-static/resources/view/dnsproxy.js:285 #: htdocs/luci-static/resources/view/dnsproxy.js:285
msgid "Bogus-NXDOMAIN" msgid "Bogus-NXDOMAIN"
msgstr "" msgstr ""
@@ -122,7 +128,7 @@ msgstr ""
msgid "Failed to apply DNS profile: %s" msgid "Failed to apply DNS profile: %s"
msgstr "" msgstr ""
#: htdocs/luci-static/resources/view/dnsproxy.js:393 #: htdocs/luci-static/resources/view/dnsproxy.js:395
msgid "Fallback DNS" msgid "Fallback DNS"
msgstr "" msgstr ""
+1 -2
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for fastnet LUCI_TITLE:=LuCI support for fastnet
LUCI_DEPENDS:=+fastnet LUCI_DEPENDS:=+fastnet
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
PKG_VERSION:=0.7.2-r1 PKG_VERSION:=0.7.7-r1
# PKG_RELEASE MUST be empty for luci.mk # PKG_RELEASE MUST be empty for luci.mk
PKG_RELEASE:= PKG_RELEASE:=
LUCI_MINIFY_CSS:=0 LUCI_MINIFY_CSS:=0
@@ -17,4 +17,3 @@ LUCI_MINIFY_JS:=0
include $(TOPDIR)/feeds/luci/luci.mk include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature # call BuildPackage - OpenWrt buildroot signature
@@ -6,9 +6,21 @@
'require view'; 'require view';
'require fs'; 'require fs';
const callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
});
function getServiceStatus() { function getServiceStatus() {
return L.resolveDefault(callServiceList('filebrowser-q'), {}).then(function(res) {
let isRunning = false; let isRunning = false;
try {
isRunning = res['filebrowser-q']['instances']['instance1']['running'];
} catch (e) { }
return isRunning; return isRunning;
});
} }
function renderStatus(isRunning, port) { function renderStatus(isRunning, port) {
@@ -26,16 +38,8 @@ function renderStatus(isRunning, port) {
} }
return view.extend({ return view.extend({
load: async function () { load() {
const promises = await Promise.all([ return uci.load('filebrowser-q');
L.resolveDefault(fs.stat('/var/run/filebrowser-q.pid'), null),
uci.load('filebrowser-q')
]);
const data = {
isRunning: promises[0],
conf: promises[1]
};
return data;
}, },
render(data) { render(data) {
@@ -49,24 +53,17 @@ return view.extend({
s = m.section(form.TypedSection); s = m.section(form.TypedSection);
s.anonymous = true; s.anonymous = true;
s.render = function() { s.render = function() {
poll.add(function() { poll.add(function() {
return fs.stat('/var/run/filebrowser-q.pid').then(function(stat) { return L.resolveDefault(getServiceStatus()).then(function(res) {
let view = document.getElementById('service_status'); let view = document.getElementById('service_status');
if (view) { view.innerHTML = renderStatus(res, webport);
view.innerHTML = renderStatus(stat, webport); });
} });
}).catch(function() {
let view = document.getElementById('service_status');
if (view) {
view.innerHTML = renderStatus(null, webport);
}
});
});
return E('div', { class: 'cbi-section', id: 'status_bar' }, [ return E('div', { class: 'cbi-section', id: 'status_bar' }, [
E('p', { id: 'service_status' }, _('Collecting data...')) E('p', { id: 'service_status' }, _('Collecting data...'))
]); ]);
}; }
s = m.section(form.NamedSection, 'config', 'filebrowser'); s = m.section(form.NamedSection, 'config', 'filebrowser');
+2 -2
View File
@@ -7,9 +7,9 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
LUCI_TITLE:=KaiPlus LUCI_TITLE:=KaiPlus
PKG_VERSION:=1.0.1 PKG_VERSION:=1.0.9
PKG_RELEASE:=1 PKG_RELEASE:=1
LUCI_DEPENDS:=+kaiplus +luci-compat LUCI_DEPENDS:=+kaiplus +luci-compat +luci-lib-linkeaseauth
LUCI_MINIFY_CSS:=0 LUCI_MINIFY_CSS:=0
LUCI_MINIFY_JS:=0 LUCI_MINIFY_JS:=0
@@ -62,4 +62,10 @@ base_path.default = "/apps/kaiplus/"
base_path.rmempty = false base_path.rmempty = false
base_path.readonly = true base_path.readonly = true
local release_channel = s:option(ListValue, "release_channel", translate("Release channel"))
release_channel:value("stable", translate("Stable"))
release_channel:value("canary", translate("Canary"))
release_channel.default = "stable"
release_channel.rmempty = false
return m return m
+1 -1
View File
@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-mosdns PKG_NAME:=luci-app-mosdns
PKG_VERSION:=1.7.13 PKG_VERSION:=1.7.14
PKG_RELEASE:=1 PKG_RELEASE:=1
LUCI_TITLE:=LuCI Support for mosdns LUCI_TITLE:=LuCI Support for mosdns
@@ -160,8 +160,18 @@ const injectStyles = () => {
'.btn-allow:hover { background: #059669 !important; color: #fff !important; }', '.btn-allow:hover { background: #059669 !important; color: #fff !important; }',
'.btn-block { background: rgba(239, 68, 68, 0.12) !important; color: #dc2626 !important; border: 1px solid rgba(239, 68, 68, 0.3) !important; }', '.btn-block { background: rgba(239, 68, 68, 0.12) !important; color: #dc2626 !important; border: 1px solid rgba(239, 68, 68, 0.3) !important; }',
'.btn-block:hover { background: #dc2626 !important; color: #fff !important; }', '.btn-block:hover { background: #dc2626 !important; color: #fff !important; }',
'.mosdns-pagination-bar { display: flex !important; justify-content: space-between !important; align-items: center !important; margin-top: 0.5rem !important; padding: 0.75rem 0.5rem 0.25rem 0.5rem !important; border-top: 1px solid var(--border-color-medium, rgba(125,125,125,0.15)) !important; flex-wrap: wrap !important; gap: 0.5rem !important; box-sizing: border-box !important; width: 100% !important; }',
'.mosdns-pagination-bar::after, .mosdns-pagination-bar::before { display: none !important; content: none !important; }',
'.mosdns-pagination-info { display: inline-flex !important; align-items: center !important; font-size: 0.85rem !important; opacity: 0.85 !important; }',
'.mosdns-pagination-btns { display: flex !important; gap: 0.5rem !important; margin-left: auto !important; }',
'.mosdns-page-input { height: 24px !important; line-height: 22px !important; text-align: center !important; font-size: 0.82rem !important; font-weight: 600 !important; padding: 0 4px !important; margin: 0 4px !important; border: 1px solid rgba(125,125,125,0.3) !important; border-radius: 4px !important; background: var(--cbi-section-bg, #fff) !important; color: inherit !important; display: inline-block !important; vertical-align: middle !important; box-sizing: border-box !important; transition: all 0.2s ease !important; }',
'.mosdns-page-input:focus { border-color: #3b82f6 !important; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25) !important; outline: none !important; }',
'.mosdns-page-input::-webkit-outer-spin-button, .mosdns-page-input::-webkit-inner-spin-button { -webkit-appearance: none !important; margin: 0 !important; }',
'.mosdns-page-input[type=number] { -moz-appearance: textfield !important; }',
'@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }', '@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }',
'@media (prefers-color-scheme: dark) {', '@media (prefers-color-scheme: dark) {',
' .mosdns-pagination-bar { border-top-color: rgba(255,255,255,0.08) !important; }',
' .mosdns-page-input { background: rgba(255,255,255,0.06) !important; border-color: rgba(255,255,255,0.18) !important; color: #fff !important; }',
' .mosdns-stat-card, .mosdns-rank-panel, .mosdns-modal-meta-item, .mosdns-answer-row { background: rgba(255,255,255,0.03); border-color: rgba(255,255,255,0.08); box-shadow: none; }', ' .mosdns-stat-card, .mosdns-rank-panel, .mosdns-modal-meta-item, .mosdns-answer-row { background: rgba(255,255,255,0.03); border-color: rgba(255,255,255,0.08); box-shadow: none; }',
' .mosdns-sparkline-tooltip { background: #1e242b; border-color: rgba(255,255,255,0.15); box-shadow: 0 4px 12px rgba(0,0,0,0.5); }', ' .mosdns-sparkline-tooltip { background: #1e242b; border-color: rgba(255,255,255,0.15); box-shadow: 0 4px 12px rgba(0,0,0,0.5); }',
' .mosdns-log-row:hover { background: rgba(255,255,255,0.04); }', ' .mosdns-log-row:hover { background: rgba(255,255,255,0.04); }',
@@ -864,7 +874,7 @@ const renderLogsTable = logsData => {
E('td', { class: 'td col-time', style: 'font-size: 0.82rem; opacity: 0.7; white-space: nowrap;' }, formatTimestamp(item.timestamp)), E('td', { class: 'td col-time', style: 'font-size: 0.82rem; opacity: 0.7; white-space: nowrap;' }, formatTimestamp(item.timestamp)),
E('td', { E('td', {
class: 'td col-client mosdns-mono', class: 'td col-client mosdns-mono',
style: 'font-size: 0.82rem; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;', style: 'font-size: 0.82rem; max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;',
title: clientInfo.title title: clientInfo.title
}, clientInfo.display), }, clientInfo.display),
E('td', { class: 'td col-domain', style: 'max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;', title: item.domain || '-' }, [ E('td', { class: 'td col-domain', style: 'max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;', title: item.domain || '-' }, [
@@ -887,47 +897,99 @@ const renderLogsTable = logsData => {
])); ]));
} }
return E('div', {}, [ const inputWidth = Math.max(46, (String(totalPages).length * 8 + 18)) + 'px';
E('table', { class: 'table cbi-section-table mosdns-table', style: 'margin-top: 0.25rem;' }, [ const pageInput = E('input', {
E('tr', { class: 'tr table-titles' }, [ type: 'number',
E('th', { class: 'th col-time', style: 'width: 85px;' }, _('Time')), class: 'cbi-input-text mosdns-page-input',
E('th', { class: 'th col-client', style: 'width: 125px;' }, _('Client IP')), min: 1,
E('th', { class: 'th col-domain' }, _('Domain & Record')), max: totalPages,
E('th', { class: 'th col-status', style: 'width: 90px;' }, _('Status')), value: pageIdx + 1,
E('th', { class: 'th col-answers' }, _('Answers')), style: `width: ${inputWidth};`,
E('th', { class: 'th col-latency', style: 'width: 80px; text-align: right;' }, _('Elapsed')) title: _('Enter page number and press Enter to jump')
]), });
...rows
]),
E('div', { style: 'display: flex; justify-content: space-between; align-items: center; margin-top: 0.75rem;' }, [ const doJump = () => {
E('span', { style: 'font-size: 0.85rem; opacity: 0.7;' }, _('Page %d / %d (%d entries)').format(pageIdx + 1, totalPages, total)), const val = parseInt(pageInput.value, 10);
E('div', { style: 'display: flex; gap: 0.5rem;' }, [ if (isNaN(val)) {
E('button', { pageInput.value = pageIdx + 1;
class: 'btn cbi-button cbi-button-action', return;
disabled: pageIdx === 0 ? 'disabled' : null, }
click: () => { const targetPage = Math.max(1, Math.min(totalPages, val));
if (pageIdx > 0) { pageInput.value = targetPage;
pageIdx--; if (targetPage - 1 !== pageIdx) {
updateLiveStatusBadge(); pageIdx = targetPage - 1;
refreshLogs(); updateLiveStatusBadge();
refreshLogs();
}
};
pageInput.addEventListener('keydown', ev => {
if (ev.key === 'Enter') {
ev.preventDefault();
pageInput.blur();
}
});
pageInput.addEventListener('focus', () => {
pageInput.select();
});
pageInput.addEventListener('blur', () => {
doJump();
});
const pageParts = _('Page %d / %d (%d entries)').format(999999, totalPages, total).split('999999');
const paginationLabel = E('div', {
class: 'mosdns-pagination-info'
}, [
pageParts[0] || '',
pageInput,
pageParts[1] || ''
]);
return E('div', {}, [
E('table', { class: 'table cbi-section-table mosdns-table', style: 'margin-top: 0.25rem; margin-bottom: 0;' }, [
E('tr', { class: 'tr table-titles' }, [
E('th', { class: 'th col-time', style: 'width: 85px;' }, _('Time')),
E('th', { class: 'th col-client', style: 'width: 125px;' }, _('Client IP')),
E('th', { class: 'th col-domain' }, _('Domain & Record')),
E('th', { class: 'th col-status', style: 'width: 90px;' }, _('Status')),
E('th', { class: 'th col-answers' }, _('Answers')),
E('th', { class: 'th col-latency', style: 'width: 95px; text-align: right;' }, _('Elapsed'))
]),
...rows
]),
E('div', {
class: 'mosdns-pagination-bar'
}, [
paginationLabel,
E('div', { class: 'mosdns-pagination-btns' }, [
E('button', {
class: 'btn cbi-button cbi-button-action',
disabled: pageIdx === 0 ? 'disabled' : null,
click: () => {
if (pageIdx > 0) {
pageIdx--;
updateLiveStatusBadge();
refreshLogs();
}
} }
} }, _('Previous')),
}, _('Previous')), E('button', {
E('button', { class: 'btn cbi-button cbi-button-action',
class: 'btn cbi-button cbi-button-action', disabled: (pageIdx + 1) >= totalPages ? 'disabled' : null,
disabled: (pageIdx + 1) >= totalPages ? 'disabled' : null, click: () => {
click: () => { if ((pageIdx + 1) < totalPages) {
if ((pageIdx + 1) < totalPages) { pageIdx++;
pageIdx++; updateLiveStatusBadge();
updateLiveStatusBadge(); refreshLogs();
refreshLogs(); }
} }
} }, _('Next'))
}, _('Next')) ])
]) ])
]) ]);
]);
}; };
const pollScheduler = async () => { const pollScheduler = async () => {
+77 -73
View File
@@ -17,19 +17,19 @@ msgstr ""
msgid "API Options" msgid "API Options"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:718 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:728
msgid "Add domain to the blocklist to block DNS resolution:" msgid "Add domain to the blocklist to block DNS resolution:"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:717 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:727
msgid "Add domain to the whitelist to permit DNS resolution:" msgid "Add domain to the whitelist to permit DNS resolution:"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:710 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:720
msgid "Add to Blocklist" msgid "Add to Blocklist"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:710 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:720
msgid "Add to Whitelist" msgid "Add to Whitelist"
msgstr "" msgstr ""
@@ -90,7 +90,7 @@ msgstr ""
msgid "Aliyun Public DNS (DNS over QUIC)" msgid "Aliyun Public DNS (DNS over QUIC)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1026 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1088
msgid "All Queries" msgid "All Queries"
msgstr "" msgstr ""
@@ -99,8 +99,8 @@ msgstr ""
msgid "Another update is already in progress." msgid "Another update is already in progress."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:805 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:815
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:897 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:957
msgid "Answers" msgid "Answers"
msgstr "" msgstr ""
@@ -108,7 +108,7 @@ msgstr ""
msgid "Apple domains optimization" msgid "Apple domains optimization"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1053 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1115
msgid "" msgid ""
"Are you sure you want to clear all real-time query logs and top rankings?" "Are you sure you want to clear all real-time query logs and top rankings?"
msgstr "" msgstr ""
@@ -127,11 +127,11 @@ msgid ""
"rules through scheduled tasks." "rules through scheduled tasks."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:478 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:488
msgid "Average Processing Time" msgid "Average Processing Time"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:538 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:548
msgid "Avg Processing" msgid "Avg Processing"
msgstr "" msgstr ""
@@ -155,8 +155,8 @@ msgid ""
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:31 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:31
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:633 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:643
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:635 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:645
msgid "Block Lists" msgid "Block Lists"
msgstr "" msgstr ""
@@ -164,11 +164,11 @@ msgstr ""
msgid "Block PTR" msgid "Block PTR"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1027 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1089
msgid "Blocked Only" msgid "Blocked Only"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:456 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:466
msgid "Blocked by Filters" msgid "Blocked by Filters"
msgstr "" msgstr ""
@@ -190,16 +190,16 @@ msgstr ""
msgid "Cache Prefetching" msgid "Cache Prefetching"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1028 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1090
msgid "Cached Only" msgid "Cached Only"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:467 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:477
msgid "Cached Queries" msgid "Cached Queries"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:730 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:740
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1058 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1120
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@@ -229,7 +229,7 @@ msgstr ""
msgid "Cisco Public DNS (208.67.222.222)" msgid "Cisco Public DNS (208.67.222.222)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1078 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1140
msgid "Clear" msgid "Clear"
msgstr "" msgstr ""
@@ -237,22 +237,22 @@ msgstr ""
msgid "Clear logs" msgid "Clear logs"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1049 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1111
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1052 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1114
msgid "Clear query logs" msgid "Clear query logs"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:861 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:871
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:878 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:888
msgid "Click to view full details" msgid "Click to view full details"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:787 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:797
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:894 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:954
msgid "Client IP" msgid "Client IP"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:832 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:842
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35
msgid "Close" msgid "Close"
msgstr "" msgstr ""
@@ -292,7 +292,7 @@ msgstr ""
msgid "Configuration Editor" msgid "Configuration Editor"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:734 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:744
msgid "Confirm" msgid "Confirm"
msgstr "" msgstr ""
@@ -330,7 +330,7 @@ msgstr ""
msgid "DNS Forward" msgid "DNS Forward"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:444 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:454
msgid "DNS Queries Total" msgid "DNS Queries Total"
msgstr "" msgstr ""
@@ -374,20 +374,20 @@ msgstr ""
msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)" msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:895 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:955
msgid "Domain & Record" msgid "Domain & Record"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:627 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:637
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:655 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:665
msgid "Domain name cannot be empty." msgid "Domain name cannot be empty."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:720 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:730
msgid "Domain:" msgid "Domain:"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:898 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:958
msgid "Elapsed" msgid "Elapsed"
msgstr "" msgstr ""
@@ -425,6 +425,10 @@ msgstr ""
msgid "Enabled" msgid "Enabled"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:908
msgid "Enter page number and press Enter to jump"
msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:505 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:505
msgid "Enter the GeoIP.dat category to be exported, Allow add multiple tags" msgid "Enter the GeoIP.dat category to be exported, Allow add multiple tags"
msgstr "" msgstr ""
@@ -478,7 +482,7 @@ msgstr ""
msgid "Failed to clean logs." msgid "Failed to clean logs."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1072 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1134
msgid "Failed to clear query logs:" msgid "Failed to clear query logs:"
msgstr "" msgstr ""
@@ -486,15 +490,15 @@ msgstr ""
msgid "Failed to start update." msgid "Failed to start update."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:970 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1032
msgid "Failed to update query logs:" msgid "Failed to update query logs:"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:699 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:709
msgid "Failed to update rule file: %s" msgid "Failed to update rule file: %s"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1044 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1106
msgid "First Page / Resume" msgid "First Page / Resume"
msgstr "" msgstr ""
@@ -595,10 +599,10 @@ msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:410 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:410
msgid "" msgid ""
"IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" " "IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" target=\"_blank"
"target=\"_blank\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a " "\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a href=\"https://"
"href=\"https://www.cloudflare.com/ips-v6\" target=\"_blank\">https://www." "www.cloudflare.com/ips-v6\" target=\"_blank\">https://www.cloudflare.com/ips-"
"cloudflare.com/ips-v6</a>" "v6</a>"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:190 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:190
@@ -621,7 +625,7 @@ msgstr ""
msgid "Info" msgid "Info"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:828 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:838
msgid "Intercept" msgid "Intercept"
msgstr "" msgstr ""
@@ -631,7 +635,7 @@ msgid ""
"seconds)." "seconds)."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:479 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:489
msgid "Latency" msgid "Latency"
msgstr "" msgstr ""
@@ -716,12 +720,12 @@ msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:33 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:33
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:106 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:106
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:70 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:70
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1094 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1156
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3 #: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3
msgid "MosDNS" msgid "MosDNS"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:510 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:520
msgid "" msgid ""
"MosDNS API is unreachable. Please ensure MosDNS is running and stats_api " "MosDNS API is unreachable. Please ensure MosDNS is running and stats_api "
"plugin is enabled." "plugin is enabled."
@@ -739,15 +743,15 @@ msgstr ""
msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS" msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:927 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:989
msgid "Next" msgid "Next"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:768 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:778
msgid "No DNS answer records returned." msgid "No DNS answer records returned."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:560 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:570
msgid "No data available" msgid "No data available"
msgstr "" msgstr ""
@@ -755,19 +759,19 @@ msgstr ""
msgid "No log data." msgid "No log data."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:886 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:896
msgid "No query log entries found." msgid "No query log entries found."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:904 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:941
msgid "Page %d / %d (%d entries)" msgid "Page %d / %d (%d entries)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:438 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:448
msgid "Per-query speed" msgid "Per-query speed"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:821 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:831
msgid "Permit" msgid "Permit"
msgstr "" msgstr ""
@@ -802,7 +806,7 @@ msgstr ""
msgid "Prevent DNS Leaks" msgid "Prevent DNS Leaks"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:916 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:978
msgid "Previous" msgid "Previous"
msgstr "" msgstr ""
@@ -821,7 +825,7 @@ msgstr ""
msgid "Quad9 Public DNS (9.9.9.9)" msgid "Quad9 Public DNS (9.9.9.9)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:811 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:821
msgid "Query Log Details" msgid "Query Log Details"
msgstr "" msgstr ""
@@ -831,7 +835,7 @@ msgid ""
"consume more memory)" "consume more memory)"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1067 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1129
msgid "Query logs cleared successfully." msgid "Query logs cleared successfully."
msgstr "" msgstr ""
@@ -839,7 +843,7 @@ msgstr ""
msgid "RUNNING" msgid "RUNNING"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1098 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1160
msgid "Real-time Query Logs" msgid "Real-time Query Logs"
msgstr "" msgstr ""
@@ -869,7 +873,7 @@ msgstr ""
msgid "Ring Buffer Capacity" msgid "Ring Buffer Capacity"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:799 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:809
msgid "Rule Hit" msgid "Rule Hit"
msgstr "" msgstr ""
@@ -889,7 +893,7 @@ msgstr ""
msgid "Save the cache locally and reload the cache dump on the next startup" msgid "Save the cache locally and reload the cache dump on the next startup"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1015 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1077
msgid "Search domain or client IP..." msgid "Search domain or client IP..."
msgstr "" msgstr ""
@@ -901,7 +905,7 @@ msgstr ""
msgid "Starting update..." msgid "Starting update..."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1094 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1156
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:38 #: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:38
msgid "Statistics" msgid "Statistics"
msgstr "" msgstr ""
@@ -910,7 +914,7 @@ msgstr ""
msgid "Stats Dump" msgid "Stats Dump"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:896 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:956
msgid "Status" msgid "Status"
msgstr "" msgstr ""
@@ -930,7 +934,7 @@ msgstr ""
msgid "TCP/DoT Connection Multiplexing" msgid "TCP/DoT Connection Multiplexing"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:723 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:733
msgid "Target file: %s (supports domain matching rules)" msgid "Target file: %s (supports domain matching rules)"
msgstr "" msgstr ""
@@ -966,20 +970,20 @@ msgid ""
"content in yaml format." "content in yaml format."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:791 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:801
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:893 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:953
msgid "Time" msgid "Time"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:599 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:609
msgid "Top Blocked Domains" msgid "Top Blocked Domains"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:606 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:616
msgid "Top Clients" msgid "Top Clients"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:592 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:602
msgid "Top Queried Domains" msgid "Top Queried Domains"
msgstr "" msgstr ""
@@ -1029,7 +1033,7 @@ msgstr ""
msgid "Updating Database..." msgid "Updating Database..."
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:795 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:805
msgid "Upstream" msgid "Upstream"
msgstr "" msgstr ""
@@ -1051,8 +1055,8 @@ msgid ""
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:633 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:643
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:635 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:645
msgid "White Lists" msgid "White Lists"
msgstr "" msgstr ""
@@ -1070,15 +1074,15 @@ msgstr ""
msgid "https://gh-proxy.com" msgid "https://gh-proxy.com"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:427 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:437
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:540 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:550
msgid "● Live" msgid "● Live"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:223 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:233
msgid "● Live Auto-refresh" msgid "● Live Auto-refresh"
msgstr "" msgstr ""
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:227 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:237
msgid "❚❚ Paused (Page %d)" msgid "❚❚ Paused (Page %d)"
msgstr "" msgstr ""
+81 -86
View File
@@ -27,19 +27,19 @@ msgstr "API 监听端口"
msgid "API Options" msgid "API Options"
msgstr "API 选项" msgstr "API 选项"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:718 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:728
msgid "Add domain to the blocklist to block DNS resolution:" msgid "Add domain to the blocklist to block DNS resolution:"
msgstr "将域名添加到黑名单以拦截 DNS 解析:" msgstr "将域名添加到黑名单以拦截 DNS 解析:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:717 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:727
msgid "Add domain to the whitelist to permit DNS resolution:" msgid "Add domain to the whitelist to permit DNS resolution:"
msgstr "将域名添加到白名单以放行 DNS 解析:" msgstr "将域名添加到白名单以放行 DNS 解析:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:710 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:720
msgid "Add to Blocklist" msgid "Add to Blocklist"
msgstr "添加到黑名单" msgstr "添加到黑名单"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:710 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:720
msgid "Add to Whitelist" msgid "Add to Whitelist"
msgstr "添加到白名单" msgstr "添加到白名单"
@@ -105,7 +105,7 @@ msgstr "阿里云公共 DNSDNS over HTTPS"
msgid "Aliyun Public DNS (DNS over QUIC)" msgid "Aliyun Public DNS (DNS over QUIC)"
msgstr "阿里云公共 DNSDNS over QUIC" msgstr "阿里云公共 DNSDNS over QUIC"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1026 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1088
msgid "All Queries" msgid "All Queries"
msgstr "所有查询" msgstr "所有查询"
@@ -114,8 +114,8 @@ msgstr "所有查询"
msgid "Another update is already in progress." msgid "Another update is already in progress."
msgstr "另一个更新正在进行中。" msgstr "另一个更新正在进行中。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:805 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:815
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:897 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:957
msgid "Answers" msgid "Answers"
msgstr "应答结果" msgstr "应答结果"
@@ -123,7 +123,7 @@ msgstr "应答结果"
msgid "Apple domains optimization" msgid "Apple domains optimization"
msgstr "Apple 域名解析优化" msgstr "Apple 域名解析优化"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1053 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1115
msgid "" msgid ""
"Are you sure you want to clear all real-time query logs and top rankings?" "Are you sure you want to clear all real-time query logs and top rankings?"
msgstr "确定要清空所有实时查询日志和排行数据吗?" msgstr "确定要清空所有实时查询日志和排行数据吗?"
@@ -142,11 +142,11 @@ msgid ""
"rules through scheduled tasks." "rules through scheduled tasks."
msgstr "通过定时任务自动更新 GeoIP 和 GeoSite 数据库以及广告过滤规则。" msgstr "通过定时任务自动更新 GeoIP 和 GeoSite 数据库以及广告过滤规则。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:478 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:488
msgid "Average Processing Time" msgid "Average Processing Time"
msgstr "平均延迟" msgstr "平均延迟"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:538 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:548
msgid "Avg Processing" msgid "Avg Processing"
msgstr "平均处理" msgstr "平均处理"
@@ -172,8 +172,8 @@ msgstr ""
"解析。" "解析。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:31 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:31
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:633 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:643
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:635 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:645
msgid "Block Lists" msgid "Block Lists"
msgstr "黑名单" msgstr "黑名单"
@@ -181,11 +181,11 @@ msgstr "黑名单"
msgid "Block PTR" msgid "Block PTR"
msgstr "PTR 黑名单" msgstr "PTR 黑名单"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1027 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1089
msgid "Blocked Only" msgid "Blocked Only"
msgstr "仅拦截" msgstr "仅拦截"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:456 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:466
msgid "Blocked by Filters" msgid "Blocked by Filters"
msgstr "拦截总数" msgstr "拦截总数"
@@ -207,16 +207,16 @@ msgstr "自动保存缓存"
msgid "Cache Prefetching" msgid "Cache Prefetching"
msgstr "缓存预取" msgstr "缓存预取"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1028 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1090
msgid "Cached Only" msgid "Cached Only"
msgstr "仅缓存" msgstr "仅缓存"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:467 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:477
msgid "Cached Queries" msgid "Cached Queries"
msgstr "缓存命中" msgstr "缓存命中"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:730 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:740
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1058 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1120
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
@@ -246,7 +246,7 @@ msgstr "思科公共 DNS208.67.220.220"
msgid "Cisco Public DNS (208.67.222.222)" msgid "Cisco Public DNS (208.67.222.222)"
msgstr "思科公共 DNS208.67.222.222" msgstr "思科公共 DNS208.67.222.222"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1078 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1140
msgid "Clear" msgid "Clear"
msgstr "清空" msgstr "清空"
@@ -254,22 +254,22 @@ msgstr "清空"
msgid "Clear logs" msgid "Clear logs"
msgstr "清空日志" msgstr "清空日志"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1049 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1111
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1052 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1114
msgid "Clear query logs" msgid "Clear query logs"
msgstr "清除查询日志" msgstr "清除查询日志"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:861 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:871
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:878 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:888
msgid "Click to view full details" msgid "Click to view full details"
msgstr "点击查看完整详情" msgstr "点击查看完整详情"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:787 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:797
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:894 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:954
msgid "Client IP" msgid "Client IP"
msgstr "客户端 IP" msgstr "客户端 IP"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:832 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:842
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:35
msgid "Close" msgid "Close"
msgstr "关闭" msgstr "关闭"
@@ -309,7 +309,7 @@ msgstr "配置文件"
msgid "Configuration Editor" msgid "Configuration Editor"
msgstr "配置编辑器" msgstr "配置编辑器"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:734 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:744
msgid "Confirm" msgid "Confirm"
msgstr "确认" msgstr "确认"
@@ -348,7 +348,7 @@ msgstr "DNS 缓存大小"
msgid "DNS Forward" msgid "DNS Forward"
msgstr "DNS 转发" msgstr "DNS 转发"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:444 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:454
msgid "DNS Queries Total" msgid "DNS Queries Total"
msgstr "DNS 查询总数" msgstr "DNS 查询总数"
@@ -393,20 +393,20 @@ msgstr ""
msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)" msgid "DoH/TCP/DoT Connection Multiplexing idle timeout (default 30 seconds)"
msgstr "DoH/TCP/DoT 连接复用空闲保持时间(默认 30 秒)" msgstr "DoH/TCP/DoT 连接复用空闲保持时间(默认 30 秒)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:895 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:955
msgid "Domain & Record" msgid "Domain & Record"
msgstr "域名与类型" msgstr "域名与类型"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:627 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:637
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:655 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:665
msgid "Domain name cannot be empty." msgid "Domain name cannot be empty."
msgstr "域名不能为空" msgstr "域名不能为空"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:720 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:730
msgid "Domain:" msgid "Domain:"
msgstr "域名:" msgstr "域名:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:898 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:958
msgid "Elapsed" msgid "Elapsed"
msgstr "耗时" msgstr "耗时"
@@ -444,6 +444,10 @@ msgstr "启用此选项 fallback 策略会强制转发到远程 DNS"
msgid "Enabled" msgid "Enabled"
msgstr "启用" msgstr "启用"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:908
msgid "Enter page number and press Enter to jump"
msgstr "输入页码并按回车跳转"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:505 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:505
msgid "Enter the GeoIP.dat category to be exported, Allow add multiple tags" msgid "Enter the GeoIP.dat category to be exported, Allow add multiple tags"
msgstr "输入需要导出的 GeoIP.dat 类别条目,允许添加多个标签" msgstr "输入需要导出的 GeoIP.dat 类别条目,允许添加多个标签"
@@ -497,7 +501,7 @@ msgstr "导出目录:/var/mosdns"
msgid "Failed to clean logs." msgid "Failed to clean logs."
msgstr "清理日志失败:%s" msgstr "清理日志失败:%s"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1072 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1134
msgid "Failed to clear query logs:" msgid "Failed to clear query logs:"
msgstr "清除查询日志失败:" msgstr "清除查询日志失败:"
@@ -505,15 +509,15 @@ msgstr "清除查询日志失败:"
msgid "Failed to start update." msgid "Failed to start update."
msgstr "启动更新失败" msgstr "启动更新失败"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:970 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1032
msgid "Failed to update query logs:" msgid "Failed to update query logs:"
msgstr "更新查询日志失败:" msgstr "更新查询日志失败:"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:699 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:709
msgid "Failed to update rule file: %s" msgid "Failed to update rule file: %s"
msgstr "更新规则文件失败: %s" msgstr "更新规则文件失败: %s"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1044 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1106
msgid "First Page / Resume" msgid "First Page / Resume"
msgstr "重置并恢复" msgstr "重置并恢复"
@@ -614,15 +618,15 @@ msgstr "IP 地址"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:410 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:410
msgid "" msgid ""
"IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" " "IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" target=\"_blank"
"target=\"_blank\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a " "\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a href=\"https://"
"href=\"https://www.cloudflare.com/ips-v6\" target=\"_blank\">https://www." "www.cloudflare.com/ips-v6\" target=\"_blank\">https://www.cloudflare.com/ips-"
"cloudflare.com/ips-v6</a>" "v6</a>"
msgstr "" msgstr ""
"IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" " "IPv4 CIDR: <a href=\"https://www.cloudflare.com/ips-v4\" target=\"_blank"
"target=\"_blank\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a " "\">https://www.cloudflare.com/ips-v4</a> <br /> IPv6 CIDR: <a href=\"https://"
"href=\"https://www.cloudflare.com/ips-v6\" target=\"_blank\">https://www." "www.cloudflare.com/ips-v6\" target=\"_blank\">https://www.cloudflare.com/ips-"
"cloudflare.com/ips-v6</a>" "v6</a>"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:190 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:190
msgid "" msgid ""
@@ -644,7 +648,7 @@ msgstr "空闲超时"
msgid "Info" msgid "Info"
msgstr "信息" msgstr "信息"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:828 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:838
msgid "Intercept" msgid "Intercept"
msgstr "拦截" msgstr "拦截"
@@ -654,7 +658,7 @@ msgid ""
"seconds)." "seconds)."
msgstr "后台线程扫描缓存以进行预取的时间间隔(单位:秒)" msgstr "后台线程扫描缓存以进行预取的时间间隔(单位:秒)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:479 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:489
msgid "Latency" msgid "Latency"
msgstr "延迟" msgstr "延迟"
@@ -742,12 +746,12 @@ msgstr "修改 DNS 应答结果的最小 TTL 值 (秒),0 表示不修改"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:33 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:33
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:106 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:106
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:70 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/logs.js:70
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1094 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1156
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3 #: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:3
msgid "MosDNS" msgid "MosDNS"
msgstr "MosDNS" msgstr "MosDNS"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:510 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:520
msgid "" msgid ""
"MosDNS API is unreachable. Please ensure MosDNS is running and stats_api " "MosDNS API is unreachable. Please ensure MosDNS is running and stats_api "
"plugin is enabled." "plugin is enabled."
@@ -767,15 +771,15 @@ msgstr "未运行"
msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS" msgid "Netflix, Disney+, Hulu and streaming media rules list will use this DNS"
msgstr "自定义 Netflix、Disney+、Hulu 以及 “流媒体” 规则列表的 DNS 服务器" msgstr "自定义 Netflix、Disney+、Hulu 以及 “流媒体” 规则列表的 DNS 服务器"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:927 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:989
msgid "Next" msgid "Next"
msgstr "下一页" msgstr "下一页"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:768 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:778
msgid "No DNS answer records returned." msgid "No DNS answer records returned."
msgstr "无 DNS 应答记录" msgstr "无 DNS 应答记录"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:560 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:570
msgid "No data available" msgid "No data available"
msgstr "暂无数据" msgstr "暂无数据"
@@ -783,19 +787,19 @@ msgstr "暂无数据"
msgid "No log data." msgid "No log data."
msgstr "无日志数据。" msgstr "无日志数据。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:886 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:896
msgid "No query log entries found." msgid "No query log entries found."
msgstr "未找到查询日志记录。" msgstr "未找到查询日志记录。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:904 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:941
msgid "Page %d / %d (%d entries)" msgid "Page %d / %d (%d entries)"
msgstr "第 %d / %d 页(%d 条记录)" msgstr "第 %d / %d 页(%d 条记录)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:438 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:448
msgid "Per-query speed" msgid "Per-query speed"
msgstr "单次查询速度" msgstr "单次查询速度"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:821 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:831
msgid "Permit" msgid "Permit"
msgstr "放行" msgstr "放行"
@@ -832,7 +836,7 @@ msgstr "当剩余 TTL 小于此值(秒)时触发预取"
msgid "Prevent DNS Leaks" msgid "Prevent DNS Leaks"
msgstr "防止 DNS 泄漏" msgstr "防止 DNS 泄漏"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:916 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:978
msgid "Previous" msgid "Previous"
msgstr "上一页" msgstr "上一页"
@@ -851,7 +855,7 @@ msgstr "Quad9 公共 DNS149.112.112.112"
msgid "Quad9 Public DNS (9.9.9.9)" msgid "Quad9 Public DNS (9.9.9.9)"
msgstr "Quad9 公共 DNS9.9.9.9" msgstr "Quad9 公共 DNS9.9.9.9"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:811 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:821
msgid "Query Log Details" msgid "Query Log Details"
msgstr "查询日志详情" msgstr "查询日志详情"
@@ -861,7 +865,7 @@ msgid ""
"consume more memory)" "consume more memory)"
msgstr "查询日志环形缓冲区容量(FIFO 覆盖,默认 2000,数值越大占用内存越多)" msgstr "查询日志环形缓冲区容量(FIFO 覆盖,默认 2000,数值越大占用内存越多)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1067 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1129
msgid "Query logs cleared successfully." msgid "Query logs cleared successfully."
msgstr "查询日志清除成功。" msgstr "查询日志清除成功。"
@@ -869,7 +873,7 @@ msgstr "查询日志清除成功。"
msgid "RUNNING" msgid "RUNNING"
msgstr "运行中" msgstr "运行中"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1098 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1160
msgid "Real-time Query Logs" msgid "Real-time Query Logs"
msgstr "实时查询日志" msgstr "实时查询日志"
@@ -901,7 +905,7 @@ msgstr "远程 DNS 服务器"
msgid "Ring Buffer Capacity" msgid "Ring Buffer Capacity"
msgstr "环形缓冲区容量" msgstr "环形缓冲区容量"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:799 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:809
msgid "Rule Hit" msgid "Rule Hit"
msgstr "命中规则" msgstr "命中规则"
@@ -921,7 +925,7 @@ msgstr "保存统计数据与查询日志在本地,并在下次启动时重新
msgid "Save the cache locally and reload the cache dump on the next startup" msgid "Save the cache locally and reload the cache dump on the next startup"
msgstr "保存缓存到本地文件,以供下次启动时重新载入使用" msgstr "保存缓存到本地文件,以供下次启动时重新载入使用"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1015 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1077
msgid "Search domain or client IP..." msgid "Search domain or client IP..."
msgstr "搜索域名或客户端 IP..." msgstr "搜索域名或客户端 IP..."
@@ -933,7 +937,7 @@ msgstr "设置日志文件的最大容量(单位:MB)。"
msgid "Starting update..." msgid "Starting update..."
msgstr "正在启动更新..." msgstr "正在启动更新..."
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1094 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:1156
#: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:38 #: luci-app-mosdns/root/usr/share/luci/menu.d/luci-app-mosdns.json:38
msgid "Statistics" msgid "Statistics"
msgstr "统计" msgstr "统计"
@@ -942,7 +946,7 @@ msgstr "统计"
msgid "Stats Dump" msgid "Stats Dump"
msgstr "自动保存统计" msgstr "自动保存统计"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:896 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:956
msgid "Status" msgid "Status"
msgstr "状态" msgstr "状态"
@@ -962,7 +966,7 @@ msgstr "支持本地文件,例如:file:///var/mosdns/example.txt"
msgid "TCP/DoT Connection Multiplexing" msgid "TCP/DoT Connection Multiplexing"
msgstr "TCP/DoT 连接复用" msgstr "TCP/DoT 连接复用"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:723 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:733
msgid "Target file: %s (supports domain matching rules)" msgid "Target file: %s (supports domain matching rules)"
msgstr "目标文件: %s (支持域名匹配规则)" msgstr "目标文件: %s (支持域名匹配规则)"
@@ -1002,20 +1006,20 @@ msgstr ""
"这是文件 “/etc/mosdns/config_custom.yaml” 的内容,您的 MosDNS 配置将从此文件" "这是文件 “/etc/mosdns/config_custom.yaml” 的内容,您的 MosDNS 配置将从此文件"
"生成。仅接受 yaml 格式的配置内容。" "生成。仅接受 yaml 格式的配置内容。"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:791 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:801
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:893 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:953
msgid "Time" msgid "Time"
msgstr "时间" msgstr "时间"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:599 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:609
msgid "Top Blocked Domains" msgid "Top Blocked Domains"
msgstr "拦截域名 (Top 10)" msgstr "拦截域名 (Top 10)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:606 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:616
msgid "Top Clients" msgid "Top Clients"
msgstr "活跃客户端 (Top 10)" msgstr "活跃客户端 (Top 10)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:592 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:602
msgid "Top Queried Domains" msgid "Top Queried Domains"
msgstr "查询域名 (Top 10)" msgstr "查询域名 (Top 10)"
@@ -1065,7 +1069,7 @@ msgstr "更新成功"
msgid "Updating Database..." msgid "Updating Database..."
msgstr "更新数据库..." msgstr "更新数据库..."
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:795 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:805
msgid "Upstream" msgid "Upstream"
msgstr "上游服务器" msgstr "上游服务器"
@@ -1091,8 +1095,8 @@ msgstr ""
"则)" "则)"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/rules.js:23
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:633 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:643
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:635 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:645
msgid "White Lists" msgid "White Lists"
msgstr "白名单" msgstr "白名单"
@@ -1110,24 +1114,15 @@ msgstr "信风公共 DNS114.114.115.115"
msgid "https://gh-proxy.com" msgid "https://gh-proxy.com"
msgstr "https://gh-proxy.com" msgstr "https://gh-proxy.com"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:427 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:437
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:540 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:550
msgid "● Live" msgid "● Live"
msgstr "● 实时" msgstr "● 实时"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:223 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:233
msgid "● Live Auto-refresh" msgid "● Live Auto-refresh"
msgstr "● 实时刷新" msgstr "● 实时刷新"
#: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:227 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/statistics.js:237
msgid "❚❚ Paused (Page %d)" msgid "❚❚ Paused (Page %d)"
msgstr "❚❚ 暂停 (第 %d 页)" msgstr "❚❚ 暂停 (第 %d 页)"
#~ msgid "Action"
#~ msgstr "操作"
#~ msgid "Add %s to Blocklist"
#~ msgstr "将 %s 添加到黑名单"
#~ msgid "Add %s to Whitelist"
#~ msgstr "将 %s 添加到白名单"
@@ -1,2 +1,3 @@
#!/bin/sh #!/bin/sh
[ "$ACTION" = ifup ] && /etc/init.d/mosdns restart [ "$ACTION" = "ifup" ] || exit 0
[ "$INTERFACE" = "wan" ] && /etc/init.d/mosdns reload
+21 -1
View File
@@ -429,7 +429,11 @@ generate_config() {
json_add_string "exec" "\$modify_ddns_ttl" json_add_string "exec" "\$modify_ddns_ttl"
json_close_object json_close_object
json_add_object json_add_object
json_add_string "matches" "!qname \$ddnslist" json_add_array "matches"
json_add_string "" "!qname \$ddnslist"
json_add_string "" "!qname \$blocklist"
json_add_string "" "!qname \$adlist"
json_close_array
json_add_string "exec" "\$modify_ttl" json_add_string "exec" "\$modify_ttl"
json_close_object json_close_object
[ "$cloudflare" -eq 1 ] && { [ "$cloudflare" -eq 1 ] && {
@@ -571,6 +575,22 @@ generate_config() {
json_add_array "args" json_add_array "args"
json_add_object json_add_object
json_add_string "matches" "qname \$blocklist" json_add_string "matches" "qname \$blocklist"
json_add_string "exec" "black_hole 0.0.0.0 ::"
json_close_object
json_add_object
json_add_string "matches" "qname \$adlist"
json_add_string "exec" "black_hole 0.0.0.0 ::"
json_close_object
json_add_object
json_add_string "matches" "has_resp"
json_add_string "exec" "ttl 3600"
json_close_object
json_add_object
json_add_string "matches" "has_resp"
json_add_string "exec" "accept"
json_close_object
json_add_object
json_add_string "matches" "qname \$blocklist"
json_add_string "exec" "reject 3" json_add_string "exec" "reject 3"
json_close_object json_close_object
json_add_object json_add_object
@@ -72,7 +72,7 @@ function get_adlist() {
let adblock = uci_cursor.get('mosdns', 'config', 'adblock'); let adblock = uci_cursor.get('mosdns', 'config', 'adblock');
if (adblock !== '1') { if (adblock !== '1') {
mkdir('/etc/mosdns/rule', 0755); mkdir('/var/mosdns', 0755);
exec_sys('rm -rf /etc/mosdns/rule/adlist /etc/mosdns/rule/.ad_source'); exec_sys('rm -rf /etc/mosdns/rule/adlist /etc/mosdns/rule/.ad_source');
writefile('/var/mosdns/disable-ads.txt', ''); writefile('/var/mosdns/disable-ads.txt', '');
print("/var/mosdns/disable-ads.txt\n"); print("/var/mosdns/disable-ads.txt\n");
+9 -1
View File
@@ -7,7 +7,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.9.1 PKG_VERSION:=26.9.9
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)
@@ -164,9 +164,17 @@ define Package/$(PKG_NAME)/conffiles
/usr/share/passwall/rules/domains_excluded /usr/share/passwall/rules/domains_excluded
endef endef
define Package/$(PKG_NAME)/postinst
#!/bin/sh
rm -f $${IPKG_INSTROOT}/usr/share/passwall/rules/*.nft
rm -rf $${IPKG_INSTROOT}/tmp/etc/passwall_tmp
exit 0
endef
define Package/$(PKG_NAME)/postrm define Package/$(PKG_NAME)/postrm
#!/bin/sh #!/bin/sh
rm -f $${IPKG_INSTROOT}/usr/share/passwall/rules/*.nft rm -f $${IPKG_INSTROOT}/usr/share/passwall/rules/*.nft
rm -rf $${IPKG_INSTROOT}/tmp/etc/passwall_tmp
exit 0 exit 0
endef endef
@@ -47,6 +47,11 @@ function waitForElement(selector, callback) {
observer.observe(document.body, { childList: true, subtree: true }); observer.observe(document.body, { childList: true, subtree: true });
} }
function waitForElementId(id, callback) {
waitForElement("#" + CSS.escape(id), callback);
//waitForElement('[id="' + id + '"]', callback);
}
function get_current_url() { function get_current_url() {
return window.location.origin + window.location.pathname; return window.location.origin + window.location.pathname;
} }
@@ -56,14 +61,11 @@ function getOption(config, section, opt) {
const id = `cbid.${config}.${section}.${opt}`; const id = `cbid.${config}.${section}.${opt}`;
obj = document.getElementsByName(id)[0] || document.getElementById(id); obj = document.getElementsByName(id)[0] || document.getElementById(id);
if (obj) { if (obj) {
//lua luci combobox
const combobox = document.getElementById('cbi.combobox.' + id); const combobox = document.getElementById('cbi.combobox.' + id);
if (combobox) { if (combobox) {
obj.combobox = combobox; obj.combobox = combobox;
} }
const div = document.getElementById(id);
if (div && div.getElementsByTagName("li").length > 0) {
obj = div;
}
return obj; return obj;
} else { } else {
return null; return null;
@@ -479,7 +479,21 @@ function add_node()
uci_set(uid, "group", group) uci_set(uid, "group", group)
end end
uci_set(uid, "type", "Socks") if api.is_finded("ipt2socks") then
uci_set(uid, "type", "Socks")
elseif api.finded_com("sing-box") then
uci_set(uid, "type", "sing-box")
elseif api.finded_com("xray") then
uci_set(uid, "type", "Xray")
elseif api.is_finded("sslocal") then
uci_set(uid, "type", "SS-Rust")
elseif api.is_finded("ssr-local") then
uci_set(uid, "type", "SSR")
elseif api.finded_com("hysteria") then
uci_set(uid, "type", "Hysteria2")
elseif api.is_finded("naive") then
uci_set(uid, "type", "Naiveproxy")
end
if redirect == "1" then if redirect == "1" then
uci_save() uci_save()
@@ -852,9 +866,13 @@ local backup_files = {
function create_backup() function create_backup()
local date = os.date("%y%m%d%H%M") local date = os.date("%y%m%d%H%M")
local tar_file = "/tmp/passwall-" .. date .. "-backup.tar.gz" local tar_file = "/tmp/passwall-" .. date .. "-backup.tar.gz"
local version_file = "/tmp/passwall-version"
local version = api.get_version()
fs.remove(tar_file) fs.remove(tar_file)
local cmd = "tar -czf " .. tar_file .. " " .. table.concat(backup_files, " ") fs.writefile(version_file, version .. "\n")
local cmd = "tar -czf " .. tar_file .. " " .. table.concat(backup_files, " ") .. " " .. "-C /tmp passwall-version"
luci.sys.call(cmd) luci.sys.call(cmd)
fs.remove(version_file)
http.header("Content-Disposition", "attachment; filename=passwall-" .. date .. "-backup.tar.gz") http.header("Content-Disposition", "attachment; filename=passwall-" .. date .. "-backup.tar.gz")
http.header("X-Backup-Filename", "passwall-" .. date .. "-backup.tar.gz") http.header("X-Backup-Filename", "passwall-" .. date .. "-backup.tar.gz")
http.prepare_content("application/octet-stream") http.prepare_content("application/octet-stream")
@@ -895,7 +913,12 @@ function restore_backup()
uci:revert(c_config) uci:revert(c_config)
uci:revert(api.s_config) uci:revert(api.s_config)
luci.sys.call("echo '' > /tmp/log/passwall.log") luci.sys.call("echo '' > /tmp/log/passwall.log")
api.log(" * PassWall 配置文件上传成功…") api.log(" * PassWall 备份文件上传成功…")
local version_file = "passwall-version"
local version = luci.sys.exec("tar -xOf " .. file_path .. " " .. version_file .. " 2>/dev/null"):match("^%s*(.-)%s*$")
if version and version ~= "" then
api.log(" * 备份文件由 PassWall " .. version .. " 生成。")
end
local temp_dir = '/tmp/passwall_bak' local temp_dir = '/tmp/passwall_bak'
luci.sys.call("mkdir -p " .. temp_dir) luci.sys.call("mkdir -p " .. temp_dir)
if luci.sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then if luci.sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then
@@ -909,8 +932,9 @@ function restore_backup()
end end
end end
if type == "all" or type == "client" then if type == "all" or type == "client" then
api.log(" * PassWall 配置还原成功…") api.log(" * PassWall 备份还原成功…")
api.log(" * 重启 PassWall 服务中…\n") api.log(" * 重启 PassWall 服务中…\n")
api.sh_uci_set(c_config, "@global[0]", "flush_set", "1", true)
luci.sys.call('/etc/init.d/passwall restart > /dev/null 2>&1 &') luci.sys.call('/etc/init.d/passwall restart > /dev/null 2>&1 &')
end end
if type == "all" or type == "server" then if type == "all" or type == "server" then
@@ -918,7 +942,7 @@ function restore_backup()
end end
result = { status = "success", message = "Upload completed", path = file_path } result = { status = "success", message = "Upload completed", path = file_path }
else else
api.log(" * PassWall 配置文件解压失败,请重试!") api.log(" * PassWall 备份文件解压失败,请重试!")
result = { status = "error", message = "Decompression failed" } result = { status = "error", message = "Decompression failed" }
end end
luci.sys.call("rm -rf " .. temp_dir) luci.sys.call("rm -rf " .. temp_dir)
@@ -47,20 +47,23 @@ end
-- [[ ACLs Settings ]]-- -- [[ ACLs Settings ]]--
s = m:section(NamedSection, arg[1], translate("ACLs"), translate("ACLs")) s = m:section(NamedSection, arg[1], translate("ACLs"), translate("ACLs"))
s.addremove = false
s.dynamic = false s:tab("Main", translate("Main"))
s:tab("Proxy", translate("Proxy"))
s:tab("DNS", translate("DNS"))
s:tab("Log", translate("Log"))
---- Enable ---- Enable
o = s:option(Flag, "enabled", translate("Enable")) o = s:taboption("Main", Flag, "enabled", translate("Enable"))
o.default = 1 o.default = 1
o.rmempty = false o.rmempty = false
---- Remarks ---- Remarks
o = s:option(Value, "remarks", translate("Remarks")) o = s:taboption("Main", Value, "remarks", translate("Remarks"))
o.default = arg[1] o.default = arg[1]
o.rmempty = false o.rmempty = false
o = s:option(Value, "interface", translate("Source Interface")) o = s:taboption("Main", Value, "interface", translate("Source Interface"))
o:value("", translate("All")) o:value("", translate("All"))
local iface = api.get_network_devices() local iface = api.get_network_devices()
for _, d in ipairs(iface) do for _, d in ipairs(iface) do
@@ -94,7 +97,7 @@ table.sort(mac_t, function(a,b)
end) end)
---- Source ---- Source
sources = s:option(DynamicList, "sources", translate("Source")) sources = s:taboption("Main", DynamicList, "sources", translate("Source"))
sources.description = "<ul><li>" .. translate("Example:") sources.description = "<ul><li>" .. translate("Example:")
.. "</li><li>" .. translate("MAC") .. ": 00:00:00:FF:FF:FF" .. "</li><li>" .. translate("MAC") .. ": 00:00:00:FF:FF:FF"
.. "</li><li>" .. translate("IP") .. ": 192.168.1.100" .. "</li><li>" .. translate("IP") .. ": 192.168.1.100"
@@ -152,13 +155,14 @@ sources.validate = function(self, value, t)
return value return value
end end
o = s:option(ListValue, "mode", translate("Mode")) local NODE = m:get("@global[0]", "node") or ""
o = s:taboption("Main", ListValue, "mode", translate("Mode"))
o:value("0", translate("No Proxy")) o:value("0", translate("No Proxy"))
o:value("1", translate("Proxy")) o:value("1", translate("Proxy"))
---- TCP No Redir Ports ---- TCP No Redir Ports
local TCP_NO_REDIR_PORTS = m:get("@global_forwarding[0]", "tcp_no_redir_ports") local TCP_NO_REDIR_PORTS = m:get("@global_forwarding[0]", "tcp_no_redir_ports")
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports")) o = s:taboption("Proxy", Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
o:value("", translate("Use global config") .. "(" .. TCP_NO_REDIR_PORTS .. ")") o:value("", translate("Use global config") .. "(" .. TCP_NO_REDIR_PORTS .. ")")
o:value("disable", translate("No patterns are used")) o:value("disable", translate("No patterns are used"))
o:value("1:65535", translate("All")) o:value("1:65535", translate("All"))
@@ -167,7 +171,7 @@ o.validate = port_validate
---- UDP No Redir Ports ---- UDP No Redir Ports
local UDP_NO_REDIR_PORTS = m:get("@global_forwarding[0]", "udp_no_redir_ports") local UDP_NO_REDIR_PORTS = m:get("@global_forwarding[0]", "udp_no_redir_ports")
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"), o = s:taboption("Proxy", Value, "udp_no_redir_ports", translate("UDP No Redir Ports"),
"<font color='red'>" .. "<font color='red'>" ..
translate("Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") .. translate("Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") ..
"</font>") "</font>")
@@ -177,48 +181,48 @@ o:value("1:65535", translate("All"))
o:depends("mode", "1") o:depends("mode", "1")
o.validate = port_validate o.validate = port_validate
o = s:option(DummyValue, "_hide_node_option", "") o = s:taboption("Main", HideValue, "_hide_node_option", "")
o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends("mode", "0") o:depends("mode", "0")
o:depends({ tcp_no_redir_ports = "1:65535", udp_no_redir_ports = "1:65535" }) o:depends({ tcp_no_redir_ports = "1:65535", udp_no_redir_ports = "1:65535" })
if TCP_NO_REDIR_PORTS == "1:65535" and UDP_NO_REDIR_PORTS == "1:65535" then if TCP_NO_REDIR_PORTS == "1:65535" and UDP_NO_REDIR_PORTS == "1:65535" then
o:depends({ tcp_no_redir_ports = "", udp_no_redir_ports = "" }) o:depends({ tcp_no_redir_ports = "", udp_no_redir_ports = "" })
end end
o = s:option(Flag, "use_global_config", translatef("Use global config")) o = s:taboption("Main", Flag, "use_global_config", translatef("Use global config"))
o.default = "0" o.default = "0"
o.rmempty = false o.rmempty = false
o:depends({ _hide_node_option = "1", ['!reverse'] = true }) o:depends({ _hide_node_option = "1", ['!reverse'] = true })
o = s:option(ListValue, "node", "<a style='color: red'>" .. translate("Proxy Node") .. "</a>") o = s:taboption("Main", ListValue, "node", "<a style='color: red'>" .. translate("Proxy Node") .. "</a>")
o.default = "" o.group = {}
o:depends({ _hide_node_option = false, use_global_config = false }) o:depends({ _hide_node_option = false, use_global_config = false })
o.template = m:template_path("/cbi/nodes_listvalue") o.template = m:template_path("/cbi/nodes_listvalue")
o.group = {} o.description = translate("Use global configuration when using the global node.")
o = s:option(DummyValue, "_acl_node_bool", "") current_node_id = m:get(arg[1], "node")
o.template = m:template_path("/cbi/hidevalue") local node_value = s.fields["node"]:formvalue(s.section)
o.value = "1" if node_value then
o:depends({ node = "", ['!reverse'] = true }) current_node_id = node_value
end
current_node = current_node_id and m:get(current_node_id) or {}
---- Log o = s:taboption("Main", HideValue, "node_save_before", "")
o = s:option(Flag, "log", translate("Enable Node Log")) o.value = current_node[".name"]
o.default = 0 o.cbid = function(self, section) return "node_save_before" end
o.rmempty = false
o:depends({ _hide_node_option = false, use_global_config = false })
o = s:option(ListValue, "loglevel", "Sing-Box/Xray " .. translate("Log Level")) o = s:taboption("Main", HideValue, "_acl_node_bool", "")
o.default = "warn" o:depends({ node = "", ['!reverse'] = true })
o:value("debug", "Debug")
o:value("info", "Info") o = s:taboption("Main", HideValue, "_diff_global_node", "")
o:value("warn", "Warning") if NODE ~= "" then
o:value("error", "Error") o:depends({ node = NODE, ['!reverse'] = true })
o:depends("log", "1") else
o:depends({ _acl_node_bool = "1" })
end
---- TCP Proxy Drop Ports ---- TCP Proxy Drop Ports
local TCP_PROXY_DROP_PORTS = m:get("@global_forwarding[0]", "tcp_proxy_drop_ports") local TCP_PROXY_DROP_PORTS = m:get("@global_forwarding[0]", "tcp_proxy_drop_ports")
o = s:option(Value, "tcp_proxy_drop_ports", translate("TCP Proxy Drop Ports")) o = s:taboption("Proxy", Value, "tcp_proxy_drop_ports", translate("TCP Proxy Drop Ports"))
o:value("", translate("Use global config") .. "(" .. TCP_PROXY_DROP_PORTS .. ")") o:value("", translate("Use global config") .. "(" .. TCP_PROXY_DROP_PORTS .. ")")
o:value("disable", translate("No patterns are used")) o:value("disable", translate("No patterns are used"))
o.validate = port_validate o.validate = port_validate
@@ -227,7 +231,7 @@ o:depends({ _acl_node_bool = "1" })
---- UDP Proxy Drop Ports ---- UDP Proxy Drop Ports
local UDP_PROXY_DROP_PORTS = m:get("@global_forwarding[0]", "udp_proxy_drop_ports") local UDP_PROXY_DROP_PORTS = m:get("@global_forwarding[0]", "udp_proxy_drop_ports")
o = s:option(Value, "udp_proxy_drop_ports", translate("UDP Proxy Drop Ports")) o = s:taboption("Proxy", Value, "udp_proxy_drop_ports", translate("UDP Proxy Drop Ports"))
o:value("", translate("Use global config") .. "(" .. UDP_PROXY_DROP_PORTS .. ")") o:value("", translate("Use global config") .. "(" .. UDP_PROXY_DROP_PORTS .. ")")
o:value("disable", translate("No patterns are used")) o:value("disable", translate("No patterns are used"))
o:value("443", translate("QUIC")) o:value("443", translate("QUIC"))
@@ -237,7 +241,7 @@ o:depends({ _acl_node_bool = "1" })
---- TCP Redir Ports ---- TCP Redir Ports
local TCP_REDIR_PORTS = m:get("@global_forwarding[0]", "tcp_redir_ports") local TCP_REDIR_PORTS = m:get("@global_forwarding[0]", "tcp_redir_ports")
o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"), translatef("Only work with using the %s node.", "TCP")) o = s:taboption("Proxy", Value, "tcp_redir_ports", translate("TCP Redir Ports"), translatef("Only work with using the %s node.", "TCP"))
o:value("", translate("Use global config") .. "(" .. TCP_REDIR_PORTS .. ")") o:value("", translate("Use global config") .. "(" .. TCP_REDIR_PORTS .. ")")
o:value("1:65535", translate("All")) o:value("1:65535", translate("All"))
o:value("80,443", "80,443") o:value("80,443", "80,443")
@@ -249,7 +253,7 @@ o:depends({ _acl_node_bool = "1" })
---- UDP Redir Ports ---- UDP Redir Ports
local UDP_REDIR_PORTS = m:get("@global_forwarding[0]", "udp_redir_ports") local UDP_REDIR_PORTS = m:get("@global_forwarding[0]", "udp_redir_ports")
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"), translatef("Only work with using the %s node.", "UDP")) o = s:taboption("Proxy", Value, "udp_redir_ports", translate("UDP Redir Ports"), translatef("Only work with using the %s node.", "UDP"))
o:value("", translate("Use global config") .. "(" .. UDP_REDIR_PORTS .. ")") o:value("", translate("Use global config") .. "(" .. UDP_REDIR_PORTS .. ")")
o:value("1:65535", translate("All")) o:value("1:65535", translate("All"))
o:value("53", "53") o:value("53", "53")
@@ -257,7 +261,7 @@ o.validate = port_validate
o:depends({ use_global_config = true }) o:depends({ use_global_config = true })
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1" })
o = s:option(DummyValue, "tips", " ") o = s:taboption("Proxy", DummyValue, "tips", " ")
o.rawhtml = true o.rawhtml = true
o.cfgvalue = function(t, n) o.cfgvalue = function(t, n)
return string.format('<font color="red">%s</font>', return string.format('<font color="red">%s</font>',
@@ -266,71 +270,67 @@ end
o:depends({ use_global_config = true }) o:depends({ use_global_config = true })
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1" })
o = s:option(Flag, "use_direct_list", translatef("Use %s", translate("Direct List"))) o = s:taboption("Proxy", Flag, "use_direct_list", translatef("Use %s", translate("Direct List")))
o.default = "1" o.default = "1"
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
o = s:option(Flag, "use_proxy_list", translatef("Use %s", translate("Proxy List"))) o = s:taboption("Proxy", Flag, "use_proxy_list", translatef("Use %s", translate("Proxy List")))
o.default = "1" o.default = "1"
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
o = s:option(Flag, "use_block_list", translatef("Use %s", translate("Block List"))) o = s:taboption("Proxy", Flag, "use_block_list", translatef("Use %s", translate("Block List")))
o.default = "1" o.default = "1"
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
if has_gfwlist then if has_gfwlist then
o = s:option(Flag, "use_gfw_list", translatef("Use %s", translate("GFW List"))) o = s:taboption("Proxy", Flag, "use_gfw_list", translatef("Use %s", translate("GFW List")))
o.default = "1" o.default = "1"
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
end end
if has_chnlist or has_chnroute then if has_chnlist or has_chnroute then
o = s:option(ListValue, "chn_list", translate("China List")) o = s:taboption("Proxy", ListValue, "chn_list", translate("China List"))
o:value("0", translate("Close(Not use)")) o:value("0", translate("Close(Not use)"))
o:value("direct", translate("Direct Connection")) o:value("direct", translate("Direct Connection"))
o:value("proxy", translate("Proxy")) o:value("proxy", translate("Proxy"))
o.default = "direct" o.default = "direct"
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
end end
o = s:option(ListValue, "tcp_proxy_mode", "TCP " .. translate("Proxy Mode")) o = s:taboption("Proxy", ListValue, "tcp_proxy_mode", "TCP " .. translate("Proxy Mode"))
o:value("disable", translate("No Proxy")) o:value("disable", translate("No Proxy"))
o:value("proxy", translate("Proxy")) o:value("proxy", translate("Proxy"))
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
o = s:option(ListValue, "udp_proxy_mode", "UDP " .. translate("Proxy Mode")) o = s:taboption("Proxy", ListValue, "udp_proxy_mode", "UDP " .. translate("Proxy Mode"))
o:value("disable", translate("No Proxy")) o:value("disable", translate("No Proxy"))
o:value("proxy", translate("Proxy")) o:value("proxy", translate("Proxy"))
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
o = s:option(DummyValue, "switch_mode", " ") o = s:taboption("Proxy", DummyValue, "switch_mode", "")
o.template = m:template_path("/global/proxy") o.template = m:template_path("/global/proxy")
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
-- Node → DNS Depends Settings -- Node → DNS Depends Settings
o = s:option(DummyValue, "_node_sel_shunt", "") o = s:taboption("Main", HideValue, "_node_sel_shunt", "")
o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends({ node = "__always__" }) o:depends({ node = "__always__" })
o = s:option(DummyValue, "_node_sel_other", "") o = s:taboption("Main", HideValue, "_node_sel_other", "")
o.template = m:template_path("/cbi/hidevalue") o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
o.value = "1"
o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
---- DNS ---- DNS
o = s:option(ListValue, "dns_shunt", "DNS " .. translate("Shunt")) o = s:taboption("DNS", ListValue, "dns_shunt", "DNS " .. translate("Shunt"))
o.default = "chinadns-ng" o.default = "chinadns-ng"
o:value("dnsmasq", "Dnsmasq") o:value("dnsmasq", "Dnsmasq")
o:value("chinadns-ng", translate("ChinaDNS-NG (recommended)")) o:value("chinadns-ng", translate("ChinaDNS-NG (recommended)"))
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
o = s:option(Flag, "filter_proxy_ipv6", translate("Filter Proxy Host IPv6"), translate("Experimental feature.")) o = s:taboption("DNS", Flag, "filter_proxy_ipv6", translate("Filter Proxy Host IPv6"), translate("Experimental feature."))
o.default = "0" o.default = "0"
o:depends({ _acl_node_bool = "1" }) o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
---- DNS Forward Mode ---- DNS Forward Mode
o = s:option(ListValue, "dns_mode", translate("Filter Mode")) o = s:taboption("DNS", ListValue, "dns_mode", translate("Filter Mode"))
if api.is_finded("dns2socks") then if api.is_finded("dns2socks") then
o:value("dns2socks", "dns2socks") o:value("dns2socks", "dns2socks")
end end
@@ -340,7 +340,7 @@ end
if has_xray then if has_xray then
o:value("xray", "Xray") o:value("xray", "Xray")
end end
o:depends({ _acl_node_bool = "1", _node_sel_other = "1" }) o:depends({ _acl_node_bool = "1", _node_sel_other = "1", _diff_global_node = "1" })
o.write = function(self, section, value) o.write = function(self, section, value)
if value == "dns2socks" then if value == "dns2socks" then
m:del(section, "v2ray_dns_mode") m:del(section, "v2ray_dns_mode")
@@ -369,7 +369,7 @@ o.remove = function(self, section)
end end
end end
o = s:option(ListValue, "xray_dns_mode", translate("Request protocol")) o = s:taboption("DNS", ListValue, "xray_dns_mode", translate("Request protocol"))
o.default = "tcp" o.default = "tcp"
o:value("tcp", "TCP") o:value("tcp", "TCP")
o:value("udp", "UDP") o:value("udp", "UDP")
@@ -384,12 +384,14 @@ o.write = function(self, section, value)
end end
end end
o = s:option(ListValue, "singbox_dns_mode", translate("Request protocol")) o = s:taboption("DNS", ListValue, "singbox_dns_mode", translate("Request protocol"))
o.default = "tcp" o.default = "tcp"
o:value("tcp", "TCP") o:value("tcp", "TCP")
o:value("udp", "UDP") o:value("udp", "UDP")
o:value("doh", "DoH") o:value("doh", "DoH")
o:value("http3", "HTTP3(DoH3)") o:value("http3", "HTTP3(DoH3)")
o:value("tls", "TLS(DoT)")
o:value("quic", "QUIC(DoQ)")
o:depends("dns_mode", "sing-box") o:depends("dns_mode", "sing-box")
o.cfgvalue = function(self, section) o.cfgvalue = function(self, section)
return m:get(section, "v2ray_dns_mode") return m:get(section, "v2ray_dns_mode")
@@ -401,7 +403,7 @@ o.write = function(self, section, value)
end end
---- DNS Forward ---- DNS Forward
o = s:option(Value, "remote_dns", translate("Remote DNS")) o = s:taboption("DNS", Value, "remote_dns", translate("Remote DNS"))
o.datatype = "or(ipaddr,ipaddrport(1))" o.datatype = "or(ipaddr,ipaddrport(1))"
o.default = "1.1.1.1" o.default = "1.1.1.1"
o:value("1.1.1.1", "1.1.1.1 (CloudFlare)") o:value("1.1.1.1", "1.1.1.1 (CloudFlare)")
@@ -417,8 +419,10 @@ o:depends({xray_dns_mode = "udp"})
o:depends({xray_dns_mode = "tcp"}) o:depends({xray_dns_mode = "tcp"})
o:depends({singbox_dns_mode = "udp"}) o:depends({singbox_dns_mode = "udp"})
o:depends({singbox_dns_mode = "tcp"}) o:depends({singbox_dns_mode = "tcp"})
o:depends({singbox_dns_mode = "tls"})
o:depends({singbox_dns_mode = "quic"})
o = s:option(Value, "remote_dns_doh", translate("Remote DNS DoH")) o = s:taboption("DNS", Value, "remote_dns_doh", translate("Remote DNS DoH"))
o.description = translate("Format: URL[,IP] (optional IP to map the domain in the URL)") o.description = translate("Format: URL[,IP] (optional IP to map the domain in the URL)")
o:value("https://1.1.1.1/dns-query", "1.1.1.1 (CloudFlare)") o:value("https://1.1.1.1/dns-query", "1.1.1.1 (CloudFlare)")
o:value("https://1.1.1.2/dns-query", "1.1.1.2 (CloudFlare-Security)") o:value("https://1.1.1.2/dns-query", "1.1.1.2 (CloudFlare-Security)")
@@ -457,15 +461,15 @@ o:depends({xray_dns_mode = "doh"})
o:depends({singbox_dns_mode = "doh"}) o:depends({singbox_dns_mode = "doh"})
o:depends({singbox_dns_mode = "http3"}) o:depends({singbox_dns_mode = "http3"})
o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet")) o = s:taboption("DNS", Value, "remote_dns_client_ip", translate("EDNS Client Subnet"))
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" .. o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).") translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
o.datatype = "ipaddr" o.datatype = "ipaddr"
o:depends({dns_mode = "sing-box"}) o:depends({dns_mode = "sing-box"})
o:depends({dns_mode = "xray"}) o:depends({dns_mode = "xray"})
o:depends({_node_sel_shunt = "1"}) o:depends({_node_sel_shunt = "1", _diff_global_node = "1"})
o = s:option(Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the domain that proxy.")) o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the domain that proxy."))
o.default = "0" o.default = "0"
o.rmempty = false o.rmempty = false
o:depends({dns_mode = "sing-box"}) o:depends({dns_mode = "sing-box"})
@@ -483,12 +487,12 @@ o.validate = function(self, value, t)
return value return value
end end
o = s:option(Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL") o = s:taboption("DNS", Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL")
o.datatype = "min(1)" o.datatype = "min(1)"
o.default = "30" o.default = "30"
o:depends({dns_mode = "sing-box"}) o:depends({dns_mode = "sing-box"})
o = s:option(ListValue, "chinadns_ng_default_tag", translate("Default DNS")) o = s:taboption("DNS", ListValue, "chinadns_ng_default_tag", translate("Default DNS"))
o.default = "none" o.default = "none"
o:value("gfw", translate("Remote DNS")) o:value("gfw", translate("Remote DNS"))
o:value("chn", translate("Direct DNS")) o:value("chn", translate("Direct DNS"))
@@ -506,50 +510,119 @@ o.description = desc
.. "</ul>" .. "</ul>"
o:depends({dns_shunt = "chinadns-ng", tcp_proxy_mode = "proxy", chn_list = "direct"}) o:depends({dns_shunt = "chinadns-ng", tcp_proxy_mode = "proxy", chn_list = "direct"})
o = s:option(Flag, "force_https_soa", translate("Force HTTPS SOA"), translate("Force queries with qtype 65 to respond with an SOA record.")) o = s:taboption("DNS", Flag, "force_https_soa", translate("Force HTTPS SOA"), translate("Force queries with qtype 65 to respond with an SOA record."))
o.default = "0" o.default = "0"
o.rmempty = false o.rmempty = false
o:depends({dns_shunt = "chinadns-ng"}) o:depends({dns_shunt = "chinadns-ng"})
o = s:option(ListValue, "use_default_dns", translate("Default DNS")) o = s:taboption("DNS", ListValue, "use_default_dns", translate("Default DNS"))
o.default = "direct" o.default = "direct"
o:value("remote", translate("Remote DNS")) o:value("remote", translate("Remote DNS"))
o:value("direct", translate("Direct DNS")) o:value("direct", translate("Direct DNS"))
o.description = desc .. "</ul>" o.description = desc .. "</ul>"
o:depends({dns_shunt = "dnsmasq", tcp_proxy_mode = "proxy", chn_list = "direct"}) o:depends({dns_shunt = "dnsmasq", tcp_proxy_mode = "proxy", chn_list = "direct"})
---- Log
o = s:taboption("Log", Flag, "log", translate("Enable Node Log"))
o.default = 0
o.rmempty = false
o:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
o = s:taboption("Log", ListValue, "loglevel", "Sing-Box/Xray " .. translate("Log Level"))
o.default = "warn"
o:value("debug", "Debug")
o:value("info", "Info")
o:value("warn", "Warning")
o:value("error", "Error")
o:depends("log", "1")
o = s:taboption("Log", DummyValue, "_node_log", translate("Log File"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local log_file = api.TMP_PATH .. "/acl/" .. arg[1] .. "/node.log"
local log_url = api.url("get_redir_log") .. "?id=" .. arg[1]
local s = "<code>%s</code>&nbsp;&nbsp;" % log_file
if api.fs.access(log_file) then
local btn = string.format(
'<input class="btn cbi-button cbi-button-apply" type="button" value="%s" onclick="window.open(\'%s\', \'_blank\')" />',
translate("View Log"),
log_url
)
s = s .. btn
end
return s
end
o:depends("log", "1")
o = s:taboption("Log", Flag, "log_chinadns_ng", translate("Enable") .. " ChinaDNS-NG " .. translate("Log"))
o.default = "0"
o.rmempty = false
o:depends("dns_shunt", "chinadns-ng")
o = s:taboption("Log", DummyValue, "_chinadns_ng_log", translate("Log File"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local log_file = api.TMP_PATH .. "/acl/" .. arg[1] .. "/chinadns_ng.log"
local log_url = api.url("get_chinadns_log") .. "?flag=" .. arg[1]
local s = "<code>%s</code>&nbsp;&nbsp;" % log_file
if api.fs.access(log_file) then
local btn = string.format(
'<input class="btn cbi-button cbi-button-apply" type="button" value="%s" onclick="window.open(\'%s\', \'_blank\')" />',
translate("View Log"),
log_url
)
s = s .. btn
end
return s
end
o:depends("log_chinadns_ng", "1")
local o_node = s.fields["node"] local o_node = s.fields["node"]
local shunt_list = {}
for k, v in pairs(socks_list) do for k, v in pairs(socks_list) do
o_node:value(v.id, v["remark"]) o_node:value(v.id, v["remark"])
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end end
for k, v in pairs(nodes_table) do for k, v in pairs(nodes_table) do
if #normal_list == 0 then if #normal_list == 0 then
s.fields["dns_mode"]:depends({ _acl_node_bool = "1" }) s.fields["dns_mode"]:depends({ _acl_node_bool = "1", _diff_global_node = "1" })
break break
end end
if v.protocol == "_shunt" then if v.protocol and v.protocol == "_shunt" then
if v.type == "Xray" and has_xray then if v.type == "Xray" and has_xray then
o_node:value(v.id, v["remark"]) o_node:value(v.id, v["remark"])
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
s.fields["xray_dns_mode"]:depends({ _acl_node_bool = "1", node = v.id }) s.fields["xray_dns_mode"]:depends({ _acl_node_bool = "1", _diff_global_node = "1", node = v.id })
s.fields["_node_sel_shunt"]:depends({ node = v.id }) s.fields["_node_sel_shunt"]:depends({ node = v.id })
end end
if v.type == "sing-box" and has_singbox then if v.type == "sing-box" and has_singbox then
o_node:value(v.id, v["remark"]) o_node:value(v.id, v["remark"])
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
s.fields["singbox_dns_mode"]:depends({ _acl_node_bool = "1", node = v.id }) s.fields["singbox_dns_mode"]:depends({ _acl_node_bool = "1", _diff_global_node = "1", node = v.id })
s.fields["_node_sel_shunt"]:depends({ node = v.id }) s.fields["_node_sel_shunt"]:depends({ node = v.id })
s.fields["remote_rewrite_ttl"]:depends({ _acl_node_bool = "1", node = v.id }) s.fields["remote_rewrite_ttl"]:depends({ _acl_node_bool = "1", _diff_global_node = "1", node = v.id })
end end
shunt_list[#shunt_list + 1] = v
else else
o_node:value(v.id, v["remark"]) o_node:value(v.id, v["remark"])
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end end
end end
m:appendTemplate("/acl/config_footer", {section = arg[1]}) -- Shunt
if current_node.protocol == "_shunt" and current_node[".name"] ~= NODE then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s, {
node = current_node,
verify_option = s.fields["node"],
tab = "Shunt",
tab_desc = translate("Shunt Rule"),
})
end
m:appendTemplate("/include/node_change", { verify_option = s.fields["node"], shunt_list = api.jsonc.stringify(shunt_list) })
return api.return_map(m) return api.return_map(m)
@@ -20,7 +20,10 @@ for _, k in ipairs(com.order) do
if k ~= "chinadns-ng" then if k ~= "chinadns-ng" then
o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name)) o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name))
o.default = v.default_path or ("/usr/bin/" .. k) o.default = v.default_path or ("/usr/bin/" .. k)
o.rmempty = false o.placeholder = o.default
function o:remove(section)
self:write(section, self.default)
end
end end
end end
@@ -112,6 +112,10 @@ o:value("", translate("Close"))
o.group = {""} o.group = {""}
current_node_id = m:get(s.section, "node") current_node_id = m:get(s.section, "node")
local node_value = s.fields["node"]:formvalue(s.section)
if node_value then
current_node_id = node_value
end
current_node = current_node_id and m:get(current_node_id) or {} current_node = current_node_id and m:get(current_node_id) or {}
-- Shunt Start -- Shunt Start
@@ -120,17 +124,10 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
if current_node.protocol == "_shunt" then if current_node.protocol == "_shunt" then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua") local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s, { setfenv(shunt_lua, getfenv(1))(m, s, {
s_cfgid = s.section,
node_id = current_node_id,
node = current_node, node = current_node,
socks_list = socks_list,
urltest_list = urltest_list,
balancing_list = balancing_list,
iface_list = iface_list,
normal_list = normal_list,
verify_option = s.fields["node"], verify_option = s.fields["node"],
tab = "Shunt", tab = "Shunt",
tab_desc = translate("Shunt Rule") tab_desc = translate("Shunt Rule"),
}) })
end end
else else
@@ -139,7 +136,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
tips.cfgvalue = function(t, n) tips.cfgvalue = function(t, n)
return string.format('<a style="color: red">%s</a>', translate("There are no available nodes, please add or subscribe nodes first.")) return string.format('<a style="color: red">%s</a>', translate("There are no available nodes, please add or subscribe nodes first."))
end end
tips:depends({ node = "", ["!reverse"] = true }) tips:depends("_node", "1")
for k, v in pairs(shunt_list) do for k, v in pairs(shunt_list) do
tips:depends("node", v.id) tips:depends("node", v.id)
end end
@@ -153,7 +150,7 @@ o = s:taboption("Main", Value, "node_socks_port", translate("Node") .. " Socks "
o.default = 1070 o.default = 1070
o.placeholder = 1070 o.placeholder = 1070
o.datatype = "range(1,65535)" o.datatype = "range(1,65535)"
o:depends({ node = "", ["!reverse"] = true }) o:depends("_node", "1")
--[[ --[[
if has_singbox or has_xray then if has_singbox or has_xray then
o = s:taboption("Main", Value, "node_http_port", translate("Node") .. " HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use")) o = s:taboption("Main", Value, "node_http_port", translate("Node") .. " HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
@@ -163,17 +160,20 @@ end
]]-- ]]--
o = s:taboption("Main", Flag, "node_socks_bind_local", translate("Node") .. " Socks " .. translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) o = s:taboption("Main", Flag, "node_socks_bind_local", translate("Node") .. " Socks " .. translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
o.default = "1" o.default = "1"
o:depends({ node = "", ["!reverse"] = true }) o:depends("_node", "1")
o = s:taboption("Main", HideValue, "node_save_before", "")
o.value = current_node[".name"]
o.cbid = function(self, section) return "node_save_before" end
o = s:taboption("Main", HideValue, "_node", "")
o:depends({ node = "", ['!reverse'] = true })
-- Node → DNS Depends Settings -- Node → DNS Depends Settings
o = s:taboption("Main", DummyValue, "_node_sel_shunt", "") o = s:taboption("Main", HideValue, "_node_sel_shunt", "")
o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends({ node = "__always__" }) o:depends({ node = "__always__" })
o = s:taboption("Main", DummyValue, "_node_sel_other", "") o = s:taboption("Main", HideValue, "_node_sel_other", "")
o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends({ _node_sel_shunt = "1", ['!reverse'] = true }) o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
-- [[ DNS Settings ]]-- -- [[ DNS Settings ]]--
@@ -364,6 +364,8 @@ o:value("tcp", "TCP")
o:value("udp", "UDP") o:value("udp", "UDP")
o:value("doh", "DoH") o:value("doh", "DoH")
o:value("http3", "HTTP3(DoH3)") o:value("http3", "HTTP3(DoH3)")
o:value("tls", "TLS(DoT)")
o:value("quic", "QUIC(DoQ)")
o:depends("dns_mode", "sing-box") o:depends("dns_mode", "sing-box")
o:depends("smartdns_dns_mode", "sing-box") o:depends("smartdns_dns_mode", "sing-box")
o.cfgvalue = function(self, section) o.cfgvalue = function(self, section)
@@ -406,6 +408,8 @@ o:depends({xray_dns_mode = "udp"})
o:depends({xray_dns_mode = "tcp"}) o:depends({xray_dns_mode = "tcp"})
o:depends({singbox_dns_mode = "udp"}) o:depends({singbox_dns_mode = "udp"})
o:depends({singbox_dns_mode = "tcp"}) o:depends({singbox_dns_mode = "tcp"})
o:depends({singbox_dns_mode = "tls"})
o:depends({singbox_dns_mode = "quic"})
---- DoH ---- DoH
o = s:taboption("DNS", Value, "remote_dns_doh", translate("Remote DNS DoH")) o = s:taboption("DNS", Value, "remote_dns_doh", translate("Remote DNS DoH"))
@@ -556,7 +560,7 @@ o:value("disable", translate("No Proxy"))
o:value("proxy", translate("Proxy")) o:value("proxy", translate("Proxy"))
o.default = "proxy" o.default = "proxy"
o = s:taboption("Proxy", DummyValue, "switch_mode", " ") o = s:taboption("Proxy", DummyValue, "switch_mode", "")
o.template = m:template_path("/global/proxy") o.template = m:template_path("/global/proxy")
---- Check the transparent proxy component ---- Check the transparent proxy component
@@ -608,21 +612,46 @@ o:value("info", "Info")
o:value("warn", "Warning") o:value("warn", "Warning")
o:value("error", "Error") o:value("error", "Error")
o = s:taboption("log", Flag, "advanced_log_feature", translate("Advanced log feature"), translate("For professionals only.")) o = s:taboption("log", DummyValue, "_node_log", translate("Log File"))
o.default = "0" o.rawhtml = true
o = s:taboption("log", Flag, "sys_log", translate("Logging to system log"), translate("Logging to the system log for more advanced functions. For example, send logs to a dedicated log server.")) o.cfgvalue = function(t, n)
o:depends("advanced_log_feature", "1") local log_file = api.TMP_PATH .. "/acl/default/global.log"
o.default = "0" local log_url = api.url("get_redir_log") .. "?id=default"
o = s:taboption("log", Value, "persist_log_path", translate("Persist log file directory"), translate("The path to the directory used to store persist log files, the \"/\" at the end can be omitted. Leave it blank to disable this feature.")) local s = "<code>%s</code>&nbsp;&nbsp;" % log_file
o:depends({ ["advanced_log_feature"] = 1, ["sys_log"] = 0 }) if api.fs.access(log_file) then
o = s:taboption("log", Value, "log_event_filter", translate("Log Event Filter"), translate("Support regular expression.")) local btn = string.format(
o:depends("advanced_log_feature", "1") '<input class="btn cbi-button cbi-button-apply" type="button" value="%s" onclick="window.open(\'%s\', \'_blank\')" />',
o = s:taboption("log", Value, "log_event_cmd", translate("Shell Command"), translate("Shell command to execute, replace log content with %s.")) translate("View Log"),
o:depends("advanced_log_feature", "1") log_url
)
s = s .. btn
end
return s
end
o:depends("log_node", "1")
o = s:taboption("log", Flag, "log_chinadns_ng", translate("Enable") .. " ChinaDNS-NG " .. translate("Log")) o = s:taboption("log", Flag, "log_chinadns_ng", translate("Enable") .. " ChinaDNS-NG " .. translate("Log"))
o.default = "0" o.default = "0"
o.rmempty = false o.rmempty = false
o:depends("dns_shunt", "chinadns-ng")
o = s:taboption("log", DummyValue, "_chinadns_ng_log", translate("Log File"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local log_file = api.TMP_PATH .. "/acl/default/chinadns_ng.log"
local log_url = api.url("get_chinadns_log") .. "?flag=default"
local s = "<code>%s</code>&nbsp;&nbsp;" % log_file
if api.fs.access(log_file) then
local btn = string.format(
'<input class="btn cbi-button cbi-button-apply" type="button" value="%s" onclick="window.open(\'%s\', \'_blank\')" />',
translate("View Log"),
log_url
)
s = s .. btn
end
return s
end
o:depends("log_chinadns_ng", "1")
o = s:taboption("log", DummyValue, "_log_tips", " ") o = s:taboption("log", DummyValue, "_log_tips", " ")
o.rawhtml = true o.rawhtml = true
@@ -638,6 +667,8 @@ s:tab("maintain", translate("Maintain"))
o = s:taboption("maintain", DummyValue, "") o = s:taboption("maintain", DummyValue, "")
o.template = m:template_path("/global/backup") o.template = m:template_path("/global/backup")
m:appendTemplate("/include/node_change", { verify_option = s.fields["node"], shunt_list = api.jsonc.stringify(shunt_list) })
-- [[ Socks Server ]]-- -- [[ Socks Server ]]--
o = s:taboption("Main", Flag, "socks_enabled", "Socks " .. translate("Main switch")) o = s:taboption("Main", Flag, "socks_enabled", "Socks " .. translate("Main switch"))
o.rmempty = false o.rmempty = false
@@ -747,7 +778,7 @@ for k, v in pairs(nodes_table) do
if #normal_list == 0 and #iface_list == 0 then if #normal_list == 0 and #iface_list == 0 then
break break
end end
if v.protocol == "_shunt" then if v.protocol and v.protocol == "_shunt" then
if has_singbox or has_xray then if has_singbox or has_xray then
o_node:value(v.id, v["remark"]) o_node:value(v.id, v["remark"])
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
@@ -775,7 +806,7 @@ for k, v in pairs(nodes_table) do
end end
end end
m:appendTemplate("/global/footer", {shunt_list = api.jsonc.stringify(shunt_list)}) m:appendTemplate("/global/footer")
m:appendTemplate("/cbi/sortable", {sectiontype = s2.sectiontype}) m:appendTemplate("/cbi/sortable", {sectiontype = s2.sectiontype})
@@ -1,12 +1,28 @@
local m, s, data = ... local m, s, data = ...
if not data.node_id or not data.node then if not data.node or not data.node[".name"] then
return return
end end
local s_cfgid = data.s_cfgid local api = m.api
local current_node_id = data.node_id local section_id = s.section
local current_node = data.node
local current_node_id = current_node[".name"]
local verify_option = data.verify_option
local node_list = data.node_list or api.get_node_list() local node_list = data.node_list or api.get_node_list()
local shunt_section_type = "shunt_option_list"
if data.tab then
if #s.tab_names > 0 then
table.insert(s.tab_names, 2, data.tab)
s.tabs[data.tab] = {
title = data.tab_desc,
childs = { }
}
else
s:tab(data.tab, data.tab_desc)
end
end
local groups = {} local groups = {}
m:foreach("shunt_rules", function(s) m:foreach("shunt_rules", function(s)
@@ -15,6 +31,9 @@ m:foreach("shunt_rules", function(s)
end end
end) end)
local node_save_before = luci.http.formvalue("node_save_before")
local load_shunt = luci.http.formvalue("load_shunt")
local function get_cfgvalue() local function get_cfgvalue()
return function(self, section) return function(self, section)
return m:get(current_node_id, self.option) return m:get(current_node_id, self.option)
@@ -22,8 +41,10 @@ local function get_cfgvalue()
end end
local function get_write() local function get_write()
return function(self, section, value) return function(self, section, value)
if data.verify_option then if node_save_before and node_save_before ~= current_node_id then return end
if data.verify_option:formvalue(section) == current_node_id then if load_shunt == "1" then return end
if verify_option then
if verify_option:formvalue(section) == current_node_id then
m:set(current_node_id, self.option, value) m:set(current_node_id, self.option, value)
end end
else else
@@ -33,8 +54,10 @@ local function get_write()
end end
local function get_remove() local function get_remove()
return function(self, section) return function(self, section)
if data.verify_option then if node_save_before and node_save_before ~= current_node_id then return end
if data.verify_option:formvalue(section) == current_node_id then if load_shunt == "1" then return end
if verify_option then
if verify_option:formvalue(section) == current_node_id then
m:del(current_node_id, self.option) m:del(current_node_id, self.option)
end end
else else
@@ -43,10 +66,6 @@ local function get_remove()
end end
end end
if data.tab then
s:tab(data.tab, data.tab_desc)
end
local function add_option(class, option_name, option_title, option_desc) local function add_option(class, option_name, option_title, option_desc)
local a local a
if data.tab then if data.tab then
@@ -62,8 +81,8 @@ local function add_option(class, option_name, option_title, option_desc)
a.write = get_write() a.write = get_write()
a.remove = get_remove() a.remove = get_remove()
end end
if data.verify_option then if verify_option then
a:depends(data.verify_option.option, current_node_id) a:depends(verify_option.option, current_node_id)
end end
return a return a
end end
@@ -80,7 +99,7 @@ local function add_depends(o, deps)
end end
end end
if data.node.type == "Xray" then if current_node.type == "Xray" then
o = add_option(ListValue, "domainStrategy", translate("Domain Strategy")) o = add_option(ListValue, "domainStrategy", translate("Domain Strategy"))
o:value("AsIs") o:value("AsIs")
o:value("IPIfNonMatch") o:value("IPIfNonMatch")
@@ -133,7 +152,7 @@ table.insert(shunt_rules, {
s2 = m:section(Table, shunt_rules, " ") s2 = m:section(Table, shunt_rules, " ")
s2.config = m.config s2.config = m.config
s2.sectiontype = "shunt_option_list" s2.sectiontype = shunt_section_type
o = s2:option(DummyValue, "remarks", translate("Rule")) o = s2:option(DummyValue, "remarks", translate("Rule"))
o.rawhtml = true o.rawhtml = true
@@ -156,9 +175,13 @@ _node.cfgvalue = function(self, section)
return m:get(current_node_id, shunt_rules[section]["_node_option"]) or shunt_rules[section]["_node_default"] return m:get(current_node_id, shunt_rules[section]["_node_option"]) or shunt_rules[section]["_node_default"]
end end
_node.write = function(self, section, value) _node.write = function(self, section, value)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:set(current_node_id, shunt_rules[section]["_node_option"], value) return m:set(current_node_id, shunt_rules[section]["_node_option"], value)
end end
_node.remove = function(self, section) _node.remove = function(self, section)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:del(current_node_id, shunt_rules[section]["_node_option"]) return m:del(current_node_id, shunt_rules[section]["_node_option"])
end end
@@ -169,9 +192,13 @@ o.cfgvalue = function(self, section)
return m:get(current_node_id, shunt_rules[section]["_fakedns_option"]) return m:get(current_node_id, shunt_rules[section]["_fakedns_option"])
end end
o.write = function(self, section, value) o.write = function(self, section, value)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:set(current_node_id, shunt_rules[section]["_fakedns_option"], value) return m:set(current_node_id, shunt_rules[section]["_fakedns_option"], value)
end end
o.remove = function(self, section) o.remove = function(self, section)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:del(current_node_id, shunt_rules[section]["_fakedns_option"]) return m:del(current_node_id, shunt_rules[section]["_fakedns_option"])
end end
@@ -185,9 +212,13 @@ proxy_tag_node.cfgvalue = function(self, section)
return m:get(current_node_id, shunt_rules[section]["_proxy_tag_option"]) return m:get(current_node_id, shunt_rules[section]["_proxy_tag_option"])
end end
proxy_tag_node.write = function(self, section, value) proxy_tag_node.write = function(self, section, value)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:set(current_node_id, shunt_rules[section]["_proxy_tag_option"], value) return m:set(current_node_id, shunt_rules[section]["_proxy_tag_option"], value)
end end
proxy_tag_node.remove = function(self, section) proxy_tag_node.remove = function(self, section)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:del(current_node_id, shunt_rules[section]["_proxy_tag_option"]) return m:del(current_node_id, shunt_rules[section]["_proxy_tag_option"])
end end
@@ -203,4 +234,11 @@ for k1, v1 in pairs(node_list) do
end end
end end
m:appendTemplate("/include/shunt_options", {id = current_node_id, s_cfgid = s_cfgid or current_node_id, normal_list = api.jsonc.stringify(node_list.normal_list)}) m:appendTemplate("/include/shunt_options", {
node_id = current_node_id,
section_id = section_id or current_node_id,
section_type = shunt_section_type,
tab_name = data.tab,
normal_list = api.jsonc.stringify(node_list.normal_list),
shunt_list = api.jsonc.stringify(node_list.shunt_list)
})
@@ -61,7 +61,6 @@ end
local types_dir = "/usr/lib/lua/luci/model/cbi/" .. api.appname .. "/client/type/" local types_dir = "/usr/lib/lua/luci/model/cbi/" .. api.appname .. "/client/type/"
s.val = {} s.val = {}
s.val["type"] = m:get(arg[1], "type") s.val["type"] = m:get(arg[1], "type")
s.val["protocol"] = m:get(arg[1], "protocol")
if luci.http.formvalue("cbi.submit") == "1" then if luci.http.formvalue("cbi.submit") == "1" then
local formvalue_type = luci.http.formvalue(formvalue_key .. "type") local formvalue_type = luci.http.formvalue(formvalue_key .. "type")
@@ -72,34 +71,6 @@ end
o = s:option(ListValue, "type", translate("Type")) o = s:option(ListValue, "type", translate("Type"))
if api.is_finded("ipt2socks") then
local type_name = "Socks"
s.fields["type"]:value(type_name, "Socks")
if s.val["type"] == type_name then
local s2 = NamedSection(m, arg[1], "server")
s2.type_name = type_name
s2.option_prefix = "socks_"
o = s2:option(ListValue, "del_protocol", " ") --始终隐藏,用于删除 protocol
o:depends({ __hide = "1" })
o.rewrite_option = "protocol"
o = s2:option(Value, "address", translate("Address (Support Domain Name)"))
o = s2:option(Value, "port", translate("Port"))
o.datatype = "port"
o = s2:option(Value, "username", translate("Username"))
o = s2:option(Value, "password", translate("Password"))
o.password = true
api.luci_types(s, s2)
end
end
local type_table = {} local type_table = {}
for filename in api.fs.dir(types_dir) do for filename in api.fs.dir(types_dir) do
table.insert(type_table, filename) table.insert(type_table, filename)
@@ -0,0 +1,35 @@
if not api.is_finded("ipt2socks") then
return
end
-- [[ Socks ]]
local m, s1 = ...
local type_name = "Socks"
s1.fields["type"]:value(type_name, "Socks")
if s1.val["type"] and s1.val["type"] ~= type_name then
return
end
local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name
s.option_prefix = "socks_"
api.set_type_cbi(s)
o = s:option(ListValue, "del_protocol", "¡¡") --ʼÖÕÒþ²Ø£¬ÓÃÓÚɾ³ý protocol
o:depends({ __hide = "1" })
o.rewrite_option = "protocol"
o = s:option(Value, "address", translate("Address (Support Domain Name)"))
o = s:option(Value, "port", translate("Port"))
o.datatype = "port"
o = s:option(Value, "username", translate("Username"))
o = s:option(Value, "password", translate("Password"))
o.password = true
api.type_cbi_section(s1, s)
@@ -17,15 +17,11 @@ if s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "singbox_" s.option_prefix = "singbox_"
api.set_type_cbi(s)
local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol")
if formvalue_proto then s1.val["protocol"] = formvalue_proto end
local arg_select_proto = luci.http.formvalue("select_proto") or ""
local ss_method_new_list = { local ss_method_new_list = {
"none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
@@ -72,24 +68,20 @@ end
o:value("_urltest", translate("URLTest")) o:value("_urltest", translate("URLTest"))
o:value("_shunt", translate("Shunt")) o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface")) o:value("_iface", translate("Custom Interface"))
function o.custom_cfgvalue(self, section)
if arg_select_proto ~= "" then local protocol_val = m:get(s.section, "protocol")
return arg_select_proto local formvalue_proto = s.fields["protocol"]:formvalue(s.section)
else if formvalue_proto then
return m:get(section, self.config_option) protocol_val = formvalue_proto
end
end end
local load_urltest_options = s1.val["protocol"] == "_urltest" or arg_select_proto == "_urltest" local load_urltest_options = protocol_val == "_urltest"
local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt" local load_shunt_options = protocol_val == "_shunt"
local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface" local load_iface_options = protocol_val == "_iface"
local load_normal_options = true local load_normal_options = true
if load_urltest_options or load_shunt_options or load_iface_options then if load_urltest_options or load_shunt_options or load_iface_options then
load_normal_options = nil load_normal_options = nil
end end
if not arg_select_proto:find("_") then
load_normal_options = true
end
local netdev_list = api.get_network_devices() local netdev_list = api.get_network_devices()
local node_list = api.get_node_list() local node_list = api.get_node_list()
@@ -115,11 +107,11 @@ if load_urltest_options then -- [[ URLTest Start ]]
end end
end end
-- 读取旧 DynamicList -- 读取旧 DynamicList
function o.custom_cfgvalue(self, section) function o.cfgvalue(self, section)
return table.concat(m:get(section, "urltest_node") or {}, " ") return table.concat(m:get(section, "urltest_node") or {}, " ")
end end
-- 写入保持 DynamicList -- 写入保持 DynamicList
function o.custom_write(self, section, value) function o.write(self, section, value)
local old = m:get(section, "urltest_node") or {} local old = m:get(section, "urltest_node") or {}
local new, set = {}, {} local new, set = {}, {}
for v in value:gmatch("%S+") do for v in value:gmatch("%S+") do
@@ -628,6 +620,7 @@ if singbox_tags:find("with_utls") then
o:depends({ protocol = "shadowsocks", tls = true }) o:depends({ protocol = "shadowsocks", tls = true })
o:depends({ protocol = "trojan", tls = true }) o:depends({ protocol = "trojan", tls = true })
o:depends({ protocol = "anytls", tls = true }) o:depends({ protocol = "anytls", tls = true })
o:depends({ protocol = "http", tls = true })
o = s:option(Value, "reality_publicKey", translate("Public Key")) o = s:option(Value, "reality_publicKey", translate("Public Key"))
o:depends({ reality = true }) o:depends({ reality = true })
@@ -935,7 +928,7 @@ if not load_shunt_options then
for k1, v1 in pairs(node_list) do for k1, v1 in pairs(node_list) do
if k1 ~= "shunt_list" and k1 ~= "iface_list" then if k1 ~= "shunt_list" and k1 ~= "iface_list" then
for i, v in ipairs(v1) do for i, v in ipairs(v1) do
if v.id ~= arg[1] then if v.id ~= s.section then
o1:value(v.id, v.remark) o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
if k1 == "normal_list" then if k1 == "normal_list" then
@@ -949,14 +942,15 @@ if not load_shunt_options then
end end
end end
api.luci_types(s1, s) api.type_cbi_section(s1, s)
if load_shunt_options then if load_shunt_options then
local current_node = m:get(arg[1]) or {}
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua") local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s1, { setfenv(shunt_lua, getfenv(1))(m, s1, {
node_id = arg[1], node = {
node = current_node, [".name"] = s.section,
type = type_name,
},
node_list = node_list, node_list = node_list,
}) })
end end
@@ -15,15 +15,11 @@ if s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "xray_" s.option_prefix = "xray_"
api.set_type_cbi(s)
local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol")
if formvalue_proto then s1.val["protocol"] = formvalue_proto end
local arg_select_proto = luci.http.formvalue("select_proto") or ""
local ss_method_list = { local ss_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
@@ -53,31 +49,26 @@ if api.compare_versions(xray_version, ">=", "1.8.12") then
end end
o:value("_shunt", translate("Shunt")) o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface")) o:value("_iface", translate("Custom Interface"))
function o.custom_cfgvalue(self, section) local protocol_val = m:get(s.section, "protocol")
if arg_select_proto ~= "" then local formvalue_proto = s.fields["protocol"]:formvalue(s.section)
return arg_select_proto if formvalue_proto then
else protocol_val = formvalue_proto
return m:get(section, self.config_option)
end
end end
local load_balancing_options = s1.val["protocol"] == "_balancing" or arg_select_proto == "_balancing" local load_balancing_options = protocol_val == "_balancing"
local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt" local load_shunt_options = protocol_val == "_shunt"
local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface" local load_iface_options = protocol_val == "_iface"
local load_normal_options = true local load_normal_options = true
if load_balancing_options or load_shunt_options or load_iface_options then if load_balancing_options or load_shunt_options or load_iface_options then
load_normal_options = nil load_normal_options = nil
end end
if not arg_select_proto:find("_") then
load_normal_options = true
end
local netdev_list = api.get_network_devices() local netdev_list = api.get_network_devices()
local node_list = api.get_node_list() local node_list = api.get_node_list()
local fallback_list = {} local fallback_list = {}
local is_balancer = nil local is_balancer = nil
for k, e in ipairs(node_list.balancing_list or {}) do for k, e in ipairs(node_list.balancing_list or {}) do
if e.id ~= arg[1] then if e.id ~= s.section then
fallback_list[#fallback_list + 1] = { fallback_list[#fallback_list + 1] = {
id = e["id"], id = e["id"],
remark = e["remark"], remark = e["remark"],
@@ -110,11 +101,11 @@ if load_balancing_options then -- [[ Load balancing Start ]]
end end
end end
-- 读取旧 DynamicList -- 读取旧 DynamicList
function o.custom_cfgvalue(self, section) function o.cfgvalue(self, section)
return table.concat(m:get(section, "balancing_node") or {}, " ") return table.concat(m:get(section, "balancing_node") or {}, " ")
end end
-- 写入保持 DynamicList -- 写入保持 DynamicList
function o.custom_write(self, section, value) function o.write(self, section, value)
local old = m:get(section, "balancing_node") or {} local old = m:get(section, "balancing_node") or {}
local new, set = {}, {} local new, set = {}, {}
for v in value:gmatch("%S+") do for v in value:gmatch("%S+") do
@@ -213,7 +204,7 @@ if load_balancing_options then -- [[ Load balancing Start ]]
local depth = get_fallback_depth(v.id) local depth = get_fallback_depth(v.id)
-- 超过最大套娃层数后,不允许继续选择 balancer -- 超过最大套娃层数后,不允许继续选择 balancer
if depth < MAX_FALLBACK_DEPTH if depth < MAX_FALLBACK_DEPTH
and not will_loop(arg[1], v.id) and not will_loop(s.section, v.id)
then then
o:value(v.id, v.remark) o:value(v.id, v.remark)
o.group[#o.group + 1] = (v.group and v.group ~= "") and v.group or translate("default") o.group[#o.group + 1] = (v.group and v.group ~= "") and v.group or translate("default")
@@ -410,6 +401,7 @@ o = s:option(Flag, "tls", translate("TLS"))
o.default = 0 o.default = 0
o:depends({ protocol = "vmess" }) o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" }) o:depends({ protocol = "vless" })
o:depends({ protocol = "http" })
o:depends({ protocol = "trojan" }) o:depends({ protocol = "trojan" })
o:depends({ protocol = "shadowsocks" }) o:depends({ protocol = "shadowsocks" })
@@ -678,13 +670,13 @@ o.validate = function(self, value)
if v then return v end if v then return v end
return nil, "XHTTP Extra " .. translate("Must be JSON text!") return nil, "XHTTP Extra " .. translate("Must be JSON text!")
end end
o.custom_cfgvalue = function(self, section, value) o.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
return api.base64Decode(raw) return api.base64Decode(raw)
end end
end end
o.custom_write = function(self, section, value) o.write = function(self, section, value)
m:set(section, "xhttp_extra", api.base64Encode(value) or "") m:set(section, "xhttp_extra", api.base64Encode(value) or "")
local success, data = pcall(api.jsonc.parse, value) local success, data = pcall(api.jsonc.parse, value)
if success and data then if success and data then
@@ -700,7 +692,7 @@ o.custom_write = function(self, section, value)
m:del(section, "download_address") m:del(section, "download_address")
end end
end end
o.custom_remove = function(self, section, value) o.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")
end end
@@ -728,6 +720,8 @@ o:depends({ protocol = "vless", transport = "raw" })
o:depends({ protocol = "vless", transport = "ws" }) o:depends({ protocol = "vless", transport = "ws" })
o:depends({ protocol = "vless", transport = "grpc" }) o:depends({ protocol = "vless", transport = "grpc" })
o:depends({ protocol = "vless", transport = "httpupgrade" }) o:depends({ protocol = "vless", transport = "httpupgrade" })
o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "shadowsocks" }) o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "trojan" }) o:depends({ protocol = "trojan" })
@@ -763,13 +757,13 @@ o.validate = function(self, value)
if v then return v end if v then return v end
return nil, "FinalMask " .. translate("Must be JSON text!") return nil, "FinalMask " .. translate("Must be JSON text!")
end end
o.custom_cfgvalue = function(self, section, value) o.cfgvalue = function(self, section, value)
local raw = m:get(section, "finalmask") local raw = m:get(section, "finalmask")
if raw then if raw then
return api.base64Decode(raw) return api.base64Decode(raw)
end end
end end
o.custom_write = function(self, section, value) o.write = function(self, section, value)
m:set(section, "finalmask", api.base64Encode(value) or "") m:set(section, "finalmask", api.base64Encode(value) or "")
end end
@@ -871,7 +865,7 @@ if not load_shunt_options then
for k1, v1 in pairs(node_list) do for k1, v1 in pairs(node_list) do
if k1 ~= "shunt_list" and k1 ~= "iface_list" then if k1 ~= "shunt_list" and k1 ~= "iface_list" then
for i, v in ipairs(v1) do for i, v in ipairs(v1) do
if v.id ~= arg[1] then if v.id ~= s.section then
o1:value(v.id, v.remark) o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
if k1 == "normal_list" then if k1 == "normal_list" then
@@ -885,14 +879,16 @@ if not load_shunt_options then
end end
end end
api.luci_types(s1, s) api.type_cbi_section(s1, s)
if load_shunt_options then if load_shunt_options then
local current_node = m:get(arg[1]) or {} local current_node = m:get(arg[1]) or {}
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua") local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s1, { setfenv(shunt_lua, getfenv(1))(m, s1, {
node_id = arg[1], node = {
node = current_node, [".name"] = s.section,
type = type_name,
},
node_list = node_list, node_list = node_list,
}) })
end end
@@ -12,9 +12,11 @@ if s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "ssrust_" s.option_prefix = "ssrust_"
api.set_type_cbi(s)
local ssrust_encrypt_method_list = { local ssrust_encrypt_method_list = {
"none", "plain", "none", "plain",
@@ -69,4 +71,4 @@ end
o = s:option(Value, "plugin_opts", translate("opts")) o = s:option(Value, "plugin_opts", translate("opts"))
o:depends({ plugin_enabled = true }) o:depends({ plugin_enabled = true })
api.luci_types(s1, s) api.type_cbi_section(s1, s)
@@ -12,9 +12,11 @@ if s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "ssr_" s.option_prefix = "ssr_"
api.set_type_cbi(s)
local ssr_encrypt_method_list = { local ssr_encrypt_method_list = {
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb", "none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
@@ -63,4 +65,4 @@ o.default = 300
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required")) o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0 o.default = 0
api.luci_types(s1, s) api.type_cbi_section(s1, s)
@@ -12,13 +12,11 @@ if s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "hysteria2_" s.option_prefix = "hysteria2_"
api.set_type_cbi(s)
local function _n(name)
return s.option_prefix .. name
end
o = s:option(ListValue, "protocol", translate("Protocol")) o = s:option(ListValue, "protocol", translate("Protocol"))
o:value("udp", "UDP") o:value("udp", "UDP")
@@ -32,22 +30,22 @@ o:depends({ realms = false })
o = s:option(Value, "hop", translate("Port hopping range")) o = s:option(Value, "hop", translate("Port hopping range"))
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).") o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ realms = false }) o:depends({ realms = false })
o = s:option(Value, "hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5.")) o = s:option(Value, "hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
o.datatype = "or(uinteger,portrange)" o.datatype = "or(uinteger,portrange)"
o.placeholder = "30" o.placeholder = "30"
o.default = "30" o.default = "30"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ realms = false }) o:depends({ realms = false })
o = s:option(Flag, "realms", translate("Realms")) o = s:option(Flag, "realms", translate("Realms"))
o.default = "0" o.default = "0"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ realms = "1" }) o:depends({ realms = "1" })
o.validate = function(self, value) o.validate = function(self, value)
value = api.trim(value) value = api.trim(value)
@@ -58,26 +56,26 @@ end
o = s:option(DynamicList, "realm_stun", translate("Realm STUN")) o = s:option(DynamicList, "realm_stun", translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ realms = "1" }) o:depends({ realms = "1" })
o = s:option(Flag, "realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success.")) o = s:option(Flag, "realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
o.default = "0" o.default = "0"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ realms = "1" }) o:depends({ realms = "1" })
o = s:option(Value, "auth_password", translate("Auth Password")) o = s:option(Value, "auth_password", translate("Auth Password"))
o.password = true o.password = true
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(ListValue, "obfs_type", translate("Obfs Type")) o = s:option(ListValue, "obfs_type", translate("Obfs Type"))
o:value("", translate("Disable")) o:value("", translate("Disable"))
o:value("salamander") o:value("salamander")
o:value("gecko") o:value("gecko")
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Value, "obfs_password", translate("Obfs Password")) o = s:option(Value, "obfs_password", translate("Obfs Password"))
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ obfs_type = "salamander" }) o:depends({ obfs_type = "salamander" })
o:depends({ obfs_type = "gecko" }) o:depends({ obfs_type = "gecko" })
@@ -86,14 +84,14 @@ o.datatype = "uinteger"
o.placeholder = "512" o.placeholder = "512"
o.default = "512" o.default = "512"
o:depends({ obfs_type = "gecko" }) o:depends({ obfs_type = "gecko" })
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
o.datatype = "uinteger" o.datatype = "uinteger"
o.placeholder = "1200" o.placeholder = "1200"
o.default = "1200" o.default = "1200"
o:depends({ obfs_type = "gecko" }) o:depends({ obfs_type = "gecko" })
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Flag, "fast_open", translate("Fast Open")) o = s:option(Flag, "fast_open", translate("Fast Open"))
o.default = "0" o.default = "0"
@@ -106,28 +104,28 @@ o.default = "0"
o = s:option(Value, "tls_pinSHA256", translate("PinSHA256"),translate("Certificate fingerprint")) o = s:option(Value, "tls_pinSHA256", translate("PinSHA256"),translate("Certificate fingerprint"))
o = s:option(Value, "up_mbps", translate("Max upload Mbps")) o = s:option(Value, "up_mbps", translate("Max upload Mbps"))
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Value, "down_mbps", translate("Max download Mbps")) o = s:option(Value, "down_mbps", translate("Max download Mbps"))
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Value, "recv_window", translate("QUIC stream receive window")) o = s:option(Value, "recv_window", translate("QUIC stream receive window"))
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Value, "recv_window_conn", translate("QUIC connection receive window")) o = s:option(Value, "recv_window_conn", translate("QUIC connection receive window"))
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Value, "idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)") o = s:option(Value, "idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
o.datatype = "range(4,120)" o.datatype = "range(4,120)"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Value, "keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)") o = s:option(Value, "keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
o.datatype = "range(2,60)" o.datatype = "range(2,60)"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Flag, "disable_mtu_discovery", translate("Disable MTU detection")) o = s:option(Flag, "disable_mtu_discovery", translate("Disable MTU detection"))
o.default = "0" o.default = "0"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Flag, "ech", translate("ECH")) o = s:option(Flag, "ech", translate("ECH"))
@@ -145,6 +143,6 @@ end
o = s:option(Flag, "lazy_start", translate("Lazy Start")) o = s:option(Flag, "lazy_start", translate("Lazy Start"))
o.default = "0" o.default = "0"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
api.luci_types(s1, s) api.type_cbi_section(s1, s)
@@ -12,9 +12,11 @@ if s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "naive_" s.option_prefix = "naive_"
api.set_type_cbi(s)
o = s:option(ListValue, "protocol", translate("Protocol")) o = s:option(ListValue, "protocol", translate("Protocol"))
o:value("https", translate("HTTPS")) o:value("https", translate("HTTPS"))
@@ -30,4 +32,4 @@ o = s:option(Value, "username", translate("Username"))
o = s:option(Value, "password", translate("Password")) o = s:option(Value, "password", translate("Password"))
o.password = true o.password = true
api.luci_types(s1, s) api.type_cbi_section(s1, s)
@@ -15,9 +15,11 @@ if s1.val["type"] and s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "socks_" s.option_prefix = "socks_"
api.set_type_cbi(s)
o = s:option(Value, "port", translate("Listen Port")) o = s:option(Value, "port", translate("Listen Port"))
o.datatype = "port" o.datatype = "port"
@@ -44,4 +46,4 @@ o:depends({ firewall_allow = true })
o = s:option(Flag, "log", translate("Log")) o = s:option(Flag, "log", translate("Log"))
o.default = "1" o.default = "1"
api.luci_types(s1, s) api.type_cbi_section(s1, s)
@@ -21,9 +21,11 @@ if s1.val["type"] and s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "singbox_" s.option_prefix = "singbox_"
api.set_type_cbi(s)
local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'") local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
@@ -48,13 +50,13 @@ o.validate = function(self, value)
if v then return v end if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!") return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end end
o.custom_cfgvalue = function(self, section, value) o.cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str") local config_str = m:get(section, "config_str")
if config_str then if config_str then
return api.base64Decode(config_str) return api.base64Decode(config_str)
end end
end end
o.custom_write = function(self, section, value) o.write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "") m:set(section, "config_str", api.base64Encode(value) or "")
end end
@@ -562,4 +564,4 @@ o:value("warn")
o:value("error") o:value("error")
o:depends({ log = true }) o:depends({ log = true })
api.luci_types(s1, s) api.type_cbi_section(s1, s)
@@ -19,9 +19,11 @@ if s1.val["type"] and s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "xray_" s.option_prefix = "xray_"
api.set_type_cbi(s)
local ss_method_list = { local ss_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
@@ -44,13 +46,13 @@ o.validate = function(self, value)
if v then return v end if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!") return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end end
o.custom_cfgvalue = function(self, section, value) o.cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str") local config_str = m:get(section, "config_str")
if config_str then if config_str then
return api.base64Decode(config_str) return api.base64Decode(config_str)
end end
end end
o.custom_write = function(self, section, value) o.write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "") m:set(section, "config_str", api.base64Encode(value) or "")
end end
@@ -204,13 +206,18 @@ o.validate = function(self, value, t)
end end
o:depends({ protocol = "vmess" }) o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" }) o:depends({ protocol = "vless" })
o:depends({ protocol = "http" })
o:depends({ protocol = "shadowsocks" }) o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "trojan" }) o:depends({ protocol = "trojan" })
-- [[ REALITY部分 ]] -- -- [[ REALITY部分 ]] --
o = s:option(Flag, "reality", translate("REALITY")) o = s:option(Flag, "reality", translate("REALITY"))
o.default = 0 o.default = 0
o:depends({ tls = true }) o:depends({ tls = true, transport = "raw" })
o:depends({ tls = true, transport = "ws" })
o:depends({ tls = true, transport = "grpc" })
o:depends({ tls = true, transport = "httpupgrade" })
o:depends({ tls = true, transport = "xhttp" })
o = s:option(Value, "reality_private_key", translate("Private Key")) o = s:option(Value, "reality_private_key", translate("Private Key"))
o:depends({ reality = true }) o:depends({ reality = true })
@@ -257,8 +264,8 @@ end
-- [[ TLS部分 ]] -- -- [[ TLS部分 ]] --
o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem" o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. s.section .. ".pem"
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end if o and o:formvalue(s.section) then o.default = o:formvalue(s.section) end
o:depends({ tls = true, reality = false }) o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2"}) o:depends({ protocol = "hysteria2"})
o.validate = function(self, value, t) o.validate = function(self, value, t)
@@ -273,8 +280,8 @@ o.validate = function(self, value, t)
end end
o = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key") o = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key" o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. s.section .. ".key"
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end if o and o:formvalue(s.section) then o.default = o:formvalue(s.section) end
o:depends({ tls = true, reality = false }) o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2"}) o:depends({ protocol = "hysteria2"})
o.validate = function(self, value, t) o.validate = function(self, value, t)
@@ -396,13 +403,13 @@ o:depends({ use_finalmask = true })
o.rows = 10 o.rows = 10
o.wrap = "off" o.wrap = "off"
o.datatype = "json" o.datatype = "json"
o.custom_cfgvalue = function(self, section, value) o.cfgvalue = function(self, section, value)
local raw = m:get(section, "finalmask") local raw = m:get(section, "finalmask")
if raw then if raw then
return api.base64Decode(raw) return api.base64Decode(raw)
end end
end end
o.custom_write = function(self, section, value) o.write = function(self, section, value)
m:set(section, "finalmask", api.base64Encode(value) or "") m:set(section, "finalmask", api.base64Encode(value) or "")
end end
@@ -549,4 +556,4 @@ o:value("warning")
o:value("error") o:value("error")
o:depends({ log = true }) o:depends({ log = true })
api.luci_types(s1, s) api.type_cbi_section(s1, s)
@@ -16,9 +16,11 @@ if s1.val["type"] and s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "ssrust_" s.option_prefix = "ssrust_"
api.set_type_cbi(s)
local ssrust_encrypt_method_list = { local ssrust_encrypt_method_list = {
"plain", "none", "plain", "none",
@@ -39,13 +41,13 @@ o.validate = function(self, value)
if v then return v end if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!") return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end end
o.custom_cfgvalue = function(self, section, value) o.cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str") local config_str = m:get(section, "config_str")
if config_str then if config_str then
return api.base64Decode(config_str) return api.base64Decode(config_str)
end end
end end
o.custom_write = function(self, section, value) o.write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "") m:set(section, "config_str", api.base64Encode(value) or "")
end end
@@ -88,4 +90,4 @@ o = s:option(Flag, "log", translate("Log"))
o.default = "1" o.default = "1"
o.rmempty = false o.rmempty = false
api.luci_types(s1, s) api.type_cbi_section(s1, s)
@@ -16,9 +16,11 @@ if s1.val["type"] and s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "ssr_" s.option_prefix = "ssr_"
api.set_type_cbi(s)
local ssr_encrypt_method_list = { local ssr_encrypt_method_list = {
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb", "none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
@@ -52,13 +54,13 @@ o.validate = function(self, value)
if v then return v end if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!") return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end end
o.custom_cfgvalue = function(self, section, value) o.cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str") local config_str = m:get(section, "config_str")
if config_str then if config_str then
return api.base64Decode(config_str) return api.base64Decode(config_str)
end end
end end
o.custom_write = function(self, section, value) o.write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "") m:set(section, "config_str", api.base64Encode(value) or "")
end end
@@ -119,4 +121,4 @@ o = s:option(Flag, "log", translate("Log"))
o.default = "1" o.default = "1"
o.rmempty = false o.rmempty = false
api.luci_types(s1, s) api.type_cbi_section(s1, s)
@@ -12,13 +12,11 @@ if s1.val["type"] and s1.val["type"] ~= type_name then
return return
end end
local s = NamedSection(m, arg[1], "server") local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name s.type_name = type_name
s.option_prefix = "hysteria2_" s.option_prefix = "hysteria2_"
api.set_type_cbi(s)
local function _n(name)
return s.option_prefix .. name
end
o = s:option(Flag, "custom", translate("Use Custom Config")) o = s:option(Flag, "custom", translate("Use Custom Config"))
@@ -33,13 +31,13 @@ o.validate = function(self, value)
if v then return v end if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!") return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end end
o.custom_cfgvalue = function(self, section, value) o.cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str") local config_str = m:get(section, "config_str")
if config_str then if config_str then
return api.base64Decode(config_str) return api.base64Decode(config_str)
end end
end end
o.custom_write = function(self, section, value) o.write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "") m:set(section, "config_str", api.base64Encode(value) or "")
end end
@@ -55,11 +53,11 @@ o:depends({ custom = false })
o = s:option(Flag, "realms", translate("Realms")) o = s:option(Flag, "realms", translate("Realms"))
o.default = "0" o.default = "0"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ custom = false }) o:depends({ custom = false })
o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ realms = "1" }) o:depends({ realms = "1" })
o.validate = function(self, value) o.validate = function(self, value)
value = api.trim(value) value = api.trim(value)
@@ -70,23 +68,23 @@ end
o = s:option(DynamicList, "realm_stun", translate("Realm STUN")) o = s:option(DynamicList, "realm_stun", translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ realms = "1" }) o:depends({ realms = "1" })
o = s:option(Flag, "realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success.")) o = s:option(Flag, "realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
o.default = "0" o.default = "0"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ realms = "1" }) o:depends({ realms = "1" })
o = s:option(ListValue, "obfs_type", translate("Obfs Type")) o = s:option(ListValue, "obfs_type", translate("Obfs Type"))
o:value("", translate("Disable")) o:value("", translate("Disable"))
o:value("salamander") o:value("salamander")
o:value("gecko") o:value("gecko")
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ custom = false }) o:depends({ custom = false })
o = s:option(Value, "obfs_password", translate("Obfs Password")) o = s:option(Value, "obfs_password", translate("Obfs Password"))
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ obfs_type = "salamander" }) o:depends({ obfs_type = "salamander" })
o:depends({ obfs_type = "gecko" }) o:depends({ obfs_type = "gecko" })
@@ -95,31 +93,31 @@ o.datatype = "uinteger"
o.placeholder = "512" o.placeholder = "512"
o.default = "512" o.default = "512"
o:depends({ obfs_type = "gecko" }) o:depends({ obfs_type = "gecko" })
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
o.datatype = "uinteger" o.datatype = "uinteger"
o.placeholder = "1200" o.placeholder = "1200"
o.default = "1200" o.default = "1200"
o:depends({ obfs_type = "gecko" }) o:depends({ obfs_type = "gecko" })
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o = s:option(Flag, "udp", translate("UDP")) o = s:option(Flag, "udp", translate("UDP"))
o.default = "1" o.default = "1"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ custom = false }) o:depends({ custom = false })
o = s:option(Value, "up_mbps", translate("Max upload Mbps")) o = s:option(Value, "up_mbps", translate("Max upload Mbps"))
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ custom = false }) o:depends({ custom = false })
o = s:option(Value, "down_mbps", translate("Max download Mbps")) o = s:option(Value, "down_mbps", translate("Max download Mbps"))
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ custom = false }) o:depends({ custom = false })
o = s:option(Flag, "ignoreClientBandwidth", translate("ignoreClientBandwidth")) o = s:option(Flag, "ignoreClientBandwidth", translate("ignoreClientBandwidth"))
o.default = "0" o.default = "0"
o.rewrite_option = _n(o.option) o.rewrite_option = o.option
o:depends({ custom = false }) o:depends({ custom = false })
o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
@@ -181,4 +179,4 @@ o = s:option(Flag, "log", translate("Log"))
o.default = "1" o.default = "1"
o.rmempty = false o.rmempty = false
api.luci_types(s1, s) api.type_cbi_section(s1, s)
+131 -115
View File
@@ -13,7 +13,7 @@ jsonc = require "luci.jsonc"
i18n = require "luci.i18n" i18n = require "luci.i18n"
appname = "passwall" appname = "passwall"
curl_args = { "-skfL", "--connect-timeout 3", "--retry 3" } curl_args = { "-skfL", "--connect-timeout 3", "--retry 3", "-H 'Accept: */*'" }
command_timeout = 300 command_timeout = 300
OPENWRT_ARCH = nil OPENWRT_ARCH = nil
DISTRIB_ARCH = nil DISTRIB_ARCH = nil
@@ -163,12 +163,16 @@ function sh_uci_commit(config)
exec_call(string.format("uci -q commit %s", config)) exec_call(string.format("uci -q commit %s", config))
end end
function del_cache_var(key)
sys.call(string.format('. /usr/share/passwall2/utils.sh ; del_cache_var "%s"', key))
end
function set_cache_var(key, val) function set_cache_var(key, val)
sys.call(string.format('. /usr/share/passwall/utils.sh ; set_cache_var %s "%s"', key, val)) sys.call(string.format('. /usr/share/passwall/utils.sh ; set_cache_var "%s" "%s"', key, val))
end end
function get_cache_var(key) function get_cache_var(key)
local val = sys.exec(string.format('. /usr/share/passwall/utils.sh ; echo -n $(get_cache_var %s)', key)) local val = sys.exec(string.format('. /usr/share/passwall/utils.sh ; echo -n $(get_cache_var "%s")', key))
if val == "" then val = nil end if val == "" then val = nil end
return val return val
end end
@@ -1378,11 +1382,22 @@ function to_move(app_name,file)
end end
function get_version() function get_version()
local version = sys.exec("opkg list-installed luci-app-passwall 2>/dev/null | awk '{print $3}'") local version
if not version or #version == 0 then local version_file = CACHE_PATH .. "/passwall_version"
version = sys.exec("apk list luci-app-passwall 2>/dev/null | awk '/installed/ {print $1}' | cut -d'-' -f4-") sys.call("mkdir -p " .. CACHE_PATH)
if fs.access(version_file) then
version = fs.readfile(version_file)
else
version = sys.exec("opkg list-installed luci-app-passwall 2>/dev/null | awk '{print $3}'")
if not version or version == "" then
version = sys.exec("apk list luci-app-passwall 2>/dev/null | awk '/installed/ {print $1}' | cut -d'-' -f4-")
end
version = (version or ""):match("^%s*(.-)%s*$")
if version ~= "" then
fs.writefile(version_file, version)
end
end end
return (version or ""):gsub("\n", ""):match("^([^-]+)") return version:match("^([^-]+)") or ""
end end
function to_check_self() function to_check_self()
@@ -1506,11 +1521,111 @@ function set_default_cbi()
return cbi.AbstractValue.write(self, section, new_val) return cbi.AbstractValue.write(self, section, new_val)
end end
end end
if true then
--HideValue
local HideValue = util.class(cbi.DummyValue)
function HideValue.__init__(self, ...)
cbi.DummyValue.__init__(self, ...)
self.template = self.map:template_path("/cbi/hidevalue")
self.value = "1"
end
cbi.HideValue = HideValue
end
end
function set_type_cbi(s)
local cbi = require "luci.cbi"
local s1 = s.parent
function s.option(s_self, class, option, ...)
local obj = class(s_self.map, s_self, option, ...)
obj.config_option = option
obj.option_prefix = s_self.option_prefix
obj.option = s_self.option_prefix .. option
obj.cfgvalue = function(self, section)
if self.rewrite_option then
return self.map:get(section, self.rewrite_option)
else
return self.map:get(section, self.config_option)
end
end
obj.write = function(self, section, value)
if s1.fields["type"]:formvalue(s_self.section) == s_self.type_name then
local new_val = value
if util.instanceof(self, cbi.DynamicList) then
local new_t = {}
if type(value) == "table" then
new_t = table_remove_duplicates(value)
else
new_t = { value }
end
if self.cast == "string" then
new_val = table.concat(new_t, " ")
else
new_val = new_t
end
end
if self.rewrite_option then
self.map:set(section, self.rewrite_option, new_val)
else
self.map:set(section, self.config_option, new_val)
end
end
end
obj.remove = function(self, section)
if s1.fields["type"]:formvalue(s_self.section) == s_self.type_name then
if self.rewrite_option then
self.map:del(section, self.rewrite_option)
else
self.map:del(section, self.config_option)
end
end
end
obj.deplist2json = function(self, section, deplist)
local deps, i, d = { }
if type(self.deps) == "table" then
if not next(self.deps) then
self:depends({ type = s.type_name })
end
local list = deplist or self.deps
for i, d in ipairs(list) do
if s.type_name and not d["type"] then
d["type"] = s.type_name
end
local a, k, v = { }
for k, v in pairs(d) do
if k:find("!", 1, true) then
a[k] = v
elseif k:find("^", 1, true) then
a[k:sub(2)] = v
elseif k:find(".", 1, true) then
a['cbid%s' % k] = v
elseif s_self.fields[k] then
a['cbid.%s.%s.%s' %{ self.config, section, s_self.fields[k].option }] = v
else
a['cbid.%s.%s.%s' %{ self.config, section, k }] = v
end
end
deps[#deps+1] = a
end
end
return util.serialize_json(deps)
end
s_self:append(obj)
s_self.fields[option] = obj
return obj
end
end
function type_cbi_section(s, s2)
for i, v in ipairs(s2.children) do
local o = s2.children[i]
s:append(o)
s.fields[o.option] = o
end
end end
function return_map(map) function return_map(map)
local cbi = require "luci.cbi" local cbi = require "luci.cbi"
local api = require "luci.passwall.api"
if true then if true then
-- header -- header
local header = cbi.Template(appname .. "/cbi/header") local header = cbi.Template(appname .. "/cbi/header")
@@ -1526,113 +1641,6 @@ function return_map(map)
return map return map
end end
function luci_types(s, s2)
local cbi = require "luci.cbi"
local m = s.map
local id = s2.section
local type_name = s2.type_name
local option_prefix = s2.option_prefix
local fv_type
local field_type = s.fields["type"]
if field_type then
fv_type = field_type:formvalue(id)
end
for i, v in ipairs(s2.children) do
local o = s2.children[i]
o.config_option = o.option
o.option_prefix = option_prefix
o.option = option_prefix .. o.option
if not o.not_rewrite then
o.cfgvalue = function(self, section)
-- Add a custom `custom_cfgvalue` attribute. If a custom `custom_cfgvalue` function exists, the custom `cfgvalue` logic will be used.
if self.custom_cfgvalue then
return self:custom_cfgvalue(section)
else
if self.rewrite_option then
return m:get(section, self.rewrite_option)
else
return m:get(section, self.config_option)
end
end
end
o.write = function(self, section, value)
if s.fields["type"]:formvalue(id) == type_name then
-- Add a custom `custom_write` attribute; if a custom `custom_write` function exists, then use the custom write logic.
if self.custom_write then
self:custom_write(section, value)
else
local new_val = value
if util.instanceof(self, cbi.DynamicList) then
local new_t = {}
if type(value) == "table" then
new_t = table_remove_duplicates(value)
else
new_t = { value }
end
if self.cast == "string" then
new_val = table.concat(new_t, " ")
else
new_val = new_t
end
end
if self.rewrite_option then
m:set(section, self.rewrite_option, new_val)
else
m:set(section, self.config_option, new_val)
end
end
end
end
o.remove = function(self, section)
if s.fields["type"]:formvalue(id) == type_name then
-- Add a custom `custom_remove` attribute; if a custom `custom_remove` function exists, use the custom remove logic.
if self.custom_remove then
self:custom_remove(section)
else
if self.rewrite_option then
m:del(section, self.rewrite_option)
else
m:del(section, self.config_option)
end
end
end
end
end
local deps = o.deps
if #deps > 0 then
local function process_deps(dep)
local rewrite_deps = {}
for k, v in pairs(dep) do
if k:find("!") then
rewrite_deps[k] = v
else
rewrite_deps[option_prefix .. k] = v
end
end
if not rewrite_deps['!reverse'] then
rewrite_deps["type"] = type_name
end
return rewrite_deps
end
for index, value in ipairs(deps) do
local rewrite_deps = process_deps(value)
if rewrite_deps then
deps[index] = rewrite_deps
end
end
else
o:depends({ type = type_name })
end
if fv_type and fv_type ~= type_name then
o.rmempty = true
end
s:append(o)
end
end
function get_std_domain(domain) function get_std_domain(domain)
domain = trim(domain) domain = trim(domain)
if domain == "" then return "" end if domain == "" then return "" end
@@ -2068,3 +2076,11 @@ function gen_wireguard_key()
} }
end end
end end
function get_socks_port_by_cache(node_id)
return get_cache_var("node_%s_socks_port" % { node_id })
end
function set_socks_port_to_cache(node_id, v)
set_cache_var("node_%s_socks_port" % { node_id }, v)
end
@@ -93,43 +93,64 @@ function gen_outbound(flag, node, tag, proxy_table)
end end
local remarks = node.remarks local remarks = node.remarks
local proxy_tag = nil local proxy_tag, fragment, record_fragment
local fragment = nil
local record_fragment = nil
local run_socks_instance = true
if proxy_table ~= nil and type(proxy_table) == "table" then if proxy_table ~= nil and type(proxy_table) == "table" then
proxy_tag = proxy_table.tag or nil proxy_tag = proxy_table.tag or nil
fragment = (proxy_table.fragment and node.protocol ~= "naive" and not node.hysteria2_realms) and true or nil fragment = (proxy_table.fragment and node.protocol ~= "naive" and not node.hysteria2_realms) and true or nil
record_fragment = (proxy_table.record_fragment and node.protocol ~= "naive" and not node.hysteria2_realms) and true or nil record_fragment = (proxy_table.record_fragment and node.protocol ~= "naive" and not node.hysteria2_realms) and true or nil
run_socks_instance = proxy_table.run_socks_instance
end end
if node.type ~= "sing-box" then if node.type ~= "sing-box" then
local relay_port = node.port if node.type == "Socks" then
local new_port = api.get_new_port() node.protocol = "socks"
local config_file = string.format("%s_%s_%s.json", flag, tag, new_port) proxy_tag = "socks <- " .. node_id
if tag and node_id and not tag:find(node_id) then else
config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port) local new_port
end local run_socks_instance = true
if run_socks_instance then if NO_RUN then
sys.call(string.format('/usr/share/passwall/app.sh run_socks "%s"> /dev/null', TMP_PORT = TMP_PORT and TMP_PORT + 1 or 3001
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s relay_port=%s", new_port = TMP_PORT
new_port, --flag run_socks_instance = nil
node_id, --node else
"127.0.0.1", --bind local relay_port = (proxy_tag and node.port) and tostring(node.port) or ""
new_port, --socks port if relay_port == "" then
config_file, --config file local cache = api.get_socks_port_by_cache(node_id)
(proxy_tag and relay_port) and tostring(relay_port) or "" --relay port if cache then
new_port = cache
run_socks_instance = nil
end
end
if run_socks_instance then
new_port = api.get_new_port()
local config_file = string.format("nodesocks_%s_%s.json", node_id, new_port)
if tag and node_id and not tag:find(node_id) then
config_file = string.format("nodesocks_%s_%s_%s.json", tag, node_id, new_port)
end
sys.call(string.format('/usr/share/passwall/app.sh run_socks "%s"> /dev/null',
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s relay_port=%s",
new_port, --flag
node_id, --node
"127.0.0.1", --bind
new_port, --socks port
config_file, --config file
relay_port --relay port
)
)
) )
) if relay_port == "" then
) api.set_socks_port_to_cache(node_id, new_port)
end
end
end
if new_port then
node = {
protocol = "socks",
address = "127.0.0.1",
port = new_port
}
proxy_tag = "socks <- " .. node_id
end
end end
node = {
protocol = "socks",
address = "127.0.0.1",
port = new_port
}
proxy_tag = "socks <- " .. node_id
else else
if proxy_tag then if proxy_tag then
node.detour = proxy_tag node.detour = proxy_tag
@@ -481,9 +502,9 @@ function gen_outbound(flag, node, tag, proxy_table)
obfs = node.hysteria_obfs, obfs = node.hysteria_obfs,
auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil, auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil, auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
recv_window_conn = tonumber(node.hysteria_recv_window_conn), --1.14 将变更为 stream_receive_window stream_receive_window = tonumber(node.hysteria_recv_window_conn),
recv_window = tonumber(node.hysteria_recv_window), --1.14 将变更为 connection_receive_window connection_receive_window = tonumber(node.hysteria_recv_window),
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false, --1.14 将变更为 disable_path_mtu_discovery disable_path_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
tls = tls tls = tls
} }
end end
@@ -910,10 +931,10 @@ function gen_config_server(node)
down_mbps = tonumber(node.hysteria_down_mbps), down_mbps = tonumber(node.hysteria_down_mbps),
obfs = node.hysteria_obfs, obfs = node.hysteria_obfs,
users = users, users = users,
recv_window_conn = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil, --1.14 to stream_receive_window stream_receive_window = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil,
recv_window_client = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil, --1.14 to connection_receive_window connection_receive_window = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil,
max_conn_client = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil, --1.14 to max_concurrent_streams max_concurrent_streams = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil,
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false, --1.14 to disable_path_mtu_discover disable_path_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
tls = tls tls = tls
} }
end end
@@ -1127,8 +1148,10 @@ function gen_config(var)
local direct_dns_query_strategy = var["direct_dns_query_strategy"] local direct_dns_query_strategy = var["direct_dns_query_strategy"]
local remote_dns_udp_server = var["remote_dns_udp_server"] local remote_dns_udp_server = var["remote_dns_udp_server"]
local remote_dns_udp_port = var["remote_dns_udp_port"] local remote_dns_udp_port = var["remote_dns_udp_port"]
local remote_dns_quic = var["remote_dns_quic"]
local remote_dns_tcp_server = var["remote_dns_tcp_server"] local remote_dns_tcp_server = var["remote_dns_tcp_server"]
local remote_dns_tcp_port = var["remote_dns_tcp_port"] local remote_dns_tcp_port = var["remote_dns_tcp_port"]
local remote_dns_tls = var["remote_dns_tls"]
local remote_dns_doh = var["remote_dns_doh"] local remote_dns_doh = var["remote_dns_doh"]
local remote_dns_http3 = var["remote_dns_http3"] local remote_dns_http3 = var["remote_dns_http3"]
local remote_dns_client_ip = var["remote_dns_client_ip"] local remote_dns_client_ip = var["remote_dns_client_ip"]
@@ -1138,10 +1161,11 @@ function gen_config(var)
local dns_cache = var["dns_cache"] local dns_cache = var["dns_cache"]
local dns_socks_address = var["dns_socks_address"] local dns_socks_address = var["dns_socks_address"]
local dns_socks_port = var["dns_socks_port"] local dns_socks_port = var["dns_socks_port"]
local no_run = var["no_run"]
local use_proxy_list = var["use_proxy_list"] local use_proxy_list = var["use_proxy_list"]
local use_gfw_list = var["use_gfw_list"] local use_gfw_list = var["use_gfw_list"]
local chn_list = var["chn_list"] local chn_list = var["chn_list"]
local run_in_global = var["run_in_global"]
NO_RUN = var["no_run"]
local dns_domain_rules = {} local dns_domain_rules = {}
local dns = nil local dns = nil
@@ -1196,7 +1220,7 @@ function gen_config(var)
format = format, format = format,
path = _type == "local" and w or nil, path = _type == "local" and w or nil,
url = _type == "remote" and w or nil, url = _type == "remote" and w or nil,
http_client = _type == "remote" and "remote_http_client" or nil, http_client = (_type == "remote" and version_ge_1_14_0) and "remote_http_client" or nil,
--update_interval = _type == "remote" and "1d" or nil, --update_interval = _type == "remote" and "1d" or nil,
} }
end end
@@ -1335,7 +1359,9 @@ function gen_config(var)
ut_nodes = _node.urltest_node ut_nodes = _node.urltest_node
end end
-- api.log(" - 加载 Sing-Box URLTest 节点【" .. (_node.remarks or "") .. "】,子节点数量:" .. #(ut_nodes or {})) if not NO_RUN and run_in_global then
api.log(" - 加载 Sing-Box URLTest 节点【" .. (_node.remarks or "") .. "】,子节点数量:" .. #(ut_nodes or {}))
end
local valid_nodes = {} local valid_nodes = {}
for i = 1, #(ut_nodes or {}) do for i = 1, #(ut_nodes or {}) do
@@ -1350,7 +1376,7 @@ function gen_config(var)
end end
end end
if is_new_ut_node then if is_new_ut_node then
local outboundTag = gen_outbound_get_tag(flag, ut_node_id, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) local outboundTag = gen_outbound_get_tag(flag, ut_node_id, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil })
if outboundTag then if outboundTag then
valid_nodes[#valid_nodes + 1] = outboundTag valid_nodes[#valid_nodes + 1] = outboundTag
end end
@@ -1453,7 +1479,6 @@ function gen_config(var)
to_node.port = new_port to_node.port = new_port
to_outbound = gen_outbound(node[".name"], to_node, tag, { to_outbound = gen_outbound(node[".name"], to_node, tag, {
tag = tag, tag = tag,
run_socks_instance = not no_run
}) })
else else
to_outbound = gen_outbound(node[".name"], to_node) to_outbound = gen_outbound(node[".name"], to_node)
@@ -1546,7 +1571,6 @@ function gen_config(var)
local proxy_table = { local proxy_table = {
fragment = singbox_settings.fragment == "1", fragment = singbox_settings.fragment == "1",
record_fragment = singbox_settings.record_fragment == "1", record_fragment = singbox_settings.record_fragment == "1",
run_socks_instance = not no_run,
} }
local preproxy_node_id = node[rule_name .. "_proxy_tag"] local preproxy_node_id = node[rule_name .. "_proxy_tag"]
if preproxy_node_id == _node_id then preproxy_node_id = nil end if preproxy_node_id == _node_id then preproxy_node_id = nil end
@@ -1830,7 +1854,6 @@ function gen_config(var)
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node or node_id, nil, { COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node or node_id, nil, {
fragment = singbox_settings.fragment == "1" or nil, fragment = singbox_settings.fragment == "1" or nil,
record_fragment = singbox_settings.record_fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil,
run_socks_instance = not no_run
}) })
end end
@@ -1883,13 +1906,19 @@ function gen_config(var)
remote_server.type = "udp" remote_server.type = "udp"
remote_server.server = remote_dns_udp_server remote_server.server = remote_dns_udp_server
remote_server.server_port = server_port remote_server.server_port = server_port
if remote_dns_quic then
remote_server.type = "quic"
remote_server.server_port = tonumber(remote_dns_udp_port) or 853
end
elseif remote_dns_tcp_server then elseif remote_dns_tcp_server then
local server_port = tonumber(remote_dns_tcp_port) or 53 local server_port = tonumber(remote_dns_tcp_port) or 53
remote_server.type = "tcp" remote_server.type = "tcp"
remote_server.server = remote_dns_tcp_server remote_server.server = remote_dns_tcp_server
remote_server.server_port = server_port remote_server.server_port = server_port
if remote_dns_tls then
remote_server.type = "tls"
remote_server.server_port = tonumber(remote_dns_tcp_port) or 853
end
elseif remote_dns_doh then elseif remote_dns_doh then
local _a = api.parseDoH(remote_dns_doh) local _a = api.parseDoH(remote_dns_doh)
if _a then if _a then
@@ -2208,14 +2237,10 @@ function gen_config(var)
}) })
else else
table.insert(dns.rules, 1, { table.insert(dns.rules, 1, {
action = "evaluate", preferred_by = "hosts",
action = "route",
server = "hosts" server = "hosts"
}) })
table.insert(dns.rules, 2, {
match_response = true,
ip_accept_any = true,
action = "respond"
})
end end
if COMMON.default_outbound_tag == "block" then if COMMON.default_outbound_tag == "block" then
@@ -2281,7 +2306,7 @@ function gen_config(var)
-- 实验性 -- 实验性
experimental = experimental, experimental = experimental,
-- HTTP Client -- HTTP Client
http_clients = http_clients http_clients = version_ge_1_14_0 and http_clients or nil,
} }
table.insert(outbounds, { table.insert(outbounds, {
type = "direct", type = "direct",
@@ -2289,7 +2314,7 @@ function gen_config(var)
routing_mark = 255, routing_mark = 255,
}) })
for index, value in ipairs(config.outbounds) do for index, value in ipairs(config.outbounds) do
if not value["_flag_proxy_tag"] and not value.detour and value["_id"] and value.server and (value.server_port or value.server_ports) and not no_run then if not value["_flag_proxy_tag"] and not value.detour and value["_id"] and value.server and (value.server_port or value.server_ports) and not NO_RUN then
sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list")) sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list"))
end end
if not value.detour and not value.bind_interface and value.server then if not value.detour and not value.bind_interface and value.server then
@@ -2437,7 +2462,7 @@ if arg[1] then
var = jsonc.parse(arg[2]) var = jsonc.parse(arg[2])
end end
print(func(var)) print(func(var))
if (next(GEO_VAR.SITE_TAGS) or next(GEO_VAR.IP_TAGS)) and not no_run then if (next(GEO_VAR.SITE_TAGS) or next(GEO_VAR.IP_TAGS)) and not NO_RUN then
convert_geofile() convert_geofile()
end end
end end
+91 -76
View File
@@ -12,7 +12,7 @@ local GLOBAL = {
local xray_version = api.get_app_version("xray") local xray_version = api.get_app_version("xray")
local xray_min_version = "26.3.27" local xray_min_version = "26.7.11"
local function get_domain_excluded() local function get_domain_excluded()
local path = string.format("/usr/share/%s/rules/domains_excluded", api.c_config) local path = string.format("/usr/share/%s/rules/domains_excluded", api.c_config)
@@ -43,55 +43,67 @@ function gen_outbound(flag, node, tag, proxy_table)
end end
local remarks = node.remarks local remarks = node.remarks
local proxy_tag = nil local proxy_tag, dialer_proxy_tag, fragment, noise
local fragment = nil
local noise = nil
local run_socks_instance = true
if proxy_table ~= nil and type(proxy_table) == "table" then if proxy_table ~= nil and type(proxy_table) == "table" then
proxy_tag = proxy_table.tag or nil proxy_tag = proxy_table.tag or nil
fragment = (proxy_table.fragment and not node.hysteria2_realms) and true or nil fragment = (proxy_table.fragment and not node.hysteria2_realms) and true or nil
noise = (proxy_table.noise and not node.hysteria2_realms) and true or nil noise = (proxy_table.noise and not node.hysteria2_realms) and true or nil
run_socks_instance = proxy_table.run_socks_instance
end end
if node.type ~= "Xray" then if node.type ~= "Xray" then
if node.type == "Socks" then if node.type == "Socks" then
node.protocol = "socks" node.protocol = "socks"
node.transport = "tcp" node.transport = "raw"
else else
local relay_port = node.port local new_port
local new_port = api.get_new_port() local run_socks_instance = true
local config_file = string.format("%s_%s_%s.json", flag, tag, new_port) if NO_RUN then
if tag and node_id and not tag:find(node_id) then TMP_PORT = TMP_PORT and TMP_PORT + 1 or 3001
config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port) new_port = TMP_PORT
end run_socks_instance = nil
if run_socks_instance then else
sys.call(string.format('/usr/share/passwall/app.sh run_socks "%s"> /dev/null', local relay_port = (proxy_tag and node.port) and tostring(node.port) or ""
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s relay_port=%s", if relay_port == "" then
new_port, --flag local cache = api.get_socks_port_by_cache(node_id)
node_id, --node if cache then
"127.0.0.1", --bind new_port = cache
new_port, --socks port run_socks_instance = nil
config_file, --config file end
(proxy_tag and relay_port) and tostring(relay_port) or "" --relay port end
if run_socks_instance then
new_port = api.get_new_port()
local config_file = string.format("nodesocks_%s_%s.json", node_id, new_port)
if tag and node_id and not tag:find(node_id) then
config_file = string.format("nodesocks_%s_%s_%s.json", tag, node_id, new_port)
end
sys.call(string.format('/usr/share/passwall/app.sh run_socks "%s"> /dev/null',
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s relay_port=%s",
new_port, --flag
node_id, --node
"127.0.0.1", --bind
new_port, --socks port
config_file, --config file
relay_port --relay port
)
)
) )
)) if relay_port == "" then
api.set_socks_port_to_cache(node_id, new_port)
end
end
end
if new_port then
node = {}
node.protocol = "socks"
node.transport = "raw"
node.address = "127.0.0.1"
node.port = new_port
end end
node = {}
node.protocol = "socks"
node.transport = "tcp"
node.address = "127.0.0.1"
node.port = new_port
end end
node.stream_security = "none" node.stream_security = "none"
proxy_tag = "socks <- " .. node_id proxy_tag = "socks <- " .. node_id
else else
if proxy_tag then dialer_proxy_tag = proxy_tag
node.proxySettings = {
tag = proxy_tag,
transportLayer = true
}
end
end end
if node.type == "Xray" then if node.type == "Xray" then
@@ -124,6 +136,10 @@ function gen_outbound(flag, node, tag, proxy_table)
node.stream_security = "tls" node.stream_security = "tls"
end end
if node.protocol == "http" and node.stream_security == "tls" then
node.transport = "raw"
end
if remarks then if remarks then
tag = tag .. ":" .. remarks tag = tag .. ":" .. remarks
end end
@@ -135,7 +151,6 @@ function gen_outbound(flag, node, tag, proxy_table)
_flag = flag, _flag = flag,
_flag_proxy_tag = proxy_tag, _flag_proxy_tag = proxy_tag,
tag = tag, tag = tag,
proxySettings = node.proxySettings or nil,
protocol = node.protocol, protocol = node.protocol,
mux = { mux = {
enabled = (node.mux == "1") and true or false, enabled = (node.mux == "1") and true or false,
@@ -143,7 +158,7 @@ function gen_outbound(flag, node, tag, proxy_table)
xudpConcurrency = (node.mux == "1" and ((node.xudp_concurrency) and tonumber(node.xudp_concurrency) or 8)) or nil xudpConcurrency = (node.mux == "1" and ((node.xudp_concurrency) and tonumber(node.xudp_concurrency) or 8)) or nil
} or nil, } or nil,
-- 底层传输配置 -- 底层传输配置
streamSettings = (node.streamSettings or node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan" or node.protocol == "hysteria") and { streamSettings = (node.streamSettings or dialer_proxy_tag or node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan" or node.protocol == "hysteria" or node.protocol == "http") and {
sockopt = { sockopt = {
mark = 255, mark = 255,
domainStrategy = node.domain_strategy or "UseIP", domainStrategy = node.domain_strategy or "UseIP",
@@ -154,9 +169,10 @@ function gen_outbound(flag, node, tag, proxy_table)
PrioritizeIPv6 = false, PrioritizeIPv6 = false,
Interleave = 1, Interleave = 1,
MaxConcurrentTry = 4 MaxConcurrentTry = 4
} or nil } or nil,
dialerProxy = dialer_proxy_tag,
}, },
[(api.compare_versions(xray_version, "<", "26.7.11")) and "network" or "method"] = node.transport, -- Todo: Remove version check and "network" method = node.transport,
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,
@@ -548,7 +564,7 @@ function gen_config_server(node)
allowTransparent = false, allowTransparent = false,
users = users users = users
} }
node.transport = "tcp" node.transport = "raw"
node.tcp_guise = "none" node.tcp_guise = "none"
elseif node.protocol == "shadowsocks" then elseif node.protocol == "shadowsocks" then
settings = { settings = {
@@ -675,7 +691,7 @@ function gen_config_server(node)
protocol = node.protocol, protocol = node.protocol,
settings = settings, settings = settings,
streamSettings = { streamSettings = {
[(api.compare_versions(xray_version, "<", "26.7.11")) and "network" or "method"] = node.transport, -- Todo: Remove version check and "network" method = node.transport,
security = "none", security = "none",
tlsSettings = ("1" == node.tls) and { tlsSettings = ("1" == node.tls) and {
disableSystemRoot = false, disableSystemRoot = false,
@@ -857,9 +873,6 @@ function gen_config_server(node)
config.outbounds[index][k] = nil config.outbounds[index][k] = nil
end end
end end
if value.protocol == "freedom" and api.compare_versions(xray_version, "<", "26.5.3") then -- Todo is to remove it
value.settings = nil
end
end end
return config return config
@@ -897,10 +910,11 @@ function gen_config(var)
local dns_socks_address = var["dns_socks_address"] local dns_socks_address = var["dns_socks_address"]
local dns_socks_port = var["dns_socks_port"] local dns_socks_port = var["dns_socks_port"]
local loglevel = var["loglevel"] or "warning" local loglevel = var["loglevel"] or "warning"
local no_run = var["no_run"]
local use_proxy_list = var["use_proxy_list"] local use_proxy_list = var["use_proxy_list"]
local use_gfw_list = var["use_gfw_list"] local use_gfw_list = var["use_gfw_list"]
local chn_list = var["chn_list"] local chn_list = var["chn_list"]
local run_in_global = var["run_in_global"]
NO_RUN = var["no_run"]
local dns_domain_rules = {} local dns_domain_rules = {}
local dns = nil local dns = nil
@@ -1075,7 +1089,9 @@ function gen_config(var)
blc_nodes = _node.balancing_node blc_nodes = _node.balancing_node
end end
-- api.log(" - 加载 Xray 负载均衡 节点【" .. (_node.remarks or "") .. "】,子节点数量:" .. #(blc_nodes or {})) if not NO_RUN and run_in_global then
api.log(" - 加载 Xray 负载均衡 节点【" .. (_node.remarks or "") .. "】,子节点数量:" .. #(blc_nodes or {}))
end
local valid_nodes = {} local valid_nodes = {}
for i = 1, #(blc_nodes or {}) do for i = 1, #(blc_nodes or {}) do
@@ -1090,7 +1106,7 @@ function gen_config(var)
end end
end end
if is_new_blc_node then if is_new_blc_node then
local outboundTag = gen_outbound_get_tag(flag, blc_node_id, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run }) local outboundTag = gen_outbound_get_tag(flag, blc_node_id, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil })
if outboundTag then if outboundTag then
valid_nodes[#valid_nodes + 1] = outboundTag valid_nodes[#valid_nodes + 1] = outboundTag
end end
@@ -1119,7 +1135,7 @@ function gen_config(var)
local fallback_node = get_node_by_id(fallback_node_id) local fallback_node = get_node_by_id(fallback_node_id)
if fallback_node then if fallback_node then
if fallback_node.protocol ~= "_balancing" then if fallback_node.protocol ~= "_balancing" then
local outboundTag = gen_outbound_get_tag(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run }) local outboundTag = gen_outbound_get_tag(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil })
if outboundTag then if outboundTag then
fallback_node_tag = outboundTag fallback_node_tag = outboundTag
end end
@@ -1209,10 +1225,9 @@ function gen_config(var)
end end
if preproxy_outbound then if preproxy_outbound then
outbound.tag = preproxy_outbound.tag .. " -> " .. outbound.tag outbound.tag = preproxy_outbound.tag .. " -> " .. outbound.tag
outbound.proxySettings = { outbound.streamSettings = outbound.streamSettings or {}
tag = preproxy_outbound.tag, outbound.streamSettings.sockopt = outbound.streamSettings.sockopt or {}
transportLayer = true outbound.streamSettings.sockopt.dialerProxy = preproxy_outbound.tag
}
if not exist then if not exist then
last_insert_outbound = preproxy_outbound last_insert_outbound = preproxy_outbound
end end
@@ -1252,7 +1267,6 @@ function gen_config(var)
}) })
to_outbound = gen_outbound(node[".name"], to_node, to_node[".name"], { to_outbound = gen_outbound(node[".name"], to_node, to_node[".name"], {
tag = to_node[".name"], tag = to_node[".name"],
run_socks_instance = not no_run
}) })
else else
to_outbound = gen_outbound(node[".name"], to_node) to_outbound = gen_outbound(node[".name"], to_node)
@@ -1260,10 +1274,9 @@ function gen_config(var)
if to_outbound then if to_outbound then
to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag
if to_node.type == "Xray" then if to_node.type == "Xray" then
to_outbound.proxySettings = { to_outbound.streamSettings = to_outbound.streamSettings or {}
tag = outbound.tag, to_outbound.streamSettings.sockopt = to_outbound.streamSettings.sockopt or {}
transportLayer = true to_outbound.streamSettings.sockopt.dialerProxy = outbound.tag
}
end end
table.insert(outbounds_table, to_outbound) table.insert(outbounds_table, to_outbound)
default_outTag = to_outbound.tag default_outTag = to_outbound.tag
@@ -1318,9 +1331,9 @@ function gen_config(var)
interface = node.iface interface = node.iface
} }
}, },
settings = (api.compare_versions(xray_version, ">", "26.4.25")) and { -- Todo: Remove version check settings = {
finalRules = {{ action = "allow" }} finalRules = {{ action = "allow" }}
} or nil }
} }
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface)) sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
end end
@@ -1361,7 +1374,6 @@ function gen_config(var)
local proxy_table = { local proxy_table = {
fragment = xray_settings.fragment == "1", fragment = xray_settings.fragment == "1",
noise = xray_settings.noise == "1", noise = xray_settings.noise == "1",
run_socks_instance = not no_run,
} }
local preproxy_node_id = node[rule_name .. "_proxy_tag"] local preproxy_node_id = node[rule_name .. "_proxy_tag"]
if preproxy_node_id == _node_id then preproxy_node_id = nil end if preproxy_node_id == _node_id then preproxy_node_id = nil end
@@ -1563,7 +1575,6 @@ function gen_config(var)
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node or node_id, nil, { COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node or node_id, nil, {
fragment = xray_settings.fragment == "1" or nil, fragment = xray_settings.fragment == "1" or nil,
noise = xray_settings.noise == "1" or nil, noise = xray_settings.noise == "1" or nil,
run_socks_instance = not no_run
}) })
if COMMON.default_outbound_tag then if COMMON.default_outbound_tag then
routing = { routing = {
@@ -1800,15 +1811,14 @@ function gen_config(var)
remote_dns_outbound = { remote_dns_outbound = {
tag = "dns-out", tag = "dns-out",
protocol = "dns", protocol = "dns",
proxySettings = dns_outbound_tag and { streamSettings = dns_outbound_tag and {
tag = (dns_outbound_tag ~= "blackhole") and dns_outbound_tag or "direct" sockopt = { dialerProxy = (dns_outbound_tag ~= "blackhole") and dns_outbound_tag or "direct" }
} or nil, } or nil,
settings = { settings = {
address = (chn_list ~= "proxy") and "8.8.8.8" or "223.5.5.5", rewriteAddress = (chn_list ~= "proxy") and "8.8.8.8" or "223.5.5.5",
port = 53, rewritePort = 53,
network = "tcp", rewriteNetwork = "tcp",
nonIPQuery = (api.compare_versions(xray_version, "<", "26.4.25")) and "reject" or nil, -- Todo is to remove it rules = {}
rules = (api.compare_versions(xray_version, ">", "26.4.17")) and {} or nil
} }
} }
@@ -2033,12 +2043,12 @@ function gen_config(var)
protocol = "freedom", protocol = "freedom",
tag = "direct", tag = "direct",
settings = { settings = {
domainStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP", finalRules = {{ action = "allow" }}
finalRules = (api.compare_versions(xray_version, ">", "26.4.25")) and {{ action = "allow" }} or nil -- Todo: Remove version check
}, },
streamSettings = { streamSettings = {
sockopt = { sockopt = {
mark = 255 mark = 255,
domainStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP"
} }
} }
} }
@@ -2060,7 +2070,7 @@ function gen_config(var)
for index, value in ipairs(config.outbounds) do for index, value in ipairs(config.outbounds) do
local pt = value.protocol local pt = value.protocol
local exclude = { blackhole=1, dns=1, freedom=1, loopback=1 } local exclude = { blackhole=1, dns=1, freedom=1, loopback=1 }
if not value["_flag_proxy_tag"] and value["_id"] and pt and not exclude[pt] and not no_run then if not value["_flag_proxy_tag"] and value["_id"] and pt and not exclude[pt] and not NO_RUN then
sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list")) sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list"))
end end
for k, v in pairs(config.outbounds[index]) do for k, v in pairs(config.outbounds[index]) do
@@ -2138,7 +2148,7 @@ function gen_proto_config(var)
local outbound = { local outbound = {
protocol = server_proto, protocol = server_proto,
streamSettings = { streamSettings = {
network = "tcp", method = "raw",
security = "none" security = "none"
}, },
settings = { settings = {
@@ -2163,10 +2173,12 @@ function gen_proto_config(var)
table.insert(outbounds, { table.insert(outbounds, {
protocol = "freedom", protocol = "freedom",
tag = "direct", tag = "direct",
settings = (api.compare_versions(xray_version, ">", "26.4.25")) and { -- Todo: Remove version check settings = {
finalRules = {{ action = "allow" }} finalRules = {{ action = "allow" }}
} or nil, },
sockopt = {mark = 255} streamSettings = {
sockopt = {mark = 255}
}
}) })
local config = { local config = {
@@ -2178,7 +2190,10 @@ function gen_proto_config(var)
-- 传出连接 -- 传出连接
outbounds = outbounds, outbounds = outbounds,
-- 路由 -- 路由
routing = routing routing = routing,
version = {
min = xray_min_version
}
} }
return jsonc.stringify(config, 1) return jsonc.stringify(config, 1)
end end
@@ -1,121 +0,0 @@
<%
local map = self.map
local api = map.api
local sid = self.section
-%>
<script type="text/javascript">
//<![CDATA[
function to_edit_node(btn) {
if (!btn) return;
const idReg = /^cbid\..*\..*node$/;
let hidden_select = null;
const selects = document.querySelectorAll('select[id^="cbid."][id*="."][id$="node"]');
for (const sel of selects) {
if ( idReg.test(sel.id) && getComputedStyle(sel).display === "none" && (sel.compareDocumentPosition(btn) & Node.DOCUMENT_POSITION_FOLLOWING)) {
hidden_select = sel;
}
}
if (!hidden_select) return;
let node_select_value = hidden_select?.options[0]?.value;
if (!node_select_value || node_select_value.indexOf("tcp") === 0) {
return;
}
let to_url = '<%=api.url("node_config")%>/' + node_select_value;
if (node_select_value.indexOf("socks_") === 0) {
to_url = '<%=api.url("socks_config")%>/' + node_select_value;
}
location.href = to_url;
}
document.addEventListener('DOMContentLoaded', function () {
function go() {
const node = document.querySelector(".cbi-section-node");
if (!node) return false;
let changed = false;
const all_node = node.querySelectorAll("[id]");
const reg1 = /^cbid\..*node\.main$/;
for (const el of all_node) {
if (!reg1.test(el.id)) continue;
if (el.querySelector(".node-actions")) continue;
const cbid = el.id.replace(/\.main$/, "");
const hidden_select = document.getElementById(cbid);
const node_select_value = hidden_select?.options[0]?.value;
if (!node_select_value) {
continue;
}
let html = '<a href="#" onclick="return to_edit_node(this);"><%:Edit%></a>';
const m = cbid.match(/\.node$/);
if (m) {
html += '<a href="#" onclick="window.open(\'' + '<%=api.url("get_redir_log") .. "?id=" .. sid%>' + '\', \'_blank\')"><%:Log%></a>';
}
html = '<div class="node-actions" style="display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; margin-left:4px;">' + html + '</div>'
el.insertAdjacentHTML("beforeend", html);
changed = true;
}
return changed;
}
const target = document.querySelector('form') || document.body;
const observer = new MutationObserver(() => go() ? observer.disconnect() : 0);
observer.observe(target, { childList: true, subtree: true });
const timer = setInterval(() => go() ? (clearInterval(timer), observer.disconnect()) : 0, 300);
setTimeout(() => { clearInterval(timer); observer.disconnect(); }, 5000);
});
(function () {
const startTime = Date.now();
const TIMEOUT = 3000;
const waitForDnsSelect = () => {
const dns_select = document.querySelector("select[id*='dns_shunt']");
if (dns_select) {
initDnsSelect(dns_select);
return;
}
if (Date.now() - startTime >= TIMEOUT) {
return;
}
requestAnimationFrame(waitForDnsSelect);
};
waitForDnsSelect();
const initDnsSelect = (dns_select) => {
if (dns_select.value === "chinadns-ng") {
addLogLink(dns_select);
}
if (dns_select._dnsLogBinded) return;
dns_select._dnsLogBinded = true;
dns_select.addEventListener("change", () => {
const existingLogLink = dns_select.parentElement.querySelector("a.dns-log-link");
if (existingLogLink) {
existingLogLink.remove();
}
if (dns_select.value === "chinadns-ng") {
addLogLink(dns_select);
}
});
};
const addLogLink = (select) => {
if (select.parentElement.querySelector("a.dns-log-link")) {
return;
}
const logLink = document.createElement("a");
logLink.innerHTML = "<%:Log%>";
logLink.href = "#";
logLink.className = "dns-log-link";
logLink.style.marginLeft = "10px";
logLink.setAttribute("onclick", "window.open('" + '<%=api.url("get_chinadns_log") .. "?flag=" .. sid%>' + "', '_blank')");
select.insertAdjacentElement("afterend", logLink);
};
})();
//]]>
</script>
@@ -65,13 +65,13 @@ window.dl_dropdown_data["<%=cbid%>"] = <%=json.stringify(dropdown_data)%>;
</script> </script>
<div id="<%=cbid%>.main" class="dl-dropdown-container"> <div id="<%=cbid%>.main" class="dl-dropdown-container">
<select id="<%=cbid%>.ref" class="cbi-input-select" style="display:none !important;"> <div style="display:none !important;">
<option value>placeholder</option> <select id="<%=cbid%>.ref" class="cbi-input-select" style="display:none !important;">
</select> <option value>placeholder</option>
</select>
</div>
<!-- 已选节点显示区 --> <!-- 已选节点显示区 -->
<div id="<%=cbid%>.selected" class="dl-selected-list"></div> <div id="<%=cbid%>.selected" class="dl-selected-list"></div>
<!-- 模拟 listvalue 控件外观 --> <!-- 模拟 listvalue 控件外观 -->
<div class="cbi-input-value cbi-input-select dl-dropdown-display" id="<%=cbid%>.display" tabindex="0"> <div class="cbi-input-value cbi-input-select dl-dropdown-display" id="<%=cbid%>.display" tabindex="0">
<span id="<%=cbid%>.label" class="dl-dropdown-label"> <span id="<%=cbid%>.label" class="dl-dropdown-label">
@@ -90,9 +90,11 @@ window.lv_dropdown_data["<%=cbid%>"] = <%=json.stringify(dropdown_data)%>;
<div id="<%=cbid%>.main" class="lv-dropdown-container"> <div id="<%=cbid%>.main" class="lv-dropdown-container">
<!-- 隐藏 select(保存实际配置值) --> <!-- 隐藏 select(保存实际配置值) -->
<select id="<%=cbid%>" name="<%=cbid%>" class="cbi-input-select" data-update="change" style="display:none !important;"> <div style="display:none !important;">
<option value="<%=current_key%>" selected="selected">placeholder</option> <select id="<%=cbid%>" name="<%=cbid%>" class="cbi-input-select" data-update="change" style="display:none !important;">
</select> <option value="<%=current_key%>" selected="selected">placeholder</option>
</select>
</div>
<!-- 模拟 ListValue 控件外观 --> <!-- 模拟 ListValue 控件外观 -->
<div class="cbi-input-value cbi-input-select lv-dropdown-display" id="<%=cbid%>.display" tabindex="0"> <div class="cbi-input-value cbi-input-select lv-dropdown-display" id="<%=cbid%>.display" tabindex="0">
<span id="<%=cbid%>.label" class="lv-dropdown-label" title="<%=pcdata(current_label)%>"> <span id="<%=cbid%>.label" class="lv-dropdown-label" title="<%=pcdata(current_label)%>">

Some files were not shown because too many files have changed in this diff Show More