parent
c0a2aea1c8
commit
5aa3db5849
@ -21,11 +21,15 @@ function index()
|
||||
entry({"admin", "services", "shadowsocksr", "subscribe"}, call("subscribe"))
|
||||
entry({"admin", "services", "shadowsocksr", "checkport"}, call("check_port"))
|
||||
entry({"admin", "services", "shadowsocksr", "log"}, form("shadowsocksr/log"), _("Log"), 80).leaf = true
|
||||
entry({"admin", "services", "shadowsocksr", "get_log"}, call("get_log")).leaf = true
|
||||
entry({"admin", "services", "shadowsocksr", "clear_log"}, call("clear_log")).leaf = true
|
||||
entry({"admin", "services", "shadowsocksr", "run"}, call("act_status"))
|
||||
entry({"admin", "services", "shadowsocksr", "ping"}, call("act_ping"))
|
||||
entry({"admin", "services", "shadowsocksr", "reset"}, call("act_reset"))
|
||||
entry({"admin", "services", "shadowsocksr", "restart"}, call("act_restart"))
|
||||
entry({"admin", "services", "shadowsocksr", "delete"}, call("act_delete"))
|
||||
--[[Backup]]
|
||||
entry({"admin", "services", "shadowsocksr", "backup"}, call("create_backup")).leaf = true
|
||||
end
|
||||
|
||||
function subscribe()
|
||||
@ -107,9 +111,9 @@ function check_port()
|
||||
ret = socket:connect(s.server, s.server_port)
|
||||
if tostring(ret) == "true" then
|
||||
socket:close()
|
||||
retstring = retstring .. "<font color = 'green'>[" .. server_name .. "] OK.</font><br />"
|
||||
retstring = retstring .. "<font><b style='color:green'>[" .. server_name .. "] OK.</b></font><br />"
|
||||
else
|
||||
retstring = retstring .. "<font color = 'red'>[" .. server_name .. "] Error.</font><br />"
|
||||
retstring = retstring .. "<font><b style='color:red'>[" .. server_name .. "] Error.</b></font><br />"
|
||||
end
|
||||
if iret == 0 then
|
||||
luci.sys.call("ipset del ss_spec_wan_ac " .. s.server)
|
||||
@ -120,7 +124,7 @@ function check_port()
|
||||
end
|
||||
|
||||
function act_reset()
|
||||
luci.sys.call("/etc/init.d/shadowsocksr reset &")
|
||||
luci.sys.call("/etc/init.d/shadowsocksr reset >/dev/null 2>&1")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr"))
|
||||
end
|
||||
|
||||
@ -133,3 +137,28 @@ function act_delete()
|
||||
luci.sys.call("/etc/init.d/shadowsocksr restart &")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
|
||||
end
|
||||
|
||||
function get_log()
|
||||
luci.http.write(luci.sys.exec("[ -f '/var/log/ssrplus.log' ] && cat /var/log/ssrplus.log"))
|
||||
end
|
||||
|
||||
function clear_log()
|
||||
luci.sys.call("echo '' > /var/log/ssrplus.log")
|
||||
end
|
||||
|
||||
function create_backup()
|
||||
local backup_files = {
|
||||
"/etc/config/shadowsocksr",
|
||||
"/etc/ssrplus/*"
|
||||
}
|
||||
local date = os.date("%Y%m%d")
|
||||
local tar_file = "/tmp/shadowsocksr-" .. date .. "-backup.tar.gz"
|
||||
nixio.fs.remove(tar_file)
|
||||
local cmd = "tar -czf " .. tar_file .. " " .. table.concat(backup_files, " ")
|
||||
luci.sys.call(cmd)
|
||||
luci.http.header("Content-Disposition", "attachment; filename=shadowsocksr-" .. date .. "-backup.tar.gz")
|
||||
luci.http.header("X-Backup-Filename", "shadowsocksr-" .. date .. "-backup.tar.gz")
|
||||
luci.http.prepare_content("application/octet-stream")
|
||||
luci.http.write(nixio.fs.readfile(tar_file))
|
||||
nixio.fs.remove(tar_file)
|
||||
end
|
||||
|
@ -1,5 +1,7 @@
|
||||
local m, s, o
|
||||
local uci = luci.model.uci.cursor()
|
||||
local server_table = {}
|
||||
local type_table = {}
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec('type -t -p "%s"' % e) ~= "" and true or false
|
||||
end
|
||||
@ -10,6 +12,9 @@ uci:foreach("shadowsocksr", "servers", function(s)
|
||||
elseif s.server and s.server_port then
|
||||
server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port}
|
||||
end
|
||||
if s.type then
|
||||
type_table[s[".name"]] = s.type
|
||||
end
|
||||
end)
|
||||
|
||||
local key_table = {}
|
||||
@ -96,7 +101,7 @@ o.default = "https://raw.githubusercontent.com/neodevpro/neodevhost/master/lite_
|
||||
o:depends("adblock", "1")
|
||||
o.description = translate("Support AdGuardHome and DNSMASQ format list")
|
||||
|
||||
o = s:option(Button, "reset", translate("Reset to defaults"))
|
||||
o = s:option(Button, "Reset", translate("Reset to defaults"))
|
||||
o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
luci.sys.call("/etc/init.d/shadowsocksr reset")
|
||||
@ -107,15 +112,82 @@ end
|
||||
s = m:section(TypedSection, "socks5_proxy", translate("Global SOCKS5 Proxy Server"))
|
||||
s.anonymous = true
|
||||
|
||||
-- Enable/Disable Option
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
-- Server Selection
|
||||
o = s:option(ListValue, "server", translate("Server"))
|
||||
o:value("nil", translate("Disable"))
|
||||
o:value("same", translate("Same as Global Server"))
|
||||
for _, key in pairs(key_table) do
|
||||
o:value(key, server_table[key])
|
||||
end
|
||||
o.default = "nil"
|
||||
o.default = "same"
|
||||
o.rmempty = false
|
||||
|
||||
-- Dynamic value handling based on enabled/disabled state
|
||||
o.cfgvalue = function(self, section)
|
||||
local enabled = m:get(section, "enabled")
|
||||
if enabled == "0" then
|
||||
return m:get(section, "old_server") or "same"
|
||||
end
|
||||
return Value.cfgvalue(self, section) or "same" -- Default to `same` when enabled
|
||||
end
|
||||
|
||||
o.write = function(self, section, value)
|
||||
local enabled = m:get(section, "enabled")
|
||||
if enabled == "0" then
|
||||
local old_server = Value.cfgvalue(self, section) or "same"
|
||||
if old_server ~= "nil" then
|
||||
m:set(section, "old_server", old_server)
|
||||
end
|
||||
m:set(section, "server", "nil")
|
||||
else
|
||||
m:del(section, "old_server")
|
||||
-- Write the value normally when enabled
|
||||
Value.write(self, section, value)
|
||||
end
|
||||
end
|
||||
|
||||
-- Socks Auth
|
||||
if is_finded("xray") then
|
||||
o = s:option(ListValue, "socks5_auth", translate("Socks5 Auth Mode"), translate("Socks protocol auth methods, default:noauth."))
|
||||
o.default = "noauth"
|
||||
o:value("noauth", "NOAUTH")
|
||||
o:value("password", "PASSWORD")
|
||||
o.rmempty = true
|
||||
for key, server_type in pairs(type_table) do
|
||||
if server_type == "v2ray" then
|
||||
-- 如果服务器类型是 v2ray,则设置依赖项显示
|
||||
o:depends("server", key)
|
||||
end
|
||||
end
|
||||
|
||||
-- Socks User
|
||||
o = s:option(Value, "socks5_user", translate("Socks5 User"), translate("Only when auth is password valid, Mandatory."))
|
||||
o.rmempty = true
|
||||
o:depends("socks5_auth", "password")
|
||||
|
||||
-- Socks Password
|
||||
o = s:option(Value, "socks5_pass", translate("Socks5 Password"), translate("Only when auth is password valid, Not mandatory."))
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
o:depends("socks5_auth", "password")
|
||||
|
||||
-- Socks Mixed
|
||||
o = s:option(Flag, "socks5_mixed", translate("Enabled Mixed"), translate("Mixed as an alias of socks, default:Enabled."))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
for key, server_type in pairs(type_table) do
|
||||
if server_type == "v2ray" then
|
||||
-- 如果服务器类型是 v2ray,则设置依赖项显示
|
||||
o:depends("server", key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Local Port
|
||||
o = s:option(Value, "local_port", translate("Local Port"))
|
||||
o.datatype = "port"
|
||||
o.default = 1080
|
||||
@ -132,6 +204,7 @@ o.default = 0
|
||||
o = s:option(ListValue, "fragment_packets", translate("Fragment Packets"), translate("\"1-3\" is for segmentation at TCP layer, applying to the beginning 1 to 3 data writes by the client. \"tlshello\" is for TLS client hello packet fragmentation."))
|
||||
o.default = "tlshello"
|
||||
o:value("tlshello", "tlshello")
|
||||
o:value("1-1", "1-1")
|
||||
o:value("1-2", "1-2")
|
||||
o:value("1-3", "1-3")
|
||||
o:value("1-5", "1-5")
|
||||
|
@ -618,8 +618,7 @@ o:depends({type = "v2ray", v2ray_protocol = "socks"})
|
||||
|
||||
-- 传输协议
|
||||
o = s:option(ListValue, "transport", translate("Transport"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("raw", "RAW")
|
||||
o:value("raw", "RAW (TCP)")
|
||||
o:value("kcp", "mKCP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("httpupgrade", "HTTPUpgrade")
|
||||
@ -635,17 +634,9 @@ o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "socks"})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "http"})
|
||||
|
||||
-- [[ TCP部分 ]]--
|
||||
-- [[ RAW部分 ]]--
|
||||
-- TCP伪装
|
||||
o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
|
||||
o:depends("transport", "tcp")
|
||||
o:value("none", translate("None"))
|
||||
o:value("http", "HTTP")
|
||||
o.rmempty = true
|
||||
|
||||
-- [[ RAW部分 ]]--
|
||||
-- RAW伪装
|
||||
o = s:option(ListValue, "raw_guise", translate("Camouflage Type"))
|
||||
o:depends("transport", "raw")
|
||||
o:value("none", translate("None"))
|
||||
o:value("http", "HTTP")
|
||||
@ -654,13 +645,11 @@ o.rmempty = true
|
||||
-- HTTP域名
|
||||
o = s:option(Value, "http_host", translate("HTTP Host"))
|
||||
o:depends("tcp_guise", "http")
|
||||
o:depends("raw_guise", "http")
|
||||
o.rmempty = true
|
||||
|
||||
-- HTTP路径
|
||||
o = s:option(Value, "http_path", translate("HTTP Path"))
|
||||
o:depends("tcp_guise", "http")
|
||||
o:depends("raw_guise", "http")
|
||||
o.rmempty = true
|
||||
|
||||
-- [[ WS部分 ]]--
|
||||
@ -935,17 +924,20 @@ if is_finded("xray") then
|
||||
-- [[ XTLS ]]--
|
||||
o = s:option(ListValue, "tls_flow", translate("Flow"))
|
||||
for _, v in ipairs(tls_flows) do
|
||||
if v == "none" then
|
||||
o.default = "none"
|
||||
o:value("none", translate("none"))
|
||||
else
|
||||
o:value(v, translate(v))
|
||||
end
|
||||
end
|
||||
o.rmempty = true
|
||||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "tcp", tls = true})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "raw", tls = true})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "tcp", reality = true})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "raw", reality = true})
|
||||
|
||||
-- [[ uTLS ]]--
|
||||
o = s:option(ListValue, "fingerprint", translate("Finger Print"))
|
||||
o.default = "chrome"
|
||||
o.default = ""
|
||||
o:value("chrome", translate("chrome"))
|
||||
o:value("firefox", translate("firefox"))
|
||||
o:value("safari", translate("safari"))
|
||||
@ -1024,7 +1016,7 @@ o:depends("mux", true)
|
||||
|
||||
|
||||
-- [[ MPTCP ]]--
|
||||
o = s:option(Flag, "mptcp", translate("MPTCP"))
|
||||
o = s:option(Flag, "mptcp", translate("MPTCP"), translate("Enabling MPTCP Requires Server Support."))
|
||||
o.rmempty = false
|
||||
o.default = false
|
||||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||||
|
@ -1,5 +1,6 @@
|
||||
require "luci.ip"
|
||||
require "nixio.fs"
|
||||
require "luci.sys"
|
||||
local m, s, o
|
||||
|
||||
m = Map("shadowsocksr")
|
||||
@ -140,4 +141,11 @@ o.remove = function(self, section, value)
|
||||
nixio.fs.writefile(netflixconf, "")
|
||||
end
|
||||
|
||||
if luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 then
|
||||
m.apply_on_parse = true
|
||||
function m.on_apply(self)
|
||||
luci.sys.call("/etc/init.d/shadowsocksr reload > /dev/null 2>&1 &")
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
||||
|
@ -1,20 +1,102 @@
|
||||
require "luci.util"
|
||||
require "nixio.fs"
|
||||
require "luci.sys"
|
||||
require "luci.http"
|
||||
|
||||
f = SimpleForm("logview")
|
||||
f.reset = false
|
||||
f.submit = false
|
||||
t = f:field(TextValue, "conf")
|
||||
t.rmempty = true
|
||||
t.rows = 20
|
||||
function t.cfgvalue()
|
||||
if nixio.fs.access("/var/log/ssrplus.log") then
|
||||
local logs = luci.util.execi("cat /var/log/ssrplus.log")
|
||||
local s = ""
|
||||
for line in logs do
|
||||
s = line .. "\n" .. s
|
||||
end
|
||||
return s
|
||||
f:append(Template("shadowsocksr/log"))
|
||||
|
||||
-- 自定义 log 函数
|
||||
function log(...)
|
||||
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
|
||||
local f, err = io.open("/var/log/ssrplus.log", "a")
|
||||
if f and err == nil then
|
||||
f:write(result .. "\n")
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
t.readonly = "readonly"
|
||||
return f
|
||||
|
||||
-- 创建备份与恢复表单
|
||||
fb = SimpleForm('backup-restore')
|
||||
fb.reset = false
|
||||
fb.submit = false
|
||||
s = fb:section(SimpleSection, translate("Backup and Restore"), translate("Backup or Restore Client and Server Configurations.") ..
|
||||
"<br><font style='color:red'><b>" ..
|
||||
translate("Note: Restoring configurations across different versions may cause compatibility issues.") ..
|
||||
"</b></font>")
|
||||
o = s:option(DummyValue, '', nil)
|
||||
o.template = "shadowsocksr/backup_restore"
|
||||
|
||||
-- 定义备份目标文件和目录
|
||||
local backup_targets = {
|
||||
files = {
|
||||
"/etc/config/shadowsocksr"
|
||||
},
|
||||
dirs = {
|
||||
"/etc/ssrplus"
|
||||
}
|
||||
}
|
||||
|
||||
local file_path = '/tmp/shadowsocksr_upload.tar.gz'
|
||||
local temp_dir = '/tmp/shadowsocksr_bak'
|
||||
local fd
|
||||
|
||||
-- 处理文件上传
|
||||
luci.http.setfilehandler(function(meta, chunk, eof)
|
||||
if not fd and meta and meta.name == "ulfile" and chunk then
|
||||
-- 初始化上传处理
|
||||
luci.sys.call("rm -rf " .. temp_dir)
|
||||
nixio.fs.remove(file_path)
|
||||
fd = nixio.open(file_path, "w")
|
||||
luci.sys.call("echo '' > /var/log/ssrplus.log")
|
||||
end
|
||||
|
||||
if fd and chunk then
|
||||
fd:write(chunk)
|
||||
end
|
||||
|
||||
if eof and fd then
|
||||
fd:close()
|
||||
fd = nil
|
||||
if nixio.fs.access(file_path) then
|
||||
log(" * shadowsocksr 配置文件上传成功…") -- 使用自定义的 log 函数
|
||||
luci.sys.call("mkdir -p " .. temp_dir)
|
||||
|
||||
if luci.sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then
|
||||
-- 处理文件还原
|
||||
for _, target in ipairs(backup_targets.files) do
|
||||
local temp_file = temp_dir .. target
|
||||
if nixio.fs.access(temp_file) then
|
||||
luci.sys.call(string.format("cp -f '%s' '%s'", temp_file, target))
|
||||
log(" * 文件 " .. target .. " 还原成功…") -- 使用自定义的 log 函数
|
||||
end
|
||||
end
|
||||
|
||||
-- 处理目录还原
|
||||
for _, target in ipairs(backup_targets.dirs) do
|
||||
local temp_dir_path = temp_dir .. target
|
||||
if nixio.fs.access(temp_dir_path) then
|
||||
luci.sys.call(string.format("cp -rf '%s'/* '%s/'", temp_dir_path, target))
|
||||
log(" * 目录 " .. target .. " 还原成功…") -- 使用自定义的 log 函数
|
||||
end
|
||||
end
|
||||
|
||||
log(" * shadowsocksr 配置还原成功…") -- 使用自定义的 log 函数
|
||||
log(" * 重启 shadowsocksr 服务中…\n") -- 使用自定义的 log 函数
|
||||
luci.sys.call('/etc/init.d/shadowsocksr restart > /dev/null 2>&1 &')
|
||||
else
|
||||
log(" * shadowsocksr 配置文件解压失败,请重试!") -- 使用自定义的 log 函数
|
||||
end
|
||||
else
|
||||
log(" * shadowsocksr 配置文件上传失败,请重试!") -- 使用自定义的 log 函数
|
||||
end
|
||||
|
||||
-- 清理临时文件
|
||||
luci.sys.call("rm -rf " .. temp_dir)
|
||||
nixio.fs.remove(file_path)
|
||||
end
|
||||
end)
|
||||
|
||||
return f, fb
|
||||
|
@ -29,7 +29,7 @@ o:value("5", translate("Every Friday"))
|
||||
o:value("6", translate("Every Saturday"))
|
||||
o:value("0", translate("Every Sunday"))
|
||||
o.default = "*"
|
||||
o.rmempty = false
|
||||
o.rmempty = true
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
o = s:option(ListValue, "auto_update_day_time", translate("Update time (every day)"))
|
||||
@ -37,7 +37,7 @@ for t = 0, 23 do
|
||||
o:value(t, t .. ":00")
|
||||
end
|
||||
o.default = 2
|
||||
o.rmempty = false
|
||||
o.rmempty = true
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
o = s:option(ListValue, "auto_update_min_time", translate("Update Interval (min)"))
|
||||
@ -45,7 +45,7 @@ for i = 0, 59 do
|
||||
o:value(i, i .. ":00")
|
||||
end
|
||||
o.default = 30
|
||||
o.rmempty = false
|
||||
o.rmempty = true
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
o = s:option(DynamicList, "subscribe_url", translate("Subscribe URL"))
|
||||
|
@ -155,6 +155,14 @@ if nixio.fs.access("/usr/bin/kcptun-client") then
|
||||
end
|
||||
end
|
||||
|
||||
s = m:field(Button, "Restart", translate("Restart ShadowSocksR Plus+"))
|
||||
s.inputtitle = translate("Restart Service")
|
||||
s.inputstyle = "reload"
|
||||
s.write = function()
|
||||
luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "client"))
|
||||
end
|
||||
|
||||
s = m:field(DummyValue, "google", translate("Google Connectivity"))
|
||||
s.value = translate("No Check")
|
||||
s.template = "shadowsocksr/check"
|
||||
|
154
luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm
Normal file
154
luci-app-ssr-plus/luasrc/view/shadowsocksr/backup_restore.htm
Normal file
@ -0,0 +1,154 @@
|
||||
<%+cbi/valueheader%>
|
||||
<div class="cbi-value" id="_backup_div">
|
||||
<label class="cbi-value-title"><%:Create Backup File%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input type="button" class="btn cbi-button cbi-input-apply" onclick="dl_backup()" value="<%:DL Backup%>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-value" id="_upload_div">
|
||||
<label class="cbi-value-title"><%:Restore Backup File%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input type="button" class="btn cbi-button cbi-input-apply" id="upload-btn" value="<%:RST Backup%>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-value" id="_reset_div">
|
||||
<label class="cbi-value-title"><%:Restore to default configuration%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input type="button" class="btn cbi-button cbi-button-remove" onclick="do_reset()" value="<%:Do Reset%>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-modal" class="up-modal" style="display:none;">
|
||||
<div class="up-modal-content">
|
||||
<h3><%:Restore Backup File%></h3>
|
||||
<div class="cbi-value" id="_upload_div">
|
||||
<div class="up-cbi-value-field">
|
||||
<input class="cbi-input-file" type="file" id="ulfile" name="ulfile" accept=".tar.gz" required />
|
||||
<br />
|
||||
<div class="up-button-container">
|
||||
<input type="submit" class="btn cbi-button cbi-input-apply" value="<%:UL Restore%>" />
|
||||
<button class="btn cbi-button cbi-button-remove" id="upload-close"><%:CLOSE WIN%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.up-modal {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border: 2px solid #ccc;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.up-modal-content {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.up-button-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.up-cbi-value-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// JavaScript 版本的 url 函数
|
||||
function url(...args) {
|
||||
let url = "/cgi-bin/luci/admin/services/shadowsocksr";
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] !== "") {
|
||||
url += "/" + args[i];
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// 上传按钮点击事件
|
||||
document.getElementById("upload-btn").addEventListener("click", function() {
|
||||
document.getElementById("upload-modal").style.display = "block";
|
||||
});
|
||||
|
||||
// 关闭上传模态框
|
||||
document.getElementById("upload-close").addEventListener("click", function() {
|
||||
document.getElementById("upload-modal").style.display = "none";
|
||||
});
|
||||
|
||||
// 备份下载函数
|
||||
function dl_backup(btn) {
|
||||
fetch(url("backup"), { // 使用 JavaScript 版本的 url 函数
|
||||
method: 'POST',
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error("备份失败!");
|
||||
}
|
||||
const filename = response.headers.get("X-Backup-Filename");
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
return response.blob().then(blob => ({ blob, filename }));
|
||||
})
|
||||
.then(result => {
|
||||
if (!result) return;
|
||||
const { blob, filename } = result;
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(error => alert(error.message));
|
||||
}
|
||||
|
||||
// 恢复出厂设置
|
||||
function do_reset(btn) {
|
||||
if (confirm("<%: Do you want to restore the client to default settings?%>")) {
|
||||
setTimeout(function () {
|
||||
if (confirm("<%: Are you sure you want to restore the client to default settings?%>")) {
|
||||
// 清理日志
|
||||
var xhr1 = new XMLHttpRequest();
|
||||
xhr1.open("GET", url("clear_log"), true); // 使用 JavaScript 版本的 url 函数
|
||||
xhr1.send();
|
||||
// 恢复出厂
|
||||
var xhr2 = new XMLHttpRequest();
|
||||
xhr2.open("GET", url("reset"), true); // 使用 JavaScript 版本的 url 函数
|
||||
xhr2.send();
|
||||
// 处理响应
|
||||
xhr2.onload = function() {
|
||||
if (xhr2.status === 200) {
|
||||
window.location.href = url("reset");
|
||||
}
|
||||
};
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<%+cbi/valuefooter%>
|
37
luci-app-ssr-plus/luasrc/view/shadowsocksr/log.htm
Normal file
37
luci-app-ssr-plus/luasrc/view/shadowsocksr/log.htm
Normal file
@ -0,0 +1,37 @@
|
||||
<%
|
||||
local dsp = require "luci.dispatcher"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function clearlog(btn) {
|
||||
XHR.get('<%=dsp.build_url("admin/services/shadowsocksr/clear_log")%>', null,
|
||||
function(x, data) {
|
||||
if (x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = "";
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
XHR.poll(5, '<%=dsp.build_url("admin/services/shadowsocksr/get_log")%>', null,
|
||||
function(x, data) {
|
||||
if (x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
// 将日志分行处理,移除最后一行空行但保留中间空行
|
||||
var logs = x.responseText.split("\n");
|
||||
if (logs[logs.length - 1].trim() === "") {
|
||||
logs.pop(); // 删除最后的空行
|
||||
}
|
||||
logs = logs.reverse().join("\n"); // 倒序排列
|
||||
log_textarea.innerHTML = logs;
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<fieldset class="cbi-section" id="_log_fieldset">
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clearlog()" value="<%:Clear logs%>" />
|
||||
<textarea id="log_textarea" class="cbi-input-textarea" style="width: 100%;margin-top: 10px;" data-update="change" rows="20" wrap="off" readonly="readonly"></textarea>
|
||||
</fieldset>
|
@ -217,8 +217,9 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
case "trojan":
|
||||
try {
|
||||
var url = new URL("http://" + ssu[1]);
|
||||
var params = url.searchParams;
|
||||
} catch(e) {
|
||||
alert(e)
|
||||
alert(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -232,7 +233,65 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = url.searchParams.get("sni");
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fingerprint')[0].value = params.get("fp") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni");
|
||||
if (params.get("allowInsecure") === "1") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true; // 设置 insecure 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event); // 触发事件
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].value =
|
||||
params.get("type") == "http" ? "h2" :
|
||||
(["tcp", "raw"].includes(params.get("type")) ? "raw" :
|
||||
(params.get("type") || "raw"));
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].dispatchEvent(event);
|
||||
switch (params.get("type")) {
|
||||
case "ws":
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
break;
|
||||
case "httpupgrade":
|
||||
if (params.get("security") !== "tls") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
|
||||
break;
|
||||
case "splithttp":
|
||||
if (params.get("security") !== "tls") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
|
||||
break;
|
||||
case "kcp":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || "";
|
||||
break;
|
||||
case "http":
|
||||
/* this is non-standard, bullshit */
|
||||
case "h2":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
|
||||
break;
|
||||
case "quic":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_security')[0].value = params.get("quicSecurity") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_key')[0].value = params.get("key") || "";
|
||||
break;
|
||||
case "grpc":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.serviceName')[0].value = params.get("serviceName") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.grpc_mode')[0].value = params.get("mode") || "gun";
|
||||
break;
|
||||
case "raw":
|
||||
case "tcp":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].dispatchEvent(event);
|
||||
if (params.get("headerType") === "http") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
@ -254,11 +313,12 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = ssm.port;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alter_id')[0].value = ssm.aid;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.vmess_id')[0].value = ssm.id;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].value = ssm.net;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].value =
|
||||
(ssm.net === "raw" || ssm.net === "tcp") ? "raw" : ssm.net;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].dispatchEvent(event);
|
||||
if (ssm.net == "tcp") {
|
||||
if (ssm.net === "raw" || ssm.net === "tcp") {
|
||||
if (ssm.type && ssm.type != "http") {
|
||||
ssm.type = "none"
|
||||
ssm.type = "none";
|
||||
} else {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = ssm.host;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_path')[0].value = ssm.path;
|
||||
@ -294,8 +354,10 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = ssm.sni || ssm.host;
|
||||
}
|
||||
if (ssm.mux !== undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].dispatchEvent(event);
|
||||
}
|
||||
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "vless":
|
||||
@ -303,88 +365,101 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
var url = new URL("http://" + ssu[1]);
|
||||
var params = url.searchParams;
|
||||
} catch(e) {
|
||||
alert(e)
|
||||
alert(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = "vless";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "80";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.vmess_id')[0].value = url.username;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].value =
|
||||
params.get("type") == "http" ? "h2" :
|
||||
(params.get("type") == "raw" ? "raw" :
|
||||
(params.get("type") || "tcp"));
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.vless_encryption')[0].value = params.get("encryption") || "none";
|
||||
// Check if the elements exist before trying to modify them
|
||||
function setElementValue(name, value) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
if (element.type === "checkbox" || element.type === "radio") {
|
||||
element.checked = value === true;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
function dispatchEventIfExists(name, event) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.alias', url.hash ? decodeURIComponent(url.hash.slice(1)) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.type', "v2ray");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.type', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.v2ray_protocol', "vless");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.v2ray_protocol', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server', url.hostname);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server_port', url.port || "80");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.vmess_id', url.username);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.transport',
|
||||
params.get("type") === "http" ? "h2" :
|
||||
(["tcp", "raw"].includes(params.get("type")) ? "raw" :
|
||||
(params.get("type") || "tcp"))
|
||||
);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.transport', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.vless_encryption', params.get("encryption") || "none");
|
||||
if ([ "tls", "xtls", "reality" ].includes(params.get("security"))) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.' + params.get("security"))[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.' + params.get("security"))[0].dispatchEvent(event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.' + params.get("security"), true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.' + params.get("security"), event);
|
||||
|
||||
if (params.get("security") === "reality") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.reality_publickey')[0].value = params.get("pbk") ? decodeURIComponent(params.get("pbk")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.reality_shortid')[0].value = params.get("sid") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.reality_spiderx')[0].value = params.get("spx") ? decodeURIComponent(params.get("spx")) : "";
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_publickey', params.get("pbk") ? decodeURIComponent(params.get("pbk")) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_shortid', params.get("sid") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_spiderx', params.get("spx") ? decodeURIComponent(params.get("spx")) : "");
|
||||
}
|
||||
if (params.get("security") === "xtls") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_flow')[0].value = params.get("flow") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_flow')[0].dispatchEvent(event);
|
||||
}
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fingerprint')[0].value = params.get("fp") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni") || "";
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
|
||||
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.get("fp") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.get("sni") || "");
|
||||
}
|
||||
switch (params.get("type")) {
|
||||
case "ws":
|
||||
if (params.get("security") !== "tls")
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
break;
|
||||
case "httpupgrade":
|
||||
if (params.get("security") !== "tls")
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
break;
|
||||
case "splithttp":
|
||||
if (params.get("security") !== "tls")
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
|
||||
if (params.get("security") !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.splithttp_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.splithttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
|
||||
break;
|
||||
case "kcp":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || "";
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.get("headerType") || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.get("seed") || "");
|
||||
break;
|
||||
case "http":
|
||||
/* this is non-standard, bullshit */
|
||||
case "h2":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
|
||||
break;
|
||||
case "quic":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_security')[0].value = params.get("quicSecurity") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_key')[0].value = params.get("key") || "";
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_guise', params.get("headerType") || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_security', params.get("quicSecurity") || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_key', params.get("key") || "");
|
||||
break;
|
||||
case "grpc":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.serviceName')[0].value = params.get("serviceName") || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.grpc_mode')[0].value = params.get("mode") || "gun";
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.serviceName', params.get("serviceName") || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.grpc_mode', params.get("mode") || "gun");
|
||||
break;
|
||||
case "tcp":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].dispatchEvent(event);
|
||||
if (params.get("headerType") === "http") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
|
||||
}
|
||||
case "raw":
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.raw_guise')[0].value = params.get("headerType") || "none";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.raw_guise')[0].dispatchEvent(event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tcp_guise', params.get("headerType") || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tcp_guise', event);
|
||||
if (params.get("headerType") === "http") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -91,6 +91,8 @@ msgstr "TLS 主机名"
|
||||
msgid "allowInsecure"
|
||||
msgstr "允许不安全连接"
|
||||
|
||||
msgid "Enabling MPTCP Requires Server Support."
|
||||
msgstr "启用 MPTCP 需服务端支持。"
|
||||
|
||||
msgid "concurrency"
|
||||
msgstr "TCP 最大并发连接数"
|
||||
@ -377,6 +379,12 @@ msgstr "强制走代理"
|
||||
msgid "UDP Relay"
|
||||
msgstr "UDP 中继"
|
||||
|
||||
msgid "Restart ShadowSocksR Plus+"
|
||||
msgstr "重启 ShadowSocksR Plus+"
|
||||
|
||||
msgid "Restart Service"
|
||||
msgstr "重启服务"
|
||||
|
||||
msgid "Google Connectivity"
|
||||
msgstr "【谷歌】连通性检查"
|
||||
|
||||
@ -863,6 +871,30 @@ msgstr "本机服务端"
|
||||
msgid "Global SOCKS5 Proxy Server"
|
||||
msgstr "SOCKS5 代理服务端(全局)"
|
||||
|
||||
msgid "Socks5 Auth Mode"
|
||||
msgstr "Socks5 认证方式"
|
||||
|
||||
msgid "Socks protocol auth methods, default:noauth."
|
||||
msgstr "Socks 协议的认证方式,默认值:noauth。"
|
||||
|
||||
msgid "Socks5 User"
|
||||
msgstr "Socks5 用户名"
|
||||
|
||||
msgid "Only when auth is password valid, Mandatory."
|
||||
msgstr "仅当 auth 为 password 时有效,必填。"
|
||||
|
||||
msgid "Socks5 Password"
|
||||
msgstr "Socks5 密码"
|
||||
|
||||
msgid "Only when auth is password valid, Not mandatory."
|
||||
msgstr "仅当 auth 为 password 时有效,非必填。"
|
||||
|
||||
msgid "Enabled Mixed"
|
||||
msgstr "启用 Mixed"
|
||||
|
||||
msgid "Mixed as an alias of socks, default:Enabled."
|
||||
msgstr "Mixed 作为 SOCKS 的别名,默认:启用。"
|
||||
|
||||
msgid "Xray Fragment Settings"
|
||||
msgstr "Xray 分片设置"
|
||||
|
||||
@ -1147,3 +1179,45 @@ msgstr "socks5 服务器可以从外部接收的最大数据包大小(单位
|
||||
|
||||
msgid "Disable ChinaDNS-NG"
|
||||
msgstr "直通模式(禁用 ChinaDNS-NG)"
|
||||
|
||||
msgid "Clear logs"
|
||||
msgstr "清空日志"
|
||||
|
||||
msgid "Backup and Restore"
|
||||
msgstr "备份还原"
|
||||
|
||||
msgid "Backup or Restore Client and Server Configurations."
|
||||
msgstr "备份或还原客户端及服务端配置。"
|
||||
|
||||
msgid "Note: Restoring configurations across different versions may cause compatibility issues."
|
||||
msgstr "注意:不同版本间的配置恢复可能会导致兼容性问题。"
|
||||
|
||||
msgid "Create Backup File"
|
||||
msgstr "创建备份文件"
|
||||
|
||||
msgid "Restore Backup File"
|
||||
msgstr "恢复备份文件"
|
||||
|
||||
msgid "DL Backup"
|
||||
msgstr "下载备份"
|
||||
|
||||
msgid "RST Backup"
|
||||
msgstr "恢复备份"
|
||||
|
||||
msgid "UL Restore"
|
||||
msgstr "上传恢复"
|
||||
|
||||
msgid "CLOSE WIN"
|
||||
msgstr "关闭窗口"
|
||||
|
||||
msgid "Restore to default configuration"
|
||||
msgstr "恢复默认配置"
|
||||
|
||||
msgid "Do Reset"
|
||||
msgstr "执行重置"
|
||||
|
||||
msgid "Do you want to restore the client to default settings?"
|
||||
msgstr "是否要恢复客户端默认配置?"
|
||||
|
||||
msgid "Are you sure you want to restore the client to default settings?"
|
||||
msgstr "是否真的要恢复客户端默认配置?"
|
||||
|
@ -12,6 +12,7 @@ local chain = arg[5] or "0"
|
||||
local chain_local_port = string.split(chain, "/")[2] or "0"
|
||||
|
||||
local server = ucursor:get_all("shadowsocksr", server_section)
|
||||
local socks_server = ucursor:get_all("shadowsocksr", "@socks5_proxy[0]") or {}
|
||||
local xray_fragment = ucursor:get_all("shadowsocksr", "@global_xray_fragment[0]") or {}
|
||||
local xray_noise = ucursor:get_all("shadowsocksr", "@xray_noise_packets[0]") or {}
|
||||
local outbound_settings = nil
|
||||
@ -28,7 +29,7 @@ function vmess_vless()
|
||||
alterId = (server.v2ray_protocol == "vmess" or not server.v2ray_protocol) and tonumber(server.alter_id) or nil,
|
||||
security = (server.v2ray_protocol == "vmess" or not server.v2ray_protocol) and server.security or nil,
|
||||
encryption = (server.v2ray_protocol == "vless") and server.vless_encryption or nil,
|
||||
flow = ((server.xtls == '1') or (server.tls == '1') or (server.reality == '1')) and server.tls_flow or nil
|
||||
flow = (((server.xtls == '1') or (server.tls == '1') or (server.reality == '1')) and server.tls_flow ~= "none") and server.tls_flow or nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,7 +184,17 @@ if proto:find("tcp") and socks_port ~= "0" then
|
||||
-- socks
|
||||
protocol = "socks",
|
||||
port = tonumber(socks_port),
|
||||
settings = {auth = "noauth", udp = true}
|
||||
settings = {
|
||||
auth = socks_server.socks5_auth,
|
||||
udp = true,
|
||||
mixed = (socks_server.socks5_mixed == '1') and true or false,
|
||||
accounts = (socks_server.socks5_auth ~= "noauth") and {
|
||||
{
|
||||
user = socks_server.socks5_user,
|
||||
pass = socks_server.socks5_pass
|
||||
}
|
||||
} or nil
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
@ -220,26 +231,15 @@ end
|
||||
fingerprint = server.fingerprint,
|
||||
serverName = server.tls_host
|
||||
} or nil,
|
||||
tcpSettings = (server.transport == "tcp" and server.tcp_guise == "http") and {
|
||||
rawSettings = (server.transport == "raw" or server.transport == "tcp") and {
|
||||
-- tcp
|
||||
header = {
|
||||
type = server.tcp_guise,
|
||||
request = {
|
||||
type = server.tcp_guise or "none",
|
||||
request = (server.tcp_guise == "http") and {
|
||||
-- request
|
||||
path = {server.http_path} or {"/"},
|
||||
headers = {Host = {server.http_host} or {}}
|
||||
}
|
||||
}
|
||||
} or nil,
|
||||
rawSettings = (server.transport == "raw" and server.raw_guise == "http") and {
|
||||
-- raw
|
||||
header = {
|
||||
type = server.raw_guise,
|
||||
request = {
|
||||
-- request
|
||||
path = {server.http_path} or {"/"},
|
||||
headers = {Host = {server.http_host} or {}}
|
||||
}
|
||||
} or nil
|
||||
}
|
||||
} or nil,
|
||||
kcpSettings = (server.transport == "kcp") and {
|
||||
@ -256,10 +256,7 @@ end
|
||||
} or nil,
|
||||
wsSettings = (server.transport == "ws") and (server.ws_path or server.ws_host or server.tls_host) and {
|
||||
-- ws
|
||||
headers = (server.ws_host or server.tls_host) and {
|
||||
-- headers
|
||||
Host = server.ws_host or server.tls_host
|
||||
} or nil,
|
||||
Host = server.ws_host or server.tls_host or nil,
|
||||
path = server.ws_path,
|
||||
maxEarlyData = tonumber(server.ws_ed) or nil,
|
||||
earlyDataHeaderName = server.ws_ed_header or nil
|
||||
@ -297,8 +294,9 @@ end
|
||||
initial_windows_size = tonumber(server.initial_windows_size) or nil
|
||||
} or nil,
|
||||
sockopt = {
|
||||
tcpMptcp = (server.mptcp == "1") and true or false, -- MPTCP
|
||||
tcpNoDelay = (server.mptcp == "1") and true or false, -- MPTCP
|
||||
mark = 250,
|
||||
tcpMptcp = (server.mptcp == "1") and true or nil, -- MPTCP
|
||||
tcpNoDelay = (server.mptcp == "1") and true or nil, -- MPTCP
|
||||
tcpcongestion = server.custom_tcpcongestion, -- 连接服务器节点的 TCP 拥塞控制算法
|
||||
dialerProxy = (xray_fragment.fragment == "1" or xray_fragment.noise == "1") and "dialerproxy" or nil
|
||||
}
|
||||
@ -335,8 +333,10 @@ if xray_fragment.fragment ~= "0" or (xray_fragment.noise ~= "0" and xray_noise.e
|
||||
},
|
||||
streamSettings = {
|
||||
sockopt = {
|
||||
tcpMptcp = (server.mptcp == "1") and true or false, -- MPTCP
|
||||
tcpNoDelay = (server.mptcp == "1") and true or false -- MPTCP
|
||||
mark = 250,
|
||||
tcpMptcp = (server.mptcp == "1") and true or nil, -- MPTCP
|
||||
tcpNoDelay = (server.mptcp == "1") and true or nil, -- MPTCP
|
||||
tcpcongestion = server.custom_tcpcongestion -- 连接服务器节点的 TCP 拥塞控制算法
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -172,6 +172,9 @@ local function processData(szType, content)
|
||||
result.v2ray_protocol = 'vmess'
|
||||
result.server = info.add
|
||||
result.server_port = info.port
|
||||
if info.net == "tcp" then
|
||||
info.net = "raw"
|
||||
end
|
||||
result.transport = info.net
|
||||
result.alter_id = info.aid
|
||||
result.vmess_id = info.id
|
||||
@ -194,7 +197,7 @@ local function processData(szType, content)
|
||||
result.h2_host = info.host
|
||||
result.h2_path = info.path
|
||||
end
|
||||
if info.net == 'tcp' then
|
||||
if info.net == 'raw' or info.net == 'tcp' then
|
||||
if info.type and info.type ~= "http" then
|
||||
info.type = "none"
|
||||
end
|
||||
@ -316,6 +319,7 @@ local function processData(szType, content)
|
||||
result.server = nil
|
||||
end
|
||||
elseif szType == "trojan" then
|
||||
local params = {}
|
||||
local idx_sp = 0
|
||||
local alias = ""
|
||||
if content:find("#") then
|
||||
@ -324,20 +328,27 @@ local function processData(szType, content)
|
||||
end
|
||||
local info = content:sub(1, idx_sp - 1)
|
||||
local hostInfo = split(info, "@")
|
||||
local host = split(hostInfo[2], ":")
|
||||
local userinfo = hostInfo[1]
|
||||
local password = userinfo
|
||||
|
||||
-- 分离服务器地址和端口
|
||||
local host = split(hostInfo[2], ":")
|
||||
local server = host[1]
|
||||
local port = host[2]
|
||||
|
||||
result.alias = UrlDecode(alias)
|
||||
result.type = v2_tj
|
||||
result.v2ray_protocol = "trojan"
|
||||
result.server = host[1]
|
||||
result.server = server
|
||||
result.password = password
|
||||
|
||||
-- 按照官方的建议 默认验证ssl证书
|
||||
result.insecure = "0"
|
||||
result.tls = "1"
|
||||
if host[2]:find("?") then
|
||||
local query = split(host[2], "?")
|
||||
|
||||
if port:find("?") then
|
||||
local query = split(port, "?")
|
||||
result.server_port = query[1]
|
||||
local params = {}
|
||||
for _, v in pairs(split(query[2], '&')) do
|
||||
local t = split(v, '=')
|
||||
params[t[1]] = t[2]
|
||||
@ -346,10 +357,62 @@ local function processData(szType, content)
|
||||
-- 未指定peer(sni)默认使用remote addr
|
||||
result.tls_host = params.sni
|
||||
end
|
||||
else
|
||||
result.server_port = host[2]
|
||||
|
||||
if params.allowInsecure then
|
||||
-- 处理 insecure 参数
|
||||
result.insecure = params.allowInsecure
|
||||
end
|
||||
else
|
||||
result.server_port = port
|
||||
end
|
||||
|
||||
if v2_tj ~= "trojan" then
|
||||
if params.fp then
|
||||
-- 处理 fingerprint 参数
|
||||
result.fingerprint = params.fp
|
||||
end
|
||||
-- 处理传输协议
|
||||
result.transport = params.type or "tcp" -- 默认传输协议为 tcp
|
||||
if result.transport == "tcp" then
|
||||
result.transport = "raw"
|
||||
end
|
||||
if result.transport == "ws" then
|
||||
result.ws_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
|
||||
result.ws_path = params.path and UrlDecode(params.path) or "/"
|
||||
elseif result.transport == "httpupgrade" then
|
||||
result.httpupgrade_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
|
||||
result.httpupgrade_path = params.path and UrlDecode(params.path) or "/"
|
||||
elseif result.transport == "splithttp" then
|
||||
result.splithttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil
|
||||
result.splithttp_path = params.path and UrlDecode(params.path) or "/"
|
||||
elseif result.transport == "http" or result.transport == "h2" then
|
||||
result.transport = "h2"
|
||||
result.h2_host = params.host and UrlDecode(params.host) or nil
|
||||
result.h2_path = params.path and UrlDecode(params.path) or nil
|
||||
elseif result.transport == "kcp" then
|
||||
result.kcp_guise = params.headerType or "none"
|
||||
result.seed = params.seed
|
||||
result.mtu = 1350
|
||||
result.tti = 50
|
||||
result.uplink_capacity = 5
|
||||
result.downlink_capacity = 20
|
||||
result.read_buffer_size = 2
|
||||
result.write_buffer_size = 2
|
||||
elseif result.transport == "quic" then
|
||||
result.quic_guise = params.headerType or "none"
|
||||
result.quic_security = params.quicSecurity or "none"
|
||||
result.quic_key = params.key
|
||||
elseif result.transport == "grpc" then
|
||||
result.serviceName = params.serviceName
|
||||
result.grpc_mode = params.mode or "gun"
|
||||
elseif result.transport == "tcp" or result.transport == "raw" then
|
||||
result.tcp_guise = params.headerType and params.headerType ~= "" and params.headerType or "none"
|
||||
if result.tcp_guise == "http" then
|
||||
result.tcp_host = params.host and UrlDecode(params.host) or nil
|
||||
result.tcp_path = params.path and UrlDecode(params.path) or nil
|
||||
end
|
||||
end
|
||||
end
|
||||
result.password = password
|
||||
elseif szType == "vless" then
|
||||
local url = URL.parse("http://" .. content)
|
||||
local params = url.query
|
||||
@ -400,18 +463,12 @@ local function processData(szType, content)
|
||||
elseif result.transport == "grpc" then
|
||||
result.serviceName = params.serviceName
|
||||
result.grpc_mode = params.mode or "gun"
|
||||
elseif result.transport == "tcp" then
|
||||
elseif result.transport == "tcp" or result.transport == "raw" then
|
||||
result.tcp_guise = params.headerType or "none"
|
||||
if result.tcp_guise == "http" then
|
||||
result.tcp_host = params.host and UrlDecode(params.host) or nil
|
||||
result.tcp_path = params.path and UrlDecode(params.path) or nil
|
||||
end
|
||||
elseif result.transport == "raw" then
|
||||
result.raw_guise = params.headerType or "none"
|
||||
if result.raw_guise == "http" then
|
||||
result.tcp_host = params.host and UrlDecode(params.host) or nil
|
||||
result.tcp_path = params.path and UrlDecode(params.path) or nil
|
||||
end
|
||||
end
|
||||
end
|
||||
if not result.alias then
|
||||
|
@ -121,7 +121,7 @@ index b469328..3a0b055 100644
|
||||
bool "Include ShadowsocksR Libev Client"
|
||||
default y
|
||||
diff --git a/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua b/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua
|
||||
index 8ceaba7..f381a54 100644
|
||||
index 0469b87..eb7ccb7 100644
|
||||
--- a/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua
|
||||
+++ b/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua
|
||||
@@ -7,7 +7,7 @@ function index()
|
||||
@ -134,10 +134,10 @@ index 8ceaba7..f381a54 100644
|
||||
page.acl_depends = { "luci-app-ssr-plus" }
|
||||
entry({"admin", "services", "shadowsocksr", "client"}, cbi("shadowsocksr/client"), _("SSR Client"), 10).leaf = true
|
||||
diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua
|
||||
index 9853997..de97787 100644
|
||||
index 88351ff..e5f1593 100644
|
||||
--- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua
|
||||
+++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua
|
||||
@@ -70,45 +70,6 @@ o.default = "https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt"
|
||||
@@ -75,45 +75,6 @@ o.default = "https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt"
|
||||
o.description = translate("Customize Netflix IP Url")
|
||||
o:depends("netflix_enable", "1")
|
||||
|
||||
@ -238,7 +238,7 @@ index 26de9ba..b24183e 100644
|
||||
o = s:option(Value, "chinadns_forward", translate("Domestic DNS Server"))
|
||||
o:value("", translate("Disable ChinaDNS-NG"))
|
||||
diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua
|
||||
index 2a5c5e2..65b916e 100644
|
||||
index c7e84ec..83a88a2 100644
|
||||
--- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua
|
||||
+++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua
|
||||
@@ -92,7 +92,7 @@ if Process_list:find("ssr.server") then
|
||||
@ -338,7 +338,7 @@ index d0b77f1..259cb7f 100644
|
||||
res();
|
||||
});
|
||||
diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm
|
||||
index 0e19670..a7485ac 100644
|
||||
index 2607fb7..62e1b1c 100644
|
||||
--- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm
|
||||
+++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm
|
||||
@@ -69,9 +69,9 @@ function export_ssr_url(btn, urlname, sid) {
|
||||
@ -398,25 +398,25 @@ index 0e19670..a7485ac 100644
|
||||
return false;
|
||||
case "trojan":
|
||||
try {
|
||||
@@ -234,7 +234,7 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = url.searchParams.get("sni");
|
||||
@@ -293,7 +293,7 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
break;
|
||||
}
|
||||
|
||||
- s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
+ s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "vmess":
|
||||
var sstr = b64DecodeUnicode(ssu[1]);
|
||||
@@ -296,7 +296,7 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
}
|
||||
@@ -358,7 +358,7 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].dispatchEvent(event);
|
||||
}
|
||||
- s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
+ s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "vless":
|
||||
try {
|
||||
@@ -388,10 +388,10 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
@@ -463,10 +463,10 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -430,10 +430,10 @@ index 0e19670..a7485ac 100644
|
||||
}
|
||||
}
|
||||
diff --git a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po
|
||||
index 69dae19..7621a51 100644
|
||||
index 9992e81..6d547ca 100644
|
||||
--- a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po
|
||||
+++ b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po
|
||||
@@ -569,27 +569,6 @@ msgstr "使用 DNS2TCP 查询"
|
||||
@@ -577,27 +577,6 @@ msgstr "使用 DNS2TCP 查询"
|
||||
msgid "Use DNS2SOCKS query and cache"
|
||||
msgstr "使用 DNS2SOCKS 查询并缓存"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user