⛄ Sync 2026-11-01 05:43:29
No files matched your search
@@ -5,10 +5,10 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dae
|
||||
PKG_VERSION:=2026.10.18
|
||||
PKG_RELEASE:=52
|
||||
PKG_VERSION:=2026.10.30
|
||||
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_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_HASH:=skip
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=daed
|
||||
PKG_VERSION:=2026.10.18
|
||||
PKG_RELEASE:=64
|
||||
PKG_VERSION:=2026.10.30
|
||||
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_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_HASH:=skip
|
||||
|
||||
@@ -3,7 +3,7 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_NAME:=dockerd
|
||||
PKG_FLAGS:=hold
|
||||
PKG_VERSION:=27.3.1
|
||||
PKG_RELEASE:=6
|
||||
PKG_RELEASE:=8
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
|
||||
@@ -223,6 +223,8 @@ start_service() {
|
||||
procd_set_param command /usr/bin/dockerd --config-file="${DOCKERD_CONF}"
|
||||
fi
|
||||
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
|
||||
}
|
||||
|
||||
@@ -286,8 +288,50 @@ iptables_add_blocking_rule() {
|
||||
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() {
|
||||
if /etc/init.d/dockerd running; then
|
||||
service_stop "/usr/bin/dockerd"
|
||||
fi
|
||||
local i=0
|
||||
|
||||
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}"
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gately
|
||||
PKG_VERSION:=0.6.1.1
|
||||
PKG_RELEASE:=19
|
||||
PKG_VERSION:=0.6.2
|
||||
PKG_RELEASE:=20
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/wangwanjie/gately/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gowebdav
|
||||
PKG_VERSION:=5.17.2
|
||||
PKG_RELEASE:=27
|
||||
PKG_VERSION:=5.17.3
|
||||
PKG_RELEASE:=28
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=grpcurl
|
||||
PKG_VERSION:=1.9.4
|
||||
PKG_RELEASE:=8
|
||||
PKG_RELEASE:=9
|
||||
|
||||
PKG_SOURCE:=v$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/fullstorydev/grpcurl/archive/refs/tags/
|
||||
@@ -32,7 +32,7 @@ GO_PKG_LDFLAGS:=-s -w
|
||||
GO_PKG_TAGS:=master
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
include ../golang/golang-package.mk
|
||||
|
||||
define Package/grpcurl
|
||||
TITLE:=grpcurl is a command-line tool that lets you interact with gRPC servers
|
||||
|
||||
@@ -10,12 +10,12 @@ KAIPLUS_ARCH_x86_64:=amd64
|
||||
KAIPLUS_ARCH_aarch64:=arm64
|
||||
KAIPLUS_ARCH:=$(KAIPLUS_ARCH_$(ARCH))
|
||||
|
||||
KAIPLUS_HASH_x86_64:=5588a5e1b7874bc5e631ab8ff1288226f567a84d3115808f22352d142c40f5d6
|
||||
KAIPLUS_HASH_aarch64:=5be29e4715a1181937c0156a6954738ce96fde2f338a1b03cd32c68b00e61524
|
||||
KAIPLUS_HASH_x86_64:=9e28ac8f2ec7b59dfa546b5ed58375397dfda3cd95d35b776fe7f252acb8878f
|
||||
KAIPLUS_HASH_aarch64:=af045078037244a4fd80e21ef5a3ba3a9391918adc4a7791fda5ae777386c122
|
||||
|
||||
PKG_NAME:=kaiplus
|
||||
PKG_VERSION:=1.0.9
|
||||
PKG_RELEASE:=15
|
||||
PKG_VERSION:=1.0.10
|
||||
PKG_RELEASE:=16
|
||||
|
||||
KAIPLUS_URL_FILE:=kaiplus-linux-$(KAIPLUS_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.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'))
|
||||
o.value('2', _("Hardware flow offloading"), _('Hardware based offloading for routing with/without NAT.') + ' ' + _(' Requires hardware NAT support.'));
|
||||
if (hasMediatekHNAT)
|
||||
o.value('3', "Mediatek HNAT", "基于Mediatek原厂闭源驱动的HNAT卸载");
|
||||
o.value('3', _('MediaTek HNAT'), _('Hardware NAT offloading using the MediaTek vendor driver.'));
|
||||
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.load = function (section_id) {
|
||||
const flow_offloading = uci.get('firewall', section_id, 'flow_offloading');
|
||||
@@ -146,7 +147,7 @@ return view.extend({
|
||||
return '4';
|
||||
if (hasMediatekHNAT && flow_offloading === '3')
|
||||
return '3';
|
||||
return (flow_offloading === '1')
|
||||
return (flow_offloading === '1' && L.hasSystemFeature('offloading'))
|
||||
? ((flow_offloading_hw === '1' && has_hw_offloading) ? '2' : '1')
|
||||
: '0';
|
||||
};
|
||||
|
||||
@@ -1587,3 +1587,19 @@ msgstr "Shell 脚本语法错误。"
|
||||
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js
|
||||
msgid "Unable to restart the firewall."
|
||||
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 模式下自动选择加速引擎。"
|
||||
@@ -1573,3 +1573,19 @@ msgstr "Shell 指令碼語法錯誤。"
|
||||
#: applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/custom.js
|
||||
msgid "Unable to restart the firewall."
|
||||
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 模式下自動選擇加速引擎。"
|
||||
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-open-box
|
||||
PKG_VERSION:=0.1.378
|
||||
PKG_RELEASE:=54
|
||||
PKG_VERSION:=0.1.383
|
||||
PKG_RELEASE:=55
|
||||
|
||||
PKG_MAINTAINER:=liandu2024
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-openvpn-plus
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=10
|
||||
PKG_RELEASE:=11
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Manfred Jaider <info@masmbit.com>
|
||||
|
||||
|
||||
@@ -51,15 +51,15 @@ Even though the traffic has to pass through **Double-NAT** and simple port forwa
|
||||
|
||||
## 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" />
|
||||
| <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" />
|
||||
| <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" />
|
||||
| <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" />
|
||||
| <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" />
|
||||
| <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" />
|
||||
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  | 
|
||||
|
||||
## Configuration Layout
|
||||
|
||||
@@ -84,6 +84,7 @@ luci-app-openvpn-plus/
|
||||
│ │ └── luci-app-openvpn-plus.po
|
||||
│ ├── zh-cn/
|
||||
│ │ └── luci-app-openvpn-plus.po
|
||||
├── screenshots/
|
||||
└── root/
|
||||
├── etc/
|
||||
│ ├── config/
|
||||
|
||||
@@ -730,6 +730,33 @@ openssldgst() {
|
||||
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]
|
||||
symlink() {
|
||||
shift # Remove "symlink" from the arguments list
|
||||
@@ -834,25 +861,8 @@ symlink() {
|
||||
# Remove the temporary session files for this instance from RAM
|
||||
rm -rf "${ram_link_dir}"/ovpn-tmp-*-"${ovpn_instance}"
|
||||
|
||||
# Remove RAM base directory if it has become completely empty
|
||||
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
|
||||
# Clean up empty symlink folder
|
||||
internal_cleansymlink "${ram_link_dir}" "${www_link_dir}"
|
||||
|
||||
printf "%s\n" "SUCCESS: Link directory and static profile deleted from RAM."
|
||||
else
|
||||
@@ -869,7 +879,6 @@ symlink() {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# Helper to prevent routing loops for IPv4 and IPv6 subnets
|
||||
internal_checkloopback() {
|
||||
# internal (no shift)
|
||||
@@ -1944,7 +1953,6 @@ internal_cleaninst() {
|
||||
if internal_checkname "${instname}"; then
|
||||
# Safely remove files, dead symlinks or directories
|
||||
rm -rf "${f}"
|
||||
logger -t openvpn-luci "Cleanup: Removed orphaned item '${f}'."
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
@@ -1988,20 +1996,7 @@ cleanup() {
|
||||
# Step 3: Remove orphan symlink files
|
||||
internal_cleaninst "${active_list}" "${www_link_dir}/ovpn-tmp-*-*"
|
||||
internal_cleaninst "${active_list}" "${ram_link_dir}/ovpn-tmp-*-*"
|
||||
|
||||
# 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
|
||||
internal_cleansymlink "${ram_link_dir}" "${www_link_dir}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -774,10 +774,11 @@ const openAutomatedPostKeyGenModal = async function (newInstanceItem, appData, w
|
||||
if (optionalShowBtnCancel !== true) {
|
||||
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 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 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);
|
||||
});
|
||||
|
||||
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-section' }, [
|
||||
promptMessageNode,
|
||||
|
||||
@@ -392,54 +392,10 @@ const getValidCommonName = function (name) {
|
||||
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
|
||||
*/
|
||||
const renderSubnetInputs = function () {
|
||||
const renderSubnetInputs = function (checkIpCallback) {
|
||||
// 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 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
|
||||
const ipResult = checkIPAddress(rawSub);
|
||||
const ipResult = (typeof checkIpCallback === 'function') ? checkIpCallback(rawSub) : null
|
||||
if (!ipResult) {
|
||||
subnetInput.classList.add('cbi-input-invalid');
|
||||
hasValidationError = true;
|
||||
@@ -1111,7 +1067,6 @@ const showMessage = function (text, type, box) {
|
||||
box.style.setProperty('display', 'block', 'important');
|
||||
};
|
||||
|
||||
|
||||
const hideMessage = function (box) {
|
||||
box.style.setProperty('display', 'none', 'important');
|
||||
}
|
||||
@@ -1411,7 +1366,7 @@ const renderOptionSiteServer = function (portForwardingAlert, wizardData) {
|
||||
};
|
||||
|
||||
// 4. Load the input fields row component
|
||||
const networkInputComponent = renderSubnetInputs();
|
||||
const networkInputComponent = renderSubnetInputs(wizardData.networkCallbacks.checkIp);
|
||||
|
||||
// 5. Create the "Add" button
|
||||
const addEntryBtn = E('button', {
|
||||
|
||||
@@ -572,27 +572,71 @@ const sha256sum = async function (textToHash) {
|
||||
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.
|
||||
*/
|
||||
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
|
||||
let ip = (window.location.hostname || "").trim().replace(/^\[|\]$/g, '');
|
||||
let browserIp = (window.location.hostname || "").trim().replace(/^\[|\]$/g, '');
|
||||
let ip = '';
|
||||
let mask = '255.255.255.0';
|
||||
|
||||
// If browser URL is text (like openwrt.lan), fallback to configured UCI IPs
|
||||
if (!ipv4Pattern.test(ip) && !ipv6Pattern.test(ip)) {
|
||||
ip = L.uci.get('network', 'lan', 'ipaddr') || L.uci.get('network', 'lan', 'ip6addr') || '192.168.1.1';
|
||||
// Use our solid validation function to check the browser IP
|
||||
let checked = checkIp(browserIp);
|
||||
|
||||
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
|
||||
if (ipv4Pattern.test(ip)) {
|
||||
if (checked && checked.isV6 === false) {
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -965,7 +1009,7 @@ const checkNetworkStructure = function (appData) {
|
||||
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() : [];
|
||||
let deviceMask = (net.getNetmask && net.getNetmask()) ? net.getNetmask() : (net.data && net.data.netmask) ? net.data.netmask : null;
|
||||
if (deviceMask === null || deviceMask === undefined || deviceMask === '') {
|
||||
@@ -977,7 +1021,7 @@ const checkNetworkStructure = function (appData) {
|
||||
const wanIpStr = String(rawWanIp);
|
||||
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)) {
|
||||
isDoubleNat = true;
|
||||
}
|
||||
@@ -1004,7 +1048,7 @@ const checkNetworkStructure = function (appData) {
|
||||
gatewayIp = OPENVPN.IPv4.ZERO;
|
||||
}
|
||||
|
||||
// Set values to global object (Fixed: changed 'gateway' to 'gatewayIp')
|
||||
// Set values to global object
|
||||
networkStructure.doubleNat = isDoubleNat;
|
||||
networkStructure.apMode = isApMode;
|
||||
networkStructure.gateway = gatewayIp;
|
||||
@@ -1038,6 +1082,7 @@ const checkNetworkStructure = function (appData) {
|
||||
*/
|
||||
const networkCallbacks = ({
|
||||
sha256sum: sha256sum,
|
||||
checkIp: checkIp,
|
||||
getLanIp: getLanIp,
|
||||
queryPublicIp: queryPublicIp,
|
||||
getDdnsOrPublicIp: getDdnsOrPublicIp,
|
||||
@@ -2785,7 +2830,7 @@ const openSiteToSiteExportModal = function (instance_id, nextId, serverAssets, a
|
||||
selectDropdown.appendChild(E('option', { 'disabled': true }, ['- - -']));
|
||||
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
|
||||
const errorDisplayNode = E('div', {
|
||||
@@ -4789,7 +4834,7 @@ const generate_s2s_nft_file = async function (deviceName, localSubnet, remoteSub
|
||||
|
||||
let lanDevice = 'br-lan';
|
||||
const res = await luci_app_openvpn_plus([CFG.LIBEXEC.getl3dev]);
|
||||
if (res && res !='' && !res.includes("ERROR:")) {
|
||||
if (res && res != '' && !res.includes("ERROR:")) {
|
||||
lanDevice = res;
|
||||
}
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 95 KiB |
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.10.29
|
||||
PKG_RELEASE:=359
|
||||
PKG_RELEASE:=360
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -963,17 +963,17 @@ function read_rulelist()
|
||||
end
|
||||
|
||||
local backup_files = {
|
||||
"/etc/config/passwall",
|
||||
"/etc/config/passwall_server",
|
||||
"/etc/passwall/rules/block_host",
|
||||
"/etc/passwall/rules/block_ip",
|
||||
"/etc/passwall/rules/direct_host",
|
||||
"/etc/passwall/rules/direct_ip",
|
||||
"/etc/passwall/rules/proxy_host",
|
||||
"/etc/passwall/rules/proxy_ip",
|
||||
"/etc/passwall/rules/lanlist_ipv4",
|
||||
"/etc/passwall/rules/lanlist_ipv6",
|
||||
"/etc/passwall/rules/domains_excluded"
|
||||
"/etc/config/passwall",
|
||||
"/etc/config/passwall_server",
|
||||
"/etc/passwall/rules/block_host",
|
||||
"/etc/passwall/rules/block_ip",
|
||||
"/etc/passwall/rules/direct_host",
|
||||
"/etc/passwall/rules/direct_ip",
|
||||
"/etc/passwall/rules/proxy_host",
|
||||
"/etc/passwall/rules/proxy_ip",
|
||||
"/etc/passwall/rules/lanlist_ipv4",
|
||||
"/etc/passwall/rules/lanlist_ipv6",
|
||||
"/etc/passwall/rules/domains_excluded"
|
||||
}
|
||||
|
||||
function create_backup()
|
||||
|
||||
@@ -172,7 +172,7 @@ o.validate = port_validate
|
||||
---- 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"),
|
||||
"<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.") ..
|
||||
"</font>")
|
||||
o:value("", translate("Use global config") .. "(" .. UDP_NO_REDIR_PORTS .. ")")
|
||||
|
||||
@@ -119,7 +119,7 @@ o.rawhtml = true
|
||||
o.cfgvalue = function(self, section)
|
||||
return string.format([[
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribeAll()" value="%s" />]],
|
||||
translate("Manual subscription All"))
|
||||
translate("Manual subscription All"))
|
||||
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>")
|
||||
|
||||
@@ -68,9 +68,9 @@ o.validate = port_validate
|
||||
|
||||
---- UDP No Redir Ports
|
||||
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"),
|
||||
"<font color='red'>" .. translate(
|
||||
"Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") ..
|
||||
"</font>")
|
||||
"<font color='red'>" .. translate(
|
||||
"Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") ..
|
||||
"</font>")
|
||||
o.default = "disable"
|
||||
o:value("disable", translate("No patterns are used"))
|
||||
o:value("1:65535", translate("All"))
|
||||
|
||||
@@ -18,7 +18,7 @@ s.type_name = type_name
|
||||
s.option_prefix = "socks_"
|
||||
api.set_type_cbi(s)
|
||||
|
||||
o = s:option(ListValue, "del_protocol", " ") --始终隐藏,用于删除 protocol
|
||||
o = s:option(ListValue, "del_protocol", " ") --始终隐藏,用于删除 protocol
|
||||
o:depends({ __hide = "1" })
|
||||
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:depends({ protocol = "_urltest" })
|
||||
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 ]]
|
||||
|
||||
if load_iface_options then -- [[ 自定义接口 Start ]]
|
||||
|
||||
@@ -2037,7 +2037,7 @@ function gen_config(var)
|
||||
table.insert(dns.servers, remote_server)
|
||||
|
||||
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, {
|
||||
tag = fakedns_tag,
|
||||
type = "fakeip",
|
||||
@@ -2178,7 +2178,7 @@ function gen_config(var)
|
||||
local remote_dns_server = api.clone(remote_server)
|
||||
remote_dns_server.tag = value.shunt_tag
|
||||
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
|
||||
table.insert(dns.servers, remote_dns_server)
|
||||
dns_rule.server = remote_dns_server.tag
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
return function(colorStr) {
|
||||
if (!colorStr)
|
||||
return null;
|
||||
return null;
|
||||
ctx.clearRect(0, 0, 1, 1);
|
||||
// 2. Apply the color
|
||||
ctx.fillStyle = colorStr;
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
return function(colorStr) {
|
||||
if (!colorStr)
|
||||
return null;
|
||||
return null;
|
||||
ctx.clearRect(0, 0, 1, 1);
|
||||
// 2. Apply the color
|
||||
ctx.fillStyle = colorStr;
|
||||
|
||||
@@ -35,10 +35,10 @@ local groups = {}
|
||||
local group_order = {}
|
||||
for _, item in ipairs(values) do
|
||||
local g = item.group
|
||||
if not g or g == "" then
|
||||
g = translate("default")
|
||||
if not g or g == "" then
|
||||
g = translate("default")
|
||||
end
|
||||
if not groups[g] then
|
||||
if not groups[g] then
|
||||
groups[g] = {}
|
||||
table.insert(group_order, g)
|
||||
end
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
return function(colorStr) {
|
||||
if (!colorStr)
|
||||
return null;
|
||||
return null;
|
||||
ctx.clearRect(0, 0, 1, 1);
|
||||
// 2. Apply the color
|
||||
ctx.fillStyle = colorStr;
|
||||
@@ -258,7 +258,7 @@
|
||||
const keyword = searchInput.value.trim().toLowerCase();
|
||||
const isSearching = keyword.length > 0;
|
||||
// 当前实例下的所有 checkbox
|
||||
const cbs = isSearching
|
||||
const cbs = isSearching
|
||||
? Array.from(nodeList.querySelectorAll("input[type=checkbox]")).filter(cb => cb.closest("li").style.display !== "none")
|
||||
: nodeList.querySelectorAll("input[type=checkbox]");
|
||||
let checked = 0;
|
||||
|
||||
@@ -137,7 +137,7 @@ local dropdown_data = {
|
||||
</select>
|
||||
</div>
|
||||
<!-- 隐藏 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 控件外观 -->
|
||||
<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)%>">
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
return function(colorStr) {
|
||||
if (!colorStr)
|
||||
return null;
|
||||
return null;
|
||||
ctx.clearRect(0, 0, 1, 1);
|
||||
// 2. Apply the color
|
||||
ctx.fillStyle = colorStr;
|
||||
|
||||
@@ -5,11 +5,11 @@ local appname = map.config
|
||||
local sid = "@global[0]"
|
||||
-%>
|
||||
<style>
|
||||
#cbi-<%=appname%>-socks .td.cbi-value-field > div {
|
||||
#cbi-<%=appname%>-socks .td.cbi-value-field > div {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@media (max-width: 1152px) {
|
||||
#cbi-<%=appname%>-socks .td.cbi-value-field > div {
|
||||
#cbi-<%=appname%>-socks .td.cbi-value-field > div {
|
||||
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>';
|
||||
|
||||
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>'
|
||||
);
|
||||
} catch(err) {
|
||||
|
||||
@@ -187,7 +187,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
//<![CDATA[
|
||||
var imgs = document.getElementsByTagName('img');
|
||||
|
||||
@@ -133,7 +133,7 @@ local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
||||
var checkBoxList = [];
|
||||
var len = domList.length;
|
||||
while(len--) {
|
||||
var dom = domList[len];
|
||||
var dom = domList[len];
|
||||
if(dom.type == 'checkbox' && dom.checked) {
|
||||
checkBoxList.push(dom.name);
|
||||
}
|
||||
@@ -162,7 +162,7 @@ local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
||||
var checkBoxList = [];
|
||||
var len = domList.length;
|
||||
while(len--) {
|
||||
var dom = domList[len];
|
||||
var dom = domList[len];
|
||||
if(dom.type == 'checkbox' && dom.checked) {
|
||||
if(type == 'geoip' && (dom.name === 'chnroute' || dom.name === 'chnroute6')) {
|
||||
checkBoxList.push(dom.name);
|
||||
|
||||
@@ -10,7 +10,7 @@ EEOF
|
||||
|
||||
if [ -e "/etc/config/ucitrack" ]; then
|
||||
uci -q batch <<-EOF
|
||||
delete ucitrack.@passwall[-1]
|
||||
delete ucitrack.@passwall[-1]
|
||||
add ucitrack passwall
|
||||
set ucitrack.@passwall[-1].init=passwall
|
||||
commit ucitrack
|
||||
|
||||
@@ -1096,7 +1096,7 @@ start_dns() {
|
||||
local ACL_RULE_DNSMASQ=0
|
||||
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" ] && \
|
||||
[ "$(config_n_get $acl_section dns_shunt)" = "dnsmasq" ]; then
|
||||
[ "$(config_n_get $acl_section dns_shunt)" = "dnsmasq" ]; then
|
||||
ACL_RULE_DNSMASQ=1
|
||||
break
|
||||
fi
|
||||
@@ -1109,7 +1109,7 @@ start_dns() {
|
||||
china_ng_local_dns=${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)")
|
||||
china_ng_local_dns="tcp://${DIRECT_DNS}"
|
||||
v2ray_local_dns="direct_dns_tcp_server=${DIRECT_DNS}"
|
||||
|
||||
@@ -625,7 +625,7 @@ local function encode_node(node)
|
||||
end
|
||||
|
||||
function parseClashNode(raw, remark)
|
||||
if not raw then return "" end
|
||||
if not raw then return "" end
|
||||
domain_redir = {}
|
||||
local ok, lyaml = pcall(require, "lyaml")
|
||||
if not ok then return raw end
|
||||
|
||||
@@ -605,7 +605,7 @@ if CHNLIST == "proxy" then DEFAULT_TAG = "chn" end
|
||||
--全局模式,默认使用远程DNS
|
||||
if only_global then
|
||||
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")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -761,7 +761,7 @@ function add_rule(var)
|
||||
server_out:close()
|
||||
ipset_out:close()
|
||||
end
|
||||
|
||||
|
||||
if not GEO_SUCCESS then new_text = "" end
|
||||
local f_out = io.open(CACHE_TEXT_FILE, "a")
|
||||
f_out:write(new_text)
|
||||
|
||||
@@ -172,9 +172,7 @@ local auto_config_lines = {}
|
||||
-- 直连规则显式覆盖远程兜底的缓存、IPv6、测速和双栈优选设置。
|
||||
local DIRECT_RULE_OPTIONS = " -enable-cache -address -6 -speed-check-mode ping,tcp:80,tcp:443 -response-mode first-ping -d yes"
|
||||
--设置默认 DNS 分组(兜底组),全局代理模式仍使用远程组。
|
||||
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)
|
||||
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)
|
||||
local only_global = (DEFAULT_PROXY_MODE == "proxy" and CHN_LIST == "0" and USE_GFW_LIST == "0") and 1
|
||||
if only_global == 1 then
|
||||
DEFAULT_DNS_GROUP = "proxy"
|
||||
|
||||
@@ -817,7 +817,7 @@ filter_node() {
|
||||
local address=$(config_n_get "$node" address)
|
||||
local port=$(config_n_get "$node" port)
|
||||
local hop=$(config_n_get "$node" hysteria2_hop)
|
||||
[ -n "$hop" ] && port="${port:+$port,}$hop"
|
||||
[ -n "$hop" ] && port="${port:+$port,}$hop"
|
||||
[ -z "$address" ] && return 1
|
||||
echo "$address" | grep -Eq "$EXCLUDE_VPSIP" && return 1
|
||||
[ -z "$port" ] && return 1
|
||||
|
||||
@@ -197,20 +197,20 @@ insert_nftset() {
|
||||
RS = "[ \t\n\r]+"
|
||||
ORS = ""
|
||||
}
|
||||
$0 != "" {
|
||||
if (!first) {
|
||||
printf "add element %s %s { \n", t, n
|
||||
$0 != "" {
|
||||
if (!first) {
|
||||
printf "add element %s %s { \n", t, n
|
||||
first = 1
|
||||
} else {
|
||||
print ",\n"
|
||||
}
|
||||
printf " %s", $0
|
||||
}
|
||||
END {
|
||||
if (first) {
|
||||
print "\n }\n"
|
||||
}
|
||||
}' | nft -f -
|
||||
} else {
|
||||
print ",\n"
|
||||
}
|
||||
printf " %s", $0
|
||||
}
|
||||
END {
|
||||
if (first) {
|
||||
print "\n }\n"
|
||||
}
|
||||
}' | nft -f -
|
||||
}
|
||||
|
||||
gen_nftset() {
|
||||
@@ -300,7 +300,7 @@ load_acl() {
|
||||
use_shunt_node=0
|
||||
[ -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
|
||||
node_remark="Socks 配置($(config_n_get $NODE port) 端口)"
|
||||
else
|
||||
@@ -856,7 +856,7 @@ filter_node() {
|
||||
local address=$(config_n_get "$node" address)
|
||||
local port=$(config_n_get "$node" port)
|
||||
local hop=$(config_n_get "$node" hysteria2_hop)
|
||||
[ -n "$hop" ] && port="${port:+$port,}$hop"
|
||||
[ -n "$hop" ] && port="${port:+$port,}$hop"
|
||||
[ -z "$address" ] && return 1
|
||||
echo "$address" | grep -Eq "$EXCLUDE_VPSIP" && return 1
|
||||
[ -z "$port" ] && return 1
|
||||
@@ -954,7 +954,7 @@ add_firewall_rule() {
|
||||
add_script_mwan3
|
||||
mwan3_start
|
||||
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_GFW ipv4_addr "2d"
|
||||
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_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_GFW6 ipv6_addr "2d"
|
||||
gen_nftset $NFTSET_LOCAL6 ipv6_addr 0
|
||||
@@ -1502,7 +1502,7 @@ add_firewall_rule() {
|
||||
|
||||
del_firewall_rule() {
|
||||
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
|
||||
nft delete rule $NFTABLE_NAME ${nft} handle ${handle} 2>/dev/null
|
||||
done
|
||||
|
||||
@@ -345,7 +345,7 @@ local function extract_domain(s)
|
||||
local last_dot = nil
|
||||
for i = 1, len do
|
||||
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 not start then start = 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.method = hostInfo[#hostInfo-2]
|
||||
result.obfs = hostInfo[#hostInfo-1]
|
||||
result.password = base64Decode(hostInfo[#hostInfo])
|
||||
result.password = base64Decode(hostInfo[#hostInfo])
|
||||
local params = {}
|
||||
for _, v in pairs(split(dat[2], '&')) do
|
||||
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
|
||||
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"
|
||||
elseif result.type == "Xray" and info.net == "tcp" then
|
||||
info.net = "raw"
|
||||
@@ -962,7 +962,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
|
||||
if params.type then
|
||||
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"
|
||||
elseif result.type == "Xray" and params.type == "tcp" then
|
||||
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
|
||||
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"
|
||||
elseif result.type == "Xray" and params.type == "tcp" then
|
||||
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
|
||||
result.type = "Xray"
|
||||
end
|
||||
if result.type == "sing-box" and params.type == "raw" then
|
||||
if result.type == "sing-box" and params.type == "raw" then
|
||||
params.type = "tcp"
|
||||
elseif result.type == "Xray" and params.type == "tcp" then
|
||||
params.type = "raw"
|
||||
@@ -2117,7 +2117,7 @@ local function update_node(manual)
|
||||
uci_set(cfgid, "chain_proxy", "3")
|
||||
uci_set(cfgid, "outbound_iface", outbound_iface_group)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.10.29
|
||||
PKG_RELEASE:=160
|
||||
PKG_RELEASE:=161
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -114,7 +114,7 @@ o.rawhtml = true
|
||||
o.cfgvalue = function(self, section)
|
||||
return string.format([[
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribeAll()" value="%s" />]],
|
||||
translate("Manual subscription All"))
|
||||
translate("Manual subscription All"))
|
||||
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>")
|
||||
|
||||
@@ -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.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.default = "tlshello"
|
||||
o:value("tlshello", "tlshello")
|
||||
|
||||
@@ -19,13 +19,13 @@ m.description = (m.description or "") .. [[
|
||||
<style>
|
||||
/* Scope to passwall2 CBI fields so it won't leak elsewhere */
|
||||
div[id^="cbid.passwall2."] .cbi-value-field {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
}
|
||||
div[id^="cbid.passwall2."] .cbi-checkbox {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
]]
|
||||
|
||||
@@ -160,7 +160,7 @@ o = s:option(Value, "autoswitch_retry_num", translate("Timeout retry num"))
|
||||
o.datatype = "min(1)"
|
||||
o.default = 1
|
||||
o:depends("enable_autoswitch", true)
|
||||
|
||||
|
||||
o = s:option(Flag, "autoswitch_restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
|
||||
o: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:depends({ reality = true })
|
||||
|
||||
|
||||
o = s:option(Value, "reality_shortId", translate("Short Id"))
|
||||
o:depends({ reality = true })
|
||||
end
|
||||
|
||||
@@ -304,10 +304,10 @@ if singbox_tags:find("with_utls") then
|
||||
o:depends({ protocol = "vless", tls = true })
|
||||
o:depends({ protocol = "trojan", tls = true })
|
||||
o:depends({ protocol = "anytls", tls = true })
|
||||
|
||||
|
||||
o = s:option(Value, "reality_private_key", translate("Private Key"))
|
||||
o:depends({ reality = true })
|
||||
|
||||
|
||||
o = s:option(Value, "reality_shortId", translate("Short Id"))
|
||||
o:depends({ reality = true })
|
||||
|
||||
|
||||
@@ -536,7 +536,7 @@ function strToTable(str)
|
||||
if str == nil or type(str) ~= "string" then
|
||||
return {}
|
||||
end
|
||||
|
||||
|
||||
return loadstring("return " .. str)()
|
||||
end
|
||||
|
||||
@@ -922,7 +922,7 @@ function clone(org)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local res = {}
|
||||
copy(org, 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,
|
||||
reuse_port = true
|
||||
}
|
||||
|
||||
|
||||
if node.type == "SSR" then
|
||||
config.protocol = node.protocol
|
||||
config.protocol_param = node.protocol_param
|
||||
@@ -115,7 +115,7 @@ function gen_config(var)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return jsonc.stringify(config, 1)
|
||||
end
|
||||
|
||||
|
||||
@@ -1730,7 +1730,7 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local rule = {
|
||||
action = "route",
|
||||
inbound = inboundTag,
|
||||
@@ -2326,7 +2326,7 @@ function gen_config(var)
|
||||
table.insert(route.rule_set, v)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if inbounds or outbounds then
|
||||
local config = {
|
||||
log = {
|
||||
@@ -2468,7 +2468,7 @@ function gen_proto_config(var)
|
||||
}
|
||||
if outbound then table.insert(outbounds, outbound) end
|
||||
end
|
||||
|
||||
|
||||
local config = {
|
||||
log = {
|
||||
disabled = true,
|
||||
|
||||
@@ -115,7 +115,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
else
|
||||
dialer_proxy_tag = proxy_tag
|
||||
end
|
||||
|
||||
|
||||
if node.type == "Xray" then
|
||||
if node.tls and node.tls == "1" then
|
||||
node.stream_security = "tls"
|
||||
@@ -982,7 +982,7 @@ function gen_config(var)
|
||||
local routing = nil
|
||||
local observatory = nil
|
||||
local burstObservatory = nil
|
||||
local strategy = nil
|
||||
local strategy = nil
|
||||
local COMMON = {}
|
||||
|
||||
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 })
|
||||
return balancer_tag, loopback_outbound
|
||||
end
|
||||
|
||||
|
||||
function set_outbound_detour(node, outbound, outbounds_table)
|
||||
if not node or not outbound or not outbounds_table then return nil end
|
||||
local default_outTag = outbound.tag
|
||||
@@ -1684,7 +1684,7 @@ function gen_config(var)
|
||||
rules = {}
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local dns_host = ""
|
||||
if flag == "global" then
|
||||
dns_host = api.uci_get_c("@global[0]", "dns_hosts") or ""
|
||||
@@ -1708,7 +1708,7 @@ function gen_config(var)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local _remote_dns = {
|
||||
tag = remote_dns_tag,
|
||||
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"
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
local default_dns_tag_name = remote_dns_tag
|
||||
if not COMMON.default_outbound_tag or COMMON.default_outbound_tag == "direct" then
|
||||
default_dns_tag_name = direct_dns_tag
|
||||
end
|
||||
|
||||
|
||||
if dns_servers and #dns_servers > 0 then
|
||||
-- Default DNS logic
|
||||
local default_dns_server = nil
|
||||
@@ -2064,7 +2064,7 @@ function gen_config(var)
|
||||
udp_inbound.settings.allowedNetwork = "udp"
|
||||
table.insert(inbounds, udp_inbound)
|
||||
end
|
||||
|
||||
|
||||
if inbounds or outbounds then
|
||||
local config = {
|
||||
env = (function()
|
||||
@@ -2138,7 +2138,7 @@ function gen_config(var)
|
||||
for index, value in ipairs(config.outbounds) do
|
||||
local s = value.settings
|
||||
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.peers and s.peers[1] and s.peers[1].endpoint) or
|
||||
(s.address and s.port)) then
|
||||
@@ -2168,7 +2168,7 @@ function gen_proto_config(var)
|
||||
local server_port = var["server_port"]
|
||||
local server_username = var["server_username"]
|
||||
local server_password = var["server_password"]
|
||||
|
||||
|
||||
local inbounds = {}
|
||||
local outbounds = {}
|
||||
local routing = nil
|
||||
@@ -2194,7 +2194,7 @@ function gen_proto_config(var)
|
||||
end
|
||||
table.insert(inbounds, inbound)
|
||||
end
|
||||
|
||||
|
||||
if local_http_address and local_http_port then
|
||||
local inbound = {
|
||||
listen = local_http_address,
|
||||
@@ -2214,7 +2214,7 @@ function gen_proto_config(var)
|
||||
end
|
||||
table.insert(inbounds, inbound)
|
||||
end
|
||||
|
||||
|
||||
if server_proto ~= "nil" and server_address ~= "nil" and server_port ~= "nil" then
|
||||
local outbound = {
|
||||
protocol = server_proto,
|
||||
@@ -2250,7 +2250,7 @@ function gen_proto_config(var)
|
||||
sockopt = {mark = 255}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
local config = {
|
||||
log = {
|
||||
loglevel = "warning"
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
return function(colorStr) {
|
||||
if (!colorStr)
|
||||
return null;
|
||||
return null;
|
||||
ctx.clearRect(0, 0, 1, 1);
|
||||
// 2. Apply the color
|
||||
ctx.fillStyle = colorStr;
|
||||
@@ -213,7 +213,7 @@
|
||||
if (!rgb) return 'unknown'; // Handle invalid colors
|
||||
// Calculate YIQ brightness (human eye perception)
|
||||
const brightness = ((rgb.r * 299) + (rgb.g * 587) + (rgb.b * 114)) / 1000;
|
||||
|
||||
|
||||
return brightness > 128 ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
return function(colorStr) {
|
||||
if (!colorStr)
|
||||
return null;
|
||||
return null;
|
||||
ctx.clearRect(0, 0, 1, 1);
|
||||
// 2. Apply the color
|
||||
ctx.fillStyle = colorStr;
|
||||
|
||||
@@ -35,10 +35,10 @@ local groups = {}
|
||||
local group_order = {}
|
||||
for _, item in ipairs(values) do
|
||||
local g = item.group
|
||||
if not g or g == "" then
|
||||
g = translate("default")
|
||||
if not g or g == "" then
|
||||
g = translate("default")
|
||||
end
|
||||
if not groups[g] then
|
||||
if not groups[g] then
|
||||
groups[g] = {}
|
||||
table.insert(group_order, g)
|
||||
end
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
return function(colorStr) {
|
||||
if (!colorStr)
|
||||
return null;
|
||||
return null;
|
||||
ctx.clearRect(0, 0, 1, 1);
|
||||
// 2. Apply the color
|
||||
ctx.fillStyle = colorStr;
|
||||
@@ -163,7 +163,7 @@
|
||||
if (!rgb) return 'unknown'; // Handle invalid colors
|
||||
// Calculate YIQ brightness (human eye perception)
|
||||
const brightness = ((rgb.r * 299) + (rgb.g * 587) + (rgb.b * 114)) / 1000;
|
||||
|
||||
|
||||
return brightness > 128 ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@
|
||||
const keyword = searchInput.value.trim().toLowerCase();
|
||||
const isSearching = keyword.length > 0;
|
||||
// 当前实例下的所有 checkbox
|
||||
const cbs = isSearching
|
||||
const cbs = isSearching
|
||||
? Array.from(nodeList.querySelectorAll("input[type=checkbox]")).filter(cb => cb.closest("li").style.display !== "none")
|
||||
: nodeList.querySelectorAll("input[type=checkbox]");
|
||||
let checked = 0;
|
||||
|
||||
@@ -137,7 +137,7 @@ local dropdown_data = {
|
||||
</select>
|
||||
</div>
|
||||
<!-- 隐藏 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 控件外观 -->
|
||||
<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)%>">
|
||||
|
||||