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 = "
" + + 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 = "
" - 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 -%> +