Sync 2026-08-13 21:00:03

This commit is contained in:
github-actions[bot]
2026-08-13 21:00:03 +08:00
parent c57dcedc98
commit 29133c66a8
48 changed files with 1339 additions and 1967 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.8.12
PKG_RELEASE:=215
PKG_RELEASE:=216
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -20,19 +20,31 @@ function is_timehhmm(str) {
}
if (typeof cbi_validators !== 'undefined' && cbi_validators !== null) {
cbi_validators['base64'] = function() {
return isBase64(this);
}
cbi_validators['json'] = function() {
return is_json(this);
}
cbi_validators['timehhmm'] = function() {
return is_timehhmm(this);
}
cbi_validators['uuid'] = function() {
return isUUID(this);
}
} else {
L.require('validation').then(function(validation) {
validation.types['base64'] = function() {
return this.assert(isBase64(this.value), _('Must be Base64 text!'));
}
validation.types['json'] = function() {
return this.assert(is_json(this.value), _('Must be JSON text!'));
}
validation.types['timehhmm'] = function() {
return this.assert(is_timehhmm(this.value), _('valid time (hh:mm)'));
}
validation.types['uuid'] = function() {
return this.assert(isUUID(this.value), _('Must be UUID format!'));
}
});
}
@@ -8,6 +8,11 @@ function arraysEqual(a, b) {
return true;
}
function isBase64(str) {
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/;
return base64Regex.test(str);
}
function decodeIfBase64(str) {
try {
let s = str.replace(/-/g, '+').replace(/_/g, '/');
@@ -24,6 +29,11 @@ function decodeIfBase64(str) {
return str;
}
function isUUID(str) {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(str);
}
function waitForElement(selector, callback) {
const el = document.querySelector(selector);
if (el) return callback(el);
@@ -57,12 +57,13 @@ function index()
--[[ Server ]]
entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true
entry({"admin", "services", appname, "server_user"}, cbi(appname .. "/server/user")).leaf = true
entry({"admin", "services", appname, "server_config"}, cbi(appname .. "/server/server_config")).leaf = true
entry({"admin", "services", appname, "server_user_config"}, cbi(appname .. "/server/user_config")).leaf = true
--[[ API ]]
entry({"admin", "services", appname, "server_update_config"}, call("server_update_config")).leaf = true
entry({"admin", "services", appname, "server_user_status"}, call("server_user_status")).leaf = true
entry({"admin", "services", appname, "server_user_log"}, call("server_user_log")).leaf = true
entry({"admin", "services", appname, "server_status"}, call("server_status")).leaf = true
entry({"admin", "services", appname, "server_log"}, call("server_log")).leaf = true
entry({"admin", "services", appname, "server_get_log"}, call("server_get_log")).leaf = true
entry({"admin", "services", appname, "server_clear_log"}, call("server_clear_log")).leaf = true
entry({"admin", "services", appname, "link_add_node"}, call("link_add_node")).leaf = true
@@ -89,7 +90,6 @@ function index()
entry({"admin", "services", appname, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true
entry({"admin", "services", appname, "reassign_group"}, call("reassign_group")).leaf = true
entry({"admin", "services", appname, "get_node"}, call("get_node")).leaf = true
entry({"admin", "services", appname, "save_node_order"}, call("save_node_order")).leaf = true
entry({"admin", "services", appname, "save_node_list_opt"}, call("save_node_list_opt")).leaf = true
entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true
entry({"admin", "services", appname, "rollback_rules"}, call("rollback_rules")).leaf = true
@@ -101,7 +101,6 @@ function index()
entry({"admin", "services", appname, "get_shunt_rules"}, call("get_shunt_rules")).leaf = true
entry({"admin", "services", appname, "add_shunt_rule"}, call("add_shunt_rule")).leaf = true
entry({"admin", "services", appname, "delete_select_shunt_rules"}, call("delete_select_shunt_rules")).leaf = true
entry({"admin", "services", appname, "save_shunt_rule_order"}, call("save_shunt_rule_order")).leaf = true
--[[rule_list]]
entry({"admin", "services", appname, "read_rulelist"}, call("read_rulelist")).leaf = true
@@ -797,19 +796,6 @@ function get_node()
http_write_json(result)
end
function save_node_order()
local ids = http.formvalue("ids") or ""
local new_order = {}
for id in ids:gmatch("([^,]+)") do
new_order[#new_order + 1] = id
end
for idx, name in ipairs(new_order) do
luci.sys.call(string.format("uci -q reorder %s.%s=%d", appname, name, idx - 1))
end
api.sh_uci_commit(appname)
http_write_json({ status = "ok" })
end
function reassign_group()
local ids = http.formvalue("ids") or ""
local group = http.formvalue("group") or "default"
@@ -878,14 +864,14 @@ function server_update_config()
http_write_json_error()
end
function server_user_status()
function server_status()
local e = {}
e.index = http.formvalue("index")
e.status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep -i '%s' >/dev/null", appname .. "_server", http.formvalue("id"))) == 0
http_write_json(e)
end
function server_user_log()
function server_log()
local id = http.formvalue("id")
if fs.access("/tmp/etc/passwall_server/" .. id .. ".log") then
local content = luci.sys.exec("cat /tmp/etc/passwall_server/" .. id .. ".log")
@@ -1286,16 +1272,3 @@ function delete_select_shunt_rules()
api.uci_save(uci, appname, true, true)
end
end
function save_shunt_rule_order()
local ids = http.formvalue("ids") or ""
local new_order = {}
for id in ids:gmatch("([^,]+)") do
new_order[#new_order + 1] = id
end
for idx, name in ipairs(new_order) do
luci.sys.call(string.format("uci -q reorder %s.%s=%d", appname, name, idx - 1))
end
api.sh_uci_commit(appname)
http_write_json({ status = "ok" })
end
@@ -1,7 +1,5 @@
local api = require "luci.passwall.api"
local appname = "passwall"
local sys = api.sys
local datatypes = api.datatypes
api.set_default_cbi()
@@ -30,6 +28,29 @@ o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
o.default = o.keylist[3]
m:append(Template(appname .. "/node_list/node_list"))
if true then
local o = Template(appname .. "/node_list/node_list")
o.map = m
o.api = api
m:append(o)
if luci.http.formvalue("cbi.submit") == "1" then
local group_order = {}
group_order = luci.http.formvaluetable("group.order")
if group_order then
for k, v in pairs(group_order) do
if v and v~= "" then
local new_order = {}
string.gsub(v, "[^" .. " " .. "]+", function(w)
new_order[#new_order + 1] = w
end)
for idx, name in ipairs(new_order) do
m.uci:reorder(appname, name, idx - 1)
end
end
end
end
end
end
return api.return_map(m)
@@ -146,7 +146,26 @@ end
s:append(Template(appname .. "/rule/rule_version"))
if has_xray or has_singbox then
m:append(Template(appname .. "/rule/shunt_rule_list"))
local o = Template(appname .. "/rule/shunt_rule_list")
o.api = api
m:append(o)
if luci.http.formvalue("cbi.submit") == "1" then
local group_order = luci.http.formvaluetable("group.order")
if group_order then
for k, v in pairs(group_order) do
if v and v~= "" then
local new_order = {}
string.gsub(v, "[^" .. " " .. "]+", function(w)
new_order[#new_order + 1] = w
end)
for idx, name in ipairs(new_order) do
m.uci:reorder(appname, name, idx - 1)
end
end
end
end
end
end
return api.return_map(m)
@@ -18,38 +18,39 @@ e.default = "90"
e.datatype = "range(0,600)"
e.rmempty = false
local cfgname = "user"
t = m:section(TypedSection, cfgname, translate("Users Manager"))
t.anonymous = true
t.addremove = true
t.sortable = true
t.template = "cbi/tblsection"
t.extedit = api.url("server_user", "%s")
function t.create(e, t)
s_server = m:section(TypedSection, "server", translate("Servers Manager"))
s_server.anonymous = true
s_server.addremove = true
s_server.sortable = true
s_server.template = "cbi/tblsection"
s_server.extedit = api.url("server_config", "%s")
function s_server.create(self, section)
local uid = api.gen_random_char()
TypedSection.create(e, uid)
luci.http.redirect(e.extedit:format(uid))
TypedSection.create(self, uid)
luci.http.redirect(self.extedit:format(uid))
end
function t.remove(e, t)
e.map.proceed = true
e.map:del(t)
luci.http.redirect(api.url("server"))
function s_server.remove(self, section)
local o = m:get(section) or {}
if o[".type"] == self.sectiontype then
m:del(section)
end
end
e = t:option(Flag, "enable", translate("Enable"))
e = s_server:option(Flag, "enable", translate("Enable"))
e.width = "5%"
e.rmempty = false
e = t:option(DummyValue, "status", translate("Status"))
e = s_server:option(DummyValue, "status", translate("Status"))
e.rawhtml = true
e.cfgvalue = function(t, n)
return string.format('<font class="_users_status">%s</font>', translate("Collecting data..."))
return string.format('<font class="_servers_status">%s</font>', translate("Collecting data..."))
end
e = t:option(DummyValue, "remarks", translate("Remarks"))
e = s_server:option(DummyValue, "remarks", translate("Remarks"))
e.width = "15%"
e = t:option(DummyValue, "type", translate("Type"))
e = s_server:option(DummyValue, "type", translate("Type"))
e.width = "20%"
e.rawhtml = true
e.cfgvalue = function(t, n)
@@ -87,20 +88,74 @@ e.cfgvalue = function(t, n)
return str
end
e = t:option(DummyValue, "port", translate("Port"))
e = s_server:option(DummyValue, "port", translate("Port"))
e = t:option(Flag, "log", translate("Log"))
e = s_server:option(Flag, "log", translate("Log"))
e.default = "1"
e.rmempty = false
local sortable = Template(appname .. "/cbi/sortable")
sortable.api = api
sortable.appname = m.config
sortable.target_cfgname = cfgname
sortable.target_cfgname = s_server.sectiontype
m:append(sortable)
local server_list_status = Template(appname .. "/server/server_list_status")
server_list_status.api = api
server_list_status.appname = m.config
server_list_status.sectiontype = s_server.sectiontype
m:append(server_list_status)
s_user = m:section(TypedSection, "user", translate("Users Manager"))
s_user.anonymous = true
s_user.addremove = true
s_user.sortable = true
s_user.template = "cbi/tblsection"
s_user.extedit = api.url("server_user_config", "%s")
s_user.create = function(e, section)
local uid = api.gen_random_char()
TypedSection.create(e, uid)
luci.http.redirect(e.extedit:format(uid))
end
s_user.remove = function(self, section)
local o = m:get(section) or {}
if o[".type"] == self.sectiontype then
m.uci:foreach(m.config, "server", function(o)
if o.user and section == o.user then
m:set(o[".name"], "user", "")
end
local changed = false
local users = o.users or {}
for i = #users, 1, -1 do
if section == users[i] then
table.remove(users, i)
changed = true
end
end
if changed then
m:set(o[".name"], "users", users)
end
end)
m:del(section)
luci.http.redirect(api.url("server"))
end
end
e = s_user:option(DummyValue, "username", translate("Username"))
e.width = "25%"
e = s_user:option(DummyValue, "password", translate("Password"))
e.width = "25%"
e = s_user:option(DummyValue, "uuid", "UUID")
e.width = "25%"
local sortable = Template(appname .. "/cbi/sortable")
sortable.api = api
sortable.appname = m.config
sortable.target_cfgname = s_user.sectiontype
m:append(sortable)
m:append(Template(appname .. "/server/log"))
m:append(Template(appname .. "/server/users_list_status"))
return api.return_map(m)
@@ -20,7 +20,12 @@ m:append(header)
m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
s = m:section(NamedSection, arg[1], "user", "")
user_list = {}
m.uci:foreach(m.config, "user", function(s)
user_list[#user_list + 1] = s
end)
s = m:section(NamedSection, arg[1], "server", "")
s.addremove = false
s.dynamic = false
@@ -28,6 +28,12 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(DynamicList, _n("users"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("realms"), translate("Realms"))
o.default = "0"
o.rewrite_option = o.option
@@ -48,11 +54,6 @@ o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio
o.rewrite_option = o.option
o:depends({ [_n("realms")] = "1" })
o = s:option(Value, _n("auth_password"), translate("Auth Password"))
o.password = true
o.rewrite_option = o.option
o:depends({ [_n("custom")] = false })
o = s:option(ListValue, _n("obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
@@ -48,27 +48,18 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("auth"), translate("Auth"))
o.validate = function(self, value, t)
if value and value == "1" then
local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or ""
local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or ""
if user_v == "" or pass_v == "" then
return nil, translate("Username and Password must be used together!")
end
end
return value
o = s:option(DynamicList, _n("users"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "http" })
o = s:option(Value, _n("username"), translate("Username"))
o:depends({ [_n("auth")] = true })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o:depends({ [_n("auth")] = true })
o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "trojan" })
o:depends({ [_n("protocol")] = "hysteria2" })
o = s:option(ListValue, _n("d_protocol"), translate("Destination protocol"))
o:value("tcp", "TCP")
@@ -97,6 +88,9 @@ o.rewrite_option = "method"
for a, t in ipairs(x_ss_method_list) do o:value(t) end
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(Value, _n("ss_password"), translate("Password"))
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(ListValue, _n("ss_network"), translate("Transport"))
o.default = "tcp,udp"
o:value("tcp", "TCP")
@@ -109,14 +103,6 @@ o.default = "1"
o.rmempty = false
o:depends({ [_n("protocol")] = "socks" })
o = s:option(DynamicList, _n("uuid"), translate("ID") .. "/" .. translate("Password"))
for i = 1, 3 do
o:value(api.gen_uuid())
end
o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "trojan" })
o = s:option(ListValue, _n("flow"), translate("flow"))
o.default = ""
o:value("", translate("Disable"))
@@ -141,10 +127,6 @@ o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o:depends({ [_n("hysteria2_realms")] = "1" })
o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
@@ -60,35 +60,27 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("auth"), translate("Auth"))
o.validate = function(self, value, t)
if value and value == "1" then
local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or ""
local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or ""
if user_v == "" or pass_v == "" then
return nil, translate("Username and Password must be used together!")
end
end
return value
o = s:option(DynamicList, _n("users"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("protocol")] = "mixed" })
o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "http" })
o = s:option(Value, _n("username"), translate("Username"))
o:depends({ [_n("auth")] = true })
o:depends({ [_n("protocol")] = "naive" })
o:depends({ [_n("protocol")] = "anytls" })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o:depends({ [_n("auth")] = true })
o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "trojan" })
o:depends({ [_n("protocol")] = "naive" })
o:depends({ [_n("protocol")] = "hysteria" })
o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "hysteria2" })
o:depends({ [_n("protocol")] = "anytls" })
if singbox_tags:find("with_quic") then
o = s:option(Value, _n("hysteria_obfs"), translate("Obfs Password"))
o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(Value, _n("hysteria_up_mbps"), translate("Max upload Mbps"))
o.default = "100"
o:depends({ [_n("protocol")] = "hysteria" })
@@ -97,20 +89,6 @@ if singbox_tags:find("with_quic") then
o.default = "100"
o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(Value, _n("hysteria_obfs"), translate("Obfs Password"))
o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(ListValue, _n("hysteria_auth_type"), translate("Auth Type"))
o:value("disable", translate("Disable"))
o:value("string", translate("STRING"))
o:value("base64", translate("BASE64"))
o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(Value, _n("hysteria_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "string"})
o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "base64"})
o = s:option(Value, _n("hysteria_recv_window_conn"), translate("QUIC stream receive window"))
o:depends({ [_n("protocol")] = "hysteria" })
@@ -174,10 +152,6 @@ if singbox_tags:find("with_quic") then
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o:depends({ [_n("hysteria2_realms")] = "1" })
o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
@@ -233,14 +207,8 @@ o.rewrite_option = "method"
for a, t in ipairs(ss_method_list) do o:value(t) end
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(DynamicList, _n("uuid"), translate("ID") .. "/" .. translate("Password"))
for i = 1, 3 do
o:value(api.gen_uuid())
end
o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "trojan" })
o:depends({ [_n("protocol")] = "tuic" })
o = s:option(Value, _n("ss_password"), translate("Password"))
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(ListValue, _n("flow"), translate("flow"))
o.default = ""
@@ -27,22 +27,11 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o = s:option(Flag, _n("auth"), translate("Auth"))
o.validate = function(self, value, t)
if value and value == "1" then
local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or ""
local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or ""
if user_v == "" or pass_v == "" then
return nil, translate("Username and Password must be used together!")
end
end
return value
o = s:option(ListValue, _n("user"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o = s:option(Value, _n("username"), translate("Username"))
o:depends({ [_n("auth")] = true })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o:depends({ [_n("auth")] = true })
o = s:option(Flag, _n("log"), translate("Log"))
@@ -32,8 +32,10 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o = s:option(ListValue, _n("user"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("custom")] = false })
o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
@@ -45,8 +45,10 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o = s:option(ListValue, _n("user"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("custom")] = false })
o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
@@ -0,0 +1,31 @@
api = require "luci.passwall.api"
appname = api.appname
fs = api.fs
api.set_default_cbi()
m = Map("passwall_server", translate("User Config"))
m.redirect = api.url("server")
api.set_apply_on_parse(m)
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
s = m:section(NamedSection, arg[1], "user", "")
s.addremove = false
s.dynamic = false
o = s:option(Value, "username", translate("Username"))
o.datatype = "and(uciname,maxlength(24))"
o.rmempty = false
o = s:option(Value, "password", translate("Password"))
o.rmempty = false
o = s:option(Value, "uuid", "UUID")
o.datatype = "uuid"
o.default = api.gen_uuid()
o.rmempty = false
return api.return_map(m)
@@ -369,6 +369,12 @@ function is_timehhmm(str)
end
datatypes.timehhmm = is_timehhmm
function is_uuid(str)
local pattern = "^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$"
return string.match(str, pattern) ~= nil
end
datatypes.uuid = is_uuid
function is_normal_node(e)
if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
return false
@@ -102,29 +102,33 @@ local function start()
nft_file:write('flush chain inet fw4 PSW-SERVER\n')
nft_file:write('insert rule inet fw4 input position 0 jump PSW-SERVER comment "PSW-SERVER"\n')
end
uci:foreach(CONFIG, "user", function(user)
local id = user[".name"]
local enable = user.enable
uci:foreach(CONFIG, "server", function(server)
local id = server[".name"]
local enable = server.enable
if enable and tonumber(enable) == 1 then
local enable_log = user.log
local enable_log = server.log
local log_path = nil
if enable_log and enable_log == "1" then
log_path = CONFIG_PATH .. "/" .. id .. ".log"
else
log_path = nil
end
local remarks = user.remarks
local port = tonumber(user.port)
local remarks = server.remarks
local port = tonumber(server.port)
local bin
local config = {}
local config_file = CONFIG_PATH .. "/" .. id .. ".json"
local udp_forward = 1
local type = user.type or ""
local type = server.type or ""
if type == "Socks" then
local auth = ""
if user.auth and user.auth == "1" then
local username = user.username or ""
local password = user.password or ""
if server.auth and server.auth == "1" then
local user = nil
if server.user then
user = uci:get_all("passwall_server", server.user)
end
local username = user and user.username or ""
local password = user and user.password or ""
if username ~= "" and password ~= "" then
username = "-u " .. username
password = "-P " .. password
@@ -133,59 +137,59 @@ local function start()
end
bin = ln_run("/usr/bin/microsocks", "microsocks_" .. id, string.format("-i :: -p %s %s", port, auth), log_path)
elseif type == "SSR" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
else
config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
config = require(require_dir .. "util_shadowsocks").gen_config_server(server)
end
local udp_param = ""
udp_forward = tonumber(user.udp_forward) or 1
udp_forward = tonumber(server.udp_forward) or 1
if udp_forward == 1 then
udp_param = "-u"
end
type = type:lower()
bin = ln_run("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param, log_path)
elseif type == "SS-Rust" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
else
config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
config = require(require_dir .. "util_shadowsocks").gen_config_server(server)
end
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
elseif type == "Xray" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
if log_path then
if not config.log then
config.log = {}
end
config.log.loglevel = user.loglevel
config.log.loglevel = server.loglevel
end
else
config = require(require_dir .. "util_xray").gen_config_server(user)
config = require(require_dir .. "util_xray").gen_config_server(server)
end
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
elseif type == "sing-box" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
if log_path then
if not config.log then
config.log = {}
end
config.log.timestamp = true
config.log.disabled = false
config.log.level = user.loglevel
config.log.level = server.loglevel
config.log.output = log_path
end
else
config = require(require_dir .. "util_sing-box").gen_config_server(user)
config = require(require_dir .. "util_sing-box").gen_config_server(server)
end
bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path)
elseif type == "Hysteria2" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
else
config = require(require_dir .. "util_hysteria2").gen_config_server(user)
config = require(require_dir .. "util_hysteria2").gen_config_server(server)
end
bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path)
end
@@ -203,7 +207,7 @@ local function start()
cmd(bin)
end
local bind_local = user.bind_local or 0
local bind_local = server.bind_local or 0
if bind_local and tonumber(bind_local) ~= 1 and port then
if nft_flag == "0" then
ipt(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
@@ -4,6 +4,20 @@ local uci = api.uci
local jsonc = api.jsonc
function gen_config_server(node)
local users = node.users or {}
local users = nil
if node.users and #node.users > 0 then
users = {}
for i, v in ipairs(node.users) do
local user = uci:get_all("passwall2_server", v) or {}
if user[".type"] == "user" then
users[user.username] = user.password
end
end
if not next(users) then
users = nil
end
end
local config = {
listen = (function()
if node.hysteria2_realms and node.hysteria2_realm_url then
@@ -25,10 +39,10 @@ function gen_config_server(node)
password = node.hysteria2_obfs_password
}
} or nil,
auth = {
type = "password",
password = node.hysteria2_auth_password
},
auth = users and {
type = "userpass",
userpass = users
} or nil,
bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and {
up = node.hysteria2_up_mbps and node.hysteria2_up_mbps .. " mbps" or nil,
down = node.hysteria2_down_mbps and node.hysteria2_down_mbps .. " mbps" or nil
@@ -4,9 +4,13 @@ local uci = api.uci
local jsonc = api.jsonc
function gen_config_server(node)
local user = nil
if node.user then
user = uci:get_all("passwall2_server", node.user)
end
local config = {}
config.server_port = tonumber(node.port)
config.password = node.password
config.password = user and user.password or ""
config.timeout = tonumber(node.timeout)
config.fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
config.method = node.method
@@ -766,39 +766,71 @@ function gen_config_server(node)
listen_port = tonumber(node.port),
}
local users = node.users or {}
local users = nil
if node.users and #node.users > 0 then
users = {}
for i, v in ipairs(node.users) do
local user = uci:get_all("passwall2_server", v) or {}
if user[".type"] == "user" then
local u = {}
if node.protocol == "mixed" or node.protocol == "socks" or node.protocol == "http" or node.protocol == "naive" then
u.username = user.username
u.password = user.password
end
if node.protocol == "shadowsocks" or node.protocol == "trojan" then
u.name = user.username
u.password = user.password
end
if node.protocol == "vmess" then
u.name = user.username
u.uuid = user.uuid
u.alterId = 0
end
if node.protocol == "vless" then
u.name = user.username
u.uuid = user.uuid
u.flow = node.flow
end
if node.protocol == "hysteria" then
u.name = user.username
u.auth_str = user.password
end
if node.protocol == "tuic" then
u.name = user.username
u.password = user.password
u.uuid = user.uuid
end
if node.protocol == "hysteria2" then
u.name = user.username
u.password = user.password
end
users[#users + 1] = u
end
end
if #users == 0 then
users = nil
end
end
local protocol_table = nil
if node.protocol == "mixed" then
protocol_table = {
users = (node.auth == "1") and {
{
username = node.username,
password = node.password
}
} or nil,
users = users,
set_system_proxy = false
}
end
if node.protocol == "socks" then
protocol_table = {
users = (node.auth == "1") and {
{
username = node.username,
password = node.password
}
} or nil
users = users
}
end
if node.protocol == "http" then
protocol_table = {
users = (node.auth == "1") and {
{
username = node.username,
password = node.password
}
} or nil,
users = users,
tls = (node.tls == "1") and tls or nil,
}
end
@@ -806,21 +838,14 @@ function gen_config_server(node)
if node.protocol == "shadowsocks" then
protocol_table = {
method = node.method,
password = node.password,
password = node.ss_password,
users = users,
multiplex = mux,
}
end
if node.protocol == "vmess" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
alterId = 0,
}
end
if users then
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
@@ -831,15 +856,7 @@ function gen_config_server(node)
end
if node.protocol == "vless" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
flow = node.flow,
}
end
if users then
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
@@ -850,14 +867,7 @@ function gen_config_server(node)
end
if node.protocol == "trojan" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
password = node.uuid[i],
}
end
if users then
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
@@ -870,15 +880,12 @@ function gen_config_server(node)
end
if node.protocol == "naive" then
protocol_table = {
users = {
{
username = node.username,
password = node.password
}
},
tls = tls,
}
if users then
protocol_table = {
users = users,
tls = tls
}
end
end
if node.protocol == "hysteria" then
@@ -888,13 +895,7 @@ function gen_config_server(node)
up_mbps = tonumber(node.hysteria_up_mbps),
down_mbps = tonumber(node.hysteria_down_mbps),
obfs = node.hysteria_obfs,
users = {
{
name = "user1",
auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
}
},
users = users,
recv_window_conn = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil, --1.14 to stream_receive_window
recv_window_client = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil, --1.14 to connection_receive_window
max_conn_client = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil, --1.14 to max_concurrent_streams
@@ -904,31 +905,21 @@ function gen_config_server(node)
end
if node.protocol == "tuic" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
password = node.password
}
end
tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function()
local alpn = {}
string.gsub(node.tuic_alpn, '[^,]+', function(w)
table.insert(alpn, w)
end)
if #alpn > 0 then return alpn end
return nil
end)() or nil
protocol_table = {
users = users,
congestion_control = node.tuic_congestion_control or "cubic",
zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
tls = tls
}
end
tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function()
local alpn = {}
string.gsub(node.tuic_alpn, '[^,]+', function(w)
table.insert(alpn, w)
end)
if #alpn > 0 then return alpn end
return nil
end)() or nil
protocol_table = {
users = users,
congestion_control = node.tuic_congestion_control or "cubic",
zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
tls = tls
}
end
if node.protocol == "hysteria2" then
@@ -953,12 +944,7 @@ function gen_config_server(node)
end
return o
end)(node.hysteria2_obfs_type),
users = {
{
name = "user1",
password = node.hysteria2_auth_password or nil,
}
},
users = users,
ignore_client_bandwidth = (node.hysteria2_ignore_client_bandwidth == "1") and true or false,
tls = tls,
realm = node.hysteria2_realms and (function()
@@ -978,15 +964,12 @@ function gen_config_server(node)
end
if node.protocol == "anytls" then
protocol_table = {
users = {
{
name = (node.username and node.username ~= "") and node.username or "sekai",
password = node.password
}
},
tls = tls,
}
if users then
protocol_table = {
users = users,
tls = tls,
}
end
end
if node.protocol == "direct" then
+47 -35
View File
@@ -488,17 +488,46 @@ function gen_config_server(node)
{ protocol = "freedom", tag = "direct", settings = { finalRules = {{ action = "allow" }}}}, { protocol = "blackhole", tag = "blocked" }
}
if node.protocol == "vmess" or node.protocol == "vless" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
id = node.uuid[i],
flow = (node.protocol == "vless"
and (node.tls == "1" or (node.decryption and node.decryption ~= "" and node.decryption ~= "none"))
and node.flow and node.flow ~= "") and node.flow or nil
}
local users = node.users or {}
local users = nil
if node.users and #node.users > 0 then
users = {}
for i, v in ipairs(node.users) do
local user = uci:get_all("passwall2_server", v) or {}
if user[".type"] == "user" then
local u = {}
if node.protocol == "socks" or node.protocol == "http" then
u.user = user.username
u.pass = user.password
end
if node.protocol == "shadowsocks" or node.protocol == "trojan" then
u.email = user.username
u.password = user.password
end
if node.protocol == "vmess" then
u.email = user.username
u.id = user.uuid
u.alterId = 0
end
if node.protocol == "vless" then
u.email = user.username
u.id = user.uuid
u.flow = node.flow
end
if node.protocol == "hysteria2" then
u.email = user.username
u.auth = user.password
end
users[#users + 1] = u
end
end
if #users == 0 then
users = nil
end
end
if node.protocol == "vmess" or node.protocol == "vless" then
if users then
settings = {
users = users,
decryption = (node.protocol == "vless") and ((node.decryption and node.decryption ~= "") and node.decryption or "none") or nil
@@ -507,40 +536,25 @@ function gen_config_server(node)
elseif node.protocol == "socks" then
settings = {
udp = ("1" == node.udp_forward) and true or false,
auth = ("1" == node.auth) and "password" or "noauth",
users = ("1" == node.auth) and {
{
user = node.username,
pass = node.password
}
} or nil
auth = users and "password" or "noauth",
users = users
}
elseif node.protocol == "http" then
settings = {
allowTransparent = false,
users = ("1" == node.auth) and {
{
user = node.username,
pass = node.password
}
} or nil
users = users
}
node.transport = "tcp"
node.tcp_guise = "none"
elseif node.protocol == "shadowsocks" then
settings = {
method = node.method,
password = node.password,
network = node.ss_network or "TCP,UDP"
password = node.ss_password,
users = users,
network = node.ss_network or "tcp,udp"
}
elseif node.protocol == "trojan" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
password = node.uuid[i],
}
end
if users then
settings = {
users = users
}
@@ -548,9 +562,7 @@ function gen_config_server(node)
elseif node.protocol == "hysteria2" then
settings = {
version = 2,
users = node.hysteria2_auth_password and {
{ auth = node.hysteria2_auth_password }
}
users = users
}
elseif node.protocol == "dokodemo-door" then
settings = {
@@ -1,6 +1,7 @@
<%
local api = require "luci.passwall.api"
local api = self.api
local appname = api.appname
local map = self.map
local is_js_luci = api.is_js_luci()
local section_tag = is_js_luci and "div" or "fieldset"
-%>
@@ -500,15 +501,7 @@ table td, .table .td {
let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)");
if (firstDataRow && firstDataRow !== row) {
parent.insertBefore(row, firstDataRow);
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_node_order(group);
if (!arraysEqual(new_order, origin_group_node_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
set_order(group);
}
break;
case "del_all":
@@ -652,48 +645,24 @@ table td, .table .td {
return ids;
}
function save_current_page_order(group) {
var table = document.getElementById("cbi-<%=appname%>-nodes-" + group + "-table");
function set_order(group) {
let table = document.getElementById("cbi-<%=appname%>-nodes-" + group + "-table");
if (!table) {
alert("<%:No table!%>");
return;
}
var rows = table.querySelectorAll("tr.cbi-section-table-row");
let rows = table.querySelectorAll("tr.cbi-section-table-row");
if (!rows || rows.length === 0) {
alert("<%:No nodes!%>");
return;
}
var btn = document.getElementById("save_order_btn_" + group);
if (btn) {
btn.style.display = "none";
btn.disabled = true;
}
var ids = [];
let ids = [];
rows.forEach(function(row) {
var id = row.id.replace("cbi-<%=appname%>-", "");
let id = row.id.replace("cbi-<%=appname%>-", "");
ids.push(id);
});
ajax.abortAll();
XHR.get('<%=api.url("save_node_order")%>', {
group: group,
ids: ids.join(",")
},
function(x, result) {
if (btn) {
btn.style.display = null;
btn.disabled = false;
}
if (x && x.status === 200) {
origin_group_node_order[group] = get_node_order(group);
alert("<%:Saved current page order successfully.%>");
if (btn) {
btn.style.display = "none";
}
} else {
alert("<%:Save failed!%>");
}
}
);
const input_order = document.getElementsByName(`group.order.${group}`)[0];
input_order.value = ids.join(" ");
}
//获取当前使用的节点
@@ -947,16 +916,7 @@ table td, .table .td {
forceFallback: false,
swapThreshold: 0.65,
onEnd: function (evt) {
//save_current_page_order(group); // 自动提交保存
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_node_order(group);
if (!arraysEqual(new_order, origin_group_node_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
set_order(group);
}
};
try {
@@ -1302,9 +1262,9 @@ table td, .table .td {
</tr>
{{node-tr}}
</table>
<input type="hidden" name="group.order.{{group}}">
<div class="cbi-section-create cbi-tblsection-create">
<input class="cbi-button cbi-button-add" type="button" value="<%:Add%>" onclick="node_action('add')">
<input class="cbi-button cbi-button-apply" style="display: none" type="button" id="save_order_btn_{{group}}" value="<%:Save Order%>" onclick="save_current_page_order('{{group}}')">
</div>
</<%=section_tag%>>
</script>
@@ -1,7 +1,6 @@
<%
local api = require "luci.passwall.api"
local api = self.api
local appname = api.appname
local uci = api.uci
local is_js_luci = api.is_js_luci()
local section_tag = is_js_luci and "div" or "fieldset"
local title_tag = is_js_luci and "h3" or "legend"
@@ -185,15 +184,7 @@ table td, .table .td {
let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)");
if (firstDataRow && firstDataRow !== row) {
parent.insertBefore(row, firstDataRow);
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_shunt_rule_order(group);
if (!arraysEqual(new_order, origin_group_shunt_rule_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
set_order(group);
}
}
@@ -214,48 +205,24 @@ table td, .table .td {
return ids;
}
function save_current_page_order(group) {
var table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table");
function set_order(group) {
let table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table");
if (!table) {
alert("<%:No table!%>");
return;
}
var rows = table.querySelectorAll("tr.cbi-section-table-row");
let rows = table.querySelectorAll("tr.cbi-section-table-row");
if (!rows || rows.length === 0) {
alert("<%:No shunt_rules!%>");
return;
}
var btn = document.getElementById("save_order_btn_" + group);
if (btn) {
btn.style.display = "none";
btn.disabled = true;
}
var ids = [];
let ids = [];
rows.forEach(function(row) {
var id = row.id.replace("cbi-<%=appname%>-", "");
let id = row.id.replace("cbi-<%=appname%>-", "");
ids.push(id);
});
XHR.get('<%=api.url("save_shunt_rule_order")%>', {
group: group,
ids: ids.join(",")
},
function(x, result) {
if (btn) {
btn.style.display = null;
btn.disabled = false;
}
if (x && x.status === 200) {
// origin_group_shunt_rule_order[group] = get_shunt_rule_order(group);
alert("<%:Saved current page order successfully.%>");
// if (btn) {
// btn.style.display = "none";
// }
window.location.reload();
} else {
alert("<%:Save failed!%>");
}
}
);
const input_order = document.getElementsByName(`group.order.${group}`)[0];
input_order.value = ids.join(" ");
}
// List drag and rearrange
@@ -274,16 +241,7 @@ table td, .table .td {
forceFallback: false,
swapThreshold: 0.65,
onEnd: function (evt) {
//save_current_page_order(group); // Auto save
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_shunt_rule_order(group);
if (!arraysEqual(new_order, origin_group_shunt_rule_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
set_order(group);
}
};
try {
@@ -339,10 +297,10 @@ table td, .table .td {
</tr>
{{shunt_rule-tr}}
</table>
<input type="hidden" name="group.order.{{group}}">
<div class="cbi-section-create cbi-tblsection-create">
<input type="text" id="add_name_{{group}}">
<input class="cbi-button cbi-button-add" type="button" value="<%:Add%>" onclick="toPage('add')">
<input class="cbi-button cbi-button-apply" style="display: none" type="button" id="save_order_btn_{{group}}" value="<%:Save Order%>" onclick="save_current_page_order('{{group}}')">
</div>
</<%=section_tag%>>
</script>
@@ -4,7 +4,7 @@ local api = self.api
<script type="text/javascript">
//<![CDATA[
let node_id = "<%=self.section%>";
let node_config_url = '<%=api.url("server_user")%>/' + node_id;
let node_config_url = '<%=api.url("server_config")%>/' + node_id;
document.addEventListener("DOMContentLoaded", function () {
waitForElement('select[name*="<%=self.config%>"][name*="type"]', function(el) {
@@ -0,0 +1,38 @@
<%
local api = self.api
-%>
<script type="text/javascript">
//<![CDATA[
var _servers_status = document.getElementsByClassName('_servers_status');
for(var i = 0; i < _servers_status.length; i++) {
var id = _servers_status[i].parentElement.parentElement.parentElement.id;
id = id.substr(id.lastIndexOf("-") + 1);
XHR.get('<%=api.url("server_status")%>', {
index: i,
id: id
},
function(x, result) {
_servers_status[result.index].setAttribute("style","font-weight:bold;");
_servers_status[result.index].setAttribute("color",result.status ? "green":"red");
_servers_status[result.index].innerHTML = (result.status ? '✓' : 'X');
}
);
}
var edit_btn = document.getElementById("cbi-<%=self.appname%>-<%=self.sectiontype%>").getElementsByClassName("cbi-button cbi-button-edit");
for (var i = 0; i < edit_btn.length; i++) {
try {
var onclick_str = edit_btn[i].getAttribute("onclick");
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
var td = edit_btn[i].parentNode;
var new_div = "";
// Add `log` button
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Log%>" onclick="window.open(\'' + '<%=api.url("server_log")%>' + '?id=' + id + '\', \'_blank\')"/>&nbsp;&nbsp;';
td.innerHTML = new_div + td.innerHTML;
}
catch(err) {
console.error(err);
}
}
//]]>
</script>
@@ -1,38 +0,0 @@
<%
local api = require "luci.passwall.api"
-%>
<script type="text/javascript">
//<![CDATA[
var _users_status = document.getElementsByClassName('_users_status');
for(var i = 0; i < _users_status.length; i++) {
var id = _users_status[i].parentElement.parentElement.parentElement.id;
id = id.substr(id.lastIndexOf("-") + 1);
XHR.get('<%=api.url("server_user_status")%>', {
index: i,
id: id
},
function(x, result) {
_users_status[result.index].setAttribute("style","font-weight:bold;");
_users_status[result.index].setAttribute("color",result.status ? "green":"red");
_users_status[result.index].innerHTML = (result.status ? '✓' : 'X');
}
);
}
var edit_btn = document.getElementById("cbi-passwall_server-user").getElementsByClassName("cbi-button cbi-button-edit");
for (var i = 0; i < edit_btn.length; i++) {
try {
var onclick_str = edit_btn[i].getAttribute("onclick");
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
var td = edit_btn[i].parentNode;
var new_div = "";
//添加"日志"按钮
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Log%>" onclick="window.open(\'' + '<%=api.url("server_user_log")%>' + '?id=' + id + '\', \'_blank\')"/>&nbsp;&nbsp;';
td.innerHTML = new_div + td.innerHTML;
}
catch(err) {
console.error(err);
}
}
//]]>
</script>
+12 -6
View File
@@ -490,12 +490,6 @@ msgstr "节点备注"
msgid "Add Mode"
msgstr "添加方式"
msgid "Save Order"
msgstr "保存当前顺序"
msgid "Saved current page order successfully."
msgstr "保存当前页面顺序成功。"
msgid "Drag to reorder"
msgstr "拖动以重排"
@@ -1670,9 +1664,15 @@ msgstr "服务器端"
msgid "Server Config"
msgstr "服务器配置"
msgid "Servers Manager"
msgstr "服务器管理"
msgid "Users Manager"
msgstr "用户管理"
msgid "User Config"
msgstr "用户配置"
msgid "Logs"
msgstr "日志"
@@ -2109,9 +2109,15 @@ msgstr "使用自定义配置"
msgid "Custom Config"
msgstr "自定义配置"
msgid "Must be Base64 text!"
msgstr "必须是 Base64 内容!"
msgid "Must be JSON text!"
msgstr "必须是 JSON 文本内容!"
msgid "Must be UUID format!"
msgstr "必须是 UUID 格式!"
msgid "Geo View"
msgstr "Geo 查询"