diff --git a/dae/Makefile b/dae/Makefile
index f60d1bd4..f725cb28 100644
--- a/dae/Makefile
+++ b/dae/Makefile
@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=dae
-PKG_VERSION:=2026.08.06
+PKG_VERSION:=2026.08.08
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_PROTO:=git
-PKG_SOURCE_VERSION:=c7547891533d195731507977cb36734a5a18aa14
+PKG_SOURCE_VERSION:=f52534a912a2f0a1406740384684667c1ece89c0
PKG_SOURCE_URL:=https://github.com/olicesx/dae.git
PKG_MIRROR_HASH:=skip
diff --git a/ddns-go/Makefile b/ddns-go/Makefile
index 03ad6864..6a0b7b61 100644
--- a/ddns-go/Makefile
+++ b/ddns-go/Makefile
@@ -8,12 +8,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ddns-go
-PKG_VERSION:=6.17.4
+PKG_VERSION:=6.17.5
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)?
-PKG_HASH:=80f3816d95933949ce7306b988b2612d91b95c6992af8e956378628fde19b615
+PKG_HASH:=910ba8e64d05fd48043f4c88492f32f459367be022ec08f0aa6e977c5c3419bf
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE
diff --git a/luci-app-passwall/htdocs/luci-static/resources/view/passwall/func.js b/luci-app-passwall/htdocs/luci-static/resources/view/passwall/func.js
new file mode 100644
index 00000000..252e8feb
--- /dev/null
+++ b/luci-app-passwall/htdocs/luci-static/resources/view/passwall/func.js
@@ -0,0 +1,45 @@
+function arraysEqual(a, b) {
+ if (a === b) return true;
+ if (a == null || b == null) return false;
+ if (a.length !== b.length) return false;
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) return false;
+ }
+ return true;
+}
+
+function waitForElement(selector, callback) {
+ const el = document.querySelector(selector);
+ if (el) return callback(el);
+ const observer = new MutationObserver(() => {
+ const el = document.querySelector(selector);
+ if (el) {
+ observer.disconnect();
+ callback(el);
+ }
+ });
+ observer.observe(document.body, { childList: true, subtree: true });
+}
+
+function get_current_url() {
+ return window.location.origin + window.location.pathname;
+}
+
+function getOption(config, section, opt) {
+ let obj;
+ const id = `cbid.${config}.${section}.${opt}`;
+ obj = document.getElementsByName(id)[0] || document.getElementById(id);
+ if (obj) {
+ const combobox = document.getElementById('cbi.combobox.' + id);
+ if (combobox) {
+ obj.combobox = combobox;
+ }
+ const div = document.getElementById(id);
+ if (div && div.getElementsByTagName("li").length > 0) {
+ obj = div;
+ }
+ return obj;
+ } else {
+ return null;
+ }
+}
diff --git a/luci-app-passwall/luasrc/controller/passwall.lua b/luci-app-passwall/luasrc/controller/passwall.lua
index e30a61f2..f26db0ab 100644
--- a/luci-app-passwall/luasrc/controller/passwall.lua
+++ b/luci-app-passwall/luasrc/controller/passwall.lua
@@ -79,8 +79,8 @@ function index()
entry({"admin", "services", appname, "connect_status"}, call("connect_status")).leaf = true
entry({"admin", "services", appname, "ping_node"}, call("ping_node")).leaf = true
entry({"admin", "services", appname, "urltest_node"}, call("urltest_node")).leaf = true
+ entry({"admin", "services", appname, "update_config"}, call("update_config")).leaf = true
entry({"admin", "services", appname, "add_node"}, call("add_node")).leaf = true
- entry({"admin", "services", appname, "update_node"}, call("update_node")).leaf = true
entry({"admin", "services", appname, "set_node"}, call("set_node")).leaf = true
entry({"admin", "services", appname, "copy_node"}, call("copy_node")).leaf = true
entry({"admin", "services", appname, "clear_all_nodes"}, call("clear_all_nodes")).leaf = true
@@ -96,6 +96,10 @@ function index()
entry({"admin", "services", appname, "subscribe_manual"}, call("subscribe_manual")).leaf = true
entry({"admin", "services", appname, "subscribe_manual_all"}, call("subscribe_manual_all")).leaf = true
entry({"admin", "services", appname, "flush_set"}, call("flush_set")).leaf = true
+ 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
@@ -471,6 +475,23 @@ function urltest_node()
http_write_json(e)
end
+function update_config()
+ local id = http.formvalue("id") -- Node id
+ local data = http.formvalue("data") -- json new Data
+ if id and data then
+ local data_t = jsonParse(data) or {}
+ if next(data_t) then
+ for k, v in pairs(data_t) do
+ uci:set(appname, id, k, v)
+ end
+ api.uci_save(uci, appname)
+ http_write_json_ok()
+ return
+ end
+ end
+ http_write_json_error()
+end
+
function add_node()
local redirect = http.formvalue("redirect")
@@ -493,23 +514,6 @@ function add_node()
end
end
-function update_node()
- local id = http.formvalue("id") -- Node id
- local data = http.formvalue("data") -- json new Data
- if id and data then
- local data_t = jsonParse(data) or {}
- if next(data_t) then
- for k, v in pairs(data_t) do
- uci:set(appname, id, k, v)
- end
- api.uci_save(uci, appname)
- http_write_json_ok()
- return
- end
- end
- http_write_json_error()
-end
-
function set_node()
local protocol = http.formvalue("protocol")
local section = http.formvalue("section")
@@ -1134,3 +1138,87 @@ function fetch_certsha256()
local data = api.fetch_cert_sha256(address, port, sni, timeout, h3)
http_write_json(data ~= "" and { code = 1, data = data } or { code = 0 })
end
+
+function get_shunt_rules()
+ local id = http.formvalue("id")
+ local result = {}
+
+ if id then
+ result = uci:get_all(appname, id)
+ else
+ local default_items = {}
+ local other_items = {}
+ uci:foreach(appname, "shunt_rules", function(t)
+ if not t.group or t.group == "" then
+ default_items[#default_items + 1] = t
+ else
+ other_items[#other_items + 1] = t
+ end
+ end)
+ for i = 1, #default_items do result[#result + 1] = default_items[i] end
+ for i = 1, #other_items do result[#result + 1] = other_items[i] end
+ end
+ http_write_json(result)
+end
+
+function add_shunt_rule()
+ local add_name = http.formvalue("add_name")
+ local redirect = http.formvalue("redirect")
+
+ local uuid = add_name
+ if add_name then
+ local has = uci:get(appname, uuid)
+ if has then
+ http_write_json_error({ message = "This ID already exists." })
+ return
+ end
+ else
+ uuid = api.gen_short_uuid()
+ end
+ uci:section(appname, "shunt_rules", uuid)
+
+ local group = http.formvalue("group")
+ if group and group ~= "default" then
+ uci:set(appname, uuid, "group", group)
+ end
+
+ if redirect == "1" then
+ api.uci_save(uci, appname)
+ http.redirect(api.url("shunt_rules", uuid))
+ else
+ api.uci_save(uci, appname)
+ http_write_json_ok({uuid = uuid, redirect_url = api.url("shunt_rules", uuid)})
+ end
+end
+
+function delete_select_shunt_rules()
+ local ids = http.formvalue("ids")
+ local redirect = http.formvalue("redirect")
+ string.gsub(ids, '[^' .. "," .. ']+', function(w)
+ uci:foreach(appname, "nodes", function(s)
+ if s["protocol"] and s["protocol"] == "_shunt" then
+ uci:delete(appname, s[".name"], w)
+ end
+ end)
+ uci:delete(appname, w)
+ end)
+ if redirect == "1" then
+ api.uci_save(uci, appname)
+ http.redirect(api.url("rule"))
+ else
+ 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-passwall/luasrc/model/cbi/passwall/client/global.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
index 003e9fbc..85969ad8 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
@@ -154,6 +154,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
if current_node.protocol == "_shunt" then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s, {
+ s_cfgid = s:cfgsections()[1],
node_id = current_node_id,
node = current_node,
socks_list = socks_list,
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua
index 7711808d..4867e92f 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua
@@ -4,9 +4,17 @@ if not data.node_id or not data.node then
return
end
+local s_cfgid = data.s_cfgid
local current_node_id = data.node_id
local node_list = data.node_list or api.get_node_list()
+local groups = {}
+m.uci:foreach(appname, "shunt_rules", function(s)
+ if s.group and s.group ~= "" then
+ groups[s.group] = true
+ end
+end)
+
local function get_cfgvalue()
return function(self, section)
return m:get(current_node_id, self.option)
@@ -92,15 +100,27 @@ o = add_option(Flag, "fakedns", 'FakeDNS' .. " " ..
translate("Suitable scenarios for let the node servers get the target domain names.") .. "
" ..
translate("Such as: DNS unlocking of streaming media, reducing DNS query latency, etc."))
+shunt_group = add_option(ListValue, "shunt_group", translate("Shunt Rule Group"))
+shunt_group:value("", translate("default"))
+for k, v in pairs(groups) do
+ shunt_group:value(k)
+end
+
+local shunt_group_val = m:get(current_node_id, "shunt_group") or ""
+shunt_group_val = shunt_group_val:lower()
local shunt_rules = {}
m.uci:foreach(appname, "shunt_rules", function(e)
- e.id = e[".name"]
- e.remarks = e.remarks or e[".name"]
- e["_node_option"] = e[".name"]
- e["_node_default"] = ""
- e["_fakedns_option"] = e[".name"] .. "_fakedns"
- e["_proxy_tag_option"] = e[".name"] .. "_proxy_tag"
- table.insert(shunt_rules, e)
+ local group = e.group or ""
+ group = group:lower()
+ if group == shunt_group_val then
+ e.id = e[".name"]
+ e.remarks = e.remarks or e[".name"]
+ e["_node_option"] = e[".name"]
+ e["_node_default"] = ""
+ e["_fakedns_option"] = e[".name"] .. "_fakedns"
+ e["_proxy_tag_option"] = e[".name"] .. "_proxy_tag"
+ table.insert(shunt_rules, e)
+ end
end)
table.insert(shunt_rules, {
id = ".default",
@@ -185,6 +205,8 @@ end
local footer = Template(appname .. "/include/shunt_options")
footer.api = api
+footer.config = m.config
footer.id = current_node_id
+footer.s_cfgid = s_cfgid or current_node_id
footer.normal_list = api.jsonc.stringify(node_list.normal_list)
m:append(footer)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
index b0b84851..adad962d 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
@@ -145,34 +145,8 @@ end
s:append(Template(appname .. "/rule/rule_version"))
-local cfgname = "shunt_rules"
-
if has_xray or has_singbox then
- s = m:section(TypedSection, cfgname, "Sing-Box/Xray " .. translate("Shunt Rule"), "" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "")
- s.template = "cbi/tblsection"
- s.anonymous = false
- s.addremove = true
- s.sortable = true
- s.extedit = api.url("shunt_rules", "%s")
- function s.create(e, t)
- TypedSection.create(e, t)
- luci.http.redirect(e.extedit:format(t))
- end
- function s.remove(e, t)
- m.uci:foreach(appname, "nodes", function(s)
- if s["protocol"] and s["protocol"] == "_shunt" then
- m:del(s[".name"], t)
- end
- end)
- TypedSection.remove(e, t)
- end
-
- o = s:option(DummyValue, "remarks", translate("Remarks"))
+ m:append(Template(appname .. "/rule/shunt_rule_list"))
end
-local sortable = Template(appname .. "/cbi/sortable")
-sortable.api = api
-sortable.target_cfgname = cfgname
-m:append(sortable)
-
return api.return_map(m)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
index 4b45eba4..8ae6a0e6 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
@@ -44,6 +44,19 @@ function clean_text(text)
:gsub("[ \t]*\n[ \t]*", "\n")
end
+local remarks_lookup = {}
+local groups = {}
+m.uci:foreach(appname, "shunt_rules", function(s)
+ if s[".name"] ~= arg[1] then
+ if s.remarks then
+ remarks_lookup[s.remarks] = s[".name"]
+ end
+ if s.group and s.group ~= "" then
+ groups[s.group] = true
+ end
+ end
+end)
+
s = m:section(NamedSection, arg[1], "shunt_rules", "")
s.addremove = false
s.dynamic = false
@@ -56,9 +69,34 @@ remarks.validate = function(self, value, section)
if value == "" then
return nil, translate("Remark cannot be empty.")
end
+ if remarks_lookup[value] then
+ return nil, translate("This remark already exists, please change a new remark.")
+ end
return value
end
+o = s:option(Value, "group", translate("Shunt Rule Group"))
+o.default = ""
+o:value("", translate("default"))
+for k, v in pairs(groups) do
+ o:value(k)
+end
+o.write = function(self, section, value)
+ value = api.trim(value)
+ local lower = value:lower()
+
+ if lower == "" or lower == "default" then
+ return m:del(section, self.option)
+ end
+
+ for _, v in ipairs(self.keylist or {}) do
+ if v:lower() == lower then
+ return m:set(section, self.option, v)
+ end
+ end
+ m:set(section, self.option, value)
+end
+
protocol = s:option(MultiValue, "protocol", translate("Protocol"))
protocol:value("http")
protocol:value("tls")
diff --git a/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/luci-app-passwall/luasrc/passwall/util_sing-box.lua
index be04bf81..4200fec4 100644
--- a/luci-app-passwall/luasrc/passwall/util_sing-box.lua
+++ b/luci-app-passwall/luasrc/passwall/util_sing-box.lua
@@ -1594,11 +1594,15 @@ function gen_config(var)
[".name"] = "GFW_Mode_List",
remarks = "GFW_Mode_List",
domain_list = (domain_list ~= "") and domain_list or nil,
- ip_list = (ip_list ~= "") and ip_list or nil
+ ip_list = (ip_list ~= "") and ip_list or nil,
+ group = node["shunt_group"]
})
end
end
foreach_shunt_rule(function(e)
+ if node["shunt_group"] ~= e.group then
+ return
+ end
local outboundTag = gen_shunt_node(e[".name"])
if outboundTag and e.remarks then
if outboundTag == "default" then
diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua
index bb93f10b..5e861f08 100644
--- a/luci-app-passwall/luasrc/passwall/util_xray.lua
+++ b/luci-app-passwall/luasrc/passwall/util_xray.lua
@@ -1405,11 +1405,15 @@ function gen_config(var)
[".name"] = "GFW_Mode_List",
remarks = "GFW_Mode_List",
domain_list = (domain_list ~= "") and domain_list or nil,
- ip_list = (ip_list ~= "") and ip_list or nil
+ ip_list = (ip_list ~= "") and ip_list or nil,
+ group = node["shunt_group"]
})
end
end
foreach_shunt_rule(function(e)
+ if node["shunt_group"] ~= e.group then
+ return
+ end
local outbound_tag = gen_shunt_node(e[".name"])
if outbound_tag and e.remarks then
if outbound_tag == "default" then
diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/header.htm b/luci-app-passwall/luasrc/view/passwall/cbi/header.htm
index 5d284d90..a2a716bb 100644
--- a/luci-app-passwall/luasrc/view/passwall/cbi/header.htm
+++ b/luci-app-passwall/luasrc/view/passwall/cbi/header.htm
@@ -1,2 +1,24 @@
+
+
+
diff --git a/luci-app-passwall/luasrc/view/passwall/global/footer.htm b/luci-app-passwall/luasrc/view/passwall/global/footer.htm
index 7c7b1b4d..5c7daa9f 100644
--- a/luci-app-passwall/luasrc/view/passwall/global/footer.htm
+++ b/luci-app-passwall/luasrc/view/passwall/global/footer.htm
@@ -256,7 +256,7 @@ local appname = api.appname
let new_val = el.target.value
const new_hasItem = shunt_list.find(element => element.id == new_val);
if (new_hasItem) {
- XHR.get('<%=api.url("update_node")%>', {
+ XHR.get('<%=api.url("update_config")%>', {
id: "<%=self.global_cfgid%>",
data: JSON.stringify({
tcp_node: new_val
diff --git a/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm b/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm
index 7f5e4439..5a5b3608 100644
--- a/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm
+++ b/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm
@@ -53,6 +53,22 @@
}
}
}
+ document.addEventListener("DOMContentLoaded", function () {
+ waitForElement('select[name*="<%=self.config%>"][name*="shunt_group"]', function(el) {
+ let o_val = el.value;
+ el.addEventListener("change", () => {
+ el.blur();
+ if (o_val != el.value) {
+ let save = true;
+ if (save) {
+ update_config("<%=self.id%>", {
+ shunt_group: getOption("<%=self.config%>", "<%=self.s_cfgid%>", "shunt_group").value,
+ }, get_current_url());
+ }
+ }
+ });
+ });
+ });
document.addEventListener("DOMContentLoaded", () => setTimeout(() => {
refresh_depends();
const table_dom = document.getElementById("cbi-passwall-shunt_option_list");
diff --git a/luci-app-passwall/luasrc/view/passwall/node_config/footer.htm b/luci-app-passwall/luasrc/view/passwall/node_config/footer.htm
index 7a16884f..819243f9 100644
--- a/luci-app-passwall/luasrc/view/passwall/node_config/footer.htm
+++ b/luci-app-passwall/luasrc/view/passwall/node_config/footer.htm
@@ -1,8 +1,8 @@
-<%
-local api = self.api
--%>
+
+
+
+<% if api.is_js_luci() then -%>
+
+<%- end %>
+
+
+
+
+
+
+
+
+