⛄ Sync 2026-11-01 05:43:29

This commit is contained in:
github-actions[bot] committed 2026-11-01 05:43:29 +08:00
1 parent fae68afc83
commit d1e51134a0
187 files changed
+3752 -2114

No files matched your search

+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=dae PKG_NAME:=dae
PKG_VERSION:=2026.10.18 PKG_VERSION:=2026.10.30
PKG_RELEASE:=52 PKG_RELEASE:=53
PKG_SOURCE:=dae-src-2026.10.18-32ed09f0d784.tar.gz PKG_SOURCE:=dae-src-2026.10.30-55c59e68c059.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:=skip PKG_HASH:=skip
+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=daed PKG_NAME:=daed
PKG_VERSION:=2026.10.18 PKG_VERSION:=2026.10.30
PKG_RELEASE:=64 PKG_RELEASE:=65
PKG_SOURCE:=daed-src-2026.10.18-97c371b47eb5.tar.gz PKG_SOURCE:=daed-src-2026.10.30-1850c01d032c.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:=skip PKG_HASH:=skip
+1 -1
View File
@@ -3,7 +3,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=dockerd PKG_NAME:=dockerd
PKG_FLAGS:=hold PKG_FLAGS:=hold
PKG_VERSION:=27.3.1 PKG_VERSION:=27.3.1
PKG_RELEASE:=6 PKG_RELEASE:=8
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_LICENSE_FILES:=LICENSE PKG_LICENSE_FILES:=LICENSE
+47 -3
View File
@@ -223,6 +223,8 @@ start_service() {
procd_set_param command /usr/bin/dockerd --config-file="${DOCKERD_CONF}" procd_set_param command /usr/bin/dockerd --config-file="${DOCKERD_CONF}"
fi fi
procd_set_param limits nofile="${nofile} ${nofile}" procd_set_param limits nofile="${nofile} ${nofile}"
# Give dockerd the time to stop the containers before it gets killed
procd_set_param term_timeout 300
procd_close_instance procd_close_instance
} }
@@ -286,8 +288,50 @@ iptables_add_blocking_rule() {
config_list_foreach "${cfg}" blocked_interfaces handle_iptables_rule "${device}" "${extra_iptables_args}" config_list_foreach "${cfg}" blocked_interfaces handle_iptables_rule "${device}" "${extra_iptables_args}"
} }
# Read procd's notifications until one contains the given text, or none comes for the given seconds
procd_notification() {
local line
while read -r -t "${2}" line <&3; do
case "${line}" in
*"${1}"*) return 0 ;;
esac
done
return 1
}
stop_service() { stop_service() {
if /etc/init.d/dockerd running; then local i=0
service_stop "/usr/bin/dockerd"
fi procd_running dockerd || return 0
# Subscribe to procd's notifications before it stops dockerd, to learn when it is done.
# The subscriber leaves procd's lock (fd 1000) to this script, which releases it on exit.
STOP_EVENTS="/var/run/dockerd.stop.$$"
mkfifo "${STOP_EVENTS}" || return 0
ubus -t 330 subscribe service > "${STOP_EVENTS}" 1000>&- &
STOP_SUBSCRIBER=$!
exec 3< "${STOP_EVENTS}"
# The subscription takes effect a moment later, and a stop notified before it would be missed:
# wait until procd passes on an event sent to itself
while [ "${i}" -lt 10 ]; do
ubus call service event '{ "type": "dockerd.stopping", "data": {} }'
if procd_notification '"type":"dockerd.stopping"' 1; then
STOP_SUBSCRIBED=1
return 0
fi
i=$((i + 1))
done
}
service_stopped() {
[ -n "${STOP_EVENTS}" ] || return 0
# Wait for dockerd, which exits after stopping the containers, so that a shutdown unmounts the data root after them
[ -n "${STOP_SUBSCRIBED}" ] && procd_notification '"service.stop": {"service":"dockerd"}' 330
exec 3<&-
# A restart ignores SIGTERM while it stops dockerd, and so does the subscriber it started
kill -KILL "${STOP_SUBSCRIBER}" 2> /dev/null
rm -f "${STOP_EVENTS}"
} }
+2 -2
View File
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=gately PKG_NAME:=gately
PKG_VERSION:=0.6.1.1 PKG_VERSION:=0.6.2
PKG_RELEASE:=19 PKG_RELEASE:=20
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/wangwanjie/gately/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/wangwanjie/gately/tar.gz/v$(PKG_VERSION)?
+2 -2
View File
@@ -6,8 +6,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=gowebdav PKG_NAME:=gowebdav
PKG_VERSION:=5.17.2 PKG_VERSION:=5.17.3
PKG_RELEASE:=27 PKG_RELEASE:=28
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
+2 -2
View File
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=grpcurl PKG_NAME:=grpcurl
PKG_VERSION:=1.9.4 PKG_VERSION:=1.9.4
PKG_RELEASE:=8 PKG_RELEASE:=9
PKG_SOURCE:=v$(PKG_VERSION).tar.gz PKG_SOURCE:=v$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/fullstorydev/grpcurl/archive/refs/tags/ PKG_SOURCE_URL:=https://github.com/fullstorydev/grpcurl/archive/refs/tags/
@@ -32,7 +32,7 @@ GO_PKG_LDFLAGS:=-s -w
GO_PKG_TAGS:=master GO_PKG_TAGS:=master
include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk include ../golang/golang-package.mk
define Package/grpcurl define Package/grpcurl
TITLE:=grpcurl is a command-line tool that lets you interact with gRPC servers TITLE:=grpcurl is a command-line tool that lets you interact with gRPC servers
+4 -4
View File
@@ -10,12 +10,12 @@ KAIPLUS_ARCH_x86_64:=amd64
KAIPLUS_ARCH_aarch64:=arm64 KAIPLUS_ARCH_aarch64:=arm64
KAIPLUS_ARCH:=$(KAIPLUS_ARCH_$(ARCH)) KAIPLUS_ARCH:=$(KAIPLUS_ARCH_$(ARCH))
KAIPLUS_HASH_x86_64:=5588a5e1b7874bc5e631ab8ff1288226f567a84d3115808f22352d142c40f5d6 KAIPLUS_HASH_x86_64:=9e28ac8f2ec7b59dfa546b5ed58375397dfda3cd95d35b776fe7f252acb8878f
KAIPLUS_HASH_aarch64:=5be29e4715a1181937c0156a6954738ce96fde2f338a1b03cd32c68b00e61524 KAIPLUS_HASH_aarch64:=af045078037244a4fd80e21ef5a3ba3a9391918adc4a7791fda5ae777386c122
PKG_NAME:=kaiplus PKG_NAME:=kaiplus
PKG_VERSION:=1.0.9 PKG_VERSION:=1.0.10
PKG_RELEASE:=15 PKG_RELEASE:=16
KAIPLUS_URL_FILE:=kaiplus-linux-$(KAIPLUS_ARCH) KAIPLUS_URL_FILE:=kaiplus-linux-$(KAIPLUS_ARCH)
KAIPLUS_DOWNLOAD:=$(KAIPLUS_URL_FILE)-$(KAIPLUS_HASH_$(ARCH)) KAIPLUS_DOWNLOAD:=$(KAIPLUS_URL_FILE)-$(KAIPLUS_HASH_$(ARCH))
@@ -130,13 +130,14 @@ return view.extend({
o = s.option(form.RichListValue, "offloading_type", _("Flow offloading type")); o = s.option(form.RichListValue, "offloading_type", _("Flow offloading type"));
o.value('0', _("None")); o.value('0', _("None"));
o.value('1', _("Software flow offloading"), _('Software based offloading for routing/NAT.')); if (L.hasSystemFeature('offloading'))
o.value('1', _("Software flow offloading"), _('Software based offloading for routing/NAT.'));
if (L.hasSystemFeature('offloading_hw')) if (L.hasSystemFeature('offloading_hw'))
o.value('2', _("Hardware flow offloading"), _('Hardware based offloading for routing with/without NAT.') + ' ' + _(' Requires hardware NAT support.')); o.value('2', _("Hardware flow offloading"), _('Hardware based offloading for routing with/without NAT.') + ' ' + _(' Requires hardware NAT support.'));
if (hasMediatekHNAT) if (hasMediatekHNAT)
o.value('3', "Mediatek HNAT", "基于Mediatek原厂闭源驱动的HNAT卸载"); o.value('3', _('MediaTek HNAT'), _('Hardware NAT offloading using the MediaTek vendor driver.'));
if (hasQualcommECM) if (hasQualcommECM)
o.value('4', 'Qualcomm NSS ECM 加速', 'ECM Auto 模式,自动选择加速引擎。'); o.value('4', _('Qualcomm NSS ECM acceleration'), _('Automatically select the acceleration engine in ECM Auto mode.'));
o.optional = false; o.optional = false;
o.load = function (section_id) { o.load = function (section_id) {
const flow_offloading = uci.get('firewall', section_id, 'flow_offloading'); const flow_offloading = uci.get('firewall', section_id, 'flow_offloading');
@@ -146,7 +147,7 @@ return view.extend({
return '4'; return '4';
if (hasMediatekHNAT && flow_offloading === '3') if (hasMediatekHNAT && flow_offloading === '3')
return '3'; return '3';
return (flow_offloading === '1') return (flow_offloading === '1' && L.hasSystemFeature('offloading'))
? ((flow_offloading_hw === '1' && has_hw_offloading) ? '2' : '1') ? ((flow_offloading_hw === '1' && has_hw_offloading) ? '2' : '1')
: '0'; : '0';
}; };
+16
View File
@@ -1587,3 +1587,19 @@ msgstr "Shell 脚本语法错误。"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js #: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js
msgid "Unable to restart the firewall." msgid "Unable to restart the firewall."
msgstr "无法重启防火墙。" msgstr "无法重启防火墙。"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
msgid "MediaTek HNAT"
msgstr "MediaTek HNAT"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
msgid "Hardware NAT offloading using the MediaTek vendor driver."
msgstr "使用 MediaTek 原厂驱动的硬件 NAT 卸载。"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
msgid "Qualcomm NSS ECM acceleration"
msgstr "Qualcomm NSS ECM 加速"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
msgid "Automatically select the acceleration engine in ECM Auto mode."
msgstr "在 ECM Auto 模式下自动选择加速引擎。"
+16
View File
@@ -1573,3 +1573,19 @@ msgstr "Shell 指令碼語法錯誤。"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js #: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js
msgid "Unable to restart the firewall." msgid "Unable to restart the firewall."
msgstr "無法重新啟動防火牆。" msgstr "無法重新啟動防火牆。"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
msgid "MediaTek HNAT"
msgstr "MediaTek HNAT"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
msgid "Hardware NAT offloading using the MediaTek vendor driver."
msgstr "使用 MediaTek 原廠驅動的硬體 NAT 卸載。"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
msgid "Qualcomm NSS ECM acceleration"
msgstr "Qualcomm NSS ECM 加速"
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js
msgid "Automatically select the acceleration engine in ECM Auto mode."
msgstr "在 ECM Auto 模式下自動選擇加速引擎。"
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-open-box PKG_NAME:=luci-app-open-box
PKG_VERSION:=0.1.378 PKG_VERSION:=0.1.383
PKG_RELEASE:=54 PKG_RELEASE:=55
PKG_MAINTAINER:=liandu2024 PKG_MAINTAINER:=liandu2024
PKG_LICENSE:=GPL-3.0-or-later PKG_LICENSE:=GPL-3.0-or-later
+1 -1
View File
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-openvpn-plus PKG_NAME:=luci-app-openvpn-plus
PKG_VERSION:=1.0.0 PKG_VERSION:=1.0.0
PKG_RELEASE:=10 PKG_RELEASE:=11
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=Manfred Jaider <info@masmbit.com> PKG_MAINTAINER:=Manfred Jaider <info@masmbit.com>
+9 -8
View File
@@ -51,15 +51,15 @@ Even though the traffic has to pass through **Double-NAT** and simple port forwa
## Screenshots ## Screenshots
| 01 - Server/Client View | 02 - Status View | | Server/Client View | Status View |
| :---: | :---: | | :---: | :---: |
| <img width="1200" alt="01" src="https://github.com/user-attachments/assets/b5f13132-0414-4490-9c80-9a361e1bda40" /> | <img width="1200" alt="02" src="https://github.com/user-attachments/assets/7635f2bd-4839-45e4-9804-d01ce38bde7b" /> | ![Server/Client View](screenshots/server.png) | ![Status View](screenshots/status-dark.png) |
| <img width="2398" alt="03" src="https://github.com/user-attachments/assets/0170376e-3dfe-4ca3-8f03-d450cc7ed0e4" /> | <img width="2430" alt="15" src="https://github.com/user-attachments/assets/1710419c-4f14-4594-a3a4-d8147ec1fe50" /> | ![Client View](screenshots/client.png) | ![QR Export](screenshots/qr-client-export.png) |
| <img width="1796" alt="09" src="https://github.com/user-attachments/assets/1c77aefe-197d-4e64-ab87-898ff50faab1" /> | <img width="1798" alt="10" src="https://github.com/user-attachments/assets/f37365a6-4090-443b-89fe-05a5cd92e714" /> | ![Wizard Mobile Start](screenshots/wizard-mobile-server-01.png) | ![Wizard Mobile Tunnel](screenshots/wizard-mobile-server-02.png) |
| <img width="1798" alt="13" src="https://github.com/user-attachments/assets/147250df-079c-4f77-9c5c-00819b4b495c" /> | <img width="1800" alt="11" src="https://github.com/user-attachments/assets/814bbeb0-a0b8-4b38-93b8-03a978d75692" /> | ![Wizard Mobile IP Setup](screenshots/wizard-mobile-server-03.png) | ![Wizard DDNS Setup](screenshots/wizard-mobile-server-04.png) |
| <img width="1798" alt="12" src="https://github.com/user-attachments/assets/57726ec2-c18d-4531-96ec-0d58a818648a" /> | <img width="2384" alt="04" src="https://github.com/user-attachments/assets/8470fa8c-b4a4-4369-b9c5-bfbd8e27526d" /> | ![Wizard Site-To-Site Start](screenshots/wizard-main-office-01.png) | ![Wizard Site-To-Site Routing](screenshots/wizard-main-office-02.png) |
| <img width="2382" alt="05" src="https://github.com/user-attachments/assets/1ef91c41-675c-40d8-a9da-644370ac2fdf" /> | <img width="2394" alt="06" src="https://github.com/user-attachments/assets/95fbdf13-142f-4f67-9117-15eef266e304" /> | ![Wizard Port Forwarding](screenshots/port-forward.png) | ![Wizard Add Instance](screenshots/add-instance.png) |
| ![Wizard Key View](screenshots/keyview.png) | ![Wizard KeyGen](screenshots/keygen.png)
## Configuration Layout ## Configuration Layout
@@ -84,6 +84,7 @@ luci-app-openvpn-plus/
│ │ └── luci-app-openvpn-plus.po │ │ └── luci-app-openvpn-plus.po
│ ├── zh-cn/ │ ├── zh-cn/
│ │ └── luci-app-openvpn-plus.po │ │ └── luci-app-openvpn-plus.po
├── screenshots/
└── root/ └── root/
├── etc/ ├── etc/
│ ├── config/ │ ├── config/
@@ -730,6 +730,33 @@ openssldgst() {
return 0 return 0
} }
# Clean up empty symlink folder
internal_cleansymlink() {
# internal function (no shift)
local ram_link_dir="${1}"
local www_link_dir="${2}"
# Remove the temporary RAM directory ONLY if it is completely empty
[ -d "${ram_link_dir}" ] && rmdir "${ram_link_dir}" 2>/dev/null
# Smart Web-Folder Cleanup: Check if files other than index.html exist
if [ -d "${www_link_dir}" ]; then
local has_profiles="0"
local check_link
# If a real symlink or folder matching the pattern exists, has_profiles becomes 1
for check_link in "${www_link_dir}"/ovpn-tmp-*-*; do
if [ -e "${check_link}" ] || [ -L "${check_link}" ]; then
has_profiles="1"
break
fi
done
# Remove the web folder if no active profile links are left
if [ "${has_profiles}" = "0" ]; then
rm -rf "${www_link_dir}"
fi
fi
}
# Manage temporary download links for mobile profiles [create|delete] # Manage temporary download links for mobile profiles [create|delete]
symlink() { symlink() {
shift # Remove "symlink" from the arguments list shift # Remove "symlink" from the arguments list
@@ -834,25 +861,8 @@ symlink() {
# Remove the temporary session files for this instance from RAM # Remove the temporary session files for this instance from RAM
rm -rf "${ram_link_dir}"/ovpn-tmp-*-"${ovpn_instance}" rm -rf "${ram_link_dir}"/ovpn-tmp-*-"${ovpn_instance}"
# Remove RAM base directory if it has become completely empty # Clean up empty symlink folder
rmdir "${ram_link_dir}" 2>/dev/null internal_cleansymlink "${ram_link_dir}" "${www_link_dir}"
# Smart Web-Folder Cleanup: Check if files other than index.html exist
if [ -d "${www_link_dir}" ]; then
local has_profiles="0"
local check_link
# If a real symlink or folder matching the pattern exists, has_profiles becomes 1
for check_link in "${www_link_dir}"/ovpn-tmp-*-*; do
if [ -e "${check_link}" ] || [ -L "${check_link}" ]; then
has_profiles="1"
break
fi
done
# Remove the web folder if no active profile links are left
if [ "${has_profiles}" = "0" ]; then
rm -rf "${www_link_dir}"
fi
fi
printf "%s\n" "SUCCESS: Link directory and static profile deleted from RAM." printf "%s\n" "SUCCESS: Link directory and static profile deleted from RAM."
else else
@@ -869,7 +879,6 @@ symlink() {
return 0 return 0
} }
# Helper to prevent routing loops for IPv4 and IPv6 subnets # Helper to prevent routing loops for IPv4 and IPv6 subnets
internal_checkloopback() { internal_checkloopback() {
# internal (no shift) # internal (no shift)
@@ -1944,7 +1953,6 @@ internal_cleaninst() {
if internal_checkname "${instname}"; then if internal_checkname "${instname}"; then
# Safely remove files, dead symlinks or directories # Safely remove files, dead symlinks or directories
rm -rf "${f}" rm -rf "${f}"
logger -t openvpn-luci "Cleanup: Removed orphaned item '${f}'."
fi fi
;; ;;
esac esac
@@ -1988,20 +1996,7 @@ cleanup() {
# Step 3: Remove orphan symlink files # Step 3: Remove orphan symlink files
internal_cleaninst "${active_list}" "${www_link_dir}/ovpn-tmp-*-*" internal_cleaninst "${active_list}" "${www_link_dir}/ovpn-tmp-*-*"
internal_cleaninst "${active_list}" "${ram_link_dir}/ovpn-tmp-*-*" internal_cleaninst "${active_list}" "${ram_link_dir}/ovpn-tmp-*-*"
internal_cleansymlink "${ram_link_dir}" "${www_link_dir}"
# Remove the temporary RAM directory ONLY if it is completely empty
[ -d "${ram_link_dir}" ] && rmdir "${ram_link_dir}" 2>/dev/null
# Smart Web-Folder Cleanup: Check if files other than index.html exist
if [ -d "${www_link_dir}" ]; then
local found_profiles
found_profiles="${www_link_dir}/ovpn-tmp-"*-*
# If the text still has a '*', no active profiles were found
if [ "${found_profiles}" = "${www_link_dir}/ovpn-tmp-*-*" ]; then
rm -rf "${www_link_dir}"
fi
fi
return 0 return 0
} }
@@ -774,10 +774,11 @@ const openAutomatedPostKeyGenModal = async function (newInstanceItem, appData, w
if (optionalShowBtnCancel !== true) { if (optionalShowBtnCancel !== true) {
styleHide = ' display:none !important;'; styleHide = ' display:none !important;';
} }
const backupHide = (wizardParams && wizardParams.isServerBackup) ? '' : styleHide;
const yesBtn = E('button', { 'class': 'btn cbi-button cbi-button-action important', 'style': 'margin-right:10px;' }, TXT.KEY.generate_unique_keys); const yesBtn = E('button', { 'class': 'btn cbi-button cbi-button-action important', 'style': 'margin-right:10px;' }, TXT.KEY.generate_unique_keys);
const noBtn = E('button', { 'class': 'btn cbi-button cbi-button-neutral', 'style': 'margin-right:10px;' }, TXT.KEY.clone_default_keys); const noBtn = E('button', { 'class': 'btn cbi-button cbi-button-neutral', 'style': 'margin-right:10px;' }, TXT.KEY.clone_default_keys);
const backupBtn = E('button', { 'class': 'btn cbi-button cbi-button-positive important', 'style': 'margin-right:10px;' }, TXT.BTN.backup); const backupBtn = E('button', { 'class': 'btn cbi-button cbi-button-positive important', 'style': 'margin-right:10px;' + backupHide }, TXT.BTN.backup);
const cancelBtn = E('button', { 'class': 'btn cbi-button cbi-button-neutral', 'style': 'margin-right:10px;' + styleHide }, TXT.BTN.cancel); const cancelBtn = E('button', { 'class': 'btn cbi-button cbi-button-neutral', 'style': 'margin-right:10px;' + styleHide }, TXT.BTN.cancel);
const closeBtn = E('button', { 'class': 'cbi-button cbi-button-action important', 'style': 'display:none;' }, TXT.BTN.close); const closeBtn = E('button', { 'class': 'cbi-button cbi-button-action important', 'style': 'display:none;' }, TXT.BTN.close);
@@ -904,7 +905,14 @@ const openAutomatedPostKeyGenModal = async function (newInstanceItem, appData, w
callbacks.uciSaveApplyRestart(instance_id); callbacks.uciSaveApplyRestart(instance_id);
}); });
L.ui.showModal(TXT.KEY.add_server_instance + ' (' + instance_id + ')', [ let displayName;
if (cnName) {
displayName = ': ' + cnName;
} else {
displayName = ' (' + instance_id + ')';
}
L.ui.showModal(TXT.KEY.add_server_instance + displayName, [
E('div', { 'class': 'cbi-map' }, [ E('div', { 'class': 'cbi-map' }, [
E('div', { 'class': 'cbi-section' }, [ E('div', { 'class': 'cbi-section' }, [
promptMessageNode, promptMessageNode,
@@ -392,54 +392,10 @@ const getValidCommonName = function (name) {
return cleanName; return cleanName;
}; };
/**
* Validate and clean any given IPv4 or IPv6 address string
*/
const checkIPAddress = function (rawIp) {
if (!rawIp) {
return null;
}
// Delete all spaces, tabs and hidden lines from the text (tr -d ' \t\r\n')
const ip = rawIp.replace(/[\s\t\r\n]/g, '');
if (ip === '') {
return null;
}
const ipv4Regex = /^([0-9]{1,3}\.){3}[0-9]{1,3}$/;
// FIXED: Standard compliant IPv6 pattern that correctly handles "::" and blocks ":::"
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){1,7}:$|^(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}$|^(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}$|^(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}$|^[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}$|^:(?::[0-9a-fA-F]{1,4}){1,7}$|^::$/;
// Case 1: Check if the text is a valid IPv4 address (numbers 0 to 255)
if (ipv4Regex.test(ip)) {
const segments = ip.split('.');
const octet1 = parseInt(segments[0], 10);
const octet2 = parseInt(segments[1], 10);
const octet3 = parseInt(segments[2], 10);
const octet4 = parseInt(segments[3], 10);
// Match the awk rule: $1<=255 && $2<=255 && $3<=255 && $4<=255
if (octet1 <= 255 && octet2 <= 255 && octet3 <= 255 && octet4 <= 255) {
return { ip: ip, isV6: false };
}
}
// Case 2: Check if the text matches the verified IPv6 regex
if (ipv6Regex.test(ip)) {
if (ip.indexOf(':') !== -1) {
// Change all hex letters to lowercase for safe matching later (tr 'A-Z' 'a-z')
return { ip: ip.toLowerCase(), isV6: true };
}
}
return null;
};
/** /**
* Generates an isolated, self-validating Site-to-Site network inputs row layout block * Generates an isolated, self-validating Site-to-Site network inputs row layout block
*/ */
const renderSubnetInputs = function () { const renderSubnetInputs = function (checkIpCallback) {
// Create the 3 input fields with simple, easy text placeholders // Create the 3 input fields with simple, easy text placeholders
const cnInput = E('input', { 'type': 'text', 'class': 'cbi-input-text', 'placeholder': TXT.INFO.remote_office_name, 'style': 'width:32%; font-family:monospace; margin-right:1%;' }); const cnInput = E('input', { 'type': 'text', 'class': 'cbi-input-text', 'placeholder': TXT.INFO.remote_office_name, 'style': 'width:32%; font-family:monospace; margin-right:1%;' });
const subnetInput = E('input', { 'type': 'text', 'class': 'cbi-input-text', 'placeholder': TXT.INFO.lan_ip + ' (192.168.2.0)', 'style': 'width:32%; font-family:monospace; margin-right:1%;' }); const subnetInput = E('input', { 'type': 'text', 'class': 'cbi-input-text', 'placeholder': TXT.INFO.lan_ip + ' (192.168.2.0)', 'style': 'width:32%; font-family:monospace; margin-right:1%;' });
@@ -503,7 +459,7 @@ const renderSubnetInputs = function () {
} }
// 2. Validate IP Address using your verified template logic // 2. Validate IP Address using your verified template logic
const ipResult = checkIPAddress(rawSub); const ipResult = (typeof checkIpCallback === 'function') ? checkIpCallback(rawSub) : null
if (!ipResult) { if (!ipResult) {
subnetInput.classList.add('cbi-input-invalid'); subnetInput.classList.add('cbi-input-invalid');
hasValidationError = true; hasValidationError = true;
@@ -1111,7 +1067,6 @@ const showMessage = function (text, type, box) {
box.style.setProperty('display', 'block', 'important'); box.style.setProperty('display', 'block', 'important');
}; };
const hideMessage = function (box) { const hideMessage = function (box) {
box.style.setProperty('display', 'none', 'important'); box.style.setProperty('display', 'none', 'important');
} }
@@ -1411,7 +1366,7 @@ const renderOptionSiteServer = function (portForwardingAlert, wizardData) {
}; };
// 4. Load the input fields row component // 4. Load the input fields row component
const networkInputComponent = renderSubnetInputs(); const networkInputComponent = renderSubnetInputs(wizardData.networkCallbacks.checkIp);
// 5. Create the "Add" button // 5. Create the "Add" button
const addEntryBtn = E('button', { const addEntryBtn = E('button', {
@@ -572,27 +572,71 @@ const sha256sum = async function (textToHash) {
return 'ERROR'; return 'ERROR';
}; };
/**
* Validate and clean any given IPv4 or IPv6 address string safely
*/
const checkIp = function (rawIp) {
if (!rawIp) {
return null;
}
// Delete all spaces, tabs and hidden lines from the text (tr -d ' \t\r\n')
const ip = rawIp.replace(/[\s\t\r\n]/g, '').trim();
if (ip === '') {
return null;
}
const ipv4Regex = /^([0-9]{1,3}\.){3}[0-9]{1,3}$/;
const ipv6Regex = /^([\da-fA-F]{1,4}:|:){1,7}([\da-fA-F]{1,4}|:)$/;
// Case 1: Check if the text is a valid IPv4 address (numbers 0 to 255)
if (ipv4Regex.test(ip)) {
const segments = ip.split('.');
if (parseInt(segments[0], 10) <= 255 &&
parseInt(segments, 10) <= 255 &&
parseInt(segments, 10) <= 255 &&
parseInt(segments, 10) <= 255) {
return { ip: ip, isV6: false };
}
}
// Case 2: Check if the text is a syntactically valid IPv6 address. Extra check for triple colons (:::) to keep the regex small
if (ipv6Regex.test(ip) && ip.indexOf(':') !== -1 && ip.indexOf(':::') === -1) {
return { ip: ip.toLowerCase(), isV6: true };
}
return null;
};
/** /**
* Global helper function to determine the fallback IP and mask from browser or UCI config. * Global helper function to determine the fallback IP and mask from browser or UCI config.
*/ */
const getHostnameFallback = function (returnMask) { const getHostnameFallback = function (returnMask) {
// Basic regex patterns to check for valid IPv4 or IPv6 layouts
const ipv4Pattern = /^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/;
const ipv6Pattern = /^(([0-9a-fA-F]{1,4}:){1,7}|:)(:[0-9a-fA-F]{1,4}){1,7}$|^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
// Get the address string from the browser URL bar and remove IPv6 brackets // Get the address string from the browser URL bar and remove IPv6 brackets
let ip = (window.location.hostname || "").trim().replace(/^\[|\]$/g, ''); let browserIp = (window.location.hostname || "").trim().replace(/^\[|\]$/g, '');
let ip = '';
let mask = '255.255.255.0'; let mask = '255.255.255.0';
// If browser URL is text (like openwrt.lan), fallback to configured UCI IPs // Use our solid validation function to check the browser IP
if (!ipv4Pattern.test(ip) && !ipv6Pattern.test(ip)) { let checked = checkIp(browserIp);
ip = L.uci.get('network', 'lan', 'ipaddr') || L.uci.get('network', 'lan', 'ip6addr') || '192.168.1.1';
if (checked) {
ip = checked.ip;
} else {
// If browser URL is a hostname (like openwrt.lan), fallback to configured UCI IPs
let uciIp = L.uci.get('network', 'lan', 'ipaddr') || L.uci.get('network', 'lan', 'ip6addr') || '192.168.1.1';
// Remove any trailing subnet mask from UCI strings (like /64) safely
ip = uciIp.split('/')[0].trim();
// Re-validate the UCI fallback IP to know its family (v4 or v6)
checked = checkIp(ip);
} }
// Set the matching subnet mask format based on the detected IP family // Set the matching subnet mask format based on the detected IP family
if (ipv4Pattern.test(ip)) { if (checked && checked.isV6 === false) {
mask = L.uci.get('network', 'lan', 'netmask') || OPENVPN.IPv4.MASK24; mask = L.uci.get('network', 'lan', 'netmask') || OPENVPN.IPv4.MASK24;
} else if (ipv6Pattern.test(ip)) { } else if (checked && checked.isV6 === true) {
mask = L.uci.get('network', 'lan', 'ip6assign') || '64'; mask = L.uci.get('network', 'lan', 'ip6assign') || '64';
} }
@@ -965,7 +1009,7 @@ const checkNetworkStructure = function (appData) {
gatewayIp = gw4; gatewayIp = gw4;
} }
// Check WAN IPv4 addresses for Double NAT and extract WAN net-boundaries securely [sh] // Check WAN IPv4 addresses for Double NAT and extract WAN net-boundaries securely
const ipaddrs = net.getIPAddrs ? net.getIPAddrs() : []; const ipaddrs = net.getIPAddrs ? net.getIPAddrs() : [];
let deviceMask = (net.getNetmask && net.getNetmask()) ? net.getNetmask() : (net.data && net.data.netmask) ? net.data.netmask : null; let deviceMask = (net.getNetmask && net.getNetmask()) ? net.getNetmask() : (net.data && net.data.netmask) ? net.data.netmask : null;
if (deviceMask === null || deviceMask === undefined || deviceMask === '') { if (deviceMask === null || deviceMask === undefined || deviceMask === '') {
@@ -977,7 +1021,7 @@ const checkNetworkStructure = function (appData) {
const wanIpStr = String(rawWanIp); const wanIpStr = String(rawWanIp);
const ip = wanIpStr.split('/')[0]; const ip = wanIpStr.split('/')[0];
// If the WAN IP is private, we have a Double NAT [sh] // If the WAN IP is private, we have a Double NAT
if (privateIpRegex.test(ip)) { if (privateIpRegex.test(ip)) {
isDoubleNat = true; isDoubleNat = true;
} }
@@ -1004,7 +1048,7 @@ const checkNetworkStructure = function (appData) {
gatewayIp = OPENVPN.IPv4.ZERO; gatewayIp = OPENVPN.IPv4.ZERO;
} }
// Set values to global object (Fixed: changed 'gateway' to 'gatewayIp') // Set values to global object
networkStructure.doubleNat = isDoubleNat; networkStructure.doubleNat = isDoubleNat;
networkStructure.apMode = isApMode; networkStructure.apMode = isApMode;
networkStructure.gateway = gatewayIp; networkStructure.gateway = gatewayIp;
@@ -1038,6 +1082,7 @@ const checkNetworkStructure = function (appData) {
*/ */
const networkCallbacks = ({ const networkCallbacks = ({
sha256sum: sha256sum, sha256sum: sha256sum,
checkIp: checkIp,
getLanIp: getLanIp, getLanIp: getLanIp,
queryPublicIp: queryPublicIp, queryPublicIp: queryPublicIp,
getDdnsOrPublicIp: getDdnsOrPublicIp, getDdnsOrPublicIp: getDdnsOrPublicIp,
@@ -2785,7 +2830,7 @@ const openSiteToSiteExportModal = function (instance_id, nextId, serverAssets, a
selectDropdown.appendChild(E('option', { 'disabled': true }, ['- - -'])); selectDropdown.appendChild(E('option', { 'disabled': true }, ['- - -']));
selectDropdown.appendChild(E('option', { 'value': 'backup_server_profile' }, [ICON.BACKUP + TXT.MSG.backup_server_profile + ': ' + displayId])); selectDropdown.appendChild(E('option', { 'value': 'backup_server_profile' }, [ICON.BACKUP + TXT.MSG.backup_server_profile + ': ' + displayId]));
const subnetInputs = appData.wizardClass.renderSubnetInputs(); const subnetInputs = appData.wizardClass.renderSubnetInputs(networkCallbacks.checkIp);
// Creates a hidden yellowish warning message box for network conflicts or duplicate names // Creates a hidden yellowish warning message box for network conflicts or duplicate names
const errorDisplayNode = E('div', { const errorDisplayNode = E('div', {
@@ -4789,7 +4834,7 @@ const generate_s2s_nft_file = async function (deviceName, localSubnet, remoteSub
let lanDevice = 'br-lan'; let lanDevice = 'br-lan';
const res = await luci_app_openvpn_plus([CFG.LIBEXEC.getl3dev]); const res = await luci_app_openvpn_plus([CFG.LIBEXEC.getl3dev]);
if (res && res !='' && !res.includes("ERROR:")) { if (res && res != '' && !res.includes("ERROR:")) {
lanDevice = res; lanDevice = res;
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.10.29 PKG_VERSION:=26.10.29
PKG_RELEASE:=359 PKG_RELEASE:=360
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \ PKG_CONFIG_DEPENDS:= \
@@ -963,17 +963,17 @@ function read_rulelist()
end end
local backup_files = { local backup_files = {
"/etc/config/passwall", "/etc/config/passwall",
"/etc/config/passwall_server", "/etc/config/passwall_server",
"/etc/passwall/rules/block_host", "/etc/passwall/rules/block_host",
"/etc/passwall/rules/block_ip", "/etc/passwall/rules/block_ip",
"/etc/passwall/rules/direct_host", "/etc/passwall/rules/direct_host",
"/etc/passwall/rules/direct_ip", "/etc/passwall/rules/direct_ip",
"/etc/passwall/rules/proxy_host", "/etc/passwall/rules/proxy_host",
"/etc/passwall/rules/proxy_ip", "/etc/passwall/rules/proxy_ip",
"/etc/passwall/rules/lanlist_ipv4", "/etc/passwall/rules/lanlist_ipv4",
"/etc/passwall/rules/lanlist_ipv6", "/etc/passwall/rules/lanlist_ipv6",
"/etc/passwall/rules/domains_excluded" "/etc/passwall/rules/domains_excluded"
} }
function create_backup() function create_backup()
@@ -172,7 +172,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:taboption("Proxy", 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>")
o:value("", translate("Use global config") .. "(" .. UDP_NO_REDIR_PORTS .. ")") o:value("", translate("Use global config") .. "(" .. UDP_NO_REDIR_PORTS .. ")")
@@ -119,7 +119,7 @@ o.rawhtml = true
o.cfgvalue = function(self, section) o.cfgvalue = function(self, section)
return string.format([[ return string.format([[
<input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribeAll()" value="%s" />]], <input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribeAll()" value="%s" />]],
translate("Manual subscription All")) translate("Manual subscription All"))
end end
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>") s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
@@ -68,9 +68,9 @@ o.validate = port_validate
---- UDP No Redir Ports ---- UDP No Redir Ports
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"), o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"),
"<font color='red'>" .. translate( "<font color='red'>" .. translate(
"Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") .. "Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") ..
"</font>") "</font>")
o.default = "disable" o.default = "disable"
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"))
@@ -18,7 +18,7 @@ s.type_name = type_name
s.option_prefix = "socks_" s.option_prefix = "socks_"
api.set_type_cbi(s) api.set_type_cbi(s)
o = s:option(ListValue, "del_protocol", " ") --始终隐藏,用于删除 protocol o = s:option(ListValue, "del_protocol", " ") --始终隐藏,用于删除 protocol
o:depends({ __hide = "1" }) o:depends({ __hide = "1" })
o.rewrite_option = "protocol" o.rewrite_option = "protocol"
@@ -167,7 +167,7 @@ if load_urltest_options then -- [[ URLTest Start ]]
o = s:option(Flag, "urltest_interrupt_exist_connections", translate("Interrupt existing connections")) o = s:option(Flag, "urltest_interrupt_exist_connections", translate("Interrupt existing connections"))
o:depends({ protocol = "_urltest" }) o:depends({ protocol = "_urltest" })
o.default = "0" o.default = "0"
o.description = translate("Interrupt existing connections when the selected outbound has changed.") o.description = translate("Interrupt existing connections when the selected outbound has changed.")
end -- [[ URLTest End ]] end -- [[ URLTest End ]]
if load_iface_options then -- [[ 自定义接口 Start ]] if load_iface_options then -- [[ 自定义接口 Start ]]
@@ -2037,7 +2037,7 @@ function gen_config(var)
table.insert(dns.servers, remote_server) table.insert(dns.servers, remote_server)
fakedns_tag = "remote_fakeip" fakedns_tag = "remote_fakeip"
if remote_dns_fake or inner_fakedns == "1" then if remote_dns_fake or inner_fakedns == "1" then
table.insert(dns.servers, { table.insert(dns.servers, {
tag = fakedns_tag, tag = fakedns_tag,
type = "fakeip", type = "fakeip",
@@ -2178,7 +2178,7 @@ function gen_config(var)
local remote_dns_server = api.clone(remote_server) local remote_dns_server = api.clone(remote_server)
remote_dns_server.tag = value.shunt_tag remote_dns_server.tag = value.shunt_tag
local is_local = (remote_server.address and api.is_local_ip(remote_server.address)) or local is_local = (remote_server.address and api.is_local_ip(remote_server.address)) or
(remote_server.server and api.is_local_ip(remote_server.server)) --dns为本地ip,不走代理 (remote_server.server and api.is_local_ip(remote_server.server)) --dns为本地ip,不走代理
remote_dns_server.detour = is_local and "direct" or value.outboundTag remote_dns_server.detour = is_local and "direct" or value.outboundTag
table.insert(dns.servers, remote_dns_server) table.insert(dns.servers, remote_dns_server)
dns_rule.server = remote_dns_server.tag dns_rule.server = remote_dns_server.tag
@@ -175,7 +175,7 @@
const ctx = canvas.getContext('2d', { willReadFrequently: true }); const ctx = canvas.getContext('2d', { willReadFrequently: true });
return function(colorStr) { return function(colorStr) {
if (!colorStr) if (!colorStr)
return null; return null;
ctx.clearRect(0, 0, 1, 1); ctx.clearRect(0, 0, 1, 1);
// 2. Apply the color // 2. Apply the color
ctx.fillStyle = colorStr; ctx.fillStyle = colorStr;
@@ -169,7 +169,7 @@
const ctx = canvas.getContext('2d', { willReadFrequently: true }); const ctx = canvas.getContext('2d', { willReadFrequently: true });
return function(colorStr) { return function(colorStr) {
if (!colorStr) if (!colorStr)
return null; return null;
ctx.clearRect(0, 0, 1, 1); ctx.clearRect(0, 0, 1, 1);
// 2. Apply the color // 2. Apply the color
ctx.fillStyle = colorStr; ctx.fillStyle = colorStr;
@@ -35,10 +35,10 @@ local groups = {}
local group_order = {} local group_order = {}
for _, item in ipairs(values) do for _, item in ipairs(values) do
local g = item.group local g = item.group
if not g or g == "" then if not g or g == "" then
g = translate("default") g = translate("default")
end end
if not groups[g] then if not groups[g] then
groups[g] = {} groups[g] = {}
table.insert(group_order, g) table.insert(group_order, g)
end end
@@ -125,7 +125,7 @@
const ctx = canvas.getContext('2d', { willReadFrequently: true }); const ctx = canvas.getContext('2d', { willReadFrequently: true });
return function(colorStr) { return function(colorStr) {
if (!colorStr) if (!colorStr)
return null; return null;
ctx.clearRect(0, 0, 1, 1); ctx.clearRect(0, 0, 1, 1);
// 2. Apply the color // 2. Apply the color
ctx.fillStyle = colorStr; ctx.fillStyle = colorStr;
@@ -258,7 +258,7 @@
const keyword = searchInput.value.trim().toLowerCase(); const keyword = searchInput.value.trim().toLowerCase();
const isSearching = keyword.length > 0; const isSearching = keyword.length > 0;
// 当前实例下的所有 checkbox // 当前实例下的所有 checkbox
const cbs = isSearching const cbs = isSearching
? Array.from(nodeList.querySelectorAll("input[type=checkbox]")).filter(cb => cb.closest("li").style.display !== "none") ? Array.from(nodeList.querySelectorAll("input[type=checkbox]")).filter(cb => cb.closest("li").style.display !== "none")
: nodeList.querySelectorAll("input[type=checkbox]"); : nodeList.querySelectorAll("input[type=checkbox]");
let checked = 0; let checked = 0;
@@ -137,7 +137,7 @@ local dropdown_data = {
</select> </select>
</div> </div>
<!-- 隐藏 input(保存实际配置值) --> <!-- 隐藏 input(保存实际配置值) -->
<input type="text" id="<%=cbid%>" name="<%=cbid%>" value="<%=current_key%>" data-update="change" style="display:none !important;" /> <input type="text" id="<%=cbid%>" name="<%=cbid%>" value="<%=current_key%>" data-update="change" style="display:none !important;" />
<!-- 模拟 ListValue 控件外观 --> <!-- 模拟 ListValue 控件外观 -->
<div class="cbi-input-value cbi-input-select v-dropdown-display" id="<%=cbid%>.display" tabindex="0"> <div class="cbi-input-value cbi-input-select v-dropdown-display" id="<%=cbid%>.display" tabindex="0">
<span id="<%=cbid%>.label" class="v-dropdown-label" title="<%=pcdata(current_label)%>"> <span id="<%=cbid%>.label" class="v-dropdown-label" title="<%=pcdata(current_label)%>">
@@ -164,7 +164,7 @@
const ctx = canvas.getContext('2d', { willReadFrequently: true }); const ctx = canvas.getContext('2d', { willReadFrequently: true });
return function(colorStr) { return function(colorStr) {
if (!colorStr) if (!colorStr)
return null; return null;
ctx.clearRect(0, 0, 1, 1); ctx.clearRect(0, 0, 1, 1);
// 2. Apply the color // 2. Apply the color
ctx.fillStyle = colorStr; ctx.fillStyle = colorStr;
@@ -5,11 +5,11 @@ local appname = map.config
local sid = "@global[0]" local sid = "@global[0]"
-%> -%>
<style> <style>
#cbi-<%=appname%>-socks .td.cbi-value-field > div { #cbi-<%=appname%>-socks .td.cbi-value-field > div {
white-space: nowrap; white-space: nowrap;
} }
@media (max-width: 1152px) { @media (max-width: 1152px) {
#cbi-<%=appname%>-socks .td.cbi-value-field > div { #cbi-<%=appname%>-socks .td.cbi-value-field > div {
white-space: normal; white-space: normal;
} }
} }
@@ -150,7 +150,7 @@ local sid = "@global[0]"
html += '<a href="#" onclick="window.open(\'' + '<%=api.url("get_socks_log")%>?name=' + id.replace("cbi-<%=appname%>-", "") + '\', \'_blank\')"><%:Log%></a>'; html += '<a href="#" onclick="window.open(\'' + '<%=api.url("get_socks_log")%>?name=' + id.replace("cbi-<%=appname%>-", "") + '\', \'_blank\')"><%:Log%></a>';
node_select.insertAdjacentHTML("afterend", node_select.insertAdjacentHTML("afterend",
'<div class="node-actions" style="display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; margin-left:4px;">' '<div class="node-actions" style="display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; margin-left:4px;">'
+ html + '</div>' + html + '</div>'
); );
} catch(err) { } catch(err) {
@@ -187,7 +187,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
//<![CDATA[ //<![CDATA[
var imgs = document.getElementsByTagName('img'); var imgs = document.getElementsByTagName('img');
@@ -133,7 +133,7 @@ local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
var checkBoxList = []; var checkBoxList = [];
var len = domList.length; var len = domList.length;
while(len--) { while(len--) {
var dom = domList[len];   var dom = domList[len];
if(dom.type == 'checkbox' && dom.checked) { if(dom.type == 'checkbox' && dom.checked) {
checkBoxList.push(dom.name); checkBoxList.push(dom.name);
} }
@@ -162,7 +162,7 @@ local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
var checkBoxList = []; var checkBoxList = [];
var len = domList.length; var len = domList.length;
while(len--) { while(len--) {
var dom = domList[len];   var dom = domList[len];
if(dom.type == 'checkbox' && dom.checked) { if(dom.type == 'checkbox' && dom.checked) {
if(type == 'geoip' && (dom.name === 'chnroute' || dom.name === 'chnroute6')) { if(type == 'geoip' && (dom.name === 'chnroute' || dom.name === 'chnroute6')) {
checkBoxList.push(dom.name); checkBoxList.push(dom.name);
@@ -10,7 +10,7 @@ EEOF
if [ -e "/etc/config/ucitrack" ]; then if [ -e "/etc/config/ucitrack" ]; then
uci -q batch <<-EOF uci -q batch <<-EOF
delete ucitrack.@passwall[-1] delete ucitrack.@passwall[-1]
add ucitrack passwall add ucitrack passwall
set ucitrack.@passwall[-1].init=passwall set ucitrack.@passwall[-1].init=passwall
commit ucitrack commit ucitrack
@@ -1096,7 +1096,7 @@ start_dns() {
local ACL_RULE_DNSMASQ=0 local ACL_RULE_DNSMASQ=0
for acl_section in $(uci show ${CONFIG} | grep "=acl_rule" | cut -d '.' -sf 2 | cut -d '=' -sf 1); do for acl_section in $(uci show ${CONFIG} | grep "=acl_rule" | cut -d '.' -sf 2 | cut -d '=' -sf 1); do
if [ "$(config_n_get $acl_section enabled)" = "1" ] && \ if [ "$(config_n_get $acl_section enabled)" = "1" ] && \
[ "$(config_n_get $acl_section dns_shunt)" = "dnsmasq" ]; then [ "$(config_n_get $acl_section dns_shunt)" = "dnsmasq" ]; then
ACL_RULE_DNSMASQ=1 ACL_RULE_DNSMASQ=1
break break
fi fi
@@ -1109,7 +1109,7 @@ start_dns() {
china_ng_local_dns=${LOCAL_DNS} china_ng_local_dns=${LOCAL_DNS}
v2ray_local_dns="direct_dns_udp_server=${LOCAL_DNS}" v2ray_local_dns="direct_dns_udp_server=${LOCAL_DNS}"
;; ;;
tcp) tcp)
local DIRECT_DNS=$(normalize_dns "$(config_n_get @global[0] direct_dns 223.5.5.5:53)") local DIRECT_DNS=$(normalize_dns "$(config_n_get @global[0] direct_dns 223.5.5.5:53)")
china_ng_local_dns="tcp://${DIRECT_DNS}" china_ng_local_dns="tcp://${DIRECT_DNS}"
v2ray_local_dns="direct_dns_tcp_server=${DIRECT_DNS}" v2ray_local_dns="direct_dns_tcp_server=${DIRECT_DNS}"
@@ -625,7 +625,7 @@ local function encode_node(node)
end end
function parseClashNode(raw, remark) function parseClashNode(raw, remark)
if not raw then return "" end if not raw then return "" end
domain_redir = {} domain_redir = {}
local ok, lyaml = pcall(require, "lyaml") local ok, lyaml = pcall(require, "lyaml")
if not ok then return raw end if not ok then return raw end
@@ -605,7 +605,7 @@ if CHNLIST == "proxy" then DEFAULT_TAG = "chn" end
--全局模式,默认使用远程DNS --全局模式,默认使用远程DNS
if only_global then if only_global then
DEFAULT_TAG = "gfw" DEFAULT_TAG = "gfw"
if NO_IPV6_TRUST == "1" and not IS_SHUNT_NODE then if NO_IPV6_TRUST == "1" and not IS_SHUNT_NODE then
table.insert(config_lines, "no-ipv6") table.insert(config_lines, "no-ipv6")
end end
end end
@@ -761,7 +761,7 @@ function add_rule(var)
server_out:close() server_out:close()
ipset_out:close() ipset_out:close()
end end
if not GEO_SUCCESS then new_text = "" end if not GEO_SUCCESS then new_text = "" end
local f_out = io.open(CACHE_TEXT_FILE, "a") local f_out = io.open(CACHE_TEXT_FILE, "a")
f_out:write(new_text) f_out:write(new_text)
@@ -172,9 +172,7 @@ local auto_config_lines = {}
-- 直连规则显式覆盖远程兜底的缓存、IPv6、测速和双栈优选设置。 -- 直连规则显式覆盖远程兜底的缓存、IPv6、测速和双栈优选设置。
local DIRECT_RULE_OPTIONS = " -enable-cache -address -6 -speed-check-mode ping,tcp:80,tcp:443 -response-mode first-ping -d yes" local DIRECT_RULE_OPTIONS = " -enable-cache -address -6 -speed-check-mode ping,tcp:80,tcp:443 -response-mode first-ping -d yes"
--设置默认 DNS 分组(兜底组),全局代理模式仍使用远程组。 --设置默认 DNS 分组(兜底组),全局代理模式仍使用远程组。
local DEFAULT_DNS_GROUP = (USE_DEFAULT_DNS == "direct" and "direct") or local DEFAULT_DNS_GROUP = (USE_DEFAULT_DNS == "direct" and "direct") or (USE_DEFAULT_DNS == "remote" and "proxy") or (USE_DEFAULT_DNS == "auto" and AUTO_GROUP)
(USE_DEFAULT_DNS == "remote" and "proxy") or
(USE_DEFAULT_DNS == "auto" and AUTO_GROUP)
local only_global = (DEFAULT_PROXY_MODE == "proxy" and CHN_LIST == "0" and USE_GFW_LIST == "0") and 1 local only_global = (DEFAULT_PROXY_MODE == "proxy" and CHN_LIST == "0" and USE_GFW_LIST == "0") and 1
if only_global == 1 then if only_global == 1 then
DEFAULT_DNS_GROUP = "proxy" DEFAULT_DNS_GROUP = "proxy"
@@ -817,7 +817,7 @@ filter_node() {
local address=$(config_n_get "$node" address) local address=$(config_n_get "$node" address)
local port=$(config_n_get "$node" port) local port=$(config_n_get "$node" port)
local hop=$(config_n_get "$node" hysteria2_hop) local hop=$(config_n_get "$node" hysteria2_hop)
[ -n "$hop" ] && port="${port:+$port,}$hop" [ -n "$hop" ] && port="${port:+$port,}$hop"
[ -z "$address" ] && return 1 [ -z "$address" ] && return 1
echo "$address" | grep -Eq "$EXCLUDE_VPSIP" && return 1 echo "$address" | grep -Eq "$EXCLUDE_VPSIP" && return 1
[ -z "$port" ] && return 1 [ -z "$port" ] && return 1
@@ -197,20 +197,20 @@ insert_nftset() {
RS = "[ \t\n\r]+" RS = "[ \t\n\r]+"
ORS = "" ORS = ""
} }
$0 != "" { $0 != "" {
if (!first) { if (!first) {
printf "add element %s %s { \n", t, n printf "add element %s %s { \n", t, n
first = 1 first = 1
} else { } else {
print ",\n" print ",\n"
} }
printf " %s", $0 printf " %s", $0
} }
END { END {
if (first) { if (first) {
print "\n }\n" print "\n }\n"
} }
}' | nft -f - }' | nft -f -
} }
gen_nftset() { gen_nftset() {
@@ -300,7 +300,7 @@ load_acl() {
use_shunt_node=0 use_shunt_node=0
[ -n "$node" ] && [ "$(config_n_get $node protocol)" = "_shunt" ] && use_shunt_node=1 [ -n "$node" ] && [ "$(config_n_get $node protocol)" = "_shunt" ] && use_shunt_node=1
[ "${use_global_config}" = "1" ] && { [ "${use_global_config}" = "1" ] && {
if [ "$(config_get_type $NODE)" = "socks" ]; then if [ "$(config_get_type $NODE)" = "socks" ]; then
node_remark="Socks 配置($(config_n_get $NODE port) 端口)" node_remark="Socks 配置($(config_n_get $NODE port) 端口)"
else else
@@ -856,7 +856,7 @@ filter_node() {
local address=$(config_n_get "$node" address) local address=$(config_n_get "$node" address)
local port=$(config_n_get "$node" port) local port=$(config_n_get "$node" port)
local hop=$(config_n_get "$node" hysteria2_hop) local hop=$(config_n_get "$node" hysteria2_hop)
[ -n "$hop" ] && port="${port:+$port,}$hop" [ -n "$hop" ] && port="${port:+$port,}$hop"
[ -z "$address" ] && return 1 [ -z "$address" ] && return 1
echo "$address" | grep -Eq "$EXCLUDE_VPSIP" && return 1 echo "$address" | grep -Eq "$EXCLUDE_VPSIP" && return 1
[ -z "$port" ] && return 1 [ -z "$port" ] && return 1
@@ -954,7 +954,7 @@ add_firewall_rule() {
add_script_mwan3 add_script_mwan3
mwan3_start mwan3_start
set_tproxy_sysctl set_tproxy_sysctl
gen_nftset $NFTSET_WAN ipv4_addr 0 gen_nftset $NFTSET_WAN ipv4_addr 0
gen_nftset $NFTSET_VPS ipv4_addr 0 gen_nftset $NFTSET_VPS ipv4_addr 0
gen_nftset $NFTSET_GFW ipv4_addr "2d" gen_nftset $NFTSET_GFW ipv4_addr "2d"
gen_nftset $NFTSET_LOCAL ipv4_addr 0 gen_nftset $NFTSET_LOCAL ipv4_addr 0
@@ -976,7 +976,7 @@ add_firewall_rule() {
gen_nftset $NFTSET_SHUNT ipv4_addr "2d" gen_nftset $NFTSET_SHUNT ipv4_addr "2d"
gen_nftset $NFTSET_SHUNT_STATIC ipv4_addr 0 gen_nftset $NFTSET_SHUNT_STATIC ipv4_addr 0
gen_nftset $NFTSET_WAN6 ipv6_addr 0 gen_nftset $NFTSET_WAN6 ipv6_addr 0
gen_nftset $NFTSET_VPS6 ipv6_addr 0 gen_nftset $NFTSET_VPS6 ipv6_addr 0
gen_nftset $NFTSET_GFW6 ipv6_addr "2d" gen_nftset $NFTSET_GFW6 ipv6_addr "2d"
gen_nftset $NFTSET_LOCAL6 ipv6_addr 0 gen_nftset $NFTSET_LOCAL6 ipv6_addr 0
@@ -1502,7 +1502,7 @@ add_firewall_rule() {
del_firewall_rule() { del_firewall_rule() {
for nft in "dstnat" "srcnat" "nat_output" "mangle_prerouting" "mangle_output"; do for nft in "dstnat" "srcnat" "nat_output" "mangle_prerouting" "mangle_output"; do
local handles=$(nft -a list chain $NFTABLE_NAME ${nft} 2>/dev/null | grep -E "PSW_" | awk -F '# handle ' '{print$2}') local handles=$(nft -a list chain $NFTABLE_NAME ${nft} 2>/dev/null | grep -E "PSW_" | awk -F '# handle ' '{print$2}')
for handle in $handles; do for handle in $handles; do
nft delete rule $NFTABLE_NAME ${nft} handle ${handle} 2>/dev/null nft delete rule $NFTABLE_NAME ${nft} handle ${handle} 2>/dev/null
done done
@@ -345,7 +345,7 @@ local function extract_domain(s)
local last_dot = nil local last_dot = nil
for i = 1, len do for i = 1, len do
local b = s:byte(i) local b = s:byte(i)
-- 允许的域名字符:a-zA-Z0-9.- -- 允许的域名字符:a-zA-Z0-9.-
if (b >= 48 and b <= 57) or (b >= 65 and b <= 90) or (b >= 97 and b <= 122) or b == 45 or b == 46 then if (b >= 48 and b <= 57) or (b >= 65 and b <= 90) or (b >= 97 and b <= 122) or b == 45 or b == 46 then
if not start then start = i end if not start then start = i end
if b == 46 then last_dot = i end if b == 46 then last_dot = i end
@@ -608,7 +608,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.protocol = hostInfo[#hostInfo-3] result.protocol = hostInfo[#hostInfo-3]
result.method = hostInfo[#hostInfo-2] result.method = hostInfo[#hostInfo-2]
result.obfs = hostInfo[#hostInfo-1] result.obfs = hostInfo[#hostInfo-1]
result.password = base64Decode(hostInfo[#hostInfo]) result.password = base64Decode(hostInfo[#hostInfo])
local params = {} local params = {}
for _, v in pairs(split(dat[2], '&')) do for _, v in pairs(split(dat[2], '&')) do
local s = v:find("=", 1, true) local s = v:find("=", 1, true)
@@ -695,7 +695,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if not info.net then info.net = "tcp" end if not info.net then info.net = "tcp" end
info.net = string.lower(info.net) info.net = string.lower(info.net)
if result.type == "sing-box" and info.net == "raw" then if result.type == "sing-box" and info.net == "raw" then
info.net = "tcp" info.net = "tcp"
elseif result.type == "Xray" and info.net == "tcp" then elseif result.type == "Xray" and info.net == "tcp" then
info.net = "raw" info.net = "raw"
@@ -962,7 +962,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if params.type then if params.type then
params.type = string.lower(params.type) params.type = string.lower(params.type)
if result.type == "sing-box" and params.type == "raw" then if result.type == "sing-box" and params.type == "raw" then
params.type = "tcp" params.type = "tcp"
elseif result.type == "Xray" and params.type == "tcp" then elseif result.type == "Xray" and params.type == "tcp" then
params.type = "raw" params.type = "raw"
@@ -1175,7 +1175,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if not params.type then params.type = "tcp" end if not params.type then params.type = "tcp" end
params.type = string.lower(params.type) params.type = string.lower(params.type)
if result.type == "sing-box" and params.type == "raw" then if result.type == "sing-box" and params.type == "raw" then
params.type = "tcp" params.type = "tcp"
elseif result.type == "Xray" and params.type == "tcp" then elseif result.type == "Xray" and params.type == "tcp" then
params.type = "raw" params.type = "raw"
@@ -1302,7 +1302,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if ({ xhttp=true, kcp=true, mkcp=true })[params.type] and result.type ~= "Xray" and has_xray then if ({ xhttp=true, kcp=true, mkcp=true })[params.type] and result.type ~= "Xray" and has_xray then
result.type = "Xray" result.type = "Xray"
end end
if result.type == "sing-box" and params.type == "raw" then if result.type == "sing-box" and params.type == "raw" then
params.type = "tcp" params.type = "tcp"
elseif result.type == "Xray" and params.type == "tcp" then elseif result.type == "Xray" and params.type == "tcp" then
params.type = "raw" params.type = "raw"
@@ -2117,7 +2117,7 @@ local function update_node(manual)
uci_set(cfgid, "chain_proxy", "3") uci_set(cfgid, "chain_proxy", "3")
uci_set(cfgid, "outbound_iface", outbound_iface_group) uci_set(cfgid, "outbound_iface", outbound_iface_group)
end end
end end
end end
end end
end end
+1 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2 PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.10.29 PKG_VERSION:=26.10.29
PKG_RELEASE:=160 PKG_RELEASE:=161
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \ PKG_CONFIG_DEPENDS:= \
@@ -114,7 +114,7 @@ o.rawhtml = true
o.cfgvalue = function(self, section) o.cfgvalue = function(self, section)
return string.format([[ return string.format([[
<input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribeAll()" value="%s" />]], <input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribeAll()" value="%s" />]],
translate("Manual subscription All")) translate("Manual subscription All"))
end end
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>") s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
@@ -201,7 +201,7 @@ if has_xray then
o = s_xray:option(Flag, "fragment", translate("Fragment"), translate("TCP fragments, which can deceive the censorship system in some cases, such as bypassing SNI blacklists.")) o = s_xray:option(Flag, "fragment", translate("Fragment"), translate("TCP fragments, which can deceive the censorship system in some cases, such as bypassing SNI blacklists."))
o.default = 0 o.default = 0
o = s_xray:option(ListValue, "fragment_packets", translate("Fragment Packets"), translate(" \"1-3\" is for segmentation at TCP layer, applying to the beginning 1 to 3 data writes by the client. \"tlshello\" is for TLS client hello packet fragmentation.")) o = s_xray:option(ListValue, "fragment_packets", translate("Fragment Packets"), translate(" \"1-3\" is for segmentation at TCP layer, applying to the beginning 1 to 3 data writes by the client. \"tlshello\" is for TLS client hello packet fragmentation."))
o.default = "tlshello" o.default = "tlshello"
o:value("tlshello", "tlshello") o:value("tlshello", "tlshello")
@@ -19,13 +19,13 @@ m.description = (m.description or "") .. [[
<style> <style>
/* Scope to passwall2 CBI fields so it won't leak elsewhere */ /* Scope to passwall2 CBI fields so it won't leak elsewhere */
div[id^="cbid.passwall2."] .cbi-value-field { div[id^="cbid.passwall2."] .cbi-value-field {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 1em; gap: 1em;
} }
div[id^="cbid.passwall2."] .cbi-checkbox { div[id^="cbid.passwall2."] .cbi-checkbox {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
} }
</style> </style>
]] ]]
@@ -160,7 +160,7 @@ o = s:option(Value, "autoswitch_retry_num", translate("Timeout retry num"))
o.datatype = "min(1)" o.datatype = "min(1)"
o.default = 1 o.default = 1
o:depends("enable_autoswitch", true) o:depends("enable_autoswitch", true)
o = s:option(Flag, "autoswitch_restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node.")) o = s:option(Flag, "autoswitch_restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
o:depends("enable_autoswitch", true) o:depends("enable_autoswitch", true)
@@ -715,7 +715,7 @@ if singbox_tags:find("with_utls") then
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 })
o = s:option(Value, "reality_shortId", translate("Short Id")) o = s:option(Value, "reality_shortId", translate("Short Id"))
o:depends({ reality = true }) o:depends({ reality = true })
end end
@@ -304,10 +304,10 @@ if singbox_tags:find("with_utls") then
o:depends({ protocol = "vless", tls = true }) o:depends({ protocol = "vless", 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 = 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 })
o = s:option(Value, "reality_shortId", translate("Short Id")) o = s:option(Value, "reality_shortId", translate("Short Id"))
o:depends({ reality = true }) o:depends({ reality = true })
+2 -2
View File
@@ -536,7 +536,7 @@ function strToTable(str)
if str == nil or type(str) ~= "string" then if str == nil or type(str) ~= "string" then
return {} return {}
end end
return loadstring("return " .. str)() return loadstring("return " .. str)()
end end
@@ -922,7 +922,7 @@ function clone(org)
end end
end end
end end
local res = {} local res = {}
copy(org, res) copy(org, res)
return res return res
@@ -78,7 +78,7 @@ function gen_config(var)
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false, fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false,
reuse_port = true reuse_port = true
} }
if node.type == "SSR" then if node.type == "SSR" then
config.protocol = node.protocol config.protocol = node.protocol
config.protocol_param = node.protocol_param config.protocol_param = node.protocol_param
@@ -115,7 +115,7 @@ function gen_config(var)
}) })
end end
end end
return jsonc.stringify(config, 1) return jsonc.stringify(config, 1)
end end
@@ -1730,7 +1730,7 @@ function gen_config(var)
end end
end end
end end
local rule = { local rule = {
action = "route", action = "route",
inbound = inboundTag, inbound = inboundTag,
@@ -2326,7 +2326,7 @@ function gen_config(var)
table.insert(route.rule_set, v) table.insert(route.rule_set, v)
end end
end end
if inbounds or outbounds then if inbounds or outbounds then
local config = { local config = {
log = { log = {
@@ -2468,7 +2468,7 @@ function gen_proto_config(var)
} }
if outbound then table.insert(outbounds, outbound) end if outbound then table.insert(outbounds, outbound) end
end end
local config = { local config = {
log = { log = {
disabled = true, disabled = true,
@@ -115,7 +115,7 @@ function gen_outbound(flag, node, tag, proxy_table)
else else
dialer_proxy_tag = proxy_tag dialer_proxy_tag = proxy_tag
end end
if node.type == "Xray" then if node.type == "Xray" then
if node.tls and node.tls == "1" then if node.tls and node.tls == "1" then
node.stream_security = "tls" node.stream_security = "tls"
@@ -982,7 +982,7 @@ function gen_config(var)
local routing = nil local routing = nil
local observatory = nil local observatory = nil
local burstObservatory = nil local burstObservatory = nil
local strategy = nil local strategy = nil
local COMMON = {} local COMMON = {}
local CACHE_TEXT_FILE = CACHE_PATH .. "/cache_" .. flag .. ".txt" local CACHE_TEXT_FILE = CACHE_PATH .. "/cache_" .. flag .. ".txt"
@@ -1253,7 +1253,7 @@ function gen_config(var)
table.insert(rules, { inboundTag = { inbound_tag }, balancerTag = balancer_tag }) table.insert(rules, { inboundTag = { inbound_tag }, balancerTag = balancer_tag })
return balancer_tag, loopback_outbound return balancer_tag, loopback_outbound
end end
function set_outbound_detour(node, outbound, outbounds_table) function set_outbound_detour(node, outbound, outbounds_table)
if not node or not outbound or not outbounds_table then return nil end if not node or not outbound or not outbounds_table then return nil end
local default_outTag = outbound.tag local default_outTag = outbound.tag
@@ -1684,7 +1684,7 @@ function gen_config(var)
rules = {} rules = {}
} }
end end
local dns_host = "" local dns_host = ""
if flag == "global" then if flag == "global" then
dns_host = api.uci_get_c("@global[0]", "dns_hosts") or "" dns_host = api.uci_get_c("@global[0]", "dns_hosts") or ""
@@ -1708,7 +1708,7 @@ function gen_config(var)
end end
end) end)
end end
local _remote_dns = { local _remote_dns = {
tag = remote_dns_tag, tag = remote_dns_tag,
queryStrategy = (remote_dns_query_strategy and remote_dns_query_strategy ~= "") and remote_dns_query_strategy or "UseIPv4" queryStrategy = (remote_dns_query_strategy and remote_dns_query_strategy ~= "") and remote_dns_query_strategy or "UseIPv4"
@@ -1858,12 +1858,12 @@ function gen_config(var)
outboundTag = "dns-out" outboundTag = "dns-out"
}) })
end end
local default_dns_tag_name = remote_dns_tag local default_dns_tag_name = remote_dns_tag
if not COMMON.default_outbound_tag or COMMON.default_outbound_tag == "direct" then if not COMMON.default_outbound_tag or COMMON.default_outbound_tag == "direct" then
default_dns_tag_name = direct_dns_tag default_dns_tag_name = direct_dns_tag
end end
if dns_servers and #dns_servers > 0 then if dns_servers and #dns_servers > 0 then
-- Default DNS logic -- Default DNS logic
local default_dns_server = nil local default_dns_server = nil
@@ -2064,7 +2064,7 @@ function gen_config(var)
udp_inbound.settings.allowedNetwork = "udp" udp_inbound.settings.allowedNetwork = "udp"
table.insert(inbounds, udp_inbound) table.insert(inbounds, udp_inbound)
end end
if inbounds or outbounds then if inbounds or outbounds then
local config = { local config = {
env = (function() env = (function()
@@ -2138,7 +2138,7 @@ function gen_config(var)
for index, value in ipairs(config.outbounds) do for index, value in ipairs(config.outbounds) do
local s = value.settings local s = value.settings
if not value["_flag_proxy_tag"] and value["_id"] and s and not NO_RUN and if not value["_flag_proxy_tag"] and value["_id"] and s and not NO_RUN and
((s.vnext and s.vnext[1] and s.vnext[1].address and s.vnext[1].port) or ((s.vnext and s.vnext[1] and s.vnext[1].address and s.vnext[1].port) or
(s.servers and s.servers[1] and s.servers[1].address and s.servers[1].port) or (s.servers and s.servers[1] and s.servers[1].address and s.servers[1].port) or
(s.peers and s.peers[1] and s.peers[1].endpoint) or (s.peers and s.peers[1] and s.peers[1].endpoint) or
(s.address and s.port)) then (s.address and s.port)) then
@@ -2168,7 +2168,7 @@ function gen_proto_config(var)
local server_port = var["server_port"] local server_port = var["server_port"]
local server_username = var["server_username"] local server_username = var["server_username"]
local server_password = var["server_password"] local server_password = var["server_password"]
local inbounds = {} local inbounds = {}
local outbounds = {} local outbounds = {}
local routing = nil local routing = nil
@@ -2194,7 +2194,7 @@ function gen_proto_config(var)
end end
table.insert(inbounds, inbound) table.insert(inbounds, inbound)
end end
if local_http_address and local_http_port then if local_http_address and local_http_port then
local inbound = { local inbound = {
listen = local_http_address, listen = local_http_address,
@@ -2214,7 +2214,7 @@ function gen_proto_config(var)
end end
table.insert(inbounds, inbound) table.insert(inbounds, inbound)
end end
if server_proto ~= "nil" and server_address ~= "nil" and server_port ~= "nil" then if server_proto ~= "nil" and server_address ~= "nil" and server_port ~= "nil" then
local outbound = { local outbound = {
protocol = server_proto, protocol = server_proto,
@@ -2250,7 +2250,7 @@ function gen_proto_config(var)
sockopt = {mark = 255} sockopt = {mark = 255}
} }
}) })
local config = { local config = {
log = { log = {
loglevel = "warning" loglevel = "warning"
@@ -175,7 +175,7 @@
const ctx = canvas.getContext('2d', { willReadFrequently: true }); const ctx = canvas.getContext('2d', { willReadFrequently: true });
return function(colorStr) { return function(colorStr) {
if (!colorStr) if (!colorStr)
return null; return null;
ctx.clearRect(0, 0, 1, 1); ctx.clearRect(0, 0, 1, 1);
// 2. Apply the color // 2. Apply the color
ctx.fillStyle = colorStr; ctx.fillStyle = colorStr;
@@ -213,7 +213,7 @@
if (!rgb) return 'unknown'; // Handle invalid colors if (!rgb) return 'unknown'; // Handle invalid colors
// Calculate YIQ brightness (human eye perception) // Calculate YIQ brightness (human eye perception)
const brightness = ((rgb.r * 299) + (rgb.g * 587) + (rgb.b * 114)) / 1000; const brightness = ((rgb.r * 299) + (rgb.g * 587) + (rgb.b * 114)) / 1000;
return brightness > 128 ? 'light' : 'dark'; return brightness > 128 ? 'light' : 'dark';
} }
@@ -169,7 +169,7 @@
const ctx = canvas.getContext('2d', { willReadFrequently: true }); const ctx = canvas.getContext('2d', { willReadFrequently: true });
return function(colorStr) { return function(colorStr) {
if (!colorStr) if (!colorStr)
return null; return null;
ctx.clearRect(0, 0, 1, 1); ctx.clearRect(0, 0, 1, 1);
// 2. Apply the color // 2. Apply the color
ctx.fillStyle = colorStr; ctx.fillStyle = colorStr;
@@ -35,10 +35,10 @@ local groups = {}
local group_order = {} local group_order = {}
for _, item in ipairs(values) do for _, item in ipairs(values) do
local g = item.group local g = item.group
if not g or g == "" then if not g or g == "" then
g = translate("default") g = translate("default")
end end
if not groups[g] then if not groups[g] then
groups[g] = {} groups[g] = {}
table.insert(group_order, g) table.insert(group_order, g)
end end
@@ -125,7 +125,7 @@
const ctx = canvas.getContext('2d', { willReadFrequently: true }); const ctx = canvas.getContext('2d', { willReadFrequently: true });
return function(colorStr) { return function(colorStr) {
if (!colorStr) if (!colorStr)
return null; return null;
ctx.clearRect(0, 0, 1, 1); ctx.clearRect(0, 0, 1, 1);
// 2. Apply the color // 2. Apply the color
ctx.fillStyle = colorStr; ctx.fillStyle = colorStr;
@@ -163,7 +163,7 @@
if (!rgb) return 'unknown'; // Handle invalid colors if (!rgb) return 'unknown'; // Handle invalid colors
// Calculate YIQ brightness (human eye perception) // Calculate YIQ brightness (human eye perception)
const brightness = ((rgb.r * 299) + (rgb.g * 587) + (rgb.b * 114)) / 1000; const brightness = ((rgb.r * 299) + (rgb.g * 587) + (rgb.b * 114)) / 1000;
return brightness > 128 ? 'light' : 'dark'; return brightness > 128 ? 'light' : 'dark';
} }
@@ -258,7 +258,7 @@
const keyword = searchInput.value.trim().toLowerCase(); const keyword = searchInput.value.trim().toLowerCase();
const isSearching = keyword.length > 0; const isSearching = keyword.length > 0;
// 当前实例下的所有 checkbox // 当前实例下的所有 checkbox
const cbs = isSearching const cbs = isSearching
? Array.from(nodeList.querySelectorAll("input[type=checkbox]")).filter(cb => cb.closest("li").style.display !== "none") ? Array.from(nodeList.querySelectorAll("input[type=checkbox]")).filter(cb => cb.closest("li").style.display !== "none")
: nodeList.querySelectorAll("input[type=checkbox]"); : nodeList.querySelectorAll("input[type=checkbox]");
let checked = 0; let checked = 0;
@@ -137,7 +137,7 @@ local dropdown_data = {
</select> </select>
</div> </div>
<!-- 隐藏 input(保存实际配置值) --> <!-- 隐藏 input(保存实际配置值) -->
<input type="text" id="<%=cbid%>" name="<%=cbid%>" value="<%=current_key%>" data-update="change" style="display:none !important;" /> <input type="text" id="<%=cbid%>" name="<%=cbid%>" value="<%=current_key%>" data-update="change" style="display:none !important;" />
<!-- 模拟 ListValue 控件外观 --> <!-- 模拟 ListValue 控件外观 -->
<div class="cbi-input-value cbi-input-select v-dropdown-display" id="<%=cbid%>.display" tabindex="0"> <div class="cbi-input-value cbi-input-select v-dropdown-display" id="<%=cbid%>.display" tabindex="0">
<span id="<%=cbid%>.label" class="v-dropdown-label" title="<%=pcdata(current_label)%>"> <span id="<%=cbid%>.label" class="v-dropdown-label" title="<%=pcdata(current_label)%>">
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff. Show more