diff --git a/ddns-go/Makefile b/ddns-go/Makefile
index 1a52bfa9a..7d4dcbdc6 100644
--- a/ddns-go/Makefile
+++ b/ddns-go/Makefile
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ddns-go
-PKG_VERSION:=6.17.1
-PKG_RELEASE:=18
+PKG_VERSION:=6.17.2
+PKG_RELEASE:=19
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)?
diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile
index 1e94a163f..f43ac0860 100644
--- a/luci-app-passwall/Makefile
+++ b/luci-app-passwall/Makefile
@@ -7,8 +7,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
-PKG_VERSION:=26.7.15
-PKG_RELEASE:=180
+PKG_VERSION:=26.7.16
+PKG_RELEASE:=181
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
diff --git a/luci-app-passwall/htdocs/luci-static/resources/view/passwall/cbi.js b/luci-app-passwall/htdocs/luci-static/resources/view/passwall/cbi.js
new file mode 100644
index 000000000..3f9d64706
--- /dev/null
+++ b/luci-app-passwall/htdocs/luci-static/resources/view/passwall/cbi.js
@@ -0,0 +1,38 @@
+function is_json(str) {
+ try {
+ const result = JSON.parse(str);
+ return typeof result === 'object' && result !== null;
+ } catch (e) {
+ return false;
+ }
+}
+
+function is_timehhmm(str) {
+ const match = String(str).match(/^(\d?\d):(\d\d)$/);
+ if (match) {
+ const hour = parseInt(match[1], 10);
+ const minute = parseInt(match[2], 10);
+ if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
+ return true;
+ }
+ }
+ return false;
+}
+
+if (typeof cbi_validators !== 'undefined' && cbi_validators !== null) {
+ cbi_validators['json'] = function() {
+ return is_json(this);
+ }
+ cbi_validators['timehhmm'] = function() {
+ return is_timehhmm(this);
+ }
+} else {
+ L.require('validation').then(function(validation) {
+ validation.types['json'] = function() {
+ return this.assert(is_json(this.value), _('Must be JSON text!'));
+ }
+ validation.types['timehhmm'] = function() {
+ return this.assert(is_timehhmm(this.value), _('valid time (hh:mm)'));
+ }
+ });
+}
diff --git a/luci-app-passwall/luasrc/controller/passwall.lua b/luci-app-passwall/luasrc/controller/passwall.lua
index 012052c10..602656b68 100644
--- a/luci-app-passwall/luasrc/controller/passwall.lua
+++ b/luci-app-passwall/luasrc/controller/passwall.lua
@@ -1152,10 +1152,20 @@ function fetch_certsha256()
local port = (id ~= "") and uci:get(appname, id, "port") or 0
local sni = (id ~= "") and uci:get(appname, id, "tls_serverName") or ""
sni = (sni ~= "") and sni or address
+ local protocol = uci:get(appname, id, "protocol")
+ local h3, timeout = false, 10
+ if protocol == "hysteria2" then
+ h3 = true
+ timeout = 60
+ if port == 0 then
+ local hop = uci:get(appname, id, "hysteria2_hop") or "0"
+ port = tonumber(hop:match("^%s*(%d+)"))
+ end
+ end
if address == "" or port == 0 then
http_write_json_error()
return
end
- local data = api.fetch_cert_sha256(address, port, sni, 5)
+ local data = api.fetch_cert_sha256(address, port, sni, timeout, h3)
http_write_json(data ~= "" and { code = 1, data = data } or { code = 0 })
end
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua
index 9d5edc7e4..e3f3d9a17 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua
@@ -2,6 +2,8 @@ local api = require "luci.passwall.api"
local appname = "passwall"
local sys = api.sys
+api.set_default_cbi()
+
m = Map(appname)
api.set_apply_on_parse(m)
@@ -13,7 +15,8 @@ o.rmempty = false
o.default = false
-- [[ ACLs Settings ]]--
-s = m:section(TypedSection, "acl_rule")
+local cfgname = "acl_rule"
+s = m:section(TypedSection, cfgname)
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true
@@ -101,4 +104,9 @@ o:value("1:65535", translate("All"))
o:value("53", "53")
]]--
-return m
+local sortable = Template(appname .. "/cbi/sortable")
+sortable.api = api
+sortable.target_cfgname = cfgname
+m:append(sortable)
+
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
index d908e37d0..4dec15808 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
@@ -1,6 +1,8 @@
local api = require "luci.passwall.api"
local appname = "passwall"
+api.set_default_cbi()
+
m = Map(appname)
m.redirect = api.url("acl")
api.set_apply_on_parse(m)
@@ -601,4 +603,4 @@ for k, v in pairs(nodes_table) do
end
end
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua
index 5f573a9ed..3f2a77604 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua
@@ -2,6 +2,8 @@ local api = require "luci.passwall.api"
local com = require "luci.passwall.com"
local appname = "passwall"
+api.set_default_cbi()
+
m = Map(appname)
api.set_apply_on_parse(m)
@@ -34,4 +36,4 @@ o.cfgvalue = function(t, n)
return string.format('%s', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."))
end
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
index 9d19ae149..9e40564d3 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
@@ -8,6 +8,8 @@ local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
local has_chnlist = fs.access("/usr/share/passwall/rules/chnlist")
local has_chnroute = fs.access("/usr/share/passwall/rules/chnroute")
+api.set_default_cbi()
+
m = Map(appname)
api.set_apply_on_parse(m)
@@ -879,4 +881,4 @@ footer.shunt_list = api.jsonc.stringify(shunt_list)
m:append(footer)
m:append(Template(appname .. "/global/status_bottom"))
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua
index 972a96a7f..c4784a875 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua
@@ -15,6 +15,8 @@ for k, e in ipairs(api.get_valid_nodes()) do
end
end
+api.set_default_cbi()
+
m = Map(appname, "HAPROXY " .. translate("Load Balancing"))
api.set_apply_on_parse(m)
@@ -101,7 +103,8 @@ end
o:depends("health_check_type", "passwall_logic")
-- [[ Balancing Settings ]]--
-s = m:section(TypedSection, "haproxy_config", translate("Node List"))
+local cfgname = "haproxy_config"
+s = m:section(TypedSection, cfgname, translate("Node List"))
s.description = "" ..
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") .. "
" ..
translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") .. ""
@@ -177,6 +180,11 @@ o:value(0, translate("Primary"))
o:value(1, translate("Standby"))
o.rmempty = false
+local sortable = Template(appname .. "/cbi/sortable")
+sortable.api = api
+sortable.target_cfgname = cfgname
+m:append(sortable)
+
m:append(Template(appname .. "/haproxy/js"))
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua
index 9cbb3a9ee..1f8ba7af4 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua
@@ -1,6 +1,8 @@
api = require "luci.passwall.api"
appname = "passwall"
+api.set_default_cbi()
+
m = Map(appname, translate("Node Config"))
m.redirect = api.url("node_list")
api.set_apply_on_parse(m)
@@ -120,7 +122,6 @@ local footer = Template(appname .. "/node_config/footer")
footer.api = api
footer.config = m.config
footer.section = arg[1]
-
m:append(footer)
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
index 3bdf8fcca..8c16f598f 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
@@ -3,6 +3,8 @@ local appname = "passwall"
local sys = api.sys
local datatypes = api.datatypes
+api.set_default_cbi()
+
m = Map(appname)
api.set_apply_on_parse(m)
@@ -33,4 +35,4 @@ s:append(Template(appname .. "/node_list/link_add_node"))
m:append(Template(appname .. "/node_list/node_list"))
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
index 481477aa9..276f28131 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
@@ -38,6 +38,8 @@ if has_hysteria2 then
table.insert(hysteria2_type, s)
end
+api.set_default_cbi()
+
m = Map(appname)
api.set_apply_on_parse(m)
@@ -127,7 +129,8 @@ o.cfgvalue = function(self, section)
translate("Manual subscription All"))
end
-s = m:section(TypedSection, "subscribe_list", "", "" .. 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.") .. "")
+local cfgname = "subscribe_list"
+s = m:section(TypedSection, cfgname, "", "" .. 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.") .. "")
s.addremove = true
s.anonymous = true
s.sortable = true
@@ -148,7 +151,7 @@ o.validate = function(self, value, section)
return nil, translate("Remark cannot be empty.")
end
local duplicate = false
- m.uci:foreach(appname, "subscribe_list", function(e)
+ m.uci:foreach(appname, cfgname, function(e)
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
duplicate = true
return false
@@ -223,6 +226,11 @@ o.cfgvalue = function(self, section)
section, translate("Manual subscription"))
end
+local sortable = Template(appname .. "/cbi/sortable")
+sortable.api = api
+sortable.target_cfgname = cfgname
+m:append(sortable)
+
m:append(Template(appname .. "/node_subscribe/js"))
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
index e3d2c44c5..98e52f7e1 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
@@ -2,6 +2,8 @@ local api = require "luci.passwall.api"
local uci = api.uci
local appname = "passwall"
+api.set_default_cbi()
+
m = Map(appname)
m.redirect = api.url("node_subscribe")
api.set_apply_on_parse(m)
@@ -244,13 +246,7 @@ o:value(0, translate("Every Sunday"))
o = s:option(Value, "update_time_mode", translate("Update Time"))
for t = 0, 23 do o:value(t .. ":00") end
o.default = "0:00"
-o.validate = function(self, value)
- local b = api.is_timehhmm(value)
- if b then
- return value
- end
- return nil
-end
+o.datatype = "timehhmm"
o:depends("update_week_mode", "0")
o:depends("update_week_mode", "1")
o:depends("update_week_mode", "2")
@@ -321,4 +317,4 @@ for k, v in pairs(nodes_table) do
end
end
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
index 2f53c6aed..efe0cccc7 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
@@ -8,6 +8,8 @@ local port_validate = function(self, value, t)
return value:gsub("-", ":")
end
+api.set_default_cbi()
+
m = Map(appname)
api.set_apply_on_parse(m)
@@ -43,13 +45,7 @@ for index, value in ipairs({"stop", "start", "restart"}) do
o = s:option(Value, value .. "_time_mode", translate(value .. " Time"))
for t = 0, 23 do o:value(t .. ":00") end
o.default = "0:00"
- o.validate = function(self, value)
- local b = api.is_timehhmm(value)
- if b then
- return value
- end
- return nil
- end
+ o.datatype = "timehhmm"
o:depends(value .. "_week_mode", "0")
o:depends(value .. "_week_mode", "1")
o:depends(value .. "_week_mode", "2")
@@ -271,4 +267,4 @@ if has_singbox then
o.default = 0
end
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
index c6e12ed8d..979d4fe40 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
@@ -3,6 +3,8 @@ local appname = "passwall"
local has_xray = api.finded_com("xray")
local has_singbox = api.finded_com("sing-box")
+api.set_default_cbi()
+
m = Map(appname)
api.set_apply_on_parse(m)
@@ -112,13 +114,7 @@ o:value(0, translate("Every Sunday"))
o = s:option(Value, "update_time_mode", translate("Update Time"))
for t = 0, 23 do o:value(t .. ":00") end
o.default = "0:00"
-o.validate = function(self, value)
- local b = api.is_timehhmm(value)
- if b then
- return value
- end
- return nil
-end
+o.datatype = "timehhmm"
o:depends("update_week_mode", "0")
o:depends("update_week_mode", "1")
o:depends("update_week_mode", "2")
@@ -146,8 +142,10 @@ end
s:append(Template(appname .. "/rule/rule_version"))
+local cfgname = "shunt_rules"
+
if has_xray or has_singbox then
- s = m:section(TypedSection, "shunt_rules", "Sing-Box/Xray " .. translate("Shunt Rule"), "" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "")
+ s = m:section(TypedSection, cfgname, "Sing-Box/Xray " .. translate("Shunt Rule"), "" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "")
s.template = "cbi/tblsection"
s.anonymous = false
s.addremove = true
@@ -169,4 +167,9 @@ if has_xray or has_singbox then
o = s:option(DummyValue, "remarks", translate("Remarks"))
end
-return m
+local sortable = Template(appname .. "/cbi/sortable")
+sortable.api = api
+sortable.target_cfgname = cfgname
+m:append(sortable)
+
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua
index ac2fcdbaa..8bbda0abf 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua
@@ -1,5 +1,5 @@
local api = require "luci.passwall.api"
-local appname = "passwall"
+local appname = api.appname
local fs = api.fs
local sys = api.sys
local uci = api.uci
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
index 85596c9b4..4b45eba49 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
@@ -2,6 +2,8 @@ local api = require "luci.passwall.api"
local appname = "passwall"
local datatypes = api.datatypes
+api.set_default_cbi()
+
m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule"))
m.redirect = api.url("rule")
api.set_apply_on_parse(m)
@@ -245,4 +247,4 @@ ip_list.description = "
"
o = s:option(Flag, "invert", "Invert", translate("Invert match result.") .. " " .. translate("Only support Sing-Box."))
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua
index e2025548f..7427bbf65 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua
@@ -1,6 +1,8 @@
local api = require "luci.passwall.api"
local appname = "passwall"
+api.set_default_cbi()
+
m = Map(appname)
m.redirect = api.url()
api.set_apply_on_parse(m)
@@ -166,4 +168,4 @@ o = s:option(DummyValue, "btn")
o.template = appname .. "/socks_auto_switch/btn"
o:depends("backup_node_add_mode", "manual")
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
index 00fd19a44..ffb01c339 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
@@ -667,6 +667,14 @@ o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in J
o:depends({ [_n("use_xhttp_extra")] = true })
o.rows = 10
o.wrap = "off"
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, "XHTTP Extra " .. translate("Must be JSON text!")
+end
o.custom_cfgvalue = function(self, section, value)
local raw = m:get(section, "xhttp_extra")
if raw then
@@ -689,14 +697,6 @@ o.custom_write = function(self, section, value)
m:del(section, "download_address")
end
end
-o.validate = function(self, value)
- value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
- if api.jsonc.parse(value) then
- return value
- else
- return nil, "XHTTP Extra " .. translate("Must be JSON text!")
- end
-end
o.custom_remove = function(self, section, value)
m:del(section, "xhttp_extra")
m:del(section, "download_address")
@@ -752,6 +752,14 @@ o.rows = 10
o.wrap = "off"
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "
" ..
translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, "FinalMask " .. translate("Must be JSON text!")
+end
o.custom_cfgvalue = function(self, section, value)
local raw = m:get(section, "finalmask")
if raw then
@@ -761,14 +769,6 @@ end
o.custom_write = function(self, section, value)
m:set(section, "finalmask", api.base64Encode(value) or "")
end
-o.validate = function(self, value)
- value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
- if api.jsonc.parse(value) then
- return value
- else
- return nil, "FinalMask " .. translate("Must be JSON text!")
- end
-end
--[[Fast Open]]
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua
index d6ee9323b..f3a147a58 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua
@@ -1,4 +1,7 @@
local api = require "luci.passwall.api"
+local appname = api.appname
+
+api.set_default_cbi()
m = Map("passwall_server", translate("Server-Side"))
api.set_apply_on_parse(m)
@@ -10,7 +13,8 @@ t.addremove = false
e = t:option(Flag, "enable", translate("Enable"))
e.rmempty = false
-t = m:section(TypedSection, "user", translate("Users Manager"))
+local cfgname = "user"
+t = m:section(TypedSection, cfgname, translate("Users Manager"))
t.anonymous = true
t.addremove = true
t.sortable = true
@@ -85,7 +89,14 @@ e = t:option(Flag, "log", translate("Log"))
e.default = "1"
e.rmempty = false
-m:append(Template("passwall/server/log"))
+local sortable = Template(appname .. "/cbi/sortable")
+sortable.api = api
+sortable.appname = m.config
+sortable.target_cfgname = cfgname
+m:append(sortable)
-m:append(Template("passwall/server/users_list_status"))
-return m
+m:append(Template(appname .. "/server/log"))
+
+m:append(Template(appname .. "/server/users_list_status"))
+
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua
index e3402ac3f..661727d6d 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua
@@ -131,12 +131,12 @@ o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
o.rows = 10
o.wrap = "off"
o:depends({ [_n("custom")] = true })
-o.validate = function(self, value, t)
- if value and api.jsonc.parse(value) then
- return value
- else
- return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
- end
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.custom_cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str")
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
index 17fca6502..7e1cf8769 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
@@ -406,6 +406,7 @@ o = s:option(TextValue, _n("finalmask"), "FinalMask JSON")
o:depends({ [_n("use_finalmask")] = true })
o.rows = 10
o.wrap = "off"
+o.datatype = "json"
o.custom_cfgvalue = function(self, section, value)
local raw = m:get(section, "finalmask")
if raw then
@@ -415,14 +416,6 @@ end
o.custom_write = function(self, section, value)
m:set(section, "finalmask", api.base64Encode(value) or "")
end
-o.validate = function(self, value)
- value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
- if api.jsonc.parse(value) then
- return value
- else
- return nil, "FinalMask " .. translate("Must be JSON text!")
- end
-end
--[[acceptProxyProtocol]]
o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
@@ -517,16 +510,16 @@ for _, d in ipairs(netdev_list) do
o:value(d.name, d.label)
end
-o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
+o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
o.rows = 10
o.wrap = "off"
o:depends({ [_n("custom")] = true })
-o.validate = function(self, value, t)
- if value and api.jsonc.parse(value) then
- return value
- else
- return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
- end
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.custom_cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str")
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua
index 92ff2fa27..75d2553be 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua
@@ -489,16 +489,16 @@ for _, d in ipairs(netdev_list) do
o:value(d.name, d.label)
end
-o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
+o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
o.rows = 10
o.wrap = "off"
o:depends({ [_n("custom")] = true })
-o.validate = function(self, value, t)
- if value and api.jsonc.parse(value) then
- return value
- else
- return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
- end
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.custom_cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str")
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua
index ed2a28631..54b710ec0 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua
@@ -49,16 +49,16 @@ o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
o.default = "0"
o:depends({ [_n("custom")] = false })
-o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
+o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
o.rows = 10
o.wrap = "off"
o:depends({ [_n("custom")] = true })
-o.validate = function(self, value, t)
- if value and api.jsonc.parse(value) then
- return value
- else
- return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
- end
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.custom_cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str")
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua
index 02a94f61b..d77cf74c0 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua
@@ -76,16 +76,16 @@ o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
o.default = "0"
o:depends({ [_n("custom")] = false })
-o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
+o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
o.rows = 10
o.wrap = "off"
o:depends({ [_n("custom")] = true })
-o.validate = function(self, value, t)
- if value and api.jsonc.parse(value) then
- return value
- else
- return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
- end
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.custom_cfgvalue = function(self, section, value)
local config_str = m:get(section, "config_str")
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua
index 35312f536..89b702923 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua
@@ -2,6 +2,8 @@ api = require "luci.passwall.api"
appname = api.appname
fs = api.fs
+api.set_default_cbi()
+
m = Map("passwall_server", translate("Server Config"))
m.redirect = api.url("server")
api.set_apply_on_parse(m)
@@ -56,4 +58,4 @@ footer.config = m.config
footer.section = arg[1]
m:append(footer)
-return m
+return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/passwall/api.lua b/luci-app-passwall/luasrc/passwall/api.lua
index 111f34cc2..62b05f758 100644
--- a/luci-app-passwall/luasrc/passwall/api.lua
+++ b/luci-app-passwall/luasrc/passwall/api.lua
@@ -348,8 +348,16 @@ function strToTable(str)
return loadstring("return " .. str)()
end
-function is_timehhmm(timeStr)
- local hour, minute = string.match(timeStr, "^(%d?%d):(%d%d)$")
+function is_json(str)
+ if str and jsonc.parse(str) then
+ return true
+ end
+ return false
+end
+datatypes.json = is_json
+
+function is_timehhmm(str)
+ local hour, minute = string.match(str, "^(%d?%d):(%d%d)$")
if hour and minute then
hour = tonumber(hour)
minute = tonumber(minute)
@@ -359,6 +367,7 @@ function is_timehhmm(timeStr)
end
return false
end
+datatypes.timehhmm = is_timehhmm
function is_normal_node(e)
if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
@@ -1327,6 +1336,40 @@ function to_check_self()
}
end
+function set_default_cbi()
+ local cbi = require "luci.cbi"
+ if true then
+ --TextValue
+ local TextValue = cbi.TextValue
+ local original_init = TextValue.__init__
+ function TextValue.__init__(self, ...)
+ original_init(self, ...)
+ self.template = appname .. "/cbi/tvalue"
+ end
+ end
+end
+
+function return_map(map)
+ local cbi = require "luci.cbi"
+ local api = require "luci.passwall.api"
+ if true then
+ -- header
+ local header = cbi.Template(appname .. "/cbi/header")
+ header.api = api
+ header.config = map.config
+ table.insert(map.children, 1, header)
+ end
+ if true then
+ -- footer
+ local footer = cbi.Template(appname .. "/cbi/footer")
+ footer.api = api
+ footer.config = map.config
+ map:append(footer)
+ end
+
+ return map
+end
+
function luci_types(id, m, s, type_name, option_prefix)
local fv_type
local field_type = s.fields["type"]
@@ -1673,18 +1716,29 @@ function cleanEmptyTables(t)
return next(t) and t or nil
end
-function fetch_cert_sha256(host, port, sni, timeout)
+function fetch_cert_sha256(host, port, sni, timeout, http3)
if not host then return "" end
port = tonumber(port) or 443
sni = sni or host
timeout = tonumber(timeout) or 5
- local cmd = string.format(
- "timeout %d openssl s_client -connect %s:%d -servername %s -showcerts /dev/null " ..
- "| awk 'BEGIN{c=0}/BEGIN CERT/{c++} c==1{print} /END CERT/{if(c==1)exit}' " ..
- "| openssl x509 -outform der 2>/dev/null " ..
- "| sha256sum 2>/dev/null",
- timeout, host, port, sni
- )
+ local cmd
+ if http3 then
+ cmd = string.format(
+ "timeout %d curl --http3 -k -w '%%{certs}' -o /dev/null https://%s:%d 2>/dev/null " ..
+ "| awk 'BEGIN{c=0}/BEGIN CERT/{c++} c==1{print} /END CERT/{if(c==1)exit}' " ..
+ "| openssl x509 -outform der 2>/dev/null " ..
+ "| sha256sum 2>/dev/null",
+ timeout, host, port
+ )
+ else
+ cmd = string.format(
+ "timeout %d openssl s_client -connect %s:%d -servername %s -showcerts /dev/null " ..
+ "| awk 'BEGIN{c=0}/BEGIN CERT/{c++} c==1{print} /END CERT/{if(c==1)exit}' " ..
+ "| openssl x509 -outform der 2>/dev/null " ..
+ "| sha256sum 2>/dev/null",
+ timeout, host, port, sni
+ )
+ end
local out = trim(sys.exec(cmd))
local fp = out:match("^([0-9a-fA-F]+)")
if not fp or fp:lower():match("^e3b0c44298fc1c149afbf4c8996fb924") then
diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua
index 2ed73aeae..a64549288 100644
--- a/luci-app-passwall/luasrc/passwall/util_xray.lua
+++ b/luci-app-passwall/luasrc/passwall/util_xray.lua
@@ -153,8 +153,8 @@ function gen_outbound(flag, node, tag, proxy_table)
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
- pinnedPeerCertSha256 = node.tls_pinSHA256,
- verifyPeerCertByName = node.tls_CertByName,
+ pinnedPeerCertSha256 = node.tls_pinSHA256 or "",
+ verifyPeerCertByName = node.tls_CertByName or "",
echConfigList = (node.ech == "1") and node.ech_config or nil,
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
certificate = api.split(node.tls_certificate_pem, "\n"),
diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/footer.htm b/luci-app-passwall/luasrc/view/passwall/cbi/footer.htm
new file mode 100644
index 000000000..46a25d3e7
--- /dev/null
+++ b/luci-app-passwall/luasrc/view/passwall/cbi/footer.htm
@@ -0,0 +1 @@
+
diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/header.htm b/luci-app-passwall/luasrc/view/passwall/cbi/header.htm
new file mode 100644
index 000000000..5d284d908
--- /dev/null
+++ b/luci-app-passwall/luasrc/view/passwall/cbi/header.htm
@@ -0,0 +1,2 @@
+
+
diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
index 9de708e5f..0e8940eff 100644
--- a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
+++ b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
@@ -403,7 +403,7 @@
function lv_expandGroupOfCurrent(cbid, listContainer, hiddenSelect) {
const key = hiddenSelect.options[0].value;
if (!key) return;
- const targetLi = listContainer.querySelector("li[data-key='" + key.replace(/'/g,"\\'") + "']");
+ const targetLi = listContainer.querySelector(`li[data-key='${CSS.escape(key)}']`);
if (!targetLi) return;
let parentGroup = targetLi.closest(".lv-group");
if (parentGroup) {
diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm
index 1f784d04b..d470ea4b1 100644
--- a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm
+++ b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm
@@ -402,7 +402,7 @@
function v_expandGroupOfCurrent(cbid, listContainer, hiddenInput) {
const key = hiddenInput.value;
if (!key) return;
- const targetLi = listContainer.querySelector("li[data-key='" + key.replace(/'/g,"\\'") + "']");
+ const targetLi = listContainer.querySelector(`li[data-key='${CSS.escape(key)}']`);
if (!targetLi) return;
let parentGroup = targetLi.closest(".v-group");
if (parentGroup) {
diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/sortable.htm b/luci-app-passwall/luasrc/view/passwall/cbi/sortable.htm
new file mode 100644
index 000000000..2f508eafd
--- /dev/null
+++ b/luci-app-passwall/luasrc/view/passwall/cbi/sortable.htm
@@ -0,0 +1,159 @@
+<%
+local api = self.api
+local appname = self.appname or api.appname
+local target_cfgname = self.target_cfgname
+-%>
+
+
+
\ No newline at end of file
diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/tvalue.htm b/luci-app-passwall/luasrc/view/passwall/cbi/tvalue.htm
new file mode 100644
index 000000000..8037b3d28
--- /dev/null
+++ b/luci-app-passwall/luasrc/view/passwall/cbi/tvalue.htm
@@ -0,0 +1,13 @@
+<%+cbi/valueheader%>
+
+<%+cbi/valuefooter%>
diff --git a/luci-app-passwall/luasrc/view/passwall/haproxy/js.htm b/luci-app-passwall/luasrc/view/passwall/haproxy/js.htm
index 22256df1a..6c84ed975 100644
--- a/luci-app-passwall/luasrc/view/passwall/haproxy/js.htm
+++ b/luci-app-passwall/luasrc/view/passwall/haproxy/js.htm
@@ -2,46 +2,8 @@
local api = require "luci.passwall.api"
local appname = api.appname
-%>
-
diff --git a/luci-app-passwall/luasrc/view/passwall/rule/rule_version.htm b/luci-app-passwall/luasrc/view/passwall/rule/rule_version.htm
index c0834a1c8..d8ec03784 100644
--- a/luci-app-passwall/luasrc/view/passwall/rule/rule_version.htm
+++ b/luci-app-passwall/luasrc/view/passwall/rule/rule_version.htm
@@ -4,16 +4,8 @@ local fs = api.fs
local has_old_geoip = fs.access("/tmp/bak_v2ray/geoip.dat")
local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
-%>
-
@@ -219,120 +187,5 @@ local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
}
);
}
-
- //分流规则添加拖拽排序
- document.addEventListener("DOMContentLoaded", function () {
- function initSortableForTable() {
- var section = document.getElementById("cbi-<%=api.appname%>-shunt_rules");
- if (!section) return;
-
- hideSortColumn(section);
-
- // === 插入 drag handle ===
- var rows = section.querySelectorAll("tr");
- rows.forEach(function(row) {
- var btn = row.querySelector(".cbi-button-remove:last-of-type");
- if (!btn) return;
- if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
- var handle = document.createElement("span");
- handle.className = "drag-handle center";
- handle.title = "<%:Drag to reorder%>";
- handle.innerHTML = "⠿";
- btn.after(handle);
- });
-
- // === 初始化 Sortable ===
- var table = section.getElementsByTagName("table")[0];
- if (!table) return;
- var root = table.tBodies[0] || table;
- if (root._sortable_initialized) return root._sortable_instance;
- root._sortable_initialized = true;
-
- // 保存原始顺序
- root._origOrder = getCurrentOrder(root);
-
- try {
- root._sortable_instance = Sortable.create(root, {
- handle: ".drag-handle",
- draggable: "tr.cbi-section-table-row",
- animation: 150,
- ghostClass: "dragging-row",
- fallbackOnBody: true,
- forceFallback: false,
- swapThreshold: 0.65,
- onEnd: function (evt) {
- updateHiddenInput(root, section);
- }
- });
- return root._sortable_instance;
- } catch (e) {
- root._sortable_initialized = false;
- console.error("Sortable init failed:", e);
- }
- }
-
- // 获取 table 当前行顺序
- function getCurrentOrder(root) {
- var order = [];
- var rows = root.querySelectorAll("tr.cbi-section-table-row");
- rows.forEach(function (tr) {
- var id = tr.id || "";
- if (id.startsWith("cbi-<%=api.appname%>-"))
- id = id.replace("cbi-<%=api.appname%>-", "");
- order.push(id);
- });
- return order;
- }
-
- // 拖拽完成后更新 hidden input
- function updateHiddenInput(root, section) {
- var newOrder = getCurrentOrder(root);
- var changed = newOrder.join(" ") !== root._origOrder.join(" ");
- var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
- if (hiddenInput) {
- hiddenInput.value = changed ? newOrder.join(" ") : "";
- }
- }
-
- // 隐藏18.06 up/down 列
- function hideSortColumn(section) {
- var table = section.getElementsByTagName("table")[0];
- if(!table) return;
- var rows = table.querySelectorAll("tr.cbi-section-table-row");
- if(!rows.length) return;
- var colCount = rows[0].children.length, sortCol = -1;
- for(var col=0; col= THRESHOLD)
- setTimeout(initSortableForTable, 200);
- else
- requestAnimationFrame(tick);
- }
- tick();
- })();
- });
//]]>
diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po
index 68f6484b1..548ca2fd1 100644
--- a/luci-app-passwall/po/zh-cn/passwall.po
+++ b/luci-app-passwall/po/zh-cn/passwall.po
@@ -1862,8 +1862,14 @@ msgstr "TLS 用于验证 leaf 证书的 name。"
msgid "Fetch Manually"
msgstr "手动获取"
-msgid "Fetch Failed"
-msgstr "获取失败"
+msgid "Fetch Failed!"
+msgstr "获取失败!"
+
+msgid "Requires OpenSSL to retrieve the TLS certificate chain fingerprint."
+msgstr "获取 TLS 证书链指纹需要 OpenSSL 支持。"
+
+msgid "QUIC protocols such as HY2 also require curl with http3 support."
+msgstr "HY2 等 QUIC 协议还需要 curl 支持 http3。"
msgid "TLS Certificate (PEM)"
msgstr "TLS 证书(PEM)"
@@ -2180,3 +2186,6 @@ msgstr "Gecko 包大小(最小)"
msgid "Gecko Packet Size (max)"
msgstr "Gecko 包大小(最大)"
+
+msgid "valid time (hh:mm)"
+msgstr "有效时间(hh:mm)"
diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile
index 772ca3acd..970189e57 100644
--- a/luci-app-passwall2/Makefile
+++ b/luci-app-passwall2/Makefile
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.7.16
-PKG_RELEASE:=63
+PKG_RELEASE:=64
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
index 914320be3..e8ba15897 100644
--- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua
+++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
@@ -177,8 +177,8 @@ function gen_outbound(flag, node, tag, proxy_table)
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
- pinnedPeerCertSha256 = node.tls_pinSHA256,
- verifyPeerCertByName = node.tls_CertByName,
+ pinnedPeerCertSha256 = node.tls_pinSHA256 or "",
+ verifyPeerCertByName = node.tls_CertByName or "",
echConfigList = (node.ech == "1") and node.ech_config or nil,
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
certificate = api.split(node.tls_certificate_pem, "\n"),
diff --git a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm
index 9de708e5f..0e8940eff 100644
--- a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm
+++ b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_listvalue_com.htm
@@ -403,7 +403,7 @@
function lv_expandGroupOfCurrent(cbid, listContainer, hiddenSelect) {
const key = hiddenSelect.options[0].value;
if (!key) return;
- const targetLi = listContainer.querySelector("li[data-key='" + key.replace(/'/g,"\\'") + "']");
+ const targetLi = listContainer.querySelector(`li[data-key='${CSS.escape(key)}']`);
if (!targetLi) return;
let parentGroup = targetLi.closest(".lv-group");
if (parentGroup) {
diff --git a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_value_com.htm b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_value_com.htm
index 1f784d04b..d470ea4b1 100644
--- a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_value_com.htm
+++ b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_value_com.htm
@@ -402,7 +402,7 @@
function v_expandGroupOfCurrent(cbid, listContainer, hiddenInput) {
const key = hiddenInput.value;
if (!key) return;
- const targetLi = listContainer.querySelector("li[data-key='" + key.replace(/'/g,"\\'") + "']");
+ const targetLi = listContainer.querySelector(`li[data-key='${CSS.escape(key)}']`);
if (!targetLi) return;
let parentGroup = targetLi.closest(".v-group");
if (parentGroup) {
diff --git a/virtualdog/Makefile b/virtualdog/Makefile
index c2013293c..f79df6a6b 100644
--- a/virtualdog/Makefile
+++ b/virtualdog/Makefile
@@ -7,8 +7,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=virtualdog
-PKG_VERSION:=0.0.2
-PKG_RELEASE:=2
+PKG_VERSION:=0.0.3
+PKG_RELEASE:=3
# Point this at the released binary tarball when you publish builds.
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/Carseason/openwrt-packages/releases/download/prebuilt/
diff --git a/xt-tls/Makefile b/xt-tls/Makefile
index 929c2e663..eb49e0a37 100644
--- a/xt-tls/Makefile
+++ b/xt-tls/Makefile
@@ -5,13 +5,13 @@ include $(INCLUDE_DIR)/kernel.mk
# The name and version of your package are used to define the variable to point to the build directory of your package: $(PKG_BUILD_DIR)
PKG_NAME:=xt_tls
PKG_VERSION:=0.3.5
-PKG_RELEASE:=1
+PKG_RELEASE:=2
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/Lochnair/xt_tls.git
PKG_SOURCE_PROTO:=git
PKG_SOURCE_SUBDIR=$(PKG_NAME)-$(PKG_VERSION)
-PKG_SOURCE_VERSION:=e393d3a343ad94b5b891b7dabc83fabf22742954
+PKG_SOURCE_VERSION:=137644fe539dd910c63ce527f6456ab38f148e3f
PKG_MAINTAINER:=Nils Andreas Svee
PKG_LICENSE=GPL-3.0