luci-app-ssr-plus: sync upstream

last commit: 1869e7f23f
This commit is contained in:
sbwml 2025-01-06 22:48:58 +08:00
parent c0a2aea1c8
commit 5aa3db5849
14 changed files with 753 additions and 164 deletions

View File

@ -21,11 +21,15 @@ function index()
entry({"admin", "services", "shadowsocksr", "subscribe"}, call("subscribe")) entry({"admin", "services", "shadowsocksr", "subscribe"}, call("subscribe"))
entry({"admin", "services", "shadowsocksr", "checkport"}, call("check_port")) entry({"admin", "services", "shadowsocksr", "checkport"}, call("check_port"))
entry({"admin", "services", "shadowsocksr", "log"}, form("shadowsocksr/log"), _("Log"), 80).leaf = true 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", "run"}, call("act_status"))
entry({"admin", "services", "shadowsocksr", "ping"}, call("act_ping")) entry({"admin", "services", "shadowsocksr", "ping"}, call("act_ping"))
entry({"admin", "services", "shadowsocksr", "reset"}, call("act_reset")) entry({"admin", "services", "shadowsocksr", "reset"}, call("act_reset"))
entry({"admin", "services", "shadowsocksr", "restart"}, call("act_restart")) entry({"admin", "services", "shadowsocksr", "restart"}, call("act_restart"))
entry({"admin", "services", "shadowsocksr", "delete"}, call("act_delete")) entry({"admin", "services", "shadowsocksr", "delete"}, call("act_delete"))
--[[Backup]]
entry({"admin", "services", "shadowsocksr", "backup"}, call("create_backup")).leaf = true
end end
function subscribe() function subscribe()
@ -107,9 +111,9 @@ function check_port()
ret = socket:connect(s.server, s.server_port) ret = socket:connect(s.server, s.server_port)
if tostring(ret) == "true" then if tostring(ret) == "true" then
socket:close() 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 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 end
if iret == 0 then if iret == 0 then
luci.sys.call("ipset del ss_spec_wan_ac " .. s.server) luci.sys.call("ipset del ss_spec_wan_ac " .. s.server)
@ -120,7 +124,7 @@ function check_port()
end end
function act_reset() 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")) luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr"))
end end
@ -133,3 +137,28 @@ function act_delete()
luci.sys.call("/etc/init.d/shadowsocksr restart &") luci.sys.call("/etc/init.d/shadowsocksr restart &")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers")) luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
end 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

View File

@ -1,5 +1,7 @@
local m, s, o
local uci = luci.model.uci.cursor() local uci = luci.model.uci.cursor()
local server_table = {} local server_table = {}
local type_table = {}
local function is_finded(e) local function is_finded(e)
return luci.sys.exec('type -t -p "%s"' % e) ~= "" and true or false return luci.sys.exec('type -t -p "%s"' % e) ~= "" and true or false
end end
@ -10,6 +12,9 @@ uci:foreach("shadowsocksr", "servers", function(s)
elseif s.server and s.server_port then 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} server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port}
end end
if s.type then
type_table[s[".name"]] = s.type
end
end) end)
local key_table = {} local key_table = {}
@ -96,7 +101,7 @@ o.default = "https://raw.githubusercontent.com/neodevpro/neodevhost/master/lite_
o:depends("adblock", "1") o:depends("adblock", "1")
o.description = translate("Support AdGuardHome and DNSMASQ format list") 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.inputstyle = "reload"
o.write = function() o.write = function()
luci.sys.call("/etc/init.d/shadowsocksr reset") 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 = m:section(TypedSection, "socks5_proxy", translate("Global SOCKS5 Proxy Server"))
s.anonymous = true s.anonymous = true
o = s:option(ListValue, "server", translate("Server")) -- Enable/Disable Option
o:value("nil", translate("Disable")) o = s:option(Flag, "enabled", translate("Enable"))
o:value("same", translate("Same as Global Server")) o.default = 0
for _, key in pairs(key_table) do
o:value(key, server_table[key])
end
o.default = "nil"
o.rmempty = false o.rmempty = false
-- Server Selection
o = s:option(ListValue, "server", translate("Server"))
o:value("same", translate("Same as Global Server"))
for _, key in pairs(key_table) do
o:value(key, server_table[key])
end
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 = s:option(Value, "local_port", translate("Local Port"))
o.datatype = "port" o.datatype = "port"
o.default = 1080 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 = 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.default = "tlshello"
o:value("tlshello", "tlshello") o:value("tlshello", "tlshello")
o:value("1-1", "1-1")
o:value("1-2", "1-2") o:value("1-2", "1-2")
o:value("1-3", "1-3") o:value("1-3", "1-3")
o:value("1-5", "1-5") o:value("1-5", "1-5")

View File

@ -618,8 +618,7 @@ o:depends({type = "v2ray", v2ray_protocol = "socks"})
-- 传输协议 -- 传输协议
o = s:option(ListValue, "transport", translate("Transport")) o = s:option(ListValue, "transport", translate("Transport"))
o:value("tcp", "TCP") o:value("raw", "RAW (TCP)")
o:value("raw", "RAW")
o:value("kcp", "mKCP") o:value("kcp", "mKCP")
o:value("ws", "WebSocket") o:value("ws", "WebSocket")
o:value("httpupgrade", "HTTPUpgrade") 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 = "socks"})
o:depends({type = "v2ray", v2ray_protocol = "http"}) o:depends({type = "v2ray", v2ray_protocol = "http"})
-- [[ TCP部分 ]]-- -- [[ RAW部分 ]]--
-- TCP伪装 -- TCP伪装
o = s:option(ListValue, "tcp_guise", translate("Camouflage Type")) 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:depends("transport", "raw")
o:value("none", translate("None")) o:value("none", translate("None"))
o:value("http", "HTTP") o:value("http", "HTTP")
@ -654,13 +645,11 @@ o.rmempty = true
-- HTTP域名 -- HTTP域名
o = s:option(Value, "http_host", translate("HTTP Host")) o = s:option(Value, "http_host", translate("HTTP Host"))
o:depends("tcp_guise", "http") o:depends("tcp_guise", "http")
o:depends("raw_guise", "http")
o.rmempty = true o.rmempty = true
-- HTTP路径 -- HTTP路径
o = s:option(Value, "http_path", translate("HTTP Path")) o = s:option(Value, "http_path", translate("HTTP Path"))
o:depends("tcp_guise", "http") o:depends("tcp_guise", "http")
o:depends("raw_guise", "http")
o.rmempty = true o.rmempty = true
-- [[ WS部分 ]]-- -- [[ WS部分 ]]--
@ -935,17 +924,20 @@ if is_finded("xray") then
-- [[ XTLS ]]-- -- [[ XTLS ]]--
o = s:option(ListValue, "tls_flow", translate("Flow")) o = s:option(ListValue, "tls_flow", translate("Flow"))
for _, v in ipairs(tls_flows) do for _, v in ipairs(tls_flows) do
o:value(v, translate(v)) if v == "none" then
o.default = "none"
o:value("none", translate("none"))
else
o:value(v, translate(v))
end
end end
o.rmempty = true 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 = "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}) o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "raw", reality = true})
-- [[ uTLS ]]-- -- [[ uTLS ]]--
o = s:option(ListValue, "fingerprint", translate("Finger Print")) o = s:option(ListValue, "fingerprint", translate("Finger Print"))
o.default = "chrome" o.default = ""
o:value("chrome", translate("chrome")) o:value("chrome", translate("chrome"))
o:value("firefox", translate("firefox")) o:value("firefox", translate("firefox"))
o:value("safari", translate("safari")) o:value("safari", translate("safari"))
@ -1024,7 +1016,7 @@ o:depends("mux", true)
-- [[ MPTCP ]]-- -- [[ 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.rmempty = false
o.default = false o.default = false
o:depends({type = "v2ray", v2ray_protocol = "vless"}) o:depends({type = "v2ray", v2ray_protocol = "vless"})

View File

@ -1,5 +1,6 @@
require "luci.ip" require "luci.ip"
require "nixio.fs" require "nixio.fs"
require "luci.sys"
local m, s, o local m, s, o
m = Map("shadowsocksr") m = Map("shadowsocksr")
@ -140,4 +141,11 @@ o.remove = function(self, section, value)
nixio.fs.writefile(netflixconf, "") nixio.fs.writefile(netflixconf, "")
end 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 return m

View File

@ -1,20 +1,102 @@
require "luci.util" require "luci.util"
require "nixio.fs" require "nixio.fs"
require "luci.sys"
require "luci.http"
f = SimpleForm("logview") f = SimpleForm("logview")
f.reset = false f.reset = false
f.submit = false f.submit = false
t = f:field(TextValue, "conf") f:append(Template("shadowsocksr/log"))
t.rmempty = true
t.rows = 20 -- 自定义 log 函数
function t.cfgvalue() function log(...)
if nixio.fs.access("/var/log/ssrplus.log") then local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
local logs = luci.util.execi("cat /var/log/ssrplus.log") local f, err = io.open("/var/log/ssrplus.log", "a")
local s = "" if f and err == nil then
for line in logs do f:write(result .. "\n")
s = line .. "\n" .. s f:close()
end end
return s
end
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

View File

@ -29,7 +29,7 @@ o:value("5", translate("Every Friday"))
o:value("6", translate("Every Saturday")) o:value("6", translate("Every Saturday"))
o:value("0", translate("Every Sunday")) o:value("0", translate("Every Sunday"))
o.default = "*" o.default = "*"
o.rmempty = false o.rmempty = true
o:depends("auto_update", "1") o:depends("auto_update", "1")
o = s:option(ListValue, "auto_update_day_time", translate("Update time (every day)")) 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") o:value(t, t .. ":00")
end end
o.default = 2 o.default = 2
o.rmempty = false o.rmempty = true
o:depends("auto_update", "1") o:depends("auto_update", "1")
o = s:option(ListValue, "auto_update_min_time", translate("Update Interval (min)")) 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") o:value(i, i .. ":00")
end end
o.default = 30 o.default = 30
o.rmempty = false o.rmempty = true
o:depends("auto_update", "1") o:depends("auto_update", "1")
o = s:option(DynamicList, "subscribe_url", translate("Subscribe URL")) o = s:option(DynamicList, "subscribe_url", translate("Subscribe URL"))

View File

@ -155,6 +155,14 @@ if nixio.fs.access("/usr/bin/kcptun-client") then
end end
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 = m:field(DummyValue, "google", translate("Google Connectivity"))
s.value = translate("No Check") s.value = translate("No Check")
s.template = "shadowsocksr/check" s.template = "shadowsocksr/check"
@ -181,10 +189,10 @@ if uci:get_first("shadowsocksr", 'global', 'apple_optimization', '0') ~= '0' the
end end
if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') ~= '0' then if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') ~= '0' then
s = m:field(DummyValue, "nfip_data", translate("Netflix IP Data")) s = m:field(DummyValue, "nfip_data", translate("Netflix IP Data"))
s.rawhtml = true s.rawhtml = true
s.template = "shadowsocksr/refresh" s.template = "shadowsocksr/refresh"
s.value = nfip_count .. " " .. translate("Records") s.value = nfip_count .. " " .. translate("Records")
end end
if uci:get_first("shadowsocksr", 'global', 'adblock', '0') == '1' then if uci:get_first("shadowsocksr", 'global', 'adblock', '0') == '1' then

View 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%>

View 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>

View File

@ -217,8 +217,9 @@ function import_ssr_url(btn, urlname, sid) {
case "trojan": case "trojan":
try { try {
var url = new URL("http://" + ssu[1]); var url = new URL("http://" + ssu[1]);
var params = url.searchParams;
} catch(e) { } catch(e) {
alert(e) alert(e);
return false; 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 + '.password')[0].value = decodeURIComponent(url.username);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true; document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); 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>"; s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
return false; 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 + '.server_port')[0].value = ssm.port;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alter_id')[0].value = ssm.aid; 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 + '.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); 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") { if (ssm.type && ssm.type != "http") {
ssm.type = "none" ssm.type = "none";
} else { } else {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = ssm.host; document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = ssm.host;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_path')[0].value = ssm.path; 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')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = ssm.sni || ssm.host; 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].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].dispatchEvent(event); document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].dispatchEvent(event);
}
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>"; s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
return false; return false;
case "vless": case "vless":
@ -303,88 +365,101 @@ function import_ssr_url(btn, urlname, sid) {
var url = new URL("http://" + ssu[1]); var url = new URL("http://" + ssu[1]);
var params = url.searchParams; var params = url.searchParams;
} catch(e) { } catch(e) {
alert(e) alert(e);
return false; return false;
} }
// Check if the elements exist before trying to modify them
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : ""; function setElementValue(name, value) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray"; const element = document.getElementsByName(name)[0];
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event); if (element) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = "vless"; if (element.type === "checkbox" || element.type === "radio") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event); element.checked = value === true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname; } else {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "80"; element.value = value;
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" : function dispatchEventIfExists(name, event) {
(params.get("type") || "tcp")); const element = document.getElementsByName(name)[0];
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].dispatchEvent(event); if (element) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.vless_encryption')[0].value = params.get("encryption") || "none"; 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"))) { if ([ "tls", "xtls", "reality" ].includes(params.get("security"))) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.' + params.get("security"))[0].checked = true; setElementValue('cbid.shadowsocksr.' + sid + '.' + params.get("security"), true);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.' + params.get("security"))[0].dispatchEvent(event); dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.' + params.get("security"), event);
if (params.get("security") === "reality") { if (params.get("security") === "reality") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.reality_publickey')[0].value = params.get("pbk") ? decodeURIComponent(params.get("pbk")) : ""; setElementValue('cbid.shadowsocksr.' + sid + '.reality_publickey', params.get("pbk") ? decodeURIComponent(params.get("pbk")) : "");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.reality_shortid')[0].value = params.get("sid") || ""; setElementValue('cbid.shadowsocksr.' + sid + '.reality_shortid', params.get("sid") || "");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.reality_spiderx')[0].value = params.get("spx") ? decodeURIComponent(params.get("spx")) : ""; setElementValue('cbid.shadowsocksr.' + sid + '.reality_spiderx', params.get("spx") ? decodeURIComponent(params.get("spx")) : "");
} }
if (params.get("security") === "xtls") { setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_flow')[0].value = params.get("flow") || ""; dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_flow')[0].dispatchEvent(event);
} setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.get("fp") || "");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fingerprint')[0].value = params.get("fp") || ""; setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.get("sni") || "");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni") || "";
} }
switch (params.get("type")) { switch (params.get("type")) {
case "ws": case "ws":
if (params.get("security") !== "tls") if (params.get("security") !== "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : ""; setElementValue('cbid.shadowsocksr.' + sid + '.ws_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/"; }
setElementValue('cbid.shadowsocksr.' + sid + '.ws_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
break; break;
case "httpupgrade": case "httpupgrade":
if (params.get("security") !== "tls") if (params.get("security") !== "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : ""; setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/"; }
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
break; break;
case "splithttp": case "splithttp":
if (params.get("security") !== "tls") if (params.get("security") !== "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : ""; setElementValue('cbid.shadowsocksr.' + sid + '.splithttp_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/"; }
setElementValue('cbid.shadowsocksr.' + sid + '.splithttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
break; break;
case "kcp": case "kcp":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none"; setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.get("headerType") || "none");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || ""; setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.get("seed") || "");
break; break;
case "http": case "http":
/* this is non-standard, bullshit */ /* this is non-standard, bullshit */
case "h2": case "h2":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : ""; setElementValue('cbid.shadowsocksr.' + sid + '.h2_host', 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_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
break; break;
case "quic": case "quic":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_guise')[0].value = params.get("headerType") || "none"; setElementValue('cbid.shadowsocksr.' + sid + '.quic_guise', params.get("headerType") || "none");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_security')[0].value = params.get("quicSecurity") || "none"; setElementValue('cbid.shadowsocksr.' + sid + '.quic_security', params.get("quicSecurity") || "none");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_key')[0].value = params.get("key") || ""; setElementValue('cbid.shadowsocksr.' + sid + '.quic_key', params.get("key") || "");
break; break;
case "grpc": case "grpc":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.serviceName')[0].value = params.get("serviceName") || ""; setElementValue('cbid.shadowsocksr.' + sid + '.serviceName', params.get("serviceName") || "");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.grpc_mode')[0].value = params.get("mode") || "gun"; setElementValue('cbid.shadowsocksr.' + sid + '.grpc_mode', params.get("mode") || "gun");
break; break;
case "tcp": 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": case "raw":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.raw_guise')[0].value = params.get("headerType") || "none"; setElementValue('cbid.shadowsocksr.' + sid + '.tcp_guise', params.get("headerType") || "none");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.raw_guise')[0].dispatchEvent(event); dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tcp_guise', event);
if (params.get("headerType") === "http") { if (params.get("headerType") === "http") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : ""; setElementValue('cbid.shadowsocksr.' + sid + '.http_host', 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_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
} }
break; break;
} }

View File

@ -91,6 +91,8 @@ msgstr "TLS 主机名"
msgid "allowInsecure" msgid "allowInsecure"
msgstr "允许不安全连接" msgstr "允许不安全连接"
msgid "Enabling MPTCP Requires Server Support."
msgstr "启用 MPTCP 需服务端支持。"
msgid "concurrency" msgid "concurrency"
msgstr "TCP 最大并发连接数" msgstr "TCP 最大并发连接数"
@ -377,6 +379,12 @@ msgstr "强制走代理"
msgid "UDP Relay" msgid "UDP Relay"
msgstr "UDP 中继" msgstr "UDP 中继"
msgid "Restart ShadowSocksR Plus+"
msgstr "重启 ShadowSocksR Plus+"
msgid "Restart Service"
msgstr "重启服务"
msgid "Google Connectivity" msgid "Google Connectivity"
msgstr "【谷歌】连通性检查" msgstr "【谷歌】连通性检查"
@ -863,6 +871,30 @@ msgstr "本机服务端"
msgid "Global SOCKS5 Proxy Server" msgid "Global SOCKS5 Proxy Server"
msgstr "SOCKS5 代理服务端(全局)" 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" msgid "Xray Fragment Settings"
msgstr "Xray 分片设置" msgstr "Xray 分片设置"
@ -1147,3 +1179,45 @@ msgstr "socks5 服务器可以从外部接收的最大数据包大小(单位
msgid "Disable ChinaDNS-NG" msgid "Disable ChinaDNS-NG"
msgstr "直通模式(禁用 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 "是否真的要恢复客户端默认配置?"

View File

@ -12,6 +12,7 @@ local chain = arg[5] or "0"
local chain_local_port = string.split(chain, "/")[2] or "0" local chain_local_port = string.split(chain, "/")[2] or "0"
local server = ucursor:get_all("shadowsocksr", server_section) 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_fragment = ucursor:get_all("shadowsocksr", "@global_xray_fragment[0]") or {}
local xray_noise = ucursor:get_all("shadowsocksr", "@xray_noise_packets[0]") or {} local xray_noise = ucursor:get_all("shadowsocksr", "@xray_noise_packets[0]") or {}
local outbound_settings = nil 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, 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, 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, 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
} }
} }
} }
@ -180,10 +181,20 @@ end
-- 检查是否启用 socks 代理 -- 检查是否启用 socks 代理
if proto:find("tcp") and socks_port ~= "0" then if proto:find("tcp") and socks_port ~= "0" then
table.insert(Xray.inbounds, { table.insert(Xray.inbounds, {
-- socks -- socks
protocol = "socks", protocol = "socks",
port = tonumber(socks_port), 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 end
@ -220,26 +231,15 @@ end
fingerprint = server.fingerprint, fingerprint = server.fingerprint,
serverName = server.tls_host serverName = server.tls_host
} or nil, } or nil,
tcpSettings = (server.transport == "tcp" and server.tcp_guise == "http") and { rawSettings = (server.transport == "raw" or server.transport == "tcp") and {
-- tcp -- tcp
header = { header = {
type = server.tcp_guise, type = server.tcp_guise or "none",
request = { request = (server.tcp_guise == "http") and {
-- request -- request
path = {server.http_path} or {"/"}, path = {server.http_path} or {"/"},
headers = {Host = {server.http_host} or {}} headers = {Host = {server.http_host} or {}}
} } or nil
}
} 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 { kcpSettings = (server.transport == "kcp") and {
@ -256,10 +256,7 @@ end
} or nil, } or nil,
wsSettings = (server.transport == "ws") and (server.ws_path or server.ws_host or server.tls_host) and { wsSettings = (server.transport == "ws") and (server.ws_path or server.ws_host or server.tls_host) and {
-- ws -- ws
headers = (server.ws_host or server.tls_host) and { Host = server.ws_host or server.tls_host or nil,
-- headers
Host = server.ws_host or server.tls_host
} or nil,
path = server.ws_path, path = server.ws_path,
maxEarlyData = tonumber(server.ws_ed) or nil, maxEarlyData = tonumber(server.ws_ed) or nil,
earlyDataHeaderName = server.ws_ed_header or nil earlyDataHeaderName = server.ws_ed_header or nil
@ -297,8 +294,9 @@ end
initial_windows_size = tonumber(server.initial_windows_size) or nil initial_windows_size = tonumber(server.initial_windows_size) or nil
} or nil, } or nil,
sockopt = { sockopt = {
tcpMptcp = (server.mptcp == "1") and true or false, -- MPTCP mark = 250,
tcpNoDelay = (server.mptcp == "1") and true or false, -- MPTCP tcpMptcp = (server.mptcp == "1") and true or nil, -- MPTCP
tcpNoDelay = (server.mptcp == "1") and true or nil, -- MPTCP
tcpcongestion = server.custom_tcpcongestion, -- 连接服务器节点的 TCP 拥塞控制算法 tcpcongestion = server.custom_tcpcongestion, -- 连接服务器节点的 TCP 拥塞控制算法
dialerProxy = (xray_fragment.fragment == "1" or xray_fragment.noise == "1") and "dialerproxy" or nil 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 = { streamSettings = {
sockopt = { sockopt = {
tcpMptcp = (server.mptcp == "1") and true or false, -- MPTCP mark = 250,
tcpNoDelay = (server.mptcp == "1") and true or false -- MPTCP tcpMptcp = (server.mptcp == "1") and true or nil, -- MPTCP
tcpNoDelay = (server.mptcp == "1") and true or nil, -- MPTCP
tcpcongestion = server.custom_tcpcongestion -- 连接服务器节点的 TCP 拥塞控制算法
} }
} }
}) })

View File

@ -172,6 +172,9 @@ local function processData(szType, content)
result.v2ray_protocol = 'vmess' result.v2ray_protocol = 'vmess'
result.server = info.add result.server = info.add
result.server_port = info.port result.server_port = info.port
if info.net == "tcp" then
info.net = "raw"
end
result.transport = info.net result.transport = info.net
result.alter_id = info.aid result.alter_id = info.aid
result.vmess_id = info.id result.vmess_id = info.id
@ -194,7 +197,7 @@ local function processData(szType, content)
result.h2_host = info.host result.h2_host = info.host
result.h2_path = info.path result.h2_path = info.path
end end
if info.net == 'tcp' then if info.net == 'raw' or info.net == 'tcp' then
if info.type and info.type ~= "http" then if info.type and info.type ~= "http" then
info.type = "none" info.type = "none"
end end
@ -316,6 +319,7 @@ local function processData(szType, content)
result.server = nil result.server = nil
end end
elseif szType == "trojan" then elseif szType == "trojan" then
local params = {}
local idx_sp = 0 local idx_sp = 0
local alias = "" local alias = ""
if content:find("#") then if content:find("#") then
@ -324,20 +328,27 @@ local function processData(szType, content)
end end
local info = content:sub(1, idx_sp - 1) local info = content:sub(1, idx_sp - 1)
local hostInfo = split(info, "@") local hostInfo = split(info, "@")
local host = split(hostInfo[2], ":")
local userinfo = hostInfo[1] local userinfo = hostInfo[1]
local password = userinfo local password = userinfo
-- 分离服务器地址和端口
local host = split(hostInfo[2], ":")
local server = host[1]
local port = host[2]
result.alias = UrlDecode(alias) result.alias = UrlDecode(alias)
result.type = v2_tj result.type = v2_tj
result.v2ray_protocol = "trojan" result.v2ray_protocol = "trojan"
result.server = host[1] result.server = server
result.password = password
-- 按照官方的建议 默认验证ssl证书 -- 按照官方的建议 默认验证ssl证书
result.insecure = "0" result.insecure = "0"
result.tls = "1" 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] result.server_port = query[1]
local params = {}
for _, v in pairs(split(query[2], '&')) do for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=') local t = split(v, '=')
params[t[1]] = t[2] params[t[1]] = t[2]
@ -346,10 +357,62 @@ local function processData(szType, content)
-- 未指定peersni默认使用remote addr -- 未指定peersni默认使用remote addr
result.tls_host = params.sni result.tls_host = params.sni
end end
if params.allowInsecure then
-- 处理 insecure 参数
result.insecure = params.allowInsecure
end
else else
result.server_port = host[2] 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 end
result.password = password
elseif szType == "vless" then elseif szType == "vless" then
local url = URL.parse("http://" .. content) local url = URL.parse("http://" .. content)
local params = url.query local params = url.query
@ -400,18 +463,12 @@ local function processData(szType, content)
elseif result.transport == "grpc" then elseif result.transport == "grpc" then
result.serviceName = params.serviceName result.serviceName = params.serviceName
result.grpc_mode = params.mode or "gun" 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" result.tcp_guise = params.headerType or "none"
if result.tcp_guise == "http" then if result.tcp_guise == "http" then
result.tcp_host = params.host and UrlDecode(params.host) or nil result.tcp_host = params.host and UrlDecode(params.host) or nil
result.tcp_path = params.path and UrlDecode(params.path) or nil result.tcp_path = params.path and UrlDecode(params.path) or nil
end 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
end end
if not result.alias then if not result.alias then

View File

@ -121,7 +121,7 @@ index b469328..3a0b055 100644
bool "Include ShadowsocksR Libev Client" bool "Include ShadowsocksR Libev Client"
default y default y
diff --git a/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua b/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua 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 --- a/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua
+++ b/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua +++ b/luci-app-ssr-plus/luasrc/controller/shadowsocksr.lua
@@ -7,7 +7,7 @@ function index() @@ -7,7 +7,7 @@ function index()
@ -134,10 +134,10 @@ index 8ceaba7..f381a54 100644
page.acl_depends = { "luci-app-ssr-plus" } page.acl_depends = { "luci-app-ssr-plus" }
entry({"admin", "services", "shadowsocksr", "client"}, cbi("shadowsocksr/client"), _("SSR Client"), 10).leaf = true 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 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 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua
+++ b/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.description = translate("Customize Netflix IP Url")
o:depends("netflix_enable", "1") o:depends("netflix_enable", "1")
@ -238,7 +238,7 @@ index 26de9ba..b24183e 100644
o = s:option(Value, "chinadns_forward", translate("Domestic DNS Server")) o = s:option(Value, "chinadns_forward", translate("Domestic DNS Server"))
o:value("", translate("Disable ChinaDNS-NG")) 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 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 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua
+++ b/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 @@ -92,7 +92,7 @@ if Process_list:find("ssr.server") then
@ -338,7 +338,7 @@ index d0b77f1..259cb7f 100644
res(); res();
}); });
diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm 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 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm
+++ b/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) { @@ -69,9 +69,9 @@ function export_ssr_url(btn, urlname, sid) {
@ -398,25 +398,25 @@ index 0e19670..a7485ac 100644
return false; return false;
case "trojan": case "trojan":
try { try {
@@ -234,7 +234,7 @@ function import_ssr_url(btn, urlname, sid) { @@ -293,7 +293,7 @@ function import_ssr_url(btn, urlname, sid) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); break;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = url.searchParams.get("sni"); }
- s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>"; - s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
+ s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>"; + s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
return false; return false;
case "vmess": case "vmess":
var sstr = b64DecodeUnicode(ssu[1]); 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].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].dispatchEvent(event); document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].dispatchEvent(event);
}
- s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>"; - s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
+ s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>"; + s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
return false; return false;
case "vless": case "vless":
try { try {
@@ -388,10 +388,10 @@ function import_ssr_url(btn, urlname, sid) { @@ -463,10 +463,10 @@ function import_ssr_url(btn, urlname, sid) {
} }
break; 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 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 --- a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po
+++ b/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" msgid "Use DNS2SOCKS query and cache"
msgstr "使用 DNS2SOCKS 查询并缓存" msgstr "使用 DNS2SOCKS 查询并缓存"