diff --git a/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js b/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js index 95664e5a..4c0dfa2b 100644 --- a/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js +++ b/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js @@ -151,6 +151,16 @@ return L.view.extend({ o.value(0, _("In-kernel path manager")); o.value(1, _("Userspace path manager")); o.default = 0; + // LuCI's CBI removes an option instead of writing it whenever the + // submitted value equals .default and rmempty is left at its class + // default of true (see issue #4348 for the same pattern). That would + // silently delete network.globals.mptcp_pm_type on every Save & Apply + // of this page (not just when this field itself is touched), and the + // init script's own fallback for an absent value is "1" (userspace + // path manager) -- the opposite of this field's default -- which + // disables in-kernel fullmesh subflow creation entirely and breaks + // multi-WAN bonding down to the master WAN only (issue #4349). + o.rmempty = false; o = s.option(form.ListValue, "mptcp_disable_initial_config", _("Initial MPTCP configuration")); o.depends("mptcp_pm_type","1"); @@ -283,6 +293,13 @@ return L.view.extend({ o.value("backup", _("backup")); //o.value("handover", _("handover")); o.default = "off"; + // Same rmempty/default collision as mptcp_pm_type above: without this, + // every interface currently set to "off" has network..multipath + // silently deleted on each Save & Apply of this page. Currently harmless + // (the init script's own fallback for an absent value is also "off"), + // but keep the persisted value explicit so a future fallback change + // can't quietly flip behavior for these interfaces too. + o.rmempty = false; o = s.option(form.Value, "multipath_weight", _("Weight"), _("Only used by *weight schedulers/path managers. Ignored if no weight scheduler is selected.") + '
' + _("A weight >100 make it more attractive, a weight <100 make it less attractive. Max 256")); o.datatype = "uinteger"; diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index fa82c4cc..b1a76c9e 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 PKG_VERSION:=26.8.10 -PKG_RELEASE:=78 +PKG_RELEASE:=79 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/cbi.js b/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/cbi.js index 3f9d6470..61224808 100644 --- a/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/cbi.js +++ b/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/cbi.js @@ -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!')); + } }); } diff --git a/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/func.js b/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/func.js index e7923077..8c9e27c8 100644 --- a/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/func.js +++ b/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/func.js @@ -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); diff --git a/luci-app-passwall2/luasrc/controller/passwall2.lua b/luci-app-passwall2/luasrc/controller/passwall2.lua index 8659f5ad..6e9d2c71 100644 --- a/luci-app-passwall2/luasrc/controller/passwall2.lua +++ b/luci-app-passwall2/luasrc/controller/passwall2.lua @@ -56,12 +56,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 @@ -88,7 +89,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 @@ -100,7 +100,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 --[[Components update]] entry({"admin", "services", appname, "check_passwall2"}, call("app_check")).leaf = true @@ -649,19 +648,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" @@ -725,14 +711,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 nixio.fs.access("/tmp/etc/passwall2_server/" .. id .. ".log") then local content = luci.sys.exec("cat /tmp/etc/passwall2_server/" .. id .. ".log") @@ -1059,7 +1045,7 @@ function add_shunt_rule() if add_name then local has = uci:get(appname, uid) if has then - http_write_json_error({ message = "This ID already exists." }) + http_write_json_error({ message = i18n.translate("This ID already exists.") }) return end else @@ -1099,16 +1085,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 diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua index ba3539e8..fed50b6e 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua @@ -1,7 +1,5 @@ local api = require "luci.passwall2.api" local appname = api.appname -local datatypes = api.datatypes -local sys = api.sys 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) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua index d89efb60..6db355fb 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua @@ -88,6 +88,27 @@ end s:append(Template(appname .. "/rule/rule_version")) -m:append(Template(appname .. "/rule/shunt_rule_list")) +if true then + 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) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua index ef9ba277..cdb4cc9c 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua @@ -13,38 +13,38 @@ t.addremove = false e = t:option(Flag, "enable", translate("Enable")) 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('%s', translate("Collecting data...")) + return string.format('%s', 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) @@ -82,20 +82,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) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/user.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/server_config.lua similarity index 90% rename from luci-app-passwall2/luasrc/model/cbi/passwall2/server/user.lua rename to luci-app-passwall2/luasrc/model/cbi/passwall2/server/server_config.lua index b3da0a04..8e5824a7 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/user.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/server_config.lua @@ -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 diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/hysteria2.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/hysteria2.lua index 5473fc30..998682af 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/hysteria2.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/hysteria2.lua @@ -26,6 +26,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 @@ -46,11 +52,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") diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua index 7fa58696..435bb26e 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua @@ -48,27 +48,17 @@ 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 +87,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 +102,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 +126,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") diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua index d0f01773..d3066bbc 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua @@ -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 = "" diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ss-rust.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ss-rust.lua index a6bca4f9..d94c237e 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ss-rust.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ss-rust.lua @@ -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")) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ssr.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ssr.lua index d77cf74c..d1db102f 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ssr.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ssr.lua @@ -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")) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/user_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/user_config.lua new file mode 100644 index 00000000..1cbb1ad7 --- /dev/null +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/user_config.lua @@ -0,0 +1,31 @@ +api = require "luci.passwall2.api" +appname = api.appname +fs = api.fs + +api.set_default_cbi() + +m = Map("passwall2_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) diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua index 804fd092..9d1853fa 100644 --- a/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/luci-app-passwall2/luasrc/passwall2/api.lua @@ -388,6 +388,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 diff --git a/luci-app-passwall2/luasrc/passwall2/server_app.lua b/luci-app-passwall2/luasrc/passwall2/server_app.lua index 360305ff..7b28da0a 100644 --- a/luci-app-passwall2/luasrc/passwall2/server_app.lua +++ b/luci-app-passwall2/luasrc/passwall2/server_app.lua @@ -102,78 +102,78 @@ local function start() nft_file:write('flush chain inet fw4 PSW2-SERVER\n') nft_file:write('insert rule inet fw4 input position 0 jump PSW2-SERVER comment "PSW2-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 == "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 @@ -191,7 +191,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 PSW2-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) diff --git a/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua b/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua index de2a1f2d..b145fbb3 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua @@ -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 diff --git a/luci-app-passwall2/luasrc/passwall2/util_shadowsocks.lua b/luci-app-passwall2/luasrc/passwall2/util_shadowsocks.lua index f4645138..77b51795 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_shadowsocks.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_shadowsocks.lua @@ -4,9 +4,14 @@ 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 diff --git a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua index 72c17551..cabd7696 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua @@ -789,39 +789,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 @@ -829,21 +861,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, @@ -854,15 +879,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, @@ -873,14 +890,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, @@ -893,15 +903,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 @@ -911,13 +918,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 @@ -936,13 +937,7 @@ function gen_config_server(node) return nil end)() or nil protocol_table = { - users = { - { - name = "user1", - uuid = node.uuid, - password = node.password - } - }, + 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", @@ -972,12 +967,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() @@ -997,15 +987,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 diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index 1df0c1a8..47ac9e12 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -496,17 +496,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 @@ -515,40 +544,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 } @@ -556,9 +570,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 = { diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm b/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm index d2d42741..4d9f7073 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm @@ -1,7 +1,7 @@ <% -local api = require "luci.passwall2.api" +local api = self.api local appname = api.appname -local uci = api.uci +local map = self.map local default_node_type = "" local shunt_rule_list = {} @@ -12,7 +12,7 @@ if node then if (node_type == "Xray" or node_type == "sing-box") and node_protocol == "_shunt" then default_node_type = node_protocol local node_shunt_group = api.uci_get_type_id(node, "shunt_group") - uci:foreach(appname, "shunt_rules", function(e) + map.uci:foreach(appname, "shunt_rules", function(e) if e[".name"] and e.remarks and e.group == node_shunt_group then shunt_rule_list[#shunt_rule_list + 1] = e end @@ -517,15 +517,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": @@ -669,48 +661,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(" "); } function get_now_use_node() { @@ -947,16 +915,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_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 +1261,9 @@ table td, .table .td { {{node-tr}} +
-
diff --git a/luci-app-passwall2/luasrc/view/passwall2/rule/shunt_rule_list.htm b/luci-app-passwall2/luasrc/view/passwall2/rule/shunt_rule_list.htm index 6e5a6559..da1cf155 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/rule/shunt_rule_list.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/rule/shunt_rule_list.htm @@ -1,10 +1,8 @@ <% -local api = require "luci.passwall2.api" +local api = self.api local appname = api.appname -local uci = api.uci -%> -