diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua index 5491e4249..d6ee9323b 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua @@ -41,17 +41,42 @@ end e = t:option(DummyValue, "remarks", translate("Remarks")) e.width = "15%" ----- Type e = t:option(DummyValue, "type", translate("Type")) +e.width = "20%" +e.rawhtml = true e.cfgvalue = function(t, n) - local v = Value.cfgvalue(t, n) - if v then - if v == "sing-box" or v == "Xray" then - local protocol = m:get(n, "protocol") - return v .. " -> " .. protocol + local str = "" + local type = m:get(n, "type") or "" + if type == "sing-box" or type == "Xray" then + local protocol = m:get(n, "protocol") or "" + if protocol == "vmess" then + protocol = "VMess" + elseif protocol == "vless" then + protocol = "VLESS" + elseif protocol == "shadowsocks" then + protocol = "SS" + elseif protocol == "shadowsocksr" then + protocol = "SSR" + elseif protocol == "wireguard" then + protocol = "WG" + elseif protocol == "hysteria" then + protocol = "HY" + elseif protocol == "hysteria2" then + protocol = "HY2" + elseif protocol == "anytls" then + protocol = "AnyTLS" + else + protocol = protocol:gsub("^%l",string.upper) + local custom = m:get(n, "custom") or "0" + if custom == "1" then + protocol = translate("Custom Config") + end end - return v + if type == "sing-box" then type = "Sing-Box" end + type = type .. " " .. protocol end + str = str .. translate(type) + return str end e = t:option(DummyValue, "port", translate("Port")) diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua index 5ee377931..49d264693 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua @@ -20,29 +20,38 @@ end s.fields["type"]:value(type_name, "Hysteria2") +o = s:option(Flag, _n("custom"), translate("Use Custom Config")) + o = s:option(Value, _n("port"), translate("Listen Port")) o.datatype = "port" +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("obfs"), translate("Obfs Password")) o.rewrite_option = o.option +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("auth_password"), translate("Auth Password")) o.password = true o.rewrite_option = o.option +o:depends({ [_n("custom")] = false }) o = s:option(Flag, _n("udp"), translate("UDP")) o.default = "1" o.rewrite_option = o.option +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("up_mbps"), translate("Max upload Mbps")) o.rewrite_option = o.option +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("down_mbps"), translate("Max download Mbps")) o.rewrite_option = o.option +o:depends({ [_n("custom")] = false }) o = s:option(Flag, _n("ignoreClientBandwidth"), translate("ignoreClientBandwidth")) o.default = "0" o.rewrite_option = o.option +o:depends({ [_n("custom")] = false }) o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem" @@ -57,6 +66,7 @@ o.validate = function(self, value, t) end return nil end +o:depends({ [_n("custom")] = false }) o = s:option(FileUpload, _n("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key") o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key" @@ -71,6 +81,28 @@ o.validate = function(self, value, t) end return nil end +o:depends({ [_n("custom")] = false }) + +o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) +o.rows = 10 +o.wrap = "off" +o:depends({ [_n("custom")] = true }) +o.validate = function(self, value, t) + if value and api.jsonc.parse(value) then + return value + else + return nil, translate("Must be JSON text!") + end +end +o.custom_cfgvalue = function(self, section, value) + local config_str = m:get(section, "config_str") + if config_str then + return api.base64Decode(config_str) + end +end +o.custom_write = function(self, section, value) + m:set(section, "config_str", api.base64Encode(value)) +end o = s:option(Flag, _n("log"), translate("Log")) o.default = "1" diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua index e273d6c9d..fd71d5fe3 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua @@ -28,6 +28,8 @@ local header_type_list = { s.fields["type"]:value(type_name, "Xray") +o = s:option(Flag, _n("custom"), translate("Use Custom Config")) + o = s:option(ListValue, _n("protocol"), translate("Protocol")) o:value("vmess", "Vmess") o:value("vless", "VLESS") @@ -36,9 +38,11 @@ o:value("socks", "Socks") o:value("shadowsocks", "Shadowsocks") o:value("trojan", "Trojan") o:value("dokodemo-door", "dokodemo-door") +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("port"), translate("Listen Port")) o.datatype = "port" +o:depends({ [_n("custom")] = false }) o = s:option(Flag, _n("auth"), translate("Auth")) o.validate = function(self, value, t) @@ -343,6 +347,7 @@ o:depends({ [_n("transport")] = "grpc" }) o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used.")) o.default = "0" +o:depends({ [_n("custom")] = false }) -- [[ Fallback部分 ]]-- o = s:option(Flag, _n("fallback"), translate("Fallback")) @@ -369,9 +374,11 @@ o:depends({ [_n("fallback")] = true }) o = s:option(Flag, _n("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) o.default = "0" +o:depends({ [_n("custom")] = false }) o = s:option(Flag, _n("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!")) o.default = "0" +o:depends({ [_n("custom")] = false }) local nodes_table = {} for k, e in ipairs(api.get_valid_nodes()) do @@ -389,6 +396,7 @@ o:value("_socks", translate("Custom Socks")) o:value("_http", translate("Custom HTTP")) o:value("_iface", translate("Custom Interface")) for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("outbound_node_address"), translate("Address (Support Domain Name)")) o:depends({ [_n("outbound_node")] = "_socks"}) @@ -412,6 +420,27 @@ o = s:option(Value, _n("outbound_node_iface"), translate("Interface")) o.default = "eth1" o:depends({ [_n("outbound_node")] = "_iface"}) +o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) +o.rows = 10 +o.wrap = "off" +o:depends({ [_n("custom")] = true }) +o.validate = function(self, value, t) + if value and api.jsonc.parse(value) then + return value + else + return nil, translate("Must be JSON text!") + end +end +o.custom_cfgvalue = function(self, section, value) + local config_str = m:get(section, "config_str") + if config_str then + return api.base64Decode(config_str) + end +end +o.custom_write = function(self, section, value) + m:set(section, "config_str", api.base64Encode(value)) +end + o = s:option(Flag, _n("log"), translate("Log")) o.default = "1" o.rmempty = false diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua index 236698416..ca1614f80 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua @@ -32,6 +32,8 @@ local ss_method_list = { s.fields["type"]:value(type_name, "Sing-Box") +o = s:option(Flag, _n("custom"), translate("Use Custom Config")) + o = s:option(ListValue, _n("protocol"), translate("Protocol")) o:value("mixed", "Mixed") o:value("socks", "Socks") @@ -54,9 +56,11 @@ if version_ge_1_12_0 then o:value("anytls", "AnyTLS") end o:value("direct", "Direct") +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("port"), translate("Listen Port")) o.datatype = "port" +o:depends({ [_n("custom")] = false }) o = s:option(Flag, _n("auth"), translate("Auth")) o.validate = function(self, value, t) @@ -391,9 +395,11 @@ o:depends({ [_n("tcpbrutal")] = true }) o = s:option(Flag, _n("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) o.default = "0" +o:depends({ [_n("custom")] = false }) o = s:option(Flag, _n("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!")) o.default = "0" +o:depends({ [_n("custom")] = false }) local nodes_table = {} for k, e in ipairs(api.get_valid_nodes()) do @@ -411,6 +417,7 @@ o:value("_socks", translate("Custom Socks")) o:value("_http", translate("Custom HTTP")) o:value("_iface", translate("Custom Interface")) for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("outbound_node_address"), translate("Address (Support Domain Name)")) o:depends({ [_n("outbound_node")] = "_socks" }) @@ -434,6 +441,27 @@ o = s:option(Value, _n("outbound_node_iface"), translate("Interface")) o.default = "eth1" o:depends({ [_n("outbound_node")] = "_iface" }) +o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) +o.rows = 10 +o.wrap = "off" +o:depends({ [_n("custom")] = true }) +o.validate = function(self, value, t) + if value and api.jsonc.parse(value) then + return value + else + return nil, translate("Must be JSON text!") + end +end +o.custom_cfgvalue = function(self, section, value) + local config_str = m:get(section, "config_str") + if config_str then + return api.base64Decode(config_str) + end +end +o.custom_write = function(self, section, value) + m:set(section, "config_str", api.base64Encode(value)) +end + o = s:option(Flag, _n("log"), translate("Log")) o.default = "1" o.rmempty = false diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua index 63ed44c55..22b97424d 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua @@ -24,21 +24,49 @@ local ssrust_encrypt_method_list = { s.fields["type"]:value(type_name, translate("Shadowsocks Rust")) +o = s:option(Flag, _n("custom"), translate("Use Custom Config")) + o = s:option(Value, _n("port"), translate("Listen Port")) o.datatype = "port" +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("password"), translate("Password")) o.password = true +o:depends({ [_n("custom")] = false }) o = s:option(ListValue, _n("method"), translate("Encrypt Method")) for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("timeout"), translate("Connection Timeout")) o.datatype = "uinteger" o.default = 300 +o:depends({ [_n("custom")] = false }) o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) o.default = "0" +o:depends({ [_n("custom")] = false }) + +o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) +o.rows = 10 +o.wrap = "off" +o:depends({ [_n("custom")] = true }) +o.validate = function(self, value, t) + if value and api.jsonc.parse(value) then + return value + else + return nil, translate("Must be JSON text!") + end +end +o.custom_cfgvalue = function(self, section, value) + local config_str = m:get(section, "config_str") + if config_str then + return api.base64Decode(config_str) + end +end +o.custom_write = function(self, section, value) + m:set(section, "config_str", api.base64Encode(value)) +end o = s:option(Flag, _n("log"), translate("Log")) o.default = "1" diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss.lua index 1d6b7c3c5..cac798b84 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss.lua @@ -27,21 +27,49 @@ local ss_encrypt_method_list = { s.fields["type"]:value(type_name, translate("Shadowsocks")) +o = s:option(Flag, _n("custom"), translate("Use Custom Config")) + o = s:option(Value, _n("port"), translate("Listen Port")) o.datatype = "port" +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("password"), translate("Password")) o.password = true +o:depends({ [_n("custom")] = false }) o = s:option(ListValue, _n("method"), translate("Encrypt Method")) for a, t in ipairs(ss_encrypt_method_list) do o:value(t) end +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("timeout"), translate("Connection Timeout")) o.datatype = "uinteger" o.default = 300 +o:depends({ [_n("custom")] = false }) o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) o.default = "0" +o:depends({ [_n("custom")] = false }) + +o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) +o.rows = 10 +o.wrap = "off" +o:depends({ [_n("custom")] = true }) +o.validate = function(self, value, t) + if value and api.jsonc.parse(value) then + return value + else + return nil, translate("Must be JSON text!") + end +end +o.custom_cfgvalue = function(self, section, value) + local config_str = m:get(section, "config_str") + if config_str then + return api.base64Decode(config_str) + end +end +o.custom_write = function(self, section, value) + m:set(section, "config_str", api.base64Encode(value)) +end o = s:option(Flag, _n("log"), translate("Log")) o.default = "1" diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua index a6ba4fb9a..924974bb6 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua @@ -37,31 +37,63 @@ local ssr_obfs_list = { s.fields["type"]:value(type_name, translate("ShadowsocksR")) +o = s:option(Flag, _n("custom"), translate("Use Custom Config")) + o = s:option(Value, _n("port"), translate("Listen Port")) o.datatype = "port" +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("password"), translate("Password")) o.password = true +o:depends({ [_n("custom")] = false }) o = s:option(ListValue, _n("method"), translate("Encrypt Method")) for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end +o:depends({ [_n("custom")] = false }) o = s:option(ListValue, _n("protocol"), translate("Protocol")) for a, t in ipairs(ssr_protocol_list) do o:value(t) end +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("protocol_param"), translate("Protocol_param")) +o:depends({ [_n("custom")] = false }) o = s:option(ListValue, _n("obfs"), translate("Obfs")) for a, t in ipairs(ssr_obfs_list) do o:value(t) end +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("obfs_param"), translate("Obfs_param")) +o:depends({ [_n("custom")] = false }) o = s:option(Value, _n("timeout"), translate("Connection Timeout")) o.datatype = "uinteger" o.default = 300 +o:depends({ [_n("custom")] = false }) o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) o.default = "0" +o:depends({ [_n("custom")] = false }) + +o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) +o.rows = 10 +o.wrap = "off" +o:depends({ [_n("custom")] = true }) +o.validate = function(self, value, t) + if value and api.jsonc.parse(value) then + return value + else + return nil, translate("Must be JSON text!") + end +end +o.custom_cfgvalue = function(self, section, value) + local config_str = m:get(section, "config_str") + if config_str then + return api.base64Decode(config_str) + end +end +o.custom_write = function(self, section, value) + m:set(section, "config_str", api.base64Encode(value)) +end o = s:option(Flag, _n("udp_forward"), translate("UDP Forward")) o.default = "1" diff --git a/luci-app-passwall/luasrc/passwall/api.lua b/luci-app-passwall/luasrc/passwall/api.lua index 3c86dc8b5..8ace0fbb9 100644 --- a/luci-app-passwall/luasrc/passwall/api.lua +++ b/luci-app-passwall/luasrc/passwall/api.lua @@ -190,6 +190,11 @@ function base64Decode(text) end end +function base64Encode(text) + local result = nixio.bin.b64encode(text) + return result +end + --提取URL中的域名和端口(no ip) function get_domain_port_from_url(url) local scheme, domain, port = string.match(url, "^(https?)://([%w%.%-]+):?(%d*)") @@ -1232,11 +1237,16 @@ function luci_types(id, m, s, type_name, option_prefix) end s.fields[key].cfgvalue = function(self, section) - if self.rewrite_option then - return m:get(section, self.rewrite_option) + -- 添加自定义 custom_cfgvalue 属性,如果有自定义的 custom_cfgvalue 函数,则使用自定义的 cfgvalue 逻辑 + if self.custom_cfgvalue then + return self:custom_cfgvalue(section) else - if self.option:find(option_prefix) == 1 then - return m:get(section, self.option:sub(1 + #option_prefix)) + if self.rewrite_option then + return m:get(section, self.rewrite_option) + else + if self.option:find(option_prefix) == 1 then + return m:get(section, self.option:sub(1 + #option_prefix)) + end end end end diff --git a/luci-app-passwall/luasrc/passwall/server_app.lua b/luci-app-passwall/luasrc/passwall/server_app.lua index 9083ce2a6..21220e469 100644 --- a/luci-app-passwall/luasrc/passwall/server_app.lua +++ b/luci-app-passwall/luasrc/passwall/server_app.lua @@ -32,11 +32,15 @@ local function cmd(cmd) end local function ipt(arg) - cmd(ipt_bin .. " -w " .. arg) + if ipt_bin and #ipt_bin > 0 then + cmd(ipt_bin .. " -w " .. arg) + end end local function ip6t(arg) - cmd(ip6t_bin .. " -w " .. arg) + if ip6t_bin and #ip6t_bin > 0 then + cmd(ip6t_bin .. " -w " .. arg) + end end local function ln_run(s, d, command, output) @@ -116,20 +120,12 @@ local function start() local config_file = CONFIG_PATH .. "/" .. id .. ".json" local udp_forward = 1 local type = user.type or "" - if type == "Socks" then - local auth = "" - if user.auth and user.auth == "1" then - local username = user.username or "" - local password = user.password or "" - if username ~= "" and password ~= "" then - username = "-u " .. username - password = "-P " .. password - auth = username .. " " .. password - end + if type == "SS" or type == "SSR" then + if user.custom == "1" and user.config_str then + config = jsonc.parse(api.base64Decode(user.config_str)) + else + config = require(require_dir .. "util_shadowsocks").gen_config_server(user) end - bin = ln_run("/usr/bin/microsocks", "microsocks_" .. id, string.format("-i :: -p %s %s", port, auth), log_path) - elseif type == "SS" or type == "SSR" then - config = require(require_dir .. "util_shadowsocks").gen_config_server(user) local udp_param = "" udp_forward = tonumber(user.udp_forward) or 1 if udp_forward == 1 then @@ -138,22 +134,47 @@ local function start() type = type:lower() bin = ln_run("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param, log_path) elseif type == "SS-Rust" then - config = require(require_dir .. "util_shadowsocks").gen_config_server(user) + if user.custom == "1" and user.config_str then + config = jsonc.parse(api.base64Decode(user.config_str)) + else + config = require(require_dir .. "util_shadowsocks").gen_config_server(user) + end bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path) - elseif type == "sing-box" then - config = require(require_dir .. "util_sing-box").gen_config_server(user) - bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path) elseif type == "Xray" then - config = require(require_dir .. "util_xray").gen_config_server(user) + if user.custom == "1" and user.config_str then + config = jsonc.parse(api.base64Decode(user.config_str)) + if log_path then + if not config.log then + config.log = {} + end + config.log.loglevel = user.loglevel + end + else + config = require(require_dir .. "util_xray").gen_config_server(user) + end bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path) - elseif type == "Trojan" then - config = require(require_dir .. "util_trojan").gen_config_server(user) - bin = ln_run("/usr/sbin/trojan", "trojan", "-c " .. config_file, log_path) - elseif type == "Trojan-Plus" then - config = require(require_dir .. "util_trojan").gen_config_server(user) - bin = ln_run("/usr/sbin/trojan-plus", "trojan-plus", "-c " .. config_file, log_path) + elseif type == "sing-box" then + if user.custom == "1" and user.config_str then + config = jsonc.parse(api.base64Decode(user.config_str)) + if log_path then + if not config.log then + config.log = {} + end + config.log.timestamp = true + config.log.disabled = false + config.log.level = user.loglevel + config.log.output = log_path + end + else + config = require(require_dir .. "util_sing-box").gen_config_server(user) + end + bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path) elseif type == "Hysteria2" then - config = require(require_dir .. "util_hysteria2").gen_config_server(user) + if user.custom == "1" and user.config_str then + config = jsonc.parse(api.base64Decode(user.config_str)) + else + config = require(require_dir .. "util_hysteria2").gen_config_server(user) + end bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path) end @@ -163,7 +184,7 @@ local function start() f:write(jsonc.stringify(config, 1)) f:close() end - log(string.format("%s %s 生成配置文件并运行 - %s", remarks, port, config_file)) + log(string.format("%s 生成配置文件并运行 - %s", remarks, config_file)) end if bin then @@ -171,7 +192,7 @@ local function start() end local bind_local = user.bind_local or 0 - if bind_local and tonumber(bind_local) ~= 1 then + if bind_local and tonumber(bind_local) ~= 1 and port then if nft_flag == "0" then ipt(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) ip6t(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po index 77679a1c8..77a38caae 100644 --- a/luci-app-passwall/po/zh-cn/passwall.po +++ b/luci-app-passwall/po/zh-cn/passwall.po @@ -1846,6 +1846,15 @@ msgstr "端口跳跃范围" msgid "Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,)." msgstr "格式为:1000:2000 或 1000-2000 多组时用逗号(,)隔开。" +msgid "Use Custom Config" +msgstr "使用自定义配置" + +msgid "Custom Config" +msgstr "自定义配置" + +msgid "Must be JSON text!" +msgstr "必须是 JSON 文本内容!" + msgid "Geo View" msgstr "Geo 查询"