diff --git a/applications/luci-app-passwall/Makefile b/applications/luci-app-passwall/Makefile
index 44ae942705..d00bde9d2a 100644
--- a/applications/luci-app-passwall/Makefile
+++ b/applications/luci-app-passwall/Makefile
@@ -7,7 +7,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
-PKG_VERSION:=26.1.25
+PKG_VERSION:=26.2.6
PKG_RELEASE:=1
PKG_CONFIG_DEPENDS:= \
diff --git a/applications/luci-app-passwall/luasrc/controller/passwall.lua b/applications/luci-app-passwall/luasrc/controller/passwall.lua
index cc50ae8c9e..b07f21dbba 100644
--- a/applications/luci-app-passwall/luasrc/controller/passwall.lua
+++ b/applications/luci-app-passwall/luasrc/controller/passwall.lua
@@ -90,6 +90,7 @@ function index()
entry({"admin", "services", appname, "save_node_order"}, call("save_node_order")).leaf = true
entry({"admin", "services", appname, "save_node_list_opt"}, call("save_node_list_opt")).leaf = true
entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true
+ entry({"admin", "services", appname, "rollback_rules"}, call("rollback_rules")).leaf = true
entry({"admin", "services", appname, "subscribe_del_node"}, call("subscribe_del_node")).leaf = true
entry({"admin", "services", appname, "subscribe_del_all"}, call("subscribe_del_all")).leaf = true
entry({"admin", "services", appname, "subscribe_manual"}, call("subscribe_manual")).leaf = true
@@ -341,7 +342,7 @@ function connect_status()
local e = {}
e.use_time = ""
local url = http.formvalue("url")
- local baidu = string.find(url, "baidu")
+ local aliyun = string.find(url, "aliyun")
local chn_list = uci:get(appname, "@global[0]", "chn_list") or "direct"
local gfw_list = uci:get(appname, "@global[0]", "use_gfw_list") or "1"
local proxy_mode = uci:get(appname, "@global[0]", "tcp_proxy_mode") or "proxy"
@@ -349,11 +350,11 @@ function connect_status()
local socks_server = (localhost_proxy == "0") and api.get_cache_var("GLOBAL_TCP_SOCKS_server") or ""
url = "-w %{http_code}:%{time_pretransfer} " .. url
if socks_server and socks_server ~= "" then
- if (chn_list == "proxy" and gfw_list == "0" and proxy_mode ~= "proxy" and baidu ~= nil) or (chn_list == "0" and gfw_list == "0" and proxy_mode == "proxy") then
- -- 中国列表+百度 or 全局
+ if (chn_list == "proxy" and gfw_list == "0" and proxy_mode ~= "proxy" and aliyun ~= nil) or (chn_list == "0" and gfw_list == "0" and proxy_mode == "proxy") then
+ -- 中国列表+阿里 or 全局
url = "-x socks5h://" .. socks_server .. " " .. url
- elseif baidu == nil then
- -- 其他代理模式+百度以外网站
+ elseif aliyun == nil then
+ -- 其他代理模式+阿里以外网站
url = "-x socks5h://" .. socks_server .. " " .. url
end
end
@@ -424,7 +425,7 @@ function add_node()
uci:set(appname, uuid, "group", group)
end
- uci:set(appname, uuid, "type", "Xray")
+ uci:set(appname, uuid, "type", "Socks")
if redirect == "1" then
api.uci_save(uci, appname)
@@ -702,6 +703,24 @@ function update_rules()
http_write_json()
end
+function rollback_rules()
+ local arg_type = http.formvalue("type")
+ local rules = http.formvalue("rules") or ""
+ if arg_type ~= "geoip" and arg_type ~= "geosite" then
+ http_write_json_error()
+ return
+ end
+ local bak_dir = "/tmp/bak_v2ray/"
+ local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/")
+ local geo2rule = uci:get(appname, "@global_rules[0]", "geo2rule") or "0"
+ fs.move(bak_dir .. arg_type .. ".dat", geo_dir .. arg_type .. ".dat")
+ fs.rmdir(bak_dir)
+ if geo2rule == "1" and rules ~= "" then
+ luci.sys.call("lua /usr/share/passwall/rule_update.lua log '" .. rules .. "' rollback > /dev/null")
+ end
+ http_write_json_ok()
+end
+
function server_user_status()
local e = {}
e.index = http.formvalue("index")
@@ -864,27 +883,56 @@ function geo_view()
http.write(i18n.translate("Please enter query content!"))
return
end
+ local function get_rules(str, type)
+ local rules_id = {}
+ uci:foreach(appname, "shunt_rules", function(s)
+ local list
+ if type == "geoip" then list = s.ip_list else list = s.domain_list end
+ for line in string.gmatch((list or ""), "[^\r\n]+") do
+ if line ~= "" and not line:find("#") then
+ local prefix, main = line:match("^(.-):(.*)")
+ if not main then main = line end
+ if type == "geoip" and (api.datatypes.ipaddr(str) or api.datatypes.ip6addr(str)) then
+ if main:find(str, 1, true) then rules_id[#rules_id + 1] = s[".name"] end
+ else
+ if main == str then rules_id[#rules_id + 1] = s[".name"] end
+ end
+ end
+ end
+ end)
+ return rules_id
+ end
local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/")
local geosite_path = geo_dir .. "/geosite.dat"
local geoip_path = geo_dir .. "/geoip.dat"
local geo_type, file_path, cmd
local geo_string = ""
+ local bin = api.get_app_path("geoview")
if action == "lookup" then
if api.datatypes.ipaddr(value) or api.datatypes.ip6addr(value) then
geo_type, file_path = "geoip", geoip_path
else
geo_type, file_path = "geosite", geosite_path
end
- cmd = string.format("geoview -type %s -action lookup -input '%s' -value '%s' -lowmem=true", geo_type, file_path, value)
+ cmd = string.format(bin .. " -type %s -action lookup -input '%s' -value '%s' -lowmem=true", geo_type, file_path, value)
geo_string = luci.sys.exec(cmd):lower()
if geo_string ~= "" then
- local lines = {}
- for line in geo_string:gmatch("([^\n]*)\n?") do
- if line ~= "" then
- table.insert(lines, geo_type .. ":" .. line)
+ local lines, rules, seen = {}, {}, {}
+ for line in geo_string:gmatch("([^\n]+)") do
+ lines[#lines + 1] = geo_type .. ":" .. line
+ for _, r in ipairs(get_rules(line, geo_type) or {}) do
+ if not seen[r] then seen[r] = true; rules[#rules + 1] = r end
end
end
+ for _, r in ipairs(get_rules(value, geo_type) or {}) do
+ if not seen[r] then seen[r] = true; rules[#rules + 1] = r end
+ end
geo_string = table.concat(lines, "\n")
+ if #rules > 0 then
+ geo_string = geo_string .. "\n--------------------\n"
+ geo_string = geo_string .. i18n.translate("Rules containing this value:") .. "\n"
+ geo_string = geo_string .. table.concat(rules, "\n")
+ end
end
elseif action == "extract" then
local prefix, list = value:match("^(geoip:)(.*)$")
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
index 435390ce04..78ec4683d9 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
@@ -209,6 +209,10 @@ o.default = ""
o:depends({ _hide_node_option = false, use_global_config = false })
o.template = appname .. "/cbi/nodes_listvalue"
o.group = {}
+o.remove = function(self, section)
+ m:del(section, self.option)
+ m:del(section, "udp_node")
+end
o = s:option(DummyValue, "_tcp_node_bool", "")
o.template = "passwall/cbi/hidevalue"
@@ -219,14 +223,36 @@ o = s:option(ListValue, "udp_node", "" .. translate("UDP N
o.default = ""
o:value("", translate("Close"))
o:value("tcp", translate("Same as the tcp node"))
-o:depends({ _tcp_node_bool = "1" })
+o:depends({ _tcp_node_bool = "1", _node_sel_other = "1" })
o.template = appname .. "/cbi/nodes_listvalue"
o.group = {"",""}
+o.remove = function(self, section)
+ local v = s.fields["shunt_udp_node"]:formvalue(section)
+ if not f then
+ return m:del(section, self.option)
+ end
+end
+
+o = s:option(ListValue, "shunt_udp_node", "" .. translate("UDP Node") .. "")
+o:value("close", translate("Close"))
+o:value("tcp", translate("Same as the tcp node"))
+o:depends({ _tcp_node_bool = "1", _node_sel_shunt = "1" })
+o.cfgvalue = function(self, section)
+ local v = m:get(section, "udp_node") or ""
+ if v == "" then v = "close" end
+ if v ~= "close" and v ~= "tcp" then v = "tcp" end
+ return v
+end
+o.write = function(self, section, value)
+ if value == "close" then value = "" end
+ return m:set(section, "udp_node", value)
+end
o = s:option(DummyValue, "_udp_node_bool", "")
o.template = "passwall/cbi/hidevalue"
o.value = "1"
o:depends({ udp_node = "", ['!reverse'] = true })
+o:depends({ shunt_udp_node = "tcp" })
---- TCP Proxy Drop Ports
local TCP_PROXY_DROP_PORTS = m:get("@global_forwarding[0]", "tcp_proxy_drop_ports")
@@ -471,7 +497,7 @@ o:depends({dns_mode = "sing-box"})
o:depends({dns_mode = "xray"})
o:depends({_node_sel_shunt = "1"})
-o = s:option(Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
+o = s:option(Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the domain that proxy."))
o.default = "0"
o.rmempty = false
o:depends({dns_mode = "sing-box"})
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua
index b39d6df9c5..f87781f991 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua
@@ -16,7 +16,7 @@ local k, v
local com = require "luci.passwall.com"
for _, k in ipairs(com.order) do
v = com[k]
- if k ~= "geoview" and k ~= "chinadns-ng" then
+ if k ~= "chinadns-ng" then
o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name))
o.default = v.default_path or ("/usr/bin/" .. k)
o.rmempty = false
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
index 181688f720..f823687d6a 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
@@ -1,9 +1,9 @@
-local api = require "luci.passwall.api"
-local appname = "passwall"
-local datatypes = api.datatypes
+api = require "luci.passwall.api"
+appname = "passwall"
+datatypes = api.datatypes
local fs = api.fs
-local has_singbox = api.finded_com("sing-box")
-local has_xray = api.finded_com("xray")
+has_singbox = api.finded_com("sing-box")
+has_xray = api.finded_com("xray")
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")
@@ -95,7 +95,7 @@ end
m:append(Template(appname .. "/global/status"))
-local global_cfgid = m:get("@global[0]")[".name"]
+global_cfgid = m:get("@global[0]")[".name"]
s = m:section(TypedSection, "global")
s.anonymous = true
@@ -119,205 +119,61 @@ o.template = appname .. "/cbi/nodes_listvalue"
o:value("", translate("Close"))
o:value("tcp", translate("Same as the tcp node"))
o.group = {"",""}
+o:depends("_node_sel_other", "1")
+o.remove = function(self, section)
+ local v = s.fields["shunt_udp_node"]:formvalue(section)
+ if not f then
+ return m:del(section, self.option)
+ end
+end
-local tcp_node_id = m.uci:get(appname, global_cfgid, "tcp_node")
-local tcp_node = tcp_node_id and m.uci:get_all(appname, tcp_node_id) or {}
+o = s:taboption("Main", ListValue, "shunt_udp_node", "" .. translate("UDP Node") .. "")
+o:value("close", translate("Close"))
+o:value("tcp", translate("Same as the tcp node"))
+o:depends("_node_sel_shunt", "1")
+o.cfgvalue = function(self, section)
+ local v = m:get(section, "udp_node") or ""
+ if v == "" then v = "close" end
+ if v ~= "close" and v ~= "tcp" then v = "tcp" end
+ return v
+end
+o.write = function(self, section, value)
+ if value == "close" then value = "" end
+ return m:set(section, "udp_node", value)
+end
--- 分流
+-- Shunt Start
if (has_singbox or has_xray) and #nodes_table > 0 then
- if #normal_list > 0 and tcp_node.protocol == "_shunt" then
- local v = tcp_node
- if v then
- local function get_cfgvalue(shunt_node_id, option)
- return function(self, section)
- return m:get(shunt_node_id, option)
- end
- end
- local function get_write(shunt_node_id, option)
- return function(self, section, value)
- if s.fields["tcp_node"]:formvalue(section) == shunt_node_id then
- m:set(shunt_node_id, option, value)
- end
- end
- end
- local function get_remove(shunt_node_id, option)
- return function(self, section)
- if s.fields["tcp_node"]:formvalue(section) == shunt_node_id then
- m:del(shunt_node_id, option)
- end
- end
- end
- v.id = v[".name"]
- local vid = v.id
- -- shunt node type, Sing-Box or Xray
- o = s:taboption("Main", ListValue, vid .. "-type", translate("Type"))
- if has_xray then
- o:value("Xray", translate("Xray"))
- end
- if has_singbox then
- o:value("sing-box", "Sing-Box")
- end
- o:depends("tcp_node", v.id)
- o.cfgvalue = get_cfgvalue(v.id, "type")
- o.write = get_write(v.id, "type")
-
- -- pre-proxy
- o = s:taboption("Main", Flag, vid .. "-preproxy_enabled", translate("Preproxy"))
- o:depends("tcp_node", v.id)
- o.rmempty = false
- o.cfgvalue = get_cfgvalue(v.id, "preproxy_enabled")
- o.write = get_write(v.id, "preproxy_enabled")
-
- o = s:taboption("Main", ListValue, vid .. "-main_node", string.format('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default) has a separate switch that controls whether this rule uses the pre-proxy or not."))
- o:depends(vid .. "-preproxy_enabled", "1")
- o.template = appname .. "/cbi/nodes_listvalue"
- o.group = {}
- for k1, v1 in pairs(socks_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(balancing_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(urltest_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(iface_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(normal_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- o.cfgvalue = get_cfgvalue(v.id, "main_node")
- o.write = get_write(v.id, "main_node")
-
- o = s:taboption("Main", Flag, vid .. "-fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
- o:depends("tcp_node", v.id)
- o.cfgvalue = get_cfgvalue(v.id, "fakedns")
- o.write = get_write(v.id, "fakedns")
- o.remove = get_remove(v.id, "fakedns")
-
- m.uci:foreach(appname, "shunt_rules", function(e)
- local id = e[".name"]
- local node_option = vid .. "-" .. id .. "_node"
- if id and e.remarks then
- o = s:taboption("Main", ListValue, node_option, string.format('* %s', api.url("shunt_rules", id), e.remarks))
- o.cfgvalue = get_cfgvalue(v.id, id)
- o.write = get_write(v.id, id)
- o.remove = get_remove(v.id, id)
- o:depends("tcp_node", v.id)
- o:value("", translate("Close"))
- o:value("_default", translate("Default"))
- o:value("_direct", translate("Direct Connection"))
- o:value("_blackhole", translate("Blackhole"))
- o.template = appname .. "/cbi/nodes_listvalue"
- o.group = {"","","",""}
-
- local pt = s:taboption("Main", ListValue, vid .. "-".. id .. "_proxy_tag", string.format('* %s', e.remarks .. " " .. translate("Preproxy")))
- pt.cfgvalue = get_cfgvalue(v.id, id .. "_proxy_tag")
- pt.write = get_write(v.id, id .. "_proxy_tag")
- pt.remove = get_remove(v.id, id .. "_proxy_tag")
- pt:value("", translate("Close"))
- pt:value("main", translate("Preproxy Node"))
- pt:depends("__hide__", "1")
-
- local fakedns_tag = s:taboption("Main", Flag, vid .. "-".. id .. "_fakedns", string.format('* %s', e.remarks .. " " .. "FakeDNS"))
- fakedns_tag.cfgvalue = get_cfgvalue(v.id, id .. "_fakedns")
- fakedns_tag.write = get_write(v.id, id .. "_fakedns")
- fakedns_tag.remove = get_remove(v.id, id .. "_fakedns")
-
- for k1, v1 in pairs(socks_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- fakedns_tag:depends({ [node_option] = v1.id, [vid .. "-fakedns"] = "1" })
- end
- for k1, v1 in pairs(balancing_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(urltest_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(iface_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(normal_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- if not api.is_local_ip(v1.address) then --本地节点禁止使用前置
- pt:depends({ [node_option] = v1.id, [vid .. "-preproxy_enabled"] = "1" })
- end
- fakedns_tag:depends({ [node_option] = v1.id, [vid .. "-fakedns"] = "1" })
- end
- if v.default_node ~= "_direct" or v.default_node ~= "_blackhole" then
- fakedns_tag:depends({ [node_option] = "_default", [vid .. "-fakedns"] = "1" })
- end
- end
- end)
-
- local id = "default_node"
- o = s:taboption("Main", ListValue, vid .. "-" .. id, string.format('* %s', translate("Default")))
- o.cfgvalue = get_cfgvalue(v.id, id)
- o.write = get_write(v.id, id)
- o.remove = get_remove(v.id, id)
- o:depends("tcp_node", v.id)
- o:value("_direct", translate("Direct Connection"))
- o:value("_blackhole", translate("Blackhole"))
- o.template = appname .. "/cbi/nodes_listvalue"
- o.group = {"",""}
- for k1, v1 in pairs(socks_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(balancing_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(urltest_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(iface_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
- for k1, v1 in pairs(normal_list) do
- o:value(v1.id, v1.remark)
- o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default")
- end
-
- local id = "default_proxy_tag"
- o = s:taboption("Main", ListValue, vid .. "-" .. id, string.format('* %s', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
- o.cfgvalue = get_cfgvalue(v.id, id)
- o.write = get_write(v.id, id)
- o.remove = get_remove(v.id, id)
- o:value("", translate("Close"))
- o:value("main", translate("Preproxy Node"))
- o:depends("__hide__", "1")
- for k1, v1 in pairs(normal_list) do
- if v1.protocol ~= "_balancing" and v1.protocol ~= "_urltest" and not api.is_local_ip(v1.address) then
- o:depends({ [vid .. "-default_node"] = v1.id, [vid .. "-preproxy_enabled"] = "1" })
- end
- end
+ if #normal_list > 0 then
+ current_node_id = m.uci:get(appname, global_cfgid, "tcp_node")
+ current_node = current_node_id and m.uci:get_all(appname, current_node_id) or {}
+ if current_node.protocol == "_shunt" then
+ local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
+ setfenv(shunt_lua, getfenv(1))(m, s, {
+ node_id = current_node_id,
+ node = current_node,
+ socks_list = socks_list,
+ urltest_list = urltest_list,
+ balancing_list = balancing_list,
+ iface_list = iface_list,
+ normal_list = normal_list,
+ verify_option = s.fields["tcp_node"],
+ tab = "Shunt",
+ tab_desc = translate("Shunt Rule")
+ })
end
else
- local tips = s:taboption("Main", DummyValue, "tips", " ")
+ local tips = s:taboption("Main", DummyValue, "tips", " ")
tips.rawhtml = true
tips.cfgvalue = function(t, n)
return string.format('%s', translate("There are no available nodes, please add or subscribe nodes first."))
end
tips:depends({ tcp_node = "", ["!reverse"] = true })
for k, v in pairs(shunt_list) do
- tips:depends("udp_node", v.id)
+ tips:depends("tcp_node", v.id)
end
for k, v in pairs(balancing_list) do
- tips:depends("udp_node", v.id)
+ tips:depends("tcp_node", v.id)
end
end
end
@@ -387,10 +243,14 @@ o.default = "0"
-- TCP分流时dns过滤模式保存逻辑
function dns_mode_save(section)
- for k, v in pairs(shunt_list) do
- local f = s.fields[v.id .. "-type"]
- if f then
- local type_val = f:formvalue(section)
+ local f = s.fields["tcp_node"]
+ local id_val = f and f:formvalue(section) or ""
+ if id_val == "" then
+ return
+ end
+ for _, v in pairs(shunt_list) do
+ if v.id == id_val then
+ local type_val = v.type
if type_val and (type_val == "Xray" or type_val == "sing-box") then
local dns_shunt_val = s.fields["dns_shunt"]:formvalue(section)
local dns_mode_val = (dns_shunt_val ~= "smartdns") and "dns_mode" or "smartdns_dns_mode"
@@ -590,7 +450,7 @@ o:depends({dns_mode = "xray"})
o:depends("dns_shunt", "smartdns")
o:depends("_node_sel_shunt", "1")
-o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
+o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the domain that proxy."))
o.default = "0"
o:depends({dns_mode = "sing-box", dns_shunt = "dnsmasq"})
o:depends({dns_mode = "sing-box", dns_shunt = "chinadns-ng"})
@@ -928,8 +788,11 @@ for k, v in pairs(nodes_table) do
udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id })
- s.fields["xray_dns_mode"]:depends({ [v.id .. "-type"] = "Xray", _node_sel_shunt = "1" })
- s.fields["singbox_dns_mode"]:depends({ [v.id .. "-type"] = "sing-box", _node_sel_shunt = "1" })
+ if m:get(v.id, "type") == "Xray" then
+ s.fields["xray_dns_mode"]:depends({ tcp_node = v.id })
+ else
+ s.fields["singbox_dns_mode"]:depends({ tcp_node = v.id })
+ end
end
else
tcp:value(v.id, v["remark"])
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua
new file mode 100644
index 0000000000..9d571a295f
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua
@@ -0,0 +1,232 @@
+local m, s, data = ...
+
+if not data.node_id or not data.node then
+ return
+end
+
+local current_node_id = data.node_id
+
+local function get_cfgvalue()
+ return function(self, section)
+ return m:get(current_node_id, self.option)
+ end
+end
+local function get_write()
+ return function(self, section, value)
+ if data.verify_option then
+ if data.verify_option:formvalue(section) == current_node_id then
+ m:set(current_node_id, self.option, value)
+ end
+ else
+ m:set(current_node_id, self.option, value)
+ end
+ end
+end
+local function get_remove()
+ return function(self, section)
+ if data.verify_option then
+ if data.verify_option:formvalue(section) == current_node_id then
+ m:del(current_node_id, self.option)
+ end
+ else
+ m:del(current_node_id, self.option)
+ end
+ end
+end
+
+if data.tab then
+ s:tab(data.tab, data.tab_desc)
+end
+
+local function add_option(class, option_name, option_title, option_desc)
+ local a
+ if data.tab then
+ a = s:taboption(data.tab, class, option_name, option_title)
+ else
+ a = s:option(class, option_name, option_title)
+ end
+ if a then
+ if option_desc then
+ a.description = option_desc
+ end
+ a.cfgvalue = get_cfgvalue()
+ a.write = get_write()
+ a.remove = get_remove()
+ end
+ if data.verify_option then
+ a:depends(data.verify_option.option, current_node_id)
+ end
+ return a
+end
+
+local function add_depends(o, deps)
+ if #o.deps > 0 then
+ for index, value in ipairs(o.deps) do
+ for k, v in pairs(deps) do
+ o.deps[index][k] = v
+ end
+ end
+ else
+ o:depends(deps)
+ end
+end
+
+if data.node.type == "Xray" then
+ o = add_option(ListValue, "domainStrategy", translate("Domain Strategy"))
+ o:value("AsIs")
+ o:value("IPIfNonMatch")
+ o:value("IPOnDemand")
+ o.default = "IPOnDemand"
+ o.description = "
- " .. translate("'AsIs': Only use domain for routing. Default value.")
+ .. "
- " .. translate("'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again.")
+ .. "
- " .. translate("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.")
+ .. "
"
+
+ o = add_option(ListValue, "domainMatcher", translate("Domain matcher"))
+ o:value("hybrid")
+ o:value("linear")
+end
+
+o = add_option(Flag, "preproxy_enabled", translate("Preproxy") .. " " .. translate("Main switch"))
+
+main_node = add_option(ListValue, "main_node", string.format('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default) has a separate switch that controls whether this rule uses the pre-proxy or not."))
+add_depends(main_node, {["preproxy_enabled"] = true})
+main_node.template = appname .. "/cbi/nodes_listvalue"
+main_node.group = {}
+
+o = add_option(Flag, "fakedns", 'FakeDNS' .. " " .. translate("Main switch"), translate("Use FakeDNS work in the domain that proxy.") .. "
" ..
+ translate("Suitable scenarios for let the node servers get the target domain names.") .. "
" ..
+ translate("Such as: DNS unlocking of streaming media, reducing DNS query latency, etc."))
+
+local shunt_rules = {}
+m.uci:foreach(appname, "shunt_rules", function(e)
+ e.id = e[".name"]
+ e["_node_option"] = e[".name"]
+ e["_node_default"] = ""
+ e["_fakedns_option"] = e[".name"] .. "_fakedns"
+ e["_proxy_tag_option"] = e[".name"] .. "_proxy_tag"
+ table.insert(shunt_rules, e)
+end)
+table.insert(shunt_rules, {
+ id = ".default",
+ remarks = translate("Default"),
+ _node_option = "default_node",
+ _node_default = "_direct",
+ _fakedns_option = "default_fakedns",
+ _proxy_tag_option = "default_proxy_tag",
+})
+
+s2 = m:section(Table, shunt_rules, " ")
+s2.config = appname
+s2.sectiontype = "shunt_option_list"
+
+o = s2:option(DummyValue, "remarks", translate("Rule"))
+o.rawhtml = true
+o.cfgvalue = function(self, section)
+ if shunt_rules[section].id == ".default" then
+ return string.format('%s', shunt_rules[section].remarks)
+ else
+ return string.format('%s', api.url("shunt_rules", shunt_rules[section].id), shunt_rules[section].remarks)
+ end
+end
+
+_node = s2:option(Value, "_node", translate("Node"))
+_node.template = appname .. "/cbi/nodes_listvalue"
+_node.group = {"","","",""}
+_node:value("", translate("Close (Not use)"))
+_node:value("_default", translate("Use default node"))
+_node:value("_direct", translate("Direct Connection"))
+_node:value("_blackhole", translate("Blackhole (Block)"))
+_node.cfgvalue = function(self, section)
+ return m:get(current_node_id, shunt_rules[section]["_node_option"]) or shunt_rules[section]["_node_default"]
+end
+_node.write = function(self, section, value)
+ return m:set(current_node_id, shunt_rules[section]["_node_option"], value)
+end
+_node.remove = function(self, section)
+ return m:del(current_node_id, shunt_rules[section]["_node_option"])
+end
+
+o = s2:option(Flag, "_fakedns", string.format('FakeDNS', translate("Use FakeDNS work in the domain that proxy.") .. "\n" ..
+ translate("Suitable scenarios for let the node servers get the target domain names.") .. "\n" ..
+ translate("Such as: DNS unlocking of streaming media, reducing DNS query latency, etc.")))
+o.cfgvalue = function(self, section)
+ return m:get(current_node_id, shunt_rules[section]["_fakedns_option"])
+end
+o.write = function(self, section, value)
+ return m:set(current_node_id, shunt_rules[section]["_fakedns_option"], value)
+end
+o.remove = function(self, section)
+ return m:del(current_node_id, shunt_rules[section]["_fakedns_option"])
+end
+
+o = s2:option(ListValue, "_proxy_tag", string.format('%s', translate("Preproxy")))
+--TODO Choose any node as a pre-proxy. Instead of main node.
+o.template = appname .. "/cbi/nodes_listvalue"
+o.group = {"",""}
+o:value("", translate("Close (Not use)"))
+o:value("main", translate("Use preproxy node"))
+o.cfgvalue = function(self, section)
+ return m:get(current_node_id, shunt_rules[section]["_proxy_tag_option"])
+end
+o.write = function(self, section, value)
+ return m:set(current_node_id, shunt_rules[section]["_proxy_tag_option"], value)
+end
+o.remove = function(self, section)
+ return m:del(current_node_id, shunt_rules[section]["_proxy_tag_option"])
+end
+
+if data.socks_list then
+ for k, v in pairs(data.socks_list) do
+ main_node:value(v.id, v.remark)
+ main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+
+ _node:value(v.id, v.remark)
+ _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ end
+end
+if data.urltest_list then
+ for k, v in pairs(data.urltest_list) do
+ main_node:value(v.id, v.remark)
+ main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+
+ _node:value(v.id, v.remark)
+ _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ end
+end
+if data.balancing_list then
+ for k, v in pairs(data.balancing_list) do
+ main_node:value(v.id, v.remark)
+ main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+
+ _node:value(v.id, v.remark)
+ _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ end
+end
+if data.iface_list then
+ for k, v in pairs(data.iface_list) do
+ main_node:value(v.id, v.remark)
+ main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+
+ _node:value(v.id, v.remark)
+ _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ end
+end
+if data.normal_list then
+ for k, v in pairs(data.normal_list) do
+ main_node:value(v.id, v.remark)
+ main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+
+ _node:value(v.id, v.remark)
+ _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ end
+end
+
+if #main_node.keylist > 0 then
+ main_node.default = main_node.keylist[1]
+end
+
+local footer = Template(appname .. "/include/shunt_options")
+footer.api = api
+footer.id = current_node_id
+m:append(footer)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua
index 8f6bb76046..5c11d8b230 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua
@@ -1,5 +1,5 @@
-local api = require "luci.passwall.api"
-local appname = "passwall"
+api = require "luci.passwall.api"
+appname = "passwall"
m = Map(appname, translate("Node Config"))
m.redirect = api.url("node_list")
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
index b3a24812fc..90b5f81e2e 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
@@ -259,7 +259,6 @@ o:value("1", translate("Preproxy Node"))
o:value("2", translate("Landing Node"))
local descrStr = "Chained proxy works only with Xray or Sing-box nodes.
"
-descrStr = descrStr .. "The chained node must be the same type as your subscription node (Xray with Xray, Sing-box with Sing-box).
"
descrStr = descrStr .. "You can only use manual or imported nodes as chained nodes."
descrStr = translate(descrStr) .. "
" .. translate("Only support a layer of proxy.")
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
index 9bdf13292f..fa5d6d87fb 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
@@ -52,14 +52,14 @@ o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/google
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_Domain.txt", translate("ios_rule_script/ChinaMax_Domain"))
if has_xray or has_singbox then
- o = s:option(ListValue, "geoip_url", translate("GeoIP Update URL"))
+ o = s:option(Value, "geoip_url", translate("GeoIP Update URL"))
o:value("https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip.dat", translate("Loyalsoldier/geoip"))
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.dat", translate("MetaCubeX/geoip"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/geoip.dat", translate("Loyalsoldier/geoip (CDN)"))
o:value("https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", translate("MetaCubeX/geoip (CDN)"))
o.default = o.keylist[1]
- o = s:option(ListValue, "geosite_url", translate("Geosite Update URL"))
+ o = s:option(Value, "geosite_url", translate("Geosite Update URL"))
o:value("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", translate("Loyalsoldier/geosite"))
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geosite.dat", translate("MetaCubeX/geosite"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat", translate("Loyalsoldier/geosite (CDN)"))
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
index 77b7275a9f..753a7b8735 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
@@ -49,6 +49,13 @@ s.dynamic = false
remarks = s:option(Value, "remarks", translate("Remarks"))
remarks.default = arg[1]
remarks.rmempty = false
+remarks.validate = function(self, value, section)
+ value = api.trim(value)
+ if value == "" then
+ return nil, translate("Remark cannot be empty.")
+ end
+ return value
+end
protocol = s:option(MultiValue, "protocol", translate("Protocol"))
protocol:value("http")
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
index 928748461d..8efb50662f 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
@@ -1,12 +1,10 @@
local m, s = ...
-local api = require "luci.passwall.api"
-
if not api.finded_com("hysteria") then
return
end
-local type_name = "Hysteria2"
+type_name = "Hysteria2"
-- [[ Hysteria2 ]]
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua
index 2dbaa30054..b2139ad2c6 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua
@@ -1,12 +1,10 @@
local m, s = ...
-local api = require "luci.passwall.api"
-
if not api.is_finded("naive") then
return
end
-local type_name = "Naiveproxy"
+type_name = "Naiveproxy"
-- [[ Naive ]]
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
index dad012f772..ee2090d828 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
@@ -1,15 +1,12 @@
local m, s = ...
-local api = require "luci.passwall.api"
-
if not api.finded_com("xray") then
return
end
-local appname = "passwall"
local jsonc = api.jsonc
-local type_name = "Xray"
+type_name = "Xray"
-- [[ Xray ]]
@@ -28,16 +25,19 @@ local function _n(name)
return option_prefix .. name
end
+local formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "."
+local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
+
+if formvalue_proto then s.val["protocol"] = formvalue_proto end
+
+local arg_select_proto = luci.http.formvalue("select_proto") or ""
+
local ss_method_list = {
"none", "plain", "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
-local header_type_list = {
- "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
-}
-
local xray_version = api.get_app_version("xray")
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
@@ -56,15 +56,33 @@ if api.compare_versions(xray_version, ">=", "1.8.12") then
end
o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface"))
+function o.custom_cfgvalue(self, section)
+ if arg_select_proto ~= "" then
+ return arg_select_proto
+ else
+ return m:get(section, self.option:sub(1 + #option_prefix))
+ end
+end
+
+local load_balancing_options = s.val["protocol"] == "_balancing" or arg_select_proto == "_balancing"
+local load_shunt_options = s.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
+local load_iface_options = s.val["protocol"] == "_iface" or arg_select_proto == "_iface"
+local load_normal_options = true
+if load_balancing_options or load_shunt_options or load_iface_options then
+ load_normal_options = nil
+end
+if not arg_select_proto:find("_") then
+ load_normal_options = true
+end
-local nodes_table = {}
-local balancers_table = {}
-local fallback_table = {}
-local iface_table = {}
+local nodes_list = {}
+local balancing_list = {}
+local fallback_list = {}
+local iface_list = {}
local is_balancer = nil
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then
- nodes_table[#nodes_table + 1] = {
+ nodes_list[#nodes_list + 1] = {
id = e[".name"],
remark = e["remark"],
type = e["type"],
@@ -74,13 +92,13 @@ for k, e in ipairs(api.get_valid_nodes()) do
}
end
if e.protocol == "_balancing" then
- balancers_table[#balancers_table + 1] = {
+ balancing_list[#balancing_list + 1] = {
id = e[".name"],
remark = e["remark"],
group = e["group"]
}
if e[".name"] ~= arg[1] then
- fallback_table[#fallback_table + 1] = {
+ fallback_list[#fallback_list + 1] = {
id = e[".name"],
remark = e["remark"],
fallback = e["fallback_node"],
@@ -91,7 +109,7 @@ for k, e in ipairs(api.get_valid_nodes()) do
end
end
if e.protocol == "_iface" then
- iface_table[#iface_table + 1] = {
+ iface_list[#iface_list + 1] = {
id = e[".name"],
remark = e["remark"],
group = e["group"]
@@ -110,7 +128,7 @@ m.uci:foreach(appname, "socks", function(s)
end
end)
-if s.val["protocol"] == "_balancing" then -- [[ 负载均衡 Start ]]
+if load_balancing_options then -- [[ 负载均衡 Start ]]
o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, document"))
o:depends({ [_n("protocol")] = "_balancing" })
o.widget = "checkbox"
@@ -120,7 +138,7 @@ if s.val["protocol"] == "_balancing" then -- [[ 负载均衡 Start ]]
o:value(v.id, v.remark)
o.group[#o.group+1] = v.group or ""
end
- for i, v in pairs(nodes_table) do
+ for i, v in pairs(nodes_list) do
o:value(v.id, v.remark)
o.group[#o.group+1] = v.group or ""
end
@@ -164,9 +182,9 @@ if s.val["protocol"] == "_balancing" then -- [[ 负载均衡 Start ]]
o.template = appname .. "/cbi/nodes_listvalue"
o.group = {""}
local function check_fallback_chain(fb)
- for k, v in pairs(fallback_table) do
+ for k, v in pairs(fallback_list) do
if v.fallback == fb then
- fallback_table[k] = nil
+ fallback_list[k] = nil
check_fallback_chain(v.id)
end
end
@@ -179,11 +197,11 @@ if s.val["protocol"] == "_balancing" then -- [[ 负载均衡 Start ]]
o:value(v.id, v.remark)
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
- for k, v in pairs(fallback_table) do
+ for k, v in pairs(fallback_list) do
o:value(v.id, v.remark)
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
- for k, v in pairs(nodes_table) do
+ for k, v in pairs(nodes_list) do
o:value(v.id, v.remark)
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
@@ -220,146 +238,14 @@ if s.val["protocol"] == "_balancing" then -- [[ 负载均衡 Start ]]
o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.")
end -- [[ 负载均衡 End ]]
-if s.val["protocol"] == "_shunt" then -- [[ 分流模块 Start ]]
- local default_node = m.uci:get(appname, arg[1], "default_node") or "_direct"
- if #nodes_table > 0 then
- o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy"))
- o:depends({ [_n("protocol")] = "_shunt" })
-
- o = s:option(ListValue, _n("main_node"), string.format('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default) has a separate switch that controls whether this rule uses the pre-proxy or not."))
- o:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true })
- o.template = appname .. "/cbi/nodes_listvalue"
- o.group = {}
- for k, v in pairs(socks_list) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(balancers_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(iface_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(nodes_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
-
- o = s:option(Flag, _n("fakedns"), "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
- o:depends({ [_n("protocol")] = "_shunt" })
- end
- m.uci:foreach(appname, "shunt_rules", function(e)
- if e[".name"] and e.remarks then
- o = s:option(ListValue, _n(e[".name"]), string.format('* %s', api.url("shunt_rules", e[".name"]), e.remarks))
- o:value("", translate("Close"))
- o:value("_default", translate("Default"))
- o:value("_direct", translate("Direct Connection"))
- o:value("_blackhole", translate("Blackhole"))
- o:depends({ [_n("protocol")] = "_shunt" })
- o.template = appname .. "/cbi/nodes_listvalue"
- o.group = {"","","",""}
-
- if #nodes_table > 0 then
- local pt = s:option(ListValue, _n(e[".name"] .. "_proxy_tag"), string.format('* %s', e.remarks .. " " .. translate("Preproxy")))
- pt:value("", translate("Close"))
- pt:value("main", translate("Preproxy Node"))
- pt:depends("__hide__", "1")
-
- local fakedns_tag = s:option(Flag, _n(e[".name"] .. "_fakedns"), string.format('* %s', e.remarks .. " " .. "FakeDNS"))
-
- for k, v in pairs(socks_list) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = v.id })
- end
- for k, v in pairs(balancers_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(iface_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(nodes_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- if not api.is_local_ip(v.address) then --本地节点禁止使用前置
- pt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n(e[".name"])] = v.id })
- end
- fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = v.id })
- end
- if default_node ~= "_direct" or default_node ~= "_blackhole" then
- fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = "_default" })
- end
- end
- end
- end)
-
- o = s:option(DummyValue, _n("shunt_tips"), " ")
- o.not_rewrite = true
- o.rawhtml = true
- o.cfgvalue = function(t, n)
- return string.format('%s', translate("No shunt rules? Click me to go to add."))
- end
- o:depends({ [_n("protocol")] = "_shunt" })
-
- local o = s:option(ListValue, _n("default_node"), string.format('* %s', translate("Default")))
- o:depends({ [_n("protocol")] = "_shunt" })
- o:value("_direct", translate("Direct Connection"))
- o:value("_blackhole", translate("Blackhole"))
- o.template = appname .. "/cbi/nodes_listvalue"
- o.group = {"",""}
+if load_iface_options then -- [[ 自定义接口 Start ]]
+ o = s:option(Value, _n("iface"), translate("Interface"))
+ o.default = "eth1"
+ o:depends({ [_n("protocol")] = "_iface" })
+end -- [[ 自定义接口 End ]]
- if #nodes_table > 0 then
- for k, v in pairs(socks_list) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(balancers_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(iface_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- local dpt = s:option(ListValue, _n("default_proxy_tag"), string.format('* %s', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
- dpt:value("", translate("Close"))
- dpt:value("main", translate("Preproxy Node"))
- dpt:depends("__hide__", "1")
- for k, v in pairs(nodes_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- if not api.is_local_ip(v.address) then
- dpt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n("default_node")] = v.id })
- end
- end
- end
- o = s:option(ListValue, _n("domainStrategy"), translate("Domain Strategy"))
- o:value("AsIs")
- o:value("IPIfNonMatch")
- o:value("IPOnDemand")
- o.default = "IPOnDemand"
- o.description = "
- " .. translate("'AsIs': Only use domain for routing. Default value.")
- .. "
- " .. translate("'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again.")
- .. "
- " .. translate("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.")
- .. "
"
- o:depends({ [_n("protocol")] = "_shunt" })
-
- o = s:option(ListValue, _n("domainMatcher"), translate("Domain matcher"))
- o:value("hybrid")
- o:value("linear")
- o:depends({ [_n("protocol")] = "_shunt" })
-end -- [[ 分流模块 End ]]
-
-if s.val["protocol"] == "_iface" then -- [[ 自定义接口 Start ]]
-o = s:option(Value, _n("iface"), translate("Interface"))
-o.default = "eth1"
-o:depends({ [_n("protocol")] = "_iface" })
-end -- [[ 自定义接口 End ]]
+if load_normal_options then
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
@@ -499,17 +385,26 @@ o = s:option(Value, _n("tls_serverName"), translate("Domain"))
o:depends({ [_n("tls")] = true })
o:depends({ [_n("protocol")] = "hysteria2" })
-o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
-o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "hysteria2" })
+if api.compare_versions(os.date("%Y.%m.%d"), "<", "2026.6.1") then
+ o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
+ o.default = "0"
+ o:depends({ [_n("tls")] = true, [_n("reality")] = false })
+ o:depends({ [_n("protocol")] = "hysteria2" })
+end
-o = s:option(Value, _n("tls_chain_fingerprint"), translate("TLS Chain Fingerprint (SHA256)"), translate("Once set, connects only when the server’s chain fingerprint matches."))
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
+if api.compare_versions(xray_version, ">=", "26.1.31") then
+ o = s:option(Value, _n("tls_CertSha"), translate("TLS Chain Fingerprint (SHA256)"), translate("Once set, connects only when the server’s chain fingerprint matches."))
+ o:depends({ [_n("tls")] = true, [_n("reality")] = false })
+ o:depends({ [_n("protocol")] = "hysteria2" })
+
+ o = s:option(Value, _n("tls_CertByName"), translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
+ o:depends({ [_n("tls")] = true, [_n("reality")] = false })
+ o:depends({ [_n("protocol")] = "hysteria2" })
+end
o = s:option(Flag, _n("ech"), translate("ECH"))
o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
+o:depends({ [_n("tls")] = true, [_n("reality")] = false })
o:depends({ [_n("protocol")] = "hysteria2" })
o = s:option(TextValue, _n("ech_config"), translate("ECH Config"))
@@ -629,11 +524,17 @@ o:depends({ [_n("tcp_guise")] = "http" })
-- [[ mKCP部分 ]]--
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.'))
-for a, t in ipairs(header_type_list) do o:value(t) end
+o:value("none", "none")
+o:value("header-srtp", "srtp")
+o:value("header-utp", "utp")
+o:value("header-wechat", "wechat-video")
+o:value("header-dtls", "dtls")
+o:value("header-wireguard", "wireguard")
+o:value("header-dns", "dns")
o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
-o:depends({ [_n("mkcp_guise")] = "dns" })
+o:depends({ [_n("mkcp_guise")] = "header-dns" })
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
o.default = "1350"
@@ -830,8 +731,8 @@ o2:depends({ [_n("chain_proxy")] = "2" })
o2.template = appname .. "/cbi/nodes_listvalue"
o2.group = {}
-for k, v in pairs(nodes_table) do
- if v.type == "Xray" and v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then
+for k, v in pairs(nodes_list) do
+ if v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then
o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
o2:value(v.id, v.remark)
@@ -847,4 +748,20 @@ for i, v in ipairs(s.fields[_n("protocol")].keylist) do
end
end
+end
+-- [[ Normal single node End ]]
+
api.luci_types(arg[1], m, s, type_name, option_prefix)
+
+if load_shunt_options then
+ local current_node = m.uci:get_all(appname, arg[1]) or {}
+ local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
+ setfenv(shunt_lua, getfenv(1))(m, s, {
+ node_id = arg[1],
+ node = current_node,
+ socks_list = socks_list,
+ balancing_list = balancing_list,
+ iface_list = iface_list,
+ normal_list = nodes_list
+ })
+end
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
index 312cced5c3..38909ea437 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
@@ -29,6 +29,13 @@ local function _n(name)
return option_prefix .. name
end
+local formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "."
+local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
+
+if formvalue_proto then s.val["protocol"] = formvalue_proto end
+
+local arg_select_proto = luci.http.formvalue("select_proto") or ""
+
local ss_method_new_list = {
"none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
@@ -70,13 +77,31 @@ o:value("ssh", "SSH")
o:value("_urltest", translate("URLTest"))
o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface"))
+function o.custom_cfgvalue(self, section)
+ if arg_select_proto ~= "" then
+ return arg_select_proto
+ else
+ return m:get(section, self.option:sub(1 + #option_prefix))
+ end
+end
+
+local load_urltest_options = s.val["protocol"] == "_urltest" or arg_select_proto == "_urltest"
+local load_shunt_options = s.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
+local load_iface_options = s.val["protocol"] == "_iface" or arg_select_proto == "_iface"
+local load_normal_options = true
+if load_urltest_options or load_shunt_options or load_iface_options then
+ load_normal_options = nil
+end
+if not arg_select_proto:find("_") then
+ load_normal_options = true
+end
-local nodes_table = {}
-local iface_table = {}
-local urltest_table = {}
+local nodes_list = {}
+local iface_list = {}
+local urltest_list = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then
- nodes_table[#nodes_table + 1] = {
+ nodes_list[#nodes_list + 1] = {
id = e[".name"],
remark = e["remark"],
type = e["type"],
@@ -86,14 +111,14 @@ for k, e in ipairs(api.get_valid_nodes()) do
}
end
if e.protocol == "_iface" then
- iface_table[#iface_table + 1] = {
+ iface_list[#iface_list + 1] = {
id = e[".name"],
remark = e["remark"],
group = e["group"]
}
end
if e.protocol == "_urltest" then
- urltest_table[#urltest_table + 1] = {
+ urltest_list[#urltest_list + 1] = {
id = e[".name"],
remark = e["remark"],
group = e["group"]
@@ -112,7 +137,7 @@ m.uci:foreach(appname, "socks", function(s)
end
end)
-if s.val["protocol"] == "_urltest" then -- [[ URLTest Start ]]
+if load_urltest_options then -- [[ URLTest Start ]]
o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, document"))
o:depends({ [_n("protocol")] = "_urltest" })
o.widget = "checkbox"
@@ -122,7 +147,7 @@ if s.val["protocol"] == "_urltest" then -- [[ URLTest Start ]]
o:value(v.id, v.remark)
o.group[#o.group+1] = v.group or ""
end
- for i, v in pairs(nodes_table) do
+ for i, v in pairs(nodes_list) do
o:value(v.id, v.remark)
o.group[#o.group+1] = v.group or ""
end
@@ -191,131 +216,15 @@ if s.val["protocol"] == "_urltest" then -- [[ URLTest Start ]]
o.description = translate("Interrupt existing connections when the selected outbound has changed.")
end -- [[ URLTest End ]]
-if s.val["protocol"] == "_shunt" then -- [[ 分流模块 Start ]]
- local default_node = m.uci:get(appname, arg[1], "default_node") or "_direct"
- if #nodes_table > 0 then
- o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy"))
- o:depends({ [_n("protocol")] = "_shunt" })
-
- o = s:option(ListValue, _n("main_node"), string.format('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default) has a separate switch that controls whether this rule uses the pre-proxy or not."))
- o:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true })
- o.template = appname .. "/cbi/nodes_listvalue"
- o.group = {}
- for k, v in pairs(socks_list) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(urltest_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(iface_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(nodes_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
-
- o = s:option(Flag, _n("fakedns"), "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
- o:depends({ [_n("protocol")] = "_shunt" })
- end
- m.uci:foreach(appname, "shunt_rules", function(e)
- if e[".name"] and e.remarks then
- o = s:option(ListValue, _n(e[".name"]), string.format('* %s', api.url("shunt_rules", e[".name"]), e.remarks))
- o:value("", translate("Close"))
- o:value("_default", translate("Default"))
- o:value("_direct", translate("Direct Connection"))
- o:value("_blackhole", translate("Blackhole"))
- o:depends({ [_n("protocol")] = "_shunt" })
- o.template = appname .. "/cbi/nodes_listvalue"
- o.group = {"","","",""}
-
- if #nodes_table > 0 then
- local pt = s:option(ListValue, _n(e[".name"] .. "_proxy_tag"), string.format('* %s', e.remarks .. " " .. translate("Preproxy")))
- pt:value("", translate("Close"))
- pt:value("main", translate("Preproxy Node"))
- pt:depends("__hide__", "1")
-
- local fakedns_tag = s:option(Flag, _n(e[".name"] .. "_fakedns"), string.format('* %s', e.remarks .. " " .. "FakeDNS"))
-
- for k, v in pairs(socks_list) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = v.id })
- end
- for k, v in pairs(urltest_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(iface_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(nodes_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- if not api.is_local_ip(v.address) then --本地节点禁止使用前置
- pt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n(e[".name"])] = v.id })
- end
- fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = v.id })
- end
- if default_node ~= "_direct" or default_node ~= "_blackhole" then
- fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = "_default" })
- end
- end
- end
- end)
-
- o = s:option(DummyValue, _n("shunt_tips"), " ")
- o.not_rewrite = true
- o.rawhtml = true
- o.cfgvalue = function(t, n)
- return string.format('%s', translate("No shunt rules? Click me to go to add."))
- end
- o:depends({ [_n("protocol")] = "_shunt" })
-
- local o = s:option(ListValue, _n("default_node"), string.format('* %s', translate("Default")))
- o:depends({ [_n("protocol")] = "_shunt" })
- o:value("_direct", translate("Direct Connection"))
- o:value("_blackhole", translate("Blackhole"))
- o.template = appname .. "/cbi/nodes_listvalue"
- o.group = {"",""}
-
- if #nodes_table > 0 then
- for k, v in pairs(socks_list) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(urltest_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- for k, v in pairs(iface_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- local dpt = s:option(ListValue, _n("default_proxy_tag"), string.format('* %s', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
- dpt:value("", translate("Close"))
- dpt:value("main", translate("Preproxy Node"))
- dpt:depends("__hide__", "1")
- for k, v in pairs(nodes_table) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- if not api.is_local_ip(v.address) then
- dpt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n("default_node")] = v.id })
- end
- end
- end
-end -- [[ 分流模块 End ]]
-
-if s.val["protocol"] == "_iface" then -- [[ 自定义接口 Start ]]
+if load_iface_options then -- [[ 自定义接口 Start ]]
o = s:option(Value, _n("iface"), translate("Interface"))
o.default = "eth1"
o:depends({ [_n("protocol")] = "_iface" })
end
+
+if load_normal_options then
+
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
o = s:option(Value, _n("port"), translate("Port"))
@@ -868,8 +777,8 @@ o2:depends({ [_n("chain_proxy")] = "2" })
o2.template = appname .. "/cbi/nodes_listvalue"
o2.group = {}
-for k, v in pairs(nodes_table) do
- if v.type == "sing-box" and v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then
+for k, v in pairs(nodes_list) do
+ if v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then
o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
o2:value(v.id, v.remark)
@@ -877,4 +786,20 @@ for k, v in pairs(nodes_table) do
end
end
+end
+-- [[ Normal single node End ]]
+
api.luci_types(arg[1], m, s, type_name, option_prefix)
+
+if load_shunt_options then
+ local current_node = m.uci:get_all(appname, arg[1]) or {}
+ local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
+ setfenv(shunt_lua, getfenv(1))(m, s, {
+ node_id = arg[1],
+ node = current_node,
+ socks_list = socks_list,
+ urltest_list = urltest_list,
+ iface_list = iface_list,
+ normal_list = nodes_list
+ })
+end
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua
index feb15c029b..9f6946d7ae 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua
@@ -1,12 +1,10 @@
local m, s = ...
-local api = require "luci.passwall.api"
-
if not api.is_finded("sslocal") then
return
end
-local type_name = "SS-Rust"
+type_name = "SS-Rust"
-- [[ Shadowsocks Rust ]]
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua
index 1b28c488bc..537cc9ecbc 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua
@@ -1,12 +1,10 @@
local m, s = ...
-local api = require "luci.passwall.api"
-
if not api.is_finded("ss-local") and not api.is_finded("ss-redir") then
return
end
-local type_name = "SS"
+type_name = "SS"
-- [[ Shadowsocks Libev ]]
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua
index c8f5ac6f3c..be7823faa0 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua
@@ -1,12 +1,10 @@
local m, s = ...
-local api = require "luci.passwall.api"
-
if not api.is_finded("ssr-local") and not api.is_finded("ssr-redir")then
return
end
-local type_name = "SSR"
+type_name = "SSR"
-- [[ ShadowsocksR Libev ]]
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua
index 22dd8e24ae..27694f72d3 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua
@@ -1,12 +1,10 @@
local m, s = ...
-local api = require "luci.passwall.api"
-
if not api.is_finded("trojan-plus") then
return
end
-local type_name = "Trojan-Plus"
+type_name = "Trojan-Plus"
-- [[ Trojan Plus ]]
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/tuic.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/tuic.lua
index 0d882fb1e6..5ee5d8ff3c 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/tuic.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/tuic.lua
@@ -1,12 +1,10 @@
local m, s = ...
-local api = require "luci.passwall.api"
-
if not api.is_finded("tuic-client") then
return
end
-local type_name = "TUIC"
+type_name = "TUIC"
-- [[ TUIC ]]
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
index d42e2e3c57..a5ab5c6c9c 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
@@ -20,10 +20,6 @@ local x_ss_method_list = {
"none", "plain", "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
-local header_type_list = {
- "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
-}
-
-- [[ Xray ]]
s.fields["type"]:value(type_name, "Xray")
@@ -243,7 +239,7 @@ end
o = s:option(Flag, _n("ech"), translate("ECH"))
o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
+o:depends({ [_n("tls")] = true, [_n("reality")] = false })
o = s:option(TextValue, _n("ech_key"), translate("ECH Key"))
o.default = ""
@@ -318,11 +314,17 @@ o:depends({ [_n("tcp_guise")] = "http" })
-- [[ mKCP部分 ]]--
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.'))
-for a, t in ipairs(header_type_list) do o:value(t) end
+o:value("none", "none")
+o:value("header-srtp", "srtp")
+o:value("header-utp", "utp")
+o:value("header-wechat", "wechat-video")
+o:value("header-dtls", "dtls")
+o:value("header-wireguard", "wireguard")
+o:value("header-dns", "dns")
o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
-o:depends({ [_n("mkcp_guise")] = "dns" })
+o:depends({ [_n("mkcp_guise")] = "header-dns" })
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
o.default = "1350"
diff --git a/applications/luci-app-passwall/luasrc/passwall/api.lua b/applications/luci-app-passwall/luasrc/passwall/api.lua
index 05c39fa611..d1140c03e2 100644
--- a/applications/luci-app-passwall/luasrc/passwall/api.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/api.lua
@@ -457,11 +457,10 @@ function get_valid_nodes()
uci:foreach(appname, "nodes", function(e)
e.id = e[".name"]
if e.type and e.remarks then
- if (e.type == "sing-box" or e.type == "Xray") and e.protocol and
- (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
- local type = e.type
- if type == "sing-box" then type = "Sing-Box" end
- e["remark"] = "%s:[%s] " % {type .. " " .. i18n.translatef(e.protocol), e.remarks}
+ local type_name = e.type
+ if e.type == "sing-box" then type_name = "Sing-Box" end
+ if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
+ e["remark"] = trim("%s:[%s]" % {type_name .. " " .. i18n.translatef(e.protocol), e.remarks})
e["node_type"] = "special"
if not e.group or e.group == "" then
default_nodes[#default_nodes + 1] = e
@@ -473,8 +472,7 @@ function get_valid_nodes()
if port and e.address then
local address = e.address
if is_ip(address) or datatypes.hostname(address) then
- local type = e.type
- if (type == "sing-box" or type == "Xray") and e.protocol then
+ if (e.type == "sing-box" or e.type == "Xray") and e.protocol then
local protocol = e.protocol
if protocol == "vmess" then
protocol = "VMess"
@@ -497,14 +495,13 @@ function get_valid_nodes()
else
protocol = protocol:gsub("^%l",string.upper)
end
- if type == "sing-box" then type = "Sing-Box" end
- type = type .. " " .. protocol
+ type_name = type_name .. " " .. protocol
end
if is_ipv6(address) then address = get_ipv6_full(address) end
- e["remark"] = "%s:[%s]" % {type, e.remarks}
+ e["remark"] = trim("%s:[%s]" % {type_name, e.remarks})
if show_node_info == "1" then
port = port:gsub(":", "-")
- e["remark"] = "%s:[%s] %s:%s" % {type, e.remarks, address, port}
+ e["remark"] = trim("%s:[%s] %s:%s" % {type_name, e.remarks, address, port})
end
e.node_type = "normal"
if not e.group or e.group == "" then
@@ -524,11 +521,11 @@ end
function get_node_remarks(n)
local remarks = ""
if n then
- if (n.type == "sing-box" or n.type == "Xray") and n.protocol and
- (n.protocol == "_balancing" or n.protocol == "_shunt" or n.protocol == "_iface" or n.protocol == "_urltest") then
- remarks = "%s:[%s] " % {n.type .. " " .. i18n.translatef(n.protocol), n.remarks}
+ local type_name = n.type
+ if n.type == "sing-box" then type_name = "Sing-Box" end
+ if n.protocol and (n.protocol == "_balancing" or n.protocol == "_shunt" or n.protocol == "_iface" or n.protocol == "_urltest") then
+ remarks = trim("%s:[%s]" % {type_name .. " " .. i18n.translatef(n.protocol), n.remarks})
else
- local type2 = n.type
if (n.type == "sing-box" or n.type == "Xray") and n.protocol then
local protocol = n.protocol
if protocol == "vmess" then
@@ -552,10 +549,9 @@ function get_node_remarks(n)
else
protocol = protocol:gsub("^%l",string.upper)
end
- if type2 == "sing-box" then type2 = "Sing-Box" end
- type2 = type2 .. " " .. protocol
+ type_name = type_name .. " " .. protocol
end
- remarks = "%s:[%s]" % {type2, n.remarks}
+ remarks = trim("%s:[%s]" % {type_name, n.remarks})
end
end
return remarks
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_hysteria2.lua b/applications/luci-app-passwall/luasrc/passwall/util_hysteria2.lua
index f2668e4732..4053bc91f4 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_hysteria2.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_hysteria2.lua
@@ -31,25 +31,25 @@ function gen_config_server(node)
end
function gen_config(var)
- local node_id = var["-node"]
+ local node_id = var["node"]
if not node_id then
- print("-node 不能为空")
+ print("node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
- local local_tcp_redir_port = var["-local_tcp_redir_port"]
- local local_udp_redir_port = var["-local_udp_redir_port"]
- local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
- local local_socks_port = var["-local_socks_port"]
- local local_socks_username = var["-local_socks_username"]
- local local_socks_password = var["-local_socks_password"]
- local local_http_address = var["-local_http_address"] or "0.0.0.0"
- local local_http_port = var["-local_http_port"]
- local local_http_username = var["-local_http_username"]
- local local_http_password = var["-local_http_password"]
- local tcp_proxy_way = var["-tcp_proxy_way"]
- local server_host = var["-server_host"] or node.address
- local server_port = var["-server_port"] or node.port
+ local local_tcp_redir_port = var["local_tcp_redir_port"]
+ local local_udp_redir_port = var["local_udp_redir_port"]
+ local local_socks_address = var["local_socks_address"] or "0.0.0.0"
+ local local_socks_port = var["local_socks_port"]
+ local local_socks_username = var["local_socks_username"]
+ local local_socks_password = var["local_socks_password"]
+ local local_http_address = var["local_http_address"] or "0.0.0.0"
+ local local_http_port = var["local_http_port"]
+ local local_http_username = var["local_http_username"]
+ local local_http_password = var["local_http_password"]
+ local tcp_proxy_way = var["tcp_proxy_way"]
+ local server_host = var["server_host"] or node.address
+ local server_port = var["server_port"] or node.port
if api.is_ipv6(server_host) then
server_host = api.get_ipv6_full(server_host)
@@ -136,6 +136,10 @@ _G.gen_config = gen_config
if arg[1] then
local func =_G[arg[1]]
if func then
- print(func(api.get_function_args(arg)))
+ local var = nil
+ if arg[2] then
+ var = jsonc.parse(arg[2])
+ end
+ print(func(var))
end
end
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_naiveproxy.lua b/applications/luci-app-passwall/luasrc/passwall/util_naiveproxy.lua
index ee095c1e6c..194a87ef7f 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_naiveproxy.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_naiveproxy.lua
@@ -4,17 +4,17 @@ local uci = api.uci
local jsonc = api.jsonc
function gen_config(var)
- local node_id = var["-node"]
+ local node_id = var["node"]
if not node_id then
- print("-node 不能为空")
+ print("node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
- local run_type = var["-run_type"]
- local local_addr = var["-local_addr"]
- local local_port = var["-local_port"]
- local server_host = var["-server_host"] or node.address
- local server_port = var["-server_port"] or node.port
+ local run_type = var["run_type"]
+ local local_addr = var["local_addr"]
+ local local_port = var["local_port"]
+ local server_host = var["server_host"] or node.address
+ local server_port = var["server_port"] or node.port
if api.is_ipv6(server_host) then
server_host = api.get_ipv6_full(server_host)
@@ -34,6 +34,10 @@ _G.gen_config = gen_config
if arg[1] then
local func =_G[arg[1]]
if func then
- print(func(api.get_function_args(arg)))
+ local var = nil
+ if arg[2] then
+ var = jsonc.parse(arg[2])
+ end
+ print(func(var))
end
end
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua b/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua
index 4b70f06a98..d17e91e0d6 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua
@@ -31,29 +31,29 @@ end
local plugin_sh, plugin_bin
function gen_config(var)
- local node_id = var["-node"]
+ local node_id = var["node"]
if not node_id then
- print("-node 不能为空")
+ print("node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
- local server_host = var["-server_host"] or node.address
- local server_port = var["-server_port"] or node.port
- local local_addr = var["-local_addr"]
- local local_port = var["-local_port"]
- local mode = var["-mode"]
- local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
- local local_socks_port = var["-local_socks_port"]
- local local_socks_username = var["-local_socks_username"]
- local local_socks_password = var["-local_socks_password"]
- local local_http_address = var["-local_http_address"] or "0.0.0.0"
- local local_http_port = var["-local_http_port"]
- local local_http_username = var["-local_http_username"]
- local local_http_password = var["-local_http_password"]
- local local_tcp_redir_port = var["-local_tcp_redir_port"]
- local local_tcp_redir_address = var["-local_tcp_redir_address"] or "0.0.0.0"
- local local_udp_redir_port = var["-local_udp_redir_port"]
- local local_udp_redir_address = var["-local_udp_redir_address"] or "0.0.0.0"
+ local server_host = var["server_host"] or node.address
+ local server_port = var["server_port"] or node.port
+ local local_addr = var["local_addr"]
+ local local_port = var["local_port"]
+ local mode = var["mode"]
+ local local_socks_address = var["local_socks_address"] or "0.0.0.0"
+ local local_socks_port = var["local_socks_port"]
+ local local_socks_username = var["local_socks_username"]
+ local local_socks_password = var["local_socks_password"]
+ local local_http_address = var["local_http_address"] or "0.0.0.0"
+ local local_http_port = var["local_http_port"]
+ local local_http_username = var["local_http_username"]
+ local local_http_password = var["local_http_password"]
+ local local_tcp_redir_port = var["local_tcp_redir_port"]
+ local local_tcp_redir_address = var["local_tcp_redir_address"] or "0.0.0.0"
+ local local_udp_redir_port = var["local_udp_redir_port"]
+ local local_udp_redir_address = var["local_udp_redir_address"] or "0.0.0.0"
if api.is_ipv6(server_host) then
server_host = api.get_ipv6_only(server_host)
@@ -62,7 +62,7 @@ function gen_config(var)
local plugin_file
if node.plugin and node.plugin ~= "" and node.plugin ~= "none" then
- plugin_sh = var["-plugin_sh"] or ""
+ plugin_sh = var["plugin_sh"] or ""
plugin_file = (plugin_sh ~="") and plugin_sh or node.plugin
plugin_bin = node.plugin
end
@@ -77,7 +77,7 @@ function gen_config(var)
timeout = tonumber(node.timeout),
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false,
reuse_port = true,
- tcp_tproxy = var["-tcp_tproxy"] and true or nil
+ tcp_tproxy = var["tcp_tproxy"] and true or nil
}
if node.type == "SS" then
@@ -123,7 +123,7 @@ function gen_config(var)
table.insert(config.locals, {
protocol = "redir",
mode = "tcp_only",
- tcp_redir = var["-tcp_tproxy"] and "tproxy" or nil,
+ tcp_redir = var["tcp_tproxy"] and "tproxy" or nil,
local_address = local_tcp_redir_address,
local_port = tonumber(local_tcp_redir_port)
})
@@ -146,7 +146,11 @@ _G.gen_config = gen_config
if arg[1] then
local func =_G[arg[1]]
if func then
- print(func(api.get_function_args(arg)))
+ local var = nil
+ if arg[2] then
+ var = jsonc.parse(arg[2])
+ end
+ print(func(var))
if plugin_sh and plugin_sh ~="" and plugin_bin then
local f = io.open(plugin_sh, "w")
f:write("#!/bin/sh\n")
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua
index 9f18299e8c..43678e081a 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua
@@ -43,12 +43,13 @@ function geo_convert_srs(var)
if check_geoview() ~= 1 then
return
end
- local geo_path = var["-geo_path"]
- local prefix = var["-prefix"]
- local rule_name = var["-rule_name"]
+ local geo_path = var["geo_path"]
+ local prefix = var["prefix"]
+ local rule_name = var["rule_name"]
local output_srs_file = GEO_VAR.TO_SRS_PATH .. prefix .. "-" .. rule_name .. ".srs"
if not fs.access(output_srs_file) then
- local cmd = string.format("geoview -type %s -action convert -input '%s' -list '%s' -output '%s' -lowmem=true",
+ local bin = api.get_app_path("geoview")
+ local cmd = string.format(bin .. " -type %s -action convert -input '%s' -list '%s' -output '%s' -lowmem=true",
prefix, geo_path, rule_name, output_srs_file)
sys.call(cmd)
local status = fs.access(output_srs_file) and "success." or "failed!"
@@ -72,7 +73,7 @@ local function convert_geofile()
sys.call("rm -rf " .. GEO_VAR.TO_SRS_PATH .. prefix .. "-*.srs" )
end
for k in pairs(tags) do
- geo_convert_srs({["-geo_path"] = file_path, ["-prefix"] = prefix, ["-rule_name"] = k})
+ geo_convert_srs({["geo_path"] = file_path, ["prefix"] = prefix, ["rule_name"] = k})
end
end
end
@@ -892,42 +893,42 @@ function gen_config_server(node)
end
function gen_config(var)
- local flag = var["-flag"]
- local log = var["-log"] or "0"
- local loglevel = var["-loglevel"] or "warn"
- local logfile = var["-logfile"] or "/dev/null"
- local node_id = var["-node"]
- local server_host = var["-server_host"]
- local server_port = var["-server_port"]
- local tcp_proxy_way = var["-tcp_proxy_way"]
- local tcp_redir_port = var["-tcp_redir_port"]
- local udp_redir_port = var["-udp_redir_port"]
- local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
- local local_socks_port = var["-local_socks_port"]
- local local_socks_username = var["-local_socks_username"]
- local local_socks_password = var["-local_socks_password"]
- local local_http_address = var["-local_http_address"] or "0.0.0.0"
- local local_http_port = var["-local_http_port"]
- local local_http_username = var["-local_http_username"]
- local local_http_password = var["-local_http_password"]
- local dns_listen_port = var["-dns_listen_port"]
- local direct_dns_port = var["-direct_dns_port"]
- local direct_dns_udp_server = var["-direct_dns_udp_server"]
- local direct_dns_tcp_server = var["-direct_dns_tcp_server"]
- local direct_dns_query_strategy = var["-direct_dns_query_strategy"]
- local remote_dns_server = var["-remote_dns_server"]
- local remote_dns_port = var["-remote_dns_port"]
- local remote_dns_udp_server = var["-remote_dns_udp_server"]
- local remote_dns_tcp_server = var["-remote_dns_tcp_server"]
- local remote_dns_doh_url = var["-remote_dns_doh_url"]
- local remote_dns_doh_host = var["-remote_dns_doh_host"]
- local remote_dns_client_ip = var["-remote_dns_client_ip"]
- local remote_dns_query_strategy = var["-remote_dns_query_strategy"]
- local remote_dns_fake = var["-remote_dns_fake"]
- local dns_cache = var["-dns_cache"]
- local dns_socks_address = var["-dns_socks_address"]
- local dns_socks_port = var["-dns_socks_port"]
- local no_run = var["-no_run"]
+ local flag = var["flag"]
+ local log = var["log"] or "0"
+ local loglevel = var["loglevel"] or "warn"
+ local logfile = var["logfile"] or "/dev/null"
+ local node_id = var["node"]
+ local server_host = var["server_host"]
+ local server_port = var["server_port"]
+ local tcp_proxy_way = var["tcp_proxy_way"]
+ local tcp_redir_port = var["tcp_redir_port"]
+ local udp_redir_port = var["udp_redir_port"]
+ local local_socks_address = var["local_socks_address"] or "0.0.0.0"
+ local local_socks_port = var["local_socks_port"]
+ local local_socks_username = var["local_socks_username"]
+ local local_socks_password = var["local_socks_password"]
+ local local_http_address = var["local_http_address"] or "0.0.0.0"
+ local local_http_port = var["local_http_port"]
+ local local_http_username = var["local_http_username"]
+ local local_http_password = var["local_http_password"]
+ local dns_listen_port = var["dns_listen_port"]
+ local direct_dns_port = var["direct_dns_port"]
+ local direct_dns_udp_server = var["direct_dns_udp_server"]
+ local direct_dns_tcp_server = var["direct_dns_tcp_server"]
+ local direct_dns_query_strategy = var["direct_dns_query_strategy"]
+ local remote_dns_server = var["remote_dns_server"]
+ local remote_dns_port = var["remote_dns_port"]
+ local remote_dns_udp_server = var["remote_dns_udp_server"]
+ local remote_dns_tcp_server = var["remote_dns_tcp_server"]
+ local remote_dns_doh_url = var["remote_dns_doh_url"]
+ local remote_dns_doh_host = var["remote_dns_doh_host"]
+ local remote_dns_client_ip = var["remote_dns_client_ip"]
+ local remote_dns_query_strategy = var["remote_dns_query_strategy"]
+ local remote_dns_fake = var["remote_dns_fake"]
+ local dns_cache = var["dns_cache"]
+ local dns_socks_address = var["dns_socks_address"]
+ local dns_socks_port = var["dns_socks_port"]
+ local no_run = var["no_run"]
local dns_domain_rules = {}
local dns = nil
@@ -1078,7 +1079,29 @@ function gen_config(var)
table.insert(inbounds, inbound)
end
- local function gen_urltest(_node)
+ function gen_socks_config_node(node_id, socks_id, remarks)
+ if node_id then
+ socks_id = node_id:sub(1 + #"Socks_")
+ end
+ local result
+ local socks_node = uci:get_all(appname, socks_id) or nil
+ if socks_node then
+ if not remarks then
+ remarks = "Socks_" .. socks_node.port
+ end
+ result = {
+ remarks = remarks,
+ type = "sing-box",
+ protocol = "socks",
+ address = "127.0.0.1",
+ port = socks_node.port,
+ uot = "1"
+ }
+ end
+ return result
+ end
+
+ function gen_urltest(_node)
local urltest_id = _node[".name"]
local urltest_tag = "urltest-" .. urltest_id
-- existing urltest
@@ -1104,25 +1127,16 @@ function gen_config(var)
if is_new_ut_node then
local ut_node
if ut_node_id:find("Socks_") then
- local socks_id = ut_node_id:sub(1 + #"Socks_")
- local socks_node = uci:get_all(appname, socks_id) or nil
- if socks_node then
- ut_node = {
- type = "sing-box",
- protocol = "socks",
- address = "127.0.0.1",
- port = socks_node.port,
- uot = "1",
- remarks = "Socks_" .. socks_node.port
- }
- end
+ ut_node = gen_socks_config_node(ut_node_id)
else
ut_node = uci:get_all(appname, ut_node_id)
end
if ut_node then
local outbound = gen_outbound(flag, ut_node, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
if outbound then
- outbound.tag = outbound.tag .. ":" .. ut_node.remarks
+ if ut_node.remarks then
+ outbound.tag = outbound.tag .. ":" .. ut_node.remarks
+ end
table.insert(outbounds, outbound)
valid_nodes[#valid_nodes + 1] = outbound.tag
end
@@ -1144,7 +1158,7 @@ function gen_config(var)
return urltest_tag
end
- local function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name)
+ function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name)
if not node or not outbound or not outbounds_table then return nil end
local default_outTag = outbound.tag
local last_insert_outbound
@@ -1177,9 +1191,12 @@ function gen_config(var)
else
local preproxy_node = uci:get_all(appname, node.preproxy_node)
if preproxy_node then
- local preproxy_outbound = gen_outbound(nil, preproxy_node)
+ local preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
if preproxy_outbound then
- preproxy_outbound.tag = preproxy_node[".name"] .. ":" .. preproxy_node.remarks
+ preproxy_outbound.tag = preproxy_node[".name"]
+ if preproxy_node.remarks then
+ preproxy_outbound.tag = preproxy_outbound.tag .. ":" .. preproxy_node.remarks
+ end
outbound.tag = preproxy_outbound.tag .. " -> " .. outbound.tag
outbound.detour = preproxy_outbound.tag
last_insert_outbound = preproxy_outbound
@@ -1191,16 +1208,47 @@ function gen_config(var)
if node.chain_proxy == "2" and node.to_node then
local to_node = uci:get_all(appname, node.to_node)
if to_node then
- local to_outbound = gen_outbound(nil, to_node)
+ local to_outbound
+ if to_node.type ~= "sing-box" then
+ local tag = to_node[".name"]
+ local new_port = api.get_new_port()
+ table.insert(inbounds, {
+ type = "direct",
+ tag = tag,
+ listen = "127.0.0.1",
+ listen_port = new_port,
+ override_address = to_node.address,
+ override_port = tonumber(to_node.port),
+ })
+ table.insert(rules, 1, {
+ inbound = {tag},
+ outbound = outbound.tag,
+ })
+ if to_node.tls_serverName == nil then
+ to_node.tls_serverName = to_node.address
+ end
+ to_node.address = "127.0.0.1"
+ to_node.port = new_port
+ to_outbound = gen_outbound(node[".name"], to_node, tag, {
+ tag = tag,
+ run_socks_instance = not no_run
+ })
+ else
+ to_outbound = gen_outbound(node[".name"], to_node)
+ end
if to_outbound then
if shunt_rule_name then
to_outbound.tag = outbound.tag
outbound.tag = node[".name"]
else
+ if to_node.remarks then
+ to_outbound.tag = to_outbound.tag .. ":" .. to_node.remarks
+ end
to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag
end
-
- to_outbound.detour = outbound.tag
+ if to_node.type == "sing-box" then
+ to_outbound.detour = outbound.tag
+ end
table.insert(outbounds_table, to_outbound)
default_outTag = to_outbound.tag
end
@@ -1209,9 +1257,9 @@ function gen_config(var)
return default_outTag, last_insert_outbound
end
- if node.protocol == "_shunt" then
- local rules = {}
+ rules = {}
+ if node.protocol == "_shunt" then
local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil
local preproxy_tag = preproxy_rule_name
local preproxy_node_id = preproxy_rule_name and node["main_node"] or nil
@@ -1229,21 +1277,11 @@ function gen_config(var)
elseif _node_id == "_default" and rule_name ~= "default" then
rule_outboundTag = "default"
elseif _node_id and _node_id:find("Socks_") then
- local socks_id = _node_id:sub(1 + #"Socks_")
- local socks_node = uci:get_all(appname, socks_id) or nil
- if socks_node then
- local _node = {
- type = "sing-box",
- protocol = "socks",
- address = "127.0.0.1",
- port = socks_node.port,
- uot = "1",
- }
- local _outbound = gen_outbound(flag, _node, rule_name)
- if _outbound then
- table.insert(outbounds, _outbound)
- rule_outboundTag = _outbound.tag
- end
+ local socks_node = gen_socks_config_node(_node_id)
+ local _outbound = gen_outbound(flag, socks_node, rule_name)
+ if _outbound then
+ table.insert(outbounds, _outbound)
+ rule_outboundTag = _outbound.tag
end
elseif _node_id then
local _node = uci:get_all(appname, _node_id)
@@ -1336,9 +1374,15 @@ function gen_config(var)
preproxy_tag = preproxy_outboundTag
end
end
+
--default_node
local default_node_id = node.default_node or "_direct"
COMMON.default_outbound_tag = gen_shunt_node("default", default_node_id)
+
+ if inner_fakedns == "1" and node["default_fakedns"] == "1" then
+ remote_dns_fake = true
+ end
+
--shunt rule
uci:foreach(appname, "shunt_rules", function(e)
local outboundTag = gen_shunt_node(e[".name"])
@@ -1534,10 +1578,6 @@ function gen_config(var)
table.insert(rules, rule)
end
end)
-
- for index, value in ipairs(rules) do
- table.insert(route.rules, rules[index])
- end
elseif node.protocol == "_urltest" then
if node.urltest_node then
COMMON.default_outbound_tag = gen_urltest(node)
@@ -1565,6 +1605,10 @@ function gen_config(var)
end
end
end
+
+ for index, value in ipairs(rules) do
+ table.insert(route.rules, rules[index])
+ end
end
if COMMON.default_outbound_tag then
@@ -2050,19 +2094,19 @@ function gen_config(var)
end
function gen_proto_config(var)
- local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
- local local_socks_port = var["-local_socks_port"]
- local local_socks_username = var["-local_socks_username"]
- local local_socks_password = var["-local_socks_password"]
- local local_http_address = var["-local_http_address"] or "0.0.0.0"
- local local_http_port = var["-local_http_port"]
- local local_http_username = var["-local_http_username"]
- local local_http_password = var["-local_http_password"]
- local server_proto = var["-server_proto"]
- local server_address = var["-server_address"]
- local server_port = var["-server_port"]
- local server_username = var["-server_username"]
- local server_password = var["-server_password"]
+ local local_socks_address = var["local_socks_address"] or "0.0.0.0"
+ local local_socks_port = var["local_socks_port"]
+ local local_socks_username = var["local_socks_username"]
+ local local_socks_password = var["local_socks_password"]
+ local local_http_address = var["local_http_address"] or "0.0.0.0"
+ local local_http_port = var["local_http_port"]
+ local local_http_username = var["local_http_username"]
+ local local_http_password = var["local_http_password"]
+ local server_proto = var["server_proto"]
+ local server_address = var["server_address"]
+ local server_port = var["server_port"]
+ local server_username = var["server_username"]
+ local server_password = var["server_password"]
local inbounds = {}
local outbounds = {}
@@ -2135,7 +2179,11 @@ _G.geo_convert_srs = geo_convert_srs
if arg[1] then
local func =_G[arg[1]]
if func then
- print(func(api.get_function_args(arg)))
+ local var = nil
+ if arg[2] then
+ var = jsonc.parse(arg[2])
+ end
+ print(func(var))
if (next(GEO_VAR.SITE_TAGS) or next(GEO_VAR.IP_TAGS)) and not no_run then
convert_geofile()
end
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_trojan.lua b/applications/luci-app-passwall/luasrc/passwall/util_trojan.lua
index 848b689a56..29cb467d8d 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_trojan.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_trojan.lua
@@ -1,7 +1,7 @@
module("luci.passwall.util_trojan", package.seeall)
local api = require "luci.passwall.api"
local uci = api.uci
-local json = api.jsonc
+local jsonc = api.jsonc
function gen_config_server(node)
local cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"
@@ -41,18 +41,18 @@ function gen_config_server(node)
end
function gen_config(var)
- local node_id = var["-node"]
+ local node_id = var["node"]
if not node_id then
- print("-node 不能为空")
+ print("node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
- local run_type = var["-run_type"]
- local local_addr = var["-local_addr"]
- local local_port = var["-local_port"]
- local server_host = var["-server_host"] or node.address
- local server_port = var["-server_port"] or node.port
- local loglevel = var["-loglevel"] or 2
+ local run_type = var["run_type"]
+ local local_addr = var["local_addr"]
+ local local_port = var["local_port"]
+ local server_host = var["server_host"] or node.address
+ local server_port = var["server_port"] or node.port
+ local loglevel = var["loglevel"] or 2
local cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"
local cipher13 = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"
@@ -83,7 +83,7 @@ function gen_config(var)
},
udp_timeout = 60,
tcp = {
- use_tproxy = (node.type == "Trojan-Plus" and var["-use_tproxy"]) and true or nil,
+ use_tproxy = (node.type == "Trojan-Plus" and var["use_tproxy"]) and true or nil,
no_delay = true,
keep_alive = true,
reuse_port = true,
@@ -91,7 +91,7 @@ function gen_config(var)
fast_open_qlen = 20
}
}
- return json.stringify(trojan, 1)
+ return jsonc.stringify(trojan, 1)
end
_G.gen_config = gen_config
@@ -99,6 +99,10 @@ _G.gen_config = gen_config
if arg[1] then
local func =_G[arg[1]]
if func then
- print(func(api.get_function_args(arg)))
+ local var = nil
+ if arg[2] then
+ var = jsonc.parse(arg[2])
+ end
+ print(func(var))
end
end
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_tuic.lua b/applications/luci-app-passwall/luasrc/passwall/util_tuic.lua
index e138b6da56..94853943cc 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_tuic.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_tuic.lua
@@ -1,20 +1,20 @@
module("luci.passwall.util_tuic", package.seeall)
local api = require "luci.passwall.api"
local uci = api.uci
-local json = api.jsonc
+local jsonc = api.jsonc
function gen_config(var)
- local node_id = var["-node"]
+ local node_id = var["node"]
if not node_id then
- print("-node 不能为空")
+ print("node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
- local local_addr = var["-local_addr"]
- local local_port = var["-local_port"]
- local server_host = var["-server_host"] or node.address
- local server_port = var["-server_port"] or node.port
- local loglevel = var["-loglevel"] or "warn"
+ local local_addr = var["local_addr"]
+ local local_port = var["local_port"]
+ local server_host = var["server_host"] or node.address
+ local server_port = var["server_port"] or node.port
+ local loglevel = var["loglevel"] or "warn"
local tuic= {
relay = {
@@ -44,7 +44,7 @@ function gen_config(var)
},
log_level = loglevel
}
- return json.stringify(tuic, 1)
+ return jsonc.stringify(tuic, 1)
end
_G.gen_config = gen_config
@@ -52,6 +52,10 @@ _G.gen_config = gen_config
if arg[1] then
local func =_G[arg[1]]
if func then
- print(func(api.get_function_args(arg)))
+ local var = nil
+ if arg[2] then
+ var = jsonc.parse(arg[2])
+ end
+ print(func(var))
end
end
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_xray.lua b/applications/luci-app-passwall/luasrc/passwall/util_xray.lua
index a09af5d945..a23d1cf061 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_xray.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_xray.lua
@@ -6,6 +6,8 @@ local jsonc = api.jsonc
local appname = "passwall"
local fs = api.fs
+local xray_version = api.get_app_version("xray")
+
local function get_noise_packets()
local noises = {}
uci:foreach(appname, "xray_noise_packets", function(n)
@@ -149,9 +151,21 @@ function gen_outbound(flag, node, tag, proxy_table)
security = node.stream_security,
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
- allowInsecure = (node.tls_allowInsecure == "1") and true or false,
+ allowInsecure = (function()
+ if node.tls_CertSha and node.tls_CertSha ~= "" then return nil end
+ if api.compare_versions(os.date("%Y.%m.%d"), "<", "2026.6.1") and node.tls_allowInsecure == "1" then return true end
+ end)(),
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
- pinnedPeerCertificateChainSha256 = node.tls_chain_fingerprint and { node.tls_chain_fingerprint } or nil,
+ pinnedPeerCertSha256 = (function()
+ if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
+ if not node.tls_CertSha then return "" end
+ return node.tls_CertSha
+ end)(),
+ verifyPeerCertByName = (function()
+ if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
+ if not node.tls_CertByName then return "" end
+ return node.tls_CertByName
+ end)(),
echConfigList = (node.ech == "1") and node.ech_config or nil,
echForceQuery = (node.ech == "1") and (node.ech_ForceQuery or "none") or nil
} or nil,
@@ -188,12 +202,7 @@ function gen_outbound(flag, node, tag, proxy_table)
downlinkCapacity = tonumber(node.mkcp_downlinkCapacity),
congestion = (node.mkcp_congestion == "1") and true or false,
readBufferSize = tonumber(node.mkcp_readBufferSize),
- writeBufferSize = tonumber(node.mkcp_writeBufferSize),
- seed = (node.mkcp_seed and node.mkcp_seed ~= "") and node.mkcp_seed or nil,
- header = {
- type = node.mkcp_guise,
- domain = node.mkcp_domain
- }
+ writeBufferSize = tonumber(node.mkcp_writeBufferSize)
} or nil,
wsSettings = (node.transport == "ws") and {
path = node.ws_path or "/",
@@ -274,12 +283,31 @@ function gen_outbound(flag, node, tag, proxy_table)
end)(),
disablePathMTUDiscovery = (node.hysteria2_disable_mtu_discovery) and true or false
} or nil,
- udpmasks = (node.transport == "hysteria" and node.hysteria2_obfs_type and node.hysteria2_obfs_type ~= "") and {
- {
- type = node.hysteria2_obfs_type,
- settings = node.hysteria2_obfs_password and {
- password = node.hysteria2_obfs_password
- } or nil
+ finalmask = (node.transport == "mkcp") and {
+ udp = (function()
+ local t = {}
+ if node.mkcp_guise and node.mkcp_guise ~= "none" then
+ local g = { type = node.mkcp_guise }
+ if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
+ g.settings = { domain = node.mkcp_domain }
+ end
+ t[#t + 1] = g
+ end
+ local c = { type = (node.mkcp_seed and node.mkcp_seed ~= "") and "mkcp-aes128gcm" or "mkcp-original" }
+ if node.mkcp_seed and node.mkcp_seed ~= "" then
+ c.settings = { password = node.mkcp_seed }
+ end
+ t[#t + 1] = c
+ return t
+ end)()
+ } or (node.transport == "hysteria" and node.hysteria2_obfs_type and node.hysteria2_obfs_type ~= "") and {
+ udp = {
+ {
+ type = node.hysteria2_obfs_type,
+ settings = node.hysteria2_obfs_password and {
+ password = node.hysteria2_obfs_password
+ } or nil
+ }
}
} or nil
} or nil,
@@ -547,12 +575,7 @@ function gen_config_server(node)
downlinkCapacity = tonumber(node.mkcp_downlinkCapacity),
congestion = (node.mkcp_congestion == "1") and true or false,
readBufferSize = tonumber(node.mkcp_readBufferSize),
- writeBufferSize = tonumber(node.mkcp_writeBufferSize),
- seed = (node.mkcp_seed and node.mkcp_seed ~= "") and node.mkcp_seed or nil,
- header = {
- type = node.mkcp_guise,
- domain = node.mkcp_domain
- }
+ writeBufferSize = tonumber(node.mkcp_writeBufferSize)
} or nil,
wsSettings = (node.transport == "ws") and {
host = node.ws_host or nil,
@@ -571,6 +594,24 @@ function gen_config_server(node)
maxUploadSize = node.xhttp_maxuploadsize,
maxConcurrentUploads = node.xhttp_maxconcurrentuploads
} or nil,
+ finalmask = (node.transport == "mkcp") and {
+ udp = (function()
+ local t = {}
+ if node.mkcp_guise and node.mkcp_guise ~= "none" then
+ local g = { type = node.mkcp_guise }
+ if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
+ g.settings = { domain = node.mkcp_domain }
+ end
+ t[#t + 1] = g
+ end
+ local c = { type = (node.mkcp_seed and node.mkcp_seed ~= "") and "mkcp-aes128gcm" or "mkcp-original" }
+ if node.mkcp_seed and node.mkcp_seed ~= "" then
+ c.settings = { password = node.mkcp_seed }
+ end
+ t[#t + 1] = c
+ return t
+ end)()
+ } or nil,
sockopt = {
acceptProxyProtocol = (node.acceptProxyProtocol and node.acceptProxyProtocol == "1") and true or false
}
@@ -614,42 +655,42 @@ function gen_config_server(node)
end
function gen_config(var)
- local flag = var["-flag"]
- local node_id = var["-node"]
- local server_host = var["-server_host"]
- local server_port = var["-server_port"]
- local tcp_proxy_way = var["-tcp_proxy_way"] or "redirect"
- local tcp_redir_port = var["-tcp_redir_port"]
- local udp_redir_port = var["-udp_redir_port"]
- local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
- local local_socks_port = var["-local_socks_port"]
- local local_socks_username = var["-local_socks_username"]
- local local_socks_password = var["-local_socks_password"]
- local local_http_address = var["-local_http_address"] or "0.0.0.0"
- local local_http_port = var["-local_http_port"]
- local local_http_username = var["-local_http_username"]
- local local_http_password = var["-local_http_password"]
- local dns_listen_port = var["-dns_listen_port"]
- local dns_cache = var["-dns_cache"]
- local direct_dns_port = var["-direct_dns_port"]
- local direct_dns_udp_server = var["-direct_dns_udp_server"]
- local direct_dns_tcp_server = var["-direct_dns_tcp_server"]
- local direct_dns_query_strategy = var["-direct_dns_query_strategy"]
- local remote_dns_udp_server = var["-remote_dns_udp_server"]
- local remote_dns_udp_port = var["-remote_dns_udp_port"]
- local remote_dns_tcp_server = var["-remote_dns_tcp_server"]
- local remote_dns_tcp_port = var["-remote_dns_tcp_port"]
- local remote_dns_doh_url = var["-remote_dns_doh_url"]
- local remote_dns_doh_host = var["-remote_dns_doh_host"]
- local remote_dns_doh_ip = var["-remote_dns_doh_ip"]
- local remote_dns_doh_port = var["-remote_dns_doh_port"]
- local remote_dns_client_ip = var["-remote_dns_client_ip"]
- local remote_dns_fake = var["-remote_dns_fake"]
- local remote_dns_query_strategy = var["-remote_dns_query_strategy"]
- local dns_socks_address = var["-dns_socks_address"]
- local dns_socks_port = var["-dns_socks_port"]
- local loglevel = var["-loglevel"] or "warning"
- local no_run = var["-no_run"]
+ local flag = var["flag"]
+ local node_id = var["node"]
+ local server_host = var["server_host"]
+ local server_port = var["server_port"]
+ local tcp_proxy_way = var["tcp_proxy_way"] or "redirect"
+ local tcp_redir_port = var["tcp_redir_port"]
+ local udp_redir_port = var["udp_redir_port"]
+ local local_socks_address = var["local_socks_address"] or "0.0.0.0"
+ local local_socks_port = var["local_socks_port"]
+ local local_socks_username = var["local_socks_username"]
+ local local_socks_password = var["local_socks_password"]
+ local local_http_address = var["local_http_address"] or "0.0.0.0"
+ local local_http_port = var["local_http_port"]
+ local local_http_username = var["local_http_username"]
+ local local_http_password = var["local_http_password"]
+ local dns_listen_port = var["dns_listen_port"]
+ local dns_cache = var["dns_cache"]
+ local direct_dns_port = var["direct_dns_port"]
+ local direct_dns_udp_server = var["direct_dns_udp_server"]
+ local direct_dns_tcp_server = var["direct_dns_tcp_server"]
+ local direct_dns_query_strategy = var["direct_dns_query_strategy"]
+ local remote_dns_udp_server = var["remote_dns_udp_server"]
+ local remote_dns_udp_port = var["remote_dns_udp_port"]
+ local remote_dns_tcp_server = var["remote_dns_tcp_server"]
+ local remote_dns_tcp_port = var["remote_dns_tcp_port"]
+ local remote_dns_doh_url = var["remote_dns_doh_url"]
+ local remote_dns_doh_host = var["remote_dns_doh_host"]
+ local remote_dns_doh_ip = var["remote_dns_doh_ip"]
+ local remote_dns_doh_port = var["remote_dns_doh_port"]
+ local remote_dns_client_ip = var["remote_dns_client_ip"]
+ local remote_dns_fake = var["remote_dns_fake"]
+ local remote_dns_query_strategy = var["remote_dns_query_strategy"]
+ local dns_socks_address = var["dns_socks_address"]
+ local dns_socks_port = var["dns_socks_port"]
+ local loglevel = var["loglevel"] or "warning"
+ local no_run = var["no_run"]
local dns_domain_rules = {}
local dns = nil
@@ -719,7 +760,31 @@ function gen_config(var)
table.insert(inbounds, inbound)
end
- local function gen_loopback(outbound_tag, loopback_dst)
+
+ function gen_socks_config_node(node_id, socks_id, remarks)
+ if node_id then
+ socks_id = node_id:sub(1 + #"Socks_")
+ end
+ local result
+ local socks_node = uci:get_all(appname, socks_id) or nil
+ if socks_node then
+ if not remarks then
+ remarks = "Socks_" .. socks_node.port
+ end
+ result = {
+ remarks = remarks,
+ type = "Xray",
+ protocol = "socks",
+ address = "127.0.0.1",
+ port = socks_node.port,
+ transport = "tcp",
+ stream_security = "none"
+ }
+ end
+ return result
+ end
+
+ function gen_loopback(outbound_tag, loopback_dst)
if not outbound_tag or outbound_tag == "" then return nil end
local inbound_tag = loopback_dst and "lo-to-" .. loopback_dst or outbound_tag .. "-lo"
table.insert(outbounds, {
@@ -730,7 +795,7 @@ function gen_config(var)
return inbound_tag
end
- local function gen_balancer(_node, loopback_tag)
+ function gen_balancer(_node, loopback_tag)
local balancer_id = _node[".name"]
local balancer_tag = "balancer-" .. balancer_id
local loopback_dst = balancer_id -- route destination for the loopback outbound
@@ -759,26 +824,16 @@ function gen_config(var)
if is_new_blc_node then
local blc_node
if blc_node_id:find("Socks_") then
- local socks_id = blc_node_id:sub(1 + #"Socks_")
- local socks_node = uci:get_all(appname, socks_id) or nil
- if socks_node then
- blc_node = {
- type = "Xray",
- protocol = "socks",
- address = "127.0.0.1",
- port = socks_node.port,
- transport = "tcp",
- stream_security = "none",
- remarks = "Socks_" .. socks_node.port
- }
- end
+ blc_node = gen_socks_config_node(blc_node_id)
else
blc_node = uci:get_all(appname, blc_node_id)
end
if blc_node then
local outbound = gen_outbound(flag, blc_node, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
if outbound then
- outbound.tag = outbound.tag .. ":" .. blc_node.remarks
+ if blc_node.remarks then
+ outbound.tag = outbound.tag .. ":" .. blc_node.remarks
+ end
table.insert(outbounds, outbound)
valid_nodes[#valid_nodes + 1] = outbound.tag
end
@@ -803,19 +858,7 @@ function gen_config(var)
if is_new_node then
local fallback_node
if fallback_node_id:find("Socks_") then
- local socks_id = fallback_node_id:sub(1 + #"Socks_")
- local socks_node = uci:get_all(appname, socks_id) or nil
- if socks_node then
- fallback_node = {
- type = "Xray",
- protocol = "socks",
- address = "127.0.0.1",
- port = socks_node.port,
- transport = "tcp",
- stream_security = "none",
- remarks = "Socks_" .. socks_node.port
- }
- end
+ fallback_node = gen_socks_config_node(fallback_node_id)
else
fallback_node = uci:get_all(appname, fallback_node_id)
end
@@ -823,7 +866,9 @@ function gen_config(var)
if fallback_node.protocol ~= "_balancing" then
local outbound = gen_outbound(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
if outbound then
- outbound.tag = outbound.tag .. ":" .. fallback_node.remarks
+ if fallback_node.remarks then
+ outbound.tag = outbound.tag .. ":" .. fallback_node.remarks
+ end
table.insert(outbounds, outbound)
fallback_node_tag = outbound.tag
end
@@ -881,7 +926,7 @@ function gen_config(var)
return balancer_tag
end
- local function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name)
+ function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name)
if not node or not outbound or not outbounds_table then return nil end
local default_outTag = outbound.tag
local last_insert_outbound
@@ -892,9 +937,12 @@ function gen_config(var)
else
local preproxy_node = uci:get_all(appname, node.preproxy_node)
if preproxy_node then
- local preproxy_outbound = gen_outbound(nil, preproxy_node)
+ local preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
if preproxy_outbound then
- preproxy_outbound.tag = preproxy_node[".name"] .. ":" .. preproxy_node.remarks
+ preproxy_outbound.tag = preproxy_node[".name"]
+ if preproxy_node.remarks then
+ preproxy_outbound.tag = preproxy_outbound.tag .. ":" .. preproxy_node.remarks
+ end
outbound.tag = preproxy_outbound.tag .. " -> " .. outbound.tag
outbound.proxySettings = {
tag = preproxy_outbound.tag,
@@ -909,19 +957,49 @@ function gen_config(var)
if node.chain_proxy == "2" and node.to_node then
local to_node = uci:get_all(appname, node.to_node)
if to_node then
- local to_outbound = gen_outbound(nil, to_node)
+ local to_outbound
+ if to_node.type ~= "Xray" then
+ local tag = to_node[".name"]
+ local new_port = api.get_new_port()
+ table.insert(inbounds, {
+ tag = tag,
+ listen = "127.0.0.1",
+ port = new_port,
+ protocol = "dokodemo-door",
+ settings = {network = "tcp,udp", address = to_node.address, port = tonumber(to_node.port)}
+ })
+ if to_node.tls_serverName == nil then
+ to_node.tls_serverName = to_node.address
+ end
+ to_node.address = "127.0.0.1"
+ to_node.port = new_port
+ table.insert(rules, 1, {
+ inboundTag = {tag},
+ outboundTag = outbound.tag
+ })
+ to_outbound = gen_outbound(node[".name"], to_node, tag, {
+ tag = tag,
+ run_socks_instance = not no_run
+ })
+ else
+ to_outbound = gen_outbound(node[".name"], to_node)
+ end
if to_outbound then
if shunt_rule_name then
to_outbound.tag = outbound.tag
outbound.tag = node[".name"]
else
+ if to_node.remarks then
+ to_outbound.tag = to_outbound.tag .. ":" .. to_node.remarks
+ end
to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag
end
-
- to_outbound.proxySettings = {
- tag = outbound.tag,
- transportLayer = true
- }
+ if to_node.type == "Xray" then
+ to_outbound.proxySettings = {
+ tag = outbound.tag,
+ transportLayer = true
+ }
+ end
table.insert(outbounds_table, to_outbound)
default_outTag = to_outbound.tag
end
@@ -952,23 +1030,16 @@ function gen_config(var)
elseif _node_id == "_default" then
return "default", nil
elseif _node_id and _node_id:find("Socks_") then
- local socks_id = _node_id:sub(1 + #"Socks_")
- local socks_node = uci:get_all(appname, socks_id) or nil
local socks_tag
- if socks_node then
- local _node = {
- type = "Xray",
- protocol = "socks",
- address = "127.0.0.1",
- port = socks_node.port,
- transport = "tcp",
- stream_security = "none"
- }
- local outbound = gen_outbound(flag, _node, rule_name)
- if outbound then
+ local socks_node = gen_socks_config_node(_node_id)
+ local outbound = gen_outbound(flag, socks_node, rule_name)
+ if outbound then
+ if rule_name == "default" then
+ table.insert(outbounds, 1, outbound)
+ else
table.insert(outbounds, outbound)
- socks_tag = outbound.tag
end
+ socks_tag = outbound.tag
end
return socks_tag, nil
end
@@ -1095,11 +1166,17 @@ function gen_config(var)
preproxy_tag = preproxy_outbound_tag
end
end
+
--default_node
local default_node_id = node.default_node or "_direct"
local default_outboundTag, default_balancerTag = gen_shunt_node("default", default_node_id)
COMMON.default_outbound_tag = default_outboundTag
COMMON.default_balancer_tag = default_balancerTag
+
+ if inner_fakedns == "1" and node["default_fakedns"] == "1" then
+ remote_dns_fake = true
+ end
+
--shunt rule
uci:foreach(appname, "shunt_rules", function(e)
local outbound_tag, balancer_tag = gen_shunt_node(e[".name"])
@@ -1264,7 +1341,7 @@ function gen_config(var)
routing = {
domainStrategy = "AsIs",
domainMatcher = "hybrid",
- rules = {}
+ rules = rules
}
table.insert(routing.rules, {
ruleTag = "default",
@@ -1686,19 +1763,19 @@ function gen_config(var)
end
function gen_proto_config(var)
- local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
- local local_socks_port = var["-local_socks_port"]
- local local_socks_username = var["-local_socks_username"]
- local local_socks_password = var["-local_socks_password"]
- local local_http_address = var["-local_http_address"] or "0.0.0.0"
- local local_http_port = var["-local_http_port"]
- local local_http_username = var["-local_http_username"]
- local local_http_password = var["-local_http_password"]
- local server_proto = var["-server_proto"]
- local server_address = var["-server_address"]
- local server_port = var["-server_port"]
- local server_username = var["-server_username"]
- local server_password = var["-server_password"]
+ local local_socks_address = var["local_socks_address"] or "0.0.0.0"
+ local local_socks_port = var["local_socks_port"]
+ local local_socks_username = var["local_socks_username"]
+ local local_socks_password = var["local_socks_password"]
+ local local_http_address = var["local_http_address"] or "0.0.0.0"
+ local local_http_port = var["local_http_port"]
+ local local_http_username = var["local_http_username"]
+ local local_http_password = var["local_http_password"]
+ local server_proto = var["server_proto"]
+ local server_address = var["server_address"]
+ local server_port = var["server_port"]
+ local server_username = var["server_username"]
+ local server_password = var["server_password"]
local inbounds = {}
local outbounds = {}
@@ -1796,6 +1873,10 @@ _G.gen_proto_config = gen_proto_config
if arg[1] then
local func =_G[arg[1]]
if func then
- print(func(api.get_function_args(arg)))
+ local var = nil
+ if arg[2] then
+ var = jsonc.parse(arg[2])
+ end
+ print(func(var))
end
end
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm
index 2081a531b9..fe15cf6eb3 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm
@@ -96,7 +96,7 @@
- <%=pcdata(current_label ~= "" and current_label or ("("..translate("Not set")..")"))%>
+ <%=pcdata("("..translate("Not set")..")")%>
@@ -113,8 +113,6 @@
-<%+cbi/valuefooter%>
-
+
+<%+cbi/valuefooter%>
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
index 84acd5a760..2549aa9556 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm
@@ -92,7 +92,8 @@
display: inline-block;
overflow: hidden;
white-space: nowrap;
- max-width: calc(100% - 18px);
+ width: 100%;
+ text-align: left;
}
.lv-dropdown-panel {
position: fixed;
@@ -104,6 +105,7 @@
box-shadow: 0 6px 18px rgba(0,0,0,0.08);
max-height: 50vh;
overflow: auto;
+ overscroll-behavior: contain;
}
.lv-dropdown-search {
width: 100%;
@@ -159,49 +161,31 @@
return cssRules.filter(Boolean).join(' ')
}
- function lv_parseColorToRgba(color) {
- if (!color) return null;
-
- const ctx = document.createElement('canvas').getContext('2d');
- if (!ctx) return null;
-
- ctx.fillStyle = color;
- const computedColor = ctx.fillStyle;
- // Match #RRGGBB or #RRGGBBAA format
- if (computedColor.startsWith('#')) {
- const hex = computedColor.substring(1);
- // #RRGGBB (6 digits)
- if (hex.length === 6) {
- return {
- r: parseInt(hex.substring(0, 2), 16),
- g: parseInt(hex.substring(2, 4), 16),
- b: parseInt(hex.substring(4, 6), 16),
- a: 1
- };
- }
- // #RRGGBBAA (8 digits)
- if (hex.length === 8) {
- return {
- r: parseInt(hex.substring(0, 2), 16),
- g: parseInt(hex.substring(2, 4), 16),
- b: parseInt(hex.substring(4, 6), 16),
- a: parseInt(hex.substring(6, 8), 16) / 255
- };
- }
- }
- // Match rgba() or rgb() format (for browsers that return this format)
- const rgbaMatch = computedColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)$/);
- if (rgbaMatch) {
- return {
- r: parseInt(rgbaMatch[1], 10),
- g: parseInt(rgbaMatch[2], 10),
- b: parseInt(rgbaMatch[3], 10),
- a: rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1
- };
- }
-
- return null; // Invalid color
- }
+ const lv_parseColorToRgba = (function() {
+ // Create canvas and context once (Closure)
+ const canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
+ return function(colorStr) {
+ if (!colorStr)
+ return null;
+ ctx.clearRect(0, 0, 1, 1);
+ // 2. Apply the color
+ ctx.fillStyle = colorStr;
+ // 3. Fill a single pixel
+ ctx.fillRect(0, 0, 1, 1);
+ // 4. Extract pixel data [R, G, B, A]
+ const data = ctx.getImageData(0, 0, 1, 1).data;
+ return {
+ r: data[0],
+ g: data[1],
+ b: data[2],
+ // Convert alpha from 0-255 to 0-1 (rounded to 3 decimal places)
+ a: Math.round((data[3] / 255) * 1000) / 1000
+ };
+ };
+ })();
// Helper to convert back to Hex (for output consistency)
function lv_rgbToHex(r, g, b) {
@@ -554,6 +538,18 @@
"&":"&", "<":"<", ">":">", '"':""", "'":"'"
}[c]));
}
+
+ function lv_change(cbid, listContainer, hiddenSelect, labelSpan, searchInput, new_key, new_text) {
+ //改值
+ hiddenSelect.options[0].value = new_key;
+ hiddenSelect.options[0].text = new_key;
+ hiddenSelect.value = new_key;
+ labelSpan.textContent = lv_ellipsisByWidth(cbid, new_text);
+ labelSpan.title = new_text;
+ lv_highlightSelectedItem(listContainer, hiddenSelect);
+ lv_updateGroupCounts(cbid, listContainer, hiddenSelect, searchInput);
+ }
+
function lv_render_dropdown_list(cbid, panel, listContainer, hiddenSelect, labelSpan, searchInput, display) {
if (window.lv_dropdown_rendered[cbid]) return;
const data = window.lv_dropdown_data[cbid];
@@ -630,13 +626,7 @@
const changed = key !== hiddenSelect.value;
if (changed) {
//改值
- hiddenSelect.options[0].value = key;
- hiddenSelect.options[0].text = key;
- hiddenSelect.value = key;
- labelSpan.textContent = text;
- labelSpan.title = text;
- lv_highlightSelectedItem(listContainer, hiddenSelect);
- lv_updateGroupCounts(cbid, listContainer, hiddenSelect, searchInput);
+ lv_change(cbid, listContainer, hiddenSelect, labelSpan, searchInput, key, text);
}
lv_closePanel(cbid,panel,listContainer,hiddenSelect,searchInput);
if (changed) {
@@ -669,6 +659,73 @@
}
});
});
+
+ // 防止 panel 惯性滚动穿透
+ panel.addEventListener('wheel', function (e) {
+ const deltaY = e.deltaY;
+ const scrollTop = panel.scrollTop;
+ const scrollHeight = panel.scrollHeight;
+ const clientHeight = panel.clientHeight;
+ const isAtTop = scrollTop === 0;
+ const isAtBottom = scrollTop + clientHeight >= scrollHeight;
+ if (deltaY < 0 && isAtTop) {
+ e.preventDefault();
+ return;
+ }
+ if (deltaY > 0 && isAtBottom) {
+ e.preventDefault();
+ return;
+ }
+ e.stopPropagation();
+ }, { passive: false });
+ }
+
+ //截断display字符长度
+ window.lv_labelSpan_maxWidth = {};
+ function lv_ellipsisByWidth(cbid, text) {
+ const el = document.getElementById(cbid + ".label");
+ if (!el || !text) return text;
+ text = text.trim();
+ const maxWidth = el.clientWidth;
+ window.lv_labelSpan_maxWidth[cbid] = maxWidth;
+ if (maxWidth <= 0) return text;
+ const style = window.getComputedStyle(el);
+ const font = [
+ style.fontStyle,
+ style.fontVariant,
+ style.fontWeight,
+ style.fontSize || '16px',
+ style.fontFamily || 'sans-serif'
+ ].join(" ").replace(/\s+/g, ' ');
+ const canvas = lv_ellipsisByWidth._canvas || (lv_ellipsisByWidth._canvas = document.createElement("canvas"));
+ const ctx = canvas.getContext("2d");
+ ctx.font = font;
+ if (ctx.measureText(text).width <= maxWidth) {
+ return text;
+ }
+ const ellipsis = "...";
+ const ellipsisWidth = ctx.measureText(ellipsis).width;
+ const minChars = 15;
+ const probe = text.slice(0, minChars);
+ const probeWidth = ctx.measureText(probe).width;
+ if (probeWidth + ellipsisWidth > maxWidth) {
+ return text;
+ }
+ let left = 0;
+ let right = text.length;
+ let result = ellipsis;
+ while (left <= right) {
+ const mid = (left + right) >> 1;
+ const substr = text.slice(0, mid);
+ const w = ctx.measureText(substr).width;
+ if (w + ellipsisWidth <= maxWidth) {
+ result = substr + ellipsis;
+ left = mid + 1;
+ } else {
+ right = mid - 1;
+ }
+ }
+ return result;
}
const lv_adaptiveControls = new Set();
@@ -676,12 +733,23 @@
lv_adaptiveControls.add(cbid);
lv_adaptiveStyle(cbid);
}
+ function lv_labelSpanResize(cbid) {
+ const el = document.getElementById(cbid + ".label");
+ if (!el) return;
+ const maxWidth = el.clientWidth;
+ if (window.lv_labelSpan_maxWidth[cbid] == maxWidth) return;
+ let text = el.title;
+ el.textContent = lv_ellipsisByWidth(cbid, text);
+ }
let lv_adaptiveTicking = false;
window.addEventListener("resize", () => {
if (!lv_adaptiveTicking) {
lv_adaptiveTicking = true;
requestAnimationFrame(() => {
- lv_adaptiveControls.forEach(cbid => lv_adaptiveStyle(cbid));
+ lv_adaptiveControls.forEach(cbid => {
+ lv_adaptiveStyle(cbid);
+ lv_labelSpanResize(cbid);
+ });
lv_adaptiveTicking = false;
});
}
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue.htm b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue.htm
index 88920275ff..fde96d7deb 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue.htm
@@ -108,7 +108,6 @@
<%:Selected:%> <%=selected_count%>/<%=total_count%>
-<%+cbi/valuefooter%>
+
+<%+cbi/valuefooter%>
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue_com.htm b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue_com.htm
index 2ff1067312..0f51022b5c 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue_com.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multivalue_com.htm
@@ -117,49 +117,31 @@
return cssRules.filter(Boolean).join(' ')
}
- function mv_parseColorToRgba(color) {
- if (!color) return null;
-
- const ctx = document.createElement('canvas').getContext('2d');
- if (!ctx) return null;
-
- ctx.fillStyle = color;
- const computedColor = ctx.fillStyle;
- // Match #RRGGBB or #RRGGBBAA format
- if (computedColor.startsWith('#')) {
- const hex = computedColor.substring(1);
- // #RRGGBB (6 digits)
- if (hex.length === 6) {
- return {
- r: parseInt(hex.substring(0, 2), 16),
- g: parseInt(hex.substring(2, 4), 16),
- b: parseInt(hex.substring(4, 6), 16),
- a: 1
- };
- }
- // #RRGGBBAA (8 digits)
- if (hex.length === 8) {
- return {
- r: parseInt(hex.substring(0, 2), 16),
- g: parseInt(hex.substring(2, 4), 16),
- b: parseInt(hex.substring(4, 6), 16),
- a: parseInt(hex.substring(6, 8), 16) / 255
- };
- }
- }
- // Match rgba() or rgb() format (for browsers that return this format)
- const rgbaMatch = computedColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)$/);
- if (rgbaMatch) {
- return {
- r: parseInt(rgbaMatch[1], 10),
- g: parseInt(rgbaMatch[2], 10),
- b: parseInt(rgbaMatch[3], 10),
- a: rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1
- };
- }
-
- return null; // Invalid color
- }
+ const mv_parseColorToRgba = (function() {
+ // Create canvas and context once (Closure)
+ const canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
+ return function(colorStr) {
+ if (!colorStr)
+ return null;
+ ctx.clearRect(0, 0, 1, 1);
+ // 2. Apply the color
+ ctx.fillStyle = colorStr;
+ // 3. Fill a single pixel
+ ctx.fillRect(0, 0, 1, 1);
+ // 4. Extract pixel data [R, G, B, A]
+ const data = ctx.getImageData(0, 0, 1, 1).data;
+ return {
+ r: data[0],
+ g: data[1],
+ b: data[2],
+ // Convert alpha from 0-255 to 0-1 (rounded to 3 decimal places)
+ a: Math.round((data[3] / 255) * 1000) / 1000
+ };
+ };
+ })();
// Helper to convert back to Hex (for output consistency)
function mv_rgbToHex(r, g, b) {
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value.htm b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value.htm
index 9745dd8348..beb3ce2a15 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value.htm
@@ -105,7 +105,7 @@
- <%=pcdata(current_label ~= "" and current_label or current_key)%>
+ <%=pcdata(translate("-- Please choose --"))%>
@@ -126,8 +126,6 @@
-<%+cbi/valuefooter%>
-
+
+<%+cbi/valuefooter%>
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm
index 9386e91417..e9f440329d 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm
@@ -63,23 +63,32 @@
}
.v-dropdown-container {
- display: block;
+ display: inline-block;
position: relative;
white-space: nowrap;
- width: 100%;
+ min-width: 220px;
+}
+@media (max-width: 600px) {
+ .v-dropdown-container {
+ display: block;
+ white-space: normal;
+ }
}
+
.v-dropdown-display {
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
+ width: 100%;
}
.v-dropdown-label {
display: inline-block;
overflow: hidden;
white-space: nowrap;
- max-width: calc(100% - 18px);
+ width: 100%;
+ text-align: left;
}
.v-dropdown-panel {
position: fixed;
@@ -91,6 +100,7 @@
box-shadow: 0 6px 18px rgba(0,0,0,0.08);
max-height: 50vh;
overflow: auto;
+ overscroll-behavior: contain;
}
.v-dropdown-search, .v-dropdown-custom {
width: 100%;
@@ -146,49 +156,31 @@
return cssRules.filter(Boolean).join(' ')
}
- function v_parseColorToRgba(color) {
- if (!color) return null;
-
- const ctx = document.createElement('canvas').getContext('2d');
- if (!ctx) return null;
-
- ctx.fillStyle = color;
- const computedColor = ctx.fillStyle;
- // Match #RRGGBB or #RRGGBBAA format
- if (computedColor.startsWith('#')) {
- const hex = computedColor.substring(1);
- // #RRGGBB (6 digits)
- if (hex.length === 6) {
- return {
- r: parseInt(hex.substring(0, 2), 16),
- g: parseInt(hex.substring(2, 4), 16),
- b: parseInt(hex.substring(4, 6), 16),
- a: 1
- };
- }
- // #RRGGBBAA (8 digits)
- if (hex.length === 8) {
- return {
- r: parseInt(hex.substring(0, 2), 16),
- g: parseInt(hex.substring(2, 4), 16),
- b: parseInt(hex.substring(4, 6), 16),
- a: parseInt(hex.substring(6, 8), 16) / 255
- };
- }
- }
- // Match rgba() or rgb() format (for browsers that return this format)
- const rgbaMatch = computedColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)$/);
- if (rgbaMatch) {
- return {
- r: parseInt(rgbaMatch[1], 10),
- g: parseInt(rgbaMatch[2], 10),
- b: parseInt(rgbaMatch[3], 10),
- a: rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1
- };
- }
-
- return null; // Invalid color
- }
+ const v_parseColorToRgba = (function() {
+ // Create canvas and context once (Closure)
+ const canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
+ return function(colorStr) {
+ if (!colorStr)
+ return null;
+ ctx.clearRect(0, 0, 1, 1);
+ // 2. Apply the color
+ ctx.fillStyle = colorStr;
+ // 3. Fill a single pixel
+ ctx.fillRect(0, 0, 1, 1);
+ // 4. Extract pixel data [R, G, B, A]
+ const data = ctx.getImageData(0, 0, 1, 1).data;
+ return {
+ r: data[0],
+ g: data[1],
+ b: data[2],
+ // Convert alpha from 0-255 to 0-1 (rounded to 3 decimal places)
+ a: Math.round((data[3] / 255) * 1000) / 1000
+ };
+ };
+ })();
// Helper to convert back to Hex (for output consistency)
function v_rgbToHex(r, g, b) {
@@ -248,7 +240,7 @@
const styleBody = window.getComputedStyle(document.body)
const styleNode = document.createElement('style')
- const styleNames = ["color", "height", "padding", "margin", "lineHeight", "border", "borderRadius","minHeight"]
+ const styleNames = ["color", "height", "padding", "margin", "lineHeight", "border", "borderRadius", "minWidth","minHeight"]
if (styleSelect.borderBottomStyle !== "none") {
styleNames.push("borderBottomWidth", "borderBottomStyle", "borderBottomColor");
}
@@ -257,7 +249,7 @@
const panelRadius = styleSelect.borderRadius;
const optionColor = !v_isTransparent(styleOption.backgroundColor) ? styleOption.backgroundColor : !v_isTransparent(styleSelect.backgroundColor) ? styleSelect.backgroundColor : styleBody.backgroundColor
const titleColor = v_getColorSchema(optionColor) === "light" ? v_darker(optionColor, 30) : v_lighter(optionColor, 30)
- const selectStyleCSS = [`#${CSS.escape(cbid + ".display")} {`, v_style2Css(styleSelect, styleNames), v_style2Css(styleSelect, ["backgroundColor"]), `width:100%;min-width:7rem;`, "}"]
+ const selectStyleCSS = [`#${CSS.escape(cbid + ".display")} {`, v_style2Css(styleSelect, styleNames), v_style2Css(styleSelect, ["backgroundColor"]), "}"]
const optionStyleCSS = [`#${CSS.escape(cbid + ".panel")} {`, v_style2Css(styleOption, styleNames), `background-color: ${optionColor};`, `border-radius: ${panelRadius};`, "}"]
const titleStyleCSS = [`#${CSS.escape(cbid + ".panel")} .v-group-title {`, `background-color: ${titleColor} !important;`, "}"]
styleNode.textContent = [].concat(selectStyleCSS, optionStyleCSS, titleStyleCSS).join("\n")
@@ -567,7 +559,7 @@
if (changed) {
//改值
hiddenInput.value = inputValue;
- labelSpan.textContent = inputValue;
+ labelSpan.textContent = v_ellipsisByWidth(cbid, inputValue);
labelSpan.title = inputValue;
v_highlightSelectedItem(listContainer, hiddenInput);
v_updateGroupCounts(cbid, listContainer, hiddenInput, searchInput);
@@ -661,7 +653,7 @@
if (changed) {
//改值
hiddenInput.value = key;
- labelSpan.textContent = text;
+ labelSpan.textContent = v_ellipsisByWidth(cbid, text);
labelSpan.title = text;
v_highlightSelectedItem(listContainer, hiddenInput);
v_updateGroupCounts(cbid, listContainer, hiddenInput, searchInput);
@@ -701,6 +693,73 @@
e.preventDefault();
v_customEnter(cbid, labelSpan, hiddenInput, searchInput, panel, listContainer, customInput);
});
+
+ // 防止 panel 惯性滚动穿透
+ panel.addEventListener('wheel', function (e) {
+ const deltaY = e.deltaY;
+ const scrollTop = panel.scrollTop;
+ const scrollHeight = panel.scrollHeight;
+ const clientHeight = panel.clientHeight;
+ const isAtTop = scrollTop === 0;
+ const isAtBottom = scrollTop + clientHeight >= scrollHeight;
+ if (deltaY < 0 && isAtTop) {
+ e.preventDefault();
+ return;
+ }
+ if (deltaY > 0 && isAtBottom) {
+ e.preventDefault();
+ return;
+ }
+ e.stopPropagation();
+ }, { passive: false });
+ }
+
+ //截断display字符长度
+ window.v_labelSpan_maxWidth = {};
+ function v_ellipsisByWidth(cbid, text) {
+ const el = document.getElementById(cbid + ".label");
+ if (!el || !text) return text;
+ text = text.trim();
+ const maxWidth = el.clientWidth;
+ window.v_labelSpan_maxWidth[cbid] = maxWidth;
+ if (maxWidth <= 0) return text;
+ const style = window.getComputedStyle(el);
+ const font = [
+ style.fontStyle,
+ style.fontVariant,
+ style.fontWeight,
+ style.fontSize || '16px',
+ style.fontFamily || 'sans-serif'
+ ].join(" ").replace(/\s+/g, ' ');
+ const canvas = v_ellipsisByWidth._canvas || (v_ellipsisByWidth._canvas = document.createElement("canvas"));
+ const ctx = canvas.getContext("2d");
+ ctx.font = font;
+ if (ctx.measureText(text).width <= maxWidth) {
+ return text;
+ }
+ const ellipsis = "...";
+ const ellipsisWidth = ctx.measureText(ellipsis).width;
+ const minChars = 15;
+ const probe = text.slice(0, minChars);
+ const probeWidth = ctx.measureText(probe).width;
+ if (probeWidth + ellipsisWidth > maxWidth) {
+ return text;
+ }
+ let left = 0;
+ let right = text.length;
+ let result = ellipsis;
+ while (left <= right) {
+ const mid = (left + right) >> 1;
+ const substr = text.slice(0, mid);
+ const w = ctx.measureText(substr).width;
+ if (w + ellipsisWidth <= maxWidth) {
+ result = substr + ellipsis;
+ left = mid + 1;
+ } else {
+ right = mid - 1;
+ }
+ }
+ return result;
}
const v_adaptiveControls = new Set();
@@ -708,12 +767,23 @@
v_adaptiveControls.add(cbid);
v_adaptiveStyle(cbid);
}
+ function v_labelSpanResize(cbid) {
+ const el = document.getElementById(cbid + ".label");
+ if (!el) return;
+ const maxWidth = el.clientWidth;
+ if (window.v_labelSpan_maxWidth[cbid] == maxWidth) return;
+ let text = el.title;
+ el.textContent = v_ellipsisByWidth(cbid, text);
+ }
let v_adaptiveTicking = false;
window.addEventListener("resize", () => {
if (!v_adaptiveTicking) {
v_adaptiveTicking = true;
requestAnimationFrame(() => {
- v_adaptiveControls.forEach(cbid => v_adaptiveStyle(cbid));
+ v_adaptiveControls.forEach(cbid => {
+ v_adaptiveStyle(cbid);
+ v_labelSpanResize(cbid);
+ });
v_adaptiveTicking = false;
});
}
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/global/footer.htm b/applications/luci-app-passwall/luasrc/view/passwall/global/footer.htm
index ddf3b139c8..9707f9646d 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/global/footer.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/global/footer.htm
@@ -1,20 +1,47 @@
<%
local api = self.api
+local appname = api.appname
-%>
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/global/status.htm b/applications/luci-app-passwall/luasrc/view/passwall/global/status.htm
index 32878ac786..a4485a52bb 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/global/status.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/global/status.htm
@@ -138,15 +138,15 @@ <%:Load Balancing%>
<%:NOT RUNNIN
-
+
-

+
-
<%:Baidu Connection%>
<%:Touch Check%>
+ <%:AliCloud Conn.%>
<%:Touch Check%>
@@ -158,7 +158,7 @@
<%:Baidu Connection%>
-
<%:Google Connection%>
<%:Touch Check%>
+ <%:Google Conn.%>
<%:Touch Check%>
@@ -166,23 +166,23 @@ <%:Google Connection%>
-

-
+

+
-
<%:GitHub Connection%>
<%:Touch Check%>
+ <%:GitHub Conn.%>
<%:Touch Check%>
-
+
-

-
+

+
-
<%:Instagram Connection%>
<%:Touch Check%>
+ <%:YouTube Conn.%>
<%:Touch Check%>
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/haproxy/js.htm b/applications/luci-app-passwall/luasrc/view/passwall/haproxy/js.htm
index 6ba2b26e26..c542efdd44 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/haproxy/js.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/haproxy/js.htm
@@ -21,6 +21,11 @@
line-height: inherit;
user-select: none;
align-self: stretch;
+ background-color: transparent;
+}
+
+.drag-handle:hover {
+ background: transparent;
}
.dragging-row {
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm b/applications/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm
new file mode 100644
index 0000000000..e5aabfafc0
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm
@@ -0,0 +1,75 @@
+
+
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/node_config/footer.htm b/applications/luci-app-passwall/luasrc/view/passwall/node_config/footer.htm
index b238b5e1a4..ef8cf34582 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/node_config/footer.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/node_config/footer.htm
@@ -2,6 +2,8 @@
local api = self.api
-%>
+